Java泛型

Java泛型

1、什么是泛型

①定义

泛型可以适用很多很多不同的类型。泛型本质上是将类型参数化

泛型可以使用在类、方法、接口上面,分别叫做泛型类、泛型方法、泛型接口

②泛型优缺点对比

  • 不使用泛型:

    ①类型不安全,不指定泛型默认的对象是Object,底层维护Object数组。并且错误只有在运行的时候才能发现异常。

    ②需要频繁的进行类型装换,甚至有时候不知道将对应的类型装换成什么类型。

  • 使用泛型:

    ①类型是安全的,只能存指定指定泛型的对象。

    ②不需要进行强制装换,java编译器给进行了装换。

2、定义泛型

①泛型类

定义泛型类,在类名后面加一对尖括号并在尖括号中填写参数类型,参数可以有多个,多个参数使用逗号分隔。

常用约定泛型参数:

  1. T:表示任意类型,type

  2. E:集合中元素类型,element

  3. K:键值对中的key

  4. V:键值对中的value

通常地,泛型参数我们使用单个大写的字母表示泛型。

//泛型类
public class GenericClass<T> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}
@Test
public void testGenericClass() {
    GenericClass<String> g = new GenericClass<>();
    g.setValue("张三");
    //g.setValue(23);  错误的,不能指定string类型之外的类型
}

②泛型接口

//泛型接口
public interface GenericInterface<T> {

    T show(T data);

    void sayHello(T say);
}

//指定的泛型类型,替换了原先泛型参数T的位置
class impl implements GenericInterface<String> {

    @Override
    public String show(String data) {
        return data;
    }

    @Override
    public void sayHello(String say) {
        System.out.println("say:" + say);
    }
}

③泛型方法

泛型方法可以定义在泛型类之中也可以定义在普通类中。

泛型方法在返回值前面,修饰符后面添加尖括号并在其中添加参数类型。

泛型方法通常遵循的原则:如果可以定义泛型方法,那就定义泛型方法,那就不要把整个类定义成泛型类。

//普通方法中定义泛型方法
public class GenericMethod {

    //需要注意的是,在泛型类中,静态方法中的泛型参数和泛型类没有一毛钱关系
    public static <T> void fa(T value) {
        System.out.println("泛型方法1");
    }

    public <T> void fb(T value) {
        System.out.println("泛型方法2");
    }

    public <T> T fc(T value) {
        System.out.println("泛型方法3");
        return (T) value;
    }

    //限制T的类型,只能是继承自Number的类型
    public <T extends Number> void fd(T value) {
        System.out.println("泛型方法4");
    }

    //...
}

3、类型擦除

什么是类型擦除呢?类型擦除就是将指定泛型的类型替换到泛型参数的位置。没有指定泛型的类型默认使用Object替换。类型擦除对于JVM来说,根本就没有泛型的概念,只有具体的类。

编译时,对于泛型类:

第一步:去掉类名后的尖括号和尖括号中的参数,剩下的名字就是对应的类名。

第二步:对于类中使用了类型参数的变量类型,如果没有类型限定的情况下,使用Object替换,如果有类型的类型,使用限定的类型替换。

下面使用jdk提供的工具反编译一下生成的代码,查看类型擦除后是什么内容,就以上面的泛型类为例子查看:

//cmd 下
javap -help  -- 查看帮助手册
javap -s -c 文件名.class  -- 反编译并查看

javap -c -s 反编译的类文件名.class

没有指定泛型类型 的情况下,擦除类型后的默认类型为Object:

在这里插入图片描述
然后我再反编译泛型类中的那个测试方法:

@Test
public void testGenericClass() {
    //这次指定了泛型的类型是String类型
    GenericClass<String> g = new GenericClass<>();
    g.setValue("张三");
    //g.setValue(23);  错误的,不能指定string类型之外的类型
}

在这里插入图片描述

注!编译出来的信息,括号里面的是参数,括号后面的是返回值类型。

后面又写了一个方法,使用指定泛型类型的形参传入一个默认的泛型类,像下面这样:

public class TestGeneric {

    public static void main(String[] args) {
        GenericClass g = new GenericClass();
    }

    public static String test(GenericClass<String> g) {
        return g.getValue();
    }
}

查看反编译文件:
在这里插入图片描述
注!编译器自动为我们进行了强制类型转换。

结论:

泛型类的类型转换与强制类型转换的消除,是由编译器偷偷做了。

4、类型通配符

什么是类型通配符呢?类型通配符是用在方法的参数列表里面的,当传入的参数不确定的时候可以使用?来表示,?代表的就是类型通配符。并且还可以指定类型的上限和下限:

上限 <? extends Number>:所有number的子类包括其本身。

下限<? super Integer>:所有integer的父类或者接口,包括其本身。

//不确定泛型参数的类型
public void useGeneric(GenericClass<?> g) {
    g.getValue();
}
//将传入的泛型参数限制在Number的子类或者是子接口
public void useGeneric2(GenericClass<? extends Number> g) {
    g.getValue();
}
//传入的泛型参数只能是Integer的父类或者是父接口
public void useGeneric3(GenericClass<? super Integer> g) {
    g.getValue();
}

5、使用泛型注意

  1. 不能使用基本数据类型去实例化类型参数:存在基本数据类型与类类型强制转换的问题,因为他们之间不能强制转换。

    //不能使用基本数据类型去实例化类型参数
    //GenericClass<int> g = new GenericClass<>();
    //但是,可以使用包装类
    GenericClass<Integer> g = new GenericClass<>();
    
  2. 运行时的类型检查不适用于泛型:判断继承关系。对于JVM讲,没有泛型的概念,也就是说不存在这种类型。

    //不能在运行过程中去判断,泛型相关
    //if ("字符串类型" instanceof GenericClass<String>){}
    
  3. 不能实例化泛型类型的数组。

    泛型设计的原则:如果 一段代码在编译的时候没有给出“未经检查的转换”的警告,则程序在运行时不会出现ClasscastException异常。

    //不能创建泛型数组
    //GenericClass<Integer>[] g2 = new GenericClass<>[5];
    //但是这样就可以,咋也不敢问,咋也不知道为啥
    GenericClass<Integer>[] g3 = new GenericClass[5]; //ok
    
  4. 不能实例化类型参数。

    //这都是不被允许的
    new T();  //no
    T.class;  //no
    T[] t = new T[5]; //no
    
  5. 静态方法不能使用类上下文中定义的类型参数。但是和类的上下文没有一毛钱关系的类型参数可以使用。

    //在静态方法中使用类上下文的T是不被允许的
    public static void test(T value) {
        return (T) value;
    }
    //但是可以将静态方法定义成泛型方法,只不过此时的T并非彼时的T,他和类上下文没有关系,只不过起名为T而已
    public static <T> void test(T value) {
    
    }
    
  6. 泛型在异常中的使用:泛型可以被当作异常抛出,但是不能用来捕捉。

    //这样使用是不正确的
    try {
    
    } catch (T e) {
    
    }
    //这样可以使用,不过应该没人这么用把,注意T必须是继承自Throwable或者是Exception,不然异常还是不能被允许这样使用的
    public void fa(T e) throws Throwable {
        throw e;
    }
    
  7. 类型擦除冲突。方法重写的时候可能会出现类型擦除冲突,如equals(T a);总之就是类型擦除之后也不能和其他类型重复。

    //不能这么用,因为类的所有父类都是Object,并且当前类继承了父类的equals方法,如果参数指定T,当类型擦除的时候,T可能会被替换为Object就会出现重复
    public boolean equals(T o) {
        return false;
    }
    //如:
    @Override
    public boolean equals(Object o) {
    	return false;
    }
    
  8. 另外一个泛型原则:想要支持类型擦除的转换,就需要强行限制一个类或者类型参数,不能同时成为两个接口类型的子类,并且这两个接口是同一个接口的不同参数化

    //一个类,实现了同一个接口的不同参数化,这样是不被允许的,这样就需要限制一个类,或者是一个类型参数
    public class TestClass implements GenericInterface<String>, GenericInterface<Integer>{
    
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白说(๑• . •๑)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值