将一个类的定义放在另一个类的定义内部,这就是内部类;内部类允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性;
内部类的使用
1.1 创建内部类
把类的定义置于外围类的里面:
public class Parcel{
class Contents{
private int i = 11;
public int value(){ return i;}
}
public Contents contents(){
return new Contents();
}
public static void main(String[] args){
Parcel p = new Parcel();
Parcel.Contents c = p.contents();
}
}
其实上使用内部类与使用普通类相比较没有什么不同,但是因为内部类是嵌套在类中的,我们可以在外部类中定义一个方法用来返回内部类中的引用。
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在main()方法中那样,具体地指明这个对象的类型:OuterClassName.InnerClassName
1.2 链接到外部类
内部类似乎只是一种名字隐藏和组织代码的模式;当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。
内部类还拥有其外围类的所有成员的访问权,内部类的对象只能在与其外围类的对象相关联的情况下才能被创建。
构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。
1.3 生成对外部类对象的引用
如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟 .this。这样产生的引用自动具有正确的类型,这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。
在外部使用内部类的两种方式:
1.3.1 在外围类中定义一个方法,该方法返回内部类的一个对象,方法是public的
public class DotThis{
void f(){
System.out.println("DotThis.f()");
}
public class Inner{
public DotThis outer(){
return DotThis.this;
}
}
public Inner inner(){
return new Inner();
}
public static void main(String[] args){
DotThis dt = new DotThis();
DotThis.Inner dti = dt.inner();
dti.outer().f();
}
}
1.3.2 用外围类的对象得到内部类对象
有时候你可能想要告知某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在new表达式中提供对其他外部类对象的引用,这时需要使用 .new语法:
public class DotNew{
public class Inner{}
public static void main(String[] args){
DotNew dn = new DotNew();
DotNew.Inner dni = dn.new Inner();
}
}
要想直接创建内部类的对象,不能去引用外部类的名字DotNew,而是必须使用外部类的对象来创建该内部类对象。这也解决了内部类名字作用域的问题,因此你不必声明,dn.new DotNew.Inner()。
在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到创建它的外部类对象上。但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。
1.4 内部类与向上转型
当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。这是因为此内部类–某个接口的实现–能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便地隐蔽实现细节。
public interface Destination{
String readLabel();
}
public interface Contents{
int value();
}
现在Contents和Destination表示客户端程序员可用的接口。当取的得了一个指向基类或接口的引用时,甚至可能无法找出它确切的类型:
class Parcel{
private class PContents implements Contents{
private int i =11;
public int value(){return i;}
}
protected class PDestination implements Destination{
private String label;
private 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 class TestParcel{
public static void main(String[] args){
Parcel p = new Parcel();
Contents c = p.contents();
Destination d = p.destination("Tasmania");
}
}
Parcel中增加了一些新东西:内部类PContents是private,所以除了Parcel,没人能访问它。PDestination是protected,所以只有Parcel及其子类、还有与Parcel同一个包中的类能访问PDestination,其他类都不能访问PDestination。这意味着,如果客户端程序员想了解或访问这些成员,那是受限制的。
private内部类给类的设计者提供了一种途径,通过这种方式可以完全组织任何依赖于类型的编码,并且完全隐藏了实现的细节。
此外,从客户端程序员的角度来看,由于不能访问任何新增加的、原本不属于公共接口的方法,所以扩展接口是没有价值的。这也给Java编译器提供了生成更高效代码的机会。
内部类分类
上边已经简单介绍了内部类的使用,除了作为外部类的一个成员存在的内部类(成员内部类
)以外还有其他形式的内部类:局部内部类、匿名内部类
2.1 局部内部类
在Java中,可以在一个方法里面或者在任意的作用域内定义内部类。这么做的两个理由:
1.实现了某类型的接口,于是可以创建并返回对其的引用。
2.需要解决一个复杂的问题,想创建一个类来辅助解决方案,但是又不希望这个类是公共可用的。
在方法的作用域内创建一个完整的类,这称作局部内部类:
public class Parcel5{
interface Destination{}
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");
}
}
在任意的作用域内嵌入一个内部类:
public class Parcel6 {
private void internalTracking(boolean b){
if(b){
class TrackingSlip{
private String id;
TrackingSlip(String s){
id=s;
}
String getSlip(){return id;}
}
TrackingSlip ts=new TrackingSlip("slip");
String s=ts.getSlip();
}
}
public void track(){
internalTracking(true);
}
public static void main(String[] args) {
Parcel6 p=new Parcel6();
p.track();
}
}
当我们需要一个已命名的构造器,或者需要重载构造器时我们会用到局部内部类,而不能使用匿名内部类,因为匿名内部类只能用于实例初始化。当我们需要不止一个该内部类的对象,也要采用局部内部类。
局部内部类不能有访问说明符,因为它不是外围类的一部分;但是它可以访问当前代码块内的常量,以及此外围类的所有成员。局部内部类的名字在方法外是不可见的。
2.2 匿名内部类
public class Parcel {
interface Contents {}
// class MyContents implements Contents {
// private int i=11;
// public int value(){return i;}
// }
public Contents contents() {
return new Contents() {
private int i = 11;
public int value() {return i;}
};
// return new MyContents();
}
public static void main (String[]args){
Parcel p = new Parcel();
Contents c = p.contents();
}
}
创建一个继承自Contents的匿名类的对象,在这个匿名内部类中,使用了默认的构造器来生成Contents。如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数是final的。否则,编译器将会报错。如果只是传递给匿名类的基类的构造器,那么不需要将传入的形参定为final。(匿名内部类没有构造器,只能用实例化代替。)
2.3 嵌套类
如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。这通常称为嵌套类。普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就会不一样了。嵌套类意味着:
1.要创建嵌套类的对象,并不需要其外围类的对象
2.不能从嵌套类的对象中访问非静态的外围类对象
嵌套类与普通的内部类还有一个区别,普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。
接口内部的类:
正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。你放到接口中的任何类都自动地是public和static的。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口:
public interface ClassInInterface{
void howdy();
class Test implements ClassInInterface{
public void howdy(){
System.out.print("Howdy!");
}
public static void main(String[] args){
new Test().howdy();
}
}
}
如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便。
嵌套类的两个有效用途:
1.实现每个类实现接口都需要一部分公共代码
2.代替main方法对每个类进行测试
从多层嵌套类中访问外部类的成员:
一个内部类被嵌套多少层并不重要—它能透明地访问所有它所嵌入的外围类的所有成员,如下所示:
class MNA{
private void f(){}
class A{
private void g() {}
public class B{
void h(){
g();
f();
}
}
}
}
public class MultiNestingAccess{
public static void main(String[] args){
MNA mna = new MNA();
MNA.A mnaa = mna.new A();
MNA.A.B mnaab = mnaa.new B();
mnaab.h();
}
}
可以看到,在MNA.A.B中,调用方法g()和f()不需要任何条件(即使它们被定义为private)。
其他
3.1 为什么需要内部类
一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外围类的对象。所以可以认为内部类提供了某种进入其外围类的窗口
。
在外围类中不是总能享用到接口带来的方便,有时需要我们去实现接口。而使用内部类时,每个内部类都能独立地继承自一个接口的实现,所以无论外围类是否已经继承了某个接口的实现,对于内部类都没有影响。
如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,内部类使得多重继承的解决方案变得完整。
接口解决了部分问题,而内部类有效地实现了 “多重继承”。也就是说,内部类允许继承多个非接口类型(类或抽象类)。
使用内部类可以获得的一些特性:
1.内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
2.在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
3.创建内部类对象的时刻并不依赖于外围类对象的创建。
4.内部类并没有令人迷惑的“is-a”关系;它就是一个独立的实体。
3.2 闭包与回调
闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。通过内部类提供闭包的功能是优良的解决方案,它比指针更灵活、更安全。
3.3 内部类的继承
因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情就变得有点复杂。问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,而在导出类中不再存在可连续的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联:
class WithInner{
class Inner {}
}
public classs InheritInner extends WithInner.Inner{
InheritInner(WithInner wi){
wi.super();
}
public static void main(String[] args){
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
可以看到,InheritInner只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须在构造器内使用如下语法:
enclosingClassReference.super(); 这样才能提供了必要的引用,然后程序才能编译通过。
3.4 内部类的覆盖问题
内部类不可以像普通方法覆盖那样进行覆盖
覆盖内部类的方法:首先外围类继承外围类,然后内部类继承外围类(外围类.内部类方式)
3.5 内部类标识符
由于每个类都会产生一个 .class文件,其中包含了如何创建该类型的对象的全部信息,内部类也必须生成一个 .class文件以包含它们的Class对象信息。
这些类文件的命名有严格的规则:外围类的名字,加上 “$”,再加上内部类的名字。例如:LocalInnerClass.java生成的.class文件包括:
Counter.class
LocalInnerClass$1.class
LocalInnerClass$1LocalCounter.class
LocalInnerClass.class
如果内部类是匿名的,编译器会简单地产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需要直接将它们的名字加在其外围类标识符与"$"的后面。