泛型学习内容整理
程序员文章站
2022-05-23 14:22:01
...
泛型学习内容整理
1.泛型的优点
泛型可以使我们在编译阶段而不是在运行阶段就发现错误。泛型类或者泛型方法允许用户指定可以和这些类或者方法一起使用的对象类型。如果试图使用一个不相容的对象,编译器就会发现这个错误。这样的代码会产生编译错误,如图1,因为传递给compareTo方法的参数必须是 java.util.Date 类型。由于这个错误可以在编译时而不是运行被检测到,因而泛型类型使程序更加可靠。
图1:
表示形式泛型类型(formal generic type),随后可以用一个实际具体类型(actualconcrete type)来替换它。替换泛型类型称为泛型实例化(generic instantiation)。按照惯例,像E或T这样的单个大写字母用于表示形式泛型类型。泛型类型必须是引用类型,不能使用基本类型来替换泛型类型。例如图2,下面的语句是错误的。
图2:
从JDK1.5开始,无须类型转换就可以从一个元素类型已指定的线性表中获取一个值,因为编译器已经知道了这个元素类型。在JDK1.5以前,由于没有使用泛型,所以必须把返回值的类型转换为实际具体类型,如下:
package com.itjp.nineteenChapter;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
Integer i = (Integer) arrayList.get(0);
}
}
从JDK1.5开始,无须类型转换就可以从一个元素类型已指定的线性表中获取一个值,因为编译器已经知道了这个元素类型。
package com.itjp.nineteenChapter;
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
//对象泛型不用实例化可以推导是jdk1.7的特性
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
Integer i = arrayList.get(0);
}
}
2.定义泛型类和接口
可以为类或者接口定义泛型。当使用类创建对象,或者使用类或接口来声明引用变量时,必须指定具体的类型。
定义泛型类:
package com.itjp.nineteenChapter;
import java.util.ArrayList;
/**
* 泛型类
*
* @author wangzi
* @date 18/2/3 上午1:14.
*/
public class GenericStack<E> {
private ArrayList<E> arrayList;
public GenericStack() {
this.arrayList = new ArrayList<E>();
}
public E pop() {
int size = arrayList.size();
E obj = arrayList.get(size - 1);
arrayList.remove(size - 1);
return obj;
}
public void push(E obj) {
arrayList.add(obj);
}
public int size() {
return arrayList.size();
}
public E peek() {
int size = arrayList.size();
return arrayList.get(size - 1);
}
@Override
public String toString() {
return "GenericStack{" +
"arrayList=" + arrayList +
'}';
}
}
泛型测试类
package com.itjp.nineteenChapter;
import org.junit.Test;
/**
* 泛型类测试类
*
* @author wangzi
* @date 18/2/3 上午1:22.
*/
public class GenericStackTest {
@Test
public void testMethod01() {
GenericStack<String> genericStack01 = new GenericStack<>();
genericStack01.push("123");
genericStack01.push("23232");
genericStack01.push("4124");
// System.out.println(genericStack01.pop());
System.out.println(genericStack01.peek());
System.out.println(genericStack01.size());
System.out.println(genericStack01);
}
@Test
public void testMethod02() {
GenericStack<Integer> genericStack02 = new GenericStack<>();
genericStack02.push(1);
genericStack02.push(23);
genericStack02.push(3);
System.out.println(genericStack02);
genericStack02.pop();
System.out.println(genericStack02);
}
@Test
public void testMehtod03() throws Exception {
GenericStack<Object> genericStack03 = new GenericStack<>();
genericStack03.push("123");
genericStack03.push(123);
genericStack03.push(1.34);
System.out.println(genericStack03);
}
}
testMethod01输出结果:
4124
3
GenericStack{arrayList=[123, 23232, 4124]}
testMethod02输出结果:
GenericStack{arrayList=[1, 23, 3]}
GenericStack{arrayList=[1, 23]}
testMethod03输出结果:
GenericStack{arrayList=[123, 123, 1.34]}
可以不使用泛型,而将元素类型设置为Object(不写类型默认就是Object),也可以容纳任何对象类型。但是,使用泛型能够提高软件的可靠性和可读性,因为某些错误能在编译时而不是运行时被检测到。例如GenericStack,所以,只可以将字符串添加到这个栈中,如果试图向stack1中添加整数就会产生编译错误。
提示:创建泛型对象的时候,使用new GenericStack()或者new GenericStack<>()。这可能会误导构造方法的方法签名。正确构造方法的定义为:
public GenericStack() {
this.arrayList = new ArrayList<E>();
}
错误构造方法的定义为:
//这是错误的构造方法的定义方式,会报编译错误
public GenericStack<E>() {
this.arrayList = new ArrayList<E>();
}
注意:可以定义一个类或接口作为泛型类或者泛型接口的子类型。例如,在JavaAPI中,java.lang.String被定义为实现Comparable接口,如下所示:
public class String implents Comparable<String>
3.泛型方法
为了声明泛型方法,将泛型类型放在返回值之前,如果声明的是static泛型方法,将<E>
置于static关键字之后
泛型方法:
package com.itjp.nineteenChapter;
import java.util.Arrays;
/**
* 泛型方法
*
* @author wangzi
* @date 18/2/3 上午2:24.
*/
public class GenericMethod {
public <E> void print(E[] list) {
System.out.println(Arrays.deepToString(list));
}
//声明static泛型方法,将<E>置于static关键字之后
public static <E> void printStatic(E[] list) {
System.out.println(Arrays.deepToString(list));
}
}
泛型方法测试类:
package com.itjp.nineteenChapter;
import org.junit.Test;
/**
* 泛型方法测试类
*
* @author wangzi
* @date 18/2/3 上午2:35.
*/
public class GenericMethodTest {
@Test
public void testMehtod01() throws Exception {
Integer[] integers = {1, 3, 241, 23};
GenericMethod genericMethod = new GenericMethod();
genericMethod.print(integers);
GenericMethod.printStatic(integers);
}
@Test
public void testMehtod02() throws Exception {
String[] strings = {"123", "23232", "444", "strs"};
GenericMethod genericMethod = new GenericMethod();
genericMethod.print(strings);
GenericMethod.printStatic(strings);
}
}
testMethod01输出结果:
[123, 23232, 444, strs]
[123, 23232, 444, strs]
testMethod02输出结果:
[123, 23232, 444, strs]
[123, 23232, 444, strs]
为了调用泛型方法,需要将实际类型放在尖括号内作为方法名的前缀,例如:
genericMethod.<Integer>print(integers);
GenericMethod.<Integer>printStatic(integers);
或者如下简单调用:
genericMethod.print(strings);
GenericMethod.printStatic(strings);
在后面这种情况中,实际类型没有明确指定。编译器自动发现实际类型。(推荐使用后者)
注意:可以将泛型指定为另外一种类型的子类型。这样的泛型类型称为受限的(bounded)
受限的泛型
package com.itjp.nineteenChapter;
/**
* @authshor wangzi
* @date 18/2/3 上午2:56.
*/
public class GenericComparable {
/**
* 比较方法
*
* @param obj01 受限的泛型参数一
* @param obj02 受限的泛型参数二
* @param <E> 受限的泛型
* @return 1:obj01>obj02 , -1:obj01 < obj01 ,0:obj01 == obj02
*/
public static <E extends Comparable> int compareTo(E obj01, E obj02) {
if (obj01.compareTo(obj02) > 0) {
return 1;
} else if (obj01.compareTo(obj02) == 0) {
return 0;
} else {
return -1;
}
}
}
受限的泛型测试类:
package com.itjp.nineteenChapter;
import org.junit.Test;
/**
* @author wangzi
* @date 18/2/3 上午3:03.
*/
public class GenericComparableTest {
@Test
public void testMehtod01() throws Exception{
System.out.println(GenericComparable.compareTo(new Integer(3),new Integer(5)));
System.out.println(GenericComparable.compareTo(new String("523"),new String("444")));
}
}
受限的泛型反例:
报错原因:因为参数不是Comparable的子类
注意:非受限泛型类型<E>
等同于<E extends Object>
注意:为了定义一个类为泛型类型,需要将泛型类型放在类名之后,例如,GenericStack<E>
。为了定义一个方法为泛型类型,要将泛型类型放在方法返回值之前,例如,<E>
void max(E o1,E o2);
4.原始类型和向后兼容
原始类型:没有指定具体类型的泛型类和泛型接口,用于和早期的Java版本向后兼容。
可以使用使用泛型类而无须指定具体类型,如下:
GenericStack genericStack03 = new GenericStack();
它大体等价于下面的语句:
GenericStack<Object> genericStack03 = new GenericStack<>();
package com.itjp.nineteenChapter;
/**
* @author wangzi
* @date 18/2/3 上午3:29.
*/
public class Max {
/**
* 返回较大者
*
* @param o1 Comparable原始类型的子类对象
* @param o2 Comparable原始类型的子类对象
* @return
*/
public static Comparable max(Comparable o1, Comparable o2) {
if (o1.compareTo(o2) > 0) {
return o1;
}
return o2;
}
public static void main(String[] args) {
System.out.println(Max.max("welcome", 123213));
}
}
输出结果:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at java.lang.String.compareTo(String.java:111)
at com.itjp.nineteenChapter.Max.max(Max.java:16)
at com.itjp.nineteenChapter.Max.main(Max.java:22)
结论:原始类型是不安全的
5.通配泛型
可以使用非受限通配、受限通配或者下限通配来对一个泛型类型指定范围。
非受限通配(unbounded wildcard):形式是?,它和?extends Object是一样的。
受限通配(bounded wildcard:形式是?extends T,表示T或T的一个子类型。
下限通配(lower-bound wildcard):形式是?super T,表示T或者T的一个父亲类型。
6.消除泛型
泛型是使用一种称为类型消除(type erasure)的方法来实现的。编译器使用泛型类型信息来编译代码,但是随后会消除它,因此,泛型信息在运行时是不可用的。
1.泛型存在于编译阶段,一段编译器确认泛型类型是安全使用的,就会将它转换为原始类型。
2.当编译泛型类、接口和方法时,编译器使用Object类型替代泛型类型。
3.如果一个泛型类型是受限的,那么编译器就会用该受限类型来替换它。如下:
package com.itjp.nineteenChapter;
/**
* 编译时候受限泛型类型
* @authshor wangzi
* @date 18/2/3 上午2:56.
*/
public class GenericComparable {
/**
* 比较方法
*
* @param obj01 受限的泛型参数一
* @param obj02 受限的泛型参数二
* @param <E> 受限的泛型
* @return 1:obj01>obj02 , -1:obj01 < obj01 ,0:obj01 == obj02
*/
public static <E extends Comparable> int compareTo(E obj01, E obj02) {
if (obj01.compareTo(obj02) > 0) {
return 1;
} else if (obj01.compareTo(obj02) == 0) {
return 0;
} else {
return -1;
}
}
}
编译器会用该受限类型来替换它
public static int compareTo(Comparable obj01, Comparable obj02) {
if (obj01.compareTo(obj02) > 0) {
return 1;
} else if (obj01.compareTo(obj02) == 0) {
return 0;
} else {
return -1;
}
}
6.小结
1.泛型具有参数化类型的能力。可以定义使用泛型类型或方法,编译器会用具体类型来替换泛型类型。
2.泛型的主要优势是能够在编译时而不是运行时检测错误。
3.泛型类或方法允许指定这个类或方法可以带有的对象类型。如果试图使用带有不兼容对象的类或方法,编译器会检测出这个错误。
4.定义在类、接口或者方法中的泛型称为形式泛型类型,随后可以用一个实际具体类型来替换它。替换泛型类型的过程称为泛型实例化。
5.不使用类型参数的泛型称为原始类型,例如ArrayList。使用原始类型是为了兼容Java较早版本。
6.通配泛型类型有三种形式:?、?extends T和?super T,这里的T代表一个泛型类型。这一种形式?称为非受限通配,它和?extends Object是一样的。第二种形式?extends T称为受限通配,代表T或者T的一个子类型。第三类型?super T称为下限通配,表示T或T的一个父类型。
7.使用称为类型消除的方法来实现泛型。编译器使用泛型类型信息来编译代码,但是随后消除它。因此,泛型信息在运行时是不可用的。
8.不能使用泛型类型参数来创建实例。
9.不能使用泛型类型参数来创建数组。
10.不能在静态环境中使用类的泛型类型参数。
11.在异常类中不能使用泛型类型参数。
7.参考资料
1.java语言程序设计.进阶篇 chapter19
上一篇: java泛型
下一篇: java集合框架概述