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

目录
  • 什么是泛型
    • 重点概念1:泛型的作用域是在编译期间
    • 重点概念2:泛型主要作用是在编译期间提供类型安全监测机制
  • 泛型的使用
    • 泛型类
    • 泛型接口
    • 泛型方法
  • 泛型类中的泛型方法
    • 泛型通配符
    • 通配符上限
    • 通配符下限
    • 类型擦除
    • 泛型与数组
  • 小总结

什么是泛型

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());
}

类型通配符一般是使用?代替具体的类型实参,注意此处**’?’是类型实参,而不是类型形参** ,再直白点的意思就是,此处的?和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

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

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

那么,打印结果就是

key:Number

且对应的方法

如果是接口类型的泛型

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

个人备注一个疑问点,如上图所示类型擦除后会生成一个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<?>

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

到此这篇关于java泛型360度无死角详细讲解的文章就介绍到这了,更多相关java泛型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java 泛型总结(三):通配符的使用

    简介 前两篇文章介绍了泛型的基本用法.类型擦除以及泛型数组.在泛型的使用中,还有个重要的东西叫通配符,本文介绍通配符的使用. 这个系列的另外两篇文章: Java 泛型总结(一):基本用法与类型擦除 Java 泛型总结(二):泛型与数组 数组的协变 在了解通配符之前,先来了解一下数组.Java 中的数组是协变的,什么意思?看下面的例子: class Fruit {} class Apple extends Fruit {} class Jonathan extends Apple {} class

  • Java泛型在集合使用与自定义及继承上的体现和通配符的使用

    泛型的概念 集合容器类在设计阶段/声明阶段不能确定这个容器实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决.因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此把元素的类型设计成一个参数,这个类型参数叫做泛型.Collection<E>,ArrayList<E> 中<E>就是类型参数,即泛型. 所谓泛型,就是允许在定义类.接口时通过一个标识表示类中某个属性

  • Java中泛型使用的简单方法介绍

    一. 泛型是什么 "泛型",顾名思义,"泛指的类型".我们提供了泛指的概念,但具体执行的时候却可以有具体的规则来约束,比如我们用的非常多的ArrayList就是个泛型类,ArrayList作为集合可以存放各种元素,如Integer, String,自定义的各种类型等,但在我们使用的时候通过具体的规则来约束,如我们可以约束集合中只存放Integer类型的元素,如List<Integer> iniData = new ArrayList<>().

  • Java使用反射来获取泛型信息示例

    本文实例讲述了Java使用反射来获取泛型信息.分享给大家供大家参考,具体如下: 一 点睛 获得了Field对象后,就可以很容易地获得该Field的数据类型,即使用如下代码即可获得指定Field的类型: //获取Field对象f的类型 Class<?> a = f.getType(); 通过这种方式只对普通类型的Field有效.但如果该Field的类型是有泛型限制的类型,如Map<String , Integer>类型,则不能准确的得到该Field的泛型参数. 为了获得指定Field

  • Java中泛型通配符的使用方法示例

    本文实例讲述了Java中泛型通配符的使用方法.分享给大家供大家参考,具体如下: 一 点睛 引入通配符可以在泛型实例化时更加灵活地控制,也可以在方法中控制方法的参数. 语法如下: 泛型类名<? extends T> 或 泛型类名<? super T> 或 泛型类名<?> ? extends T:表示T或T的子类 ? super T:表示T或T的父类 ?:表示可以是任意类型 二 通配符在泛型类创建泛型对象中使用 1 代码 class gent<T> { publ

  • Java泛型<T> T与T的使用方法详解

    泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样. 在集合框架(Collection framework)中泛型的身影随处可见.例如,Map 类允许向一个 Map 类型的实例添加任意类的对象,即使最常见的情况在给定映射(map)中保存一个string键值对. 命名类型参数 对于常见的泛型模式,推荐的泛型类型

  • java泛型类的定义与使用详解

    本文为大家分享了java泛型类的定义与使用的具体代码,供大家参考,具体内容如下 当类中要操作的引用数据类型不确定时,可以定义泛型类完成扩展.下面是程序演示. package packB; class Student { //定义学生类 public String st = "student"; } class Worker { //定义工人类 public String wo = "worker"; } //定义泛型类 class Operate<type&g

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

    目录 什么是泛型 重点概念1:泛型的作用域是在编译期间 重点概念2:泛型主要作用是在编译期间提供类型安全监测机制 泛型的使用 泛型类 泛型接口 泛型方法 泛型类中的泛型方法 泛型通配符 通配符上限 通配符下限 类型擦除 泛型与数组 小总结 什么是泛型 Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了 编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构.泛型的本质就是 参数化类型,也就是所操作的数据类型被指定为一个参数. 重点概念1:泛型的作用域是在编译

  • Java总结篇系列:Java泛型详解

    一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: public class GenericTest { public static void main(String[] args) { List list = new ArrayList(); list.add("qqyumidi"); list.add("corn"); list.add(100); for (int i = 0; i < list.size(); i++) { S

  • Java 泛型详解与范例

    目录 一.泛型的使用 二.泛型类的定义-类型边界 三.类型擦除 四.泛型类的使用-通配符 五.泛型方法 六.泛型的限制 一.泛型的使用 前面我们学集合的时候,简单的说过泛型的使用.如下: ArrayList<Integer> list = new ArrayList<>(); Queue<Integer> queue = new LinkedList<>(); 那么使用是这样的简单,该注意什么? 尖括号里的类型,只能写引用类型 基础数据类型的话,就需要写相应

  • 2022最新vmstate 命令详解

    目录 1. 使用vmstat 2.实战 3. 问题处理中,如何运用? vmstat 是一个查看虚拟内存(Virtual Memory)使用状况的工具,但是怎样通过 vmstat 来发现系统中的瓶颈呢? 1. 使用vmstat 使用前我们先看下命令介绍及参数定义 Usage: vmstat [options] [delay [count]] Options: -a, --active active/inactive memory -f, --forks number of forks since

  • Java泛型详解

    1. Why --引入泛型机制的原因 假如我们想要实现一个String数组,并且要求它可以动态改变大小,这时我们都会想到用ArrayList来聚合String对象.然而,过了一阵,我们想要实现一个大小可以改变的Date对象数组,这时我们当然希望能够重用之前写过的那个针对String对象的ArrayList实现. 在Java 5之前,ArrayList的实现大致如下: public class ArrayList { public Object get(int i) { ... } public

  • 深入理解java泛型详解

    什么是泛型? 泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样. 可以在集合框架(Collection framework)中看到泛型的动机.例如,Map 类允许您向一个 Map 添加任意类的对象,即使最常见的情况是在给定映射(map)中保存某个特定类型(比如 String)的对象. 因为 Map.get(

  • Java 泛型详解(超详细的java泛型方法解析)

    目录 2. 什么是泛型 3. 使用泛型的好处 4. 泛型的使用 4.1 泛型类 4.2 泛型方法 4.3 泛型接口 5. 泛型通配符 5.1 通配符基本使用 5.2 通配符高级使用 6. 总结 1. 为什么使用泛型 早期的Object类型可以接收任意的对象类型,但是在实际的使用中,会有类型转换的问题.也就存在这隐患,所以Java提供了泛型来解决这个安全问题. 来看一个经典案例: public static void main(String[] args) { //测试一下泛型的经典案例 Arra

  • Java 逻辑控制详解分析

    目录 顺序结构 分支结构 if 语句 悬垂 else 问题 switch 语句 循环结构 while 循环 break continue for循环 do while 循环 顺序结构 顺序结构就是按照代码从上往下执行,我们运行的程序就是从上往下的顺序结构,当遇到方法的时候,才去执行方法.例如: System.out.println("aaa"); System.out.println("bbb"); System.out.println("ccc"

  • Java使用通配符实现增强泛型详解

    目录 使用通配符增强泛型 1.题目 2.解题思路 3.代码详解 知识点补充 使用通配符增强泛型 1.题目 泛型是JAVA重要的特性,使用泛型编程,可以使代码复用率提高. 实现:在泛型方法中使用通配符 2.解题思路 创建一个类:WildcardsTest. 创建一个方法getMiddle()用于获得给定列表的中间值. 在泛型中,使用“?”作为通配符,通配符的使用与普通的类型参数类似,如通配符可以利用extends关键字来设置取值的上限.如 <? extends Number> 表示Byte,Do

  • Java 基础详解(泛型、集合、IO、反射)

    计划把 Java 基础的有些部分再次看一遍,巩固一下,下面以及以后就会分享自己再次学习的一点笔记!不是有关标题的所有知识点,只是自己觉得模糊的一些知识点. 1.对于泛型类而言,你若没有指明其类型,默认为Object: 2.在继承泛型类以及接口的时候可以指明泛型的类型,也可以不指明: 3.泛型也数据库中的应用: 写一个 DAO 类对数据库中的数据进行增删改查其类型声明为 <T> .每张表对应一个类,对应每一张表实现一个类继承该 DAO 类并指明 DAO 泛型为该数据表对应的类,再实现一个与该表匹

随机推荐