java泛型总结

目录

语法糖

泛型简介

泛型的优点

泛型的应用场景

泛型是如何实现的

类型擦除的缺点

泛型的三种使用方式

常用的通配符有哪些?

泛型的上下限

匹配任意类型的通配符

受限泛型

设置上限 

设置下限

解释:泛型与子类继承的限制。


语法糖

首先,在讲java中的泛型之前,需要介绍一下语法糖。什么是语法糖呢?

简单来说,就是在计算机语言里面通过添加某种语法,这种语法不会对语言的编译结果产生实际的影响,但是可以使得程序员更方便的使用该语言,增加程序的可读性,减少代码量,提高开发效率。java里面常见的语法糖除了泛型除了之外,还有自动装箱拆箱,变长参数,他们都是语法糖。

但是实际上JVM虚拟机并不支持这些语法糖中的语法,他们会在编译阶段被还原成原始的基本语法结构。

泛型简介

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

Java 的泛型是伪泛型,这是因为 Java 在运行期间,所有的泛型信息都会被擦掉,这也就是通常所说类型擦除 。

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

list.add(12);
//这里直接添加会报错
list.add("a");
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
//但是通过反射添加是可以的
//这就说明在运行期间所有的泛型信息都会被擦掉
add.invoke(list, "kl");
System.out.println(list);

泛型的限制

1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。

2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

3、泛型的类型参数可以有多个。

4、泛型的参数类型可以使用extends语句,例如<T extends superclass>。习惯上成为“有界类型”。

5、泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName(Java.lang.String); 

泛型的优点

 1,类型安全。 泛型的主要目标是提高 Java 程序的类型安全。通过知道使用泛型定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。

没有泛型,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。 

2,消除强制类型转换。 泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。

3,潜在的性能收益。 泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。

泛型的应用场景

可用于定义通用返回结果 CommonResult<T> 通过参数 T 可根据具体的返回类型动态指定结果的数据类型

定义 Excel 处理类 ExcelUtil<T> 用于动态指定 Excel 导出的数据类型

用于构建集合工具类。参考 Collections 中的 sortbinarySearch 方法

用于集合类中,比如ArrayList,HashMap

泛型是如何实现的

java里面是如何实现泛型的呢?使用的是”类型擦除“。

也就是说java里面的泛型只存在于源代码里面,一旦经过编译之后,所有的泛型都会被擦除掉,全部被替换为原来的裸类型,并在对元素进行访问和修改的时候,才会加上强制类型转换。(所谓的裸类型指的是,ArrayList<Integer> 他的裸类型就是ArrayList)。也正是因为这样,如果我们在运行时判断ArrayList<String> 和ArrayList<Integer>会发现他们属于一个类型。

如果我们对使用了泛型的class文件进行反编译之后就可以发现,所有的泛型都消失了,全部被擦除,被替换成了裸类型,然后所有访问和修改的地方都添加了一个强制类型转换。比如下面,只有在元素访问的时候,做了从Object到String的强制类型转换。

编译之前


编译后

 

类型擦除的缺点

在别的语言里面,比如C#,也有实现泛型,但是实现的原理不一样,它使用的方式是:“具现化”,简单来说就是在C#里面,无论在源代码,还是在编译和,亦或是在运行期间,这些泛型的类型都是实际存在的,也就是说List<int>和List<long>他就是两个不同的类型。

而我们之前说过,java里面实现的方式是“类型擦除”,那么这种实现方式有哪些缺点呢?

1、使用类型擦除直接导致了对于原始的数据类型无法支持,比如int,long这种,因为java不支持Object类型和基本数据类型之间的强制类型转换,也就是说一旦类型擦除之后,就没法在进行 类型转换了。也正是这样,现在的泛型都是不支持原始类型的,比如ArrayList<Integer>,而不能使用ArrayList<int>。

2、运行期间无法获得泛型类型信息。因为泛型都被擦除了,都被替换成了裸类型。这样就导致了下面的程序都会报错,比如无法使用泛型来创建对象,或者数组。

泛型的三种使用方式

泛型一般有三种使用方式: 泛型类、泛型接口、泛型方法。

1.泛型类:

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T> {
    private T key;
    public Generic(T key) {
        this.key = key;
    }
    public T getKey() {
        return key;
    }
}

如何实例化泛型类:

Generic<Integer> genericInteger = new Generic<Integer>(123456);

2.泛型接口 :

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

实现泛型接口,不指定类型:

class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}

实现泛型接口,指定类型:

class GeneratorImpl implements Generator<String>{
    @Override
    public String method() {
        return "hello";
    }
}

3.泛型方法 :

public static <E> void printArray(E[] inputArray) {
    for (E element : inputArray) {
        System.out.printf("%s ", element);
    }
    System.out.println();
}
使用:

// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray(intArray);
printArray(stringArray);

常用的通配符有哪些?

常用的通配符为: T,E,K,V,?

? 表示不确定的 Java 类型

T (type) 表示具体的一个 Java 类型

K V (key value) 分别代表 Java 键值中的 Key Value

E (element) 代表 Element

泛型的上下限

匹配任意类型的通配符

在开发中对象的引用传递(向上向下传递)是最常见的,但是,在泛型的操作中,在进行引用传递的时候泛型类型必须匹配才可以传递,否则不能传递。

例如,如下没有进行泛型类型匹配,一个是String,一个是Object类型。

package Thread1;
class Info<T>{
    private T var ;        // 定义泛型变量
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // 直接打印
        return this.var.toString() ;
    }
};
public class demo1{
    public static void main(String args[]){
        Info<String> i = new Info<String>() ;        // 使用String为泛型类型
        i.setVar("MLDN") ;                            // 设置内容
        fun(i) ;                    //把String泛型类型的i对象传递给Object泛型类型的temp。
    }
    public static void fun(Info<Object> temp){        // 接收Object泛型类型的Info对象
        System.out.println("内容:" + temp) ;
    }
};

编译发生错误。

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    The method fun(Info<Object>) in the type demo1 is not applicable for the arguments (Info<String>)

    at Thread1.demo1.main(demo1.java:18)

泛型对象进行引用传递的时候,类型必须一致,如果非要传递,则可以将fun方法中Info参数的泛型取消掉(变成 void fun(Info temp)。、

以上确实改进了功能,但是似乎不是很妥当,毕竟之前指定过泛型。

以上程序在fun()方法中使用"Info<?>"的代码形式,表示可以使用任意的泛型类型对象,这样的话fun()方法定义就合理了,但是使用以上方法也有需要注意的地方,

即:如果使用“?“接收泛型对象的时候,则不能设置被泛型指定的内容

class Info<T>{
    private T var ;        // 定义泛型变量
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // 直接打印
        return this.var.toString() ;
    }
};
public class GenericsDemo14{
    public static void main(String args[]){
        Info<String> i = new Info<String>() ;        // 使用String为泛型类型
        i.setVar("MLDN") ;                            // 设置内容
        fun(i) ;
    }
    public static void fun(Info<?> temp){        // 可以接收任意的泛型对象
        System.out.println("内容:" + temp) ;
    }
};

如果使用”?“意味着可以接收任意的内容,但是此内容无法直接使得用”?“修饰的泛型的对象进行修改。如下就会出问题:

package Thread1;
class Info<T>{
    private T var ;        // 定义泛型变量
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // 直接打印
        return this.var.toString() ;
    }
};
public class demo1{
    public static void main(String args[]){
        Info<?> i = new Info<String>() ;        // 使用String为泛型类型
        i.setVar("MLDN") ;                            // 设置内容,这里会出错,因为”?“通配符修饰的对象只能接收,不能修改,也就是不能设置。
    }
};

运行结果:

Exception in thread "main" java.lang.Error: Unresolved compilation problem: 
    The method setVar(capture#1-of ?) in the type Info<capture#1-of ?> is not applicable for the arguments (String)

    at Thread1.demo1.main(demo1.java:17)

在使用”?“只能接收,不能修改。

受限泛型

之前设置泛型的时候,实际上是可以任意设置的,只要是类就可以设置。但是在JAVA的泛型中可以指定一个泛型的上限和下限。

设置上限 

class Info<T>{
    private T var ;        // 定义泛型变量
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // 直接打印
        return this.var.toString() ;
    }
};
public class GenericsDemo17{
    public static void main(String args[]){
        Info<Integer> i1 = new Info<Integer>() ;        // 声明Integer的泛型对象
        Info<Float> i2 = new Info<Float>() ;            // 声明Float的泛型对象
        i1.setVar(30) ;                                    // 设置整数,自动装箱
        i2.setVar(30.1f) ;                                // 设置小数,自动装箱
        fun(i1) ;
        fun(i2) ;
    }
    public static void fun(Info<? extends Number> temp){    // 只能接收Number及其Number的子类
        System.out.print(temp + "、") ;
    }
};

运行成功。但是,如果传人的泛型类型为String的话就不行,因为String不是Number子类。

在类中使用泛型上限。

package Thread1;
class Info<T extends Number>{    // 此处泛型只能是数字类型
    private T var ;        // 定义泛型变量
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // 直接打印
        return this.var.toString() ;
    }
};
public class demo1{
    public static void main(String args[]){
        Info<Integer> i1 = new Info<Integer>() ;        // 声明Integer的泛型对象
    }
};

如果在使用Info的时候设置成String类型,则编译的时候将会出现错误(String不是Number子类):

设置下限

class Info<T>{
    private T var ;        // 定义泛型变量
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // 直接打印
        return this.var.toString() ;
    }
};
public class GenericsDemo21{
    public static void main(String args[]){
        Info<String> i1 = new Info<String>() ;        // 声明String的泛型对象
        Info<Object> i2 = new Info<Object>() ;        // 声明Object的泛型对象
        i1.setVar("hello") ;
        i2.setVar(new Object()) ;
        fun(i1) ;
        fun(i2) ;
    }
    public static void fun(Info<? super String> temp){    // 只能接收String或Object类型的泛型,String类的父类只有Object类
        System.out.print(temp + "、") ;
    }
};

Object类和String类都是String的父类,所有运行成功,但是如果此时用Integer则会出错,因为integer并不是String父类。

解释:泛型与子类继承的限制。

一个类的子类可以通过对象多态性,为其父类实例化,但是在泛型操作中,子类的泛型类型是无法使用父类的泛型类型接收的。例如:Info<String>不能使用Info<Object>接收。

例如,以下肯定出错。

class Info<T>{
    private T var ;        // 定义泛型变量
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // 直接打印
        return this.var.toString() ;
    }
};
public class GenericsDemo23{
    public static void main(String args[]){
        Info<String> i1 = new Info<String>() ;        // 泛型类型为String
        Info<Object> i2 = null ;
        i2 = i1 ;                  //这里因为对象泛型类型不同,而出错。
    }
};

Object肯定比String大。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值