2022最新Java泛型详解(360度无死角介绍)

作者:垃圾王子晗 时间:2022-03-08 15:13:42 

什么是泛型

Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了 编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。泛型的本质就是 参数化类型,也就是所操作的数据类型被指定为一个参数。

重点概念1:泛型的作用域是在编译期间

List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
   System.out.println("泛型测试类型相同");
}

重点概念2:泛型主要作用是在编译期间提供类型安全监测机制

List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){
   String item = (String)arrayList.get(i);
   System.out.println("泛型测试item = " +item);
}
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

ArrayList可以存放任意类型,例子中首次添加了一个Sring类型,那么arrayList 再使用时都以String的方式使用,此时再次添加一个Integer类型的变量100,arrayList 只能尝试将Integer类型的变量100转为String,因此程序报错;

为了解决类似这样的问题,泛型应运而生,我们将第一行声明初始化list的代码更改一下,编译器会在编译阶段就能够帮我们发现类似这样的问题。

List<String> arrayList = new ArrayList<String>();
...
//arrayList.add(100); 在编译阶段,编译器就会报错

综上可知:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

泛型的使用

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
   //key这个成员变量的类型为T,T的类型由外部指定  
   private T key;

public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
       this.key = key;
   }

public <T,K> T showKeyName(Generic<T> container){
       System.out.println("container key :" + container.getKey());
       T test = container.getKey();
       return test;
   }

public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
       return key;
   }
}
//泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);

//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("泛型测试","key is " + genericInteger.getKey());
Log.d("泛型测试","key is " + genericString.getKey());

泛型接口

//定义一个泛型接口
public interface Generator<T> {
   public T next();
}

当实现泛型接口的类,未传入泛型实参时

/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
*/
class FruitGenerator<T> implements Generator<T>{
   @Override
   public T next() {
       return null;
   }
}

当实现泛型接口的类,传入泛型实参时:

/**
* 传入泛型实参时:
* 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
* 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
*/
public class FruitGenerator implements Generator<String> {

private String[] fruits = new String[]{"Apple", "Banana", "Pear"};

@Override
   public String next() {
       Random rand = new Random();
       return fruits[rand.nextInt(3)];
   }
}

泛型方法

泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。

例如上述Generic类中两个方法

public T getKey(){
    return key;
}

虽然在方法中使用了泛型,但是这并不是一个泛型方法。这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。所以在这个方法中才可以继续使用 T 这个泛型。

public <T,K> T showKeyName(Generic<T> container){
   System.out.println("container key :" + container.getKey());
   T test = container.getKey();
   return test;
}

这才是一个真正的泛型方法。 首先在public与返回值之间的必不可少,这表明这是一个泛型方法,并且声明了一个泛型T这个T可以,出现在这个泛型方法的任意位置,泛型的数量也可以为任意多个

泛型类中的泛型方法

public class GenericFruit {
   class Fruit{
       @Override
       public String toString() {
           return "fruit";
       }
   }

class Apple extends Fruit{
       @Override
       public String toString() {
           return "apple";
       }
   }

class Person{
       @Override
       public String toString() {
           return "Person";
       }
   }

class GenerateTest<T>{
       public void show_1(T t){
           System.out.println(t.toString());
       }

//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
       //由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
       public <E> void show_3(E t){
           System.out.println(t.toString());
       }

//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
       public <T> void show_2(T t){
           System.out.println(t.toString());
       }
   }

public static void main(String[] args) {
       Apple apple = new Apple();
       Person person = new Person();

GenerateTest<Fruit> generateTest = new GenerateTest<Fruit>();
       //apple是Fruit的子类,所以这里可以
       generateTest.show_1(apple);
       //编译器会报错,因为泛型类型实参指定的是Fruit,而传入的实参类是Person
       //generateTest.show_1(person);

//使用这两个方法都可以成功
       generateTest.show_2(apple);
       generateTest.show_2(person);

//使用这两个方法也都可以成功
       generateTest.show_3(apple);
       generateTest.show_3(person);
   }
}

泛型通配符

Ingeter是Number的一个子类,Generic与Generic实际上是相同的一种基本类型。那么问题来了,在使用Generic作为形参的方法中,能否使用Generic的实例传入呢?在逻辑上类似于Generic和Generic是否可以看成具有父子关系的泛型类型呢?

为了弄清楚这个问题,我们使用Generic这个泛型类继续看下面的例子:

public void showKeyValue1(Generic<Number> obj){
   Log.d("泛型测试","key value is " + obj.getKey());
}
Generic<Integer> gInteger = new Generic<Integer>(123);
Generic<Number> gNumber = new Generic<Number>(456);

showKeyValue(gNumber);
showKeyValue(gInteger);

showKeyValue(gInteger);这个方法编译器会为我们报错:

Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number>

而且如果我们用重载的思维再定义一个Generic<java.lang.Integer>类型的方法

public static void showKeyValue1(Generic<Integer> obj){
       System.out.println("泛型测试,key value is " + obj.getKey());

System.out.println("泛型测试类型相同");
   }

public static void showKeyValue1(Generic<Integer> obj){
       System.out.println("泛型测试,key value is " + obj.getKey());

System.out.println("泛型测试类型相同");
   }

会报错重载异常

'showKeyValue1(Generic<Integer>)' is already defined in 'com.wzh.demo.test.FruitGenerator'

这其实是印证了上述的结论:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型,也就是Generic<java.lang.Integer>和Generic<java.lang.Number>本质都是Generic,但编译期间方法入参必须指定的泛型类,因为同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

解决方案,使用泛型通配符?

public void showKeyValue1(Generic<?> obj){
   Log.d("泛型测试","key value is " + obj.getKey());
}

类型通配符一般是使用?代替具体的类型实参,注意此处**&rsquo;?&rsquo;是类型实参,而不是类型形参** ,再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,可以把?看成所有类型的父类。是一种真实的类型。当具体类型不确定的时候。那么可以用 ? 通配符来表未知类型。

通配符上限

//结构
public class XxxClass<T extend XxxClass>
//案例
public void showKeyValue1(Generic<? extend Number> obj){
   Log.d("泛型测试","key value is " + obj.getKey());
}

此时限定传参的泛型类只能是Number或者Number的子类

通配符下限

//结构
public class XxxClass<T super XxxClass>
//案例
public void showKeyValue1(Generic<? super Number> obj){
   Log.d("泛型测试","key value is " + obj.getKey());
}

此时限定传参的泛型类只能是Number或者Number的父类

类型擦除

public class Erasure<T>{
   //key这个成员变量的类型为T,T的类型由外部指定  
   private T key;

public Erasure(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
       this.key = key;
   }

public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
       return key;
   }

public <T extends List> T show(T t){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
       return t;
   }

}

我们从程序运行期间来看

public static void main(String[] args) {
       Erasure<Number> gNumber = new Erasure<Number>(456);

Class<? extends Erasure> aClass = gNumber.getClass();
       Field[] declaredFields = aClass.getDeclaredFields();
       for (Field declaredField : declaredFields) {
           System.out.println(declaredField.getName() + ":" +declaredField.getType().getSimpleName());
       }
   }

结果

key:Object

2022最新Java泛型详解(360度无死角介绍)

如果我们给泛型加一个类型通配符上限

public class Erasure<T extends Number>{....}

那么,打印结果就是

key:Number

2022最新Java泛型详解(360度无死角介绍)

且对应的方法

2022最新Java泛型详解(360度无死角介绍)

如果是接口类型的泛型

interface Info<T> {

public T info(T t);
}

public class InfoImpl implements Info<Integer>{

@Override
   public Integer info(Integer var) {
       return var;
   }

public static void main(String[] args) {
       Class<InfoImpl> infoClass = InfoImpl.class;
       Method[] declaredMethods = infoClass.getDeclaredMethods();
       for (Method declaredMethod : declaredMethods) {
           System.out.println(declaredMethod.getName() + ":" + declaredMethod.getReturnType().getSimpleName());
       }
   }
}

打印结果

main:void
info:Integer
info:Object

2022最新Java泛型详解(360度无死角介绍)

个人备注一个疑问点,如上图所示类型擦除后会生成一个Integer和Object类型的info方法,但为何我通过反射调用Object类型的info方法时候会有报错呢?

public class InfoImpl implements Info<Integer>{

@Override
   public Integer info(Integer var) {
       return var;
   }

public static void main(String[] args) throws IllegalAccessException, InstantiationException, InvocationTargetException {
       Class<InfoImpl> infoClass = InfoImpl.class;
       Method[] declaredMethods = infoClass.getDeclaredMethods();
       for (Method declaredMethod : declaredMethods) {
           System.out.println("methodNameAndType:" +declaredMethod.getName() + ":" + declaredMethod.getReturnType().getSimpleName());
           if(declaredMethod.getReturnType().getSimpleName().equals("Object")){
               InfoImpl info = infoClass.newInstance();
//                System.out.println("methodName:"  +declaredMethod.getName());
               Arrays.stream(declaredMethod.getParameterTypes()).forEach(param -> {
                   System.out.println("param:" +  param);
               });
               System.out.println("method return :" +declaredMethod.getReturnType());
               String str= "abc";
               Object invoke = declaredMethod.invoke(info, str);
               System.out.println(invoke);
           }
       }
   }
}

易混点强调:由于泛型擦除机制,T仅仅是当做一种符号,即占位符去使用,没有实际意义,所以如果你试图同T t = new T();实例化T类型的对象是通不过编译的

class GenericsA<T>
{
   T t = new T(); // Error
}

但是我们可以通过反射来完成这一需求

public T createT(Class<T> tClass) throws IllegalAccessException, InstantiationException {
    return tClass.newInstance();
}

泛型与数组

可以声明带泛型的数据引用,但不能直接创建带泛型的数组对象

小总结

泛型的作用域:编译期间;

泛型的作用

  • 编译时提供类型安全监测机制,最典型的就是各种容器类如:List、Set、Map,通过泛型在编译期间限制添加元素的类型

  • 增强代码规范性&复用性,例如设计模式模板方法模式结合泛型来使用,在模板方法中使用泛型,可以增强模板方法的复用性

泛型的类型问题

  • 泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型,可以理解为泛型就是一种作用在编译期的占位符,过了编译期,运行期的类型擦除机制会完全擦除泛型类的影响

  • 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的,参考以下报错来理解

Generic<java.lang.Integer>  cannot be applied to Generic<java.lang.Number>

但我们可以通过泛型通配符Generic<?>

泛型类对比泛型方法
泛型类是在实例化类的时候指明泛型的具体类型;泛型方法是在调用方法的时候指明泛型的具体类型

来源:https://blog.csdn.net/wwwwwww31311/article/details/127149313

标签:java,泛型
0
投稿

猜你喜欢

  • java编程下字符串的16位,32位md5加密实现方法

    2023-07-29 21:34:28
  • Spring深入探索AOP切面编程

    2023-05-27 09:37:16
  • No ‘Access-Control-Allow-Origin‘ header is present跨域及解决

    2022-07-12 13:33:08
  • pagehelper踩坑记之分页乱套问题解决

    2021-11-14 14:19:34
  • 在Java中String和Date、Timestamp之间的转换

    2023-10-07 13:52:36
  • 解析C#设计模式编程中备忘录模式的运用

    2023-06-10 11:40:00
  • Java 高并发三:Java内存模型和线程安全详解

    2021-10-24 07:04:13
  • Django之多对多查询与操作方法详解

    2021-08-03 03:21:58
  • Java避免死锁_动力节点Java学院整理

    2023-05-07 15:02:03
  • java中List接口与实现类介绍

    2022-11-17 02:54:40
  • Android Hilt Retrofit Paging3使用实例

    2022-11-20 21:06:59
  • c#反射调用方法示例

    2022-07-14 15:28:40
  • Kryo框架使用方法代码示例

    2021-05-30 15:46:05
  • Java byte数组操纵方式代码实例解析

    2022-02-18 16:54:12
  • 解决IDEA鼠标点击光标变大问题

    2022-12-07 11:52:37
  • JSON.toJSONString()空字段不忽略修改的问题

    2023-06-16 03:12:37
  • Spring Boot 入门教程

    2023-05-26 00:14:44
  • Android用 Mob 实现发送短信验证码实例

    2021-10-17 03:53:16
  • 关于Java中的IO流总结(推荐)

    2023-08-23 18:13:56
  • Bean Searcher配合SpringBoot的使用详解

    2022-06-21 23:49:00
  • asp之家 软件编程 m.aspxhome.com