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 是一个带一个类型参数的泛型接口。List类型,这告诉我们无论何时何地使用 myIntList 变量,编译器保证其中的元素的正确的类型(必须和类型参数相同)。


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
		}
	}
	
posted @ 2021-05-18 09:24  星命定轨  阅读(69)  评论(0编辑  收藏  举报