4. 内部类:
Java中内部类可以为私有内部类,既只有外部类可以访问该内部类,而Java外部类的可见性只有包可见和public两种。C++中的内部类比较类似于Java中的静态内部类,只是一种作用域限制的行为,以下为Java非静态内部类的说明:
1) 内部类可以访问外部类的所有域成员和域字段,这也同样包括私有的字段和成员。
2) Java的编译器在构造外部类调用内部类构造方法时,自动将外部类的this变量作为一个隐式参数传给了内部类的构造函数,内部类则在构造函数中保留了this变量的引用,该行为为编译器隐式行为。
2 public class InnerClass {
3 bool test() {
4 //这里的_jobYears为外部类域字段。
5 return _jobYears > 10;
6 }
7 }
8
9 public Employee(int jobYears,String name) {
10 _name = name;
11 _jobYears = jobYears;
12 _salary = 0;
13 }
14
15 public void raiseSalary() {
16 //编译器的会将以下构造隐式替换为InnerClass inner = new InnerClass(this);
17 //因为Java在为其编译的时候发现InnerClass为非静态内部类,则自动添加了以下构造:
18 //public InnerClass(Employee e)
19 InnerClass inner = new InnerClass();
20 if (test())
21 _salary += 1000;
22 }
23 private String _name;
24 private int _jobYears;
25 private int _salary;
26 }
注:针对以上事例,内部类InnerClass可以通过Employee.this._jobYears的全称来显式的代替_jobYears > 10 中的_jobYears。反过来在raiseSalary方法中可以通过this.new InnerClass()语法格式更加明确的创建InnerClass的对象。
2 public class InnerClass {
3 bool test() {
4 //这里的_jobYears为外部类域字段。
5 return Employee.this._jobYears > 10;
6 }
7 }
8
9 public Employee(int jobYears,String name) {
10 _name = name;
11 _jobYears = jobYears;
12 _salary = 0;
13 }
14
15 public void raiseSalary() {
16 //这里也可以不使用this作为内部该内部类对象的外部类对象
17 //引用,可以根据需要替换为其他外部类对象的引用,如:
18 // Employee other = new Employee();
19 // InnerClass innser = other.new InnerClass();
20 InnerClass inner = this.new InnerClass();
21 if (test())
22 _salary += 1000;
23 }
24 ......
25 }
注:在外部类的作用域之外调用public内部类的语法为 OutClass.InnerClass。
3) 局部内部类的可见范围仅仅限于声明该局部类的函数内部,见如下代码:
2 class TimePrinter implements ActionListener {
3 public void actionPerformed(ActionEvent e) {
4 Date now = new Date();
5 System.out.println("At the tone,the time is " + now);
6 //beep为外部类的域字段
7 if (beep)
8 Tookkit.getDefaultToolkit().beep();
9 }
10 }
11 ActionListener l = new TimePrinter();
12 new Timer(interval,l).start();
13 }
局部类同样可以访问函数内部的局部变量,但是要求该变量必须是final的。
2 class TimePrinter implements ActionListener {
3 public void actionPerformed(ActionEvent e) {
4 Date now = new Date();
5 System.out.println("At the tone,the time is " + now);
6 //beep为外部函数的局部变量。
7 if (beep)
8 Tookkit.getDefaultToolkit().beep();
9 }
10 }
11 ActionListener l = new TimePrinter();
12 new Timer(interval,l).start();
13 }
为了规避局部类只能访问final局部变量的限制,既一次赋值之后不能再被重新赋值。但是我们可以通过数组的方式进行巧妙的规避,在下例中数组counter对象本身是final的,因此他不可以被重新赋值,然而其引用的数组元素则可以被重新赋值,见下例:
2 final int[] counter = new int[1];
3 for (int i = 0; i < dates.length; ++i) {
4 dates[i] = new Date() {
5 public int compareTo(Date other) {
6 //这里如果counter不是数组,而是被定义为final int counter,
7 //则会导致编译失败。
8 counter[0]++;
9 return super.compareTo(other);
10 }
11 }
12 }
13 }
C++中同样可以做到这些,其规则和Java的主要差异为C++的内部类无法直接访问外部类的任何成员。
2 public:
3 void testOuter() {
4 class FunctionInnerClass {
5 public:
6 void test() {
7 printf("This is FunctionInnerClass.\n");
8 }
9 };
10 FunctionInnerClass innerClass;
11 innerClass.test();
12 }
13 };
14
15 int main()
16 {
17 OuterClass outer;
18 outer.testOuter();
19 return 0;
20 }
4) 匿名内部类,其基本规则和局部内部类相似,差别在于该内部类不能有声明构造函数,这主要是因为Java要求类的构造函数和类名相同,而匿名内部类自身没有类名,因此在new新对象的时候,传入的构造函数参数为超类的构造函数参数。C++中不支持匿名类。见下例:
2 ActionListener l = new ActionListener() {
3 public void actionPerformed(ActionEvent e) {
4 Date now = new Date();
5 System.out.println("At the tone,the time is " + now);
6 //beep为外部函数的局部变量。
7 if (beep)
8 Tookkit.getDefaultToolkit().beep();
9 }
10 }
11 new Timer(interval,l).start();
12 }
5) 静态内部类,其功能和C++中的嵌套类非常相似,但是和Java自身的非静态内部类之间还是存在一些差异,如静态内部类不能直接访问外围类的对象引用域字段,但是可以访问外部类的static域字段(包括private)。在Java中只有内部类可以被定义为static的,外围类是不可以这样定义的。
2 private static boolean classField = false;
3 private boolean objectField = false;
4 static class InnerClass {
5 public void test() {
6 //这里由于classField是静态域字段,所以静态内部类可以直接访问,
7 //但是对于objectField对象域字段而言,由于静态内部类中没有包含
8 //外部类的引用,因此不能直接访问objectField.
9 if (classField)
10 System.out.println("Hello.");
11 }
12 }
13
14 public static void main(String[] args) {
15 classField = true;
16 new InnerClass().test();
17 }
18 }
以下示例中的内部类只能是静态内部类,因为该外部类的静态方法在返回内部类的实例时,无法将一个外部类的对象引用传递给该内部类,因为必须要求该内部类为静态内部类,否则将会报编译错误。
2 static class InnerClass {
3 public void test() {
4 System.out.println("Hello.\n");
5 }
6 }
7
8 private static InnerClass createInnerClass() {
9 return new InnerClass();
10 }
11 public static void main(String[] args) {
12 createInnerClass().test();
13 }
14 }
如果InnerClass不是静态内部类,则需要将上例改写为:
2 class InnerClass {
3 public void test() {
4 System.out.println("Hello.\n");
5 }
6 }
7
8 private static InnerClass createInnerClass() {
9 //为了确保InnerClass可以得到外部类的对象引用。
10 return new TestMain().new InnerClass();
11 }
12 public static void main(String[] args) {
13 createInnerClass().test();
14 }
15 }
6) 代理类:通过以下代码step by step解释代理类的机制
2 import java.lang.reflect.Proxy;
3 import java.util.Arrays;
4 import java.util.Random;
5
6 public class TestMain {
7 public static void main(String[] args) {
8 Object[] elements = new Object[1000];
9 for (int i = 0; i < elements.length; ++i) {
10 Integer v = i + 1;
11 //h(调用处理接口)是代理类的核心处理单元。由于代理类对象只是包含了InvocationHandler
12 //这样一个对象实例,并且是存放于超类Proxy中的,而实际的被代理实例必须存放于InvocationHandler
13 //的实现类中,如这里的Integer对象v。其中的核心代理代码也是在InvocationHandler子类的
14 //invoke方法中完成的。
15 InvocationHandler h = new TraceHandler(v);
16 //1. 第一个参数表示ClassLoader,这里使用缺省加载器,因此传入null即可。
17 //2. 第二个参数表示该代理类需要implement的接口数组(Java中可以实现多个接口)。
18 //3. 调用处理器接口,是代理类如果实现代理的核心,后面会介绍该类。
19 //4. 将该代理类作为Integer的代理存入数组。
20 elements[i] = Proxy.newProxyInstance(null, new Class[] {Comparable.class}, h);
21 }
22 Integer key = new Random().nextInt(elements.length) + 1;
23 //1. 由于代理类也都实现Comparable接口,因此可以用于Arrays.binarySearch中。
24 //2. 对代理类进行二分查找的比较时,将会直接调用代理类的compareTo方法。
25 //3. 该自动生成的Proxy的子类,其中的compareTo方法会将所有外部调用的信息,连同
26 // 方法名一并传给其内部调用处理器对象的invoke方法,并调用该方法(invoke).
27 //4. 这里Proxy子类会将所有实例化时指定接口(Comparable)的方法(compareTo),以及
28 // Object中toString、equals和hashCode方法的调用都会传递给调用处理器的invoke方法。
29 //5. 因此在输出结果中不仅可以看到compareTo方法的调用被打印出,toString也可打印。
30 int result = Arrays.binarySearch(elements, key);
31 if (result >= 0)
32 System.out.println(elements[result]);
33 }
34 }
35
36 class TraceHandler implements InvocationHandler {
37 //由于Proxy的子类是动态生成的,其具体的实现也是编译器动态生成后传给JVM的。
38 //因此这里是整个代理机制中唯一存放被代理对象的地方。
39 public TraceHandler(Object t) {
40 target = t;
41 }
42
43 //在此例中,该方法是被Comparable接口中的compareTo方法调用的,该实现逻辑是位于该
44 //动态生成的Proxy子类中,如
45 //public MyProxy extends Proxy implements Comparable {
46 // int compareTo(Object other) { h.invoke(...); }
47 @Override
48 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
49 //打印出实际被调用方法的名称和参数值。
50 System.out.print(target);
51 System.out.print("." + method.getName() + "(");
52 if (args != null) {
53 for (int i = 0; i < args.length; ++i) {
54 System.out.print(args[i]);
55 if (i < args.length - 1)
56 System.out.print(", ");
57 }
58 }
59 System.out.println(")");
60 //交给被代理类做实际的比较。
61 return method.invoke(target, args);
62 }
63 private Object target = null;
64 }
65 /* 输出结果如下:
66 500.compareTo(128)
67 250.compareTo(128)
68 125.compareTo(128)
69 187.compareTo(128)
70 156.compareTo(128)
71 140.compareTo(128)
72 132.compareTo(128)
73 128.compareTo(128)
74 128.toString()
75 128 */