8-1、泛型
1、背景
为什么使用泛型?
- 1、数据类型转换
List myIntList = new ArrayList();// 1
myIntList.add(new Integer(0));// 2
Integer x = (Integer) myIntList.iterator().next();// 3
第3行 类型转换,编译器只能保证 iterator 返回的是 Object 类型。为了保证对 Integer 类型变量赋值的类型安全,必须进行类型转换。
- 2、使用泛型解决数据类型转换
List<Integer> myIntList = new ArrayList<Integer>(); // 1
myIntList.add(new Integer(0)); // 2
Integer x = myIntList.iterator().next(); // 3
List
2、定义简单的泛型
- 1、List 接口
public interface List<E> {
void add(E x);
Iterator<E> iterator();
}
3、泛型和子类继承之间的关系
注:如果 Foo 是 Bar 的一个子类型(子类或者子接口),而 G 是某种
泛型声明,那么 G <Foo>
是 G<Bar>
的子类型并不成立。
即使用泛型的对象,操作时,必须保证类型参数的一致性。
List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls; //2 该行 编译会报错
List<String> ls = new ArrayList<String>(); //1
List<Object> lo = ls;//如果该行成立
lo.add(new Object()); // 3
String s = ls.get(0); // 4: 试图把 Object 赋值给 String
/*
这里,我们使用 lo 指向 ls。我们通过 lo 来访问 ls,一个 String 的 list。
我们可以插入任意对象进去。结果是 ls 中保存的不再是 String。当我们试
图从中取出元素的时候,会得到意外的结果。
*/
4、泛型-通配符
- 1、为什么使用通配符?
//打印集合中所有元素-不使用泛型
void printCollection(Collection c) {
Iterator i = c.iterator();
for (int k = 0; k < c.size(); k++) {
System.out.println(i.next());
}
}
//打印集合中所有元素-使用泛型
void printCollection(Collection<Object> c) {
for (Object e : c) {
System.out.println(e);
}
}
/*
因为类型参数需保持一致性的原则。不使用泛型前,集合内可存放各种类型的元素;使用泛型后,只能存放特定的元素;所以出现了通配符。
*/
//打印集合中所有元素-使用泛型+通配符
void printCollection(Collection<?> c) {
for (Object e : c) {
System.out.println(e);
}
}
/*
该方法是安全的,使用通配符后,不管集合中存放什么类型的元素,都是Object 类型的(根类)。另一点使用了通配符后,对该c 对象的写入是非法的。原因如下:见下方。这是得到灵活性所付出的代价。
*/
Collection<?> c = new ArrayList<String>();
c.add(new Object());//编译不正确
/*
因为我们不知道 c 的元素类型,我们不能向其中添加对象。
另一方面,我们可以调用 get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个 Object 。
*/
- 2、有限制的通配符
图像的抽象父类
/**
* 图形的抽象父类-抽象类
* @Description: TODO:
* @author pengguozhen
* @date 2018年2月24日 上午11:08:13
*/
public abstract class Shape {
public abstract void draw(Canvas c);
}
图形实现类
/**
* 图形实现类-圆形
* @Description: TODO:
* @author pengguozhen
* @date 2018年2月24日 上午11:10:17
*/
public class Circle extends Shape {
private int x, y, radius;
@Override
public void draw(Canvas c) {
// TODO Auto-generated method stub
System.out.println("画圆!");
}
}
/**
* 矩形图形
* @Description: TODO:
* @author pengguozhen
* @date 2018年2月24日 上午11:39:42
*/
public class Rectangle extends Shape {
private int x, y, width, height;
@Override
public void draw(Canvas c) {
// TODO Auto-generated method stub
System.out.println("画矩形!");
}
}
画布类
public class Canvas {
/**
* 画特定的图形
* @Description: TODO:
* @return void
* @param s
*/
public void draw(Shape s) {
s.draw(this);//传入画布对象
}
/**
* 画出所有图形
* @Description: TODO:
* @return void
* @param shapes
*/
public void drawAll1(List<Shape> shapes) {
for (Shape s : shapes) {
s.draw(this);
}
}
/*
* 现在,类型规则导致 drawAll()只能使用 Shape 的 list 来调用。它不能, 比如说对
* List<Circle>来调用。这很不幸,因为这个方法所作的只是从这个 list 读取 shape,因此它应该也能对
* List<Circle>调用。我们真正要的是这 个方法能够接受一个任意种类的 Shape:
*
* List<? extends Shape>是有限制通配符的一个例子。这里?代表一个 未知的类型,就像我们前面看到的通配符一样。
* 但是,在这里,我们知道这个未知的类型实际上是 Shape 的一个子类。
* 我们说 Shape 是这个通配符 的上限(upper bound)。
*/
public void drawAll2(List<? extends Shape> shapes) {
for (Shape s : shapes) {
s.draw(this);
}
}
}
5、泛型方法
方法的声明也可以被泛型化——就是说,带有一个或者多个类型参数。
/**
* 写一个方法,它用一个 Object 的数组和一个 collection 作为参数,
* 完成把数组中所有 object 放入 collection 中的功能。
* @Description: TODO:
* @return void
* @param a
* @param c
*/
static void fromArrayToCollection(Object[] a, Collection<?> c) {
for (Object o : a) {
c.add(o); // 编译期错误
}
}
/*问题:
现在,你应该能够学会避免初学者试图使用 Collection<Object>作为集
合参数类型的错误了。或许你已经意识到使用 Collection<?>也不能工作。
回忆一下,你不能把对象放进一个未知类型的集合中去。
*/
解决这个问题的办法是使用 generic methods。就像类型声明,
方法的声明也可以被泛型化——就是说,带有一个或者多个类型参数。
static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
for (T o : a) {
c.add(o); // correct
}
}