java generics(泛型)

在定义类、接口和方法时,泛型使类型(类和接口)成为参数。与方法声明中使用的形参非常相似,类型参数为您提供了一种方法,可以用不同的输入重用相同的代码。不同之处在于形式参数的输入是值,而类型参数的输入是类型。

使用泛型有许多好处:

1、在编译时加强类型检测

通过使用泛型,可以在编译时捕获和修复类型错误,从而避免在运行时出现 ClassCastException 等类型转换异常。这提高了代码的可靠性和稳定性。

2、消除类型转换

使用泛型可以避免一些操作的强制类型转换

3、提高代码重用性

泛型可以使代码更加通用,可以编写一次代码来处理多种类型的数据。

4、提高性能

泛型是在编译时进行类型检查的,因此可以避免在运行时进行类型转换,从而提高了程序的性能。

泛型定义

泛型可以定义在类,接口和方法上。泛型使用 < >来指定泛型类型。

public class Aminal<T> {
    private T flag;

    public T getFlag() {
        return flag;
    }

    public Aminal(T x){
        this.flag = x;
    }

    public static void main(String[] args) {
        Aminal<String> a1 = new Aminal<>("a");
        Aminal<Integer> a2 = new Aminal<>(2);
        System.out.println(a1.getFlag());
        System.out.println(a2.getFlag());
    }
}

如上,类变量flag在class定义时候指定为泛型,在对应使用泛型变量flag的地方都需要使用泛型进行接收和传递。

常见的泛型类型标识:

E - Element(表示元素,常见于JDK的集合框架中)

K - Key(键)

V - Value(值)

N - Number(数字)

T - Type (类型)

S, U, V等 - 第2个、第3个、第4个类型

上面这些泛型类型标识只是一种约定,不会强制进行校验,你也完全可以自定义,如下

public class Fruit<XXX> {
    private XXX price;
    XXX getPrice(){return price;}
    void setPrice(XXX price){
        this.price = price;
    }

    public static void main(String[] args) {
        Fruit<Double> f1 = new Fruit<>();
        f1.setPrice(2.1d);
        Fruit<Integer> f2 = new Fruit<>();
        f2.setPrice(5);
    }
}

上面使用XXX来表示类型,一样可以正常编译使用。

多个泛型

定义泛型时,可以指定多个泛型类型,多个之间使用,隔开

public class Pair<K,V> {
    K key;
    V value;
    public Pair(K k,V v){
        this.key = k;
        this.value = v;
    }

    public static void main(String[] args) {
        Pair<Integer,String> pair = new Pair<>(666,"泛型");
        System.out.println(pair.value);
    }
}
泛型方法

泛型方法相比于普通方法的声明,还会在返回值前使用 < >来声明使用的泛型参数列表

public static <T> List<T> fromArrayToList(T[] a) {
	return Arrays.stream(a).collect(Collectors.toList());
}

这里需要主要一点类上的泛型在类方法上都可以直接使用(注意是非静态方法),不用在方法上声明。

泛型类型限定

可以限定泛型为某个类的子类或实现了某个接口,使用extends来指定父类。

public static <T extends Number> float plus(T a ,T b){
return a.floatValue() + a.floatValue();
}

如果要限定多个条件可以使用 &来连接。

<T extends Number & Comparable>
通配符限定

在泛型代码中,问号(?)被称为通配符,用来表示未知类型。通配符可以在多种情况下使用:作为参数、字段或局部变量的类型;有时作为返回类型(尽管最好的编程实践是更加具体)。通配符永远不会被用作泛型方法调用的类型参数,泛型类的实例创建,或者超类型。

通配符限定只能使用在引用类型上,是是对泛型的限定。可以限定泛型的上界和下界。

<? extends Foo>
<? super Foo> 

上界:? extends Foo表示泛型最高类型是Foo,只能是Foo及其子类。

下界:? super Foo表示泛型最低类型是Foo,只能是Foo及其父类。

无界:? 表示没有类型限制

例如:

public void printFruits(List<? extends Fruit> fruits) {
    for (Fruit fruit : fruits) {
        System.out.println(fruit);
    }
}

这里入参约束成Fruit的上界,也就是入参只能是实现了Fruit接口的类,这样在方法体中就可以调用Fruit接口统一的方法来完成逻辑操作。这里一定要理解 ?extends和 T extends的区别。?extends是针对引用类型,也就是实际参数,而T extends是方法或类的定义上

类型擦除(Type Erasure)

Java 中的泛型在编译时会进行类型擦除(Type Erasure)。类型擦除是 Java 泛型实现的一种机制,它允许你在编译时使用泛型类型(也就是在编码是进行检测),但在运行时使用的是原始类型。在编译时,泛型类型参数被擦除并替换为其边界或 Object 类型。

验证泛型擦除可以使用反射来操作class。

如下定义类

public class ErasureTest<T,X extends Number> {
    T t;
    X x;
    public void setT(T t){this.t = t;}    
}

通过反射打印类信息:

Class c = ErasureTest.class;
for (Field field : c.getDeclaredFields()) {
	System.out.println(field.getName()+":"+field.getType());
}
for (Method method : c.getDeclaredMethods()) {
  System.out.println(method.getName()+":");
  Class[] params = method.getParameterTypes();
  for (int i = 0; i < params.length; i++) {
  System.out.println("参数"+params[i].getName()+",类型:"+params[i].getTypeName());
}
/**
输出内容:
t:class java.lang.Object
x:class java.lang.Number
setT:
参数java.lang.Object,类型:java.lang.Object
*/

这里可以看到X extends Number转换成了其上界Number,T转换成了其原始类型Object。

擦除带来的问题

由于擦除,通过反射在对方法进行调用时可以跳过类型约束:

List<String> list = new ArrayList<>();
list.add("haha");
Method addMethod = list.getClass().getDeclaredMethod("add", Object.class);
addMethod.invoke(list,Integer.valueOf(1));
System.out.println(list);

如上代码,定义了一个String型的list,但是我们通过反射成功的往list添加了一个Integer类型的,上面的代码可以正常执行。这就可以绕过泛型限定。

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值