版权声明:学习内容均为本人笔记,代码均为本人依据课本所写或改编,笔记均为个人心得或书中摘抄
引言:内部类,即将一个类的定义放在另一个类的定义内部。内部类与组合是完全不同的概念。内部类看似是一种代码的隐藏机制,其实,它能够了解外部类,并且与之通信,这为我们的编程提供了极大的方便。
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*/
1.2链接到外部类
//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
*/
1.3使用.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();
}
}
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();
}
}
//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!*/
在实例初始化的内部,实现了构造器的行为——初始化,但是,你不能重载实例初始化方法, 所以你仅仅有一个这样的构造器。
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内部类完美的实现了多重继承,虽然我们可以通过实现多个接口来实现多重继承,但是如果拥有的是抽象的类或者具体的类时,那只能用内部类才能实现多重继承。
通过学习,我们也发现了内部类强大的功能。我们编写了控制温室的案例,用一个外部类继承控制框架,用多个内部类来继承各种事件,我们将控制与行为进行了分离,不仅利于代码的维护,还为开发提供了极大的方便。
温馨提示:如果有什么错误,或者有什么意见(对于排版、知识块内容选取、讲述方式等),烦请评论或私聊,也许您的一个建议和一点指点能使我更加完善自己,也能让您感受到帮助他人的乐趣。祝您的编程之路一帆风顺!