一.初次见面
曾几何时我们已经忘记了Java赠送给开发者的这块"糖"--内部类。谈起内部类大家都会先停顿一下,然后才恍然大悟。"噢...对对对,某某某时我用过内部类"。好东西我们总是顺其自然的拿起来就用。今天带大家来回顾一下InnerClass的相关知识。
曾几何时我也用过Java内部类:
package innerclass;
import java.util.ArrayList;
import java.util.List;
//测试内部类的初始化和外围类之间的通信
public class Box {
private String name;
private int capacity;
private List<Box> contents = new ArrayList<Box>();
public Box(String name, int capacity) {
this.name = name;
this.capacity = capacity;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
private void add(Box box) {
contents.add(box);
}
class MiniBox extends Box {
public MiniBox(String name, int capacity) {
super(name, capacity);
}
}
private class Manager {
int count = 0;
// 检查当前box的剩余容量
public int getRemainCap() {
return capacity;
}
public boolean check(Box b) {
int c = b.capacity;
if (getRemainCap() >= c) {
count++;
Box.this.add(b);
capacity -= c;
return true;
}
return false;
}
public int getCount() {
return count;
}
}
public static void main(String[] args) throws CloneNotSupportedException {
Box b = new Box("Big Box", 200);
Manager mng = b.new Manager();
for(MiniBox mb = b.new MiniBox("Mini Box", 50);;) {
if(mng.getRemainCap() >= mb.getCapacity()) {
mng.check(mb);
} else
break;
}
System.out.println("MiniBox数量:" + mng.getCount());
}
}
///MiniBox数量:4
我们可以把Box当成一个仓库(其实它仅仅是一个Big box它能装n个Mini box),一个仓库当然需要一个管理员,要不傻瓜似的仓库永远会让卡车开进去卸货。那么,上面的Manager就是这个Box的管理员。
在面向对象的世界里有一个类的单一职责原则。"我是一个Box,我的责任就是装尽可能多的Minibox,我不管我的肚子有多大,我尽管装;我是一个Manager,我的责任就是管理Box,至少不让你的肚子被撑破"。
二.从头开始
怎样形容说内部类呢?"它就像你肚子里的蛔虫,它知道你身体里所有的东西"。难道我私有的(private)的它也能知道吗?对,这就是内部类!
来我们看一个例子:大部分男性同志可能要如数的把工资存到夫人那里,这是一种严重的不平等。"哪里有压迫,哪里就有反抗",于是我们提出了"建立小金库"的口号。但是任何事情都有成功者和失败者,这也不例外。
1. 成功者的案例
package innerclass;
public class Husband {
// 奖金==私房钱
private int caseDough;
// 该上交的工资
protected int salary;
public int getCaseDough() {
return caseDough;
}
public void setCaseDough(int caseDough) {
this.caseDough = caseDough;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
//用私房钱喝花酒
private boolean drinkWane() {
if(this.caseDough > 0) {
System.out.println("今天花私房钱去喝酒,甚爽!");
return true;
} else
return false;
}
}
package innerclass;
//在同一个包中,老婆无法查出私房钱
public class Wife {
protected int savings;
public void getSalary(Husband h) {
savings += h.getSalary();
}
public int getSavings() {
return savings;
}
public void check() {
//老婆查不出我有私房钱
}
@Override
public String toString() {
return "老公你真好,我们家现在有存款:" + savings;
}
public static void main(String[] args) {
Husband h = new Husband();
h.setSalary(8000);
Wife w = new Wife();
for(int i = 0; i < 12; i++) {
//上缴工资
w.getSalary(h);
}
System.out.println(w.getSavings());
System.out.println(w);
}
}
/*
96000
老公你真好,我们家现在有存款:96000
*/
2.失败者的案例
package innerclass;
public class Husband {
// 奖金==私房钱
private int caseDough;
// 该上交的工资
protected int salary;
//类的内部,老婆对自己了如指掌。
private class Wife {
protected int savings;
public void getSalary(Husband h) {
savings += h.getSalary();
}
public int getSavings() {
return savings;
}
public boolean check() {
//老婆这时会查出你的私房钱
if(Husband.this.getCaseDough() > 0) {
System.out.println("老公你大胆,竟敢存私房钱!");
if(Husband.this.drinkWane()) {
System.out.println("还用私房钱喝花酒!");
}
return true;
}
return false;
}
@Override
public String toString() {
return "老公你真好,我们家现在有存款:" + savings;
}
}
public int getCaseDough() {
return caseDough;
}
public void setCaseDough(int caseDough) {
this.caseDough = caseDough;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
//用私房钱喝花酒
private boolean drinkWane() {
if(this.caseDough > 0) {
System.out.println("今天花私房钱去喝酒,甚爽!");
return true;
} else
return false;
}
public static void main(String[] args) {
Husband h = new Husband();
Wife w = h.new Wife();
h.setSalary(8000);
h.setCaseDough(1000);
w.check();
w.getSalary(h);
System.out.println(w.getSavings());
System.out.println(w);
}
}
我们分析一下失败者为什么失败!他们的老婆如何把手伸向他们的腰包!
//失败这的老婆已经用这种"粗鲁"的方式直接拿老公的工资,那么私房钱对她们来说更是//透明的了
public void getSalary(Husband h) {
savings += salary;
}
看一下反编译以后的Wife的class文件内容:
final Husband this$0;//重点在这里,这个this$0出卖了可怜的老公
public void getSalary(Husband);
Code:
Stack=3, Locals=2, Args_size=2
0: aload_0
1: dup
2: getfield #4; //Field savings:I
5: aload_0
6: getfield #2; //Field this$0:LHusband;
9: getfield #5; //Field Husband.salary:I
12: iadd
13: putfield #4; //Field savings:I
16: return
LineNumberTable:
line 12: 0
line 13: 16
#####
#2 --> const #2 = Field #18.#43; // Husband$Wife.this$0:LHusband;
const #5 = Field #46.#47; // Husband.salary:I
好吧,答案我们已经知道了,原来老公送给了老婆一个this的引用,老婆只要捏住老公的这个"小尾巴-->Husband.this",老公就会乖乖的了。
三.小异大不同
普通内部类的双胞胎兄弟--嵌套类。相貌上的区别只有一点:嵌套类多了一个static标示符;但是功能上的区别就很大了。
1. 普通的内部类的实例化必须由外围类发起,如果没有外围类对象实例就不能获得其内部类实例,但是嵌套类可以不通过外围类直接获得其实例。
2. 普通内部类能够访问外围类的所有成员,但是嵌套类只能访问外围类的静态成员。
3. 普通内部类的不能有static的变量和方法,但是嵌套类没有这样的限制。
有了内部类为什么还需要嵌套类呢?当我们不需要内部类与外围类之间有联系的时候可以将内部类声明为static。上面两个例子中的内部类从功能和数据上都存在或多或少联系,所以他们不能声明为static。我们可以看一下HashMap中的Entry的实现。HashMap
static class Entry<K,V> implements Map.Entry<K,V> {
中的Entry声明为嵌套类,LinkedList中的Entry也声明为了嵌套类。这种Entry不需要与外围类有联系,他们是用来维持数据结构和承载数据的,对于现在这个List或者Map的状态他们不需要了解。所以,他们声明为嵌套类最合适。
四.无所不在
最常用或者最常见的内部类当然就像上面例子写的那样:在类的内部声明一个类。但是内部类是无所不在的。
1. 局部内部类
package innerclass;
interface Computable {
public int compute();
}
public class Computer {
public Computable getAddResult(final int a, final int b) {
class Compute implements Computable {
@Override
public int compute() {
return a + b;
}
}
return new Compute();
}
public Computable getMulResult(final int a, final int b) {
class Compute implements Computable {
@Override
public int compute() {
return a * b;
}
}
return new Compute();
}
public Computable getRedResult(final int a, final int b) {
class Compute implements Computable {
@Override
public int compute() {
return a - b;
}
}
return new Compute();
}
public Computable getDivResult(final int a, final int b) {
class Compute implements Computable {
@Override
public int compute() {
return a / b;
}
}
return new Compute();
}
public static void main(String[] args) {
int a = 10;
int b = 2;
Computer c = new Computer();
System.out.println("加法结果:" + c.getAddResult(a, b).compute());
System.out.println("减法结果:" + c.getRedResult(a, b).compute());
System.out.println("乘法结果:" + c.getMulResult(a, b).compute());
System.out.println("除法结果:" + c.getDivResult(a, b).compute());
}
}
2. 匿名内部类
package innerclass;
interface Product {
public void speak();
public void sing();
}
interface ToyFactory {
public Product getToy() ;
}
class DollBaby implements Product {
@Override
public void speak() {
System.out.println("i am a doll baby!");
}
@Override
public void sing() {
System.out.println("la la la la ...");
}
public static ToyFactory toy = new ToyFactory() {
@Override
public Product getToy() {
return new DollBaby();
}
};
}
public class ImitateFactory {
static void perform(ToyFactory toy) {
Product p = toy.getToy();
p.sing();
p.speak();
}
public static void main(String[] args) {
perform(DollBaby.toy);
}
}
3. 局部内部类VS匿名内部类
//使用匿名内部类
public Computable getAddResult(final int a, final int b) {
return new Computable() {
@Override
public int compute() {
return a + b;
}
};
}
//使用局部内部类
public Computable getAddResult(final int a, final int b) {
class Compute implements Computable {
public Compute() {
//Constructor
}
@Override
public int compute() {
return a + b;
}
}
return new Compute();
}
从上面的代码中我们可以看出,局部内部类可以使用构造函数,而匿名内部类不能使用构造函数。所以在实际应用中如果你想重载父类或者重新实现一个自己的构造函数,那么最好还是使用局部内部类。
五.内部类的必要性
那么我们为什么需要内部类呢:内部类能够独立的继承自一个实现,所以不论外围类是否已经继承了某个实现,这对于内部类都是没有影响的。
六.多重继承
对于哺乳动物来说它的亲生父亲只会有一个,这是一个大自然的规律。在java的世界也是遵循这条规律的,所以每个类只能有一个父类。当然我们可以用接口实现多重继承,但是有的时候这确实不太合适。
Java中extends具有"is-a"的特征,implements具有"has-a"的特征。我们来看一个例子:狼孩。我们第一反应就是它是一个人,因为他是人生的。但是从他自己的角度讲更是一个狼,因为它拥有狼的生活习性:夜间活动,吃生肉腐肉...它更认为自己是一个狼。那么这个狼孩到底该implements谁,该extends谁呢??还是不要把时间花在争辩上边,就当他是一个多继承特例吧。
abstract class Human {
public Human() {
System.out.println("人的体貌特征");
}
}
abstract class Wolf {
public Wolf() {
System.out.println("狼的饮食方式");
}
abstract void eat();
}
public class WolfBoy extends Human {
class Eat extends Wolf {
@Override
void eat() {
System.out.println("吃生肉!");
}
}
public void eat() {
new Eat().eat();
}
public static void main(String[] args) {
WolfBoy boy = new WolfBoy();
boy.eat();
}
}
七.Java的闭包
Java觉得可以搞出闭包,但是有没有必要搞,那还是留给喜欢讨论的人吧。
package innerclass;
class DoSomeThing {
private void sayHello() {
System.out.println("Hi, all!");
}
class Closure {
public void sayHello() {
DoSomeThing.this.sayHello();
}
}
Closure getReference() {
return new Closure();
}
}
public class TestClosure {
public static void main(String[] args) {
DoSomeThing dst = new DoSomeThing();
dst.getReference().sayHello();
}
}