接口与内部类
1.(1)接口中不能含有实例域,也不能有静态方法和已经实现的方法。但是可以有 常量,在接口中定义的变量默认为public static final。
(2)声明接口时,不需写方法的访问类型,因为接口中方法默认都是public。但是在实现接口中的方法时,必须指明方法的访问权限。
(3)抽象类可以包含具体数据和具体方法以及未实现的方法。接口只能包含未实现的方法和常量。
(4)只能继承一个类,而可以实现多个接口。
2.在实现Comparable接口的compareTo方法时,必须考虑对称性。例如
Employee e = new Employee();
Manager m = new Manager();
e.compareTo(m)可以比较,但是m.compareTo(e)则会抛出异常,不符合反对称。
解决方法:
(1)如果不同子类之间的比较含义不一样,那么在比较之前就应该检验两个对象是否属于同一类型if(getClass()!=other.getClass()) throw new ClassCastException();
(2)如果想要一个通用方法,对两个不同子类进行比较,那么就需要在超类中提供一个compareTo方法,并声明为final。
3.深克隆与浅克隆
(1)深克隆,克隆出的对象与原对象是完全独立的(包括对象数据域中的引用类型)。
(2)实现深克隆必须重写clone方法,因为需要调用克隆对象数据域中引用类型的clone方法,以修补clone方法不克隆对象数据域中引用类型对象的问题。例如:
package TestPackage;
import java.util.Date;
public class TestClone implements Cloneable{
private String s = "test";
private Date d = new Date();
@Override
public TestClone clone() throws CloneNotSupportedException{
TestClone cloned = (TestClone) super.clone(); //调用从Object类继承来的clone方法,这一步只完成了浅克隆
cloned.d = (Date) d.clone(); //Date类重写了Object类中的clone方法,可以直接使用
cloned.s = new String(s); //String类没有重写Object类中的clone方法,不能直接使用clone方法。
return cloned;
}
public static void main(String[] args){
TestClone c = new TestClone();
try {
TestClone cc = c.clone();
System.out.println(c.s==cc.s); // 输出false
System.out.println(c.d==cc.d); //输出false
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(3)浅克隆,如果要克隆的对象的数据域中含有引用类型,则克隆出的对象的数据域中的引用类型则不会被克隆,而是指向原对象的数据域中的引用类型。
(4)调用Object类中的clone方法即可实现浅克隆。但是,需要:[1]实现Cloneable接口(无实际意义,只是一个标志)[2]使用public访问修饰符重新定义clone方法
(5)但是,实现浅克隆时,有些时候也可以不重新定义clone方法。例如:
public class Manager implements Cloneable{
public static void main(String[] args){
Manager m = new Manager();
try {
Manager mm = (Manager) m.clone(); //完全可以编译执行
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
因为clone方法属于Object类,但是访问类型是protected。而任何类都是Object类的子类,所以在Manager类中Manager类型(仅限于Manager类型,而不能是Object类型)可以直接调用clone方法。
像下面这样则不可以直接调用clone方法:
public class Manager implements Cloneable{
public static void main(String[] args){
Object obj = new Manager();
try {
Manager mm = (Manager) obj.clone(); //编译错误。因为obj为Object类型,尽管Manager类是Object类的子类,也不能在Manger类中调用Object类的protected类型方法。
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这篇文章在学习中给了我很大帮助:http://zhangjunhd.blog.51cto.com/113473/19287/ 【Thank you!】
4.所有数组都包含一个public类型的clone方法。
5.内部类
(1)一个外围类与其内部类是类与类之间的关系,而不是对象之间的关系。也就是说,一个外围类对象并不包含其内部类的对象。
(2)内部类都包含对外部类对象的一个引用(这个引用引用了实例化该内部对象的外围类对象),所以可以直接访问外围类的任意数据域(包括private类型),但是这个引用是隐式的,是不可见的。
(3)内部类可以被标识为私有类,这样就只有外部类可以创建该内部类对象。只有内部类可以被标识为私有类,常规类只可以具有包可见性或者公有可见性。
(4)在外部类中创建内部类。例如,TimePrinter是TalkClock的内部类,下面的代码在TalkClock类中。
TimePrinter t = this.new TimePrinter();//在TalkClock类中创建TimePrinter内部类对象,this可以省略。如果内部类不是private,也可以在其他类中创建内部类对象,this替换为TalkPrinter的对象。
6.局部内部类。例如在一个类的某个方法中定义一个类,称为局部内部类。不能使用public或者private修饰符声明,作用域限定在该函数内。
(1)优点:[1]对外部世界完全隐藏起来,只有所在函数知道它的存在。[2]不仅可以访问外部类,还可以访问函数中的局部变量,但变量必须是final类型的。
(2)局部内部类在访问方法中的局部变量时,会在局部内部类内创建一个该局部变量的副本,例如:
public void start(int internal, final boolean beep)
{
Class TimePrinter implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
Date now = new Date();
if(beep) //注意这个beep
{
Toolkit.getDefaultToolkit().beep();
}
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(internal, listener);
t.start();
}
在start方法(包含内部类的start方法)执行完成后,beep变量在这时已经不存在了。然而Timer类对象还是会每隔internal时间去调用actionPerformed方法,beep变量在局部内部类中并没有消失,actionPerformed方法还是可以执行成功就是因为在局部内部类中存储了该变量的副本。
(3)有时候我们想要在局部内部类中访问一个数值可变的变量,可以将这个变量声明为只包含一个变量的数组。例如:
//统计排序过程中compareTo方法的调用次数
final int[] counter = new int[1]; //声明为数组类型, final仅仅表示counter不可以再引用其他的数组。
Date[] dates = new Date[100];
for(int i = 0; i<dates.length; i++){
dates[i] = new Date(){ //匿名内部类, 继承了Date类,重写compareTo方法,只是没有名字,而且只创建这一个对象。
public int compareTo(Date other){
counter[0]++;
return super.compareTo(other);
}
};
}
Arrays.sort(dates);
(4)总结:内部类最终会被编译器翻译成用 分隔外部类名与内部类名的常规类文件。如TalkingClock TimePrinter.class。与一般类文件的不同之处在于,内部类被翻译成的类文件中,包含对外部类以及局部变量的引用,实际上它成为了一个独立的类文件。(但是要注意作用域)
7.匿名内部类。
[1]特殊的:双括号初始化,如invite(new ArrayList(){{add(“Tony”); add(“Harry”);}}//外层括号建立了一个匿名内部类,内层括号是一个对象构造块(第4章)
[2]*我没有看明白:对于匿名子类来说,重写equals方法时,if(getClass()!=other.getClass()) return false;这个方法会失效。
[3]获取静态方法所在的类名时,无法直接使用getClass方法获取,因为使用getClass时调用的是this.getClass(),而静态方法没有this。可以使用:new Object(){}.getClass().getEnclosingClass();其中getEnclosingClass方法是用来获取外部类的。
8.静态内部类
[1]静态内部类中没有对外部类的引用,所以在内部类不需要使用外部类时,可以将内部类定义为static类型。
[2]如果想要在静态方法中使用内部类,那么内部类就需要声明为static类型,因为静态方法只能使用静态的数据域/方法。
[3]声明在接口中的内部类自动成为public static类型