继上篇文章:Java和C++(www.cppentry.com)在细节上的差异:枚举与反射
六、接口与内部类:
1. 接口和抽象类:Java通过interface关键字来表示接口,接口中不能包含非静态域字段,所有的域成员均是公有的抽象方法,如Comparable接口,如果希望利用Arrays.sort方法,数组的成员必须实现该接口。抽象类中包含抽象方法,和接口一样抽象类也不能被实例化。
1) 接口不能被实例化,但是可以声明接口的变量指向其实现类的对象。
2) 每个类只能有一个超类,但是可以实现多个接口。
以下为Java的接口和抽象类的定义方式:
- public interface Comparable {
- int compareTo(Object other);
- }
-
- public interface Comparable<T> {
- int compareTo(T other);
- }
-
- abstract class Employee implements Comparable {
- public abstract int compareTo(Object other);
- }
在C++(www.cppentry.com)中同样存在接口和抽象类的概念,也和Java一样不能被实例化,但是并没有相应的关键字存在,而是以一种潜在规则的方式存在,见如下代码:
-
- class Comparable {
- public:
- virtual ~Comparable() {}
-
- virtual int compareTo(Comparable& other) = 0;
- }
-
-
- class Employee {
- public:
- virtual int compareTo(Comparable& other) { return 0; }
- virtual int backgroud() = 0;
-
- private:
- int _age;
- }
在C++(www.cppentry.com)的实现中,基于接口编程(www.cppentry.com),同时导出C接口的工厂方法对于跨编译器极为重要,该方式比较类似于Windows中的COM技术。
C++(www.cppentry.com)支持多重继承,因此也存在虚基类(菱形结构)等问题带来的负面影响,既子类的两个父类中同时存在相同签名的虚方法。见如下代码:
- class TcpServerTask {
- public:
- virtual void run() {}
- }
-
- class SentObjectTask {
- public:
- virtual void run() {}
- }
-
- class TcpServerSentTask : public TcpServerTask, public SentObjectTask { }
2. 对象克隆: Object对象中存在protected类型的clone方法,该方法将会完成子类对象clone的缺省操作,既对象域字段的浅拷贝,如果该对象的成员均为原始类型,如int、float等,或者为不可变类型,如String。这样的浅拷贝将能够达到对象clone的预期。换言之,如果对象内部存在可变对象的引用,浅拷贝将会带来原始对象和cloned对象引用相同对象引用的问题。如果希望避免该问题的发生,子类需要实现Cloneable接口。这里需要指出的是Cloneable接口并未提供clone方法,只是提供了一种契约签名。子类真正做的还是重载Object方法中的clone方法,由于Object中该方法为protected方法,所以caller不能直接调用它,只能将子类的clone方法声明为共有类型,caller才能调用。
-
- public class implements Cloneable {
-
- public Employee clone() throws CloneNotSupportedException {
- return (Employee)super.clone();
- }
- }
-
-
- public class Employee implements Cloneable {
- public Employee clone() throws CloneNotSupportedException {
-
- Employee cloned = (Employee)super.clone();
- cloned.hireday = (Date)hireday.clone();
- }
- private Date hireday;
- }
注:数组对象可以通过Array的clone(public)方法完成元素的拷贝。
在C++(www.cppentry.com)中由于并不存在Object这样的单根结构的框架,因此C++(www.cppentry.com)是以另外一种方式表现该问题的,既缺省拷贝构造和缺省等于操作符重载。和Java类似,这两个方法也是member bitwise拷贝的,但这是由编译器在生成对象模型时自动完成的缺省行为,如果该类重载了拷贝构造函数和等于操作符,在需要copy的时候则会调用重载后的方法,类的实现者应该在这两个方法中完成深拷贝。C++(www.cppentry.com)中还可以通过将这两个方法显示的声明为private类型的方法来禁用这种对象之间的copy行为,一旦出现,编译器将会在在编译器报错。在C++(www.cppentry.com)中还存在一个explicit的关键字,可以有效的防止编译器通过自行推演隐式的调用对象的拷贝构造函数和等于操作符函数,见如下代码:
-
-
- class Employee {
- private:
- char* _name;
- };
-
-
- class Employee {
- private:
- Employee(Employee& other);
- const Employee& operator= (Employee& other);
- private:
- char* _name;
- };
-
- class Employee {
- Employee(Employee& other);
- const Employee& operator= (Employee& other);
- private:
- char* _name;
- };
注:C++(www.cppentry.com)中有一种被称为引用计数的技术,经常会用在这个地方,以便提高对象copy的效率。
3. 接口与回调:严格意义上讲,回调这个属于更多的应用于C/C++(www.cppentry.com)这些支持基于过程编程(www.cppentry.com)的语言,Java中的回调是通过接口的方式来实现的,由于在接口的实现类中可以附带更多的信息,因此其表达能力要由于C/C++(www.cppentry.com)中的函数指针,见如下代码:
- public class Thread {
- public Thread(Runnable r) {}
- }
-
- public class MyTask implements Runnable {
- public MyTask(int taskID) {
- _taskID = taskID;
- }
-
- public void setOk(bool ok) {
- _ok = ok;
- }
-
- public void run() {}
- }
-
- public static void main(String[] args){
- MyTask t = new MyTask(5);
- Thread thrd = new Thread(t);
- t.setOk(true);
- thrd.start();
- }
这里的Runnable参数既为接口,Thread对象在启动的时候会调用该接口实现对象的run方法,但是在调用之前可以给该实现类传入更多的状态等相关数据,以便在线程类调用run方法时可以得到更多的信息。
以下为回调函数在C/C++(www.cppentry.com)中的实现:
- typedef int(*TestCallback)(int,int);
- int testCaller(TestCallback cb,int a,int b) {
- return cb(a,b);
- }
-
- int testCallback(int a,int b) {
- return a * b;
- }
-
- int main() {
- TestCallback cb = testCallback;
- return testCall(cb,5,6);
- }
在C++(www.cppentry.com)中还可以通过模板以更加松散的方式完成类似Java的基于接口的回调(Java的回调方式,C++(www.cppentry.com)完全可以做到),见如下代码:
- template<typename T>
- class Thread {
- public:
- Thread(T* r) _r = r {}
- void start() { if (_r) _r->run(); }
- private:
- T* _r;
- }
在以上的实现中,T无需是某个接口的实现类,只要保证该类型包含run()方法即可,注意:C++(www.cppentry.com)中的模板是引用才编译的方式,如果没有任何Thread<T>的声明,不会导致任何编译错误,只有当声明的类型对象中不包含run()方法时才会导致编译错误。
4. 内部类:Java中内部类可以为私有内部类,既只有外部类可以访问该内部类,而Java外部类的可见性只有包可见和public两种。C++(www.cppentry.com)中的内部类比较类似于Java中的静态内部类,只是一种作用域限制的行为,以下为Java非静态内部类的说明:
1) 内部类可以访问外部类的所有域成员和域字段,这也同样包括私有的字段和成员。
2) Java的编译器在构造外部类调用内部类构造方法时,自动将外部类的this变量作为一个隐式参数传给了内部类的构造函数,内部类则在构造函数中保留了this变量的引用,该行为为编译器隐式行为。
- public class Employee {
- public class InnerClass {
- bool test() {
-
- return _jobYears > 10;
- }
- }
-
- public Employee(int jobYears,String name) {
- _name = name;
- _jobYears = jobYears;
- _salary = 0;
- }
-
- public void raiseSalary() {
-
-
-
- InnerClass inner = new InnerClass();
- if (test())
- _salary += 1000;
- }
- private String _name;
- private int _jobYears;
- private int _salary;
- }
注:针对以上事例,内部类InnerClass可以通过Employee.this._jobYears的全称来显式的代替_jobYears > 10 中的_jobYears。反过来在raiseSalary方法中可以通过this.new InnerClass()语法格式更加明确的创建InnerClass的对象。
- public class Employee {
- public class InnerClass {
- bool test() {
-
- return Employee.this._jobYears > 10;
- }
- }
-
- public Employee(int jobYears,String name) {
- _name = name;
- _jobYears = jobYears;
- _salary = 0;
- }
-
- public void raiseSalary() {
-
-
-
-
- InnerClass inner = this.new InnerClass();
- if (test())
- _salary += 1000;
- }
- ......
- }
注:在外部类的作用域之外调用public内部类的语法为 OutClass.InnerClass。
3) 局部内部类的可见范围仅仅限于声明该局部类的函数内部,见如下代码:
- public void start() {
- class TimePrinter implements ActionListener {
- public void actionPerformed(ActionEvent e) {
- Date now = new Date();
- System.out.println("At the tone,the time is " + now);
-
- if (beep)
- Tookkit.getDefaultToolkit().beep();
- }
- }
- ActionListener l = new TimePrinter();
- new Timer(interval,l).start();
- }
局部类同样可以访问函数内部的局部变量,但是要求该变量必须是final的。
- public void start(final bool beep) {
- class TimePrinter implements ActionListener {
- public void actionPerformed(ActionEvent e) {
- Date now = new Date();
- System.out.println("At the tone,the time is " + now);
-
- if (beep)
- Tookkit.getDefaultToolkit().beep();
- }
- }
- ActionListener l = new TimePrinter();
- new Timer(interval,l).start();
- }
为了规避局部类只能访问final局部变量的限制,既一次赋值之后不能再被重新赋值。但是我们可以通过数组的方式进行巧妙的规避,在下例中数组counter对象本身是final的,因此他不可以被重新赋值,然而其引用的数组元素则可以被重新赋值,见下例:
- public void test() {
- final int[] counter = new int[1];
- for (int i = 0; i < dates.length; ++i) {
- dates[i] = new Date() {
- public int compareTo(Date other) {
-
-
- counter[0]++;
- return super.compareTo(other);
- }
- }
- }
- }
C++(www.cppentry.com)中同样可以做到这些,其规则和Java的主要差异为C++(www.cppentry.com)的内部类无法直接访问外部类的任何成员。
- class OuterClass {
- public:
- void testOuter() {
- class FunctionInnerClass {
- public:
- void test() {
- printf("This is FunctionInnerClass.\n");
- }
- };
- FunctionInnerClass innerClass;
- innerClass.test();
- }
- };
-
- int main()
- {
- OuterClass outer;
- outer.testOuter();
- return 0;
- }
4) 匿名内部类,其基本规则和局部内部类相似,差别在于该内部类不能有声明构造函数,这主要是因为Java要求类的构造函数和类名相同,而匿名内部类自身没有类名,因此在new新对象的时候,传入的构造函数参数为超类的构造函数参数。C++(www.cppentry.com)中不支持匿名类。见下例:
- public void start(final bool beep) {
- ActionListener l = new ActionListener() {
- public void actionPerformed(ActionEvent e) {
- Date now = new Date();
- System.out.println("At the tone,the time is " + now);
-
- if (beep)
- Tookkit.getDefaultToolkit().beep();
- }
- }
- new Timer(interval,l).start();
- }
5) 静态内部类,其功能和C++(www.cppentry.com)中的嵌套类非常相似,但是和Java自身的非静态内部类之间还是存在一些差异,如静态内部类不能直接访问外围类的对象引用域字段,但是可以访问外部类的static域字段(包括private)。在Java中只有内部类可以被定义为static的,外围类是不可以这样定义的。
- public class TestMain {
- private static boolean classField = false;
- private boolean objectField = false;
- static class InnerClass {
- public void test() {
-
-
-
- if (classField)
- System.out.println("Hello.");
- }
- }
-
- public static void main(String[] args) {
- classField = true;
- new InnerClass().test();
- }
- }
以下示例中的内部类只能是静态内部类,因为该外部类的静态方法在返回内部类的实例时,无法将一个外部类的对象引用传递给该内部类,因为必须要求该内部类为静态内部类,否则将会报编译错误。
- public class TestMain {
- static class InnerClass {
- public void test() {
- System.out.println("Hello.\n");
- }
- }
-
- private static InnerClass createInnerClass() {
- return new InnerClass();
- }
- public static void main(String[] args) {
- createInnerClass().test();
- }
- }
如果InnerClass不是静态内部类,则需要将上例改写为:
- public class TestMain {
- class InnerClass {
- public void test() {
- System.out.println("Hello.\n");
- }
- }
-
- private static InnerClass createInnerClass() {
-
- return new TestMain().new InnerClass();
- }
- public static void main(String[] args) {
- createInnerClass().test();
- }
- }
6) 代理类:通过以下代码step by step解释代理类的机制
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Proxy;
- import java.util.Arrays;
- import java.util.Random;
-
- public class TestMain {
- public static void main(String[] args) {
- Object[] elements = new Object[1000];
- for (int i = 0; i < elements.length; ++i) {
- Integer v = i + 1;
-
-
-
-
- InvocationHandler h = new TraceHandler(v);
-
-
-
-
- elements[i] = Proxy.newProxyInstance(null, new Class[] {Comparable.class}, h);
- }
- Integer key = new Random().nextInt(elements.length) + 1;
-
-
-
-
-
-
-
- int result = Arrays.binarySearch(elements, key);
- if (result >= 0)
- System.out.println(elements[result]);
- }
- }
-
- class TraceHandler implements InvocationHandler {
-
-
- public TraceHandler(Object t) {
- target = t;
- }
-
-
-
-
-
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
-
- System.out.print(target);
- System.out.print("." + method.getName() + "(");
- if (args != null) {
- for (int i = 0; i < args.length; ++i) {
- System.out.print(args[i]);
- if (i < args.length - 1)
- System.out.print(", ");
- }
- }
- System.out.println(")");
-
- return method.invoke(target, args);
- }
- private Object target = null;
- }
-
-
-
-
-
-
-
-
-
-
-