java中的泛型

java泛型是很有用的东西,我们在写代码过程中也经常接触,特别是在使用java集合类的时候,更为常见。泛型实现了参数化类型的概念,使代码可以应用于多种类型。

一、泛型类

public class TwoTuple<A, B> {
    private final A first;
    private final B second;
    public TwoTuple(A a, B b) {
        this.first = a;
        this.second = b;
    }
    public A getFirst() {
        return first;
    }
    public B getSecond() {
        return second;
    }
}

上面是常用的Tuple工具类,它就是一个泛型类,这个类有两个泛型参数A和B。使用方法如下:

public static void main(String[] args) {
    TwoTuple<String, Integer> twoTuple = new TwoTuple<String, Integer>("123", 123);
}

在实例化类的时候类名后需要用尖括号指定类型。

泛型类也可以被继承,下面的代码在继承的时候指定了类型,然后它就不再是泛型类,在实例化的时候不需要用尖括号指定类型。

class StringIntegerTwoTuple extends TwoTuple<String,Integer>{
    StringIntegerTwoTuple(String s, Integer integer) {
        super(s, integer);
    }
    public static void main(String[] args) {
        StringIntegerTwoTuple stringIntegerTwoTuple = new StringIntegerTwoTuple("123", 123);
    }
}

当然,我们也可以让继承后的类仍然是泛型类,下面的ThreeeTuple类有三个泛型参数,前两个从TwoTuple继承而来,第三个是新加属性的类型。

public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
    private final C third;
    public ThreeTuple(A a, B b, C c) {
        super(a, b);
        this.third = c;
    }
    public C getThird() {
        return third;
    }
    public static void main(String[] args) {
        ThreeTuple<String, Integer, Long> threeTuple = new ThreeTuple<String, Integer, Long>("123", 123, 123L);
    }
}

二、泛型方法

我们除了把泛型用在类上,也可以在类中包含参数化方法,而这个方法所在的类,可以是泛型类,也可以不是泛型类。要定义泛型方法,只需将泛型参数列表置于返回值之前:

class GenericMethods{
    public <T> void f(T x){
        System.out.println(x.getClass().getName());
    }
    public static void main(String[] args) {
        GenericMethods obj = new GenericMethods();
        obj.f("123");
        obj.f(123);
        obj.f(123.0);
        obj.<Integer>f(123);
    }
}

在调用泛型方法的时候,不需要显式指定参数类型,java会自动进行类型推断。也可以显式指定类型,如上面最后一句代码。在方法名前用尖括号指出类型。

借助类型推断,可以帮助我们在创建泛型对象时简化代码。可以看到我们在创建TwoTuple对象时不需要显式的指出泛型参数的类型了

public final class TupleUtil {
    public static <A, B> TwoTuple<A, B> tuple(A a, B b) {
        return new TwoTuple<A, B>(a, b);
    }
    public static void main(String[] args) {
        //借助泛型方法简化后的写法
        TwoTuple<String, Integer> tuple = tuple("abc", 123);
        //简化前的写法
        TwoTuple<String, Integer> twoTuple = new TwoTuple<String, Integer>("123", 123);
    }
}

三、边界

java为了兼容JDK5前的代码,在实现泛型的时候,采用了“类型擦除”,虽然我们的类参数使用了泛型,java最后还是会当作Object来处理,只不过在编译时做了类型检查,在我们获取数据时做隐式转换罢了。

试想下面的例子,如果我们可以确定类型T会实现HasColor接口,我们想在Colored类中调用HasColor接口承诺实现的方法要怎么办?

interface HasColor{
    java.awt.Color getColor();
}
class Colored<T>{
    T item;
    Colored(T item) {
        this.item = item;
    }
    
    T getItem() { return item; }
    
    java.awt.Color color(){return item.getColor();} 
}

为解决这个问题,我们就需要指定边界。边界有上界和下界两种,用extends关键字来表示上界,用super关键字来指定下界。在上面这个例子中我们需要指定泛型参数T的上界是HasColor。T extends HasColor表示所有的类型T都会实现HasColor接口,即HasColor是它的上界。下界的例子会随后看到。

class Colored<T extends HasColor>{
    T item;
    Colored(T item) {
        this.item = item;
    }
    T getItem() { return item; }
    java.awt.Color color(){return item.getColor();}
}

上界类型不要求必须是接口,类也可以,而且还可以是多个,但它和java的继承机制一致,上界类型中只能有一个类,可以有多个接口,且类必须排在最前。

interface HasColor{
    java.awt.Color getColor();
}
class Dimension{
    public int x,y,z;
}
class ColoredDimension<T extends Dimension & HasColor>{
    //这样会报错,类必须排在第一位,而且只能有一个
//class ColoredDimension<T extends HasColor & Dimension>{
    T item;
    ColoredDimension(T item) {
        this.item = item;
    }
    T getItem() { return item; }
    java.awt.Color color(){return item.getColor();}
    int getX(){return item.x;}
    int getY(){return item.y;}
    int getZ(){return item.z;}
}

class Bonded extends Dimension implements HasColor{
    @Override
    public Color getColor() {
        return null;
    }
}
class TestMain{
    public static void main(String[] args) {
        Bonded bonded = new Bonded();
        ColoredDimension<Bonded> coloredDimension = new ColoredDimension<Bonded>(bonded);
    }
}

四,协变、逆变和通配符

首先看看协变和逆变的定义:

如果S是T的子类型,如果可以把Collection[S]当作Collection[T]的子类型,就可以说Collection与它的参数类型保持协变

果S是T的子类型,如果可以把Collection[T]当作Collection[S]的子类型,就可以说Collection与它的参数类型保持

在java里数组默认就是协变的,但集合是不协变的:

Object[] array=new String[10];//正确
List<String> stringList = new ArrayList<String>();
List<Object> objectList=stringList;//错误

因为上转型会有风险,我如果将放Apple的集合转型为Fruit的集合,再放入Orange,这个理论是支持的,但结果并不是我们想要的。

class Fruit{}
class Apple extends Fruit{}
class Jonathan extends Apple{}
class Orange extends Fruit{}

java为解决这个问题,引入了通配符(?)。通配符代表你不需要知道它的实际类型,只知道它的上界或下界,或者把它当作Object来看待。

首先看协变,为避免类型转换风险,协变数据只允许读取,不允许添加和修改数据:

class convert{
    public static void main(String[] args) {
        List<Apple> apples = new ArrayList<Apple>();
        apples.add(new Apple());
        
//        List<Fruit> fruits=apples; //不能转换
        List<? extends Fruit> fruits=apples;
        Fruit fruit = fruits.get(0);
//        fruits.add(new Apple()); //不能添加数据
//        fruits.add(new Fruit());//不能添加数据  
    }
}

再来看看逆变,接上面的例子,现在我们集合可以操作数据。

List<? super Apple> apples2 = apples;
apples2.add(new Apple());
apples2.add(new Jonathan());


PS:该文许多代码摘自《Thinking in java》,大家有兴趣可以翻看关于泛型章节,真是太复杂了

你可能感兴趣的:(java,泛型)