Thking in java(第四版)-查缺补漏(第15章)

背景

继续学习,充电

1.泛型的概念

泛型实现了参数化类型的概念,使代码可以应用于多种类型。多态算是一种泛化机制。

泛型的主要目的之一就是用来指定容器要持有什么类型的对象,然后由编译器来保证

类型的正确性。

泛型类语法如下所示:

public class Holder3<T> {
	private T a;
	public Holder3(T a){	this.a=a;}
}

2.元组

将一组对象直接打包存储于其中的一个单一对象。允许读取其中的元素,但不允许向

其中存放新的对象。只要应用在方法的返回值,这样的话可以返回多个对象。

例如:

public class TwoTuple<A,B> {
	public final A first;
	public final B second;
	public TwoTuple(A a,B b){	first=a ; second=b;}
	public String toString(){	
		return "("+first+", "+second+")";
	}
}
static TwoTuple<String,Integer> f(){
		return new TwoTuple<String,Integer>("hi",47);
	}

3.泛型方法

语法如下:在返回值之前用“<type>”

	public <T> void f(T x){
		System.out.println(x.getClass().getName());
	}

当使用泛型类时,必须在创建对象的时候指定类型参数的值,而用泛型方法的时候,通常

不必指明参数类型,因为编译器会找出具体的类型。称为类型参数推断。

4.杠杆利用类型参数推断

在使用泛型的时候,需要向程序中加入更多的代码。例如:

Map<Person,List<? extends Pet>> petPeople=
    new HashMap<Person,List<? extends Pet>>(); 

在泛型方法中,类型参数推断可以为我们简化一部分工作,例如:

package tools;
import java.util.*;
public class New {
	public static <K,V> Map<K,V> map(){
		return new HashMap<K,V>();
	}
	public static  <T> List<T> list(){
		return new ArrayList<T>();
	}
	public static <T> LinkedList<T> lList(){
		return new LinkedList<T>();
	}
	public static <T> Set<T> set(){
		return new HashSet<T>();
	}
	public static <T> Queue<T> queue(){
		return new LinkedList<T>();
	}
	public static void main(String[] args){
		Map<String,List<String>> sls=New.map();
		List<String> ls=New.list();
		LinkedList<String> lls=New.lList();
		Set<String> ss=New.set();
		Queue<String> qs=New.queue();
	}
}

类型推断只对赋值操作有效。

在泛型方法中,要显示指明类型,必须在点操作符与方法名之间插入尖括号。

5.可变参数与泛型方法

例如:

public static <T> List<T> makeList(T... args){
		List<T> result=new ArrayList<T>();
		for(T item:args)
			result.add(item);
		return result;
	}

6.EnumSet

用来从enum直接创建Set 。EnumSet.range()传入某个范围的第一个元素与最后一个元素,然后返回一个Set。

7.泛型还可以应用于内部类和匿名内部类

public static Generator<Customer> generator(){
		return new Generator<Customer>(){
			public Customer next(){	return new Customer();}
		};	
	}

8.构建模型

下面构建了一个零售店,包括走廊、货架和商品。

package c15;
import java.util.*;
import tools.*;
class Product{
	private final int id;
	private String description;
	private double price;
	public Product(int IDnumber,String descr,double price){
		id=IDnumber;
		description =descr;
		this.price=price;
		System.out.println(toString());
	}
	public String toString(){
		return id+": "+description+", price: $"+price;
	}
	public void priceChange(double change){
		price+=change;
	}
	public static Generator<Product> generator=
			new Generator<Product>(){
				private Random rand=new Random(47);
				public Product next(){
					return new Product(rand.nextInt(1000),"Test",Math.round(rand.nextDouble()*1000.0)+0.99);
				}
			};
}
class Shelf extends ArrayList<Product>{
	public Shelf(int nProducts){
		Generators.fill(this,Product.generator,nProducts);
	}
}
class Aisle extends ArrayList<Shelf>{
	public Aisle(int  nShelves,int nProducts){
		for(int i=0;i<nShelves;i++)
			add(new Shelf(nProducts));
	}
}
public class Store extends ArrayList<Aisle> {
	 public Store(int nAisles,int nShelves,int nProducts){
		 for(int i=0;i<nAisles;i++)
			 add(new Aisle(nShelves,nProducts));
	 }
	 public String toString(){
		 StringBuilder result=new StringBuilder();
		 for(Aisle a:this)
			 for(Shelf s:a)
				 for(Product p:s){
					 result.append(p);
					 result.append("\n");
				 }
		 return result.toString();
	 }
	 public static void main(String[] args){
		 System.out.println(new Store(14,15,10));
	 }
}

9.擦除

在泛型代码内部,无法获得任何有关泛型参数类型的信息。

Java泛型是使用擦除来实现的,当你使用泛型时,任何具体的类型信息都被擦除了。比如:

List<String>和List<Integer>他们都被擦除成他们的原生类型,即List。普通的类型变量在未

制定边界的情况下被擦除为Object。

边界<T extends X>声明T必须具有类型X或者从X导出的类型,泛型类型参数将擦除到它的第

一个边界。比如:<T extends X> T被擦除到X。

擦除的核心动机是它使得泛化的客户端可以用非泛化的类库来使用。

在泛型中创建数组,使用Array.newInstance() 是推荐的方式。

10.擦除的补偿

引入类型标签

public class ClassTypeCapture<T> {
	Class<T> kind;
	public ClassTypeCapture(Class<T> kind){
		this.kind=kind;
	}
}

11.泛型数组

不能创建泛型数组,但是可以用ArraylList来实现

public class ListOfGenerics<T> {
	private List<T> array=new ArrayList<T>();
	public void add(T item){	array.add(item);}
	public T get(int index){	return array.get(index);}
}

可以定义个一个泛型数组的引用,但是不能创建这个确切类型的数组。

Generic<Integer>[] gia;

成功创建泛型数组的惟一凡是就是创建一个被擦除类型的新数组,然后对其转型。

public class GenericArray<T> {
	private T[] array;
	@SuppressWarnings("unchecked")
	public GenericArray(int sz){
		array=(T[])new Object[sz];
	}
}

因为有了擦除,suzuki运行时类型是Object[],我们用T[]进行转型,那么编译器该数组

的实际类型就丢失了。 最好是在集合内部使用Object[]。上面的代码T[]改成 Object[]

12.边界

边界使得你可以在用于泛型的参数类型上设置限制条件。这使得你可以按照自己的边界

类型来调用方法。因为擦除移除了类型信息,所以可以用无界泛型参数调用的方法只是

那些可以用Object调用的方法。

语法:<T extends X> 使用了extends关键字

14.通配符

泛型参数表达式中的问号。泛型没有内建的协变类型。

如果想要在两个类型之间建立某种类型的向上转型关系,可以使用通配符

List<? extends Fruit> flist=new ArrayList<Apple>();
//Compile Error:can't add any type of object:
//flist.add(new Apple());
//flist.add(new Fruit());
flist.add(null);
Fruit f=flist.get(0);

<? extends Fruit>表示具有任何从Fruit继承的类型的列表。但是这不意味着这个List将持有

任何类型的Fruit。List<? extends Fruit>可以合法地指向一个ArrayList<Apple>,一旦执行这

种类型的向上转型,你将丢失掉向其中传递任何对象的能力。

15.逆变

超类型通配符。<? super X> 使用关键字super

可以声明通配符是由某个特定类的任何基类来界定。

public class SuperTypeWildcards {
	static void writeTo(List<? super Apple> apples){
		apples.add(new Apple());
		apples.add(new Jonathan());
	}
}

参数Apple是Apple的某种基类型的List,这样你就知道向其中添加Apple或Apple

的子类型是安全的。

16.无界通配符

<?>被认为是一种装饰。事实上在声明要用Java泛型来编写代码。

下面是它的一个应用:


public class UnboundedWildcards2 {
	static Map map1;
	static Map<?,?> map2;
	static Map<String,?>map3;
	static void assign1(Map map){map1=map;}
	static void assign2(Map<?,?> map){	map2=map;}
	static void assign3(Map<String,?> map){	map3=map;}
	public static void main(String[] args){
		assign1(new HashMap());
		assign2(new HashMap());
		assign1(new HashMap<String,Integer>());
		assign2(new HashMap<String,Integer>());
		assign3(new HashMap<String,Integer>());
	}
}

17.捕获转换

未指定的通配符类型被捕获,并转换为确切类型。

static <T> void f1(Holder<T> holder){
		T t=holder.getA();
		System.out.println(t.getClass().getSimpleName());
	}
	static void f2(Holder<?> holder){
		f1(holder); //Call with captured type
	}

18.问题

(1)任何基本类型都不能作为类型参数。解决之道是使用基本类型的包装器类以及Java的自动包装机制。

(2)一个类不能实现同一个泛型接口的两种变体。由于擦除的原因,两种变体会成为相同的接口。

(3)使用带有泛型类型参数的转型或instanceof不会有任何效果。可以通过泛型类来转型:

public class ClassCasting {
	@SuppressWarnings("unchecked")
	public void f(String[] args)throws Exception{
		ObjectInputStream in=new ObjectInputStream(new FileInputStream(args[0]));
		List<Widget> lw2=List.class.cast(in.readObject());
	}
}

(4)不能重载,因为擦除的原因,重载方法将产生相同的类型签名。

(5)基类劫持了接口:只能与CompareblePet进行比较

public class ComparablePet implements Comparable<ComparablePet>{
    public int compareTo(ComparablePet atg){    return 0;}
}

(6)自限定类型:强制泛型当作自己的边界参数来使用。

class SelfBounded<T extends SelfBounded<T>> {
	T element;
	SelfBounded<T> set(T arg){
		element=arg;
		return this;
	}
	T get(){	return element;}
}
class A extends SelfBounded<A>{}
class B extends SelfBounded<A>{}

可以保证类型参数必须与正在被定义的类相同。自限定限制只能强制作用于继承关系。

将自限定用于泛型方法:可以防止这个方法被应用于除上述形式自限定参数之外的任何事物上。

public class SelfBoundingMethods {
	static <T extends SelfBounded<T>> T f(T arg){
		return arg.set(arg).get();
	}
	public static void main(String[] args){
		A a=f(new A());
	}
}

(7)参数协变:自限定类型的价值在于它们可以产生协变参数类型--方法参数类型会随子类而变化。

interface GenericGetter<T extends GenericGetter<T>>{
	T get();
}
interface Getter extends GenericGetter<Getter>{}
public class GenericsAndReturnTypes {
	void test(Getter g){
		Getter result=g.get();
		GenericGetter gg=g.get();
	}
}

(8)动态类型安全

提供类型检查的静态方法:checkedCollection()、checkedList()、checkedMap()、checkedSet()、

checkedSortedMap()和checkedSortedSet()。

List<Dog> dog=Collections.checkedList(new ArrayList<Dog>(),Dog.class);

 将希望动态检查的容器当作第一个参数接受,并将希望强制要求的类型作为第二个参数接受。

(9)异常,catch语句不能捕获泛型类型的异常,也不能直接或间接继承自Throwable。解决的办法如下:

package c15;
import java.util.*;
interface Processor<T,E extends Exception>{
	void process(List<T> resultCollection) throws E;
}
class ProcessRunner<T,E extends Exception> extends ArrayList<Processor<T,E>>{
	List<T> processAll() throws E{
		List<T> resultCollector =new ArrayList<T>();
		for(Processor<T,E> processor:this)
			processor.process(resultCollector);
		return resultCollector;
	}
}
class Failure1 extends Exception{}
class Processor1 implements Processor<String,Failure1>{
	static int count=3;
	public void process(List<String> resultCollector) throws Failure1{
		if(count-->1)
			resultCollector.add("Hep!");
		else
			resultCollector.add("ho!");
		if(count<0)
			throw new Failure1();
	}
}

public class ThrowGenericException {
	public static void main(String[] args){
		ProcessRunner<String,Failure1> runner=new ProcessRunner<String,Failure1>();
		for(int i=0;i<3;i++)
			runner.add(new Processor1());
		 try{
			 System.out.println(runner.processAll());
		 }catch(Failure1 e){
			 System.out.println(e);
		 }
		
	}
}

(10)混型:混合多个类的能力,以产生一个可以表示混型中所有类型的类。价值之一是它们可以

将特性和行为一致地应用于多个类之上。在混型类中修改某些东西,将会应用到所有类型上,所以

混型有点面向方面编程(AOP),而方面经常被建议用来解决混型问题。

用接口来产生混型效果,也可以使用装饰器模式,还有与动态代理混合。装饰器模式使用分层对象

来动态透明地向单个对象添加责任。

package c15;
import java.util.*;
class Basic{
	private String value;
	public void set(String val){	value=val;}
	public String get(){	return value;}
}
class Decorator extends Basic{
	protected Basic basic;
	public Decorator(Basic basic){	this.basic=basic;}
	public void set(String val){	  basic.set(val);}
	public String get(){	return basic.get();}
}
class TimeStamped extends Decorator{
	private final long timeStamp;
	public TimeStamped(Basic basic){
		super(basic);
		timeStamp=new Date().getTime();
	}
	public long getStamp(){	return timeStamp;}
}
class SerialNumbered extends Decorator{
	private static long cunter=1;
	private final long serialNumber=cunter++;
	public SerialNumbered(Basic basic){	super(basic);}
	public long getSerialNumber(){	return serialNumber;}
}

public class Decoration {
	public static void main(String[] args){
		TimeStamped t=new TimeStamped(new Basic());
		TimeStamped t2=new TimeStamped(new SerialNumbered(new Basic()));
		SerialNumbered s=new SerialNumbered(new Basic());
		SerialNumbered s2=new SerialNumbered(new TimeStamped(new Basic()));
		s2.getSerialNumber();
	}
}

尽管可以添加多层,但是最后一层才是实际类型,只有最后一层点的方法是可视的。

19.潜在类型机制

Java缺乏潜在类型机制。补偿的办法如下:

(1)可以使用反射来补偿

package c15;
import java.lang.reflect.*;
import static tools.Print.*;
class Mime{
	public void walkAgainstTheWind(){}
	public void sit(){print("pretending to sit");}
	public void pushInvisibleWalls(){}
	public String toString(){	return "Mime";}
}
class SmartDog{
	public void speak(){	printf("Woof!");}
	public void sit(){	printf("Sitting");}
	public void reproduce(){}
}
class CommunicateReflectively{
	public static void perform(Object speaker){
		Class<?> spkr=speaker.getClass();
		try{
			try{
				Method speak=spkr.getMethod("speak");
				speak.invoke(speaker);
			}catch(NoSuchMethodException e){
				print(speaker+"cannot speak");
			}
			try{
				Method sit=spkr.getMethod("sit");
				sit.invoke(speaker);
			}catch(NoSuchMethodException e){
				print(speaker+"cannot sit");
			}
		}catch(Exception e){
			throw new RuntimeException(speaker.toString(),e);
		}
	}
}
public class LatentReflection {
	public static void main(String[] args){
		CommunicateReflectively.perform(new SmartDog());
//		CommunicateReflectively.perform(new Robot());
		CommunicateReflectively.perform(new Mime());
	}
}

 (2)将一个方法应用于序列。

(3)用适配器仿真潜在类型机制。

20.将函数对象用作策略

这里跳过了,以后再看

总结

泛型机制,在使用容器类的时候很有用。如果要自己实现泛型类,泛型方法和泛型接口就会有很多的限制,

要小心。当要使用泛型机制的时候得考虑到底没有有这个必要。

学习要学到边界,才能知道能做什么和不能做什么,为以后省下不必要的麻烦。

猜你喜欢

转载自blog.csdn.net/a614528195/article/details/82054693
今日推荐