OCA/OCP Java Note (9): Class Design (1)

1. Introducing Class Inheritance

1.1. Applying Class Access Modifiers

  一个文件中可以有多个类,但最多只能有一个public类。

1.2. Defining Constructors

1.2.1. Understanding Compiler Enhancements

  可以在子类的构造器中通过 super() 显式地调用父类的构造器。如果使用 super() , super() 就必须出现子类造器的在第一行。如果没有使用 super() ,编译器会自动在子类构造器开头插入对父类无参数构造器的调用。

eg. 

上面的三种定义是等效的,编译器会把前两种转换为最后一种:  

  如果父类没有无参构造器,编译器会要求子类构造器显式地通过super()调用了父类的某个构造器。

eg. 

下面的Elephant没有构造器,编译器自动插入一个无参构造器,同时在该构造器这种插入对父类Mammal无参构造器的调用,由于Mammal没有无参构造器,导致编译错误。

下面的Elephant显式地给出了无参构造器,编译器会在其中插入对父类Mammal无参构造器的调用,由于Mammal没有无参构造器,导致编译错误。

下面的Elephant在无参构造器中显式地通过super(10)调用了父类Mammal的构造器,可以编译。

1.2.2. Reviewing Constructor Rules

  构造器须遵守以下规则:

  1. 构造器的第一条语句是使用this()调用同类的其他构造器,或者使用super()调用直接父类的构造器。
  2. super()不能在构造器的第一条语句之后使用。
  3. 如果在构造器中没有使用super(),则Java会在构造器第一行插入一个没有参数的super()。
  4. 如果父类没有无参构造器,而子类又没有定义构造器,则编译器会抛出错误。
  5. 如果父类没有无参构造器,编译器要求子类的所有构造器都显式地调用父类的构造器。

1.3. Inheriting Methods

1.3.1. Overriding a Method

  重写方法须遵守以下规则:

  1. 子类的方法必须与父类的方法具有相同的名称和签名。
  2. 子类的方法必须与父类的方法具有相同或更广的访问级别。
  3. 子类的方法不能抛出新的或比父类方法更广的检查异常(checked exception)。
  4. 如果方法有返回值,子类方法返回值的类型必须与父类相同,或者是父类方法返回类型的子类,即协变返回类型(covariant return types)。

eg. 

Snake相比于父类Reptile, hasLegs() 没有抛出异常, getWeight() 抛出的异常是父类方法抛出异常的子类,代码可以正常编译。对于子类的重写方法,可以抛出比父类更少或范围更窄的异常,因为子类的方法可能已经对异常进行了处理。

1.3.2. Redeclaring private Methods

  子类不能重写父类中的private方法,在子类中声明与父类具有相同名称和签名的private方法,相当于在子类中新建了方法,不属于方法重写,不适用方法重写的各种限制。

eg. 

BactrianCamel中的 getNumberOfHumps() 返回值类型与父类同名方法不一致,代码可以编译,因为父类的 getNumberOfHumps() 是private,子类无法访问,子类的 getNumberOfHumps() 不属于方法重写。

1.3.3. Hiding Static Methods

  在子类中声明与父类具有相同名称和签名的静态方法,称为方法隐藏,方法隐藏需要遵守方法重写的规则。如果父类方法标记为static,则子类方法也必须标记为static(方法隐藏);如果父类方法没有标记为static,则子类方法也不能标记为static(方法重写)。

eg. 

第11行无法编译,因为父类的 sneeze() 是static,故子类的 sneeze() 也必须是static;第11行无法编译,因为父类的 hibernate() 不是static,故子类的 hibernate() 也不能是static。

1.3.4. Overriding vs. Hiding Methods

  对于重写方法,无论在何处调用,子类的重写方法都会替代父类方法被调用;而对于隐藏方法,只有在子类内才会被调用。
eg. 下面的例子中,子类重写了 isBiped() :

输出是:

因为子类重写了 isBiped() , joey.getMarsupialDescription() 在继承自父类的方法中调用了子类重写的 isBiped() 。

eg. 下面的例子中,子类隐藏了 isBiped() :

输出是:

子类隐藏了 isBiped() , joey.getMarsupialDescription() 在子类实例上调用继承自父类的方法,其中调用的是父类自己的 isBiped() 。

1.3.5. Creating final methods

  final方法不能被重写或隐藏(无论子类的方法是否使用final),否则会导致编译错误。

1.4. Inheriting Variables

1.4.1. Hiding Variables

  在子类中定义与父类中具有相同名称的变量,会在子类的实例中创建该变量的两份拷贝,一份用于父类引用,一份用于子类引用。

eg. 

输出为:

2. Creating Abstract Classes

2.1. Defining an Abstract Class

  抽象方法必须定义在抽象类里,且不能有实现。

eg. 

第2行在非抽象类里声明抽象方法,无法编译;第6行和第7行给出了抽象方法的实现,无法编译。

  不能把抽象类或者抽象方法标记为final或private。

eg. 

  对于子类实现的抽象方法,不能有比抽象父类方法更严格的访问限制。

eg. 

2.2. Creating a Concrete Class

  不能直接实例化抽象类。抽象类的具体子类必须实现抽象父类的所有抽象方法。

2.3. Extending an Abstract Class

  使用抽象类继承抽象类,抽象子类可以选择实现或者不实现父类的抽象方法。

2.4. Abstract Class Definition Rules:

  1. 抽象类不直接实例化。
  2. 抽象类中可以定义零个或多个的抽象或非抽象方法。
  3. 抽象类不能标记为private或final。
  4. 一个抽象类继承了另一个抽象类,则该抽象子类继承了父类的所有抽象方法,作为其自己的抽象方法。
  5. 抽象类的直接具体子类必须实现其继承的抽象方法。

2.5. Abstract Method Definition Rules

  1. 抽象方法只能在抽象类中定义。
  2. 抽象方法不能标记为private或final。
  3. 在抽象类中,不能对所定义的抽象方法提供实现。
  4. 在子类中实现抽象方法,遵守方法重写的规则,如方法名称和签名须一致,不能使用更严格的访问限制等。