Java核心技术第八章——泛型程序设计(1)

1.泛型程序设计 泛型程序设计意味着编写的代码可以被很多不同类型的对象所重用。例如:不希望为了聚集String和Integer对象分别设计不同的类。(个人觉得此处说的聚集译为:创建一个对象,属性可以为String和Integer类型。但是却有着相同的行为或属性) 代码如下: 复制代码 public class StringTest { private String age; public String getAge() { return age; } public void setAge(String age) { this.age = age; } } 复制代码 复制代码 public class IntegerTest { private Integer age; public Integer getAge() { return Age; } public void setAge(Integer age) { this.age= age; } } 复制代码 可以看到上面两个类都有size属性和方法。只是他们的属性类型有所不同。那么我们则可以使用到泛型:例 复制代码 public class GenericTest { private T size; public T getSize() { return size; } public void setSize(T size) { this.size = size; } } 复制代码 然后只需要如下调用 复制代码 public static void main(String[] args){ GenericTest genericString= new GenericTest<>(); GenericTest genericInteger = new GenericTest<>(); } 复制代码 这样我们就很好的解决了代码重用的问题,不是吗?看不懂?不急,我只是说了泛型的好处,让我们慢慢往下了解。 2.定义简单的泛型类 没接触过泛型的同学看到上面例子中的T可能会觉得一脸懵逼,其实T只是一个类的类型变量(虽然你可以定义A、B、C...,但是我们还是要规范点的是吧),然后T需要使用尖括号括起来,当你定义多个类型变量时。可以看下面的Generic类, 复制代码 public class Generic { private T size; private U length; ... } 复制代码 当你传入Generic 时,你可以把这个类想象成如下使用 复制代码 public class Generic { private Integer size; private String length; ... } 复制代码 所以,泛型类其实可以看做普通的工厂类。 3.泛型方法 上面介绍了一个简单的泛型类,让我们来看看泛型方法是怎么写吧。 复制代码 public class ArrayAlg { public static T getMiddle(T... a){ return a[a.length/2]; } } 复制代码 可以看到,ArrayAlg是一个普通类,并不是一个泛型类。说明:泛型方法可以定义在普通类和泛型类中。 当我们调用getMiddle方法时,在方法名前的尖括号放入具体类型: String s = ArrayAlg.getMiddle("John","Q","Min"); 看起来是不是很别扭?其实可以省略类型参数,编译器会使用String[ ]数组与泛型T[ ]进行匹配推断出一定是String,这个应该就是他们私下bilibili的规则了。变成: String s = ArrayAlg.getMiddle("John","Q","Min"); 有的小伙伴可能会想到,酱紫的话,我能不能瞎比的传参数进去?例: Double middle2 = ArrayAlg.getMiddle(3.14,0); 其实当你这样写的时候编译器就已经提示错误了,因为第二个参数编译器会默认认为是Integer类型。解决方法有两种: 1.把Double改成Number类型,因为Number类型为Double和Integer的父类。 2.自己改代码去吧,谁让你瞎搞。 4.类型变量的限定 有时候,类或方法需要对类型变量加以约束,假如,某个泛型方法需要使用到Comparable接口的compareTo方法,那我们则需要限制传进来的参数必须实现Comparable接口,例: 复制代码 public static T minmax(T[] a){   compareTo... } 复制代码 传进来的参数a则必须实现Comparable接口。假如传进来参数需要实现Comparable和Serializable两个接口,则 复制代码 public static Pair minmax(T[] a){ compareTo... } 复制代码 叮咚!那么传进来的参数需要继承某个类怎么写?其实是一样的。传进来的参数需要继承某个类、实现某个接口都是使用extends关键字来控制。原因:选择关键字extends的原因是更接近子类的概念,并且Java的设计者也不打算在语言中再添加一个新的关键字 5.泛型代码和虚拟机 5.1类型擦除 Java 的泛型在编译器有效,在运行期被删除,也就是说所有泛型参数类型在编译后都会被清除掉 例如: 复制代码 public class GenericClass { private T first; public T getFirst() { return first; } ... } 复制代码 擦除类型后 复制代码 public class GenericClass { private Object first; public Object getFirst() { return first; } ... } 复制代码 可以看到,类型擦除后的GengericClass和没引用泛型没有什么两样。而且,当你使用GenericClass后,也是变成原始的GenericClass类型。 但是当我们使用限定类型变量后,类型擦除后将会使用限定类型。例: 复制代码 public class GenericClass { private T first; ... } 复制代码 可以看到限定了传进来的参数需要实现Comparable接口,那么类型擦除后如下: 复制代码 public class GenericClass{ private Comparable first; ... } 复制代码 问题:当你限定了参数需要实现Comparable和Serializable接口,就是GenericClass时,类型擦除后会变成怎样呢? 复制代码 public class GenericClass{ private Comparable first; ... } 复制代码 虚拟机会把首位实现的接口(此处为Comparable),转为擦除后对象类型。当然,如果你这样写GenericClass ,那么擦除后的类型为Serializable。 5.2翻译泛型表达式 当程序调用泛型方法时,如果擦除返回类型, 编译器将插入强制类型转换。例如List集合源码: 复制代码 public interface List extends Collection { ... E get(int index); ... } 复制代码 当不使用类型变量时,取出来的值是Object,而定义了String类型变量时,编译器将插入强制类型转换: 复制代码 List list1 = new ArrayList(); list1.add("1"); list1.get(0); //Object List list2 = new ArrayList<>(); list2.add("1"); list2.get(0); //String 复制代码 但是在虚拟机中时,list2.get(0)在调用时翻译成了两条虚拟机命令: 1.对原始方法List集合的get方法调用 2.将返回的Object进行强制类型转换成String 5.3翻译泛型方法 当GenericSubClass类继承了Generic类时,泛型方法会有什么变化? 复制代码 public class Generic { private T size; public T getSize() { return size; } ... } 复制代码 复制代码 public class GenericSubclass extends Generic { @Override public Integer getSize() { return super.getSize(); } } 复制代码 此时的@Override真的是重写父类方法吗?到了运行期间,Generic被类型擦除后getSize方法返回类型变成Object: public Object getSize() { return size; } 而GenericSubclass的getSize方法的返回类型却是Integer: public Integer getSize() { return super.getSize(); } 这并不是重写父类的方法,因为方法并不一样,所以导致GenericSubclass在虚拟机中却有了两个getSize方法: 复制代码 public Integer getSize() { ... } public Object getSize() { ... } 复制代码 导致的原因就是类型擦除与多态发生了冲突。要解决此问题,虚拟机自动在GenericSubclass中生成了一个桥方法: public Integer getSize() { return (Integer)super.getSize(); } 最后,虚拟机就会调用自己生成的桥方法来解决此冲突。 https://www.cnblogs.com/Johnson-lin/p/9580971.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信