Generics


Java Generics is a new topic in the Java environment and at the time of this writing is considered alpha status. Hence, it is important to realize that changes from the time of this writing may or will still happen. The Generics that will be explained are based on the compiler version 2.2 that is being presented as a proposal for Java 1.5 release. More information can be found at http://developer.java.sun.com/developer/technicalArticles/releases/generics/, including where to download the Java Generic tools.

A Simple Example

The concept of the Generic construct is defined by a series of angle brackets that contain some identifiers. Listing 2.5 was a simple example of a Generic definition. Listing 2.31 is another example of a simple Generic class definition.

Listing 2.31
start example
 class Generic1< classtype> { private classtype _something; public classtype getSomething() { return _something }; } 
end example
 

In Listing 2.31, the class identifier Generic1 is appended with a set of angle brackets. Contained within the angle brackets is the identifier classtype . Put all together, the first line of Listing 2.31 is saying, "The class Generic1 is a generic class that is associated with the type identifier classtype ." Within the class declaration Generic1 , the type identifier classtype will be recognized as another type, like the class types Integer or String . It is important to realize that the type identifier classtype can reference only other class types, not primitive types like int , long , or double .

In Listing 2.31, the type identifier classtype is used to declare the private data member _something , and the property method getSomething() . Whenever any consumer uses the class Generic1, the property method will always have a type associated with it. The class Generic1 can be used as shown in Listing 2.32.

Listing 2.32
start example
 Generic1< Integer> val = new Generic1< Integer>(); Integer getval = val.getSomething(); Generic1< Integer> val2 = val; 
end example
 

In Listing 2.32, the generic class Generic1 is instantiated just like a regular Java class would be, with the only additional identifier Integer being the generic declaration contained by the angle brackets. The easiest way to understand the instantiation is that declarations are abstract, but instantiations must be specific. Hence, to make the class Generic1 specific, you have to associate a type with the type identifier, which is the class type Integer . The type identifier has to be the same in the declaration of the variable val , and in the instantiation of the variable val .

The instantiated generic class is used like any other instantiated Java class, with the exception being that an instantiated generic class is specific and has an associated type with it. Therefore, in the second line of Listing 2.32 the variable getval has to be declared as the type Integer , because when the method getSomething is called, the result of the method is expected to assign the type Integer . If the declaration were any other data type, then a compile error would be generated.

The last line of Listing 2.32 is used to illustrate that when a generic class is being declared, the declaration must be specific and the right type to match what it is being assigned to. There is an exception to the last line of Listing 2.32; this is shown in Listing 2.33. (Note that Listing 2.33 assumes that the variables used are declared in Listing 2.32.)

Listing 2.33
start example
 Generic1< ?> val2 = val; val2.getSomething(); Integer intVal = ((Generic1<Integer>)val2).getSomething(); String strval = ((Generic1<String>)val2).getSomething(); 
end example
 

In Listing 2.33, the variable val2 is declared with a question mark where the type identifier should be. The question mark indicates to the Java compiler that the type of the generic class is as yet unknown, and hence should be kept that way. The assignment of the variable val2 by the type of val will work and not cause a problem. However, what is a problem is that the variable val2 is some type of reference, where the type is unknown to the program at compile time and will be determined at runtime. This means that using the variable val2 at compile time has some restrictions.

The second to fourth lines in Listing 2.33 show the restrictions. The second line calls the method getSomething but does not assign the return value. This is allowed because there is no need to make a decision on what type is being referenced because the return value is not being assigned. At runtime, the generic variable val2 will execute and know what to do because the object instance knows how it is specified.

Problems of knowing the specific details of the generic object arise in the third and fourth lines of Listing 2.33. The third and fourth lines cast the variable val2 to a specific type and then call the method getSomething . Both of the casts are legal at compile time, but one of the casts will be wrong at runtime. What will end up happening with one of the casts is that a runtime cast exception error will be generated.

In the declarations shown thus far, it is important to realize that multiple type identifiers can be used between the angle brackets. Each type identifier would have to be separated by a comma.

Generic Methods and Wildcards

The question mark introduced in Listing 2.33 is very powerful in Java Generics. The question mark is a wildcard character that indicates to the compiler that the generic type is being used with a type identifier that is as yet unknown. Using that sort of identifier allows a developer to define what kind of class to use in which context, as shown in Listing 2.34.

Listing 2.34
start example
 class Generic3 { public void method1( Generic1<?> obj) { return; } public <classtype> void method2( Generic1< classtype> obj) { return; } } 
end example
 

In Listing 2.34, the class Generic3 has been declared with two methods: method1 and method2 . They are identical methods but are declared in two different variations. To prove to yourself that both methods are identical, remove the number one and two from each identifier, and the compiler will come back with an identical method error.

The method method1 is declared with the first parameter of the generic type Generic1 , and the question mark used in the type identifier indicates that the specific type will be determined at runtime. The method method2 is declared with the angle brackets after the public scope identifier and before the return type void identifier. Within the angle brackets is the generic type associated with the method. When the method is declared this way, the method is considered to be a generic method. Then, the first parameter is the generic class Generic1 , but instead of a question mark the type identifier classtype is used.

In either case of the declaration, the specific types are known only when the method is used in some other place in the source code. Listing 2.35 shows how method1 and method2 could be consumed.

Listing 2.35
start example
 Generic3 cls = new Generic3(); cls.method1( new Generic1< Integer>()); cls.method2( new Generic1< Integer>()); 
end example
 

In Listing 2.35, the class Generic3 is instantiated like any Java class, because the class Generic3 does not have the angle brackets to define it as a generic class. Then, to call the methods method1 and method2, the generic class type Generic1 < Integer > is used. Both methods are called the same, proving that both method signatures are identical.

The reason for using the different method signatures lies in the implementation of each of methods which are shown in Listing 2.36.

Listing 2.36
start example
 class Generic3 { public void method1( Generic1<?> obj) { obj.getSomething(); return; } public <classtype> void method2( Generic1< classtype> obj) { classtype val = obj.getSomething(); return;  } } 
end example
 

In Listing 2.36, we again encounter the problem of not knowing what the type of the object was, as first shown in Listing 2.33. In the implementation of the method method1 , the method getSomething returns a specific type. The question mark was used in the declaration of method1 , so the specific type of the generic class Generic1 is not known. The programmer would have to do much thinking, searching, and juggling to figure out the type identifier.

In the case of the method method2, the generic type is known because the generic type identifier classtype is declared. Therefore, in the implementation of method2 when the variable val is being assigned, both the variable val and the method getSomething have the same type, which causes no problems.

Using Inheritance with Generics

Java generics can inherit and determine what kind of classes can be used. In the simplest example, classes can inherit from other generic classes. In Listing 2.37, a generic interface is defined, which is then implemented by two classes.

Listing 2.37
start example
 interface Interface1< classtype> {  void func( classtype param); } class Generic7 implements Interface1< Integer> {  public void func( Integer param) {  } } class Generic11< clstyp> implements Interface1< clstyp> {  public void func( clstype param) {  } } 
end example
 

In Listing 2.37, the interface Interface1 is a generic interface that uses the type identifier classtype in one of the methods func defined by the interface. The interface is declared using a simple generic declaration.

The class Generic7 in Listing 2.37 is a regular Java class that implements the generic interface Interface1 . As the class Generic7 is a regular Java class, the generic interface, which is the class type Integer, must be specialized. When the class Generic7 implements the methods of the Interface1, the type identifier is Integer , and therefore the method func must be declared with a parameter of the type Integer .

The class Generic11 is different because it is a generic Java class, with the type identifier clstyp . When the generic class implements the methods of Interface1, you can use a type identifier like in the case of the class Generic7 , or use a class specialization, which is shown by the method implementation of the generic class Generic11 . In either case, the identifier must already exist. For example, in Listing 2.37, the type identifier used for generic interface Interface1 in the context of the generic class Generic11 is clstyp . The type identifier clstyp is declared as a type identifier by the parent class Generic11 . At no time could the type identifier for the generic interface Interface1 be some other randomly defined identifier. The Java compiler in that case would spit out an error saying that the identifier had not been defined.

There is a catch to inheritance in that inheritance cannot be used to bypass the Java multiple inheritance restriction. For example, the declaration in Listing 2.38 would be illegal.

Listing 2.38
start example
 interface Interface1< classtype> { } class Cls1 implements Interface1< Integer> { } class Cls2 extends Cls1 implements Interface1< String> { } 
end example
 

In Listing 2.38, the interface Interface1 is declared as a generic interface. The class Cls1 implements Interface1 and uses the class type Integer as the type identifier. The class Cls2 extends the class Cls1 as well as implements Interface1 , but it uses the class type String as the type identifier. The class Cls2 uses a different type identifier as the class Cls1, so there are two different implementations being inherited, which is not legal with Java Generics.

Using Bounded Classes in Generic Declarations

When you use inheritance in the declaration of the generics, the purpose is to create a specialized type in the context of a generic class or method. An example of using inheritance in a declaration is shown in Listing 2.39.

Listing 2.39
start example
 class Generic1< obj> { obj _something; public obj getSomething() { return _something; }} class Generic2< A extends Generic1< C >, C> { A _something; Generic2() { something._something = null; } } class Generic5< A extends Generic1< ? > { A _something; Generic5() { something._something = null; } } 
end example
 

In Listing 2.39, the class Generic1 is defined as a generic class, and it defines a private data member something and property getSomething . The class Generic2 is a generic class declaration as well, except that the first type identifier A extends the generic class Generic1 . This way of defining a type identifier says that when the generic class Generic2 is used, it must subclass the generic class Generic1 somewhere in the inheritance hierarchy. The declaration of the class Generic1 is a tight bind because the type must be the same as the second type identifier C . The declaration of the generic class Generic5 is also saying that the type identifier must inherit from Generic1 , except the type identifier has been replaced with a question mark, meaning that any type identifier is acceptable.

Listing 2.40 is an example of how to use the generic classes Generic2 and Generic5 .

Listing 2.40
start example
 class AnotherBase extends Generic1< String> { } class YetAnotherBase extends AnotherBase { } Generic2< YetAnotherBase, String> val1 = new Generic2< YetAnotherBase, String>(); Generic5< AnotherBase > val2 = new Generic2< YetAnotherBase>(); 
end example
 

In Listing 2.40, the class AnotherBase subclasses the generic class Generic1 , which is subclassed by the class YetAnotherBase . The classes AnotherBase and YetAnotherBase could be used in conjunction with the generic classes Generic2 and Generic5 . The last two lines of Listing 2.40 show how to instantiate the generic classes Generic2 and Generic5 .

Besides using the keyword extends in Listing 2.39, you can use the keyword super, as shown in Listing 2.41. (Note that in Listing 2.41 the type identifier B is the bounding type.)

Listing 2.41
start example
 ? super B 
end example
 

Using Generics

When you're developing applications, Generics will help you solve certain problems like creating lists in a more consistent and type-safe manner. Especially interesting is the ability of Java Generics to enforce boundaries. This is useful because it means that a passed-in-type identifier will, at the minimum, implement a specific interface or extend a specific class. The ability to define a boundary when tied into the Commons Bridge makes it simpler to build generic interface-driven code than by using the Commons Bridge alone. Note, however, that all of what we have explained here could change, since all of the materials presented are alpha status.




Applied Software Engineering Using Apache Jakarta Commons
Applied Software Engineering Using Apache Jakarta Commons (Charles River Media Computer Engineering)
ISBN: 1584502460
EAN: 2147483647
Year: 2002
Pages: 109

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net