java泛型类的作用_【Java-泛型系列一-泛型的作用】

1 泛型是什么

泛型是一种在编译期间进行集合中的元素进行限定的机制。使用了泛型,在运行期见可以安全的将元素强转成指定的元素。下面举个例子看一下有和没有泛型的区别

1.1 假如没有泛型

List arrayList = new ArrayList();

arrayList.add("aaaa");

arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){

String item = (String)arrayList.get(i);

Log.d("泛型测试","item = " + item);

}

在没有使用泛型的情况下,我们往例子中的arrayList存进Integer 或者 String 都是合法的,但是在从arrayList往外取数据的时候,就会因为强转而报错(毕竟我们不可能一直使用Object)

1.2 使用泛型

List arrayList = new ArrayList();

arrayList.add("aaaa"); // IDE报异常arrayList.add(100);

当我们使用了泛型,上面代码中第二行IDE就会报异常。

在这个例子中,JAVA通过泛型这个机制帮助我们规范集合中的数据,避免我们在使用集合的时候出现异常,提前帮我们发现问题。

泛型的作用大抵可以概括为限制。

2 泛型是怎么做到的

在Java中,泛型分为两部分:类型擦除 和强转

2.1 类型擦除

当我们在代码中使用泛型的时候,编译器(例如:Javac)就会分析代码运行时要装入的数据是否符合我们的要求,如果顺利通过检查,就会生成普通的不带泛型的字节码,这种字节码可以被一般的 Java 虚拟机接收并执行。这种技术被称为擦除(erasure)。

也就是说,泛型的检查是发生在编译期,经过编译器检查而生成的字节码和普通的不使用泛型的代码所生成的字节码没什么区别。可以理解为在实际运行中,上面例子中的List 的字节码和 List 没区别,列表中本质上都是Object

举个例子

List l1 = new ArrayList();

List l2 = new ArrayList();

System.out.println(l1.getClass() == l2.getClass());

运行结果是true,证明Java对泛型进行了类型擦除,或者说是 一个泛型类被其所有调用共享。即 List被所有的 List 共享。

为什么Java要进行类型擦除,在编译器而不是在JVM进行限制呢?

一个说话是为了向下兼容

泛型是JDK1.5的“新特性”,如果选择在JVM层面来实现泛型,那么就无法兼容使用JDK1.5版本之前的服务器。而如果在编译器进行检查,然后生成同样的字节码的话,就可以完美兼容之前的版本。

2.2 强转

经过类型擦除,字节码被编译成了统一的Object,那为什么当我们在使用泛型的,得到的是一个具体类,而不是一个Object?

这是因为Java的编译器帮我们做了强转,避免我们自己去手动强转对象。比如

LinkedList ys = new LinkedList();

ys.add("zero");

ys.add("one");

String y = ys.iterator().next();

可以理解为

LinkedList ys = new LinkedList();

ys.add("zero");

ys.add("one");

String y = (String)ys.iterator().next();

总结一句话就是 泛型是一个在编译时期检查类型,在存入将对象转成Object类型,在取出的时候进行强转的机制。

3 Java实现泛型的代价-桥方法(bridge method)

我们通过编写一个Byte类来发现问题

class Byte implements Comparable {

private byte value;

public Byte(byte value) {

this.value = value;

}

public byte byteValue() {

return value;

}

public int compareTo(Byte that) {

return this.value - that.value;

}

}

我们知道,泛型本质上是一个编译器检查,在运行期中是以Object的形式存在的。在 Byte 这个例子中,如果他在运行期中是如何调用 public int compareTo(Byte that) 这个方法的。要知道,在运行期中只有Object。

为此,Java用了一种叫桥方法的技术。

我们将这个类编译成字节码文件,然后通过jdk提供的javap工具查看 Byte.class文件,如下所示

Compiled from "Byte.java"

class com.joe.test.demo.controller.testJava.Byte implements com.joe.test.demo.controller.testJava.Comparable {

private byte value;

public com.joe.test.demo.controller.testJava.Byte(byte);

public byte byteValue();

public int compareTo(com.joe.test.demo.controller.testJava.Byte);

public int compareTo(java.lang.Object);

}

可以发现,反编译出来的文件比较原始Java文件多了 public int compareTo(java.lang.Object); 这个方法,该方法的实现也很简单,先将Object参数强转成 Byte,然后调用 public int compareTo(com.joe.test.demo.controller.testJava.Byte);。

compareTo(java.lang.Object); 就是一个桥方法,通过引用桥方法,使得泛型代码在运行时期也能执行响应的方法。桥方法顾名思义,他的作用就是一个桥梁,连接Object对象和泛型参数的方法。

3.1 两个有意思的例子

3.1.1 使用泛型时要避开的方法名

class Pair {

public boolean equals(T value) {

return null;

}

}

上面这个类在IDE上会报异常,因为桥方法生成的方法和Object默认的方法竟然一模一样,这样的方法自然不被允许。

3.1.2 “不合法”的Java代码

举一个关于桥方法的极端例子,假设我们有以下这个类

class Pair {

private T value;

public T getValue() {

return value;

}

public void setValue(T value) {

this.value = value;

}

}

然后我们想要一个子类继承它

class DateInter extends Pair {

@Override

public void setValue(Date value) {

super.setValue(value);

}

@Override

public Date getValue() {

return super.getValue();

}

}

在这个子类中,我们设定父类的泛型类型为Pair。根据我们上面讨论的结果,泛型的本质是Object,同时会生成桥方法,如下所示

public void setValue(java.util.Date);

public java.util.Date getValue();

public void setValue(java.lang.Object);

public java.lang.Object getValue();

我们可以看到Object getValue()和Date getValue()是同 时存在的,可是如果是常规的两个方法,他们的方法签名是一样的,也就是说虚拟机根本不能分别这两个方法。如果是我们自己编写Java代码,这样的代码是无法通过编译器的检查的,但是虚拟机却是允许这样做的,因为虚拟机通过参数类型和返回类型来确定一个方法,所以编译器为了实现泛型的多态允许自己做这个看起来“不合法”的事情,然后交给虚拟器去区别。

4 源文信息

4.1 版权本文是由作者阅读了大量博客,根据作者的笔记整理而来。因为时间跨度大,无法一一指出来源,因此会在文章结尾列出所有参照的博客源地址。

如果您认为本文侵权,请联系作者

转载本文麻烦注明源文

4.2 参考博客Java 语言类型擦除​www.ibm.com0d14710884ad7df23c83b5f767432d80.png多角度看 Java 中的泛型​www.ibm.com5b80f736a69bc99df2c0eb5171950a7d.png人类身份验证 - SegmentFault​segmentfault.comhttps://blog.csdn.net/briblue/article/details/76736356​blog.csdn.nethttps://blog.csdn.net/lonelyroamer/article/details/7868820​blog.csdn.net

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,泛型是一种强类型机制,它可以让你在编译时检查类型错误,从而提高代码的安全性和可读性。在使用泛型时,我们经常会遇到父类和子类的泛型转换问题。 首先,我们需要明确一点:子类泛型不能转换成父类泛型。这是因为Java中的泛型是不协变的。例如,如果有一个类A和它的子类B,那么List<A>和List<B>之间是不存在继承关系的。 下面我们来看一个例子: ```java public class Animal { //... } public class Dog extends Animal { //... } public class Test { public static void main(String[] args) { List<Animal> list1 = new ArrayList<>(); List<Dog> list2 = new ArrayList<>(); list1 = list2; // 编译错误 } } ``` 在这个例子中,我们定义了Animal类和它的子类Dog。然后我们定义了两个List,分别是List<Animal>和List<Dog>。如果将List<Dog>赋值给List<Animal>,会出现编译错误。这是因为List<Animal>和List<Dog>之间不存在继承关系。 那么,如果我们想要让子类泛型转换成父类泛型,应该怎么办呢?这时我们可以使用通配符来解决问题。通配符可以表示任意类型,包括父类和子类。例如,我们可以将List<Dog>赋值给List<? extends Animal>,这样就可以实现子类泛型转换成父类泛型了。 下面我们来看一个使用通配符的例子: ```java public class Animal { //... } public class Dog extends Animal { //... } public class Test { public static void main(String[] args) { List<Animal> list1 = new ArrayList<>(); List<Dog> list2 = new ArrayList<>(); list1 = list2; // 编译错误 List<? extends Animal> list3 = new ArrayList<>(); list3 = list2; // 正确 } } ``` 在这个例子中,我们定义了List<? extends Animal>来表示任意继承自Animal的类型。然后我们将List<Dog>赋值给List<? extends Animal>,这样就可以实现子类泛型转换成父类泛型了。 总结一下,Java中的泛型是不协变的,子类泛型不能转换成父类泛型。如果需要实现子类泛型转换成父类泛型,可以使用通配符来解决问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值