Java 5.0 has been released, and many people will begin to use some new features of this JDK version. From an enhanced for loop to more complex features such as generic (generic), it will soon appear in your code. We have just completed a large Java 5.0-based task, and this article will introduce our experience with these new features. This article is not an introductory article, but an in-depth introduction to these features and their impact. It also provides some tips for using these features more effectively in projects.
Introduction
In the beta phase of JDK 1.5, we developed a Java 5 compiler for Bea's Java IDE. Because we have implemented many new features, people start to use them in new ways. Some usage is clever, and some usage should obviously be included in the disabled list. The compiler itself uses new language features, so we also have a direct experience using these features to maintain code. This article describes many of the features and experience of using them.
We assume that you are familiar with these new features, so we will not fully introduce each feature. Instead, we will talk about interesting, but probably less obvious content and usage. These skills come from our practical experience and are categorized by language features.
We will gradually transition from the simplest features to advanced features. Generics are rich in content, so they are half the length of this article.
Enhanced for Loop
To iterate collections and arrays, the enhanced for loop provides a simple and compatible syntax. There are two points worth mentioning:
Init expression
In a loop, the initialization expression is calculated only once. This means that you can usually remove a variable declaration. In this example, we must create an integer array to save the computenumbers () result to prevent this method from being recalculated for each loop. As you can see, the following code is cleaner than the above Code, and the variable numbers is not leaked:
Unreinforced for: int sum = 0; integer [] numbers = computenumbers (); For (INT I = 0; I <numbers. length; I ++) sum + = numbers [I]; enhanced for: int sum = 0; For (INT number: computenumbers () sum + = number;
Limitations
Sometimes you need to access the iterator or subscript during the iteration. It seems that the enhanced for loop should allow this operation, but in fact it is not like this. Please refer to the following example:
for (int i=0; i < numbers.length ; i++) {if (i != 0) System.out.print(",");System.out.print(numbers[i]);}
We want to print the values in the array into a comma-separated list. We need to know whether it is the first item so as to determine whether a comma should be printed. This information cannot be obtained using an enhanced for loop. We need to keep a subscript or a Boolean value to indicate whether the first item has passed. This is another example:
for (Iterator<integer> it = n.iterator() ; it.hasNext() ; )if (it.next() < 0)it.remove();
In this example, we want to delete negative items from the Integer Set. Therefore, we need to call a method for the iterator, but when using an enhanced for loop, the iterator is invisible to us. Therefore, we can only use the iteration method of Java 5 or earlier versions. By the way, it should be noted that because iterator is generic, its declaration is iterator <integer>. Many people forget this and use the original iterator format.
Note
Annotation processing is a big topic. Because this article only focuses on the core language features, we do not intend to cover all its possible forms and traps. We will discuss the built-in annotations (suppresswarnings, deprecated and override) and the limitations of General annotation processing.
Suppress warnings
This annotation disables the class or method-level compiler warning. Sometimes you know more clearly than the compiler that the Code must use a rejected method or execute some actions that cannot be statically determined to be type-safe, and use:
@SuppressWarnings("deprecation")public static void selfDestruct() {Thread.currentThread().stop();}
This may be the most useful part of built-in annotations. Unfortunately, javac 1.5.0 _ 04 does not support it. But 1.6 supports it, and sun is trying to port it back to 1.5.
Eclipse 3.1 supports this annotation, and other ides may also support it. This allows you to completely free code from warnings. If a warning occurs during compilation, you can confirm that you just added it to help you view the code that may be insecure. With the addition of generics, it will be easier to use.
Deprecated
Unfortunately, deprecated is not that useful. It was originally intended to replace the @ deprecated javadoc tag, but because it does not contain any fields, there is no way to recommend what the deprecated class or method users should use as a substitute. In most usage cases, the javadoc tag and the annotation are required at the same time.
Override
Override indicates that the methods annotated by it should override the methods with the same signature in the superclass:
@Overridepublic int hashCode() {...}
Looking at the example above, if "C" is not capitalized in hashcode, there will be no errors during compilation, but this method cannot be called at runtime as expected. By adding the override tag, the compiler will prompt whether it actually executes the rewrite.
This is also helpful when the superclass changes. If a new parameter is added to the method and the method itself is renamed, The subclass cannot be compiled because it no longer overwrites anything of the superclass.
Other annotations
Annotations are useful in other scenarios. Annotations run very well in frameworks such as EJB and web services, especially when you add sample code, instead of directly modifying the behavior but enhancing the behavior.
Annotations cannot be used as pre-processors. Sun's design specifically prevents modifying the class bytecode completely due to comments. In this way, you can correctly understand the results of the language, and tools such as IDE can also perform in-depth code analysis, refactoring, and other functions.
Note is not a silver bullet. At the first time, people tried various techniques. See the following suggestions from others:
public class Foo { @Propertyprivate int bar; }
The idea is to automatically create the getter and setter methods for the private field bar. Unfortunately, this idea has two failures: 1) It cannot run, 2) It makes the code hard to read and process. It cannot be implemented, because Sun has previously mentioned that it is particularly blocked from modifying the classes with comments.
Even if possible, it is not a good idea because it makes the code less readable. The person who saw this code for the first time will not know how the annotation was created. In addition, annotations are useless if you need to perform some operations within these methods in the future. In short, do not try to use comments to do things that can be done by conventional code.
Enumeration
Enum is very similar to the public static final int statement, which has been used as the enumerated value for many years. The biggest and most obvious improvement to Int Is type security-you cannot mistakenly use one type of enumeration to replace another type, which is different from Int, all int values are the same for the compiler. Except for a few exceptions, Enum instances should be used to replace all enumerated int structures.
Enumeration provides some additional features. The two classification classes enummap and enumset are specifically implemented for the standard set of enumeration optimization. If you know that a set only contains enumeration types, use these special sets instead of hashmap or hashset.
In most cases, you can use Enum to insert and replace all public static final int values in the code. They are comparable and can be imported statically, so references to them seem equivalent, even for internal classes (or internal enumeration types ). Note: When comparing enumeration types, the statements that declare them indicate their sequential values.
"Hidden" static method
The two static methods appear in all Enumeration type declarations. Because they are static methods on enumeration subclasses, rather than the enum method, they are not found in javadoc of Java. Lang. enum.
The first is values (), which returns an array of all possible values of the enumeration type.
The second is valueof (), which returns an enumeration type for the provided string. This enumeration type must exactly match the source code declaration.
Method
One of our favorite aspects about enumeration types is that it can have methods. In the past, you may need to write some code to convert public static final int and convert it from database type to jdbc url. Now, we can add a code sorting method to the enumeration type. The following is an example, which includes an abstract method of the databasetype Enumeration type and the implementation provided in each enumeration instance:
public enum DatabaseType {ORACLE {public String getJdbcUrl() {...}},MYSQL {public String getJdbcUrl() {...}};public abstract String getJdbcUrl();}
The enumerated type can provide its practical method directly. For example:
Databasetype dbtype = ...;
String jdbcurl = dbtype. getjdbcurl ();
To obtain a URL, you must know in advance where the practical method is.
Variable Parameter (vararg)
Correct use of variable parameters can indeed clear some junk code. A typical example is a log method with variable number of string parameters:
Log.log(String code)Log.log(String code, String arg)Log.log(String code, String arg1, String arg2)Log.log(String code, String[] args)
When talking about variable parameters, it is interesting that if you replace the first four examples with the new variable parameters, they will be compatible:
Log. Log (string code, string... ARGs)
All variable parameters are source compatible-that is to say, if you re-compile all the calling programs of the log () method, you can directly replace all four methods. However, if you need backward binary compatibility, you need to remove the first three methods. Only the method with the last string array parameter is equivalent to the variable parameter version, so it can be replaced by the variable parameter version.
Type forced conversion
If you want the caller to know which type of parameters should be used, you should avoid forced type conversion with variable parameters. Let's take a look at the example below. The first item is string, and the second item is exception:
Log.log(Object... objects) {String message = (String)objects[0];if (objects.length > 1) {Exception e = (Exception)objects[1];// Do something with the exception}}
The method signature should be as follows, and the variable parameters should be declared using string and exception respectively:
Log. Log (string message, exception E, object... objects ){...}
Do not use variable parameters to destroy the type system. It can be used only when it is strongly typed. For this rule, printstream. printf () is an interesting exception: it provides type information as its first parameter so that it can accept those types later.
Covariant return
The basic usage of covariant return is to avoid forced type conversion when the returned type of an implementation is known to be more specific than the API. In the following example, there is a zoo interface that returns the animal object. Our implementation returns an animalimpl object, but before JDK 1.5, an animal object must be declared. :
public interface Zoo {public Animal getAnimal();}public class ZooImpl implements Zoo {public Animal getAnimal(){return new AnimalImpl();}}
The following three anti-modes are replaced in the returned result of the covariant:
- Direct field access. To avoid API restrictions, some implementations expose subclass directly as fields:
Zooimpl. _ animal
- Another form is to execute a downward conversion in the calling program when you know that the implementation is actually a specific subclass:
(Animalimpl) zooimpl. getanimal (). implmethod ();
- The last form I see is a specific method, which is used to avoid problems caused by a completely different signature:
Zooimpl. _ getanimal ();
These three models have their own problems and limitations. Either it is not neat enough or it exposes unnecessary implementation details.
Covariant
The covariant return mode is neat, secure, and easy to maintain, and does not require type forced conversion or specific methods or fields:
Public animalimpl getanimal (){
Return new animalimpl ();
}
Result:
Zooimpl. getanimal (). implmethod ();
Use generic
We will understand generics from two perspectives: Using Generics and constructing generics. We will not discuss the obvious usage of list, set, and map. It is enough to know that the generic set is powerful and should be used frequently.
We will discuss how to use generic methods and how compilers deduce types. Generally, there will be no problems, but when there is a problem, the error information will be very confusing, so you need to know how to fix these problems.
Generic Method
In addition to generic types, Java 5 also introduces generic methods. In this example from Java. util. Collections, a single element list is constructed. The element type of the new list is inferred based on the object type of the input method:
Static <t> List <t> collections. singletonlist (t o) Example usage: public list <integer> getlistofone () {return collections. singletonlist (1 );}
In the example, an int is input. Therefore, the return type of the method is list <integer>. The compiler infers t to integer. This is different from the generic type because you do not need to explicitly specify the type parameter.
This also shows the interaction between automatic packing and generics. The type parameter must be of the Reference Type: this is why we get the list <integer> instead of the List <int>.
Generic method without Parameters
The emptylist () method is introduced with generics as a safe replacement of the empty_list field type in Java. util. Collections:
Static <t> List <t> collections. emptylist () Example usage: public list <integer> getnointegers () {return collections. emptylist ();}
Unlike the previous example, this method has no parameters. How does the compiler infer the T type? Basically, it will try to use the parameter once. If it does not work, it tries to use the return or value type again. In this example, list <integer> is returned, so t is inferred as integer.
What if the generic method is called outside the return statement or value assignment statement? Then the compiler will not be able to execute the second transfer of type inference. In the following example, emptylist () is called from the condition OPERATOR:
public List<Integer> getNoIntegers() {return x ? Collections.emptyList() : null;}
Because the compiler does not see the returned context and cannot infer t, it abandons and uses the object. You will see an error message, such as: "list <Object> cannot be converted to list <integer> ."
To fix this error, type parameters should be passed explicitly to the method call. In this way, the compiler will not try to deduce the type parameter, so it can get the correct result:
return x ? Collections.<Integer>emptyList() : null;
Another common cause of this situation is in method calling. If a method carries a list <string> parameter and needs to call the passed emptylist () parameter for that parameter, you also need to use this syntax.
Outside the set
There are three examples of generic types. They are not a set, but they are used in a novel way. All three examples come from the standard Java Library:
- Class <t>
The class is parameterized on the class type. This makes it possible to construct a newinstance without forced type conversion.
- Comparable <t>
Comparable is parameterized by the actual comparison type. This provides stronger typing for compareto () calls. For example, string implements comparable <string>. Calling compareto () for anything except string will fail during compilation.
- Enum <E extends Enum <E>
Enum is parameterized by Enumeration type. Enum <color> is extended for an enumeration type named color. The getdeclaringclass () method returns class objects of the enumeration type. In this example, it is a color object. Unlike getclass (), the latter may return an unknown class.
Wildcard
The most complex part of generics is the understanding of wildcards. We will discuss three types of wildcards and their usage.
First, let's take a look at how arrays work. You can assign a value to a number [] from an integer. If you try to write a float to number [], you can compile it, but it will fail during runtime, and an arraystoreexception will appear:
Integer [] IA = new integer [5]; number [] NA = IA; NA [0] = 0.5; // compiles, but fails at runtime if you try to convert this example directly to a generic type, it will fail during compilation because the assignment is not allowed: list <integer> ilist = new arraylist <integer> (); List <number> NLIST = ilist; // not allowednlist. add (1, 0.5 );
If you use generics, as long as the Code does not have a warning during compilation, you will not encounter classcastexception at runtime.
Upper Limit wildcard
What we want is a list of unknown exact element types, which is different from the array.
List <number> is a list whose element type is number.
List <? Extends number> is a list of unknown element types. It is a number or its subtype.
Upper Limit
If we update the initial example and assign it to list <? Extends number>:
List<Integer> iList = new ArrayList<Integer>();List<? extends Number> nList = iList;Number n = nList.get(0);nList.add(0.5); // Not allowed
We can get number from the list, because no matter what the exact element type of the List is (float, integer, or number), we can assign it to number.
We still cannot insert the floating point type into the list. This will fail during compilation, because we cannot prove that this is safe. If we want to add a floating point type to the list, it will destroy the initial type security of ilist -- it only stores integer.
Wildcards give us more expressive power than arrays.
Why do I use wildcards?
In the following example, wildcards are used to hide type information from API users. Internally, set is stored as customerimpl. API users only know that they are acquiring a set from which they can read the customer.
Wildcards are required because they cannot be assigned from set <customerimpl> to set <customer>:
public class CustomerFactory {private Set<CustomerImpl> _customers;public Set<? extends Customer> getCustomers() {return _customers;}}
Wildcard and coordinated return
Another common usage of wildcard is to use it together with the coordinated return. Rules with the same value assignment can be applied to the covariant return. If you want to return a more specific generic type in the override method, the declared method must use wildcards:
public interface NumberGenerator {public List<? extends Number> generate();}public class FibonacciGenerator extends NumberGenerator {public List<Integer> generate() {...}}
If you want to use an array, the interface can return number [], while the implementation can return integer [].
Lower limit
We are talking about the upper limit wildcard. There is also a lower limit wildcard. List <? Super number> is a list that is unknown for the exact "element type", but may be mnumber or a super-type of number. Therefore, it may be a list <number> or a list <Object>.
Lower-limit Wildcards are far less common than upper-limit wildcards, but they are required when they are needed.
Lower limit and Upper Limit
List<? extends Number> readList = new ArrayList<Integer>();Number n = readList.get(0);List<? super Number> writeList = new ArrayList<Object>();writeList.add(new Integer(5));
The first one is the list from which reading can be made.
The second is a list of numbers that can be written.
Unbounded wildcard
Finally, list <?> The content of the list can be of any type, and it corresponds to the list <? Extends Object> is almost the same. You can read objects at any time, but cannot write content to the list.
Wildcards in public APIs
In short, as mentioned above, wildcards are very important in hiding implementation details from the caller, but even if the lower-limit wildcard seems to provide read-only access, since remove (INT position) and other non-generic methods. If you want a truly unchanged set, you can use methods on java. util. Collection, such as unmodifiablelist ().
Remember the wildcard when writing an API. In general, you should try to use wildcards when passing generic types. It allows more calling programs to access APIs.
By receiving list <? Extends number> instead of list <number>, the following method can be called by many different types of lists:
void removeNegatives(List<? extends Number> list);
Construct generic types
Now we will discuss how to construct your own generic type. We will show some examples, in which the use of generics can improve the type security, we will also discuss some common problems when implementing generic types.
Collection-like functions
The first example of a generic class is a collection style example. Pair has two types of parameters, and the fields are type instances:
public final class Pair<A,B> {public final A first;public final B second;public Pair(A first, B second) {this.first = first;this.second = second;}}
This makes it possible to return two items from the method without writing a dedicated class for each combination of the two types. Another method is to return object [], which is insecure or untidy.
In the following usage, a file and a Boolean are returned from the method. The client of the method can directly use fields without forced type conversion:
public Pair<File,Boolean> getFileAndWriteStatus(String path){// create file and statusreturn new Pair<File,Boolean>(file, status);}Pair<File,Boolean> result = getFileAndWriteStatus("...");File f = result.first;boolean writeable = result.second;
Outside the set
In the following example, generics are used for additional compilation security. By setting the dbfactory class parameter to the created peer type, you actually force the factory subclass to return a specific child type of the Peer:
Public abstract class dbfactory <t extends dbpeer> {protected abstract t createemptypeer (); public list <t> get (string constraint) {list <t> peers = new arraylist <t> (); // database magicreturn peers;} by implementing dbfactory <customer>, customerfactory must return a customer from createemptypeer: public class customerfactory extends dbfactory <customer> {public customer createemptypeer () {return new customer ();}}
Generic Method
Whether you want to impose constraints on generic types between parameters or between parameters and return types, you can use the generic method:
For example, if a reverse function is written to reverse the position, you may not need a generic method. However, if you want to reversely return a new list, you may want the element type of the new list to be the same as that of the imported list. In this case, a generic method is required:
<T> List <t> reverse (list <t> List)
Concrete
When implementing a generic class, you may want to construct an array T []. This is not allowed because generics are implemented through erasure.
You can try to forcibly convert object [] to T []. But this is not safe.
Specific Solution
According to the general practice of the generic tutorial, the solution uses a "type token". By adding a class <t> parameter to the constructor, you can force the client to provide the correct class object for class type parameters:
public class ArrayExample<T> {private Class<T> clazz;public ArrayExample(Class<T> clazz) {this.clazz = clazz;}public T[] getArray(int size) {return (T[])Array.newInstance(clazz, size);}}
To construct arrayexample <string>, the client must pass string. class to the constructor, because the string. class type is class <string>.
Having class objects makes it possible to construct an array with the correct element type.
Conclusion
All in all, the new language features help fundamentally change Java. By understanding what scenarios to use and how to use these new features, you will write better code.
Additional reading
- Official list of new features of enhancements in JDK 5--jdk 5
- Generics tutorial (PDF) -- generic tutorial for Gilad Bracha
- Dev2dev developer centers
Source:Experiences with the new Java 5 language features http://dev2dev.bea.com/pub/a/2005/09/java_5_features.html
Author Profile |
|
Jess garms is a leader in the javelin compiler team in BEA Systems. Before that, Jess was committed to Bea's Java IDE, WebLogic Workshop. In addition, he has rich experience in cryptography. He also co-authored "professional Java security", published by wrox Publishing House. |
|
Tim Hanson is the architect of the javelin compiler in BEA Systems. Tim has done a lot of development work on Bea's Java compiler. This compiler is one of the earliest 1.5 compatible implementations. He has written many other compilers, including the CORBA/IDL compiler he wrote at IBM and the XQuery compiler. |