Java核心技术卷I

Java核心技术卷I

6.2.3 对象克隆

	1、实现Cloneable 接口
	2、了解克隆的具体含义,先来回忆为一个包含对象引用的变量建立副本时会发生什么。变量和副本都是同一个对象的引用(见图 6-1 )。
	这说明, 任何一个变量改变都会影响另一变量。
 //6.2.3 对象克隆
  Employee original=new Employee("John public ",50000);
  // 复制引用对象
  Employee copy =original;
  //Clone 方法
  //Employee copy= Clone(original) ;
  copy.raiseSalary(10);
  System.out.print("original.Salary"+original.getSalary()+"\n");  original.Salary55000.0
  System.out.print("original.Salary"+copy.getSalary()+"\n");original.Salary55000.0
	3、如果希望 copy 是一个新对象,它的初始状态与 original 相同, 但是之后它们各自会有自己不同的状态, 这种情况下就可以使用 clone 方法。
//Clone 方法
  Employee copy= Clone(original) ;
  copy.raiseSalary(10);
  

![在这
在这里插入图片描述

	4、不过并没有这么简单。clone 方法是 Object 的一个 protected 方法,这说明你的代码不能直接调用这个方法。
	如果对象中的所有数据域都是数值或其他基本类型,拷贝这些域没有任何问题、 但是如果对象包含子对象的引用,
	拷贝域就会得到相同子对象的另一个引用,这样一来,原对象和克隆的对象仍然会共享一些信息。
	
	6-2 显示了使用***默认的克隆操作是“ 浅拷贝”,并没有克隆对象中引用的其他对象。***
	(这个图显示了一个共享的 Date 对象。出于某种原因(稍后就会解释这个原因,) 这个例子使用了 Employee 类的老版本,
	其中的雇员日期仍用 Date 表示。)浅拷贝会有什么影响吗? 
	这要看具体情况。如果原对象和浅克隆对象共享的子对象是不可变的, 那么这种共享就是安全的。
	如果子对象属于一个不可变的类, 如 String, 就 是 这 种情况。或者在对象的生命期中,子对象一直包含不变的常量,
	没有更改器方法会改变它, 也没有方法会生成它的引用,这种情况下同样是安全的。
	
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200714112812239.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM3OTcwMzM3,size_16,color_FFFFFF,t_70)
    
	5、不过, 通常子对象都是可变的, 必须重新定义 clone 方法来建立一个深拷贝, 同时克隆所有子对象。在这个例子中,
	hireDay 域是一个 Date , 这是可变的, 所以它也需要克隆。(出于这个原因, 这个例子使用 Date 类型的域而不是
	 LocalDate 来展示克隆过程。如果 hireDay是不可变的 LocalDate 类的一个实例,就无需我们做任何处理了。)对于每一
	 个类,需要确定:
		 1 ) 默认的 clone 方法是否满足要求;
		 2 ) 是否可以在可变的子对象上调用 clone 来修补默认的 clone 方法;
		3 ) 是否不该使用 clone 。
		实际上第 3 个选项是默认选项。如果选择第 1 项或第 2 项,类必须: 
		1 ) 实现 Cloneable 接口;
		 2 ) 重新定义 clone 方法,并指定 public 访问修饰符。

8、泛型程序设计

8.5 泛型代码和虚拟机

虚拟机没有泛型类型对象--所有对象都属于普通类。
8.5.1 类型擦除
 无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type) 原始类型的名字就是删除去类型参数后的泛型类型名。 擦除(erased) 类型变量,并替换为限定类型(无限定的变量用Object)。
/**
 * 8.5 泛型代码和虚拟机 虚拟机没有泛型类型对象--所有对象都属于普通类
 * 5.1 无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type) 原始类型的名字就是删除去类型参数后的泛型类型名。 
 * 擦除(erased) 类型变量,并替换为限定类型(无限定的变量用Object)
 */
//例如 Pair<T> 原始类型如下所示
package pair2;

public class Pair<T> {
	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 T getFirst() {return first;}
	public T getSecond() {return second;}
	
	public void setFirst(T newValue) {first= newValue;}
	public void setSecond(T newValue) {second=newValue;}
}
// T(无限定的变量) 用 Object 替换
public class Pair1 {
	private Object first;
	private Object second;

	public Pair1(Object first, Object second) {
		this.first = first;
		this.second = second;

	}

	public void setFirst(Object newValue) {
		first = newValue;
	}

	public void serSecond(Object newValue) {
		second = newValue;
	}

	public Object getFirst() {
		return first;
	}

	public Object getSecond() {
		return second;
	}

}
8.5.2 翻译泛型表达式
当程序调用泛型方法是,如果擦除返回类型,编译器插入强制类型转换。
Pair<Employee> buddies=;
// 擦除getFirst() 的返回类型后将返回 Object 类型编译器自动将Employee 的强制类型转换。
//编译器把这个方法调用翻译为两条虚拟机指令:

//对原始方法Pair.GetFirst的调用。 
//将返回的Object 类型强制转换为Emploee 类型。
Emoloyee buddy= byddies.getFirst();
8.5.3 翻译泛型方法
	类型擦除也会出在泛型方法中。
	虚拟机中用参数类型和返回类型确定一个方法。编译器可能产生两个仅返回类型不同的方法字节码。
	总结:有关java泛型转换的事实
  • 虚拟机中没有泛型,只有普通的类和方法。
  • 所有类型参数都用他们的限定类型替换。
  • 桥方法被合成来保持多态。
  • 为保持类型安全性,必要时插入强制类型转换。
类型参数被擦出,只留下了限定类型Comparable

//泛型方法
public static <T extends Comparable> T min(T[] a)
// 擦除类型之后
public static Comparable min(Comparable[] a)
//桥方法: 用途 解决类型擦除与多态发生冲突。

public void setSecond(Object second){setSecond(Date) second;}
8.5.4 调用遗留代码
	允许泛型代码和遗留代码之间能够互操作。
	示例:设置JSlider标签可以使用方法:
		void setLabelTable(Dictionary table)
		//Dictionary 是一个原始类型,填充字典是可以使用泛型
		Dictionary<Integer,Componment> labelTable=new Hashtable();
		labelTable.put(0,new JLabel(new ImageIcon("nine.gif")));
		labelTable.put(20,new JLabel(new ImageIcon("ten.gif")));
		//对象传递时会发生警告(编译器无法确定setLabelTable 对Dictionar 对象做什么操作。有可能会产生强制力类型转换的异常。)
		slider.setLabelTable(labelTable);//warning
		//现在,看一个相反的情形, 由一个遗留的类得到一个原始类型的对象。可以将它赋给一个参数化的类型变量, 当然,这样做会看到一个警告。
		Dictionary<Integer, Components〉labelTable = slider.getLabelTable(); // Warning
		//再看一看警告, 确保标签表已经包含了 Integer 和 Component 对象。
		//在查看了警告之后,可以利用注解 ( annotation) 使之消失。注释必须放在生成这个警告
的代码所在的方法之前,如下:
@SuppressWarnings("unchecked")
Oictionary<Integer, Components〉labelTable = slider.getLabelTable(); // No warning
//或者,可以标注整个方法,如下所示: @SuppressWarnings("unchecked")
public void configureSliderO { . . . }
这个注解会关闭对方法中所有代码的检査。

8.6约束与局限性

在下面几节中, 将阐述使用 Java 泛型时需要考虑的一些限制。大多数限制都是由**类型擦除**引起的。
8.6.1 不能用基本类型实例化类型参数
不能用类型参数代替基本类型(其原因是类型擦除)。
	Pair<double> 错误
	 Pair<Double>正确
擦除之后, Pair 类含有 Object 类型的域, 而 Object 不能存储 double值
这样做与java语言中基本类型的独立状态相一致,这并不是一个致命的缺陷--只有 8 种基本类型, 当包装器类型(wrapper type) 不能接受替换时, 可以使用独立的类和方法处理它们。
	1. 整型 byte(1字节) short (2个字节) int(4个字节) long (8个字节)
	2.浮点型 float(4个字节) double(8个字节)
	3.逻辑性 boolean(八分之一个字节)
	4.字符型 char(2个字节,一个字符能存储下一个中文汉字)
8.6.2 运行时类型查询只适用于原始类型
虚拟机中的对象总有一个特定的非泛型类型。因此,所有的类型查询只产生原始类型。

例如:
if (a instanceof Pair) // Error
Pair<St「ing> p = (Pair) a;
// Warning-can only test that a is a Pair

为提醒这一风险, 试图查询一个对象是否属于某个泛型类型时,倘若使 用 instanceof 会得到一个编译器错误, 如果使用强制类型转换会得到一个警告。
  同样的道理, getClass 方法总是返回原始类型。例如:

Pair stringPair = . .
Pai「<Employee〉employeePair = . .
if (stringPair.getClassO == employeePair.getClassO) // they are equal
其比较的结果是 true, 这是因为两次调用 getClass 都将返回 Pair.class。

8.6.3 不能创建参数化类型的数组
示例
Pair<String>[] table = new Pair<String>[10]; // Error
擦除之后, table 的类型是 Pair[] 可以把它转换为 Object :
Object[] objarray = table;

数组会记住它的元素类型, 如果试图存储其他类型的元素, 就会抛出一个ArrayStoreException 异常:
objarray[0] = "Hello"; // Error component type is Pair

不过对于泛型类型, 擦除会使这种机制无效。能够通过数组存储检査, 不过仍会导致一个类型错误。以下赋值:
objarray[0] = new Pair<Employee>0;
8.6.4 Varargs(可变参数)警告
**问题:向参数个数可变的方法传递一个泛型类型的实例。**
public static <T> void addAll(Collections coll, T... ts) {
for (t : ts) coll.add⑴;
}
//ts 是一个数组,包含提供的所有实参
//现在考虑以下调用
Collection<Pair<string>> table=...;
Pair<String> pair1=;
Pair<String> pair2;
addAdd(table,pair1,pair2);
//为了调用这个方法,Java 虚拟机必须建立一个 Pair<String> 数组, 这就违反了前面的规则(这里只是警告)。

//可以采用两种方法来抑制这个警告。一种方法是为包含 addAll 调用的方法增加注解 @SuppressWamings("unchecked"。) 或者在 Java SE 7中, 还 可 以 用@SafeVarargs直接标注addAll 方法:
@SafeVarargs// 对只需读取参数数组元素的所有方法,都可以使用这个注解。注解 不安全 不会出现ArrayStoreException 异常
public static <T> void addAll(Collection<T> coll,T...ts)

8.6.5 不能实例化类型变置
Java SE 8 之后,最好的解决办法是让调用者提供一个构造器表达式。例如:
Pair<String> p=pair.makePair(String::new);

makePair 方法接收一个Supplier<T>,这是一个函数式接口,表示一个无参数而且返回
类型为 T 的函数:
public static <T> Pair<T>makePair(Supplier<T> constr)
{
retrun new PairM<>( constr.get(),constr.get());
}
//设计如下api 以便得到一个 Class 对象
public static <T> Pair<T> makePair(Class<T> cl)
{
try(reutrn new pairM<>(cl.newInstance(),cl.newInstance();))
catch(Exceprion ex){return null;}
}
//调用
Pair<String> p=Pair.makePair(String.class);
//注意,Class类本身是泛型。 例如,String.daSS 是一个 Class<String> 的实例(事实上,它是唯一的实例。) 因此,makePair 方法能够推断出 pair 的类型。
8.6.6 不能构造泛型数组
数组构造器表达式
String[] ss=ArrayList.minmax(String[]::new ,"123","32","11")	;
 比较老是方法是利用反射,调用Array.newInstance:
 public static <T extends Comparable> T[] minmax(T....a){
	 T[] mm=(T[]) Array.newInstance(a.getvClas().GetComponetType(),2);
	 }
8.6.7 泛型类的静态上下文中类型变量无效
不能在静态域或方法中引用类型变量。
public class Singleton<T>
{//如果这个程序能够运行, 就可以声明一个 Singleton<Random> 共享随机数生成器, 声明 一个 Singlet0n<JFileCh00Ser> 共享文件选择器对话框。
//但是, 这个程序无法工作。类型擦除之后, 只剩下 Singleton 类,它只包含一个 singlelnstance 域。 因此, 禁止使用带有类型变量的静态域和方法。
	private static T singleInstance;//Error
	public static T getSingInstance()//Error
	{
		if(singleInstance==null) construct new instance of T
		return singleInstance;		
	}
}
8.6.98不能抛出或捕获泛型类型的实例
既不能抛出也不能捕获泛型类对象。实际上,甚至泛型类扩展 Throwable 都是不合法的。例如,以下定义就不能正常编译:
public class Proable<T> extends Excepiton {/**/};//Error
//	catch子句中不能使用**类型变量**。例如以下方将不能编译
	public static <T extends Throwable> void doWork(Class<T> t) {
		try {
		
		}
		// Error --不能适用类型变量
		catch(T e) {
			
		}
	}
	//在异常规范中使用**类型变量**是允许的
	public static <T extends Throwable> void doWork(T t) throws T{
		try {
			
		}
		catch(Throwable realCause ){
			t.initCause(realCause);
			throw t;
		}
	}
8.6.9 可以消除对受异常的检查
java 异常处理的一个基本原则是,必须为所有受查异常提供一个处理器。可以使用反省消除这个限制。
//运行这个程序会得到一个栈轨迹
new Block() {
			public void body() throws Exception{
				Scanner in =new Scanner(new File("C:\\Users\\Administrator\\Desktop\\123.txt"));
				
				while(in.hasNext())
					System.out.print(in.next());
						
			}
		}.toThread().start();//
//----------------------------------------------------
public abstract class Block {
	public abstract void body()  throws Exception;
	
	public Thread  toThread() {
		return new Thread() {
			public void run() {
				try {
					body();
					
				}
				catch(Throwable t) {
					Block.<RuntimeException>throwAs(t);
				}
			}
			
		};
	}
	@SuppressWarnings("unchecked")// 消除对已检查异常的检查
	public static <T extends Throwable> void  throwAs(Throwable e) throws T
	{
		 
		throw(T)e;
	}
8.6.10注意擦除后的冲突
要想支持擦除的转换就需要强行限制一个类或类型变量不能同时成为两个接口类型的子类,而这个接口是同一接口的不同参数化。
class Employee implements Coinparab1e<Emp1oyee> { . . . }
class Manage extends Employee implements Comparable<Manager>{...}//error
//Manager会实现Comparable<Empleyee> 和Comparable<Manager>, 这是同一接口的不同参数化。
//这一限制与类型擦除的关系并不十分明确。毕竟,下列非泛型版本是合法的。
 class Employee implements Comparable { . . . }
 class Manager extends Employee implements Comparable { . . . }
//其原因非常微妙, 有可能与合成的桥方法产生冲突。实现了 C0mpamble<X> 的类可以获得一个桥方法:
 public int compareTo(Object other) { return compareTo((X) other); }
 //对于不同类型的 X 不能有两个这样的方法

8.7 泛型类型的继承规则

无论 S 与 T(类) 之间有什么关系  Pair<S> 与Pair <T>没有什么联系。

注意泛型与数组之间重要区别

注意泛型与数组之间重要区别。
泛型类可以扩展或实现其他的泛型类。
例如: ArrayList<T> 类实现 List<T>接口。这意味着一个ArrayList<Manager> 可以被转换为一个List<Manager>但是一个ArrayList<Manager> 不是一个ArrayList<Empleyee> 或List<Employee> 

在这里插入图片描述

8.8通配符类型

8.8.1 通配符类型中,允许类型参数变化。
例如: 
	Pair<? extends Employee>
	表示任何泛型 Pair 类型, 它的类型参数是 Employee 的子类, 如 Pair<Manager>, 但不是Pair<String>。
	
	使用通配符编写一个打印雇员的方法:
	public static void printBuddues(Pair<? extends Emplyee > p)
	{
		Employee first= p.getFirst();
		Employee second=p.getSecond();
		System.o.p(first.getName()+"\n"+second.getName());
	}
	
**类型Pair<Manager> 是Pair<? extends Employee> 的子类型**

8-3

	问题 : 使用通配符会通过 Pair<? extends Employee> 的引用破坏 Pair<Manager> 吗?
	Pair<Manager> managerBuddies = new Pair<>(ceo, cfo);
	Pair<? extends Employee> wildcardBuddies = managerBuddies; // OK
	wildcardBuddies.setFirst(lowlyEnployee); // compile-time error
	
	setFirst 的调用有一个类型错误。其方如下:
	?extends Employee getFirst()
	void setFirst(? extends Employee)
	这样将将不可能调用setFirst 方法。编译器只知道需要某个Employee 的子类型,但不知道具体是什么类型。他拒		   绝传递任何特定的类型。毕竟?不能用来匹配。
	使用 getFirst 就不存在这个问题: 将 getFirst 的返回值赋给一个 Employee 的引用完全合法。
	这就是引人有限定的通配符的关键之处。现在已经有办法区分安全的访问器方法和不安
	全的更改器方法了。
8.8.2 通配符的超类型限定

通配符限定与类型变量限定十分类似,但是,还有一个附加的能力,即可以指定一个超类型限定(supertypebound), 如下所示:

? super Manager   限制为所有 Manager 的所超类型。带有超类型限定的通配符的行为可以为方法提供参数,但不不能使用返回值 。例如,Pair<?super Manager> 有方法
void setFirst(? super Manager)
? super Manager getFirst()

编译器无法知道 setFirst 方法的具体类型, 因此调用这个方法时不能接受类型为 Employee 或 Object 的参数。 只能传递Manager 类型的对象,或者某个子类型(如 Executive) 对象。另外, 如果调用 getFirst, 不能保证返回对象的类型。只能把它赋给一个 Object。

集合

9.1.1 将集合的接口与实现分离

与现代的数据结构类库的常见情况一样, Java 集合类库也将接口( interface) 与 实 现(implementation) 分离。

//
public interface Collection<E>
{ 
//add 添加唯一值 成功返回 true
	boolean add(E element);
	
	Iterator<E> interator();
	
}
9 . 1.3 迭代器
public interface Iterator<E>
{
	E next();
	boolean hasNext();
	void remove();
	default void forEachRemainning(Consumer<? super E> action);
}
方法说明
next通过反复调用 next 方法,可以逐个访问集合中的每个元素。调用next 之前调用hasNext方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值