关于java泛型你应该知道的那些事儿

简介: 泛型的设计初衷:是为了减少类型转换错误产生的安全隐患,而不是为了实现任意化。泛型可以应用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法

泛型的设计初衷:是为了减少类型转换错误产生的安全隐患,而不是为了实现任意化。


泛型可以应用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法


  • 泛型类在类名后面用尖括号表示泛型


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


  • 泛型方法在方法声明中加入泛型


//这是泛型方法
static <T> void printHelloWorld(T t){
        LOGGER.info(t);
}
//这是一个普通方法
public T getT() {
        return t;
}


  • 泛型接口跟类类似


public interface Generator<T> {
    public T next();
}


总结:


  • 有 带尖括号的 才能表示是泛型类或方法或接口。而只有T的只能表示是泛型类型
  • 泛型类型的命名并不是必须为T,也可以使用其他字母,如X、K等,只要是命名为单个大写字即可。
  • 虽然没有强制的命名规范,但是为了便于代码阅读,也形成了一些约定俗成的命名规范,如下:


T 通用泛型类型,通常作为第一个泛型类型
S

通用泛型类型,如果需要使用多个泛型类型,可以将S作为第二个泛型类型

U 通用泛型类型,如果需要使用多个泛型类型,可以将U作为第三个泛型类型
V 通用泛型类型,如果需要使用多个泛型类型,可以将V作为第四个泛型类型
E 集合元素 泛型类型,主要用于定义集合泛型类型
K 映射-键 泛型类型,主要用于定义映射泛型类型
V 映射-值 泛型类型,主要用于定义映射泛型类型
N 数值 泛型类型,主要用于定义数值类型的泛型类型


通配符、上下边界、无界


       如果不对泛型类型做限制,则泛型类型可以实例化成任意类型,这种情况可能产生某些安全性隐患。


       为了限制允许实例化的泛型类型,我们需要一种能够限制泛型类型的手段,即:有界泛型类型


// 有界泛型类型语法 - 继承自某父类
<T extends ClassA>
//有界泛型类型语法 - 实现某接口
<T extends InterfaceB>
//有界泛型类型语法 - 多重边界
<T extends ClassA & InterfaceB & InterfaceC ... >
//示例
//N标识一个泛型类型,其类型只能是Number抽象类的子类
<N extends Number> 
//T标识一个泛型类型,其类型只能是Person类型的子类,并且实现了Comparable 和 Map接口
<T extends Person & Comparable & Map>


上边界通配符()


  • 上界:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
  • 类型参数列表中如果有多个类型参数上限,用逗号分开


private <K extends A, E extends B> E test(K arg1, E arg2){}


  • 上边界类型通配符可以确定父类型
  • 获取数据时,由于固定了上界,类型肯定是返回类型的子类型,可以通过向上转型成功获取。
  • 写入数据时,由于向下转型存在很大风险,Java泛型为了减低类型转换的安全隐患,不允许这种操作


4.jpg


Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
//不能存入任何元素
p.set(new Fruit());    //Error
p.set(new Apple());    //Error
//读取出来的东西只能存放在Fruit或它的基类里。
Fruit newFruit1=p.get();
Object newFruit2=p.get();
Apple newFruit3=p.get();    //Error


原因是编译器只知道容器内是Fruit或者它的派生类,但具体是什么类型不知道。可能是Fruit?可能是Apple?也可能是Banana,RedApple,GreenApple?编译器在看到后面用Plate赋值以后,盘子里没有被标上有“苹果”。而是标上一个占位符:CAP#1,来表示捕获一个Fruit或Fruit的子类,具体是什么类不知道,代号CAP#1。然后无论是想往里插入Apple或者Meat或者Fruit编译器都不知道能不能和这个CAP#1匹配,所以就都不允许。


下边界通配符()



  • 下界通配符不影响往里面存储,但是读取出来的数据只能是Object类型。
  • 和上界相对的就是下界 ,语法表示为:


5.jpg


Plate<? super Fruit> p=new Plate<Fruit>(new Fruit());
//存入元素正常
p.set(new Fruit());
p.set(new Apple());
//读取出来的东西只能存放在Object类里。
Apple newFruit3=p.get();    //Error
Fruit newFruit1=p.get();    //Error
Object newFruit2=p.get();


因为下界规定了元素的最小粒度的下限,实际上是放松了容器元素的类型控制。既然元素是Fruit的基类,那往里存粒度比Fruit小的都可以。但往外读取元素就费劲了,只有所有类的基类Object对象才能装下。但这样的话,元素的类型信息就全部丢失。


  1. 频繁往外读取内容的,适合用上界Extends。
  2. 经常往里插入的,适合用下界Super。


无边界通配符()


      无边界通配符 等同于上边界通配符,所以关于无边界通配符()就很好理解了。

       因为可以确定父类型是Object,所以可以以Object去获取数据(向上转型)。但是不能写入数据。


与的区别

       

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


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


 T 是一个 确定的类型,通常用于泛型类和泛型方法的定义

       ?是一个 不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

 

      类型参数 T 只具有 一种 类型限定方式:


T extends A


 但是通配符 ? 可以进行 两种限定:


? extends A
? super A     


T 可以多重限定吗 ? 不行


<T extends ClassA & InterfaceB & InterfaceC ... >


泛型使用的限制


泛型不能使用基本类型


// 编译前类型检查报错
List<int> list = new List<int>();


泛型不允许进行实例化


<T> void test(T t){
    //编译前类型检查报错
    t = new T();
}


泛型不允许进行静态化


static class Demo<T>{
        // 编译前类型检查报错
        private static T t;
        // 编译前类型检查报错
        public static T getT() {
            return t;
        }
    }


泛型不允许直接进行类型转换(通配符可以)


List<Integer> integerList = new ArrayList<Integer>();
List<Double> doubleList = new ArrayList<Double>();
//不能直接进行类型转换,类型检查报错
//integerList = doubleList;


泛型不允许直接使用instanceof运算符进行运行时类型检查(通配符可以)


List<String> stringList = new ArrayList<String >();
//不能直接使用instanceof,类型检查报错
//LOGGER.info(stringList instanceof ArrayList<Double>);
//我们可以通过通配符的方式进行instanceof运行期检查(不建议):
//通过通配符实现运行时验证
LOGGER.info(stringList instanceof ArrayList<?>);


泛型不允许创建确切类型的泛型数组(通配符可以)


//类型检查报错
//Demo6<Integer>[] iDemo6s = new Demo6<Integer>[2];


泛型不允许定义泛型异常类或者catch异常(throws可以)

  • 不允许定义泛型异常类(直接或间接扩展Throwable类)
  • 不允许捕获一个泛型异常
  • 可以以异常类作为边界
  • 可以throws泛型类型


泛型不允许作为参数进行重载


static class Demo8<T>{
    void test(List<Integer> list){}
    //不允许作为参数列表进行重载
    //void test(List<Double> list){}
}


相关文章
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
230 2
|
Java 编译器 容器
Java——包装类和泛型
包装类是Java中一种特殊类,用于将基本数据类型(如 `int`、`double`、`char` 等)封装成对象。这样做可以利用对象的特性和方法。Java 提供了八种基本数据类型的包装类:`Integer` (`int`)、`Double` (`double`)、`Byte` (`byte`)、`Short` (`short`)、`Long` (`long`)、`Float` (`float`)、`Character` (`char`) 和 `Boolean` (`boolean`)。包装类可以通过 `valueOf()` 方法或自动装箱/拆箱机制创建。
130 9
Java——包装类和泛型
|
安全 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版)
|
11月前
|
存储 安全 Java
🌱Java零基础 - 泛型详解
【10月更文挑战第7天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
96 1
|
11月前
|
Java 语音技术 容器
java数据结构泛型
java数据结构泛型
87 5
|
11月前
|
存储 Java 编译器
Java集合定义其泛型
Java集合定义其泛型
67 1
|
11月前
|
存储 Java 编译器
【用Java学习数据结构系列】初识泛型
【用Java学习数据结构系列】初识泛型
83 2
|
存储 安全 搜索推荐
Java中的泛型
【9月更文挑战第15天】在 Java 中,泛型是一种编译时类型检查机制,通过使用类型参数提升代码的安全性和重用性。其主要作用包括类型安全,避免运行时类型转换错误,以及代码重用,允许编写通用逻辑。泛型通过尖括号 `&lt;&gt;` 定义类型参数,并支持上界和下界限定,以及无界和有界通配符。使用泛型需注意类型擦除、无法创建泛型数组及基本数据类型的限制。泛型显著提高了代码的安全性和灵活性。
141 8
|
存储 算法 Java
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
128 2
14 Java集合(集合框架+泛型+ArrayList类+LinkedList类+Vector类+HashSet类等)
|
安全 Java Go
Java&Go泛型对比
总的来说,Java和Go在泛型的实现和使用上各有特点,Java的泛型更注重于类型安全和兼容性,而Go的泛型在保持类型安全的同时,提供了更灵活的类型参数和类型集的概念,同时避免了运行时的性能开销。开发者在使用时可以根据自己的需求和语言特性来选择使用哪种语言的泛型特性。
167 7