Android知识点整理14:泛型

一、简介

1、什么是泛型

Java自从Jdk1.5后引用泛型,解决了容器类型安全问题,其本质是参数化类型,就是指将所操作的数据类型作为参数的一种语法。不过Java的泛型其实是一种伪泛型,只在编译期有效,运行时会类型擦除,不像C++的模板,是在运行期也有效。

2、作用

(1)、将代码安全性检查提前到编译器

Jdk1.5引用泛型前,比如

List<Apple> apples = new ArrayList<>();
apples.add(new Banana());

这类错误,需要在运行时才能检查出来

Jdk1.5后,可以让编译器在编译的时候就借助传入参数类型检查对容器的插入,获取操作是否合法,从而将运行时类转换异常,提前到编译时。

(2)、提高代码复用性

使用泛型,可以写出更加通用的代码,提高了灵活性,这在自己写框架和第三方框架用的比较多。

(3)、可以省去类型转换

在Jdk1.5前,Java容器都是通过向上转型Object类型来实现的,当取出对象时候需要强制向下强制转换。加入泛型后,编译期会自动强制转换,省略了很多代码。

3、历史演变

在JDK1.5前,需要使用泛型作用的地方都是通过Object向上转型,这会存在安全隐患 ,在获取“真正”的数据的时候,如果不小心强制转换成了错误类型,这种错误只能在真正运行的时候才能发现。

因此Java 1.5推出了“泛型”,也就是在原本的基础上加上了编译时类型检查的语法糖。Java 的泛型推出来后,引起来很多人的吐槽,因为相对于C++等其他语言的泛型,Java的泛型代码的灵活性依然会受到很多限制。这是因为Java被规定必须保持二进制向后兼容性,也就是一个在Java 1.4版本中可以正常运行的Class文件,放在Java 1.5中必须是能够正常运行的

在1.5之前,这种类型的代码是没有问题的。

public static void addRawList(List list){
   list.add("123");
   list.add(2);
}

1.5之后泛型大量应用后:

public static void addGenericList(List<String> list){
    list.add("1");
    list.add("2");
}

虽然我们认为addRawList()方法中的代码不是类型安全的,但是某些时候这种代码是有用的,在设计JDK1.5的时候,想要实现泛型有两种选择:

  • 需要泛型化的类型(主要是容器(Collections)类型),以前有的就保持不变,然后平行地加一套泛型化版本的新类型;
  • 直接把已有的类型泛型化,让所有需要泛型化的已有类型都原地泛型化,不添加任何平行于已有类型的泛型版。

什么意思呢?也就是第一种办法是在原有的Java库的基础上,再添加一些库,这些库的功能和原本的一模一样,只是这些库是使用Java新语法泛型实现的,而第二种办法是保持和原本的库的高度一致性,不添加任何新的库。

在出现了泛型之后,原本没有使用泛型的代码就被称为raw type(原始类型)
Java 的二进制向后兼容性使得Java 需要实现前后兼容的泛型,也就是说以前使用原始类型的代码可以继续被泛型使用,现在的泛型也可以作为参数传递给原始类型的代码。
比如

 List<String> list=new ArrayList<>();
 List rawList=new ArrayList();
 addRawList(list);
 addGenericList(list);
 
 addRawList(rawList);
 addGenericList(rawList);

上面的代码能够正确的运行。

Java 设计者选择了第二种方案

为了实现以上功能,Java 设计者将泛型完全作为了语法糖加入了新的语法中,什么意思呢?也就是说泛型对于JVM来说是透明的,有泛型的和没有泛型的代码,通过编译器编译后所生成的二进制代码是完全相同的。

这个语法糖的实现被称为擦除

擦除的过程

泛型是为了将具体的类型作为参数传递给方法,类,接口。
擦除是在代码运行过程中将具体的类型都抹除。

前面说过,Java 1.5 之前需要编写模板代码的地方都是通过Object来保存具体的值。比如:

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){
    
    Student stu=new Student();
    Node  node=new Node();
    node.set(stu);
    Student stu2=(Student)node.get();
   }
}


这样的实现能满足绝大多数需求,但是泛型还是有更多方便的地方,最大的一点就是编译期类型检查,于是Java 1.5之后加入了泛型,但是这个泛型仅仅是在编译的时候帮你做了编译时类型检查,成功编译后所生成的.class文件还是一模一样的,这便是擦除

1.5 以后实现

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){
    
    Student stu=new Student();
    Node<Student>  node=new Node<>();
    node.set(stu);
    Student stu2=node.get();
  }
}

两个版本生成的.class文件:
Node:

  public Node();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public java.lang.Object get();
    Code:
       0: aload_0
       1: getfield      #2                  // Field obj:Ljava/lang/Object;
       4: areturn
  public void set(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #2                  // Field obj:Ljava/lang/Object;
       5: return
}

Node

public class Node<T> {
  public Node();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public T get();
    Code:
       0: aload_0
       1: getfield      #2                  // Field obj:Ljava/lang/Object;
       4: areturn

  public void set(T);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #2                  // Field obj:Ljava/lang/Object;
       5: return
}

 可以看到泛型就是在使用泛型代码的时候,将类型信息传递给具体的泛型代码。而经过编译后,生成的.class文件和原始的代码一模一样,就好像传递过来的类型信息又被擦除了一样。

 

二、泛型的类型

1、类泛型

public class GenericClass<T> {
    private T value;

    public void setValue(T value) {
        this.value = value;
    }

    public T getValue() {
        return value;
    }
}

类名后面接 <T>

2、接口泛型

public interface GenericInterface<T> {
     T getValue(T value);
}

接口名后面接<T>

3、方法泛型

private static <T extends Number & Comparable<T>> T max(T a, T b) {
        if(a.compareTo(b)>=0){
            return a;
        }else{
            return b;
        }
    }

方法名前面加<T>

三、泛型常见的定义

1、定义

E:元素(Element),多用于java集合框架
K:关键字(Key)
N:数字(Number)
T:类型(Type)
V:值(Value)

2、多个泛型类型参数

第二个、第三个、第四个参数: S U V

 private static <T extends Integer, S extends Integer, U extends Integer, V extends Integer>  int sum(T t, S s, U u, V v) {
        return t.intValue()+s.intValue()+u.intValue()+v.intValue();
    }

 3、泛型继承多个类和接口的情况

因为java时单继承,所以最多只能一个类,但可以实现多个接口,使用泛型的时候,类必须在 extends前面

 interface A{
        void a();
    }
    interface B{
        void b();
    }
    class C{
        void c(){
            
        }
    }
    class D<T extends C & A & B>{
        
    }

 四、泛型的通配符

1、extend

上界通配符

class C{
        void c(){

        }
        int getValue(){
            return 0;
        }
    }
    
    private void printCList(List<? extends C> list){
        for (C c: list){
            System.out.println(c.getValue());
        }
    }

能够接受 C类 或 C的子类

遵循 PESC原则(生产者、extend‘、super、消费)的原则

上界通配符 只能读数据,不能写数据

2、super

下界通配符

private void addC(List<? super C> list, C c){
        list.add(c);
    }

能够接受C类和C的父类

遵循 PESC原则(生产者、extend‘、super、消费)的原则

下界通配符 只能写数据,不能读数据

3、?

无界通配符

 private void showList(List<?> list){
        for(Object object:list){
            System.out.println(object);
        }
    }

?可以接受任何类型,类似于 T泛型,只是不用在方法前定义

 

 参考文章:https://www.cnblogs.com/dengchengchao/p/9717097.html

                   https://www.jianshu.com/p/986f732ed2f1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值