美文网首页
Java泛型

Java泛型

作者: 瑜小贤 | 来源:发表于2020-04-03 00:55 被阅读0次

1. 为什么我们需要泛型

  • 可以用于多种数据类型执行相同的代码
  • 可以帮助指定类型,在编译期间就发现错误,避免强制类型转换

2. 泛型类、泛型接口和泛型方法

2.1 泛型类

public class NormalGeneric<T>{
        private T data;

        public T getData() {
            return data;
        }

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

public class NormalGeneric2<T, K>{
        private T data;
        private K instance;

        public T getData() {
            return data;
        }

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

2.2 泛型接口

pubilc interface Genertor<T>{
    public T next();
}

//1. 实现类,可以继续使用泛型
public class ImplGenertor<T> implements Genertor<T>{
    @Override
    public T next(){
        return null;
    }
}

//2. 实现类,可以直接指定类型
public class ImplGenertor2 implements Genertor<String>{
    @Override
    public String next(){
        return null;
    }
}

2.3 泛型方法

泛型方法没有规定一定要声明在泛型类/泛型接口中。

public class GenericMethod{
    //<T>是必须的,且可以是多个,如<T, K>,没有则不是泛型方法,但没有不代表会报错
    public <T> T genericMethod(T...a){
        return a[a.length/2];
    }

    public static void main(String[] args){
        GenericMethod test = new GenericMethod();
        System.out.println(test.<String>genericMethod("mark","andy","simon"));
    // <String>可省略
        System.out.println(test.genericMethod(1, 3, 5));
    }
        
        //不是泛型方法,只是使用了NormalGeneric<Number>这个泛型类作为形参而已
        public void show(NormalGeneric<Number> obj){
        }

        //会报错!!!,因为E泛型未声明过
        public <T> T show(E obj){
        }

        //会报错!!!,因为show所在的类不是泛型类,本身show也不是泛型方法,T泛型未声明过
        public void show(T obj){
        }
}
public class GenericMethod3{
    static class Fruit{
        @Override
        public String toString(){
            return "fruit";
        }
    }

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

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

    static class GenerateTest<T>{
        //泛型类的T只影响到这个普通方法
        public void show_1(T t){
            System.out.println(t.toString());
        }

        public <E> void show_2(E t){
            System.out.println(t.toString());
        }

        //此T与泛型类的T无关
        public <T> void show_3(T t){
            System.out.println(t.toString());
        }
    }

    public static void main(String[] args){
        Apple apple = new Apple();
        Person person = new Person();
        
        GenerateTest<Fruit> generate = new GenerateTest<>();
        generate.show_1(apple); //不会报错,因为Apple是Fruit子类,泛型正是为了限制类型的
        generate.show_1(person); //报错 !!!

        generate.show_2(apple); //不报错
        generate.show_2(person); //不报错,因为show_2是泛型方法,与泛型类的泛型无关
        generate.show_3(person); //不报错,同上
    }
}

3. 泛型的变量类型限定

public class ArrayAlg{
    public static <T> T min(T a, T b){
        if(a.compareTo(b) > 0) return a; else return b;
    }
    //如何保证传入的a,b都是有compareTo方法的呢?用泛型!
    //Comparable可以是类或接口,且可以是多个,如果类和接口混用,类要放在第一位,因为java是单继承多实现
    //限定了T必须是Comparable子类,或者实现了Comparable接口
    public static <T,K extends ArrayList&Comparable> T min2(T a, T b){
        if(a.compareTo(b) > 0) return a; else return b;
    }
}
  • 类型限定在泛型类、泛型接口、泛型方法中都可以使用
  • 类型限定可以有多个,但如果接口和类混用,类要放第一个,因为java是单继承多实现

4. 泛型使用中的约束和局限性

public class Restrict<T>{
    private T data;

    public Restrict(){
      //不能实例化类型变量
      this.data = new T(); //报错!!!
    }
    //静态域或者静态方法里不能引用类型变量
    private static T instance; //报错!!!
    
    //静态方法 本身是泛型方法就行
    private static <T> void getInstance(T obj){} //不报错

    public void static main(String[] args){
        Restrict<double> test = new Restrict(); //报错!!!,基本类型不行,因为他不算是对象,只能用包装类型
        Restrict<Double> restrict = new Restrict(); //不报错
        Restrict<String> restrictStr = new Restrict(); //不报错

        if(restrict instanceof Restric<Double>) //报错!!!不支持instanceof
        if(restrict instanceof Restric<T>) //报错!!!不支持instanceof

        //getClass().getName()就是包名.Restrict,泛型类型被擦除了
        System.out.println(restrict.getClass() == restrictStr.getClass());

        Restrict<Double>[] restrictArray;
        Restrict<Double>[] restrictArray2 = new Restrict<Double>[10]; //会报错!!!

        private class Problem<T> extends Exception{} //会报错!!!

        public <T extends Throwable> void doWork(T t){
            try{
            }catch(T x){ //会报错!!! 不能捕获泛型类对象
            }
        }

        public <T extends Throwable> void doWorkSuccess(T x) throws T{
            try{
            }catch(Throwable e){ //不会报错
                throw x;
            }
        }
    }
}

约束和局限性

  • 不能实例化类型变量
  • 静态域或者静态方法里不能引用类型变量
    原因:泛型的类型在对象创建时才知道,而static先于构造函数执行,而此时泛型的类型是未知的
  • 静态方法 本身是泛型方法就行
  • 泛型类型不支持instanceof
  • getClass获得的只会是原生类型,泛型类型会被擦除
  • 可以声明泛型类的数组,但是不能初始化
  • 泛型类不能extends Exception/Throwable
  • 不能捕获泛型类对象

5. 泛型类型的继承规则

public class Employee{
    private String firstName;
    private String secondName;
    ...setter..getter...
}

public class Work extends Employee{}

public class Pair<T>{
    private T one;
    private T two;

    ...setter...getter...
    
    private static <T> void set(Pair<Employee> p){
    }
    
    public static void main(String[] args){
        //Pair<Employee>和 Pair<Worker>没有任何继承关系
        Pair<Employee> employeePair = new Pair<>();
        Pair<Worker> workerPair = new Pair<>();

        Employee employee = new Worker();  //多态,父类句柄 = 子类实例
        Pair<Employee> workerPair2 = new Pair<Worker>(); //报错!!!无继承关系

        Pair<Employee> pair = new ExtendPair<>(); //不报错,父类句柄 = 子类实例
        List<String> list = new ArrayList(); //不报错,经典例子

        set(employeePair); //不报错
        set(workerPair); //报错!!!,因为employeePair和workerPair没有继承关系,
        //如果想解决,使用通配符
    }
    //泛型类 可以继承或者扩展其他泛型类,比如List和ArrayList
    private static class ExtendPair<T> extends Pair<T>{
    }
}

6. 泛型中通配符类型

public class Food{}
public class Fruit extends Food{
    private String color;
    ...setter...getter...
}
public class Orange extends Fruit{}
public class Apple extends Fruit{}
public class HongFuShi extends Apple{}

public class GenericType<T>{
    private T data
    ...setter...getter...
}

public class WildChar{
    public static void print(GenericType<Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use(){
        GenericType<Fruit> a = new GenericType<>();
        print(a); //不报错
        GenericType<Orange> b = new GenericType<>();
        print(b); //报错!!!a和b没有继承关系
    }

    //<? extends Fruit> 表示类型的上界是Fruit,子类随便,不能是Fruit的父类了
    public static void print2(GenericType<? extends Fruit> p){
        System.out.println(p.getData().getColor());
    }

    public static void use2(){
        GenericType<Fruit> a = new GenericType<>();
        print2(a); //不报错
        GenericType<Orange> b = new GenericType<>();
        print2(b); //不报错,因为泛型可以是Fruit的子类了
        print2(new GenericType<Food>()); //报错!!!Food超出上界

        GenericType<? extends Fruit> c = a; //不报错
        GenericType<? extends Fruit> c = b; //不报错

        Apple apple = new Apple();
        Fruit fruit = new Fruit();
       //因为规定了上界,所以设置进的只知道一定是Fruit或Fruit的子类,但不能确定具体类型,所以为了安全访问数据,set会报错
        c.setData(apple); //报错!!!
        c.setData(fruit); //报错!!!
        
        //因为规定了上界,所以取出来的一定是Fruit或Fruit的子类,虽然不确定是哪个子类,但一定能用父类的对象表示,所以可以用Fruit接收
        Fruit x = c.getData(); 
        Orange x = c.getData(); //报错!!!
        Apple x = c.getData(); //报错!!!
    }





    //<? super Fruit> 表示类型的下界是Fruit,父类随便,不能是Apple的子类
    public static void printSuper(GenericType<? super Apple> p){
        System.out.println(p.getData());
    }

    public static void useSuper(){
        GenericType<Fruit> fruitType = new GenericType<>();
        GenericType<Apple> appleType = new GenericType<>();
        GenericType<HongFuShi> hongFuShiType = new GenericType<>();
        GenericType<Orange> orangeType = new GenericType<>();

        printSuper(fruitType); //不报错
        printSuper(appleType); //不报错
        printSuper(hongFuShiType); //报错!!!超越了下界
        printSuper(orangeType); //报错!!!Orange和Apple是同级,没有继承关系

        GenericType<? super Apple> x = new GenericType<>();
        x.setData(new Apple()); //不报错
        x.setData(new HongFuShi()); //不报错
        x.setData(new Fruit()); //报错!!!
        //TODO 重点!!!<? super Apple>时,set只能用Fruit或其子类,不能用父类
        //原因:HongFuShi 继承 Apple 继承 Fruit 继承 Food,
        //x的类型是Apple或者Fruit或者Food,但不能确定,对于编译器来说,只有当x类型传Apple或其子类时,才能安全的转型为Apple
        //所以,为了类型安全,set的时候,只能传Apple和Apple的子类

        Object obj = x.getData(); //不报错,返回Apple的超类,但是具体哪个不知道,只能返回Object类型
    }
}

7. 虚拟机是如何实现泛型的?

伪泛型
类型擦除

public class GenericRaw<T>{
    private T data;
    public T getData(){
        return data;
    }
    public void setData(T data){
        this.data = data;
    }
}

//在jdk里,做了泛型擦除之后,我们可以把它看成是
public class GenericRaw<Object>{
    private Object data;
    public Object getData(){
        return data;
    }
    public void setData(Object data){
        this.data = data;
    }
}

public class GenericRaw<T extends Comparable&Serializable>{
    private T data;
    public T getData(){
        return data;
    }

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

//擦除后,如果泛型是extends,用原生类型替换,且是第一个
//如果在代码中使用了Comparable,编译期会加入一个强制类型转换
public class GenericRaw<T extends ArrayList&Comparable>{
    private ArrayList data;
    public ArrayList getData(){
        return data;
    }

    public void test(){
        (Comparable)data.compareTo();
    }
    public void setData(ArrayList data){
        this.data = data;
    }
}
public class Theory{
    public static void main(String[] args){
        Map<String, String> map = new HashMap<>();
        map.put("mark", "18");
        System.out.println(map.get("mark"));
    }
}

//编译后,编译器强制加入类型转换
public class Theory{
    public static void main(String[] args){
        Map<String, String> map = new HashMap<>();
        map.put("mark", "18");
        System.out.println((String)map.get("mark"));
    }
}
public class Conflict{
    //还是会报错!!!因为会类型擦除,所以编译不通过
    public static void method(List<String> stringList){
        System.out.println("List");
    }

    public static void method(List<Integer> stringList){
        System.out.println("Integer");
    }

//下面两例,在jdk的编译中是允许的,但是在开发工具里大概率是不允许的,会报错
//jdk的编译实现中有三个判断:1.方法名 2.参数 3.返回值
//开发工具中有两个判断:1.方法名 2.参数
    public static String method(List<String> stringList){
        System.out.println("List");
        return “OK”;
    }

    public static Integer method(List<Integer> stringList){
        System.out.println("Integer");
        return 1;
    }
}

注:jdk虽然会擦除类型,但编译成的字节码中增加了许多属性,比如Signature,会记录泛型的原始类,称之为弱记忆。

相关文章

  • Java泛型教程

    Java泛型教程导航 Java 泛型概述 Java泛型环境设置 Java泛型通用类 Java泛型类型参数命名约定 ...

  • 第二十八课:泛型

    泛型出现之前 泛型出现之后 Java深度历险(五)——Java泛型

  • Kotlin 泛型

    说起 kotlin 的泛型,就离不开 java 的泛型,首先来看下 java 的泛型,当然比较熟悉 java 泛型...

  • java泛型中类型擦除的一些思考

    java泛型 java泛型介绍 java泛型的参数只可以代表类,不能代表个别对象。由于java泛型的类型参数之实际...

  • Java泛型

    参考:Java知识点总结(Java泛型) 自定义泛型类 自定义泛型接口 非泛型类中定义泛型方法 继承泛型类 通配符...

  • Java泛型—Java语法糖,只在编译有作用,编译后擦出泛型

    Java泛型—Java语法糖,只在编译有作用,编译后擦出泛型 在代码进入和离开的边界处,会处理泛型 Java泛型作...

  • JAVA 核心笔记 || [xxx] 泛型

    泛型 JAVA 的参数化类型 称为 泛型 泛型类的设计 Learn12.java 运行

  • 简单回顾Java泛型之-入门介绍

    什么时候开始有了Java泛型?什么是Java泛型?为什么要引入Java泛型?什么时候用到了泛型?可不可以给泛型下一...

  • Kotlin 泛型

    Kotlin 支持泛型, 语法和 Java 类似。例如,泛型类: 泛型函数: 类型变异 Java 的泛型中,最难理...

  • JAVA-泛型

    JAVA-泛型 sschrodinger 2018/11/15 简介 泛型是Java SE 1.5的新特性,泛型的...

网友评论

      本文标题:Java泛型

      本文链接:https://www.haomeiwen.com/subject/btxfphtx.html