《Java基础知识》Java 泛型详解
JDK 1.5 之后,Java 通过泛型解决了容器类型安全这一问题,而几乎所有人接触泛型也是通过Java的容器。那么泛型究竟是什么?
泛型的本质是参数化类型;也就是说,泛型就是将所操作的数据类型作为参数的一种语法。
先对比一下有泛型和无泛型的写法。
无泛型
public class Dog{ String name; int age; /** * 带参构造函数 * @param name * @param age */ public Dog(String name,int age){ this.age = age; this.name = name; } public void ptint(){ System.out.println("name = "+this.name+";age = "+age); } }
public class Node { private Object obj; public Object get(){ return obj; } public void set(Object obj){ this.obj=obj; } public static void main(String[] argv){ Dog dog=new Dog("花花",4); Node node=new Node(); node.set(dog); Dog dog2=(Dog)node.get(); dog2.ptint(); } }
运行结果:
使用泛型
public class Node<T> { private T obj; public T get(){ return obj; } public void set(T obj){ this.obj=obj; } public static void main(String[] argv){ Dog dog=new Dog("花花",4); Node<Dog> node=new Node(); node.set(dog); Dog dog2=node.get(); dog2.ptint(); } }
分析:
第一种:通过Object 类接收,最后使用的时候需要强制转换后才能正常使用,并且强转是否正确,只有在运行时才能知道是否正确。
第二种:通过 T 接收,在使用的使用<> 中指定本次使用具体类,可以很好的做到要用什么传什么,并且改写法,可以在编译的时候就发现类型不匹配问题。T接收,代表是Object类,所以可以传如任何对象。
在上述 Node 类中,我们假设需要使用使用传入类Dog的方法。显然obj.ptint() 是不行,obj能用只有Object的方法。那要如何使用呢?
思路是:让T不再代表Object,而是我指定的子类或者父类。
案例:
public class Node<T extends Dog> { private T obj; public T get(){ return obj; } public void set(T obj){ this.obj=obj; } public void printNode(){ obj.ptint(); } public static void main(String[] argv){ Node<Dog> node=new Node<>(); node.set(new Dog("花花",4)); node.printNode(); } }
运行结果:
到这里,已经感受到泛型的美好:那就是方便。
注意:泛型传递的时候是不变的。
案例:
public class Dog1 extends Dog { /** * 带参构造函数 * * @param name * @param age */ public Dog1(String name, int age) { super(name, age); } }
public class PublicTest { public static void main(String[] args) { Node<Dog> node0 = new Node<>();
node0.set(new Dog("花花",4)); // Node<Dog1> node0 = new Node<>(); 是会编译报错的 main1(node0); } public static void main1(Node<Dog> node) { node.printNode(); } }
运行结果:
如果将上述代码的:Node<Dog> node0 = new Node<>(); 改成被注释的代码。会编译报错。也就是泛型是Node<Dog>只能传 Node<Dog>, 计算Dog1是Dog的子类也不行。上述写死的方式实在难受,所以Java也提供了解决方式。
案例:
public static void main(String[] args) { Node<Dog1> node0 = new Node<>(); //不会编译报错,并且可以正常执行 node0.set(new Dog1("花花",6)); main1(node0); } public static void main1(Node<? extends Dog> node) { node.printNode(); }
运行结果:
上述案例中mian1方法的入参:Node<? extends Dog> node 。这样就允许传入子类了。
这个时候我们再考虑一下,既然有向下泛型,有没有向上泛型呢,答案是有的:mian1方法的入参:Node<? extends Dog> node 中的extends 改成supper就可以了。
这个案例,大家自己动手试试。
案例:
public class Node<T> { private T obj; public T get(){ return obj; } public void set(T obj){ this.obj=obj; } public void printNode(){ System.out.println("只能使用Object方法"); } }
public class PublicTest { public static void main(String[] args) { Node<Dog1> node0 = new Node<>(); //不会编译报错,并且可以正常执行 node0.set(new Dog1("花花",6)); main1(node0); } public static void main1(Node<?> node) { node.printNode(); } }
运行结果:
上述案例:支持传入向上泛型和向下泛型,不建议使用。 因为这类泛型回到一个问题,Node<T> 类中只能使用Object 的几个方法,局限很大。
泛型使用中的注意点。
案例:
import java.util.concurrent.ThreadLocalRandom; public class PublicTest { static <T> T[] toArray(T... args) { return args; } static <T> T[] pickTwo(T a, T b, T c) { switch(ThreadLocalRandom.current().nextInt(3)) { case 0: return toArray(a, b); case 1: return toArray(a, c); case 2: return toArray(b, c); } throw new AssertionError(); // Can't get here } public static void main(String[] args) { String[] attributes = pickTwo("Good", "Fast", "Cheap"); } }
运行结果:
分析:上述代码第一感觉返回回来的是一个String[] 数组才对,为什么会出现类型转换错误。文章中已经提到:T 会被表示成Object。所以返回回来的数组Java也会处理成Object[] ,Object[] 是不能转换成 String[] 的。
既然知道原因了:我们试着修改一下代码:将T继承到String。这样java返回数组就会处理成String[]。
import java.util.concurrent.ThreadLocalRandom; public class PublicTest { static <T extends String> T[] toArray(T... args) { return args; } static <T extends String> T[] pickTwo(T a, T b, T c) { switch(ThreadLocalRandom.current().nextInt(3)) { case 0: return toArray(a, b); case 1: return toArray(a, c); case 2: return toArray(b, c); } throw new AssertionError(); // Can't get here } public static void main(String[] args) { String[] attributes = pickTwo("Good", "Fast", "Cheap"); System.out.println(attributes[0]+" : "+attributes[1]); } }
运行结果:
基类劫持:
案例:
public interface Node<T> { T play(); }
public class Dog implements Node<Integer> { @Override public Integer play() { return null; } }
public class Dog1 extends Dog implements Node<String> { //... }
编译报错:因为基类已经实现 Node<Integer> 那么子类也只能实现 Node<Integer>。
Java泛型中的标记符含义:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型
S、U、V - 2nd、3rd、4th types 和T的作用一样。
总结:泛型给我们带来了方便,同时也要注意他的特点和用法。
参考资料: