This article brings you a detailed analysis (code) of wildcards in Java. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.
The irrelevance of subtypes of generic types has been mentioned in the previous part of this article. But sometimes, we want to be able to use generic types like ordinary types:
◆ Upcasting a reference to a generic object
◆ Downcasting a reference to a generic object
Upcasting a reference to a generic object
For example, suppose we have many boxes, each box contains different fruits, we need to find a way to Universal for handling any box of fruit. More generally, A is a subtype of B, and we need to find a way to assign an instance of type C to a declaration of type C.
In order to accomplish this operation, we need to use an extension declaration with wildcards, as in the following example:
List<Apple> apples = new ArrayList<Apple>(); List<? extends Fruit> fruits = apples;
"? extends" is a subtype dependency of the generic type Come to reality: Apple is a subtype of Fruit, and List
Downcast a reference to a generic object
Now let me introduce another wildcard: ? super. If type B is a supertype (parent type) of type A, then C is a subtype of C super A>:
List<Fruit> fruits = new ArrayList<Fruit>(); List<? super Apple> = fruits;
Why does using wildcard markers work?
The principle is now clear: how do we take advantage of this new grammatical structure?
? extends
Let’s revisit an example used in this second part, which talks about subtype dependencies of Java arrays:
Apple[] apples = new Apple[ 1 ]; Fruit[] fruits = apples; fruits[ 0 ] = new Strawberry();
As we can see, when you add a Strawberry object to an Apple object array declared as a Fruit array, the code can compile, but an exception is thrown at runtime.
Now we can use wildcards to convert the relevant code into generics: Because Apple is a subclass of Fruit, we use the ? extends wildcard so that the definition of a List
List<Apple> apples = new ArrayList<Apple>(); List<? extends Fruit> fruits = apples; fruits.add( new Strawberry());
This time, the code cannot be compiled! The Java compiler will prevent you from adding strawberry to a Fruit list. We can detect the error at compile time, and there is no need to check at runtime to ensure that incompatible types are added to the list. Even if you add a Fruit object to the list, it won't work:
fruits.add( new Fruit());
You can't do this. In fact you cannot write any values into a data structure that uses ? extends.
The reason is very simple, you can think of it this way: This? extends T wildcard tells the compiler that we are dealing with a subtype of type T, but we don't know what this subtype is. Because there is no way to be sure, in order to ensure type safety, we are not allowed to add any data of this type to it. On the other hand, because we know that no matter what type it is, it is always a subtype of type T. When we read data, we can ensure that the data we get is an instance of type T:
Fruit get = fruits.get( 0 );
? super
Use? What is the general situation of super wildcard? Let's look at this first:
List<Fruit> fruits = new ArrayList<Fruit>(); List<? super Apple> = fruits;
We see that fruits points to a List containing some of Apple's supertypes. Again, we don't know exactly what the superclass is, but we do know that Apple and any subclasses of Apple are compatible with its type. Since this unknown type is Apple and the superclass of GreenApple, we can write:
fruits.add( new Apple()); fruits.add( new GreenApple());
If we want to add Apple's superclass to it, the compiler will warn you:
fruits.add( new Fruit()); fruits.add( new Object());
Because we don't know what kind of super class it is, all such instances are not allowed to be added.
What about getting data from this type? It turns out that you can only take out an Object instance: because we don't know what the superclass is, the only thing the compiler can guarantee is that it is an Object, because Object is the superclass of any Java type.
Access Principles and PECS Rules
Summary of the characteristics of ? extends and the ? super wildcard, we can draw the following conclusions:
◆ If If you want to get data from a data type, use the ? extends wildcard
◆ If you want to write the object into a data structure, use the ? super wildcard
◆ If you want to save both, If you still want to take it, don't use wildcards.
This is what Maurice Naftalin calls the access principle in his book "Java Generics and Collections", and what Joshua Bloch calls the PECS rule in his book "Effective Java".
Bloch reminded that PECS stands for "Producer Extends, Consumer Super", which is easier to remember and use.
The above is continued from the bottom:
The Java Tutorial
java Generics and Collections, by Maurice Naftalin and Philip Wadler
Effective Java Chinese version ( 2nd edition), by Joshua Bloch.
尽管有这么多丰富的资料,有时我感觉,有很多的程序员仍然不太明白Java泛型的功用和意义。这就是为什么我想使用一种最简单的形式来总结一下程序员需要知道的关于Java泛型的最基本的知识。
Java泛型由来的动机
理解Java泛型最简单的方法是把它看成一种便捷语法,能节省你某些Java类型转换(casting)上的操作:
List<Apple> box = ...; Apple apple = box.get( 0 );
上面的代码自身已表达的很清楚:box是一个装有Apple对象的List。get方法返回一个Apple对象实例,这个过程不需要进行类型转换。没有泛型,上面的代码需要写成这样:
List box = ...; Apple apple = (Apple) box.get( 0 );
很明显,泛型的主要好处就是让编译器保留参数的类型信息,执行类型检查,执行类型转换操作:编译器保证了这些类型转换的绝对无误。
相对于依赖程序员来记住对象类型、执行类型转换——这会导致程序运行时的失败,很难调试和解决,而编译器能够帮助程序员在编译时强制进行大量的类型检查,发现其中的错误。
泛型的构成
由泛型的构成引出了一个类型变量的概念。根据Java语言规范,类型变量是一种没有限制的标志符,产生于以下几种情况:
◆ 泛型类声明
◆ 泛型接口声明
◆ 泛型方法声明
◆ 泛型构造器(constructor)声明
泛型类和接口
如果一个类或接口上有一个或多个类型变量,那它就是泛型。类型变量由尖括号界定,放在类或接口名的后面:
public interface List<T> extends Collection<T> { ... }
简单的说,类型变量扮演的角色就如同一个参数,它提供给编译器用来类型检查的信息。
Java类库里的很多类,例如整个Collection框架都做了泛型化的修改。例如,我们在上面的第一段代码里用到的List接口就是一个泛型类。在那段代码里,box是一个List对象,它是一个带有一个Apple类型变量的List接口的类实现的实例。编译器使用这个类型变量参数在get方法被调用、返回一个Apple对象时自动对其进行类型转换。
实际上,这新出现的泛型标记,或者说这个List接口里的get方法是这样的:
T get( int index);
get方法实际返回的是一个类型为T的对象,T是在List
泛型方法和构造器(Constructor)
非常的相似,如果方法和构造器上声明了一个或多个类型变量,它们也可以泛型化。
public static <t> T getFirst(List<T> list)
这个方法将会接受一个List
例子
你既可以使用Java类库里提供的泛型类,也可以使用自己的泛型类。
类型安全的写入数据…
下面的这段代码是个例子,我们创建了一个List
List<String> str = new ArrayList<String>(); str.add( "Hello " ); str.add( "World." );
如果我们试图在List
str.add( 1 ); // 不能编译
类型安全的读取数据…
当我们在使用List
String myString = str.get( 0 );
遍历
类库中的很多类,诸如Iterator
for (Iterator<String> iter = str.iterator(); iter.hasNext();) { String s = iter.next(); System.out.print(s); }
使用foreach
“for each”语法同样受益于泛型。前面的代码可以写出这样:
for (String s: str) { System.out.print(s); }
这样既容易阅读也容易维护。
自动封装(Autoboxing)和自动拆封(Autounboxing)
在使用Java泛型时,autoboxing/autounboxing这两个特征会被自动的用到,就像下面的这段代码:
List<Integer> ints = new ArrayList<Integer>(); ints.add( 0 ); ints.add( 1 ); int sum = 0 ; for ( int i : ints) { sum += i; }
然而,你要明白的一点是,封装和解封会带来性能上的损失,所有,通用要谨慎的使用。
子类型
在Java中,跟其它具有面向对象类型的语言一样,类型的层级可以被设计成这样:
在Java中,类型T的子类型既可以是类型T的一个扩展,也可以是类型T的一个直接或非直接实现(如果T是一个接口的话)。因为“成为某类型的子类型”是一个具有传递性质的关系,如果类型A是B的一个子类型,B是C的子类型,那么A也是C的子类型。在上面的图中:
◆ FujiApple(富士苹果)是Apple的子类型
◆ Apple是Fruit(水果)的子类型
◆ FujiApple(富士苹果)是Fruit(水果)的子类型
所有Java类型都是Object类型的子类型。
B类型的任何一个子类型A都可以被赋给一个类型B的声明:
Apple a = ...; Fruit f = a;
泛型类型的子类型
如果一个Apple对象的实例可以被赋给一个Fruit对象的声明,就像上面看到的,那么,List
答案会出乎你的意料:没有任何关系。用更通俗的话,泛型类型跟其是否子类型没有任何关系。
这意味着下面的这段代码是无效的:
List<Apple> apples = ...; List<Fruit> fruits = apples;
下面的同样也不允许:
List < Apple > apples; List < Fruit > fruits = ...; apples = fruits ;
为什么?一个苹果是一个水果,为什么一箱苹果不能是一箱水果?
在某些事情上,这种说法可以成立,但在类型(类)封装的状态和操作上不成立。如果把一箱苹果当成一箱水果会发生什么情况?
List<Apple> apples = ...; List<Fruit> fruits = apples; fruits.add( new Strawberry());
如果可以这样的话,我们就可以在list里装入各种不同的水果子类型,这是绝对不允许的。
另外一种方式会让你有更直观的理解:一箱水果不是一箱苹果,因为它有可能是一箱另外一种水果,比如草莓(子类型)。
这是一个需要注意的问题吗?
应该不是个大问题。而程序员对此感到意外的最大原因是数组和泛型类型上用法的不一致。对于泛型类型,它们和类型的子类型之间是没什么关系的。而对于数组,它们和子类型是相关的:如果类型A是类型B的子类型,那么A[]是B[]的子类型:
Apple[] apples = ...; Fruit[] fruits = apples;
可是稍等一下!如果我们把前面的那个议论中暴露出的问题放在这里,我们仍然能够在一个apple类型的数组中加入strawberrie(草莓)对象:
Apple[] apples = new Apple[ 1 ]; Fruit[] fruits = apples; fruits[ 0 ] = new Strawberry();
这样写真的可以编译,但是在运行时抛出ArrayStoreException异常。因为数组的这特点,在存储数据的操作上,Java运行时需要检查类型的兼容性。这种检查,很显然,会带来一定的性能问题,你需要明白这一点。
重申一下,泛型使用起来更安全,能“纠正”Java数组中这种类型上的缺陷。
现在估计你会感到很奇怪,为什么在数组上会有这种类型和子类型的关系,我来给你一个《Java Generics and Collections》这本书上给出的答案:如果它们不相关,你就没有办法把一个未知类型的对象数组传入一个方法里(不经过每次都封装成 Object[]),就像下面的:
void sort(Object[] o);
泛型出现后,数组的这个个性已经不再有使用上的必要了(下面一部分我们会谈到这个),实际上是应该避免使用
相关推荐:
The above is the detailed content of Detailed analysis of wildcards in java (code). For more information, please follow other related articles on the PHP Chinese website!