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. 

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

  构造器须遵守以下规则:

  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. 

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:

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

2.5. Abstract Method Definition Rules

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