四. 接口与内部类

一. 接口

1. 引入
接口:用于描述类具有的功能,而不关心具体地实现。接口不是类,一个类可以实现一个多个接口。接口中只允许声明方法和常量(不能定义域和方法的具体实现)。

如果一个类implements某个接口,就必须实现该接口中的所有方法。(抽象类允许不实现接口中的方法)

如Arrays.sort()可以对对象数组进行排序,但是要求对象所属类必须实现了Comparable接口,定义其中的CompareTo方法:

class Employee implements Comparable<Employee>{
	protected String name;
	private int age;
	public int getAge() {
		return age;
	}
	public Employee(String name, int age){
		this.name = name;
		this.age = age;
	}
	@Override
	public int compareTo(Employee o) {		
		return Integer.compare(age, o.age);
	}
}

public class MyTest {
	public static void main(String[] args) {
		Employee[] e = new Employee[3];
		e[0] = new Employee("lili", 23);
		e[1] = new Employee("haha", 12);
		e[2] = new Employee("jaja", 15);
		Arrays.sort(e);
		for(Employee m: e) {
			System.out.println(m.name + " " + m.getAge()); // 12 15 23
		}
	}
}

2. 接口的特性

  1. 接口不是类,所以不能new一个接口的对象,但是可以声明一个接口的变量,该变量用于引用实现了该接口的类的对象。
  2. 接口可以被扩展,同样使用extends关键字。
  3. 接口中可以声明常量,接口中的域被自动定义为public static final,方法被定义为public
  4. 一个类可以实现多个接口,通过逗号分隔各个实现的接口。

注意:为什么有了抽象类还需要使用接口呢?

接口与抽象类有很大的差别,首先,抽象类是一个类,可以定义域、方法、构造器等,接口不行;其次,Java只允许单继承,但可以实现多个接口。接口可以满足多继承的几乎所有的好处,同时避免了多继承带来的复杂性和低效性。如C++中的多继承,会出现二义性问题。实现多个接口却不会出现这样的问题,个人认为是因为接口中的方法都只是声明,而未实现,需要类自身进行实现,因此不存在引用哪个“父类”中的方法的问题;而且接口中的域都是public static final,也是属于类的不可变的。可以说,是接口本身的特性使其能够避免因多继承带来的问题。

3. clone()方法
当拷贝一个变量时,原始变量与拷贝变量将引用同一个对象,对拷贝变量对象的改变会影响原始变量对象。为了使两者不相互影响,就需要使用clone().

clone是Object类中的一个方法,所以所有的类对象都可以使用clone方法进行克隆。但是,由于clone()是protected的,只有在当前类中才能调用该方法克隆自身对象(如对Employee对象的克隆必须在Employee类中调用才行)。这样使得clone()太受限,一般我们会重写clone方法,将其声明为public

Object类中clone默认是浅拷贝,它并没有克隆包含在对象中的内部对象。如果一个类中的各个域都是基本类型或不可变对象(如String),clone将不会出现问题(基本类型存储的是真正的值,clone将直接复制一份值;不可变对象变量虽然存储的是对象的引用,但由于引用的对象是不可变的,因此clone一份引用也不会出错)。但如果类中包含可变对象,就必须重新定义clone方法,实现深拷贝。

  • 重写clone()需要:
  1. 实现Cloneable接口;
  2. 将protected改为public。

需要注意的是,Cloneable接口实际上不包含任何方法,仅仅作为一个标记,重写的是Object类中的clone方法,但不实现这个接口会报错。还有,将protected改为public不会影响方法的签名,是重写而不是重新定义了一个方法。

class Employee implements Cloneable{
	private String name;
	private int age;
	private Date hireDay;
	
	public Employee(String name, int age){
		this.name = name;
		this.age = age;
		this.hireDay = new Date();
	}
	public int getAge() {
		return age;
	}
	public void setHireDay(int year, int month, int day) {
		hireDay = new GregorianCalendar(year, month-1, day).getTime();
	}
	public Date getHireDay() {
		return hireDay;
	}

	@Override
	public Employee clone() throws CloneNotSupportedException {
		Employee cloned = (Employee) super.clone();
		cloned.hireDay = (Date) hireDay.clone(); 
		return cloned;
	}

}

public class MyTest{
	public static void main(String[] args) throws CloneNotSupportedException {
		Employee e = new Employee("lili", 12);
		Employee cloned = e.clone(); 
		cloned.setHireDay(2018, 9, 1);
		System.out.println("origin: " + e.getHireDay());
		System.out.println("cloned: " + cloned.getHireDay());
		//origin: Fri Nov 08 11:03:17 CST 2019
		//cloned: Sat Sep 01 00:00:00 CST 2018
	}
}

上面的例子举得不太好,setHireDay是让hireDay重新指向了一个新的对象,即使没有cloned.hireDay = (Date) hireDay.clone(); 打印的也是上述内容。

在Employee中定义的clone方法也会被子类Manager继承,如果Manager中的域都是基本类型,可以直接使用Employee中的clone方法克隆Manager对象,否则还需自己重新实现。

4. 回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

函数回调就是将函数指针的地址当作参数传递给另一个函数。
函数回调的用途简单来说就是进行事件的响应或者事件触发。

在C++中,确实是通过将函数名传递给一个函数进行回调,但在Java中是通过传递对象来实现。下面通过一个定时打印时间的例子来进行说明:

class TimePrinter implements ActionListener{
	@Override
	public void actionPerformed(ActionEvent e) {
		Date now  = new Date();
		System.out.println(now);
	}
}
public class MyTest {
	public static void main(String[] args) {
		ActionListener listener = new TimePrinter();
		Timer t =  new Timer(1000, listener); //第一个参数是时间间隔,第二个参数是监听的对象
		t.start();
	}
}

ActionListener接口中包含一个actionPerformed方法,当到达指定的时间间隔时,定时器将调用这个方法。

二. 内部类

内部类是定义在一个类中的类,使用内部类的原因:

  1. 内部类可以访问其所在外部类的所有域,包括私有域;
  2. 内部类可以对外进行隐藏;
  3. 当想定义一个回调函数而不想编写大量代码时,使用匿名内部类会很方便。

1. 通过内部类访问所在类的私有域

为实现访问所在类的私有域,内部类的对象会存在一个隐式引用,它指向了创建它的外部类对象。这个引用会自动传入内部类的构造器中进行设置。

class TalkingClock{
	private boolean beep;
	public TalkingClock(boolean beep) {
		this.beep = beep;
	}
	public void startPrint() {
		ActionListener listener = new TimePrinter();
		Timer t =  new Timer(1000, listener);
		t.start();
	}
	//该类对外不可见
	class TimePrinter implements ActionListener{
		@Override
		public void actionPerformed(ActionEvent e) {
			Date now  = new Date();
			System.out.println(now);
			if(beep) //可以访问beep,相当于TalkingClock.this.beep
				System.out.println("beep!");
		}
	}
}
public class MyTest {
	public static void main(String[] args) {
		TalkingClock tc = new TalkingClock(true);
		tc.startPrint();
	}
}

2. 局部内部类
像上述例子所示,TimePrinter类仅在startPrint()方法中用到了,所以可以将内部类直接放在该方法中,成为局部内部类。
局部内部类不能使用public或private进行声明,它的作用域仅被限定在该方法中。局部类的优势在于它完全对外隐藏(内部类还可以通过OuterClass.InnerClass进行调用),注意局部变量必须声明为final

class TalkingClock{
	public void startPrint(final boolean beep) {
		class TimePrinter implements ActionListener{
			@Override
			public void actionPerformed(ActionEvent e) {
				Date now  = new Date();
				System.out.println(now);
				if(beep) System.out.println("beep!"); //在beep释放前进行了拷贝,所以能够访问该局部变量
			}
		}
		ActionListener listener = new TimePrinter();
		Timer t =  new Timer(1000, listener);
		t.start();
	}
}

public class MyTest {
	public static void main(String[] args) {
		TalkingClock tc = new TalkingClock();
		tc.startPrint(true);	
	}
}

3. 匿名内部类
匿名内部类顾名思义就是没有名字的内部类,如果只需要使用这样一个类的对象,我们就可以使用匿名内部类来节省代码。如下所示,创建了一个实现ActionListener接口的类的新对象listener,类需要实现接口中的actionPerformed方法,这部分就放在{…}中定义。

类的构造器与类同名,匿名类没有名字,因此也没有构造器。

class TalkingClock{
	public void startPrint(final boolean beep) {
		ActionListener listener = new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				Date now  = new Date();
				System.out.println(now);
				if(beep) System.out.println("beep!");
			}
		};
		Timer t =  new Timer(1000, listener);
		t.start();
	}
}
public class MyTest2 {
	public static void main(String[] args) {
		TalkingClock tc = new TalkingClock();
		tc.startPrint(true);	
	}
}

4. 静态内部类
如果一个内部类不需要引用所在外部类的域时,可以将该内部类声明为static(注意,只有内部类才能被定义为static)。静态内部类除了不会创建一个隐式引用外,其他与普通内部类没有任何差别。

使用场景:当一个方法需要返回两个值时,可以考虑返回对象,此时就可以通过创建一个静态内部类,将返回值定义为其私有域,再在方法中返回该类的对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值