Java之泛型

1.什么是泛型

1.泛型本质是指类型参数化, 允许在定义类、接口、方法时使用, 当使用时指定具体类型
2.所有使用该泛型参数的地方都被统一化 , 保证类型一致, 如果未指定具体类型, 默认是Object类型
3.集合体系中的所有类都增加了泛型, 泛型也主要用在集合

2.基础

  • 泛型类

    public interface Node<T> {
        public T next();
    }
    

    总结:

    1. 泛型的类型不能是简单类型。
    2. instanceof操作的泛型对象不能是具体类型
  • 泛型方法

    public class Test1<T> {
    
    	// 与类所声明的类型一致
        private T t;
    
    	// 此方法不是泛型方法, 因为类型已经在初始化类时已经确定
    	// 此方法的入参与类所声明的类型一致
        private void test1(T t) {
        }
    
    	// 此方法是泛型方法
    	// 此方法的入参与类所声明的类型不一致, 即此处的T为任意类型
        private <T> void test2(T t) {
        }
    
    	// 如果该方法为静态的, 则必须需要定义为泛型方法
        private static <T> void test3(T t) {
        }
    }
    
    public class Test2 {
    
    	// 此方法是泛型方法, T为任意类型
        private <T> void test1(T t) {
        }
        
    }
    

    总结:

    1. 在调用方法的时候指明泛型的具体类型即为泛型方法
    2. 泛型类中, 存在方法入参使用泛型且被定义为静态, 则该方法必定为泛型方法
    3. 入参"T"只是个标识, 26个字母都可使用且可以任意组合, 但应尽量具有含义
  • 泛型类派生出的子类

    // 定义上述接口泛型实现类
    public class SubNode<T> implements Node<T>{
        @Override
        public T next() {
            return null;
        }
    }
    
    // 定义上述接口具体实现类
    // 此时T将指定为具体类型String
    public class SubNode implements Node<String> {
        @Override
        public String next() {
            return "";
        }
    }
    
  • 泛型数组

    • 不能创建一个确切的泛型类型的数组, 除非是通配符, 即:

      // X
      List<String>[] arr1 = new ArrayList<String>[1];
      
      // ✔
      List<?>[] arr2 = new ArrayList<?>[1];
      // 同时下面这种方式也是可行的
      List<String>[] arr3 = new ArrayList[1];
      
    • 具体原因可查看官方说明:

      // 例如可以使用此声明方式
      List<String>[] lsa = new List[1];
      Object o = lsa;
      Object[] oa = (Object[]) o;
      
      List<Integer> li = new ArrayList<Integer>();
      li.add(new Integer(3));
      // 不可靠的, 但通过了运行时存储检查
      oa[1] = li;
      
      // 运行错误: ClassCastException
      String s = lsa[1].get(0);
      
    • 获取泛型数组的一种实现法式

      // 配合函数式实现灵活
      // 不会出现ClassCastException, 可以由具体类型数组接收
      T[] targetArr = (T[]) Array.newInstance(T类型Class, 数组长度);
      
      static class Test {
      
          public <T> T[] getArray(T t) {
              Object[] array = new Object[1];
              array[0] = t;
              return (T[]) array;
          }
      
          public static void main(String[] args) {
              Test test = new Test();
              // 控制台输出[测试]
              System.out.println(Arrays.toString(test.getArray("测试")));
      
      		// 不能通过
      		// Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
              String[] array = test.getArray("测试");
              System.out.println(Arrays.toString(array));
          }
      
      }
      
  • 类型通配符(?)

    public class Test3<T> {
    	
    	// Test3<Integer>, Test3<String>实例都可作为入参
        public void test1(Object t){
    	}
    
    	// Test3<Integer>, Test3<String>实例都可作为入参
        public void test2(Test3<?> t){
    	}
    	
    	// Test3<Integer>, Test3<String>实例不可作为入参, 编译报错
        public void test3(Test3<Object> t){
    	}
    }
    

    总结:

    1. 当泛型的具体类型不确定时,可使用?替代, ?可看做所有泛型的超类
    2. 如不使用泛型类, 可使用Object

3.上限和下限

泛型上限

  • 格式: 类型名称 <? extends 类> 对象名称

  • 意义: 只能接受该类及其子类

  • 不影响往外取, 不能往里存

    /**
     * [读取]
     * 如果要从集合中[读取]类型T的数据, 并且[不能写入], 可以使用? extends通配符(Producer Extends)
     * 例如: List<? extends Animal> animalList, 动物、狗、猫、猪、鸡... 只要是动物, 都可被存入进
     */
    public static void testAnimal1() {
       List<Dog> dogList = Arrays.asList(new Dog());
       List<? extends Animal> animalList = dogList;
       
       /**
        * animalList是一个Animal的子类的List, 由于Dog是Animal的子类
        * 因此将dogList赋给animalList是合法的, 但是编译器会阻止将new Cat()加入animalList
        * 因为编译器只知道animalList是Animal的某个子类的List, 但并不知道究竟是哪个子类, 为了类型安全, 只好阻止向其中加入任何子类, 并且不可以加入new Animal()
        * 事实上, 不能够往一个使用了? extends的数据结构里写入任何的值
        */
       // 编译失败
       // animalList.add(new Cat());
       // 编译失败
       // animalList.add(new Animal());
       // 编译失败
       // animalList.add(new Dog());
    
       /**
        * 由于编译器知道它总是Animal的子类型, 但并不知道具体是哪个子类, 因此我们总可以从中读取出Animal对象
        */
       Animal animal = animalList.get(0);
       Object obj = animalList.get(0);
       // 编译失败
       // Dog dog = animalList.get(0);
    }
    

泛型下限

  • 格式: 类型名称 <? super 类> 对象名称

  • 意义: 只能接受该类及其父类

  • 不影响往里存, 往外取只能存放在Object对象里

    /**
     * [写入]
     * 如果要往集合中[写入]类型T的数据, 并且不需要[读取], 可以使用? super通配符(Consumer Super)
     * 如果既要存又要取, 就不要使用任何通配符
     */
    public static void testAnimal2() {
       List<Animal> animalList = new ArrayList<>();
       List<? super Dog> dogList = animalList;
       
       /**
        * 这里的animalList是一个Animal的超类(父类, superclass)的List
        * 同样地, 出于对类型安全的考虑, 我们可以加入Dog对象或者其任何子类(如WhiteDog)对象
        * 但由于编译器并不知道List的内容究竟是Dog的哪个超类, 因此不允许加入任何超类类型
        */
       dogList.add(new Dog());
       // dogList.add(new WhiteDog());
       // 编译失败
       // dogList.add(new Animal());
       // 编译失败
       // dogList.add(new Object());
    
       /**
        * 当我们读取的时候, 编译器在不知道是什么类型的情况下只能返回Object对象, 因为Object是所有Java类的祖先类
        */
       Object obj = dogList.get(0);
       // 编译失败
       // Dog dog = dogList.get(0);
       // 编译失败
       // Animal animal = dogList.get(0);
    }
    

4.类型擦除与桥接方法

  泛型是提供给javac编译器使用的, 它用于限制输入类型, 让编译器在源代码级别上挡住输入的非法数据
  但编译器编译完带有泛形的java程序后, 生成的class文件中将不再带有泛型信息, 以此使程序运行效率不受到影响, 这个过程称之为"擦除"
  由于类型被擦除了, 为了维持多态性, 所以编译器就自动生成了桥接方法

/**
 * 桥接方法
 * 1.类型擦除: javac -> class泛型被擦除了
 * 2.泛型虽然被擦除了,但是一定要保留泛型的行为
 */
/**
 * 泛型类
 */
public class Node<T> {

    public T data;

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

}

class SubNode extends Node<Integer> {
    @Override
    public void setData(Integer data) {
        this.data = data;
    }
}
/**
 * 被擦除后的泛型类
 */
public class Node {

    public Object data;

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

class SubNode extends Node {

    // 首先请求该方法
    @Override
    public void setData(Object data) {
        setData((Integer) data);
    }

    // 然后内部调用桥接方法, 用于对入参类型的限制
    public void setData(Integer data) {
        super.setData(data);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值