java 泛型深入理解

Posted chenzhubing

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java 泛型深入理解相关的知识,希望对你有一定的参考价值。

学习java开始接触到泛型是在容器的时候,如没有使用泛型

List list = new ArrayList();
list.add(1);
list.add("1");
list.forEach(x-> System.out.println(x));
//编译器不会报错,但是在输出list的时候要注意类型检查。

使用泛型

List<String> list = new ArrayList<String>();
list.add(1);
list.add("1");
//编译器会报参数类型不匹配的错误。

这里使用泛型的最大好处就是检查了容器安全,将运行期可能出现的类型转换异常ClassCastException转移到编译期。并且省去了类型的强制转换。

 

  • 什么是泛型

1.泛型的本质是参数化类型,将数据类型(该数据类型只能是引用类型,不包括基础的数据类型)作为参数传递(类,方法,接口)

gpublic class Generics<T> 

    private T obj;

    public Generics(T obj)
        this.obj = obj;

    



Generics<Integer> generics = new Generics(11);

这里将Integer作为参数传递给了Generics的成员变量obj。

2.泛型作用只在编译期,在编译过程中,正确校验泛型结果后会将泛型信息擦除,因此泛型信息不会进入运行阶段。

泛型的擦除保证了有泛型和没有泛型产生的代码(class文件)是一致的。

 

 List<String> list1 = new ArrayList<>();
 List<Integer> list2 = new ArrayList<>();
 System.out.println(list1.getClass().equals(list2.getClass()));
//true

说明list1,list2是同一种数据类型(List)

 void m1(List<String> strList);

  void m1(List<Intger> strlist11)

编译期会报错:‘m1(List<String>)‘ clashes with ‘m1(List<Intger>)‘; both methods have same erasure   擦除后参数都是List类型,因此不构成方法重载的条件。

3.泛型具有不可变性:不论 A B 有什么关系,A 的容器和 B 的容器都没有父子关系,称之为不可变

List<String> str = null;
 m2(str);


 static void m2(List<Object> obj)

编译期会报错:‘m2(java.util.List<java.lang.Object>)‘ in ‘com.generics.Generics‘ cannot be applied to ‘(java.util.List<java.lang.String>)‘

即List<String>  和  List<Object> 没有任何关系,哪怕String是Object的子类。

协变性:如果 A 是 B 的父类,并且 A 的容器(比如 List< A>) 也是 B 的容器(List< B>)的父类,则称之为协变的(父子关系保持一致)

如果想要让某个泛型类具有协变性,就需要用到边界。这就是后面介绍的extends,super

 

4.List<object> 和 原始类型List的区别

4.1  编译器不会对原始类型进行类型检查,会对于带泛型的进行类型检查

4.2  你可以把任何带参数的类型传递给原始类型 List,但却不能把 List< String> 传递给接受 List< Object> 的方法,因为泛型的不可变性,会产生编译错误。

 

  • 泛型的使用方式

泛型的使用方式有三种:泛型类,泛型方法,泛型接口

泛型类的基础语法:class 类名  <泛型标识>

  public 泛型标识   var //成员变量

  public 泛型标识    getSomething(泛型标识  var)   //泛型方法,方法的头部使用<T>声明

  ................................

public class Generics<T> 

    private T obj;

    public Generics(T obj)
        this.obj = obj;

    

    public <T> void getObj(T obj)

    

    public <T> T getObj1(T obj)
        return obj;
    

 

泛型接口的语法和泛型类类似

public interface IGenerics<T> 

    public <T> T next();

public <T> void next1();
public class GenericsImpl<String> implements IGenerics @Override public String next() return null;

  @Override
    public void next1() 
      
    
  • 泛型通配符(?) 

1.使用通配符可以引用包含多种类型的泛型

List<?> nlist1 = new ArrayList<String>();
        nlist1 = new ArrayList<Integer>();
        nlist1 = new ArrayList<Double>();
     //   nlist1.add("www");
   //     nlist1.add(10);
        nlist1.add(null);
        System.out.println(nlist1.get(0));

注意?表示未知的类型,因此只能获取容器里面的元素,不能添加元素(null例外),因为编译器不能保证添加的元素类型和容器的类型是否兼容。

2. ? + extends  obj: 表示该泛型的类型虽然是未知的,但是必须是obj或者obj的子类

   List<? extends Number> nlist2 = new ArrayList<Integer>();
   //     nlist2 = new ArrayList<String>();
        nlist2 = new ArrayList<Float>();
        nlist2 = new ArrayList<Double>();
        nlist2.add(null);
        System.out.println(nlist2.get(0));
 public static void add1(List<? extends  Number> list)
Number number = list.get(0);
Object obj = list.get(0);
System.out.println(number+" "+obj);

说明: 方法add1中,list里面的元素类型一定可以向上转型成Number类型,因此我们可以用Number类型来接收list  (Object当然可以,这里我们不考虑),但是我们不能往list里面添加元素(null除外)

3.? +  super obj :  表示该泛型的类型虽然是未知的,但是必须是obj或者obj的父类

        List<? super Long> nlist3 = new ArrayList<Long>();
        nlist3 = new ArrayList<Number>();
     //   nlist3 = new ArrayList<Integer>();
        nlist3.add(null);
        System.out.println(nlist3);

 public static void add2(List<? super Long> list)
list.add(10L);

说明:add2方法中,list里面的元素一定可以向下转型成Long类型,因此我们可以往list里面添加Long类型的元素,但是我们不能用Long类型来接收list。

 

  • 泛型和数组
        Object[] obj = new Integer[3];
        obj[0] = 1.2;
        obj[1] = "www";
        obj[2] = Boolean.FALSE;

上面的代码编译期间没有问题,运行期汇报java.lang.ArrayStoreException: java.lang.Double  ,因为obj编译期是Object[]类型,可以接受任何类型的参数;运行期是Integer[]类型,因此报了异常

static <T> T[] toArray(T... args) 

        return args;
    

    static <T> T[] pickTwo(T a, T b, T c) 
        switch(ThreadLocalRandom.current().nextInt(3)) 
            case 0: return toArray(a, b);
            case 1: return toArray(a, c);
            case 2: return toArray(b, c);
        
        throw new AssertionError(); // Can‘t get here
    

    public static void main(String[] args) 

        String[] attributes = pickTwo("Good", "Fast", "Cheap");
    

这是《Effective Java》中看到的例子,编译此代码没有问题,但是运行的时候却会类型转换错误:

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;

分析:可变参数 T... args 的本质是数组,由于泛型擦除,

pickTwo(T a, T b, T c) == pickTwo(Oject a, Object b, Object c)
toArray(T...) == toArray(Object [])

数组具有协变性,因此在编译期间能够接受String[]。能够通过编译。运行期间的时候 toArray()方法返回的是String[] ,因此报了类型转换错误。

 

以上是关于java 泛型深入理解的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Java之泛型

Java泛型深入理解

Java中泛型的深入理解

深入理解 Java 泛型擦除机制

深入理解Java泛型

java 泛型深入理解