我的博客网站 http://www.caoyong.xin:8080/blogger
这几天在把编程思想(初学者怎么好不要过早看这本书。)重新拿出来看了一遍,看到Java内部类,结合该书和资料,总结了一下内部类的一些知识
1:什么是内部类?
顾名思义就是在Java类中定义的类叫内部类,其实这是片面的,不仅在类的内部定义,还可以在方法中,代码块中(什么代码块,就相当于if(){大括号里面的都叫代码块}),所以这里我们就给出了内部类定义的范围
2:内部类有哪些形式?
普通内部类
局部内部类
匿名内部类
静态内部类
普通内部类
就是我们所理解的定义在类的内部的类,看下面代码
public class Out {
public class Inner {
public void innerMethod() {
System.out.println("我是内部类中的方法 ....innerMethod");
Out.this.outMethod();
}
}
public void outMethod() {
System.out.println("我是外部类的方法 ....outMethod");
}
public static void main(String[] args) {
Out d = new Out();
Out.Inner i = d.new Inner();
i.innerMethod();
}
}
这里我定义一个外部类Out 定义一个内部类Inner 在外部类中定义一个outMethod方法,在内部类中定义一个innerMethod方法。
当内部类生成对象的时候,与外部类有一定的联系,这时候就会想到,在外部类怎样得到内部类的对象?在内部类里面怎样得到外部类的对象?
这个时候时候就要用到.new 和.this,看下面代码
Out d = new Out();
Out.Inner i = d.new Inner();
i.innerMethod()
内部类对象必须通过外部类对象才能产生(静态内部类除外,稍后再提)上面的d就是外部类对象,通过外部类名.内部类名 和 d.new就可以得到内部类对象i
如何在内部类中得到外部类对象,就需要用到.this,看上面代码在内部类Inner中 Out.this.outMethod(); 外部类名.this就得到了外部类对象。
局部内部类
前面提到的都是处于外部类中的内部类,而局部内部类定义在一个方法里面或者在任意的作用域中,这么做有以下两个理由:
1.如实现某接口的内部类,可以在方法中创建并返回对其的引用
2.要解决一个复杂的问题,需要创建一个类来辅助,但不希望这个类是公共可用的
public class Test {
public double count = 100;
public Car getCar() {
class BaoMa implements Car {// 如实现某接口的内部类,可以在方法中创建并返回对其的引用
@Override
public double money() {
return count * 20;
}
}
return new BaoMa();
}
public static void main(String[] args) {
Test t = new Test();
Car baoMa = t.getCar();
System.out.println(baoMa.money());
}
}
结果:2000.00
局部内部类不能有 private 等访问说明符,因为它不是外部类的一部分,但是它可以访问当前代码块内的常量以及外部类中的所有成员
匿名内部类
匿名内部类也就是没有名字的内部类
从上面可以看出 Car baoMa = t.getCar(); 在getCar()方法里面虽然定义了一个内部类,但是我们在调用gtCar()方法的时候,好像不怎么关心内部类的名字,从而也就变成了匿名。
对于上面的代码我们可以这样来写
public class Test {
public Car getCar(){
return new Car(){//这里就实现了匿名 原本应该有一个内部类 继承Car类并重写price()方法,但是这里内部类的名字却没有,变成了匿名
int count = 100;//这个变量在内部类里面定义的。
public double price() {
return count * 20;
}
};
}
public static void main(String[] args) {
Test t = new Test();
Car car = t.getCar();
System.out.println(car.price());
}
}
在匿名内部类中使用一个在其外部定义的对象,那么编译器必须要求其参数引用是 final 类型,以上代码在低于 Java 8 的版本编译不会通过,但是在 Java 8 版本不用 final 修饰局部变量也可以编译通过,只不过不能修改值,只能打印输出或赋值给其他变量。
public Car getCar(){
final int count = 100 ;
return new Car(){
public double price() {
return count * 20;
}
};
}
可能有人就会问了,没有类名,怎么使用构造函数初始化类了?
面对这个问题,我们可以在内部类里面加上一段代码块来解决
public Car getCar(){
final int count = 100 ;
return new Car(){
{System.out.println("这里初始化内部类");}
public double price() {
return count * 20;
}
};
}
结果:
这里初始化内部类
2000.0
在上面,我们分别使用局部内部类和匿名内部类实现了这个功能,它们具有相同的行为和能力,既然局部内部类的名字在方法外是不可见的,那为什么我们仍然使用局部内部类而不是匿名内部类呢?唯一的理由是:我们需要一个已命名的构造器,或者需要重载狗仔器,而匿名内部类只能用于实例初始化,所以使用局部内部类而不使用匿名内部类的另一个理由就是:需要不止一个该内部类的对象。
静态内部类(嵌套内部类)
在了解静态内部了之前,要先了解static关键字,关于什么是static可以查看我的博客关于static的介绍。
如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为 static,即静态内部类,也称嵌套类,静态内部类和非静态内部类的最大区别就是非静态内部类对象隐士的保存了一个外部类对象的引用,这意味着:
1.不需要外部类的对象就可以创建静态内部类的对象
2.不能从静态内部类的对象中访问非静态的外部类对象
3.静态内部类中可以定义静态或者非静态的成员,而非静态内部类则不能有静态成员。
public class Test {
static int count = 100;// 静态
int noCount = 100;// 非静态
static class getCar implements Car {
String str = "我是非静态";
static String sstr = "我是静态";
public double price() {
System.out.println(str);
System.out.println(sstr);
return count * 20;
// return noCount * 20;//编译不通过 不能访问非静态对象
}
}
public static void main(String[] args) {
Test.getCar car = new Test.getCar();//直接使用外部类类名创建内部类对象
System.out.println(car.price());
}
}
结果:
我是非静态
我是静态
2000.0
这个例子很好的对应了上面的三句话。
3:内部类的继承
内部类,虽然是在类的内部定义的一个类,但是也可以去继承这个内部类,怎么继承自一个内部类?内部类的构造必须依赖其外部类对象,所以在继承内部类的时候,事情会变得复杂
看下面的例子 首先有一个基类
public class Fruit {
public class Apple{
void print() {}
}
}
接着有一个类去继承内部类Apple
public class Test extends Fruit.Apple {
Test(Fruit fruit){
fruit.super();
}
@Override
void print() {
System.out.println("内部类继承");
}
public static void main(String[] args) {
Fruit f = new Fruit();
Test t = new Test(f);
t.print();
}
}
前面提到,内部类对象的创建需要依赖外部类对象,所以我们要在导出类(Test类)的构造器中调用外部类对象。
4:内部类可以被覆盖吗
如果创建了一个内部类,然后继承其外部类并重新定义此内部类时,内部类可以被覆盖吗?例如:
class Car {
Car() {
System.out.println("new Car()"); new Tyre();
} class Tyre { // 我会被覆盖吗
Tyre() { System.out.println("new Tyre()"); }
}
}
public class BigCar extends Car {
class Tyre {
Tyre() {System.out.println("BigCar new Tyre()"); }
} public static void main(String[] args) { new BigCar();
}
}
在 Car 的构造器中新建的 Tyre 是 Car 中的 Tyre 还是 BigCar 中的 Tyre 呢?运行程序输出:
new Car()
new Tyre()
BigCar 中定义的 Tyre 内部类并没有覆盖 Car 中的 Tyre 内部类,实际上这两个内部类是完全独立的两个实体,各自在自己的命名空间内,没有谁覆盖谁之说。
5:为什么需要内部类
也就是说,内部类存在的意义是什么呢?为什么 Sun 公司如此费心地增加这项语言特性呢?这里将内部类的意义总结为以下四点:
A. 逻辑上被包含且对外隐藏
如果一个类在逻辑上被包含于另一个类,那么将此类设置为另一个类的内部类,比如轮胎类可以写作汽车类的内部类:
public class Car { public class Tyre{}
}123
上面代码中只存在被包含关系,也可通过组合方式实现,写作内部类是没有必要的,当想对外保密汽车使用何种轮胎时,写作内部类才是有必要的:
public class Car { private class Tyre{}
}123
B. 实现多重继承(这应该是内部类的意义所在)
每个内部类都能独立的继承一个接口,无论外部类是否已经继承了某个接口的实现,对于内部类都没有影响。内部类提供可以继承多个抽象类或具体的类的能力,有效的实现了多重继承,网上一个实例简单直观的展示了通过内部类实现多重继承(儿子利用多重继承来继承父亲和母亲的优良基因):
public class Father { // 父亲
public int strong() {
return 9;
}
}
public class Mother { // 母亲
public int kind() {
return 8;
}
}
public class Son { // 儿子通过内部类实现多重继承
class Father_1 extends Father {
public int strong() {
return super.strong() + 1;
}
}
class Mother_1 extends Mother {
public int kind() {
return super.kind() - 2;
}
}
public int getStrong() {
return new Father_1().strong();
}
public int getKind() {
return new Mother_1().kind();
}
}
C. 闭包与回调
闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外部类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外部类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。
通过内部类提供闭包功能比指针更灵活、更安全
例如:一个接口程序员和一个基类作家都有一个相同的方法work,相同的方法名,但是其含义完全不同,这时候就需要闭包。
class Writer { //作家基类
void work(){}
}interface programmer{ //程序员接口
void work();
}
闭包实现代码如下:
public class WriterProgrammer extends Writer {
@Override public void work(){ //写作
}
public void code() { // 写代码
}
class ProgrammerInner implements programmer {
@Override
public void work() {
code();
}
}
}
WriterProgrammer 继承自 Writer , 直接实现父类作家的work()方法,然后使用内部类实现程序员的work()方法回调code()方法。如果WriterProgrammer 同时继承自 Writer 且实现 programmer 接口,那么就不能同时实现作家和程序员的意义不同的 work()方法:
class WriterProgrammer extends Writer implements programmer{
@Override public void work() { //programmer的 work
}
}
应用程序框架就是被设计用以解决某类特定问题的一个类或一组类,而控制框架就是一类特殊的应用程序框架,它用来解决响应事件的需求,主要用来响应事件的系统被称作事件驱动系统。 Java Swing 库就是一个控制框架,它优雅的解决了 GUI 的问题,并使用了大量的内部类
控制框架的完整实现是由单个类创建的,内部类用来表示解决问题所必须的各种不同的 action,另外内部类能够很容易的访问外部类的任意成员,可以让这种实现更轻松。
例如控制温室的运作:控制灯光、水、温度调节器的开关等,每个行为都是完全不同的,使用内部类可以在单一的类中产生对同一个基类 Event 的多种导出版本,对于温室系统的每一种行为,都继承一个新的 Event 内部类,并在要实现的 action() 中编写控制代码:
public class GreenhouseControls{
public class LightOn extends Event {
public void action() { //开灯...
}
} public class LightOff extends Event {
public void action() { //关灯...
}
} public class WaterOn extends Event {
public void action() { //开水闸...
}
} // 其他操作...}
6:内部类标识符
每个类都会产生一个.class文件,其中包含了如何创建该类型的对象的全部信息,内部类也必须生成一个 .class 文件,从而可以包含它自己的 Class 对象信息,这些类文件的命名有严格的规则:外部类名字+“$”+ 内部类名字,例如:
class Car {
class Tyre {}}123
生成的 .class 文件包括:
Car.class
Car$Tyre.class