《Java编程思想》第四版之内部类学习——神奇而又实用

  版权声明:学习内容均为本人笔记,代码均为本人依据课本所写或改编,笔记均为个人心得或书中摘抄



引言:内部类,即将一个类的定义放在另一个类的定义内部。内部类与组合是完全不同的概念。内部类看似是一种代码的隐藏机制,其实,它能够了解外部类,并且与之通信,这为我们的编程提供了极大的方便。


1.1创建内部类

//190

public class Parcel2 {
	
	class Contents{
		private int i=11;
		public int value(){
			return i;
		}
	}
	
	class Destination{
		private String label;
		public Destination(String whereTo) {
			label=whereTo;
		}
		String readLabel(){
			return label;
		}
	}
	
	public Contents contents(){//外部类的方法,返回一个内部类对象
		return new Contents();
	}
	
	public void ship(String dest){
		Contents c=new Contents();
		Destination d=new Destination(dest);
		System.out.println(d.readLabel());
	}
	
	public static void main(String[] args){
		Parcel2 p=new Parcel2();
		p.ship("Tasmania");
		Parcel2 q=new Parcel2();
		Parcel2.Contents c=q.contents();//利用外部类的对象访问外部类方法,返回内部类的对象
	}
}/*Output:
Tasmania*/
从main中可以看出,我们从外部类的非静态方法中获取了内部类对象,所以必须指明这个对象的类型:OuterClassName.InnerClassName

1.2链接到外部类


从main中,使用内部类最重要的一点就是,生成的内部类对象可以与制造它的外部类对象进行通信,它可以访问外部类对象的所有成员和外围类的所有元素,不需要任何条件。
//192页

interface Selector{
	boolean end();
	Object current();
	void next();
}

public class Sequence {
	private Object[] items;
	private int next=0;
	public Sequence(int size){//固定数组大小
		items=new Object[size];
	}
	public void add(Object x){//在末尾添加新的Object
		if(next<items.length)
			items[next++]=x;
	}
	private class SequenceSelector implements Selector{//内部类实现Selector接口,迭代器设计模式
		private int i=0;
		public boolean end(){//检查序列是否到达末尾
			return i==items.length;
		}
		public Object current(){//访问当前对象
			return items[i];
		}
		public void next(){//移动下标到虾一位
			if(i<items.length)
				i++;
		}
	}
	public Selector selector(){//返回一个内部类
		return new SequenceSelector();
	}
	public static void main(String[] args){
		Sequence sequence=new Sequence(10);
		for(int i=0;i<10;i++)
			sequence.add(Integer.toString(i));//初始化外部类对象中的数组
		Selector selector=sequence.selector();//创建内部类对象,指向接口,接口调用实例的具体方法
		while(!selector.end()){
			System.out.print(selector.current()+" ");//输出外部类数组中元素
			selector.next();
		}
	}
}
/*Output:
1 2 3 4 5 6 7 8 9
*/
从内部类可以看到,内部类中使用了外部类的private类型的数组item,由此可以看出,外部类对象拥有创建它外部类对象的所有成员的访问权
原因:外部类创建内部类对象时,内部类对象会捕获一个指向外部类对象的引用,通过这个引用可以访问外部类的所有成员。


1.3使用.this和.new


生成外部类对象引用的方法:外部类的名字后面紧跟圆点和this,在编译期就会接受检查,没有运行开销
如果想让某个对象创建内部类对象,可以使用.new的语法来创建。
//193页

public class Parcel3 {
	class Contents{
		private int i=11;
		public int value(){
			return i;
		}
	}
	class Destination{
		private String label;
		public Destination(String whereTo) {
			label=whereTo;
		}
		String readLabel(){
			return label;
		}
	}
	public static void main(String[] args){
		Parcel3 p=new Parcel3();
		Parcel3.Contents c=p.new Contents();//使用了.new语法创建内部类对象
		Parcel3.Destination d=p.new Destination("Tasmania"); 
	}
}

1.4内部类与向上转型


内部类转型为基类或者为接口时,就获得了该基类或者接口的引用,此时,接口的实现完全不可见,内部类隐藏了实现的细节
//194页

interface Destination{
	String readLabel();
}
interface Contents{
	int value();
}
public class Parcel4 {
	private class PContents implements Contents{
		private int i=11;
		public int value(){
			return i;
		}
	}
	protected class PDestination implements Destination{
		private String label;
		public PDestination(String whereTo) {
			label=whereTo;
		}
		public String readLabel(){
			return label;
		}
	}
	public Destination destination(String s){//通过外部类方法创建内部类对象,并向上转型
		return new PDestination(s);
	}
	public Contents contents(){
		return new PContents();
	}
	public static void main(String[] args){
		Parcel4 p=new Parcel4();
		Destination c=p.destination("Tasmania");//向上转型为Destination接口
		Contents d=p.contents();
		//不能使用下面这种方法,因为PContents内部类为私有的
		//Parcel4.PContents pc=p.new PContents(); 
	}
}
我们可以看到外部类Parcel4中的内部类PContents是private类型的,除了Parcel4,无法访问它。
内部类PDestination是protected,也仅仅只有Parcel4及其子类和同一个包中的类才具有访问权限。所以客户端程序员访问这两个内部类是受到限制的,但是可以通过访问Parcel4中的destination()方法和contents()方法获取接口的引用,然而内部类具体的实现被隐藏,客户端程序员并不知道,也不需要知道,只需要调用接口中相应的方法就好了。


1.5在方法和作用域内的内部类


可以在一个方法或者在任意的作用域内定义内部类。
局部内部类
public class Parcel5{
	public Destination destination(String s){
		class PDestination implements Destination{//内部类在方法中
			private String label;
			private PDestination(String whereTo){
				label=whereTo;
			}
			public String readLabel(){
				return label;
			}
		}
		return new PDestination(s);//只有在方法作用域内才可以使用作用域内的内部类
	}
	public static void main(String[] args){
		Parcel5 p=new Parcel5();
		Destination d=p.destination("Tasmania");
	}
}
作用域内的类与其他类共同编译,但是只在作用域内可用,在其他作用域中使用相同的类名不会有命名冲突

1.6匿名内部类


匿名内部类在创建某个对象进行返回时,对该对象的类进行定义。类的定义和使用放到了一起。下面根据具体例子说明情况
//197页

interface Contents{
	 int value();
}

public class Parcel7 {
	public Contents contents(){
		return new Contents() {//匿名内部类,类的使用与定义结合到了一起
			private int i=1;
			public int value(){
				return i;
			}
		};
	}
	public static void main(String[] args){
		Parcel7 p=new Parcel7();
		Contents c=p.contents();
	}
}
程序中我们可以看到,在contents()方法的内部,在返回了一个Contents()引用的时候,插入了一个类的定义。这里实际的情况是,创建了一个继承自Contents的匿名类的对象,通过new表达式返回的时候,实际上已经向上转型为对Contents的引用了。
具体来说,就是下面的代码
interface Contents{
	int value();
}

public class Parcel7b {
	class MyContents implements Contents{//MyContents实现了Contents
		private int i=11;
		public int value(){
			return i;
		}
	}
	public Contents contents(){
		return new MyContents(); //contents()方法返回了一个MyContents对象,并且向上转型为Contents
	}
	public static void main(String[] args){
		Parcel7b p=new Parcel7b();
		Contents c=p.contents();
	}
}
上述匿名内部类使用了默认的构造器生成Contents,也可以使用有参数的构造器。

//197页

class Wrapping{
	private int i;
	public Wrapping(int x){
		i=x;
	}
	public int value(){
		return i;
	}
}

public class Parcel8 {
	public Wrapping wrapping(int x){
		return new Wrapping(x){//传递了适合基类构造器的参数
			public int value(){
				return super.value()*47;
			}
		};
	}
	public static void main(String[] args){
		Parcel8 p=new Parcel8();
		Wrapping w=p.wrapping(10);
		System.out.println(w.value());//Wrapping引用匹配到子类的方法
	}
}/*Output:
470*/
匿名内部类中可以看到,传入了一个适合基类构造器的参数。而且尽管Wrapping是一个具有具体实现的类,但是被导出类当作“接口”使用。

匿名内部类没有类名,没办法创建构造函数,那么如何进行初始化工作呢?
//199页

public class Parcel10 {
	public Destination destination(String dest,float price){
		return new Destination() {
			private int cost;
			{//带有实例初始化
				cost=Math.round(price);
				if(cost>100)
					System.out.println("Over budget!");
			}
			//下面这句话不能通过编译!
			//dest="newTasmania";
			private String label=dest;
			public String readLabel(){
				return label;
			}
		};
	}
	public static void main(String[] args){
		Parcel10 p=new Parcel10();
		Destination d=p.destination("Tasmania", 101.395F);
	}
}/*Output:
Over budget!*/
在实例初始化的内部,实现了构造器的行为——初始化,但是,你不能重载实例初始化方法, 所以你仅仅有一个这样的构造器。

注意:代码中,你看到了有一行不能通过编译,这是因为,在内部类使用的非final对象将会接受检查——它们不允许被修改。
《Java编程思想》这块,作者写的是内部类使用外部类对象必须要求是final类型的(作者使用的是JAVA8之前的版本),然而我使用的是JAVA8,JAVA8中,匿名内部类使用外部变量不再被强制要求用final修饰但是要求初始化后的值不能被修改,这是为何呢?

对于final类型来说:编译器编译后,final类型是常量,被存储到了常量池中,在匿名内部类中使用该变量的地方都被替换成了具体的常量值,关于外部类的变量的信息,内部类是不知道的。

对于非final类型来说:传入内部类的仅仅只是传值操作,所以在匿名内部类中改变这个值是无效的。如果在外部类中修改这个值,那么匿名内部类得到的参数值可能已经不是期望中的那个值。所以,在内部类使用外部类的变量时,不允许做任何修改才会避免所以问题。

JAVA8版本对于非final类型会进行检查,要求不允许修改。final变量自然不会被修改,也不会检查,JAVA8以前的版本要求必须是final变量才能给匿名内部类使用。

1.6.1再访工程方法

//201页

interface Game{
	boolean move();
}
interface GameFactory{
	Game getGame();
}

class Checkers implements Game{
	private Checkers(){}//构造器为private,不能直接创建对象
	private int moves=0;
	private static final int MOVES=3;
	public boolean move(){
		System.out.println("Checkers move "+moves);
		return ++moves!=MOVES;
	}
	public static GameFactory factory=new GameFactory() {//单一的工厂对象
		public Game getGame(){
			return new Checkers();
		}
	};
}

class Chess implements Game{
	private Chess(){}//构造器为private,不能直接创建对象
	private int moves=0;
	private static final int MOVES=4;
	public boolean move(){
		System.out.println("Chess move "+moves);
		return ++moves!=MOVES;
	}
	public static GameFactory factory=new GameFactory() {//单一的工厂对象
		public Game getGame(){
			return new Chess();
		}
	};
}

public class Games {
	public static void playGame(GameFactory factory){//不同的工厂对象生成不同的具体类的对象
		Game s=factory.getGame();//GameFactory接口调用相应的getGame()方法返回不同的Game对象后向上转型
		while(s.move());//Game接口自动找到相应实现类的move()方法
	}
	public static void main(String[] args){
		playGame(Checkers.factory);//传入Checkers类中的中工厂对象
		playGame(Chess.factory);//闯入Chess类中的工厂对象
	}
}/*Output:
Checkers move 0
Checkers move 1
Checkers move 2
Chess move 0
Chess move 1
Chess move 2
Chess move 3*/
可以看到,Chess和Checker类中的构造器均为private类型的,所以不能直接创建该类的对象。但是,我们可以通过这两个类中的静态(单例)工厂对象来创建属于本类的对象。

1.7嵌套类


如果不想使内部类对象与外部类对象相互联系,那么可以将内部类声明为static,这就是嵌套类。
我们知道,非static内部类必须通过外部类对象创建并且获得一个该外部类对象的引用。然而,对于static类型的内部类:
1、创建静态内部类对象,不需要外部类的对象
2、静态内部类中不能使用外部类的非静态成员(因为没有外部类对象的引用)
3、静态内部类中可以创建staic类中的数据和字段(普通内部类不可以,因为它通过外部类对象创建,不属于类成员)


在main中没有使用外部类对象就创建了内部类对象,因为创建的对象的方法是static的,而且内部类也是static的。

//202页


public class Parcel11 {
	public static class ParcelContents implements Contents{//静态内部类
		private static int i=11;//静态内部类中的静态变量
		public int value(){
			return i;
		}
	}
	public static ParcelContents contents(){
		return new ParcelContents();
	}
	public static void main(String[] args){
		Contents c=contents();
	}

1.7.1接口内部的类


//202页

public interface  ClassInInterface {
	void howdy();
	static class Test implements ClassInInterface{
		public void howdy(){
			System.out.println("Howdy");
		}
	}
	public static void main(String[] args){
		new Test().howdy();
	}
}/*Output:
Howdy
*/

正常情况下,接口内部内不能放置任何代码,但是嵌套类可以作为接口的一部分。

接口中类自动地是public和static的。类是static了,仅仅只是将该类置于接口的命名空间里,不违反接口的规则。同时,该嵌套类可以实现它命名空间下的接口。


1.8为什么需要内部类

内部类使得多重继承的解决方案变得完整,一个外部类可以实现多个接口。然而,只有继承一个抽象的类或具体的类,此时如果想继承多个抽象的类或具体的类,那么内部类可以解决多重继承中的问题。

//205页

class D{}
abstract class E{}
class Z extends D{//外部了继承了D类
	E makeE(){//
		return new E(){};//匿名内部类继承了E类,与外部类实现了多重继承
	}
}

public class MultiImplementation {
	static void takesD(D d){}//接受D类及其子类的引用
	static void takesE(E e){}//接受E类及其子类的引用
	public static void main(String[] args){
		Z z=new Z();
		takesD(z);
		takesE(z.makeE());
	}
}

Z类实现了多重继承,外部类继承了D类,匿名内部类继承了E类。


1.8.1闭包与回调


闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。内部类是面向对象的闭包,它不仅包含了外部类对象的信息,还自动拥有一个指向该外部类对象的引用,在此作用域下,内部类有权操作所有的成员,包括private成员。


Java语言中没有包括指针,通过内部类提供的闭包功能实现了回调功能。


//206页

interface Incrementable{//Incrementable中含有一个increment()方法
	void increment();
}

class Callee1 implements Incrementable{//Callee1类实现了Incrementable接口
	private int i=0;
	public void increment(){
		i++;
		System.out.println(i);
	}
}

class MyIncrement{//MyIncrement类中创建了自己的increment()方法
	public void increment(){
		System.out.println("Other operation");
	}
	static void f(MyIncrement mi){
		mi.increment();
	}
}

class Callee2 extends MyIncrement{//Callee2类继承了MyIncrement类,拥有了不同的increment()方法
	private int i=0;
	public void increment(){
		super.increment();
		i++;
		System.out.println(i);
	}
	private class Closure implements Incrementable{//内部类实现了Incrementable接口,拥实现了该接口中的increment()方法
		public void increment(){
			Callee2.this.increment();
		}
	}
	Incrementable getCallbackReference(){//产生内部类Closure的对象
		return new Closure();
	}
}

class Caller{
	private Incrementable callbackReference;//存入Incrementable引用
	Caller(Incrementable cbh){//构造方法中,要求接受Incrementable类型的引用
		callbackReference=cbh;
	}
	void go(){
		callbackReference.increment();//Incrementable引用自动调用相应的increment()方法
	}
}

public class Callbacks {
	public static void main(String[] args){
		Callee1 c1=new Callee1();
		Callee2 c2=new Callee2();
		MyIncrement.f(c2);//调用自己MyIncrement中的方法
		Caller caller1=new Caller(c1);//Caller构造函数要求Incrementable类型的接口
		Caller caller2=new Caller(c2.getCallbackReference());
		caller1.go();
		caller1.go();
		caller2.go();
		caller2.go();
	}
}/*Output:
Other operation
1
1
2
Other operation
2
Other operation
3
*/

从上述案例中可以看到Callee1作为外部类实现接口和Callee2中Closure作为内部类实现接口的区别。Callee2类继承了MyIncrement类,该类中的Increment()方法与接口中的不同,此时想用接口中的该方法,就应该用内部类实现接口,而不是用外部类实现接口,因为那样会覆盖继承自MyIncrement类中的方法。


回调:在Callee2类中,通过外部类对象调用getCallbackReferemce()方法获得了内部类Closure类的对象,这个内部类对象提供了一个返回Callee2的钩子。在main中创建了Caller类的对象caller2,并且存入了一个Incrementable类型的引用,该引用指向了Callee2的对象c2创建的内部类对象Closure,并且通过调用caller2.go()方法,调用了该内部类对象Closure中的increment()方法。
这说明无论谁获得IncrementTable的引用,都只能调用increment()方法。


1.8.2内部类与控制框架

应用程序框架就是被设计用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案。
控制框架是一类特殊的应用程序框架,它用来相应事件的需求。
下面介绍一个具体的案例分析:一个控制框架实现温室的操作:控制灯光、水、温度调节器的开关,以及响铃的重新启动系统,每个行为都是完全不同的。
首先创建一个抽象的事件类

//208页

public abstract class Event{
	private long eventTime;
	protected final long delayTime;//延迟时间
	public Event(long delayTime){
		this.delayTime=delayTime;
		start();//创建Event对象时,调用start()方法
	}
	public void start(){//获取当前时间,事件每次重新启动以后,都能运行该方法,重新获取当前时间
		eventTime=System.nanoTime()+delayTime;
	}
	public boolean ready(){//判断何时可以运行action()方法,此处是基于时间判断
		return System.nanoTime()>=eventTime;
	}
	public abstract void action();//action()方法在子类中具体时间,不同的事件有不同的操作
}

Event类是一个抽象类,该类的构造函数初始化了事件的延迟时间以及当前时间。其中start()方法可以在时间重新启动时再次调用,再次确定当前时间,

ready()方法基于时间来控制动作是否发生,action()方法的具体操作依据事件的不同,应该在不同的子类有不同的实现。

其次,创建一个管理并触发事件的实际控制框架。

//209页
import java.util.*;

public class Controller{
	private List<Event> eventList=new ArrayList<Event>();//List用于存放各个Event事件类
	public void addEvent(Event c){//向List中添加事件
		eventList.add(c);
	}
	public void run(){//时间执行
		while(eventList.size()>0){//首先判断List容器中是否还有容器
			for(Event e:new ArrayList<Event>(eventList))//依次遍历容器中的所有事件
				if(e.ready()){//基于时间判断事件是否执行
					System.out.println(e);//输出对应的事件(每个事件中都有toString()方法显示该事件信息)
					e.action();//执行对应的事件
					eventList.remove(e);//从List列表中移除该事件
				}
		}
	}
}

控制框架中的容器List存储了不同事件,run方法通过遍历这些事件,使得这些事件输出信息,并执行相应的action()操作,最后再从List容器中移除


通过以上两个类,我们创建了事件和控制事件操作的控制框架,但是我们并不清除Event到底做什么,也就是说并不知道事件具体的操作是在什么。控制框架是不变的,它存入不同的事件,对它们进行操作、控制。然而,事件是变的,我们有各种事件,每种事件也会有不同的操作。


那么怎么简便实现这样的操作呢?前边提过内部类实现多重继承的方法:
我们通过创建一个外部类继承控制框架,使得控制框架只有一个不会发生改变。不同的事件创建不同内部类使其具有不同的属性、操作、方法。而且该内部类可以轻而易举的使用外部类的对象,这使得我们的程序更加简便易行。

//210页

public class GreenhouseControls extends Controller{
	private boolean light=false;
	public class LightOn extends Event{//开灯,内部类继承了Event事件,创建时调用Event事件初始化
		public LightOn(long dealyTime){
	    	super(dealyTime);
	    }
		public void action(){//执行开灯操作
			light=true;
		}
		public String toString(){//输出开灯信息
			return "Light is on";
		}
	}
	public class LightOff extends Event{//关灯
		public LightOff(long dealyTime){
	    	super(dealyTime);
	    }
		public void action(){//执行关灯操作
			light=false;
		}
		public String toString(){//输出关灯信息
			return "Light is Off";
		}
	}
	private boolean water=false;
	public class WaterOn extends Event{//开水龙头
		public WaterOn(long dealyTime){
	    	super(dealyTime);
	    }
		public void action(){//执行开水头操作
			water=true;
		}
		public String toString(){
			return "Greenhouse water is on";
		}
	}
	public class WaterOff extends Event{//关水龙头
		public WaterOff(long dealyTime){
	    	super(dealyTime);
	    }
		public void action(){//执行关水龙头操作
			water=false;
		}
		public String toString(){
			return "Greenhouse water is off";
		}
	}
	private String thermostat="Day";
	public class ThermostatNight extends Event{//夜晚
		public ThermostatNight(long delayTime){
			super(delayTime);
		}
		public void action(){//执行夜晚操作
			thermostat="Night";
		}
		public String toString(){
			return "ThermostatDay on night setting";
		}
	}
	public class ThermostatDay extends Event{//白天
		public ThermostatDay(long delayTime){
			super(delayTime);
		}
		public void action(){//执行白天操作
			thermostat="Day";
		}
		public String toString(){
			return "ThermostatDay on day setting";
		}
	}
	public class Bell extends Event{//响铃
		public Bell(long delayTime){
			super(delayTime);
		}
		public void action(){
			addEvent(new Bell(delayTime));
		}
		public String toString(){//执行响铃操作
			return "Bing!";
		}
	}
	public class Restart extends Event{//重新启动系统
		private Event[] eventList;		
		public Restart(long delayTime,Event[] eventList){
			super(delayTime);
			this.eventList=eventList;
			for(Event e:eventList)
				addEvent(e);
		}
		public void action(){//执行重新启动系统操作:将本对象Event数组中对象重新添加到控制框架中
			for(Event e:eventList){
				e.start();//每个事件重新启动,重新获取当前时间
				addEvent(e);//每个事件添加到控制事件中
			}
			this.start();//重新启动系统事件也要重新获取当前时间
			addEvent(this);//重新启动系统事件添加至控制事件中
		}
		public String toString(){
			return "Restarting system";
		}
	}
	public static class Terminate extends Event{//关闭系统
		public Terminate(long delayTime){
			super(delayTime);
		}
		public void action(){//关闭系统操作
			System.exit(0);
		}
		public String toString(){
			return "Terminating";
		}
	}
}

可以看出,GreenhouseControls继承了一个控制框架,并且该类中有不同的内部类,它们代表了不同的事件。其中light、water和thermostat是关于灯、水、白天黑夜的事件,它们是基本的事件,通过添加相应的操作完成事件的发生。


Bell和Restart比较特殊


Bell控制响铃,在它的action()中,它每次都会重新添加一个响铃事件,所以过一会儿就会发生一次响铃事件。


Restart类中存入了一个EventList数组,它其中包含了设置的不同的Event事件,一旦执行Restart重新启动系统事件,即调用了该类的action()方法,那么这个EventList数组中的所有事件将被重新添加到控制框架中,重新被执行相应的操作(当然,添加事件之前,会从新更新它们各自事件的当前时间),最后,重新启动的这个事件也要被添加到控制框架中,不要忘记了,它自身也是一个事件!


下面,我们创建一个类用来添加各种不同的事件,并执行各种不同的操作。

//211页


public class GreenhouseControler{
	public static void main(String[] args){
		GreenhouseControls gc=new GreenhouseControls();//创建控制温室的对象
		gc.addEvent(gc.new Bell(900));//首先向框架中添加响铃对象
		Event[] eventList={//一个包含了各种事件的Event数组
				gc.new ThermostatNight(0),//外部类创建了不同的事件,这些内部类事件具有外部类的访问权限
				gc.new LightOn(200),
				gc.new LightOff(400),
				gc.new WaterOn(600),
				gc.new WaterOff(800),
				gc.new ThermostatDay(1400),
		};
		//第一步,重新启动系统的构造函数向框架中添加evenList中所有的事件
		//第二步,添加重新启动系统这个事件到框架中
		gc.addEvent(gc.new Restart(2000,eventList));
		if(args.length==1)//如果main方法中传入的String大小为1则添加关闭系统事件到末尾
			gc.addEvent(
					new GreenhouseControls.Terminate(
						new Integer(args[0])));
		gc.run();//gc对象执行控制框架的run方法,开始执行相应的操作
	}
}/*Bing!
ThermostatDay on night setting
Light is on
Light is Off
Greenhouse water is on
Greenhouse water is off
ThermostatDay on day setting
Restarting system
Terminating
*/

注意:main中添加重新启动系统事件时的顺序。

首先Restart类进行初始化,此时会初始化重新启动事件对象,

然后将eventList数组中的所有事件添加到控制框架中,此时初始化完毕。

然后回到main中将重新启动系统事件添加到控制框架中,这就使我们明白了输出的顺序。重新启动系统执行action()操作时,会将eventList中所有的事件重新获取当前时间并添加到控制框架中,最后再将自己也添加到控制框架中,不要忘记,重新启动系统也是一个Event事件!


备注:使用上述几个类,注意包访问权,main中设置参数以免循环调用!


1.9内部类的继承


如何继承一个内部类?根据前面的内容,内部类中会有一个捕获自创建它外部类对象的引用,如果继承该内部类,这个外部类的引用也要被连接初始化,内部类中也不存在可连接的默认对象。


//212页

class WithInner{
	class Inner{}
}

public class InheritInner extends WithInner.Inner{
	//public InheritInner(){} 不能使用该构造器,因为没有获取父类外部类的引用
	public InheritInner(WithInner wi) {
		wi.super();//通过父类外部类的引用初始化继承的内部类
	}
	public static void main(String[] args){
		WithInner wi=new WithInner();
		InheritInner i1=new InheritInner(wi);
	}
}

在继承内部类的类中,必须获取一个创建内部类的外部类对象的引用,通过这个引用调用内部类父类进行初始化操作。
注意:这里Inner内部类我们使用的是一个默认构造函数,是没有参数的。如果有参数又该如何呢:

class WithInner{
	class Inner{
		public Inner(int i) {}//带有参数的构造函数
	}
}

public class InheritInner extends WithInner.Inner{
	//public InheritInner(){} 不能使用该构造器,因为没有获取父类外部类的引用
	public InheritInner(WithInner wi) {
		wi.super(1);//调用父类构造器时,需要一个符合父类构造器的参数
	}
	public static void main(String[] args){
		WithInner wi=new WithInner();
		InheritInner i1=new InheritInner(wi);
	}
}

此时内部类Inner中有一个带有参数的构造器,此时我们在继承内部类的InheritInner类中,调用父类时应该添加相应的参数


1.10内部类可以被覆盖吗


如果有一个类,并且该类有一个内部类,那么创建一个新类继承该外部类,然后在新类中重新创建此内部类会覆盖父类中的内部类吗?

//212页

class Egg{
	private Yolk y;
	protected class Yolk{
		public Yolk(){
			System.out.println("Egg.Yolk()");
		}
	}
	public Egg(){
		System.out.println("New Egg()");
		y=new Yolk();
	}
}

public class BigEgg extends Egg{//继承了一个外部类
	public class Yolk{//重写继承外部类中的内部类
		public Yolk(){
			System.out.println("BigEgg.Yolk()");
		}
	}
	public static void main(String[] args){
		new BigEgg();
	}
}/*Output:
New Egg()
Egg.Yolk()*/

创建自身对象时,会调用父类构造器,然后父类构造器中顶一起了内部类,然后输出的结果并不是子类中的内部类,也就是说父类并没有获取子类内部类的对象,也就不存在转型。两个内部类没有什么关系,它们各自在自己的命名空间内。

当BigEgg中的内部类明确继承了BigEgg父类Egg外部类中的内部类Yolk类。

//213页

class Egg2{
	private Yolk y=new Yolk();//先与构造器前初始化,调用内部类构造器
	protected class Yolk{
		public Yolk(){//内部类构造函数
			System.out.println("Egg2.Yolk()");
		}
		public void f() {//内部类f()方法
			System.out.println("Egg2.Yolk().f()");
		}
	}
	public Egg2(){//初始化之前,先初始化字段
		System.out.println("New Egg2()");
	}
	public void insertYolk(Yolk yy){//获取一个Yolk类或子类的引用
		y=yy;
	}
	public void g(){
		y.f();//调用y引用指向的类的f()方法
	}
}

public class BigEgg2 extends Egg2{
	public class Yolk extends Egg2.Yolk{
		public Yolk(){
			System.out.println("BigEgg2.Yolk()");
		}
		public void f(){
			System.out.println("BigEgg2.Yolk().f()");
		}
	}
	public BigEgg2(){
		insertYolk(new Yolk());//首先初始化父类BigEgg,然后调用本类中的Yolk类的构造函数,然后调用inserYolk()方法
	}
	public static void main(String[] args){
		Egg2 e2=new BigEgg2();//调用BigEgg2()构造器,向上转型为Egg2类
		e2.g();
	}
}/*Output:
Egg2.Yolk()
New Egg2()
Egg2.Yolk()
BigEgg2.Yolk()
BigEgg2.Yolk().f()
*/

BigEgg2.Yolk2明确的继承了Egg2类中的内部类Yolk,并且覆盖了f()方法。insertYolk()方法,将子类BigEgg2中的内部类对象转型为父类Egg中的内部类,当g()调用f()方法时,覆盖的f()方法被执行。


可能很多人第一次看不懂这个输出的结果,我分析了程序的执行步骤


1、main()方法中new BigEgg2()调用了BigEgg2()构造器,由于BigEgg2()继承自Egg2,所以首先初始化父类Egg2类
2、父类Egg2初始化时,首先对字段进行初始化,所以初始化了Yolk对象的y,new Yolk()时就当用了内部类Yolk的构造函数
3、执行Egg2.Yolk的构造函数,第一次输出产生“Egg2.Yolk()”
4、执行Egg2的构造函数,第二次输出产生“New Egg2()”
5、返回到子类BigEgg2中,执行insertYolk(new Yolk(),首先执行方法中的new Yolk(),调用BigEgg2.Yolk的构造函数
6、由于BigEgg2.Yolk继承自Egg2.Yolk,所以先调用Egg.Yolk中的构造函数,第三次输出产生”Egg2.Yolk()“
7、返回到子类BigEgg2.Yolk中,执行构造函数,第四次输出产生”BigEgg2.Yolk()“
8、此时BigEgg2的构造全部结束,返回到main中向上转型为Egg2
9、执行e2.g()方法,调用y.f()方法,由于Egg2中Egg2.Yolk的引用y存入的是子类BigEgg2.Yolk的引用,所以会调用BigEgg2.Yolk中的f()方法
10、执行BigEgg2.Yolk中的f()方法,第五次输出产生”BigEgg2.Yolk().f()“


1.11局部内部类


可以在类中创建匿名内部类,静态内部类,普通内部类,也可以在代码块里创建内部类,典型的方式就是在一个方法体的内部里面创建。局部内部类不能有访问说明符,因为它不是外部类的一部分,但是它可以方位当前代码块内的常量(Java8中传入到内部类的值不一定是final类型的,只要初始化后不被修改即可),以及此外围类的所有成员。
创建局部内部类和创建匿名内部类比较

//214页

interface Counter{
	int next();//用于返回序列中的下一个值
}

public class LocalInnerClass {
	private int count=0;
	Counter getCounter(final String name){
		class LocalCounter implements Counter{//局部内部类,定义在方法中
			public LocalCounter(){
				System.out.println("LocalCounter()");
			}
			public int next(){
				System.out.print(name);
				return count++;
			}
		}
		return new LocalCounter();
	}
	Counter getCounter2(final String name){
		return new Counter(){//匿名内部类
			{
				System.out.println("Counter()");
			}
			public int next(){
				System.out.print(name);
				return count++;
			}
		};
	}
	public static void main(String[] args){
		LocalInnerClass lic=new LocalInnerClass();//创建外部类对象
		Counter c1=lic.getCounter("Local inner");//通过外部类对象调用方法创建局部内部类
		Counter c2=lic.getCounter2("Anonymous inner");//通过外部类对象创建匿名内部类
		for(int i=0;i<5;i++)
			System.out.println(c1.next());
		for(int i=0;i<5;i++)
			System.out.println(c2.next());
	}
}/*Output:
LocalCounter()
Counter()
Local inner0
Local inner1
Local inner2
Local inner3
Local inner4
Anonymous inner5
Anonymous inner6
Anonymous inner7
Anonymous inner8
Anonymous inner9*/

分别创建了局部内部类LocalCounter类和匿名内部类实现了Counter接口,该接口用于返回序列中的下一个值,这两个内部类具有了相同行为和能力。

局部内部类和匿名内部类的区别:局部内部类的名字在方法外是不可见的,局部内部类可以重载构造器,而匿名内部类只能用于实例初始化,没有构造器。

所以当我们需要不止一个内部类的对象时,应该选择局部内部类而不是匿名内部类。


1.12内部类标识符


由于每个类都会产生一个.class文件,其中包含了如何创建该类型的对象的全部信息(此信息产生一个”meta-class“,叫做Class对象),内部类也会生成一个.class文件以包含它们的Class对象信息。这些类的命名有严格的规则:外围类的名字,加上”$“,再加上内部类的名字。例如,LocalInnerClass.java生成的.class文件包括:


Counter,class
LocalInnerClass$1.class
LocalInnerClass$1LocalCounter.class
LocalInnerClass.class

如果是匿名内部类,编译器会简单的产生一个数字作为其标识符。如果内部类嵌套在别的内部类中,只需将它们的名字加在其外部类标识符与$的后面。


1.13总结


在内部类章节中,我们了解到了一个普通内部类必须要通过外部类对象来创建,并且它会获得一个外部类对象的引用,这样它就能访问外部类对象中的所有成员了。然而对于一个嵌套类也就是静态内部类来说,它不需要外部类对象就能创建对象,因为它是静态的属于类本身,不过这也限制了它的操作,静态内部类不能访问非静态的外部类对象。

除此之外,普通的内部类也不能包含static的字段和数据,我把它想象成属于外部类对象的某个”方法“,显然,普通内部类只能通过外部类对象创建,static数据和字段不属于对象,所以会同类一起编译存储在静态区,那么这个静态区属于哪个类其实是未知的。


在本章中,我们也发现了,JAVA相对本书版本已经更新了,在JAVA8的版本中,内部类接收的参数已经不必必须声明为final常量了(现在你也可以这么做),但是如果不声明为final,编译器要求这个值初始化后不能被修改,否则会报错,这是因为在Java中的传值操作,如果这个值在外部类中被修改,那么内部类得到的数值可能已经发生改变,得到了不是期望的值,这会带来很大的问题。

作用域中的类与其它类共同编译,但只在作用域内可用,在其他作用于使用相同的域名不会有命名冲突。同时,如果一个类继承了一个外部类,并且创建了相同的内部类时,其实并不会被覆盖,只有当这个继承的类创建了继承自父类中内部类的一个类时,才会出现覆盖的可能。

Java内部类完美的实现了多重继承,虽然我们可以通过实现多个接口来实现多重继承,但是如果拥有的是抽象的类或者具体的类时,那只能用内部类才能实现多重继承


通过学习,我们也发现了内部类强大的功能。我们编写了控制温室的案例,用一个外部类继承控制框架,用多个内部类来继承各种事件,我们将控制与行为进行了分离,不仅利于代码的维护,还为开发提供了极大的方便。







温馨提示:如果有什么错误,或者有什么意见(对于排版、知识块内容选取、讲述方式等),烦请评论或私聊,也许您的一个建议和一点指点能使我更加完善自己,也能让您感受到帮助他人的乐趣。祝您的编程之路一帆风顺!


如有交流请加微信:备注CSDN博友


  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值