什么是内部类?将一个类定义在另一个类的内部,这个类就叫做内部类。
内部类例子
package first;
// outer class
public class OuterClass {
// inner class
class InnerClass{
InnerClass(){
System.out.println(OuterClass.this);
System.out.println(this);
}
}
public InnerClass getInnerClass(){
return new InnerClass();
}
public static void main(String[]args) {
OuterClass.InnerClassoic = new OuterClass().getInnerClass();
}
}
Class InnerClass定义在OutClass中
内部类的默认作用域
还是在包first中建立如下类,
package first;
public class Caller {
public static void main(String[]args) {
OuterClass.InnerClassa = new OuterClass().getInnerClass();
}
}
编译正常,可以得出结论InnerClass的作用于起码不是private,应该是protected或者public的。Protected表示包内可见
然后新建包second,建立如下类,
package second;
importfirst.OuterClass;
public class Caller {
public static void main(String[]args) {
OuterClass.InnerClassa = new OuterClass().getInnerClass();
}
}
用下面的指令编译,
javac -cp ../../bin/ Caller.java
得到的结果是,
Caller.java:7: 错误: OuterClass.InnerClass在OuterClass中不是公共的; 无法从外部程序包中对其进行访问
OuterClass.InnerClass a = new OuterClass().getInnerClass();
可以看出InnerClass的作用域不是公共的,也就是不是public,得出在默认情况下是protected。
内部类对外部来的引用
当生成一个内部类的对象的时候,它和外部类的对象之间就有了一种联系,它能访问其外围对象的所有对象。
修改上面的OuterClass为,
package first;
// outer class
public class OuterClass {
public int count = 0;
void saySomething(){
System.out.println("youcall me~");
}
// inner class
class InnerClass{
InnerClass(){
System.out.println(OuterClass.this);
System.out.println(this);
}
void saySomething(){
OuterClass.this.saySomething();
System.out.println("youcall me!");
count++;
}
}
public InnerClass getInnerClass(){
return new InnerClass();
}
public static void main(String[]args) {
OuterClass oc = new OuterClass();
System.out.println(oc);
oc.getInnerClass().saySomething();
System.out.println(oc.count);
}
}
运行后的结果是,
first.OuterClass@1dd3812
first.OuterClass@1dd3812
first.OuterClass$InnerClass@8c436b
you call me~
you call me!
1
在调用InnerClass的saySomething方法时,调用了OuterClass的saySomething方法,也就是说明OuterClass的方法对于InnerClass是可见的。并且OuterClass的属性count对于InnerClass也是可见的。
再仔细观察
System.out.println(OuterClass.this);
和
System.out.println(oc);输出的结果是一样的,都是新生成的OuterClass对象的地址。
也就是说在InnerClass的对象中,持有了外围对象的引用,可以在内部对象中对外围对象进行操作。
内部类对象的初始化
上面提到了,内部对象持有了外部对象的引用,也就是说在初始化内部类的时候,外部类必须已经初始化好了。
来看看内部类如何初始化,修改main函数如下所示,
public static void main(String[]args) {
OuterClass oc= new OuterClass();
System.out.println(oc);
oc.getInnerClass().saySomething();
System.out.println(oc.count);
// 内部类的初始化
OuterClass.InnerClassoic = oc.new InnerClass();
oic.saySomething();
System.out.println(oc.count);
}
可以看出内部类初始化的格式是
外部类.内部类 内部类对象名 = 外部类对象.new 内部类(); (方式1)
如果按照我们既定的思维,
外部类.内部类 内部类对象名 = new 外部类.内部类(); (方式2)会出现什么情况呢?
public static void main(String[]args) {
OuterClass oc= new OuterClass();
System.out.println(oc);
oc.getInnerClass().saySomething();
System.out.println(oc.count);
// 内部类的初始化
OuterClass.InnerClassoic = new OuterClass.InnerClass();
oic.saySomething();
System.out.println(oc.count);
}
编译后得到如下错误,
OuterClass.java:34: 错误: 无法从静态上下文中引用非静态 变量 this
OuterClass.InnerClass oic = new OuterClass.InnerClass();
因为你采用了方式2,所以Javac将OuterClass.InnerClass作为了静态内部类来处理,但是实际上它不是静态内部类,所以会出现上面的错误。之后会提到静态内部类。
内部类的作用
上面说了那么多关于内部类的语法,那么内部类有什么作用呢?
1) 内部类的对象拥有外部类的对象的引用,方便直接对外部类对象进行操作。而如果不采用内部类,将内部类提到外面单独形成一个类,那么如果想让这个类的实例(相当于内部类对象)能够操作另一个类的实例(相当于外部类对象),就需要将后者当做参数传递到前者的函数中,或者前者拥有后者的引用(作为属性)。这样就可能会出现这么一种情况,对象A将对象B作为其属性,对象B在对象A中起到工具类的作用。然后由于对象B又需要操作对象A,对象B要持有A的对象,也就是对象A也是对象B的一个属性,这就出现了我们最不想见到的相互引用。这是一个比较糟糕的设计。
2) 屏蔽某些细节的实现。如果将内部类设置为私有的,也就是加上private限定符,那么这个内部类的实现就是仅仅对于其外部类可见,不会被其他类随意调用。也很好的满足了面向对象程序设计的思想,来看看下面的例子。
package first;
// 计费方式
interface Billing {
int billing();
}
package first;
public class Bus {
class BusBillingimplements Billing{
@Override
public int billing() {
// TODO Auto-generatedmethod stub
return 2;
}
}
}
package first;
public class Tax {
private int BASE_DIS = 2; // 起步公里数
private int BASE_PRICE = 10; // 起步价
private int curDis; // 用户公里数
private int EXCEED_PRICE = 1; // 超过起步公里后每公里的价格
class TaxBillingimplements Billing{
@Override
public int billing() {
if (curDis <=BASE_DIS){
returnBASE_PRICE;
}
returnBASE_PRICE + (curDis -BASE_DIS) * EXCEED_PRICE;
}
}
}
对于公交车和出粗车采用了完全不一样的计费方式,并且其计费方式和外围的出行工具紧密相关。
3) 内部类使得多继承的解决方案变得完整。(参见java编程思想10.8)
匿名内部类
package first;
interface InnerClassInter {
}
package first;
// outer class
public class OuterClass {
public InnerClassInter getInnerClass(){
return new InnerClassInter(){
};
}
public InnerClassIntergetInnerClass2(){
return new InnerClassInter(){
};
}
public static void main(String[] args) {
OuterClass oc = new OuterClass();
System.out.println(oc.getInnerClass().getClass());
System.out.println(oc.getInnerClass2().getClass());
}
}
在OuterClass的getInnerClass方法中,返回了一个匿名类的对象,这个匿名类没有具体的名字,其名字是JVM自动生成的。
运行后输出的结果是,
class first.OuterClass$1
classfirst.OuterClass$2
对于不同函数返回的匿名类,虽然返回的匿名类的功能完全一样,但是它们属于不同的类,所以JVM都进行了自动命名已区分这些匿名类。
我们熟悉的例子比如
newThread(new Runnable(){
}).start();
嵌套类
什么是嵌套类呢?还记得之前提到的静态内部类吗,对嵌套类就是静态内部类。如果不需要内部对象与其外围对象之间有联系,那么就可以将内部类声明为static。
这就意味着:
1) 创建内部类的对象时,不需要外部类的对象
2) 不能从嵌套类的对象访问非静态的外围对象
这两点都很好理解。
那么静态内部类如何初始化呢?
package first;
// outer class
public class OuterClass {
public static class InnerClass implements InnerClassInter{
}
public static void main(String[] args) {
OuterClass.InnerClass oic = new OuterClass.InnerClass();
}
}
外部类.内部类 内部类对象名 = new 外部类.内部类();正好符合之前我们提到的创建内部类实例的方法2