泛型的使用泛型的作用简单的泛型类泛型方法有限制的通配符擦除和翻译
泛型的使用
集合没有泛型的时候,集合存放数据时都会丢失原来的类型,全部改为Object。这样可以获得良好的通用性。但是取出的时候,就需要做类型转换,如果类型写错了,转换就会出现异常。为了有更好的安全性和可读性,Java在JDK1.5的时候加入了泛型。
泛型的应用非常重要,在教学中,务必让学生学会基本的使用:
在集合(List、Set、Map)上使用泛型
在通用类或者接口上使用泛型
在方法上使用泛型
明白什么是泛型擦除
泛型的作用
使用泛型机制编写的程序代码要比那些杂乱地使用Object 变量 ,然后再进行强制类型转换的代码具有更好的安全性和可读性 。泛型对于集合类尤其有用 ,例如 ,ArrayList就是一个无处不在的集合类
没有泛型的代码:
List list = new ArrayList<>();
list.add(123);
list.add("abc");
list.add(1>2);
list.add('E');
list.add(890.12);
for (int i = 0; i < list.size(); i++) {
System.out.println( list.get(i) );
}
输入什么类型,就输出什么类型。但是我希望list里面存放的数据类型只有字符串怎么办 ?没有代码的情况下代码是这样的:
List list = new ArrayList();
list.add("123.A");
list.add("abc.def");
for (int i = 0; i < list.size(); i++) {
String str = (String) list.get(i);
System.out.println( str.split("\.")[0] ) ;
}
但是list中如果有其他类型呢?
List list = new ArrayList();
list.add("123.A");
list.add(123.123); // 这行数据就是一个浮点型
list.add("abc.def");
for (int i = 0; i < list.size(); i++) {
String str = (String) list.get(i);
System.out.println( str.split("\.")[0] ) ;
}
[图片上传失败...(image-72ef90-1627791165361)]
发生了类型转换错误~~
为了解决这类问题,使用泛型是不二之选。通过泛型可以限制集合中数据的类型,只有符合的类型才能放到集合中
格式
在声明的集合类型后面跟上一对尖括号,实现的类型构造器的小括号前面跟上一对尖括号。里面写上需要存放的数据类型
[图片上传失败...(image-217b41-1627791165361)]
可以看到,在定义泛型后,添加 123.123 浮点数时编译器就开始报错了。在使用泛型后,代码也不需要做类型强转了。
List list = new ArrayList();
list.add("123.A");
list.add("abc.def");
for (int i = 0; i < list.size(); i++) {
String str = list.get(i);
System.out.println( str.split("\.")[0] ) ;
}
Map的演示
Map data = new HashMap<>();
data.put("name", "张三");
data.put("age", "11岁");
data.put("sex", "男");
for (Map.Entry entry: data.entrySet()) {
System.out.println( entry.getKey() +":"+ entry.getValue());
}
简单的泛型类
在定义类 Pair 时,在类名后跟上
public class Pair {
private T first;
private T second;
public Pair() {
first = null;
second = null;
}
public Pair(T first , T second){
this.first = first;
this.second = second;
}
public void setFirst(T newValue) {
first = newValue;
}
public void setSecond(T newValue) {
second = newValue;
}
public T getFirst() {
return first;
}
public T getSecond() {
return second;
}
@Override
public String toString() {
return "Pair [first=" + first + ", second=" + second + "]";
}
}
使用
public static void main(String[] args) {
System.out.println(new Pair(1, 2));
//输出: Pair [first=1, second=2]
System.out.println(new Pair("诸葛", "孔明"));
//输出: Pair [first=诸葛, second=孔明]
}
使用泛型,就如同将原来类定义的 T 替换为了指定的类型版本一样,比如:
public class Pair {
private Integer first;
private Integer second;
public Pair() {
first = null;
second = null;
}
public Pair(Integer first , Integer second){
this.first = first;
this.second = second;
}
//....
}
类型参数就跟在方法或构造函数中普通的参数一样。就像一个方法有形式参数(formal value parameters)来描述它操作的参数的种类一样,一个泛型声明也有形式类型参数(formal type parameters)。当一个方法被调用,实参(actual arguments)替换形参,方法体被执行。当一个泛型声明被调用,实际类型参数(actual type arguments)取代形式类型参数。
泛型方法
在使用前,我们先明确一下泛型的各种通配符:
T:type 数据类型
E:element 元素
K:key 键
V:value 值
?:未知类型
示例:
class ArrayAlg {
public static T getMiddle(T... a) {
return a[a.length / 2];
}
}
测试
同一个类的方法,使用不同类型的数组,都可以正常得到数据
String[] array = {"123","345","456" , "567"};
String string = ArrayAlg.getMiddle(array);
System.out.println( string );
Integer[] array2 = {33,44,55,66,77,88};
System.out.println( ArrayAlg.getMiddle(array2) );
有限制的通配符
考虑一个简单的画图程序,它可以用来画各种形状,比如矩形和圆形。 为了在程序中表示这些形状,你可以定义下面的类继承结构:
// 抽象类
public abstract class Shape {
public abstract void draw();
}
// 画布
public class Canvas {
public void draw(Shape s) {
s.draw();
}
}
/// ---- 抽象类的子类-----------------------
public class Circle extends Shape {
private int x, y, radius;
public void draw() {
// ...
}
}
public class Rectangle extends Shape {
private int x, y, width, height;
public void draw() {
// ...
}
}
所有的图形通常都有很多个形状。假定它们用一个 list 来表示,Canvas 里有一个方法来画出所有的形状会比较方便
import java.util.List;
public class Canvas {
public void draw(Shape s) {
s.draw();
}
public void drawAll(List shapes) {
for (Shape s : shapes) {
s.draw();
}
}
}
现在,类型规则导致 drawAll()只能使用 Shape的list 来调用。它不能,比如说对 List
import java.util.List;
public class Canvas {
public void draw(Shape s) {
s.draw();
}
// 注意方法参数的变化
public void drawAll(List extends Shape> shapes) {
for (Shape s : shapes) {
s.draw();
}
}
}
我们把类型 List
List extends Shape>是有限制通配符的一个例子。这里?代表一个未知的类型,就像我们前面看到的通配符一样。但是,在这里,我们知道这个未知的类型实际上是Shape 的一个子类(它可以是 Shape本身或者 Shape 的子类而不必是 extends 自 Shape)。我们说 Shape是这个通配符的上限(upper bound)。 像平常一样,要得到使用通配符的灵活性有些代价。这个代价是,现在向 shapes 中写入是非法的。比如下面的代码是不允许的
public void addRectangle(List extends Shape> shapes) {
// 编译时会报错
shapes.add( new Rectangle());
}
shapes.add 的第二个参数类型是? extends Shape ——一个 Shape 未知的子类。因此我们不知道这个类型是什么,我们不知道它是不是 Rectangle 的父类;它可能是也可能不是一个父类,所以这里传递一个 Rectangle 不安全
擦除和翻译
先看下面的代码
public static String loophole(Integer x) {
List ys = new LinkedList();
List xs = ys;
xs.add(x);
return ys.iterator().next();
}
public static void main(String[] args) {
loophole(123);
}
[图片上传失败...(image-436467-1627791165359)]
可以看到,程序类型的转换异常,但是编译器却没有报错
这样的原因是,泛型是通过 java 编译器的称为擦除(erasure)的前端处理来实现的。你可以(基本上就是)把它认为是一个从源码到源码的转换,它把泛型版本的 loophole()转换成非泛型版本。 结果是,java 虚拟机的类型安全和稳定性决不能冒险,即使在又unchecked warning 的情况下。
擦除去掉了所有的泛型类型信息。所有在尖括号之间的类型信息都被扔掉了,因此,比如说一个 List
Java 的泛型支持仅在语法级别