首页 > 其他分享 >深入理解泛型(经典详解):<T> T 和 T的使用以及public <E> List<E> get()泛型方法详解、类型擦除、通配符的使用、泛型类的应用、泛型之间的继承

深入理解泛型(经典详解):<T> T 和 T的使用以及public <E> List<E> get()泛型方法详解、类型擦除、通配符的使用、泛型类的应用、泛型之间的继承

时间:2023-12-04 19:44:06浏览次数:37  
标签:ArrayList List 类型 详解 擦除 泛型 public

 一、为什么要使用泛型?

        泛型俗称“标签”,使用<E>表示。泛型就是在允许定义类,接口时通过一个标识表示某个属性的类型或者是某个方法的返回值或者是参数类型,参数类型在具体使用的时候确定,在使用之前对类型进行检查。

        泛型意味着编写的代码可以被很多不同类型的对象重用。例如集合ArrayList,如果集合不添加泛型,里面可以存储任何类型也就是Object,当添加泛型的时候,提高了代码的重用。

        泛型提供了类型参数,比如ArrayList类有一个类型参数来指示元素的类型,使得代码具有更好的可读性,一看就知道数组列表中包含的是String对象。

ArrayList<String> list = new ArrayList<String>();

        在Java SE 7 以后的版本中,构造函数中可以省略泛型。

ArrayList<String> list = new ArrayList<>();

       当代码进行编译的时候,编译器知道ArrayList<String>添加元素的类型是String类型。当编译的时候,编译器可以对调剂的元素进行检查,避免错误类型的对象,出现和集合中的泛型不匹配的对象是无法通过编译。泛型的好处在于使得程序具有更多的可读性和安全性。


二、定义简单的泛型类

      泛型类的表示方式在类后面添加<T>,T是占位符。

       一个泛型类就是具有一个或者多个类型变量的类。

      举例:

public class Pair<T> {
    private T first;
    private T second;
    
    public Pair(){}
    public Pair(T first,T second){
        this.first = first;
        this.second = second;
    }
    
    public void setFirst(T first){
        this.first = first;
    }
    public T getFirst(){
        return first;
    }
    public void setSecond(T second){
        this.second = second;
    }
    public T getSecond(){
        return second;
    }
}

     Pair类引入了一个类型遍历的变量T,放在类名的后面。泛型类也可以引用多个变量。例如:

public class Pair<T,U>{}

    类定义的类型变量T,可以指定方法和局部变量的返回类型。

private T first;
 
public T getFirst(){
    return first;
}

   假如T的类型是String,那就是:

private String first;
 
public String getFirst(){
    return first;
}

补充:在Java库中,使用变量E表示集合的元素类型,K和V分别表示关键字和值的类型。T表示任意类型。

总结:泛型类可以看成普通类的工厂。需要什么类型的,泛型类就会类的后面添加什么类型的属性和方法。


三、泛型方法的使用

 泛型方法的定义:主要还是看的返回值类型是一个泛型

全选修饰符  返回值类型   方法名
 pulic       <T> T     getName(){
    
}

  举个简单的案例说明泛型方法的使用:

  泛型的意思就是说类型可以在后面指定,但是仍然需要告诉编译器,我需要某个特定类型作为占位符。比如T:

public List<T> f(T a){}

  编译器会问,T是什么,我怎么不认识?我并不知道这个T是类还是泛型的方法?程序员需要声明一下。

public <T> List<T> f(T a){}

 共有三个T,第一个T用来声明类型参数的,后面的两个T才是泛型的实现。

   看下<T> T 和 T的用法和区别

   <T> T  表示返回值是一个泛型,传递什么,就返回什么类型的数据。  T 表示传递的参数类型。下面依次举例:

  <T> T返回值是一个T类型,这个T是<T> T中的第二个T,告诉编辑器,我传递什么你就给我返回什么样的数据类型。

/*
    <T> T可以传入任何类型的list
    关于参数T的说明:
        第一个T表示<T>是一个泛型
        第二个T表示方法返回的是T类型的数据
        第三个T表示集合List传入的数据是T类型
*/
 
private <T> T getStudent(List<T> list){
    return ;
}

四、泛型代码和虚拟机

     虚拟机没有泛型类型的对象——所有对象都是属于普通类。

类型擦除:

①原始类型相等

  Java泛型是一个伪泛型,这是因为Java在编译期间所有泛型信息都会被擦除掉。Java泛型基本上都是在编译器这个层次上实现的,在生成字节码中是不包括泛型中的类型信息,使用泛型的时候添加上类型参数,在编译器编译的时候会去掉,这个过程称为类型擦除。

   如在List<Object>,List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型的附加信息对JVM是看不到的。Java编译器会在编译期间尽可能的发现出错的地方,但是无法在运行时刻出现类型转换和类型转换异常的情况。

  下面来举例理解泛型擦除:

   以下我们定义了两个ArrayList集合,一个是ArrayList<String>,一个是ArrayList<Integer>的泛型类型,最后我们通过获取他们类对象的getClass()的信息,结果为true。说明String和Integer都背擦除了,剩下的只是泛型类型。无论何时定义一个泛型,都自动提供一个相应的原始类型。原始类型的名字就是删去类型数后的泛型类型名。擦除类型变量,并替换成限定类型。

public class Test {
 
    public static void main(String[] args) {
 
        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");
 
        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);
 
        System.out.println(list1.getClass() == list2.getClass());
    }
 
}

②通过反射添加其他类型的元素

  在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,当我们利用反射调用的时候,却可以存储字符串,这说明Integer泛型实例在编译之后被擦除了,只保留了原始类型。

public class Test {
 
    public static void main(String[] args) throws Exception {
 
        ArrayList<Integer> list = new ArrayList<Integer>();
 
        list.add(1);  //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer
 
        list.getClass().getMethod("add", Object.class).invoke(list, "asd");
 
        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }
 
}

③类型擦除后保留的原始类型

        原始类型就是擦除后的泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,响应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定类型使用的是Object进行替换)替换。下面的T是无限定类型,使用的是Object进行替换。因为在Pair中,T是一个无限定的类型,所以使用的是Object进行替换

class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
}
class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
}

④翻译泛型表达式

当程序调用泛型方法时,如果擦除返回类型,编译器将进行强制类型转换。例如下面语句:

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

擦除getFirst()返回类型后将返回Object类型。编译器自动插入Employee的强制类型转换。也就是说,编译器把这个方法调用翻译为两条虚拟机指令:

  • 对原始方法Pair。getFirst()调用
  • 将返回的Object类强制转换为Employee类型

⑤翻译泛型方法

类型擦除也会出现在泛型方法中,T被擦除,留下的只是限定了下,比如

public static <T extends Comparable> T min(T[] a)
 
// 擦除方法后变成
 
public static Comparable min(Comparable[] a)

小结:

  •  虚拟机中没有泛型,只有普通的类和方法
  • 所有的类型参数都用他们的限定类型进行替换
  • 为了保持类型的安全性,必要时插入强制类型转换

五、通配符

通配符说白了就是占位符,这个位置我先占着,等用了再说。

①上界通配符:

上届:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。

在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:

  • 如果传入的类型不是 E 或者 E 的子类,编译不成功
  • 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用

类型参数中如果有多个类型的参数,用逗号分开。

Pair<? extends Employee>
private <K extends A, E extends B> E test(K arg1, E arg2){
    E result = arg2;
    arg2.compareTo(arg1);
    //.....
    return result;
}

②下界通配符:

用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object。

private <T> void test(List<? super T> dst, List<T> src){
    for (T t : src) {
        dst.add(t);
    }
}
 
public static void main(String[] args) {
    List<Dog> dogs = new ArrayList<>();
    List<Animal> animals = new ArrayList<>();
    new Test3().test(animals,dogs);
}

上界通配符主要用于读数据,下界通配符主要用于写数据。

③?和 T 的区别

   T:指定集合元素只能是T类型

List<T> list = new ArrayList<T>();

   ?:集合元素可以是任意类型,没有任何意义,就是个占位符,一般方法中,只是为了说明用法

List<?> list = new ArrayList<?>();

?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ?不行,比如如下这种 :

// 可以
T t = operate();
 
// 不可以
?car = operate();

六、反射和泛型,Class<T>

反射允许你在运行时分析任何对象。Class<T>在实例化的时候,T 要替换成具体类。Class<?>它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。

应用,举例:

比如我们常用的数据访问对象DAO层,当我们对数据库表进行操作的时候,不知道是哪一张表的操作,所以可以定义一个泛型类来实现

然后再定义一个方法继承DAO,比如以下的继承,类CustomerDAO可以直接使用DAO<Customer>,对应的是数据库中的Customer的表

七、泛型之间的继承关系

泛型在继承方面的体现,类A是类B的父类,G<A> 和G<B>二者不具备子父类的关系,二者是并列的关系

List<Object> list1 = null;
List<Integer> list2 = null;
 
// 二者不具备子父类关系,二者的关系是并列的
list1 = list2;

 

转载自:深入理解泛型(经典详解):<T> T 和 T的使用以及public <E> List<E> get()泛型方法详解、类型擦除、通配符的使用、泛型类的应用、泛型之间的继承_泛型类<t>与泛型方法<e>-CSDN博客

标签:ArrayList,List,类型,详解,擦除,泛型,public
From: https://www.cnblogs.com/yitongtianxia666/p/17875776.html

相关文章

  • Java基本数据类型、包装类及拆装箱详解
    Java的基本数据类型和对应的包装类是Java语言中处理数据的两个关键概念。基本数据类型提供了简单而高效的方式来存储数据,而包装类使得基本数据类型具有对象的特性。本文将深入探讨基本数据类型与包装类的应用场景及详细描述,并对自动拆箱和装箱的源码实现进行分析。基本数据类型与......
  • 【Java集合】双列集合Map详解,让你快速上手!
    Map是一种双列集合,一个元素包含两个值,一个是Key,一个是Value。Map集合中的元素,key和value的数据类型可以相同,也可以不同。一个映射不能包含重复的键;每个键最多只能有一个值。今天我们继续探索Java集合的世界,这次我们要聊的主题是——双列集合Map。首先,让我们来理解一下什么是双......
  • 【Java集合】 Map双列集合详解:让你的代码更高效!
    Map是一种双列集合,一个元素包含两个值,一个是Key,一个是Value。Map集合中的元素,key和value的数据类型可以相同,也可以不同。一个映射不能包含重复的键;每个键最多只能有一个值。今天我们继续探索Java集合的世界,这次我们要聊的主题是——双列集合Map。首先,让我们来理解一下什么是双......
  • 在net中通过Autofac实现AOP的方法及实例详解
     在本示例中,我们将使用Autofac和AspectC(Autofac.Extras.DynamicProxy2)来演示如何实现AOP(面向切面编程)。我们将创建一个简单的C#控制台应用程序,并应用AOP以解决日志记录的问题。首先,让我们讨论AOP的用途和目标。AOP(面向切面编程)的用途AOP是一种编程范式,旨在解决横切关注点(cro......
  • 详解RSA加密原理
    密码学密码学是指研究信息加密,破解密码的技术科学。密码学的起源可追溯到2000年前。而当今的密码学是以数学为基础的。密码学的历史大致可以追溯到两千年前,相传古罗马名将凯撒大帝为了防止敌方截获情报,用密码传送情报。凯撒的做法很简单,就是对二十几个罗马字母建立一张对应......
  • 【Java 进阶篇】Java Request 获取请求体数据详解
    在JavaWeb开发中,获取HTTP请求的请求体数据是一项常见任务。HTTP请求的请求体通常包含了客户端提交的数据,例如表单数据、JSON、XML等。在Java中,可以使用HttpServletRequest对象来获取HTTP请求的请求体数据。本文将详细解释如何使用Java获取HTTP请求的请求体数据,并提供示例代码。HTT......
  • 简化版Transformer :Simplifying Transformer Block论文详解
    前言 本文探讨了来自苏黎世联邦理工学院计算机科学系的BobbyHe和ThomasHofmann在他们的论文“SimplifyingTransformerBlocks”中介绍的Transformer技术的进化步骤。这是自Transformer开始以来,我看到的最好的改进。本文转载自DeephubImba作者|FreedomPreetham仅用于学......
  • Java泛型的定于与使用
    Java泛型的定于与使用泛型也叫泛类型。Java中可以声明泛型的地方。泛型的分类:泛型类:在类的定义时,声明泛型泛型接口:在接口的定义时,声明泛型泛型方法:在类的方法上声明泛型一、泛型类1、语法className<T1,T2,...,Tn>{//}/**T代表一个Java类,在类上声明......
  • Linux LVM扩容详解
    1、将物理磁盘设备初始化为物理卷pvcreate/dev/sdb/dev/sdc查看物理卷命令:pvs删除物理卷:rmpv/dev/sdb2、创建卷组,并将PV加入卷组vgcreatename/dev/sdb/dev/sdc查看卷组:vgs删除卷组rmvgname3、基于卷组创建逻辑卷lvcreate-nname1-L2Gname......
  • linux上的rsync命令详解【转】
    1.rsync简介rsync就是远程同步的意思remotesync.rsync被用在UNIX/Linux执行备份操作操作.rsync工具包被用来从一个位置到另一个位置高效地同步文件和文件夹.rsync可以实现在同一台机器的不同文件直接备份,也可以跨服务器备份.2.rsync的重要特性速度快:初次同步时,......