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[] ,因此报了类型转换错误。

原文地址:https://www.cnblogs.com/chenzhubing/p/11196405.html

时间: 2024-11-09 03:07:00

java 泛型深入理解的相关文章

对java泛型的理解

自jdk1.6之后泛型就被广泛使用了,刚开始也是这么稀里糊涂的学了但是老是搞不懂其中的奥妙,后来随着项目经验的增多慢慢的才体会到了泛型的各种好处,看了不少项目封装的底层才发现原来泛型一般和接口混合使用来满足标准制定和参数多样化这样的代码需求.弄清楚之后我自己也动手实际体验了一下,觉得还是挺简单的.接下来就分享下我学泛型的心得,写的不好大家可以指出来. 1 标识接口 虽然叫标识接口但是这个java类不仅仅局限于interface,class也是OK的,选择接口还是类关键看自己的选择:下面是我定义的

Java:泛型的理解

本文源自参考<Think in Java>,多篇博文以及阅读源码的总结 前言 Java中的泛型每各人都在使用,但是它底层的实现方法是什么呢,为何要这样实现,这样实现的优缺点有哪些,怎么解决泛型带来的问题.带着好奇,我查阅资料进行了初步的学习,在此与诸位探讨. 一 类型参数 学过JAVA的人都知道泛型,明白大概怎么使用.在类上为:class 类名 {},在方法上为:public void 方法名 (T x){}.泛型的实现使得类型变成了参数可以传入,使得类功能多样化. 具体可分为5种情况: T是

Java泛型深入理解

Java泛型是Java1.5引入的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法.泛型的类型参数只能是类(引用)类型(包括自定义类),不能是基本数据类型.Java语言引入泛型的好处是安全简单. 转载自:http://www.cnblogs.com/lwbqqyumidi/p/3837629.html

java泛型的理解

最近在看视频,看到比较经典的比大小问题.输入两个数,返回大的数,类型可以为int,long,float等. 通常的教程中用这个例子引入了构造函数以及重载的概念,在学习完泛型后,我想到能不能写一个泛型的方法,用以实现比较. 为了完成这个任务,我们首先需要了解一下泛型. 什么的泛型 泛型是java语言系统的一种扩展,支持创建可以按照类型进行参数化的类. 泛型的好处 泛型的好处也是显而易见的, 首先可以扩充代码的通用性,通过泛型可以使方法支持更多的类型. 泛型有助于增强类型安全,编译器可以对类型进行比

关于Java泛型深入理解小总结

1.何为泛型 首先泛型的本质便是类型参数化,通俗的说就是用一个变量来表示类型,这个类型可以是String,Integer等等不确定,表明可接受的类型,原理类似如下代码 int pattern; //声明一个变量未赋值,pattern可以看作是泛型 pattern = 4; pattern = 5;//4和5就可以看作是String和Integer 泛型的具体形式见泛型类.泛型方法 *泛型类形式如下 class Test<T> { private T t; Test(T t) { this.t

理解Java泛型 通配符 ? 以及其使用

什么是泛型: 泛型从字面上理解,是指一个类.接口或方法支持多种类型,使之广泛化.一般化和更加通用.Java中使用Object类来定义类型也 能实现泛型,但缺点是造成原类型信息的丢失,在使用中容易造成ClassCastException. Java泛型带到的好处: 使得一个类或方法中的类型参数化,最终达到代码复用的效果.( 不使用泛型,你可能需要每种情况的类或方法都要定义一遍 ) 实现类型检查的功能,避免ClassCastException.(这是相对于使用Object类型实现泛型而言.因为我可以

Java反射的理解(六)-- 通过反射了解集合泛型的本质

Java反射的理解(六)-- 通过反射了解集合泛型的本质 上述写了那么多,我们可能会有个疑问,为什么要用反射,步骤比我们常规的加载类操作复杂多了,别急,这个问题我最后才解答,我们先来了解集合泛型的本质. 直接上代码: import java.lang.reflect.Method; import java.util.ArrayList; public class MethodDemo4 { public static void main(String[] args) { ArrayList li

Java泛型中extends和super的理解(转)

E – Element (在集合中使用,因为集合中存放的是元素) T – Type(Java 类) K – Key(键) V – Value(值) N – Number(数值类型) ? – 表示不确定的java类型(无限制通配符类型) S.U.V – 2nd.3rd.4th types Object – 是所有类的根类,任何类的对象都可以设置给该Object引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型T.E等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换. ?

Java 泛型解析,太难了,认真读才能理解

Java 泛型 关键字说明 ? 通配符类型 <? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类 <? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object extends 示例 static class Food{} static class Fruit extends Food{} static class Apple extends Fruit{} static clas