可以将一个类的定义放在另一个类的定义内部,这就是内部类。但内部类与组合是完全不同的概念。
文章目录
定义内部类
class A {
class B{
}
}
想要创建内部类对象,需要指明具体的对象的类型。例: A.B(即OuterClassName.InnerClassName)
链接到外部类
内部类似乎是一种隐藏名字的和组织的模式。但它的用途远不止如此。当生成一个内部类对象时,此对象与制造它的外围类对象之间就有一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还有用外围类的所有元素的访问权限。
内部类自动拥有对其外围类所有成员的访问权(包括private修饰的),这是如何做到的呢?
当某个外围类对象创建了一个内部类对象时,此内部类对象必定会秘密的捕获一个指向那个外围类对象的引用。然后,在访问此外围类的成员时,就是用那个引用来选择外围类的成员。Java编译器帮我们做了这些所有的细节。
由于内部类对象需要一个指向外围类对象的引用,所以内部类的对象只能在与其外围类的对象相关联的情况下才能创建(内部类为非static的)。构建内部类对象时需要一个指向其外围类对象的引用,如果不这样做编译器就会报错。
使用.this和.new
.this
如果需要生成对外部类对象的引用,可以使用[外部类的名字]+[.this]。这样产生的引用自动地具有正确的类型,这一点在编译器就被知晓并受到检测,所以没有任何在运行时的开销。
例:
public class DotThis{
public void f(){
print("DotThis f()");
}
class Inner{
public DotThis outer(){
return DotThis.this; // 生成外围类对象的引用
}
}
public Inner inner(){
new Inner();
}
public void mian(String arg[]){
DotThis dot = new DotThis();
DotThis.Inner inner = dot.inner();
inner.outer().f();
}
}
// log
DotThis f()
创建内部对象(.new)
由于内部类对象需要一个指向外围类对象的引用(即内部类对象需要关联到一个外部类的对象上),所以创建内部类对象时需要知道一个指向外围类对象的具体引用,通过这个引用指向的对象来创建内部类对象。可以使用[外围类对象的引用]+[.new 内部类构造器]。例:
public class DotNew{
public class Inner{
}
public static void mian(String arg[]){
DotNew dotNew = new DotNew();
DotNew.Inner inner = dotNew.new Inner();
}
}
创建内部类对象。
- 需要明确指定对象的具体类型。如:DotNew.Inne。
- 通过外围类对象的具体引用来创建对象。
局部内部类
可以在一个方法里面或任意的作用域内定义内部类。典型的一种方法是时在方法内部定义。在方法内部定义一个完成的类,这个类被称为局部内部类。例:
public class A {
pulic void f(final int i){
class B {
public void g(){
}
}
}
}
局部内部类B不属于外围类A的一部分,它属于方法f。所以局部类部内不能有权限修饰符,但是局部内部类可以访问当前代码块内的常量,以及次外围类的所有成员。
匿名内部类
匿名内部类,即没有名字的类。例:
public class A{
public B b = new B{
public void f(){
}
}
public B g(){
return new B(){
public void f(){
}
}
}
}
这种奇怪的语法指的是:创建一个继承某个类(或实现某个街口)的匿名类对象,并将其向上转型为基类或接口类型。上面的例子创建一个继承B的匿名类的对象,将这个对象向上转型为B类型。
当基类的构造器需要参数时该怎么办?例:
public class B {
public B(int i){
}
}
public class A{
public B g(int i){
return new B(i){
}
}
}
当基类的构造器需要参数时,只需要传递相应的参数给基类的构造器即可。当匿名内部类定义在方法的作用域内,如果在匿名内部类的内不像使用方法中作用域的变量,那么这个变量就需要定义为final的,否则编译报错。但可以方法器外围类的左右的成员,和局部内部类一样。
由于匿名内部类没有名字,所以匿名内部类没有命名的构造器,如果在匿名内部类想使用构造器的一些效果(如初始化一个数据),那么怎么办?可以通过在实例初始化数据,即在匿名内部类中添加实例,把要想实现处理的数据放在实例中进行。但也有缺点,就是不能重载实力初始化方法(构造器可以重载)。
嵌套类
如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static的。内部类声明为static的,这个内部类称为嵌套内部类(也叫静态类)。
普通的内部类对象隐式地保存了一个引用,这个引用指向其外围类的对像。而嵌套内没有这个引用,即:
- 创建嵌套类的对象时不需要外围类的对象。
- 不能从嵌套类的对象中访问非静态的外围类对象。
- 普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类(即能包含非嵌套类),但时嵌套类能包含所有东西。
接口内部的类
正常情况下,不能接口中内部放置任何代码,但是嵌套类可以作为接口的一部分。放在接口中任何类都自动地是public和static的。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。甚至可以在接口中的内部类实现其外围接口。例:
public interface A{
void f();
class B implements A{
public void f(){
}
public static void mian(String arg[]){
B b = new B();
b.f();
}
}
}
如果想要创建某些公共接口代码,使得它们可以被某个接口的所有不通实现所共用,那么使用接口内部的嵌套类会显得很有用。
从多层嵌套类中访问外部类的成员
一个内部类不管被嵌套多少层并不重要——它能透明的访问所有它所嵌入的外围类的成员。例:
public class A{
public void a(){
}
public class B{
public void b(){
}
public class C{
public void c(){
a();
b();
}
}
}
}
pullic class Test{
pulic static void mian(String arg[]){
A a = new A();
A.B b = a.new B();
A.B.c c = b.new C();
c.c();
}
}
为什么需要内部类
内部类必须要回答一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类来来实现那个接口呢?答案是:“如果只能满足需求,那么就应该这样做。”那么内部类实现一个接口和外围类实现这个接口有什么区别呢?答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。使用内部类最吸引人的原因是:
每个内部类都能独立的继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。 从而内部类有效的实现了“多重继承”。
内部类的继承
因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类时候,事情就会变得复杂。问题在于,那个指向外围类对象的“秘密的”引用必须得初始化,而在导出类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清这种关联。例:
class WithInner{
class Inner{
}
}
public class InheritInner extends WithInner.Inner{
public InheritInner(WithInner wi){
wi.super();
}
}
public static void mian(String arg[]){
WithInner wi = new WithInner();
InheritInner in = new InheritInner(wi);
}
InheritInner继承自内部类,而不是外围类。但是当要生成一个构造器的时候,默认的构造器并不算好,而且不能只是传递一个指向外围类对象的引用。此外,必须要构造器内部使用如下语法:enclosingClassReference.super()。必须要这样写,否则编译不通过。
内部类可以被覆盖吗
创建一个内部类,然后继承其外围类并重新定义此内部类时,会发生什么?内部类可以被覆盖吗?答案是不行的。基类的内部类和导出类中重新定义的内部类时完全独立的,各自在自己的命名空间。例:
class A {}
calss T{
}
}
class B extends A{}
calss T{
}
}
B.T类型并不是A.T类型,也不能向上转型。