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》,大家有兴趣可以翻看关于泛型章节,真是太复杂了