泛型的应用

基本概念

在 Java 中,泛型是一种强类型约束机制,可以在编译期间检查类型安全性,并且可以提高代码的复用性和可读性。

为什么要设计泛型?

举个集合的例子:

这段代码是有泛型,并且会限定集合中类型是String:

Arraylist<String> list = new ArrayList<String>();
list.add("string类型");
String str = list.get(0);

假如没有泛型,以使用 Object 数组来设计 Arraylist 类。:

class Arraylist {
    private Object[] objs;
    private int i = 0;
    public void add(Object obj) {
        objs[i++] = obj;
    }
    
    public Object get(int i) {
        return objs[i];
    }
}

存取数据:

Arraylist list = new Arraylist();
list.add("string类型");
list.add(1111);
String str = (String)list.get(0);

可以看到使用了Object定义集合类型后,集合可以存储所有继承Object类的数据。这样的话从集合取出数据的话就需要进行强制转换,也是很麻烦很容易出错。

所以泛型优点就是:使用类型参数解决了元素的不确定性

再比如:

有个试想你需要一个简单的容器类,比如要存放一个苹果的篮子

class Fruit{}
class Apple extends Fruit{}

class Bucket{
    private Apple apple;
   
     public void set(Apple apple){
        this.apple = apple;
    }
   
    public Apple get(){
        return this.apple;
    }
}

水果多了,需要不同容器:

class Fruit{}
class Apple extends Fruit{}
class Banana extends Fruit{}
class Orange extends Fruit{}

class BucketApple{
    private Apple apple;

    public void set(Apple apple){
        this.apple = apple;
    }

    public Apple get(){
        return this.apple;
    }
}

class BucketBanana{
    private Banana banana;

    public void set(Banana banana){
        this.banana = banana;
    }

    public Banana get(){
        return this.banana;
    }
}
class BucketOrange{
    private Orange orange;

    public void set(Orange orange){
        this.orange = orange;
    }

    public Orange get(){
        return this.orange;
    }
}

大量重复,要写一个Object类型的Bucket就可以存放任何类型了

class Bucket{
    private Object object;

    public void set(Object object){
        this.object = object;
    }

    public Object get(){
        return this.object;
    }
}

但是问题是这种容器的类型丢失了,你不得不在输出的地方加入类型转换:

Bucket appleBucket = new Bucket();
bucket.set(new Apple());
Apple apple = (Apple)bucket.get();

泛型使用

泛型类

格式:

修饰符 class 方法名<类型>

示例:

public class GenericsMutilClass<K,V> { //此处可以是任意的标识符号,T 是 type 的简称
    private K param1; // 此变量的类型由外部决定
    private V param2; // 此变量的类型由外部决定

    public void getParams(){
        System.out.println(param1);
        System.out.println(param2);
    }

    public void setParam1(K param1) {
        this.param1 = param1;
    }

    public void setParam2(V param2) {
        this.param2 = param2;
    }
}

 class Test{
     public static void main(String[] args) {
         GenericsMutilClass<String,Integer> genericsMutilClass = new GenericsMutilClass<>();
         genericsMutilClass.setParam1("zs");
         genericsMutilClass.setParam2(20);
         genericsMutilClass.getParams();
     }
}

泛型方法

格式:

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

示例:


/**
    <T> T可以传入任何类型的list
    关于参数T的说明:
        第一个T表示<T>是一个泛型
        第二个T表示方法返回的是T类型的数据
        第三个T表示集传入的数据是T类型

*/

public class GenericsMethod {

    public <T> T genericsMethod(T t){
        return t;
    }

}
class GenericsMethodTest{
    public static void main(String[] args) {
        GenericsMethod genericsMethod = new GenericsMethod();
        Integer integer = genericsMethod.genericsMethod(111);
        System.out.println(integer);
    }
}


/**
多参数
*/
public class GenericMethod {

    //3.带可变参数的泛型方法
    public <A> void argsMethod(A ... args){
        for (A arg : args) {
            System.out.println(arg);
        }
    }
}

泛型接口

格式:

修饰符 interface 接口名<类型>{

.....

}

示例:

public interface GenericsInterface<T> {
   T genericsInterface ();
   void genericsInterface(T t);
}

class GenericsInterfaceClass implements GenericsInterface<String>{ //实现的时候指定类型

    @Override
    public String genericsInterface() {
        return null;
    }

    @Override
    public void genericsInterface(String s) {

    }
}

泛型上下限

格式:

  • 上界通配符(? extends T:表示参数化类型的可能是T或T的某个子类型。它限制了未知类型的上限。上界通配符是为了安全地读取T类型数据而设计的。
  • 下界通配符(? super T:表示参数化类型是T或T的某个父类型。下界通配符让咱们可以安全地写入T和T的子类型的对象。

示例:

public class GenericsInterExtent {
}
class GenericsInterExtent1 extends GenericsInterExtent{
}
class GenericsInterExtentTest{
    public <T extends GenericsInterExtent> T genericsInterExtent(){
        T t = null;
        GenericsInterExtent1 genericsInterExtent1 = new GenericsInterExtent1();
        t = (T)genericsInterExtent1;
        return t;
    }
}
class GenericsInterSupperTest{
    public <T> void genericsIntersuper(GenericsInterExtent1 t, List<? super GenericsInterExtent1> list){
        list.add(t);
    }
}

泛型擦除

在 Java 的泛型机制中,有两个重要的概念:类型擦除和通配符。

如在List<Object>,List<String>等类型,在编译后都会变成List,JVM看到的只是List,而由泛型的附加信息对JVM是看不到的。Java编译器会在编译期间尽可能的发现出错的地方,但是无法在运行时刻出现类型转换和类型转换异常的情况。

public class Test {
 
    public static void main(String[] args) {
 
        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");
 
        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);
 
        System.out.println(list1.getClass() == list2.getClass());//true
    }
 
}

泛型在编译时会将泛型类型擦除,将泛型类型替换成 Object 类型。这是为了向后兼容,避免对原有的 Java 代码造成影响。

如,对于下面的代码:

List<Integer> intList = new ArrayList<>();
intList.add(123);
int value = intList.get(0);

在编译时,Java 编译器会将泛型类型 List<Integer> 替换成 List,将 get 方法的返回值类型 Integer 替换成 Object,生成的字节码与下面的代码等价:

List intList = new ArrayList();
intList.add(Integer.valueOf(123));
int value = (Integer) intList.get(0);

Java 泛型只在编译时起作用,运行时并不会保留泛型类型信息。

为什会有泛型擦除呢?

是指在Java中,编译器在生成字节码时会擦除泛型类型信息。这个设计是为了兼容Java旧版本的字节码,因为泛型是在Java 5引入的,早期的Java版本并不支持泛型。擦除泛型类型信息可以使得新引入的泛型代码可以与旧版本的Java代码兼容运行。

具体来说,泛型擦除带来了以下几点原因和影响:

  1. 兼容性:通过擦除泛型信息,可以使得使用泛型编写的新代码可以在旧版本的Java虚拟机上运行,因为旧版本的JVM不支持泛型。
  2. 类型安全:尽管在运行时泛型类型信息被擦除了,但在编译时编译器会进行类型检查,从而保证类型安全性。这种方式通过静态类型检查确保泛型代码在编译时没有类型错误。
  3. 性能:泛型擦除可以减少生成的字节码的大小,从而提高性能和减少内存占用。这是因为泛型信息只在编译期间起作用,在运行时不需要保留。

总结来说,泛型擦除是为了在引入泛型后保持与旧版本Java代码的兼容性,并通过编译时的类型检查确保类型安全,尽管它在运行时会导致泛型类型信息丢失的情况。

带来的问题:

不能实例化泛型类型的数组

 T[] array = new T[10]; // 编译错误

这是因为在运行时,JVM需要知道数组的确切类型,而由于类型擦除,这个信息是不可知的。

不能实例化泛型类的类型参数

 public class GenericClass<T> {
   T obj = new T(); // 编译错误
 }

同样是因为在运行时,T的具体类型是未知的。

不能创建具体类型的泛型数组

 List<Integer>[] arrayOfLists = new List<Integer>[10]; // 编译错误

这违反了Java的类型安全原则,因为泛型类型在运行时会被擦除,导致数组的实际类型只能是List[]

泛型类不能扩展Throwable

public class GenericException<T> extends Exception { } // 编译错误

这是因为异常处理是在运行时进行的,需要知道异常的确切类型。

泛型实际应用

数据类型转换:

public class DataConverter<T> {
    public <U> U convert(T data, Class<U> targetClass) throws Exception {
        // 实现数据转换逻辑
       // U u = targetClass.getDeclaredConstructor().newInstance()
        U u = conver(data,targetClass);
        return u;
    }
}
class tt{
    public static void main(String[] args) throws Exception {
        // 数据;类型转换
        DataConverter<String> converter = new DataConverter<>();
        Integer convertedData = converter.convert("123", Integer.class);
    }
}

再比如:

 /**
     * 将List<T> 转为List<K> ,注意:此方法会舍弃掉非公有字段
     *
     * @param t1  List<T>
     * @param k1  转换后对象
     * @param <T> 原始类型
     * @param <K> 转换后类型
     * @return 转换后对象
     * @throws IllegalArgumentException 转换异常
     */
    public static <T, K> List<K> listObjToListObj(List<T> t1, Class<K> k1) {
        try {
            ObjectMapper mapper = new ObjectMapper();
            String json = mapper.writeValueAsString(t1);
            JavaType javaType = getCollectionType(mapper, k1);
            return mapper.readValue(json, javaType);
        } catch (IOException e) {
            throw new IllegalArgumentException("listObjToListObj对象转换异常" + k1, e);
        }
    }

参考:java为什么要用类型擦除实现泛型? | Pulpcode

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值