《java编程思想——第十五章(泛型)》

泛型

15.2 简单泛型

泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来来保证正确性。

// 类型参数T
public class Holder3<T> {
	private T t;
	public Holder3(T a) {
		this.t = a;
	}
	public void set(T a){
		this.t = a;
	}
	public T get() {
		return t;
	}
	public static void main(String[] args) {
		Holder3<Automobile> h2 =  new Holder3<Automobile>(new Automobile());
		Automobile a = (Automobile) h2.get();

	}
}

使用类型参数T可以暂时不指定类型,在创建对象时必须指明类型。

  1. 一个元组类库
    将一组对象打包存储咋一个对象中称为元组。
/**
 * 元组持有数据
 * @author Administrator
 *
 */

public class TwoTuple<A,B> {

	public final A first;
	public final B second;
	public TwoTuple(A a,B b){
		first = a;
		second = b;
	}
	@Override
	public String toString() {
		return "("+first+","+second+")";
	}
}

可以通过元组,返回一组对象。
2. 一个堆栈类

 // 自定义堆栈
public class LinkedStack<T> {

	private static class Node<U> {
		U item;
		Node<U> next;
		Node() {
			item = null;
			next = null;
		}
		Node(U item,Node<U> next){
			this.item = item;
			this.next = next;
		}
		boolean end(){
			return item == null && next == null;
		}
	}
	private Node<T> top = new Node<T>();
	
	public void push(T item){
		top = new Node<T>(item, top);
	}
	public T pop(){
		T result = top.item;
		if(!top.end()){
			top = top.next;
		}
		return result;
	}
	
	public static void main(String[] args) {
		LinkedStack<String> s = new LinkedStack<String>();
		for (String string : "Phasers on stun!".split("")) {
			s.push(string);
		}
		
		Print.print(s.pop());
	}
}

使用了末端哨兵来判断堆栈是否为空。
3. RandomList

15.3 泛型接口

泛型也可用于接口。

public interface Generator<T> {
	T next();
}

泛型接口应用:
生成器利用工程模式生成对象。
利用适配器模式可以实现迭代功能。

15.4 泛型方法

使用泛型了时,在创建对象时必须指明类型参数;使用泛型方法时,不必指明参数类型。编译器会找出具体的类型,被称为参数推断

定义泛型方法,只需将泛型参数列表置于返回值之前。

//泛型方法
public <T>  void f(T t) {
	System.out.println(t.getClass().getName());
}
  1. 杠杆利用参数推断
    类型推断只对赋值有效,作为参数传递无效。
  2. 可变参数与泛型方法
//可变参数与泛型方法可以很好的共存
public class GenericVarargs {
	public static <T> List<T> makeList(T... args) {
		List<T> result =  new ArrayList<T>();
		for (T t : args) {
			result.add(t);
		}
		return result;
	}
	public static void main(String[] args) {
		List<String> list = makeList("A");
		System.out.println(list);
		list = makeList("A","B","C");
		System.out.println(list);
	}
}

15.7 擦除的神秘之处

class Frob{}
class Fnorkle{}
class Quark<Q>{}
class Practice<POSITION,MOMENTUM>{}
public class LostInformation {
	public static void main(String[] args) {
		List<Frob> list =  new ArrayList<Frob>();
		Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>();
		Quark<Fnorkle> quark =  new Quark<Fnorkle>();
		Practice<Long,Double> practice = new Practice<Long,Double>();
	
		System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
		System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
		System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
		System.out.println(Arrays.toString(practice.getClass().getTypeParameters()));

	}
}
输出:
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]

在泛型代码内部,无法获得任何有关泛型参数类型的信息。你可以知道诸如类型参数标示符和泛型类型边界这类信息,但却无法知道用来创建某个特定实例的实际的类型参数。

擦除的代价是:泛型不能显式地应用运行时类型操作。因为所有参数 类型信息都丢失了。

15.5 擦除的补偿

泛型擦除无法在运行时获取类型信息,可以通过传入类型解决。

/**
 * 通过传入Class获取类型
 * @author Administrator
 */
class Building{}
class House extends Building{}
public class ClassTypeCapture<T> {
	Class<T> kind;
	public ClassTypeCapture(Class<T> kind) {
		this.kind = kind;
	}
	public boolean f(Object arg) {
		return kind.isInstance(arg);
	}
	public static void main(String[] args) {
		ClassTypeCapture<House> ct2 = 
				new ClassTypeCapture<House>(House.class);
		System.out.println(ct2.f(new Building()));
		System.out.println(ct2.f(new House()));
	}
}

泛型擦除创建对象方式:工厂方式和模板方式。
不能创建泛型数组解决方式:使用ArrayList。

15.6 边界

边界使得你可以在用于泛型的参数类型上设置条件。

<T extends 接口名或类名>

15.7 通配符

< ? extends 接口名或类名>
任何从某个类继承的类型都适用。
< ? super 接口名或类名> 超类型通配符
无界通配符:<?>

15.8 问题

  1. 任何基本数据类型都不能作为类型参数
  2. 实现参数化接口
    一个类不能实现同一个泛型接口的两种变体。
  3. 转型和警告
  4. 重载
    由于泛型擦除的原因,重载方法将产生相同签名。
  5. 基类劫持了接口

15.9 自限定类型

ClassA extens ClassB<A>

自限定会强制要求将正在定义的类当做参数传递给基类。

15.10 动态类型安全

1.5以后可以使用Collections工具类中的checkedList(list, type),checkedMap()等方法检查传入容器的类型。

15.11 异常

类型参数可应用于一个方法的throws子句中。

15.12 混型

混型:混个多个类的能力,产生一个可以表示混型中所有类型的类。

  1. 与接口混合
    使用代理方式,每个混入类型中都有相应的域,使用时代理调用方法。
  2. 装饰器模式
  3. 与动态代理结合

15.13 潜在类型机制

class Dog:
	def speak(self):
		print "atf:
	def sit(self):
		print "Sitting"
	def reproduce(self):
		pass
class Robot:
	def speak(self):
		print "atf:
	def sit(self):
		print "Sitting"
	def reproduce(self):
		pass
a = Dog()
b = Robot()
perform(a)
perform(b)

perform(anything) 没有针对任何anything类型,anything包含的接口是潜在的。
Java不支持潜在类型机制。

15.17 对缺乏潜在类型机制的补偿

  1. 反射
    利用反射可以动态地确定所需要的方法并调用它。
  2. 将方法应用于序列
    反射提供了一些有趣的可能,但它将所有的类型检查都转移到了运行时。
    尝试一些实现编译时期检查。
  3. 当你并未拥有正确接口时
    编译时期可以检查类型,但是类型被限制在继承层次之内。
  4. 用适配器仿真潜在类型
    适配器模式模仿潜在类型机制,并在编译时期检查类型。
/**
 * 适配器模式实现潜在类型机制
 * @author Administrator
 *
 * @param <T>
 */
interface Addable<T> { void add(T t);}
class AddableCollectionAdapter<T> implements Addable<T>{
	private Collection<T> c;
	public AddableCollectionAdapter(Collection<T> c) {
		this.c = c;
	}
	
	@Override
	public void add(T t) {
		c.add(t);
	}
	
}
class Adapter{
	public static<T>  Addable<T> collectionAdapter(Collection<T> c) {
		return new AddableCollectionAdapter(c);
	}
}
class AddableSimpleQueue<T> extends SimpleQueue<T> implements Addable<T>{
	public void add(T t) {
		super.add(t);
	}
}
public class Fill2 {
	public static<T> void fill(Addable<T> addable,
			Class<? extends T> classToken,int size) {
		for (int i = 0; i < size; i++) {
			try {
				addable.add(classToken.newInstance());
			} catch (Exception e) {
				throw new RuntimeException();
			}
		}
	}
	public static<T> void fill(Addable<T> addable,
			Generator< T> generator,int size) {
		for (int i = 0; i < size; i++) {
			try {
				addable.add(generator.next());
			} catch (Exception e) {
				throw new RuntimeException();
			}
		}
	}
	
	public static void main(String[] args) {
		List<Coffee> coffees = new ArrayList<>();
		Fill2.fill(new AddableCollectionAdapter<Coffee>(coffees), Coffee.class, 3);
		Fill2.fill(Adapter.collectionAdapter(coffees), Coffee.class, 3);
		for (Coffee coffee : coffees) {
			System.out.println(coffee);
		}
		System.out.println("--------------------");
		AddableSimpleQueue<Coffee> coffeequeue = new AddableSimpleQueue();
		Fill2.fill(coffeequeue, Mocha.class, 5);
		Fill2.fill(coffeequeue, Latte.class, 6);
		for (Coffee coffee : coffeequeue) {
			System.out.println(coffee);
		}
	}
}

15.18 将函数对象用作策略

策略设计模式将变化的事物隔离到函数对象中。

15.19 总结

泛型的作用除了提供类型检查,还可以编写更"泛化"的代码。

第1章 对象入门 1.1 抽象的进步 1.2 对象的接口 1.3 实现方案的隐藏 1.4 方案的重复使用 1.5 继承:重新使用接口 1.5.1 改善基础类: 尽管extends关键字暗示着我们要为接口“扩展”新功能,但实情并非肯定如此。为区分我们的新类,第二个办法是改变基础类一个现有函数的行为。我们将其称作“改善”那个函数。 为改善一个函数,只需为衍生类的函数建立一个新定义即可。我们的目标是:“尽管使用的函数接口未变,但它的新版本具有不同的表现”。 1.5.2 等价和类似关系 1.6 多形对象的互换使用 1.6.1 动态绑定 1.6.2 抽象的基础类和接口 1.7 对象的创建和存在时间 1.7.1 集合与继承器 1.7.2 单根结构 1.7.3 集合库与方便使用集合 1.7.4 清除时的困境:由谁负责清除? 1.8 违例控制:解决错误 1.9 多线程 1.10 永久性 1.11 Java和因特网: 既然Java不过另一种类型的程序设计语言,大家可能会奇怪它为什么值得如此重视,为什么还有这么多的人认为它是计算机程序设计的一个里程碑呢?如果您来自一个传统的程序设计背景,那么答案在刚开始的时候并不是很明显。Java除了可解决传统的程序设计问题以外,还能解决World Wide Web(万维网)上的编程问题。 1.11.1 什么是Web? 1.11.2 客户端编程 1.11.3 服务器端编程 1.11.4 一个独立的领域:应用程序 1.12 分析和设计 1.12.1 不要迷失: 在整个开发过程中,最重要的事情就是:不要将自己迷失!但事实上这种事情很容易发生。大多数方法都设计用来解决最大范围内的问题。当然,也存在一些特别困难的项目,需要作者付出更为艰辛的努力,或者付出更大的代价。但是,大多数项目都是比较“常规”的,所以一般都能作出成功的分析与设计,而且只需用到推荐的一小部分方法。但无论多么有限,某些形式的处理总是有益的,这可使整个项目的开发更加容易,总比直接了当开始编码好! 也就是说,假如你正在考察一种特殊的方法,其中包含了大量细节,并推荐了许多步骤和文档,那么仍然很难正确判断自己该在何时停止。时刻提醒自己注意以下几个问题: (1) 对象是什么?(怎样将自己的项目分割成一系列单独的组件?) (2) 它们的接口是什么?(需要将什么消息发给每一个对象?) 在确定了对象和它们的接口后,便可着手编写一个程序。出于对多方面原因的考虑,可能还需要比这更多的说明及文档,但要求掌握的资料绝对不能比这还少。 整个过程可划分为四个阶段,阶段0刚刚开始采用某些形式的结构。 1.12.2 阶段0:拟出一个计划: 1.12.3 阶段1:要制作什么?: 1.12.4 阶段2:如何构建? 1.12.5 阶段3:正式创建 1.12.6 阶段4:校订 1.12.7 计划的回报 1.13 Java还是C++? 第2章 一切都是对象 “尽管以C++为基础,但Java是一种更纯粹的面向对象程序设计语言”。 无论C++还是Java都属于杂合语言。但在Java中,设计者觉得这种杂合并不象在C++里那么重要。杂合语言允许采用多种编程风格;之所以说C++是一种杂合语言,是因为它支持与C语言的向后兼容能力。由于C++是C的一个超集,所以包含的许多特性都是后者不具备的,这些特性使C++在某些地方显得过于复杂。 Java语言首先便假定了我们只希望进行面向对象的程序设计。也就是说,正式用它设计之前,必须先将自己的思想转入一个面向对象的世界(除非早已习惯了这个世界的思维方式)。只有做好这个准备工作,与其他OOP语言相比,才能体会到Java的易学易用。在本章,我们将探讨Java程序的基本组件,并体会为什么说Java乃至Java程序内的一切都是对象。 2.1 用句柄操纵对象 2.2 必须创建所有对象: 创建句柄时,我们希望它同一个新对象连接。通常用new关键字达到这一目的。new的意思是:“把我变成这些对象的一种新类型”。所以在上面的例子中,可以说: String s = new String("asdf"); 它不仅指出“将我变成一个新字串”,也通过提供一个初始字串,指出了“如何生成这个新字串”。 当然,字串(String)并非唯一的类型。Java配套提供了数量众多的现成类型。对我们来讲,最重要的就是记住能自行创建类型。事实上,这应是Java程序设计的一项基本操作,是继续本书后余部分学习的基础。 2.2.1 保存在什么地方 2.2.2 特殊情况:主类型 2.2.3 Java中的数组 2.3 绝对不要清除对象:在大多数程序设计语言中,变量的“存在时间”(Lifetime)一直是程序员需要着重考虑的问题。变量应持续多长的时间?如果想清除它,那么何时进行?在变量存在时间上纠缠不清会造成大量的程序错误。在下面的小节里,将阐示Java如何帮助我们完成所有清除工作,从而极大了简化了这个问题。 2.3.1 作用域 2.3.2 对象的作用域 2.4 新建数据类型:类 2.4.1 字段和方法 2.5 方法、自变量和返回值 2.5.1 自变量列表 2.6 构建Java程序:正式构建自己的第一个Java程序前,还有几个问题需要注意。 2.6.1 名字的可见性 2.6.2 使用其他组件 2.6.3 static关键字 2.7 我们的第一个Java程序 2.8 注释和嵌入文档 2.8.1 注释文档 2.8.2 具体语法 2.8.3 嵌入HTML 2.8.4 @see:引用其他类 2.8.5 类文档标记 2.8.6 变量文档标记:变量文档只能包括嵌入的HTML以及@see引用。 2.8.7 方法文档标记 2.8.8 文档示例
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值