这是我参与「第四届青训营 」笔记创作活动的第10天
Java Generics 泛型 :可以用来进行类型定义的一种机制,避免类型转换,增加可读性
eg.
public class Position<T> {
private T sth;
}
or clearer:
public class Position<T extends Creature> {
private T sth;
}
显式地表达“Position里要放一个sth,但不确定是什么类型的”,T目前不存在只是一个占位符、一个类型的参数化。类似c++中的template。
// Stack use generics:
class Stack<A> {
void push(A a) {...}
A pop() {...}
}
String s = "Hello";
Stack<String> stack = new Stack<String>();
stack.push(s);
...
s = stack.pop();
用类参数定义了泛型类型,使用时将A替换成了具体的String类型,而避免了手动类型转化cast,增加代码可读性。
public class GenericMethods {
public <T> void f (T x) {
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f("");
gm.f(1);
gm.f(1.0);
gm.f(1.0f);
gm.f('c');
gm.f(gm);
}
}
运行结果:
java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
java.lang.Character
GenericMethods
方法中传入的类型本身也可以设置成参数,autoboxing机制使基础数据类型1变成整型对象Integer。直接写T时等价于T extends Object,编译器会把T这个泛型擦除,实现方式是将类型参数用边界类型替换,在这个例子中即是用Object替换T,将具体类型忽略,擦除到了边界【用extends申明对参数类型的限制条件,可以用'&'表示既是A的子类又是B的实现类,不支持多继承,多个并列对象中只有一个类,class写在前面后面都是接口】类型。(为了编译后能向低版本兼容)
ArrayList<Integer> arr = new ArrayList<>();
arr.add(1);
编译器会检查此处add的实参是不是Integer,虽然1是Int类型但因为autoboxing自动包装机制的存在,会被转化成Integer,故可以通过类型检查。
对泛型的处理集中在编译期,具体来说在编译时编译器会将泛型类的类型参数都用边界类型替换;对于传入对象给方法形参的指令,执行类型检查,看传入对象是否是类型参数所指定的类型;对于返回类型参数表示对象的指令,执行类型检查,插入一个自动向下转型,将对象从边界类型向下转型到类型参数所表示的类型。在字节码世界里是没有泛型概念的。如果想生成泛型对象,可以利用设计模式。
class Fruit {}
class Apple extends Fruit {}
class Plate<T> {
private T item;
public Plate(T t) {item = t;}
public void set(T t) {item = t;}
public T get() {return item;}
}
public class GenericMethods {
public static void main(String[] args) {
Plate<Fruit> p = new Plate<Apple>(new Apple()); // ***
}
}
编译器报错,装Apple的List将持有Apple和Apple的子类型对象,Fruit的List将持有Fruit和Fruit的子类型对象,这其中包括Apple,但它在类型上不能等价于Fruit的List。即Apple是一种Fruit,但Apple的List不是一种Fruit的List。