1. 嵌套类
如果不需要内部类与外围类对象之间有联系,那么可以将内部类声明为static–这就是嵌套类。
当内部类是static时就意味着:
1)要创建嵌套类的对象,并不需要外围类对象。
2)不能从嵌套类的对象中访问非静态的外围类对象。
3)普通内部类不能有static数据和字段。
public class parcel11 {
private static class ParcelContents implements Contents{
private int i = 11;
public int value() {return i;}
}
protected static class ParcelDestination implements Destination{
private String label;
private ParcelDestination(String whereto){label = whereto;}
@Override
public String ReadLable() { return label;}
public static void f(){}
static int x = 10;
static class AnotherLevel{
public static void f(){}
static int x = 10;
}
}
public static Destination destination(){return new ParcelDestination("java");}
public static Contents constents(){return new ParcelContents();}
public static void main(String[] args) {
Contents c = constents();
Destination d = destination();
}
}
在main函数中没有定义parcel11对象的必要,而是使用static方法返回Contents和Destination对象的引用。嵌套类不能使用this来连接到外部类的引用,这样这个内部类就像一个static方法。
1.1 接口内部的类
一般在接口中不能放置任何代码,如果嵌套类在接口的内部会自动转换为public和static。只是将嵌套类放置接口的命名空间内,所以不违反接口的规则。甚至可以在内部类中实现其外围接口。如:
public interface ClassInInterface {
void howdy();
class Test implements ClassInInterface{
@Override
public void howdy() {
// TODO Auto-generated method stub
System.out.println("java");
}
public static void main(String[] args){
new Test().howdy();
}
}
}
如果你想要创建某些公共代码,使得他能被某个接口的所有不同实现所公用,那么使用内部类就很方便。
嵌套类还可以用来测试代码
public class TestBed {
public void f(){System.out.println("f()");}
public static class Tester{
public static void main(String[] args) {
// TODO Auto-generated method stub
TestBed t = new TestBed();
t.f();
}
}
}
//output:
f()
这样会生成一个独立的类TestBed$Tester,发布这个程序的时候删除这个类就行了。
1.2 从多层嵌套类中访问外部类的成员
一个内部类嵌套多少层并不重要,他能透明的访问所有它所嵌入的外围类的成员。
class MNA{
private void f(){}
class A{
private void g(){}
public class B{
void h(){
g();
f();
}
}
}
}
public class MultiNestAccess {
public static void main(String[] args) {
// TODO Auto-generated method stub
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
2.为什么需要内部类
内部类最吸引人的原因是:每个内部类都能独立的继承一个接口的实现,所以无论外围类是否已经继承某个接口的实现,对内部类没有影响。
内部类允许继承多个没接口类型(类或者抽象类)
interface A{}
interface B{}
class X implements A,B{}
class Y implements A{
B getB(){
return new B(){};
}
}
public class MultiInterface {
static void taskA(A a){}
static void taskB(B b){}
public static void main(String[] args) {
// TODO Auto-generated method stub
X x = new X();
Y y = new Y();
taskA(x);
taskA(y);
taskB(x);
taskB(y.getB());
}
}
这里看到了使用单一类和使用内部类的两种方式,看上去并没有什么不同,都能实现。
class D{}
abstract class E{}
class Z extends D{
E getE(){return new E(){};}
}
public class MultiImplementation {
static void tasksD(D d){}
static void tasksE(E d){}
public static void main(String[] args) {
// TODO Auto-generated method stub
Z z = new Z();
tasksD(z);
tasksE(z.getE());
}
}
这样就实现了多继承。
使用内部类还可获得其他一些特性:
1)内部类可有有多个实例,每个实例都有自己的状态信息,与外围类对象的信息相独立。
2)在单个外围类中,可以让内部类以不同的方式实现同一个接口,或继承同一个类。稍后介绍。
3)创建内部类的对象时刻并不需要外围类对象的创建。
4)内部类没有“is-a”关系,他是独立的实体。
2.1 闭包与回调
闭包:是一个可以调用的对象,它记录了一些信息,这些信息来源于创建它的作用域。内部类就是面向对象的闭包,它包含外围了对象的信息和对外围类对象的应用,内部类有权操作所以成员包括private成员。
通过回调,对象能携带一些信息,这些信息允许在稍后的某个时刻调用初始的对象。
闭包示例:
package inner_class;
import static net.lijun.util.Print.*;
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
interface Incrementable{
void increment();
}
//这里只是实现了Incrementable
class Callee1 implements Incrementable{
private int i = 0;
public void increment() {
i++;
println(i);
}
}
class MyIncrement{
public void increment(){println("Other operation");}
static void f(MyIncrement mi){mi.increment();}
}
//如果你的类需要实现Incrementable接口就必须使用内部类
class Callee2 extends MyIncrement{
private int i = 0;
public void increment(){
super.increment();
i++;
println(i);
}
private class Closure implements Incrementable{
public void increment() {
Callee2.this.increment();
}
}
Incrementable getCallbackReference(){return new Closure();}
}
class Caller{
private Incrementable callbackReference;
Caller(Incrementable cbh){callbackReference = cbh;}
void go(){callbackReference.increment();}
}
public class Callbacks {
public static void main(String[] args) {
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}
这个例子解释了外围类实现一个接口与内部类实现一个接口的区别,Calee1是简单的解决方式。Calee2继承自‘MyIncrement’后者有个完全不同的’increment’,并且与Incrementable接口期望的方法increment完全不相干,所以Calee2继承了MyIncrement就不能为了Incrementable的用途而覆盖了increment方法,于是只能用内部类独立的实现Incrementable。
内部类Closure实现了Incrementable,以提供返回Callee2的一个钩子。
Caller的构造器需要一个Incrementable的应用作为参数,然后在以后的某个时刻,Caller可以回调Calee类。
2.2内部类与控制框架
应用程序框架是被设计用来解决某类特定问题的一个类或者一组类。
控制框架是特殊的应用程序框架,它用来解决相应事件的需求。在GUI中用的比较多。
首先接口描述了要控制的事件,因为默认的行为是基于时间去执行控制的,所以使用抽象类代替接口。
例:
public abstract class Event {
private long eventTime;
protected final long delayTime;
public Event(long delayTime){
this.delayTime = delayTime;
start();
}
public void start(){
eventTime = System.nanoTime()+delayTime;
}
public boolean ready(){
return System.nanoTime()>=eventTime;
}
public abstract void action();
}
当运行Event时随即运行start()方法,那么就可以捕获当前时间加上延时时间,生成触发时间,
ready()告诉你何时可以运行action()方法,
下面的文件包含了一个用来管理并触发事件的实际控制框架。Event对象被保存在List中.
import java.util.*;
public class Contorller {
private List<Event> eventlist = new ArrayList<Event>();
public void addEvent(Event e){eventlist.add(e);}
public void run(){
while(eventlist.size()> 0){
for(Event e:new ArrayList<Event>(eventlist)){
if(e.ready()){
System.out.println(e);
e.action();
eventlist.remove(e);
}
}
}
}
}
run()方法循环遍历eventlist寻找就绪的ready,要运行的对象。对每一个就绪的事件,使用toString打印对象,调用其action方法,然后冲队列中移除。
到现在你并不知道Event的action方法是什么。这就是这个设计的关键所在。使变化的事物与不变的事物分离开来。各种不同的Event所具有的不同行为,而你通过创建Event表现这些不同的行为。
这就是内部类要做的事情:
1)控制框架的完整实现是由单个类创建的,从而使实现的细节被封闭了起来。内部类用来表示解决问题所必须的各种不同的action()。
2)内部类可以访问外围类的任意成员,所以可以避免这种实现变得笨拙。
下面是一个温室系统的例子:
使用内部类可以在单一的类里面产生对同一基类Event的多种导出版本。
package inner_class;
public class GreenhouseControls extends Contorller {
private boolean light = false;
public class LightOn extends Event{
public LightOn(long delayTime) {
super(delayTime);
}
@Override
public void action() {light = true;}
public String toString(){return "Light is on";}
}
public class LightOff extends Event{
public LightOff(long delayTime) {super(delayTime);}
@Override
public void action() {light = false;}
public String toString(){return "Light is off";}
}
private boolean water = false;
public class WaterOn extends Event {
public WaterOn(long delayTime) { super(delayTime); }
public void action() {
// Put hardware control code here.
water = true;
}
public String toString() {
return "Greenhouse water is on";
}
}
public class WaterOff extends Event {
public WaterOff(long delayTime) { super(delayTime); }
public void action() {
// Put hardware control code here.
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() {
// Put hardware control code here.
thermostat = "Night";
}
public String toString() {
return "Thermostat on night setting";
}
}
public class ThermostatDay extends Event {
public ThermostatDay(long delayTime) {
super(delayTime);
}
public void action() {
// Put hardware control code here.
thermostat = "Day";
}
public String toString() {
return "Thermostat 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() {
for(Event e : eventList) {
e.start(); // Rerun each event
addEvent(e);
}
start(); // Rerun this Event
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的一个对象,并添加各种不同的Event对象来配置该系统。
public class GreenhouseControl {
public static void main(String[] args) {
GreenhouseControls gc = new GreenhouseControls();
gc.addEvent(gc.new Bell(900));
Event[] eventList = {
gc.new ThermostatNight(0),
gc.new LightOn(200),
gc.new LightOff(400),
gc.new WaterOn(600),
gc.new WaterOff(800),
gc.new ThermostatDay(1400)
};
gc.addEvent(gc.new Restart(2000, eventList));
if(args.length == 1)
gc.addEvent(new GreenhouseControls.Terminate(new Integer(args[0])));
gc.run();
}
}
/* Output:
Bing!
Thermostat on night setting
Light is on
Light is off
Greenhouse water is on
Greenhouse water is off
Thermostat on day setting
Restarting system
Terminating
3.内部类的继承
因为内部类的构造器必须链接到其指向外围类的应用,所以在继承内部类是事情会变得复杂。问题在于那个指向外围类的“秘密”应用必须被初始化。而在导出类中不再存在可链接的默认对象。解决这个问题需要用到特殊的语法来说清楚他们之间的关联。
class WithInner{
class Inner{}
}
public class InheritInner extends WithInner.Inner{
//InheritInner( ){}不能被编译
InheritInner(WithInner wi){
wi.super();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
可以看到,InheritInner 只继承内部类,不是外围类。但是要生成构造器是默认的构造器不好用。而且不能只传一个外围类对象的应用,必须在构造器中使用encolsingClassReference.super();这样才提供了必要的引用,程序才能编译通过。
4.内部类可以被覆盖吗
覆盖内部类就好像内部类是外围类的一个方法,其实并不起什么作用。
import static net.lijun.util.Print.*;
class Egg{
private Yolk y;
protected class Yolk{
public Yolk(){println("Egg.Yolk()");}
}
public Egg(){
println("new Egg");
y = new Yolk();
}
}
public class BigEgg extends Egg{
public class Yolk{
public Yolk(){println("BigEgg.Yolk()");}
}
public static void main(String[] args) {
new BigEgg();
}
}
可以看到当继承某个外围类时内部类并没有发生任何变化,这两个内部类是完全不同的两个实体,各自在自己的命名空间。明确的继承某个内部类是可以的。
import static net.lijun.util.Print.*;
class Egg2 {
protected class Yolk {
public Yolk() { println("Egg2.Yolk()"); }
public void f() { println("Egg2.Yolk.f()");}
}
private Yolk y = new Yolk();
public Egg2() { println("New Egg2()"); }
public void insertYolk(Yolk yy) { y = yy; }
public void g() { y.f(); }
}
public class BigEgg2 extends Egg2{
public class Yolk extends Egg2.Yolk {
public Yolk() { print("BigEgg2.Yolk()"); }
public void f() { print("BigEgg2.Yolk.f()"); }
}
public BigEgg2() { insertYolk(new Yolk()); }
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Egg2 e2 = new BigEgg2();
e2.g();
}
}
5.局部内部类
前面说过,可以在代码块里创建内部类,典型的创建方式是在一个方法体里创建。局部内部类里面不能有访问说明符,因为不是外围类的一部分;但是可以访问当前代码块里的常量,以及此外围类的所以成员。
下面的例子对局部内部类与匿名内部类的创建进行了比较。
import static net.lijun.util.Print.*;
interface Counter{
int next();
}
public class LocalInnerClass {
private int count = 0;
Counter getCounter(final String name){
class LocalCounter implements Counter{
public LocalCounter(){
println("LocalCounter()");
}
public int next() {
print(name);
return count++;
}
}
return new LocalCounter();
}
Counter getCounter2(final String name) {
return new Counter() {
{
println("Counter()");
}
public int next() {
print(name);
return count++;
}
};
}
public static void main(String[] args) {
LocalInnerClass lic = new LocalInnerClass();
Counter
c1 = lic.getCounter("Local inner "),
c2 = lic.getCounter2("Anonymous inner ");
for(int i = 0; i < 5; i++)
println(c1.next());
for(int i = 0; i < 5; i++)
println(c2.next());
}
}
Counter返回序列中的下一个值。我们使用了局部内部类和匿名内部类,他们有相同的行为和能力,既然局部内部类的名字在方法外是不可见的,那我们为什么不使用匿名内部类呢?唯一的理由是,我们需要一个已命名的构造器,或者需要重载构造器,而匿名内部类只适用于实例初始化。
另一个理由是:需要不止一个该内部类对象。
6.内部类标识符
内部类的命名规则是外围类的名字加上’$’再加上内部类的名字。如果内部类是匿名的,编译器会随机产生一个数字作为其标识符,
7.总结
接口和内部类可以解决C++中多继承的问题,此时读者应该了解到他们的语法和语言,在遇到实际情况时我们会最终理解它。