Generetics| 青训营笔记

99 阅读2分钟

这是我参与「第四届青训营 」笔记创作活动的第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。