精通Java,却不了解泛型?

简介: 本文主要介绍 Java中泛型的使用

一、初识泛型


在没有泛型的出现之前,我们通常是使用类型为 Object  的元素对象。比如我们可以构建一个类型为 Object 的集合,该集合能够存储任意数据类型的对象,但是我们从集合中取出元素的时候我们需要明确的知道存储每个元素的数据类型,这样才能进行元素转换,不然会出现 ClassCastException 异常。


1. 什么是泛型


泛型是在 Java1.5 之后引入的一个新特性,它特性提供了编译时类型安全监测机制,该机制允许我们在编译时监测出非法的类型数据结构。


它的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。这种参数类型可以用在 类、接口和方法 中,分别被称为 泛型类、泛型接口和泛型方法


2. 使用好处


  • 类型安全


有了泛型的存在,只要编译时没有出现警告,那么运行时就不会出现 ClassCastException 异常


  • 消除强制类型转换


从泛型集合中取出元素我们可以不用进行类型的转换


  • 可读性更高


可以直接看出集合中存放的是什么数据类型的元素


二、泛型的使用


1. 使用场景


1)泛型类


基本语法


class 类名称 <泛型标识,泛型标识,…> {
  private 泛型标识 变量名; 
  .....
}


使用示例


class Result<T>{
    private T data;
}


注:


  • Java 1.7 之后可以进行类型推断,后面 <> 中的具体的数据类型可以省略不写:


类名<具体的数据类型> 对象名 = new 类名<>();


  • 如果我们使用的时候没有用到 <> 来制定数据类型,那么操作类型则是 Object


  • 泛型内的类型参数只能是 类型,而不能是基本数据类型,例如int,double,float...


  • 当我们传入不同数据类型进行构造对象时,逻辑上可以看成是多个不同的数据类型,但实际上都是相同类型


网络异常,图片无法展示
|


网络异常,图片无法展示
|


以上便是泛型类的简单用法,我们想要使用哪种类型,就在创建的时候指定类型,使用的时候,该类就会自动转换成用户想要使用的类型。


那么如果我们定义了一个泛型类,构造对象的时候却没有声明数据类型,那么默认为 Object 类型,取出数据的时候则需要进行类型转换:


Result objectRes = new Result("testObejct");
String str = (String) objectRes.getData();
System.out.println(str);


规则:


  • 子类也是泛型类,那么子类和父类的泛型类型要一致


public class ResultChild<T> extends Result<T> {}


  • 子类不是泛型类,那么父类要指定数据类型


public class ResultChild extends Result<String> {}


2)泛型接口


基本语法


public 接口名称 <泛型标识, 泛型标识, ...>{
    泛型标识 方法名();
    ...
}


使用示例


public interface ResultInterface<T> {
    T getData();
}


泛型接口与泛型类一样,有以下规则:


  • 实现类不是泛型类,接口要明确数据类型


  • 实现类也是泛型类,实现类和接口的泛型类型要一致


3)泛型方法


Java 中,泛型类和泛型接口的定义相对比较简单,但是 泛型方法 就比较复杂。


  • 泛型类,是在实例化类的时候指明泛型的具体类型


  • 泛型方法,是在调用方法的时候指明泛型的具体类型


基本语法


修饰符 <T, E, ...> 返回值类型 方法名(形参列表){}


  • 修饰符与返回值类型之间的用于声明此方法为泛型方法


  • 只有声明了的方法才是泛型方法,就算返回值类型中的泛型类使用泛型的成员方法也并不是泛型方法
  • 该方法将使用泛型类型 T,此时才可以在方法中使用泛型类型 T


使用示例


private <T> Result<T> getResult(T data) {
  return new Result<T>(data);
}


泛型方法与可变参数:


private <T> void printData(T... data) {
    for (T t : data) {
        System.out.println(t);
    }
}


注:


  • 泛型方法能使方法独立于类而产生变化


  • 如果 静态(static) 方法 要使用泛型能力,就必须使其成为泛型方法


2. 类型通配符


1)什么是类型通配符


  • 类型通配符一般使用 " ? " 代替具体的实参类型


  • 类型通配符是 实参类型 ,而不是 形参类型


类型通配符又分为 类型通配符的上限类型通配符的下限


2)基本语法


类型通配符的上限


类/接口<? extends 实参类型>


注: 要求该泛型的类型,只能是实参类型,或实参类型的 子类 类型


类型通配符的下限:


类/接口<? super 实参类型>


注:要求该泛型的类型,只能是实参类型,或实参类型的 父类 类型


2)使用示例


类型通配符的上限


如果我们要打印一个 List 的值,我们可能会这么做:


private void printData(List list) {
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }
}


看上去没啥问题,但是又觉得怪怪的。因为这就跟黑匣子一样,我根本不知道这个 List 里面装的是什么类型的参数。那我们就在传参的时候定义一下类型:


private void printData(List<Object> list) {
    for (Object o : list) {
        System.out.println(o);
    }
}


但是这样定义又太广泛了,Object 是所有类型的父类,如果说我想这个方法只能操作数字类型的元素,那我就能用上 类型通配符的上限 来解决这个问题了:


private void printData(List<? extends Number> numList) {
    for (Number number : numList) {
        System.out.println(number);
    }
}


但我们需要使用这个方法时候我们就很直观的可以看出来,这个方法传的实参只能是 Number 的子类。


printData(Arrays.asList(1, 2, 3));
printData(Arrays.asList(1L, 2L, 3L));


类型通配符的下限


上面我们了解到 类型通配符上限的使用 ,那么 类型通配符上限 是如何使用的?


类型通配符上限 在我们平时开发中使用的频率也相对较少。编译器只知道集合元素是下限的父类型,但具体是哪一种父类型是不确定的。因此,从集合中取元素只能被当成Object类型处理(编译器无法确定取出的到底是哪个父类的对象)。


我们可以自定义一个复制集合的函数:


  • 首先定义两个类:


public class Animal {
}
public class Pig extends Animal{
}


  • 定义一个复制函数:


private static <T> Collection<? super T> copy(Collection<? super T> parent, Collection<T> child) {
    for (T t : child) {
        parent.add(t);
    }
    return parent;
}


  • 使用:


List<Animal> animals = new ArrayList<>();
List<Pig> pigs = new ArrayList<>();
pigs.add(new Pig());
pigs.add(new Pig());
copy(animals,pigs);
System.out.println(animals);


3. 类型擦除


因为泛型信息只存在于代码编译阶段,所以在进入 JVM 之前,会把与泛型相关的信息擦除,这就称为 类型擦除


1)无限制类型擦除


类型擦除前:


public class Result<T>{
    private T data;
}


类型擦除后:


public class Result{
    private Object data;
}


2)有限制类型擦除


类型擦除前:


public class Result<T extends Number>{
    private T data;
}


类型擦除后:


public class Result{
    private Number data;
}


3)擦除方法中类型定义的参数


类型擦除前:


private <T extends Number> T getValue(T value){
    return value;
}


类型擦除后:


private Number getValue(Number value){
    return value;
}



目录
相关文章
|
4月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
90 2
|
2月前
|
Java API
[Java]泛型
本文详细介绍了Java泛型的相关概念和使用方法,包括类型判断、继承泛型类或实现泛型接口、泛型通配符、泛型方法、泛型上下边界、静态方法中使用泛型等内容。作者通过多个示例和测试代码,深入浅出地解释了泛型的原理和应用场景,帮助读者更好地理解和掌握Java泛型的使用技巧。文章还探讨了一些常见的疑惑和误区,如泛型擦除和基本数据类型数组的使用限制。最后,作者强调了泛型在实际开发中的重要性和应用价值。
32 0
[Java]泛型
|
2月前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
18 1
|
2月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
27 5
|
3月前
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
43 9
Java——包装类和泛型
|
2月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
19 1
|
2月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
22 2
|
3月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
3月前
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。
|
2月前
|
安全 Java 编译器
Java基础-泛型机制
Java基础-泛型机制
17 0