Java面向对象程序设计--与C++对比说明:系列3(Java 继承机制)

时间:2023-03-09 09:59:09
Java面向对象程序设计--与C++对比说明:系列3(Java 继承机制)

继承(inheritance)背后的核心思想是:可以在现有类的基础上创建自己的新类,在新类中继承原来类的方法和数据域,并添加适合当前应用场景的新的数据和方法。

1. 类,超类,子类 (class,superclass,subclass):

Java 中的inheritance都是public inheritance,并不想C++中存在public,protected和private inheritance的分类。

class subclass extends superclass; 

这里有两个要点:

  • 子类是父类的一种"具体类型",子类是父类的子集,所以,任何子类的对象都属于父类的类型;
  • "子类具有父类的全部特征,并且在此基础上具有更多的特征",子类继承父类的所有字段和方法,同时子类又扩展了父类的能力;

将通用的方法放到父类当中,而将特有的方法放到子类当中,这是面向对象程序设计的要点之一,父类和子类的合理的功能划分是优秀设计的关键。

Java中的多态(polymorphism)和 C++ 的不同,在Java中不用将一个函数声明为虚函数,而在Java中要取消某个函数的多态功能,只要在函数声明前加上final关键字。

下面一段代码总结了类继承机制:

  1 /**
  2    @version 1.20 1998-09-17
  3    @author Cay Horstmann
  4 */
  5 
  6 import java.util.*;
  7 
  8 public class ManagerTest
  9 {  
 10    public static void main(String[] args)
 11    {  
 12       // construct a Manager object
 13       Manager boss = new Manager("Carl Cracker", 80000,
 14          1987, 12, 15);
 15       boss.setBonus(5000);
 16 
 17       Employee[] staff = new Employee[3];
 18 
 19       // fill the staff array with Manager and Employee objects
 20 
 21       staff[0] = boss;
 22       staff[1] = new Employee("Harry Hacker", 50000,
 23          1989, 10, 1);
 24       staff[2] = new Employee("Tommy Tester", 40000,
 25          1990, 3, 15);
 26 
 27       // print out information about all Employee objects
 28       for (int i = 0; i < staff.length; i++)
 29       {  
 30          Employee e = staff[i];
 31          System.out.println("name=" + e.getName()
 32             + ",salary=" + e.getSalary());
 33       }
 34    }
 35 }
 36 
 37 class Employee
 38 {  
 39    public Employee(String n, double s,
 40       int year, int month, int day)
 41    {  
 42       name = n;
 43       salary = s;
 44       GregorianCalendar calendar
 45          = new GregorianCalendar(year, month - 1, day);
 46          // GregorianCalendar uses 0 for January
 47       hireDay = calendar.getTime();
 48    }
 49 
 50    public String getName()
 51    {
 52       return name;
 53    }
 54 
 55    public double getSalary()
 56    {  
 57       return salary;
 58    }
 59 
 60    public Date getHireDay()
 61    {  
 62       return hireDay;
 63    }
 64 
 65    public void raiseSalary(double byPercent)
 66    {  
 67       double raise = salary * byPercent / 100;
 68       salary += raise;
 69    }
 70 
 71    private String name;
 72    private double salary;
 73    private Date hireDay;
 74 }
 75 
 76 class Manager extends Employee
 77 {  
 78    /**
 79       @param n the employee's name
 80       @param s the salary
 81       @param year the hire year
 82       @param month the hire month
 83       @param day the hire day
 84    */
 85    public Manager(String n, double s,
 86       int year, int month, int day)
 87    {  
 88       super(n, s, year, month, day);
 89       bonus = 0;
 90    }
 91 
 92    public double getSalary()
 93    { 
 94       double baseSalary = super.getSalary();
 95       return baseSalary + bonus;
 96    }
 97 
 98    public void setBonus(double b)
 99    {  
       bonus = b;
    }
 
    private double bonus;
 }

Java没有像C++那样提供多继承机制,但提供了接口机制,在后面我们将详细探究接口机制的实现。

2. Java 多态机制的具体实现:

Java中的父类和子类之间的"is-a"关系可以用一个substitution principle(替代原则来描述)。所谓"替代原则"是,在程序中任何一个期望出现父类对象地方都可以用一个子类对象

来代替!


Employee e;
e = new Employee(...);  //Employee object expected
e = new Manager(...);   //ok, Manager can be used as well

上面的一个Employee型的引用变量可以引用一个Manager型的对象。

Java中对象变量是多态的。

Java中的动态绑定,当对一个对象应用一个方法时,我们需要搞清楚到底这个过程是如何发生的,以一段代码开始:


 1 public class TestDynamic
 2 {
 3     public static void main(String[] args)
 4     {
 5         BaseClass base = new DerivedClass();
 6                 base.printMsg();
 7     }
 8 }
 9 
 class BaseClass
 {
     void printMsg()
         {
         System.out.println("This is the base class");
     }
 }
 
 class DerivedClass extends BaseClass
 {
     void printMsg()
         {
         System.out.println("This is the derived class");
     }
 }

这段代码在main函数中用父类对象变量引用了一个子类对象,然后调用父类和子类都有的printMsg方法,实验结果是:


This is the derived class

下面是与上面对象的C++代码:


 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class BaseClass
 6 {
 7     public:
 8         void printMsg()
 9         {
             cout<<"This is the base class"<<endl;
         }
 };
 
 class DerivedClass : public BaseClass
 {
     public:
         void printMsg()
         {
             cout<<"This is the derived class"<<endl;
         }    
 };
 
 int main()
 {
     BaseClass *base = new DerivedClass();
     base->printMsg();
     return ;
 }

这段代码的实验结果是:

This is the base class

若将base->printMsg(); 改成 ((DerivedClass *)base) -> printMsg(); 那么对应的结果是:

This is the derived class

可见java中的动态绑定要比C++中来的更加直接,根本不需要强制类型转换。

下面我们详述一个Java对象变量调用一个方法的整个过程:

  • x.f(param)这样的一个调用,x的实际类型是C,那么Java编译器就会找出所有C类型中和C的所有父类中f(...)的函数,这样编译器就知道的所有候选调用方法;
  • 编译器要确定参数param的类型,期间可能会用到类型的转换。这样编译器就知道需要调用的方法的名称和参数类型了。
  • 如果方法使用的是private,static或final修饰符来修饰的,那么变异器就知道这个方法,因为这是静态绑定。
  • 如果本类中找不到适合方法那么在父类中寻找。

动态绑定的特性使得Java具有了执行目前尚未实现的代码的可能,同样是Java EE的容器构建模型实现的重要基础.

3. 对继承的禁止: final类和方法

不能被继承的类被成为final类,在类的定义前加上final修饰符来表明这个类是一个final类。同样也可以将类中的一个方法声明为final的,这样任何子类都不能够重载这个方法。

注意,一个类中的数据域也可以被声明成final的,意思是一旦这个数据域被初始化之后,这个域将不能被修改。final修饰符对一个类的作用范围只是在类中的方法上,而不会在类

中的数据域上。

4. 强制类型转换:

使用强制类型转换的唯一理由是为了使用一个对象的全部功能,当我们暂时忘记了这个对象的类型的时候。

在java中任何一个对象变量都有一个类型,这个类型描述了变量指向的的是何种对象以及可以在上面进行的操作。

在进行强制类型转换的时候首先检查这个强制类型转换是否会成功是一个很好的主意!

下面对比Java和C++中强制类型转换机制:


 1 public class TestDynamic
 2 {
 3     public static void main(String[] args)
 4     {
 5         DerivedClass drive = new DerivedClass();
 6         BaseClass    base  = (BaseClass)drive;
 7                 base.printMsg();  //由于动态绑定的作用,这条语句还是打印"This is the derived class"
 8 
 9         BaseClass mybase = new BaseClass();
         DerivedClass myderived = (DerivedClass)mybase;
     }
 }
 
 class BaseClass
 {
     void printMsg()
         {
         System.out.println("This is the base class");
     }
 }
 
 class DerivedClass extends BaseClass
 {
     void printMsg()
         {
         System.out.println("This is the derived class");
     }
 }

结果输出:

This is the derived classException in thread "main" java.lang.ClassCastException: BaseClass cannot be cast to DerivedClass
    at TestDynamic.main(TestDynamic.java:10)

将父类对象强制转换成子类对象的时候就会出现上述的java.lang.ClassCastException的异常,为了避免上述异常的出现,可以

使用instanceof运算来检测对象的实际类型!

1 if(mybase instanceof DerivedClass)                 {
                     DerivedClass myderived = (DerivedClass)mybase;
         }
         else
         {
             System.out.println("Is not a instance of type DerivedClass");
         }

结果:

This is the derived classIs not a instance of type DerivedClass

关于C++中的强制类型转换请参考:http://www.cnblogs.com/jiangheng/p/3748051.html

5.抽象类(Abstract Class):(Java中的抽象类和C++中的抽象类对比)

抽象类的两个最大的意义在于:一是强制要求之子类中必须实现所要求的abstract方法,否则不能实例化; 而是限制对abstract类的实例化。这两个方面

可以形成相应的约束,从而减少出错的机会。


 1 /**
 2    @version 1.00 1999-12-17
 3    @author Cay Horstmann
 4 */
 5 
 6 import java.text.*;
 7 
 8 public class PersonTest
 9 {  
    public static void main(String[] args)
    {  
       Person[] people = new Person[];
 
       // fill the people array with Student and Employee objects
       people[] 
          = new Employee("Harry Hacker", );
       people[] 
          = new Student("Maria Morris", "computer science");
 
       // print out names and descriptions of all Person objects
       for (int i = ; i < people.length; i++)
       {  
          Person p = people[i];
          System.out.println(p.getName() + ", "
             + p.getDescription());
       }
    }
 }
 
 abstract class Person
 {  
    public Person(String n)
    {  
       name = n;
    }
 
    public abstract String getDescription();
 
    public String getName()
    {  
       return name;
    }
 
    private String name;
 }
 
 class Employee extends Person
 {  
    public Employee(String n, double s)
    {  
       // pass name to superclass constructor
       super(n);
       salary = s;
    }
 
    public double getSalary()
    {  
       return salary;
    }
 
    public String getDescription()
    {  
       NumberFormat formatter
          = NumberFormat.getCurrencyInstance();
       return "an employee with a salary of "
          + formatter.format(salary);
    }
 
    public void raiseSalary(double byPercent)
    {  
       double raise = salary * byPercent / ;
       salary += raise;
    }
 
    private double salary;
 }
 
 class Student extends Person
 {  
    /**
       @param n the student's name
       @param m the student's major
    */
    public Student(String n, String m)
    {  
       // pass n to superclass constructor
       super(n);
       major = m;
    }
 
    public String getDescription()
    {  
       return "a student majoring in " + major;
    }
 
    private String major;
 }

6. equals 方法:

Object类中的equals方法使用非常简单的方式来判断两个对象是否相等,即只判断两个对象是否指向同一个内存区域,也就是只有两个对象

变量指向同一个对象是才判断为相等。这在很多情况下是不能满足需要的。这就需要在类中自定义equals方法。

Java中的equals方法需要满足下面五个条件:

a) 反身性(reflexive): 对于每个non-null的引用x,x.equals(x)返回true;

b) 交换性(symmetric): x.equals(y) 是true,那么y.equals(x)也是true;

c) 传递性(transitive): x.equals(y) 是true,y.equals(z)是true,那么 x.equals(z)也是true;

d) 一致性(consistent): 如果x,y指向的对象没有改变,那么每次调用x.equals(y)方法得到的结果都是相同的;

e) 对于任何一个non-null的对象x, x.equals(null)的结果都是false;

编写Java类中的equals函数应该注意的事项:

1).将显示的参数命名为otherObject,以后将其转换为other;

2).if(this == otherObject) return true;

3).if(otherObject == null) return false;

4).比较this和otherObject的类型,如果equals的语义在子类中得到了改变,那么用getclass()进行测试:

             if(getclass() != otherObject.getclass()) return false;

      如果所有的子类都和父类遵循同样的equals语义,那么用instanseof测试:

             if(!(otherObject instanceof ClassName)) return false;

5) .将otherObject转换成主体类的形式:

ClassName other = (ClassName)otherObject;

6). 进行每个数据域的比较。原生数据类型用==符号来进行比较,类数据类型用每个类定义的equals方法来进行比较。

return field1 == other.field1 && field2.equals(other.field2) && ...

如果在子类中重定义equals方法,那么在子类的equals中super.equals(other).


1 public class TestEquals

 2 {
 3     public static void main(String[] args)
 4     {
 5         Manager man1 = new Manager("Bruce Lee",23,30000);
 6                 Manager man2 = new Manager("Bruce Lee",23,30000);
 7                 if(man1.equals(man2))
 8             System.out.println("Are you kidding me, There are two same manager in our company!");
 9     }
 }
 
 class Employee
 {
     public Employee(String aName,int aAge)
     {
         name = aName;
         age  = aAge;
     }
 
     public boolean equals(Object otherObject)
     {
         // The otherObject is the same object of current object
         if(this == otherObject) return true;
 
         if(otherObject == null) return false;
 
         // If the class don't match, they can not be equal
         if(getClass() != otherObject.getClass())
             return false;
     
         Employee other = (Employee)otherObject;
 
         return name.equals(other.name) && age == other.age;
     }
 
     private String name;
     private int age;
 }
 
 class Manager extends Employee
 {
     public Manager(String aName,int aAge,int aBonus)
     {
         super(aName,aAge);
                 bonus = aBonus;
     }
 
     public boolean equals(Object otherObject)
     {
         if(!super.equals(otherObject)) return false;
         
         Manager other = (Manager)otherObject;
         return bonus == other.bonus;        
     }    
     private int bonus;
 }

上面的代码中父类和子类判断相等的方式不相同,

if(getClass() != otherObject.getClass())

29             return false;

上面的语句将equals操作限定在了相同类型的对象之间;

7.HashCode方法:

下面是计算一个字符串的hashcode的代码:

 1 public class TestHash 2 {
 3     public static void main(String[] args)
 4     {
 5         String str = "Hello";
 6         int hash = 0;
 7         for(int i = 0; i < str.length(); i++)
 8         {
 9             hash = 31*hash + str.charAt(i);
         }
         System.out.println("The hashcode of string \""+str+"\" is "+hash);
     }
 }
  

hashCode 函数定义在Object类中,因此每一个对象都有一个默认的hashCode,并且这个hashCode是由这个对象

在内存中的地址推导出来的。

注意hashCode方法要和equals方法保持一致性!

8. 自动装箱和拆箱:

1)  Java中的基本数据类型如int,double等都不是对象数据类型,不继承自Object;

2)  如果需要使用Object类型的数字,则需要使用包装类Interger,Long,Double等,

但他们一旦被创建就不可改变,并且这些包装类是不可派生出子类的。

ArrayList<Type>中的Type不能是原生数据类型,而应该是一个派生自Object的对象类型。

import java.util.*;
public class TestArrayList
{
    public static void main(String[] args)
    {
        ArrayList<Person> per_list = new ArrayList<Person>();
        ArrayList<int> int_list = new ArrayList<int>();
    }
} class Person
{
    public Person(String aName,int aAge)
    {
        name = aName;
        age  = aAge;
    }     private String name;
    private int    age;
}

上面红色标记的代码导致了下面的错误:

TestArrayList.java:8: 错误: 意外的类型ArrayList<int> int_list = new ArrayList<int>();
                  ^
  需要: 引用
  找到:    int
TestArrayList.java:8: 错误: 意外的类型
        ArrayList<int> int_list = new ArrayList<int>();
                                                ^
  需要: 引用
  找到:    int
2 个错误