关于javascript:Java总结篇系列Java泛型

1次阅读

共计 7018 个字符,预计需要花费 18 分钟才能阅读完成。

**Java 总结篇系列:Java 泛型
一. 泛型概念的提出(为什么须要泛型)?
首先,咱们看下上面这段简短的代码:

1 public class GenericTest {2 3 public static void main(String[] args) {4 List list = new ArrayList(); 5 list.add(“qqyumidi”); 6 list.add(“corn”); 7 list.add(100); 8 9 for (int i = 0; i < list.size(); i++) {10 String name = (String) list.get(i); // 111 System.out.println(“name:” + name);12 }13 }14 }

定义了一个 List 类型的汇合,先向其中退出了两个字符串类型的值,随后退出一个 Integer 类型的值。这是齐全容许的,因为此时 list 默认的类型为 Object 类型。在之后的循环中,因为遗记了之前在 list 中也退出了 Integer 类型的值或其余编码起因,很容易呈现相似于 // 1 中的谬误。因为编译阶段失常,而运行时会呈现“java.lang.ClassCastException”异样。因而,导致此类谬误编码过程中不易发现。
 在如上的编码过程中,咱们发现次要存在两个问题:
1. 当咱们将一个对象放入汇合中,汇合不会记住此对象的类型,当再次从汇合中取出此对象时,改对象的编译类型变成了 Object 类型,但其运行时类型任然为其自身类型。
2. 因而,// 1 处取出汇合元素时须要人为的强制类型转化到具体的指标类型,且很容易呈现“java.lang.ClassCastException”异样。
那么有没有什么方法能够使汇合可能记住汇合内元素各类型,且可能达到只有编译时不呈现问题,运行时就不会呈现“java.lang.ClassCastException”异样呢?答案就是应用泛型。
 
二. 什么是泛型?
泛型,即“参数化类型”。一提到参数,最相熟的就是定义方法时无形参,而后调用此办法时传递实参。那么参数化类型怎么了解呢?顾名思义,就是将类型由原来的具体的类型参数化,相似于办法中的变量参数,此时类型也定义成参数模式(能够称之为类型形参),而后在应用 / 调用时传入具体的类型(类型实参)。
 看着如同有点简单,首先咱们看下下面那个例子采纳泛型的写法。

1 public class GenericTest {2 3 public static void main(String[] args) {4 / 5 List list = new ArrayList(); 6 list.add(“qqyumidi”); 7 list.add(“corn”); 8 list.add(100); 9 /10 11 List<String> list = new ArrayList<String>();12 list.add(“qqyumidi”);13 list.add(“corn”);14 //list.add(100); // 1 提醒编译谬误 15 16 for (int i = 0; i < list.size(); i++) {17 String name = list.get(i); // 218 System.out.println(“name:” + name);19 }20 }21 }

采纳泛型写法后,在 // 1 处想退出一个 Integer 类型的对象时会呈现编译谬误,通过 List<String>,间接限定了 list 汇合中只能含有 String 类型的元素,从而在 // 2 处毋庸进行强制类型转换,因为此时,汇合可能记住元素的类型信息,编译器曾经可能确认它是 String 类型了。
联合下面的泛型定义,咱们晓得在 List<String> 中,String 是类型实参,也就是说,相应的 List 接口中必定含有类型形参。且 get()办法的返回后果也间接是此形参类型(也就是对应的传入的类型实参)。上面就来看看 List 接口的的具体定义:

1 public interface List<E> extends Collection<E> {2 3 int size(); 4 5 boolean isEmpty(); 6 7 boolean contains(Object o); 8 9 Iterator<E> iterator();10 11 Object[] toArray();12 13 <T> T[] toArray(T[] a);14 15 boolean add(E e);16 17 boolean remove(Object o);18 19 boolean containsAll(Collection<?> c);20 21 boolean addAll(Collection<? extends E> c);22 23 boolean addAll(int index, Collection<? extends E> c);24 25 boolean removeAll(Collection<?> c);26 27 boolean retainAll(Collection<?> c);28 29 void clear();30 31 boolean equals(Object o);32 33 int hashCode();34 35 E get(int index);36 37 E set(int index, E element);38 39 void add(int index, E element);40 41 E remove(int index);42 43 int indexOf(Object o);44 45 int lastIndexOf(Object o);46 47 ListIterator<E> listIterator();48 49 ListIterator<E> listIterator(int index);50 51 List<E> subList(int fromIndex, int toIndex);52 }

咱们能够看到,在 List 接口中采纳泛型化定义之后,<E> 中的 E 示意类型形参,能够接管具体的类型实参,并且此接口定义中,但凡呈现 E 的中央均示意雷同的承受自内部的类型实参。
天然的,ArrayList 作为 List 接口的实现类,其定义模式是:

1 public class ArrayList<E> extends AbstractList<E> 2 implements List<E>, RandomAccess, Cloneable, java.io.Serializable {3 4 public boolean add(E e) {5 ensureCapacityInternal(size + 1); // Increments modCount!! 6 elementData[size++] = e; 7 return true; 8 } 9 10 public E get(int index) {11 rangeCheck(index);12 checkForComodification();13 return ArrayList.this.elementData(offset + index);14 }15 16 //… 省略掉其余具体的定义过程 17 18 }

由此,咱们从源代码角度明确了为什么 // 1 处退出 Integer 类型对象编译谬误,且 // 2 处 get()到的类型间接就是 String 类型了。
 
三. 自定义泛型接口、泛型类和泛型办法
从下面的内容中,大家曾经明确了泛型的具体运作过程。也晓得了接口、类和办法也都能够应用泛型去定义,以及相应的应用。是的,在具体应用时,能够分为泛型接口、泛型类和泛型办法。
自定义泛型接口、泛型类和泛型办法与上述 Java 源码中的 List、ArrayList 相似。如下,咱们看一个最简略的泛型类和办法定义:

1 public class GenericTest {2 3 public static void main(String[] args) {4 5 Box<String> name = new Box<String>(“corn”); 6 System.out.println(“name:” + name.getData()); 7 } 8 9 }10 11 class Box<T> {12 13 private T data;14 15 public Box() {16 17}18 19 public Box(T data) {20 this.data = data;21}22 23 public T getData() {24 return data;25}26 27 } 

在泛型接口、泛型类和泛型办法的定义过程中,咱们常见的如 T、E、K、V 等模式的参数罕用于示意泛型形参,因为接管来自内部应用时候传入的类型实参。那么对于不同传入的类型实参,生成的相应对象实例的类型是不是一样的呢?

1 public class GenericTest {2 3 public static void main(String[] args) {4 5 Box<String> name = new Box<String>(“corn”); 6 Box<Integer> age = new Box<Integer>(712); 7 8 System.out.println(“name class:” + name.getClass()); // com.qqyumidi.Box 9 System.out.println(“age class:” + age.getClass()); // com.qqyumidi.Box10 System.out.println(name.getClass() == age.getClass()); // true11 12 }13 14 }

由此,咱们发现,在应用泛型类时,尽管传入了不同的泛型实参,但并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来的最根本的类型(本实例中为 Box),当然,在逻辑上咱们能够了解成多个不同的泛型类型。
究其原因,在于 Java 中的泛型这一概念提出的目标,导致其只是作用于代码编译阶段,在编译过程中,对于正确测验泛型后果后,会将泛型的相干信息擦出,也就是说,胜利编译过后的 class 文件中是不蕴含任何泛型信息的。泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是雷同的根本类型。
 
四. 类型通配符
接着下面的论断,咱们晓得,Box<Number> 和 Box<Integer> 实际上都是 Box 类型,当初须要持续探讨一个问题,那么在逻辑上,相似于 Box<Number> 和 Box<Integer> 是否能够看成具备父子关系的泛型类型呢?
为了弄清这个问题,咱们持续看下上面这个例子:

1 public class GenericTest {2 3 public static void main(String[] args) {4 5 Box<Number> name = new Box<Number>(99); 6 Box<Integer> age = new Box<Integer>(712); 7 8 getData(name); 9 10 //The method getData(Box<Number>) in the type GenericTest is 11 //not applicable for the arguments (Box<Integer>)12 getData(age); // 113 14 }15 16 public static void getData(Box<Number> data){17 System.out.println(“data :” + data.getData());18 }19 20 }

咱们发现,在代码 // 1 处呈现了谬误提示信息:The method getData(Box<Number>) in the t ype GenericTest is not applicable for the arguments (Box<Integer>)。显然,通过提示信息,咱们晓得 Box<Number> 在逻辑上不能视为 Box<Integer> 的父类。那么,起因何在呢?

1 public class GenericTest {2 3 public static void main(String[] args) {4 5 Box<Integer> a = new Box<Integer>(712); 6 Box<Number> b = a; // 1 7 Box<Float> f = new Box<Float>(3.14f); 8 b.setData(f); // 2 9 10 }11 12 public static void getData(Box<Number> data) {13 System.out.println(“data :” + data.getData());14 }15 16 }17 18 class Box<T> {19 20 private T data;21 22 public Box() {23 24}25 26 public Box(T data) {27 setData(data);28 }29 30 public T getData() {31 return data;32}33 34 public void setData(T data) {35 this.data = data;36}37 38 }

这个例子中,显然 // 1 和 // 2 处必定会呈现谬误提醒的。在此咱们能够应用反证法来进行阐明。
假如 Box<Number> 在逻辑上能够视为 Box<Integer> 的父类,那么 // 1 和 // 2 处将不会有谬误提醒了,那么问题就进去了,通过 getData()办法取出数据时到底是什么类型呢?Integer? Float? 还是 Number?且因为在编程过程中的程序不可控性,导致在必要的时候必须要进行类型判断,且进行强制类型转换。显然,这与泛型的理念矛盾,因而,在逻辑上 Box<Number> 不能视为 Box<Integer> 的父类。
好,那咱们回过头来持续看“类型通配符”中的第一个例子,咱们晓得其具体的谬误提醒的深层次起因了。那么如何解决呢?总部能再定义一个新的函数吧。这和 Java 中的多态理念显然是违反的,因而,咱们须要一个在逻辑上能够用来示意同时是 Box<Integer> 和 Box<Number> 的父类的一个援用类型,由此,类型通配符应运而生。
类型通配符个别是应用 ? 代替具体的类型实参。留神了,此处是类型实参,而不是类型形参!且 Box<?> 在逻辑上是 Box<Integer>、Box<Number>… 等所有 Box< 具体类型实参 > 的父类。由此,咱们仍然能够定义泛型办法,来实现此类需要。

1 public class GenericTest {2 3 public static void main(String[] args) {4 5 Box<String> name = new Box<String>(“corn”); 6 Box<Integer> age = new Box<Integer>(712); 7 Box<Number> number = new Box<Number>(314); 8 9 getData(name);10 getData(age);11 getData(number);12 }13 14 public static void getData(Box<?> data) {15 System.out.println(“data :” + data.getData());16 }17 18 }

有时候,咱们还可能听到类型通配符下限和类型通配符上限。具体有是怎么样的呢?
在下面的例子中,如果须要定义一个性能相似于 getData()的办法,但对类型实参又有进一步的限度:只能是 Number 类及其子类。此时,须要用到类型通配符下限。

1 public class GenericTest {2 3 public static void main(String[] args) {4 5 Box<String> name = new Box<String>(“corn”); 6 Box<Integer> age = new Box<Integer>(712); 7 Box<Number> number = new Box<Number>(314); 8 9 getData(name);10 getData(age);11 getData(number);12 13 //getUpperNumberData(name); // 114 getUpperNumberData(age); // 215 getUpperNumberData(number); // 316 }17 18 public static void getData(Box<?> data) {19 System.out.println(“data :” + data.getData());20 }21 22 public static void getUpperNumberData(Box<? extends Number> data){23 System.out.println(“data :” + data.getData());24 }25 26 }

此时,显然,在代码 // 1 处调用将呈现谬误提醒,而 //2 // 3 处调用失常。
类型通配符下限通过形如 Box<? extends Number> 模式定义,绝对应的,类型通配符上限为 Box<? super Number> 模式,其含意与类型通配符下限正好相同,在此不作过多论述了。
 
五. 话外篇
本文中的例子次要是为了论述泛型中的一些思维而简略举出的,并不一定有着理论的可用性。另外,一提到泛型,置信大家用到最多的就是在汇合中,其实,在理论的编程过程中,本人能够应用泛型去简化开发,且能很好的保障代码品质。并且还要留神的一点是,Java 中没有所谓的泛型数组一说。**

正文完
 0