List list = new ArrayList();
list.add("1");
list.add(123);
String a = (String) list.get(0);
int b = (int) list.get(1);
比如上面的集合中第一次放入了字符串"1",第二次放入了int类型数字123,所以这个集合可以放入任何类型,但是取出来的时候就会非常麻烦,需要你知道特定的位置放入的是什么类型,然后进行强转,这使用非常麻烦。
自从jdk1.5后,ArrayList定义变成了
public class ArrayList extends AbstractList
public void add(E e) {}
public E get(int index) {}
List list = new ArrauList();//创建的时候指定类型
也就说说给集合限定了一种类型,以后这个集合只能存储这种类型,当然取出来的时候也就不需要强转了,因为知道了肯定是某种类型
泛型最引人注目的一个原因就是,从新创造了容器类
容器就是泛型类的一种经典应用,同样可以应用到内部类、抽象类、接口
class A{
/**
* 方法更通用,这个时候T虽然也是声明一个类型,但是该方法基本上是重载了很多次。
* 这样的方法才是通用方法。
*
* 创接类的时候无需指定方法的参数类型,编译器会通过类型参数推断
* @param t
* @param
*/
public static void test(T t){
System.out.println(t.getClass().getName());
}
}
比如上面一个方法,想获得参数所属类的名字,但是类型会特别多,有8中基本类型和自定义的无数类型。这个时候这个test方法需要重载多少次。通过泛型就可以解决这个问题。
注意一个知识点:比如类使用泛型,会显示的指定这个泛型是什么,new ArrayList,但是方法就不需要,因为方法会参数类型推断,自动判断类型,当然也可以显示的指定类型 比如 A.test(“a”)
Class c1 = new ArrayList().getClass();
Class c2 = new ArrayList().getClass();
sout(c1==c2);// true
上面例子的结果是true,也就说说他们是相同的类,但是他们的行为却不同,c1只能存放字符串,而c2只能存放int,相同的类行为却不相容
原因
泛型只是编译器的小把戏,他也是语法糖,就是编译器做了一点手脚,并不是新的功能,语法。在编译后这些泛型就被擦除了,所以这两个类才回相同。
下面看擦除后的表现:
package com.hfview.ErasedType;
/**
* 泛型擦除到object后不能调用特殊方法
*
* @author: zhw
* @since: 2019/3/19 14:58
*/
public class Manipulator {
private T obj;
public Manipulator(T obj){
this.obj = obj;
}
public void test(){
//obj.f();
}
}
class HasF{
public void f(){
System.out.println("HasF.f()");
};
}
刚开始可能为疑惑为什么不能调用obj.f();方法,这是因为擦除后,泛型类型参数将擦除到他的第一个边界,本例中就是擦除到Object,Object哪有f()方法。
擦除是java的一种妥协,java是受c++启发编成的,c++就不会擦除,因为泛型在jdk1.5才出现的,在1.5后很多类都引入了泛型,如果没有擦除,jdk1.5之前的代码和jdk1.5之后的类都不能同用了,java考虑的向前兼容,才回使用擦除,这样就不会对以前的代码产生问题。
下面证明泛型为什么只是语法糖:
package com.hfview.ErasedType;
import org.apache.poi.ss.formula.functions.T;
/**
* 通过字节码观察擦除只是语法糖
*
* @author: zhw
* @since: 2019/3/19 15:40
*/
public class GenericBase {
private Object element;
public void set(Object arg){
element = arg;
}
public Object get(){
return element;
}
public static void main(String[] args) {
Derived2 derived2 = new Derived2();
System.out.println(derived2.get());
}
}
class Derived2 extends GenericBase{
}
使用javap -c之后我把main方法贴出来
public static void main(java.lang.String[]);
Code:
0: new #3 // class com/hfview/ErasedType/Derived2
3: dup
4: invokespecial #4 // Method com/hfview/ErasedType/Derived2."":()V
7: astore_1
8: new #5 // class com/hfview/ErasedType/GenericBase
11: dup
12: invokespecial #6 // Method "":()V
15: astore_2
16: aload_2
17: aload_1
18: invokevirtual #7 // Method set:(Ljava/lang/Object;)V
21: aload_2
22: invokevirtual #8 // Method get:()Ljava/lang/Object;
25: checkcast #3 // class com/hfview/ErasedType/Derived2
28: astore_3
29: return
}
第25行很明显的用到了强转
下面我把类改造成泛型
package com.hfview.ErasedType;
import org.apache.poi.ss.formula.functions.T;
/**
* 通过字节码观察擦除只是语法糖
*
* @author: zhw
* @since: 2019/3/19 15:40
*/
public class GenericBase {
private T element;
public void set(T arg){
element = arg;
}
public T get(){
return element;
}
public static void main(String[] args) {
Derived2 derived2 = new Derived2();
GenericBase genericBase = new GenericBase();
genericBase.set(derived2);
Derived2 derived3 = (Derived2) genericBase.get();
}
}
class Derived2{
}
使用javap -c 查看字节码发现,这个字节码竟然和上面不适用泛型是一摸一样的。
public static void main(java.lang.String[]);
Code:
0: new #3 // class com/hfview/ErasedType/Derived2
3: dup
4: invokespecial #4 // Method com/hfview/ErasedType/Derived2."":()V
7: astore_1
8: new #5 // class com/hfview/ErasedType/GenericBase
11: dup
12: invokespecial #6 // Method "":()V
15: astore_2
16: aload_2
17: aload_1
18: invokevirtual #7 // Method set:(Ljava/lang/Object;)V
21: aload_2
22: invokevirtual #8 // Method get:()Ljava/lang/Object;
25: checkcast #3 // class com/hfview/ErasedType/Derived2
28: astore_3
29: return
}
泛型就是语法糖,他只是把强转动作在编译的时候自动加上了
如果需要调用泛型的某些方法,
//限制1
public interface IHasColor {
Color getColor();
}
//限制2
public class Dimension {
public int x,y,z;
}
//抽取公共部分
public class HoldItem {
T item;
public HoldItem(T item){
this.item = item;
}
T getItem(){
return item;
}
}
//限制1的应用
public class Colored extends HoldItem{
public Colored(T item) {
super(item);
}
/**
* 因为擦除到边界IHasColor,所以可以放翁getColor()方法
* @return
*/
Color color(){
return item.getColor();
}
}
//限制2的应用
public class ColorDimension extends Colored{
public ColorDimension(T item) {
super(item);
}
/**
* 边界限定为Dimension和IHasColor 就可以调用getClor也可以是调用
* Dimension中的x
* @return
*/
int getX(){
return item.x;
}
}
//继承2个限制的实现
public class Bounded extends Dimension implements IHasColor{
@Override
public Color getColor() {
return Color.BLACK;
}
}
//调用
public class Main {
public static void main(String[] args) {
Bounded bounded = new Bounded();
ColorDimension colorDimension = new ColorDimension<>(bounded);
colorDimension.color();
}
}
如果想调用泛型的特殊方法,因为会擦除到边界,所以可以调用到边界的方法,没有定义边界那就是Object。
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class CovariantArrays {
public static void main(String[] args) {
Fruit[] fruit = new Apple[10];
fruit[0] = new Apple(); // OK
fruit[1] = new Jonathan(); // OK
// Runtime type is Apple[], not Fruit[] or Orange[]:
try {
// Compiler allows you to add Fruit:
fruit[0] = new Fruit(); // ArrayStoreException
} catch(Exception e) { System.out.println(e); }
try {
// Compiler allows you to add Oranges:
fruit[0] = new Orange(); // ArrayStoreException
} catch(Exception e) { System.out.println(e); }
}
} /* Output:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
*///:~
main 方法中的第一行,创建了一个 Apple 数组并把它赋给 Fruit 数组的引用。这是有意义的,Apple 是 Fruit 的子类,一个 Apple 对象也是一种 Fruit 对象,所以一个 Apple 数组也是一种 Fruit 的数组。这称作数组的协变,Java 把数组设计为协变的。
尽管 Apple[] 可以 “向上转型” 为 Fruit[],但数组元素的实际类型还是 Apple,我们只能向数组中放入 Apple或者 Apple 的子类。在上面的代码中,向数组中放入了 Fruit 对象和 Orange 对象。对于编译器来说,这是可以通过编译的,但是在运行时期,JVM 能够知道数组的实际类型是 Apple[],所以当其它对象加入数组的时候就会抛出异常。
泛型设计的目的之一是要使这种运行时期的错误在编译期就能发现
ArrayList flist = new ArrayList();
编译器自动认为这种是错的,在编译期间就能发现错误
那么如果我们确实需要建立这种 “向上转型” 的关系怎么办呢?这就需要通配符来发挥作用了。
俗称上界边界符
List extends Fruit> list = new ArrayList();
这样就能建立起"向上转型了",注意这个是打引号的。
解释一下 ? extends Fruit
这就很尴尬了,因为你如果调用list.add(new Apple());编译器就会觉得很奇怪,因为编译器都不知道我到底是哪种类型,现在你突然想加入一个Apple对象,有可能是对的,但也有可能是错的,作为严谨的编译器怎么能允许出现可能出错,所以编译器就编译不通过。实际上这种情况除了添加null是允许的,添加任何对象编译器都会报错。
那这个东西有啥用????。
public boolean contains(Object o)
这个方法的对象是Object,所以调用 list.contains(new Apple())
是可以调用的俗称下界边界符
List super Apple> list = new ArrayList();
解释一下 ? super Fruit
如果是List
,那么我向该list中添加apple的子类肯定没问题,因为肯定可以向上转型成apple。
同样 ? super Apple 都是apple的父类的所以添加apple和apple的子类肯定也是没问题的
list .add(new Apple());
list .add(new Jonathan());
无边界通配符
还有一种通配符是无边界通配符,它的使用形式是一个单独的问号:List>,也就是没有任何限定。不做任何限制,跟不用类型参数的 List 有什么区别呢?
List> list 表示 list 是持有某种特定类型的 List,但是不知道具体是哪种类型。那么我们可以向其中添加对象吗?当然不可以,因为并不知道实际是哪种类型,所以不能添加任何类型,这是不安全的。而单独的 List list ,也就是没有传入泛型参数,表示这个 list 持有的元素的类型是 Object,因此可以添加任何类型的对象,只不过编译器会有警告信息。