Composition and Inheritance


(来源:http://www.artima.com)

Objects and Java Seminar by Bill Venners
Lecture Handout

Agenda

  • Describe composition
  • Describe inheritance
  • Talk about the order of initialization
  • Touch on composition versus inheritance
  • Discuss the final keyword

Code Reuse in Java

When you need a class, you can:
  • Use an existing class as is
  • Write a brand new class
  • Reuse an existing class via inheritance
  • Reuse an existing class via composition

Composition vs. Inheritance

  • Use composition to enlist the help of other objects:

  • Use inheritance to model is-a relationships:

  • Can also add new members to subclasses

Composition Syntax

  • Instance variables that hold references to objects:
      1 package com.artima.examples.account.ex2;
      2 
      3 /**
      4 * Represents a bank account with overdraft protection. Instances
      5 * of this class are instantiated with a specified maximum
      6 * overdraft. If a client attempts to withdraw more than the
      7 * current account balance, the bank may loan the amount in
      8 * excess of the balance to the client. The overdraft maximum
      9 * passed to an <code>OverdraftAccount</code>'s constructor
     10 * is the maximum amount the bank will lend to the client in
     11 * this manner. When a client makes a deposit, the bank will
     12 * pay itself back first before increasing the account's balance.
     13 *
     14 * <p>
     15 * Money is stored in this account in integral units. Clients
     16 * can use this account to store any kind of value, such as money
     17 * or points, etc. The meaning of the integral units stored in
     18 * this account is a decision of the client that instantiates the
     19 * account. The maximum amount of units that can be stored as
     20 * the current balance of an <code>Account</code> is Long.MAX_VALUE.
     21 */
     22 public class OverdraftAccount {
     23 
     24     /**
     25     * Helper back-end object
     26     */
     27     private Account account = new Account();
     28 
     29     /**
     30     * The maximum amount the bank will loan to the client.
     31     */
     32     private final long overdraftMax;
     33 
     34     /**
     35     * The current amount the bank has loaned to the client
     36     * which has not yet been repaid. This must be zero to
     37     * overdraftMax.
     38     */
     39     private long overdraft;
     40 
     41     /**
     42     * Constructs a new <code>OverdraftAccount</code> with the
     43     * passed <code>overdraftMax</code>.
     44     *
     45     * @param overdraftMax the maximum amount the bank will loan
     46     *     to the client
     47     */
     48     public OverdraftAccount(long overdraftMax) {
     49         this.overdraftMax = overdraftMax;
     50     }
     51 
     52     /**
     53     * Returns the current overdraft, the amount the bank has
     54     * loaned to the client that has not yet been repaid.
     55     *
     56     * @returns the current overdraft
     57     */
     58     public long getOverdraft() {
     59         return overdraft;
     60     }
     61 
     62     /**
     63     * Returns the overdraft maximum, the maximum amount the
     64     * bank will allow the client to owe it. For each instance
     65     * of <code>OverdraftAccount</code>, the overdraft maximum
     66     * is constant.
     67     *
     68     * @returns the overdraft maximum
     69     */
     70     public long getOverdraftMax() {
     71         return overdraftMax;
     72     }
     73 
     74     /**
     75     * Gets the current balance of this <code>OverdraftAccount</code>
     76     *
     77     * @returns the current balance
     78     */
     79     public long getBalance() {
     80         return account.getBalance();
     81     }
     82 
     83     /**
     84     * Withdraws exactly the passed amount from the
     85     * <code>Account</code>. If the passed amount is
     86     * less than or equal to the current balance, all withdrawn
     87     * funds will be taken from the balance, and the balance
     88     * will be decremented by the passed amount. If the passed amount
     89     * exceeds the current balance, the bank may loan the client the
     90     * difference. The bank will make the loan only if the difference
     91     * between the passed amount and the balance is less than or equal to 
     92     * the available overdraft. The available overdraft is equal to
     93     * the current overdraft (the amount already loaned to the client and
     94     * not yet repaid), subtracted from the overdraft maximum, which
     95     * is passed to the constructor of any <code>OverdraftAccount</code>.
     96     *
     97     * <p>
     98     * If the passed amount less the current balance is less than or equal
     99     * to the available overdraft, the <code>withdraw</code> method returns
    100     * the requested amount, sets the current balance to zero, and records
    101     * the loan. Otherwise, if the passed amount less the current balance
    102     * exceeds the available overdraft, the <code>withdraw</code> method throws
    103     * <code>InsufficientFundsException</code>.
    104     *
    105     * @param amount amount to withdraw
    106     * @returns amount withdrawn from the <code>Account</code>
    107     * @throws InsufficientFundsException if the <code>Account</code>
    108     *     contains insufficient funds for the requested withdrawal
    109     */ 
    110     public long withdraw(long amount)
    111         throws InsufficientFundsException {
    112 
    113         long balance = account.getBalance();
    114         if (balance >= amount) {
    115 
    116             // Balance has sufficient funds, just take the
    117             // money from the balance.
    118             balance -= amount;
    119             return amount;
    120         }
    121 
    122         long shortfall = amount - balance;
    123         long extraAvailable = overdraftMax - overdraft;
    124 
    125         if (shortfall > extraAvailable) {
    126             throw new InsufficientFundsException(shortfall - extraAvailable);
    127         }
    128         overdraft += shortfall;
    129         account.withdraw(amount - shortfall);
    130         
    131         return amount;
    132     }
    133 
    134     /**
    135     * Deposits exactly the passed amount into the <code>Account</code>. 
    136     * If the current overdraft is zero, the balance will be increased
    137     * by the passed amount. Otherwise, the bank will attempt to pay
    138     * off the overdraft first, before increasing the current balance
    139     * by the amount remaining after the overdraft is repaid, if any.
    140     *
    141     * <p>
    142     * For example, if the balance is 200, the overdraft is 100, and the
    143     * <code>deposit</code> method is invoked with a passed <code>amount</code>
    144     * of 50, the bank would use all 50 of those monetary units to pay down
    145     * the overdraft. The overdraft would be reduced to 50 and the balance would
    146     * remain at 200. If subsequently, the client deposits another 100 units,
    147     * the bank would use 50 of those units to pay off the overdraft loan and
    148     * direct the remaining 50 into the balance. The new overdraft would 
    149     * be 0 and the new balance would be 250.
    150     * 
    151     * @param amount amount to deposit
    152     * @throws ArithmeticException if requested deposit would cause the
    153     *     balance of this <code>Account</code> to exceed Long.MAX_VALUE.
    154     */
    155     public void deposit(long amount) {
    156         if (overdraft > 0) {
    157             if (amount < overdraft) {
    158                 overdraft -= amount;
    159             }
    160             else {
    161                 long diff = amount - overdraft;
    162                 overdraft = 0;
    163                 account.deposit(diff);
    164             }
    165         }
    166         else {
    167             account.deposit(amount);
    168         }
    169     }
    170 }
    
     1 package com.artima.examples.account.ex2;
     2 
     3 /**
     4 * Represents a bank account. Money is stored in this account
     5 * in integral units. Clients can use this account to store
     6 * any kind of value, such as money or points, etc. The meaning
     7 * of the integral units stored in this account is a decision
     8 * of the client that instantiates the account. The maximum
     9 * amount of units that can be stored as the current balance of
    10 * an <code>Account</code> is Long.MAX_VALUE.
    11 */
    12 public class Account {
    13 
    14     /**
    15     * The current balance
    16     */
    17     private long balance;
    18 
    19     /**
    20     * Withdraws exactly the passed amount from the
    21     * <code>Account</code>. Subclasses must withdraw
    22     * at least the passed amount, but may effectively withdraw more.
    23     * For example, if a subclass includes the notion of
    24     * a withrawal fee, the subclass's implementation of
    25     * this method may charge that fee by decrementing it
    26     * from the account at the time of withdrawal.
    27     *
    28     * @param amount amount to withdraw
    29     * @returns amount withdrawn from the <code>Account</code>
    30     * @throws InsufficientFundsException if the <code>Account</code>
    31     *     contains insufficient funds for the requested withdrawal
    32     */ 
    33     public long withdraw(long amount)
    34         throws InsufficientFundsException {
    35 
    36         if (amount > balance) {
    37             throw new InsufficientFundsException(
    38                 amount - balance);
    39         }
    40 
    41         balance -= amount;
    42         return amount;
    43     }
    44 
    45     /**
    46     * Deposits exactly the passed amount into the <code>Account</code>. 
    47     * Subclasses may effectively deposit more or less than the passed
    48     * amount into the <code>Account</code>. For example, if a subclass
    49     * includes the notion of funds matching, the subclass implementation
    50     * of this method may match some or all of the deposited amount at
    51     * the time of deposit, effectively increasing the deposited amount.
    52     * Or, if a subclass includes the notion of
    53     * a deposit fee, the subclass's implementation of
    54     * this method may charge that fee by decrementing it
    55     * from the account at the time of withdrawal, effectively reducing
    56     * the deposited amount.
    57     * 
    58     * @param amount amount to deposit
    59     * @throws ArithmeticException if requested deposit would cause the
    60     *     balance of this <code>Account</code> to exceed Long.MAX_VALUE.
    61     */
    62     public void deposit(long amount) {
    63 
    64         // TO DO: Check for overflow
    65         balance += amount;
    66     }
    67 
    68     /**
    69     * Gets the current balance of this <code>Account</code>
    70     *
    71     * @returns the current balance
    72     */
    73     public long getBalance() {
    74         return balance;
    75     }
    76 }
    77 
    
  • Composition: can change the objects at run time
  • Inheritance: object relationships cast into stone at compile time
  • Composition: explicit forwarding or delegation of method invocations

Inheritance Syntax

  • Inheritance models the is-a relationship:




  • Use the extends keyword to indicate inheritance:
    class Account {
        //...
    }
    class OverdraftAccount extends Account {
        //...
    }
    class MoneyMarketAccount extends Account {
        //...
    }
    
  • Terms: types, family of types, superclass, subclass, direct superclass, supertype
  • Every class descends from java.lang.Object:
    // "extends Object" is optional
    class Account extends Object {
        //...
    }
    
  • Only one direct superclass (single inheritance)

What Inheritance Means

  • Subclass inherits interface and, by default, implementation of superclass
  • Inheritance of interface: subclass accepts same messages as superclass
  • Can substitute subclass for superclass (OverdraftAccount for Account)
  • By default, subclasses inherit superclass's implementation:

  • Only accessible superclass members can be "inherited," but subclass object image contains all superclass instance variables
  • Can invoke superclass implementation of overidden method with super reference

Inheritance Example

 1 package com.artima.examples.account.ex1;
 2 
 3 /**
 4 * Represents a bank account. Money is stored in this account
 5 * in integral units. Clients can use this account to store
 6 * any kind of value, such as money or points, etc. The meaning
 7 * of the integral units stored in this account is a decision
 8 * of the client that instantiates the account. The maximum
 9 * amount of units that can be stored as the current balance of
10 * an <code>Account</code> is Long.MAX_VALUE.
11 */
12 public class Account {
13 
14     /**
15     * The current balance
16     */
17     private long balance;
18 
19     /**
20     * Withdraws exactly the passed amount from the
21     * <code>Account</code>. Subclasses must withdraw
22     * at least the passed amount, but may effectively withdraw more.
23     * For example, if a subclass includes the notion of
24     * a withrawal fee, the subclass's implementation of
25     * this method may charge that fee by decrementing it
26     * from the account at the time of withdrawal.
27     *
28     * @param amount amount to withdraw
29     * @returns amount withdrawn from the <code>Account</code>
30     * @throws InsufficientFundsException if the <code>Account</code>
31     *     contains insufficient funds for the requested withdrawal
32     */ 
33     public long withdraw(long amount)
34         throws InsufficientFundsException {
35 
36         if (amount > balance) {
37             throw new InsufficientFundsException(
38                 amount - balance);
39         }
40 
41         balance -= amount;
42         return amount;
43     }
44 
45     /**
46     * Deposits exactly the passed amount into the <code>Account</code>. 
47     * Subclasses may effectively deposit more or less than the passed
48     * amount into the <code>Account</code>. For example, if a subclass
49     * includes the notion of funds matching, the subclass implementation
50     * of this method may match some or all of the deposited amount at
51     * the time of deposit, effectively increasing the deposited amount.
52     * Or, if a subclass includes the notion of
53     * a deposit fee, the subclass's implementation of
54     * this method may charge that fee by decrementing it
55     * from the account at the time of withdrawal, effectively reducing
56     * the deposited amount.
57     * 
58     * @param amount amount to deposit
59     * @throws ArithmeticException if requested deposit would cause the
60     *     balance of this <code>Account</code> to exceed Long.MAX_VALUE.
61     */
62     public void deposit(long amount) {
63 
64         // TO DO: Check for overflow
65         balance += amount;
66     }
67 
68     /**
69     * Gets the current balance of this <code>Account</code>
70     *
71     * @returns the current balance
72     */
73     public long getBalance() {
74         return balance;
75     }
76 }
77 

  1 package com.artima.examples.account.ex1;
  2 
  3 /**
  4 * Represents a bank account with overdraft protection. Instances
  5 * of this class are instantiated with a specified maximum
  6 * overdraft. If a client attempts to withdraw more than the
  7 * current account balance, the bank may loan the amount in
  8 * excess of the balance to the client. The overdraft maximum
  9 * passed to an <code>OverdraftAccount</code>'s constructor
 10 * is the maximum amount the bank will lend to the client in
 11 * this manner. When a client makes a deposit, the bank will
 12 * pay itself back first before increasing the account's balance.
 13 *
 14 * <p>
 15 * As described in class <code>Account</code>, money is stored in
 16 * this account in integral units. Clients
 17 * can use this account to store any kind of value, such as money
 18 * or points, etc. The meaning of the integral units stored in
 19 * this account is a decision of the client that instantiates the
 20 * account. The maximum amount of units that can be stored as
 21 * the current balance of an <code>Account</code> is Long.MAX_VALUE.
 22 */
 23 public class OverdraftAccount extends Account {
 24 
 25     /**
 26     * The maximum amount the bank will loan to the client.
 27     */
 28     private final long overdraftMax;
 29 
 30     /**
 31     * The current amount the bank has loaned to the client
 32     * which has not yet been repaid. This must be zero to
 33     * overdraftMax.
 34     */
 35     private long overdraft;
 36 
 37     /**
 38     * Constructs a new <code>OverdraftAccount</code> with the
 39     * passed <code>overdraftMax</code>.
 40     *
 41     * @param overdraftMax the maximum amount the bank will loan
 42     *     to the client
 43     */
 44     public OverdraftAccount(long overdraftMax) {
 45         this.overdraftMax = overdraftMax;
 46     }
 47 
 48     /**
 49     * Returns the current overdraft, the amount the bank has
 50     * loaned to the client that has not yet been repaid.
 51     *
 52     * @returns the current overdraft
 53     */
 54     public long getOverdraft() {
 55         return overdraft;
 56     }
 57 
 58     /**
 59     * Returns the overdraft maximum, the maximum amount the
 60     * bank will allow the client to owe it. For each instance
 61     * of <code>OverdraftAccount</code>, the overdraft maximum
 62     * is constant.
 63     *
 64     * @returns the overdraft maximum
 65     */
 66     public long getOverdraftMax() {
 67         return overdraftMax;
 68     }
 69 
 70     /**
 71     * Withdraws exactly the passed amount from the
 72     * <code>Account</code>. If the passed amount is
 73     * less than or equal to the current balance, all withdrawn
 74     * funds will be taken from the balance, and the balance
 75     * will be decremented by the passed amount. If the passed amount
 76     * exceeds the current balance, the bank may loan the client the
 77     * difference. The bank will make the loan only if the difference
 78     * between the passed amount and the balance is less than or equal to 
 79     * the available overdraft. The available overdraft is equal to
 80     * the current overdraft (the amount already loaned to the client and
 81     * not yet repaid), subtracted from the overdraft maximum, which
 82     * is passed to the constructor of any <code>OverdraftAccount</code>.
 83     *
 84     * <p>
 85     * If the passed amount less the current balance is less than or equal
 86     * to the available overdraft, the <code>withdraw</code> method returns
 87     * the requested amount, sets the current balance to zero, and records
 88     * the loan. Otherwise, if the passed amount less the current balance
 89     * exceeds the available overdraft, the <code>withdraw</code> method throws
 90     * <code>InsufficientFundsException</code>.
 91     *
 92     * @param amount amount to withdraw
 93     * @returns amount withdrawn from the <code>Account</code>
 94     * @throws InsufficientFundsException if the <code>Account</code>
 95     *     contains insufficient funds for the requested withdrawal
 96     */ 
 97     public long withdraw(long amount)
 98         throws InsufficientFundsException {
 99 
100         long balance = getBalance();
101         if (balance >= amount) {
102 
103             // Balance has sufficient funds, just take the
104             // money from the balance.
105             balance -= amount;
106             return amount;
107         }
108 
109         long shortfall = amount - balance;
110         long extraAvailable = overdraftMax - overdraft;
111 
112         if (shortfall > extraAvailable) {
113             throw new InsufficientFundsException(shortfall - extraAvailable);
114         }
115         overdraft += shortfall;
116         super.withdraw(amount - shortfall);
117         
118         return amount;
119     }
120 
121     /**
122     * Deposits exactly the passed amount into the <code>Account</code>. 
123     * If the current overdraft is zero, the balance will be increased
124     * by the passed amount. Otherwise, the bank will attempt to pay
125     * off the overdraft first, before increasing the current balance
126     * by the amount remaining after the overdraft is repaid, if any.
127     *
128     * <p>
129     * For example, if the balance is 200, the overdraft is 100, and the
130     * <code>deposit</code> method is invoked with a passed <code>amount</code>
131     * of 50, the bank would use all 50 of those monetary units to pay down
132     * the overdraft. The overdraft would be reduced to 50 and the balance would
133     * remain at 200. If subsequently, the client deposits another 100 units,
134     * the bank would use 50 of those units to pay off the overdraft loan and
135     * direct the remaining 50 into the balance. The new overdraft would 
136     * be 0 and the new balance would be 250.
137     * 
138     * @param amount amount to deposit
139     * @throws ArithmeticException if requested deposit would cause the
140     *     balance of this <code>Account</code> to exceed Long.MAX_VALUE.
141     */
142     public void deposit(long amount) {
143         if (overdraft > 0) {
144             if (amount < overdraft) {
145                 overdraft -= amount;
146             }
147             else {
148                 long diff = amount - overdraft;
149                 overdraft = 0;
150                 super.deposit(diff);
151             }
152         }
153         else {
154             super.deposit(amount);
155         }
156     }
157 }

Order of Initialization

  • Constructors aren't inherited (instead you get a default), because you need one in each class to initialize the instance variables of that class.
  • Defined order of initialization lets you know which variables get initialized first.

    package java.lang;
    
    public class Object {
        // Has no fields
        // Has several methods, not shown...
    }
    
    class Liquid {
    
        // Has two fields:
        private int mlVolume;
        private float temperature; // in Celsius
    
        // Has several methods, not shown...
    }
    
    class Coffee extends Liquid {
    
        // Has two fields:
        private boolean swirling;
        private boolean clockwise;
    
        // Has several methods, not shown...
    }
    


  • Here's a possible Coffee image on the heap.

  • Order of initialization should be:
    1. Object's fields (of which there are none)
    2. Liquid's fields (mlVolume and temperature)
    3. Coffee's fields (swirling and clockwise)
  • <init> methods called in bottom-up order, but instance variables still initialized in top-down order.
  • Therefore, can use superclass variables to initialize subclass variables, but watch out for overridden methods.

super() Invocations

  • If you don't explicitly invoke a superclass constructor, the compiler invokes the superclass's no-arg constructor by default.
     1 // In file compoinherit/examples/ex3/Liquid.java
     2 class Liquid {
     3 
     4     private int mlVolume;
     5     private float temperature; // in Celsius
     6 
     7     public Liquid(int mlVolume, float temperature) {
     8         this.mlVolume = mlVolume;
     9         this.temperature = temperature;
    10     }
    11 
    12     public float getTemperature() {
    13         return temperature;
    14     }
    15     // Has several other methods, not shown,
    16     // but doesn't include another constructor...
    17 }
    
  • To invoke a superclass constructor that takes arguments, use the super() keyword with parens.
     1 // In file compoinherit/examples/ex3/Coffee.java
     2 public class Coffee extends Liquid {
     3 
     4     private static final float BOILING_POINT = 100.0f; // Celsius
     5     private boolean swirling;
     6     private boolean clockwise;
     7 
     8     public Coffee(int mlVolume, float temperature,
     9         boolean swirling, boolean clockwise) {
    10 
    11         super(mlVolume, temperature);
    12         if (getTemperature() > BOILING_POINT) {
    13             // Leave swirling at default value: false
    14             return;
    15         }
    16         this.swirling = swirling;
    17         if (swirling) {
    18             this.clockwise = clockwise;
    19         } // else, leave clockwise at default value: false
    20     }
    21     // has several methods, not shown,
    22     // but doesn't override getTemperature()...
    23 }
    
  • The super() must be first, and there can be only one of them per constructor.
  • Can't call super() from methods, just from constructors.

The final Keyword

  • The final keyword as several meanings depending upon context.
  • In general, however, it means you can't change something.
  • Final fields and variables are constants (Once initialized, they won't change).
  • Final methods can't be overridden in subclasses.
  • Final classes can't be subclassed.

Final Fields

  • Static final fields are constants. Can also have final instance variables and "blank" finals:

     1 public class CoffeeCup {
     2
     3     // Compile-time constants
     4     public static final int MAX_SHORT_ML = 237;
     5     public static final int MAX_TALL_ML = 355;
     6     public static final int MAX_GRANDE_ML = 473;
     7
     8     // A run-time constant
     9     public static final int favoriteNumber = 
    10         (int) (Math.random() * 100);
    11
    12     // A blank final
    13     private final int size;
    14
    15     CoffeeCup(int size) {
    16         this.size = size;
    17     }
    18     // ...
    19 }
    
    1 public class Example {
    2
    3     public static void main(String[] args) {
    4         CoffeeCup cup =
    5             new CoffeeCup(CoffeeCup.MAX_TALL_ML); //...
    6     }
    7 }
    
  • A constant (static final fields) initialized to a compile-time constant is resolved at compile time.
  • Can use constants for switch case statements and to do conditional compilation with if statements.
  • Can also have final parameters and local variables:

    1 public class Example {
    2 
    3     public static void main(final String[] args) {
    4 
    5         final int i = 12;
    6         final CoffeeCup cup = new CoffeeCup(100);
    7         //...
    8     }
    9 }
    

Final Methods and Classes

  • Final methods can't be overridden in subclasses:

    1 public class Cup {
    2 
    3     public final void add(int amount) {
    4         // ...
    5     }
    6     // ...
    7 }
    
    1 // THIS WON'T COMPILE
    2 public class CoffeeCup extends Cup {
    3 
    4     public void add(int amount) {
    5         // ...
    6     }
    7     // ...
    8 }
    
  • A final class can't be subclassed:
    1 public final class CoffeeCup extends Cup {
    2 
    3     // The end of the line in this
    4     // branch of the family tree.
    5 }
    
  • Making methods and classes final is a severe restriction on clients. Use it for security reasons, or as a last resort performance tweak.

Exercises

Problem 1.

Create two classes, Shape and Circle. Have class Circle extend class Shape. Create a static method in class Shape called sayHello() that prints "Nice Form!" to the standard output. Declare a method with the same name and signature in class Circle that prints out "Well-rounded!" to the standard output. Create a class named Problem1 with a main() method that creates a Circle object and stores its reference in a variable of type Shape. Invoke sayHello() on the reference to the Circle object.

Problem 2.

Create two classes, Vehicle and Engine, with no-arg constructors. Vehicle's constructor should print "I'm a Vehicle!" to the standard output, and Engine's constructor should print "I'm an Engine!". Create a new class PickupTruck and declare it a subclass of Vehicle. Create an instance variable in PickupTruck that is of type Engine and initialize it (with an initializer) to a new instance of class Engine. Class PickupTruck should have no constructors or methods, just the instance variable. In the main() method of a class named Problem2, create an object of class PickupTruck. Run the Problem2 application and observe the results.

Problem 3.

Create two classes, Boat and Motor, each with one constructor that takes a single parameter of type String. Both of these constructors should simply print the passed String to the standard output. Create a new class SpeedBoat and declare it a subclass of Boat. Create an instance variable in SpeedBoat that is of type Motor and initialize it (with an initializer) to a new instance of class Motor. Pass the String, "I'm a Motor!" to the Motor constructor. Give SpeedBoat a constructor that takes a String parameter. SpeedBoat's constructor should have only one statement, a super() invocation that sends the passed parameter up to the superclass constructor. When you're done, class SpeedBoat should have only one constructor and one instance variable. In the main() method of a class named Problem3, create an object of class SpeedBoat, passing in the String, "I'm a SpeedBoat!". Run the Problem3 application and observe the results.

Problem 4.

Create an inheritance hierarchy of Rodent: Mouse, Gerbil, Hamster, etc. In the base class, provide methods that are common to all Rodents, and override these in the derived classes to perform different behaviors depending on the specific type of Rodents. In the main() method of a class named Problem4, create an array of Rodent, fill it with different specific types of Rodents, and call your base-class methods to see what happens.