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的作用一样。

总结:泛型给我们带来了方便,同时也要注意他的特点和用法。 

参考资料:

https://www.cnblogs.com/dengchengchao/p/9717097.html

02-12 09:50