OCA/OCP Java Note (9): Class Design (1)
Contents
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.
public class Donkey { } public class Donkey { public Donkey() { } } public class Donkey { public Donkey() { super(); } }
上面的三种定义是等效的,编译器会把前两种转换为最后一种:
如果父类没有无参构造器,编译器会要求子类构造器显式地通过super()调用了父类的某个构造器。
eg.
下面的Elephant没有构造器,编译器自动插入一个无参构造器,同时在该构造器这种插入对父类Mammal无参构造器的调用,由于Mammal没有无参构造器,导致编译错误。
public class Mammal { public Mammal(int age) { } } public class Elephant extends Mammal { // DOES NOT COMPILE }
下面的Elephant显式地给出了无参构造器,编译器会在其中插入对父类Mammal无参构造器的调用,由于Mammal没有无参构造器,导致编译错误。
public class Elephant extends Mammal { public Elephant() { // DOES NOT COMPILE } }
下面的Elephant在无参构造器中显式地通过super(10)调用了父类Mammal的构造器,可以编译。
public class Elephant extends Mammal { public Elephant() { super(10); } }
1.2.2. Reviewing Constructor Rules
构造器须遵守以下规则:
- 构造器的第一条语句是使用this()调用同类的其他构造器,或者使用super()调用直接父类的构造器。
- super()不能在构造器的第一条语句之后使用。
- 如果在构造器中没有使用super(),则Java会在构造器第一行插入一个没有参数的super()。
- 如果父类没有无参构造器,而子类又没有定义构造器,则编译器会抛出错误。
- 如果父类没有无参构造器,编译器要求子类的所有构造器都显式地调用父类的构造器。
1.3. Inheriting Methods
1.3.1. Overriding a Method
重写方法须遵守以下规则:
- 子类的方法必须与父类的方法具有相同的名称和签名。
- 子类的方法必须与父类的方法具有相同或更广的访问级别。
- 子类的方法不能抛出新的或比父类方法更广的检查异常(checked exception)。
- 如果方法有返回值,子类方法返回值的类型必须与父类相同,或者是父类方法返回类型的子类,即协变返回类型(covariant return types)。
eg.
public class InsufficientDataException extends Exception {} public class Reptile { protected boolean hasLegs() throws InsufficientDataException { throw new InsufficientDataException(); } protected double getWeight() throws Exception { return 2; } } public class Snake extends Reptile { protected boolean hasLegs() { return false; } protected double getWeight() throws InsufficientDataException{ return 2; } }
Snake相比于父类Reptile,hasLegs() 没有抛出异常,getWeight() 抛出的异常是父类方法抛出异常的子类,代码可以正常编译。对于子类的重写方法,可以抛出比父类更少或范围更窄的异常,因为子类的方法可能已经对异常进行了处理。
1.3.2. Redeclaring private Methods
子类不能重写父类中的private方法,在子类中声明与父类具有相同名称和签名的private方法,相当于在子类中新建了方法,不属于方法重写,不适用方法重写的各种限制。
eg.
public class Camel { private String getNumberOfHumps() { return "Undefined"; } } public class BactrianCamel extends Camel { private int getNumberOfHumps() { // 不属于方法重写 return 2; } }
BactrianCamel中的getNumberOfHumps() 返回值类型与父类同名方法不一致,代码可以编译,因为父类的getNumberOfHumps() 是private,子类无法访问,子类的getNumberOfHumps() 不属于方法重写。
1.3.3. Hiding Static Methods
在子类中声明与父类具有相同名称和签名的静态方法,称为方法隐藏,方法隐藏需要遵守方法重写的规则。如果父类方法标记为static,则子类方法也必须标记为static(方法隐藏);如果父类方法没有标记为static,则子类方法也不能标记为static(方法重写)。
eg.
public class Bear { public static void sneeze() { System.out.println("Bear is sneezing"); } public void hibernate() { System.out.println("Bear is hibernating"); } } public class Panda extends Bear { public void sneeze() { // DOES NOT COMPILE System.out.println("Panda bear sneezes quietly"); } public static void hibernate() { // DOES NOT COMPILE System.out.println("Panda bear is going to sleep"); } }
第11行无法编译,因为父类的sneeze() 是static,故子类的sneeze() 也必须是static;第11行无法编译,因为父类的hibernate() 不是static,故子类的hibernate() 也不能是static。
1.3.4. Overriding vs. Hiding Methods
对于重写方法,无论在何处调用,子类的重写方法都会替代父类方法被调用;而对于隐藏方法,只有在子类内才会被调用。
eg. 下面的例子中,子类重写了isBiped() :
class Marsupial { public boolean isBiped() { return false; } public void getMarsupialDescription() { System.out.println("Marsupial walks on two legs: "+isBiped()); } } public class Kangaroo extends Marsupial { public boolean isBiped() { return true; } public void getKangarooDescription() { System.out.println("Kangaroo hops on two legs: "+isBiped()); } public static void main(String[] args) { Kangaroo joey = new Kangaroo(); joey.getMarsupialDescription(); joey.getKangarooDescription(); } }
输出是:
Marsupial walks on two legs: true Kangaroo hops on two legs: true
因为子类重写了isBiped() ,joey.getMarsupialDescription() 在继承自父类的方法中调用了子类重写的isBiped() 。
eg. 下面的例子中,子类隐藏了isBiped() :
public class Marsupial { public static boolean isBiped() { return false; } public void getMarsupialDescription() { System.out.println("Marsupial walks on two legs: "+isBiped()); } } public class Kangaroo extends Marsupial { public static boolean isBiped() { return true; } public void getKangarooDescription() { System.out.println("Kangaroo hops on two legs: "+isBiped()); } public static void main(String[] args) { Kangaroo joey = new Kangaroo(); joey.getMarsupialDescription(); joey.getKangarooDescription(); } }
输出是:
Marsupial walks on two legs: false Kangaroo hops on two legs: true
子类隐藏了isBiped() ,joey.getMarsupialDescription() 在子类实例上调用继承自父类的方法,其中调用的是父类自己的isBiped() 。
1.3.5. Creating final methods
final方法不能被重写或隐藏(无论子类的方法是否使用final),否则会导致编译错误。
1.4. Inheriting Variables
1.4.1. Hiding Variables
在子类中定义与父类中具有相同名称的变量,会在子类的实例中创建该变量的两份拷贝,一份用于父类引用,一份用于子类引用。
eg.
public class Rodent { protected int tailLength = 4; public void getRodentDetails() { System.out.println("[parentTail="+tailLength+"]"); } } public class Mouse extends Rodent { protected int tailLength = 8; public void getMouseDetails() { System.out.println("[tail="+tailLength +",parentTail="+super.tailLength+"]"); } public static void main(String[] args) { Mouse mouse = new Mouse(); mouse.getRodentDetails(); mouse.getMouseDetails(); } }
输出为:
[parentTail=4] [tail=8,parentTail=4]
2. Creating Abstract Classes
2.1. Defining an Abstract Class
抽象方法必须定义在抽象类里,且不能有实现。
eg.
public class Chicken { public abstract void peck(); // DOES NOT COMPILE } public abstract class Turtle { public abstract void swim() {} // DOES NOT COMPILE public abstract int getAge() { // DOES NOT COMPILE return 10; } }
第2行在非抽象类里声明抽象方法,无法编译;第6行和第7行给出了抽象方法的实现,无法编译。
不能把抽象类或者抽象方法标记为final或private。
eg.
public final abstract class Tortoise { // DOES NOT COMPILE } public abstract class Goat { public abstract final void chew(); // DOES NOT COMPILE } public abstract class Whale { private abstract void sing(); // DOES NOT COMPILE }
对于子类实现的抽象方法,不能有比抽象父类方法更严格的访问限制。
eg.
public abstract class Whale { protected abstract void sing(); } public class HumpbackWhale extends Whale { private void sing() { // DOES NOT COMPILE System.out.println("Humpback whale is singing"); } }
2.2. Creating a Concrete Class
不能直接实例化抽象类。抽象类的具体子类必须实现抽象父类的所有抽象方法。
2.3. Extending an Abstract Class
使用抽象类继承抽象类,抽象子类可以选择实现或者不实现父类的抽象方法。
2.4. Abstract Class Definition Rules:
- 抽象类不直接实例化。
- 抽象类中可以定义零个或多个的抽象或非抽象方法。
- 抽象类不能标记为private或final。
- 一个抽象类继承了另一个抽象类,则该抽象子类继承了父类的所有抽象方法,作为其自己的抽象方法。
- 抽象类的直接具体子类必须实现其继承的抽象方法。
2.5. Abstract Method Definition Rules
- 抽象方法只能在抽象类中定义。
- 抽象方法不能标记为private或final。
- 在抽象类中,不能对所定义的抽象方法提供实现。
- 在子类中实现抽象方法,遵守方法重写的规则,如方法名称和签名须一致,不能使用更严格的访问限制等。