认识泛型

一、泛型的概念

泛型实现了参数化类型的概念,使代码可以用于多种类型

二、泛型的目的
  1. 希望类和方法能够具备最广泛的表达能力
  2. 用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性
三、泛型的使用
  1. 普通泛型类

    public class NormalGenericsClass<T> {
        private T key;
        public Generics(T key){
            this.key = key;
        }
        public T getKey() {
            return key;
        }
        public void setKey(T key) {
            this.key = key;
        }
    }
    
    
    // 泛型类实例
    NormalGenericsClass<Integer> a = new NormalGenericsClass<Integer>(0);
    // Java7 开始支持省略后面的参数类型
    NormalGenericsClass<Integer> a = new NormalGenericsClass<>(0);
  2. 普通泛型接口

    // Java中的 Comparator接口
    public interface Comparator<T> {
    
        int compare(T o1, T o2);
    
        boolean equals(Object obj);
    }
    
    // 接口实现例子 java.text.Collator
    public abstract class Collator implements java.util.Comparator<Object>, Cloneable
    {
        //...主体代码
    }
  3. 普通泛型方法

    private <T> int normalGenericsMethod(Generics<T> i){ 
    // <T> 表示声明 T为泛型,写在返回值类型之前
    // 也可以声明多个泛型,如:<K,V>
        return 0;
    }
    
    // 这是错误的,会提示 Cannot resolve symbol 'T'
    /*private int normalGenericsMethod(Generics<T> i){ 
            return 0;
    }*/

    有上界的泛型:
    上界:通过关键字extends来表示给定的上界,那么参数就必须是给定的上界或者其子类型。上界可以是某个具体的类或接口,也可以是其他参数

  4. 上界为具体的类

    public class GenericsUpperBound<T extends Number> extends NormalGenericsClass<T> {
        public GenericsUpperBound(T key){
            super(key);
        }
    }
  5. 上界为具体的接口

    public <T extends Comparator> T compareWith(T[] arr){
        T start = arr[0];
           for(int i = 1; i < arr.length; i++){
               System.out.print(start.equals(arr[i]));
           }
           return start;
     }
  6. 上界为其他类型参数

    public class OtherUpperBound<T>{
        public <T extends E> void otherArgs(NormalGenericsClass<E> a){
            // E是OtherUpperBound的类型参数,T是otherArgs方法的类型参数
            // T的上界限定为E
        }
    }
    
    //例子:
    OtherUpperBound<Number> a = new OtherUpperBound<>;
    OtherUpperBound<Integer> b = new OtherUpperBound<>;
    a.otherArgs(b);
四、通配符
  1. 有限定通配符

    //重写6中的方法
    public void otherArgs(NormalGenericsClass<? extends E> a){
    
    }

    取自《Java编程的逻辑》8.2
    <T extends E><? extends E>的区别
    <T extends E>:用于定义类型参数,它声明了一个类型参数T,可放在泛型类定义中类名后面、泛型方法返回值前面
    <? extends E>:用于实例化类型参数,它用于实例化泛型变量中的类型参数,只是这个类型是未知的,只知道是E或E的某个子类型

  2. 无限定通配符

    public int demo(OtherUpperBound<?> a){
    
    }
    //  这两个等效
    public <T> int demo(OtherUpperBound<T> a){
    
    }

    通配符重要限制: 只能读,不能写

    取自《Java编程的逻辑》8.2
    总结:
    1) 通配符形式都可以用类型参数的形式来替代,通配符能做的,用类型参数都能做
    2) 通配符形式可以减少类型参数,形式上往往更为简单,可读性也更好,所以,能用通配符的就用通配符
    3) 如果类型参数之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,则只能用类型参数
    4) 通配符形式和类型参数往往配合使用,定义必要的类型参数,使用通配符表达依赖,并接受更广泛的数据类型

  3. 超类型通配符(无法用类型参数替代)

    public int demo(OtherUpperBound<? super E> a){
    
    }

    取自《Java编程的逻辑》8.2
    总结:
    1)通配符的目的是为了使方法接口更为灵活,可以接受更为广泛的类型
    2)

五、泛型的局限性
  1. 父类实现了一个泛型接口,子类希望自定义泛型接口中的方法,只能重写父类的实现

    class Base implements Comparable<Base>
    
    class Child extends Base
    
    // 希望重写Comparable的比较方法
    /*
        class Child extends Base implements Comparable<Child>
        // 错误,因为类型擦除的原因,实际实现的都是Comparable接口,接口不允许被实现两次
    */
    
    // 正确重写Comparable的比较方法的方式
    class Child extends Base {
        @Override
        public int compareTo(Base o){
            if(!(o instanceof Child){
                throw new IllegalArgumentException();
            }
            Child c = (Child)o;
            \\实现代码
            return 0;
        }
        \\其他代码
    }
  2. 类型参数不能作为静态变量和静态方法的类型

    Class Normal<T>{
        public static demo1(T param){
            // 错误的方法
        }
        public static <E> void demo2(E param){
            // 正确的方法
        }
    }
  3. 不能通过类型参数创建对象

        T a = new T(); // error Type parameter 'T' cannot be instantiated directly
六、泛型的使用细节
  1. 对于不同传入的类型实参,生成的相应对象实例一样

    Generics<Integer> demo = new Generics<>(123);
    Generics<String> demo2 = new Generics<>("test");
    System.out.println(demo.getClass() == demo2.getClass()); // true
    

    Java中的泛型是通过类型擦除实现的,类型参数在编译时会被替换为Object

  2. 静态方法和泛型
    静态方法无法访问类上定义的泛型;如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

    /**
     * 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
     * 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
     * 如:public static void show(T t){..},此时编译器会提示错误信息:
          "StaticGenerator cannot be refrenced from static context"
     */
    public static <T> void show(T t){
    
    }
    
  3. 泛型的上下边界添加,必须与泛型的声明一起

    //在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,
    即在泛型声明的时候添加
    //public <T> T showKeyName(Generic<T extends Number> container)
    编译器会报错:"Unexpected bound"
    public <T extends Number> T showKeyName(Generic<T> container){
        System.out.println("container key :" + container.getKey());
        T test = container.getKey();
        return test;
    }
  4. 基本类型不能用于实例化类型参数
    泛型要求能包容的是对象类型,基本类型在java中不属于对象

  5. 运行时类型查询只适用于原始类型

    NormalGenericsClass<Integer> a = new NormalGenericsClass<>(0);
    
    a instanceof NormalGenericsClass<Integer>;
    // Error Illegal generic type for instanceof
    
    a instanceof NormalGenericsClass<T>;
    // Error Cannot resolve symbol 'T'
    
    a instanceof NormalGenericsClass; // Pass
    a instanceof NormalGenericsClass<?>; // Pass
    
    a.getClass(); // class com.example.demo.generics.NormalGenericsClass
  6. 类型推断只对赋值操作有效
    Eg:

    public class New{
        public static <K,V> Map<K,V> map(){
            return new HashMap<K,V>();
        }
    }
    
    /**
    *       -- 方法中传参
    * 这时编译器不会执行类型判断。在这种情况下,编译器认为:调用泛型方法后,  
    * 其返回值被赋给一个Object类型的变量
    */
    public class Test{
        public static void main(String args[]){
            fun(New.map());
        }
    }
    
    public class Test{
        public static void main(String args[]){
            fun(New.<String, Integer)map()); // 显示地指明类型
        }
    }

参考资料:
[1]《Java编程的逻辑》
[2]《Thinking in Java》
[3] https://blog.csdn.net/s10461/article/details/53941091
[4] https://www.cnblogs.com/lwbqqyumidi/p/3837629.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值