java 泛型详解

1,泛型的定义以及存在意义

泛型,即“参数化类型”。本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。

泛型的好处其实就是约束,可以让我们编写的接口、类、方法等脱离具体类型限定而适用于更加广泛的类型,从而使代码达到低耦合、高复用、高可读、安全可靠的特点,避免主动向下转型带来的恶心可读性和隐晦的转换异常,尽可能的将类型问题暴露在IDE 提示和编译阶段。

一些常用的泛型类型变量:
E:元素(Element),多用于java集合框架
K:关键字(Key)代表java键值中的Value
V:值(Value) 代表java键值中的 Value
N:数字(Number)
T:类型(Type)表示具体的一个java类型
? : 通配符 表示不确定的 java 类型

public class NeedGeneric {
    static class C{
    }
    public static void main(String[] args) {
        List<Object> list=new ArrayList<Object>();
        list.add("A");
        list.add("B");
        list.add(new C());
        list.add(100);
        //1.将一个对象放入集合中,集合不会记住此对象的类型,
        //当再次从集合中取出此对象时,对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。
        //2.处取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。
        for (int i = 0; i < list.size(); i++) {
//            System.out.println(list.get(i));
            String value= (String) list.get(i);
            System.out.println(value);
        }
    }
}

所以使用泛型的意义在于

  1. 适用于多种数据类型执行相同的代码(代码复用)
  2. 泛型中的类型在使用时指定,不需要强制类型转换(类型安全,编译器会检查类型)

2,泛型类的使用

定义一个泛型类:public class GenericClass{}

public class GenericClass<T> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static void main(String[] args) {
        GenericClass<String> genericClass=new GenericClass<>();
        genericClass.setData("Generic Class");
        System.out.println(genericClass.getData());
    }
}

3,泛型接口的使用

定义一个泛型接口:public interface GenericIntercace{}

public interface GenericIntercace<T> {
     T getData();
}

实现泛型接口方式一:public class ImplGenericInterface1 implements GenericIntercace

public class ImplGenericInterface1<T> implements GenericIntercace<T> {
    private T data;

    private void setData(T data) {
        this.data = data;
    }
    @Override
    public T getData() {
        return data;
    }
    public static void main(String[] args) {
    	//实例化时确定泛型类型,参考List
        ImplGenericInterface1<String> implGenericInterface1 = new ImplGenericInterface1<>();
        implGenericInterface1.setData("Generic Interface1");
        System.out.println(implGenericInterface1.getData());
    }
}

实现泛型接口方式二:public class ImplGenericInterface2 implements GenericIntercace {}

//实现接口时已确定泛型类型,不同子类处理不同的业务,参考数据库Dao封装
public class ImplGenericInterface2 implements GenericIntercace<String> {
    @Override
    public String getData() {
        return "Generic Interface2";
    }

    public static void main(String[] args) {
        ImplGenericInterface2 implGenericInterface2 = new ImplGenericInterface2();
        System.out.println(implGenericInterface2.getData());
    }
}

4,泛型方法的使用

定义一个泛型方法: private static TgenericAdd(T a, T b) {}

public class GenericMethod1 {
    private static int add(int a, int b) {
        System.out.println(a + "+" + b + "=" + (a + b));
        return a + b;
    }

    private static <T> T genericAdd(T a, T b) {
        System.out.println(a + "+" + b + "="+a+b);
        return a;
    }

    public static void main(String[] args) {
        GenericMethod1.add(1, 2);
        GenericMethod1.<String>genericAdd("a", "b");
    }
}
public class GenericMethod3 {

    static class Animal {
        @Override
        public String toString() {
            return "Animal";
        }
    }

    static class Dog extends Animal {
        @Override
        public String toString() {
            return "Dog";
        }
    }

    static class Fruit {
        @Override
        public String toString() {
            return "Fruit";
        }
    }

    static class GenericClass<T> {

        public void show01(T t) {
            System.out.println(t.toString());
        }

        public <T> void show02(T t) {
            System.out.println(t.toString());
        }

        public <K> void show03(K k) {
            System.out.println(k.toString());
        }
    }

    public static void main(String[] args) {
        Animal animal = new Animal();
        Dog dog = new Dog();
        Fruit fruit = new Fruit();
        GenericClass<Animal> genericClass = new GenericClass<>();
        //泛型类在初始化时限制了参数类型
        genericClass.show01(dog);
//        genericClass.show01(fruit);

        //泛型方法的参数类型在使用时指定
        genericClass.show02(dog);
        genericClass.show02(fruit);

        genericClass.<Animal>show03(animal);
        genericClass.<Animal>show03(dog);
        genericClass.show03(fruit);
//        genericClass.<Dog>show03(animal);
    }
}

5,限定泛型类型变量

1,对类的限定:public class TypeLimitForClass<T extends List & Serializable>{}

public class TypeLimitForMethod {

    /**
     * 计算最小值
     * 如果要实现这样的功能就需要对泛型方法的类型做出限定
     */
//    private static <T> T getMin(T a, T b) {
//        return (a.compareTo(b) > 0) ? a : b;
//    }

    /**
     * 限定类型使用extends关键字指定
     * 可以使类,接口,类放在前面接口放在后面用&符号分割
     * 例如:<T extends ArrayList & Comparable<T> & Serializable>
     */
    public static <T extends Comparable<T>> T getMin(T a, T b) {
        return (a.compareTo(b) < 0) ? a : b;
    }

    public static void main(String[] args) {
        System.out.println(TypeLimitForMethod.getMin(2, 4));
        System.out.println(TypeLimitForMethod.getMin("a", "r"));
    }
}

2,对方法的限定:public static<T extends Comparable>T getMin(T a, T b) {}

public class TypeLimitForClass<T extends List & Serializable> {
    private T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static void main(String[] args) {
        ArrayList<String> stringArrayList = new ArrayList<>();
        stringArrayList.add("A");
        stringArrayList.add("B");
        ArrayList<Integer> integerArrayList = new ArrayList<>();
        integerArrayList.add(1);
        integerArrayList.add(2);
        integerArrayList.add(3);
        TypeLimitForClass<ArrayList> typeLimitForClass01 = new TypeLimitForClass<>();
        typeLimitForClass01.setData(stringArrayList);
        TypeLimitForClass<ArrayList> typeLimitForClass02 = new TypeLimitForClass<>();
        typeLimitForClass02.setData(integerArrayList);

        System.out.println(getMinListSize(typeLimitForClass01.getData().size(), typeLimitForClass02.getData().size()));

    }

    public static <T extends Comparable<T>> T getMinListSize(T a, T b) {
        return (a.compareTo(b) < 0) ? a : b;
    }

6,泛型中的约束和局限性

  • 不能实例化泛型类
  • 静态变量或方法不能引用泛型类型变量,但是静态泛型方法是可以的
  • 基本类型无法作为泛型类型
  • 无法使用instanceof关键字或==判断泛型类的类型
  • 泛型类的原生类型与所传递的泛型无关,无论传递什么类型,原生类是一样的
  • 泛型数组可以声明但无法实例化
  • 泛型类不能继承Exception或者Throwable
  • 不能捕获泛型类型限定的异常但可以将泛型限定的异常抛出
public class GenericRestrict1<T> {
    static class NormalClass {

    }

    private T data;

    /**
     * 不能实例化泛型类
     * Type parameter 'T' cannot be instantiated directly
     */
    public void setData() {
        //this.data = new T();
    }

    /**
     * 静态变量或方法不能引用泛型类型变量
     * 'com.jay.java.泛型.restrict.GenericRestrict1.this' cannot be referenced from a static context
     */
//    private static T result;

//    private static T getResult() {
//        return result;
//    }

    /**
     * 静态泛型方法是可以的
     */
    private static <K> K getKey(K k) {
        return k;
    }

    public static void main(String[] args) {
        NormalClass normalClassA = new NormalClass();
        NormalClass normalClassB = new NormalClass();
        /**
         * 基本类型无法作为泛型类型
         */
//        GenericRestrict1<int> genericRestrictInt = new GenericRestrict1<>();
        GenericRestrict1<Integer> genericRestrictInteger = new GenericRestrict1<>();
        GenericRestrict1<String> genericRestrictString = new GenericRestrict1<>();
        /**
         * 无法使用instanceof关键字判断泛型类的类型
         * Illegal generic type for instanceof
         */
//        if(genericRestrictInteger instanceof GenericRestrict1<Integer>){
//            return;
//        }

        /**
         * 无法使用“==”判断两个泛型类的实例
         * Operator '==' cannot be applied to this two instance
         */
//        if (genericRestrictInteger == genericRestrictString) {
//            return;
//        }

        /**
         * 泛型类的原生类型与所传递的泛型无关,无论传递什么类型,原生类是一样的
         */
        System.out.println(normalClassA == normalClassB);//false
        System.out.println(genericRestrictInteger == genericRestrictInteger);//
        System.out.println(genericRestrictInteger.getClass() == genericRestrictString.getClass()); //true
        System.out.println(genericRestrictInteger.getClass());//com.jay.java.泛型.restrict.GenericRestrict1
        System.out.println(genericRestrictString.getClass());//com.jay.java.泛型.restrict.GenericRestrict1

        /**
         * 泛型数组可以声明但无法实例化
         * Generic array creation
         */
        GenericRestrict1<String>[] genericRestrict1s;
//        genericRestrict1s = new GenericRestrict1<String>[10];
        genericRestrict1s = new GenericRestrict1[10];
        genericRestrict1s[0]=genericRestrictString;
    }

}

7,通配符类型

  1. <? extends Parent> 指定了泛型类型的上界

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

  • 如果传入的类型不是 E 或者 E 的子类,编译不成功
  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
  1. <? super Child> 指定了泛型类型的下界

用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object

  1. <?> 指定了没有限制的泛型类型

对于不确定或者不关心实际要操作的类型,可以使用无限制通配符(尖括号里一个问号,即 <?> ),表示可以持有任何类型

8,泛型的擦除

1、什么是泛型的擦除?

泛型的擦除是指:java编译器在编译阶段将泛型的代码转变成非泛型代码的过程。
泛型类型只有在静态类型检查期间才出现,在此以后程序中的所有泛型类型都将被擦除,替换为他们的非泛型上界。如List这样的类型将被擦除为List,而普通的类型变量在未指定边界的情况下将被擦除为Object

2、泛型擦除的原因

因为泛型不是java出现时就有的组成部分,泛型是Java SE5才出现的,那么为了兼容JavaSE5之前的非泛型类库,所以java才采用了折中的办法,即擦除。

3、泛型擦除的代价

泛型不能用于显式地引用运行时类型的操作之中,例如转型、instanceof操作和new表达式。因为所有关于参数的类型信息都丢失了。

下面看一个泛型擦除的例子:

public class FanXing {

    public static void main(String[] args) {
        List<String> stringList=new ArrayList<>();
        List<Integer> integerList=new ArrayList<>();
        Class stringClass=stringList.getClass();
        Class integerClass=integerList.getClass();
        System.out.print(stringClass == integerClass);     //true
    }

}

在代码的第4行和第5行,我们分别定义了一个接受String类型的List和一个接受Integer类型的List,按照我们正常的理解,泛型ArrayList虽然是相同的,但是我们给它传了不同的类型参数,那么c1和2的类型应该是不同的。但是结果恰恰想法,运行程序发现二者的类型时相同的。然后我们来反编译这个类来看一下它的字节码:
在这里插入图片描述
由上图我们可以看出无论是ArrayList< String>还是ArrayList< Integer>都被擦除为List,最终都是Object去getClass,可见没有任何一点类型信息的事。
因此:在泛型代码的内部,无法获得任何有关泛型参数类型的信息
接下来我们再看下一个例子:

public class FanXing<T> {

    public void test(Object arg){
        if (arg instanceof T){}     //error
        T a=new T();                //error
        T[] array=new T[100];       //error
    }

}

当我们尝试去创建T的对象以及instanceof等操作时,都是不能编译通过的,因为编译器在编译的时候已经将类型T给擦除了。
任何在运行时需要知道确切类型信息的操作都将无法工作。

参考文档:
https://www.jianshu.com/p/986f732ed2f1
https://blog.csdn.net/sinat_29774479/article/details/75072964
https://blog.csdn.net/s10461/article/details/53941091
https://blog.csdn.net/u013277209/article/details/77170247

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值