技术开发 频道

Java和C++在细节上的差异(三)

       4. 内部类:

       Java中内部类可以为私有内部类,既只有外部类可以访问该内部类,而Java外部类的可见性只有包可见和public两种。C++中的内部类比较类似于Java中的静态内部类,只是一种作用域限制的行为,以下为Java非静态内部类的说明:

  1) 内部类可以访问外部类的所有域成员和域字段,这也同样包括私有的字段和成员。

  2) Java的编译器在构造外部类调用内部类构造方法时,自动将外部类的this变量作为一个隐式参数传给了内部类的构造函数,内部类则在构造函数中保留了this变量的引用,该行为为编译器隐式行为。

1     public class Employee {
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的对象。

1     public class Employee {
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) 局部内部类的可见范围仅仅限于声明该局部类的函数内部,见如下代码:

1     public void start() {
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的。

1     public void start(final bool beep) {
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的,因此他不可以被重新赋值,然而其引用的数组元素则可以被重新赋值,见下例:

1     public void test() {
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++的内部类无法直接访问外部类的任何成员。

1     class OuterClass {
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++中不支持匿名类。见下例:

1     public void start(final bool beep) {
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的,外围类是不可以这样定义的。

1     public class TestMain {
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     }

  以下示例中的内部类只能是静态内部类,因为该外部类的静态方法在返回内部类的实例时,无法将一个外部类的对象引用传递给该内部类,因为必须要求该内部类为静态内部类,否则将会报编译错误。

1     public class TestMain {
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不是静态内部类,则需要将上例改写为:

1     public class TestMain {
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解释代理类的机制

1     import java.lang.reflect.InvocationHandler;
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        */
0
相关文章