CSC224 Apr26

slide version

single file version

Contents

  1. Packages and Classpath
  2. Specifying the classpath
  3. Compilation
  4. Location of Generated Class Files
  5. Execution, classpath, and packages
  6. Inheritance Introduction
  7. Example (from text, with a few changes)
  8. Creating a subclass: SavingsAccount
  9. Example: BankApp.java
  10. Compatibility of Types
  11. "Down casting"
  12. Cast Exceptions
  13. Polymorphic Types
  14. Checking the runtime type
  15. Interfaces
  16. Inheritance, Classes, and Interfaces
  17. Iterator interface
  18. Fibonacci Example

Packages and Classpath[1] [top]

The classpath specifies where to look for class files. Can be specified for execution (java).

A package is a way to qualify the name of a class or group of classes. It also imposes a condition on where the .java and .class files for the class can be. A package name is typically a directory path with all the "/" (or "\") directory separators replaced by "." The fully qualified class name includes the package name, another ".", and then the class name.

A package statement at the beginning of a .java file declares the class to be in the named package. If there is no package statement, the class is in the default package which has no name.

Specifying the classpath[2] [top]

The classpath can be specified by setting the environment variable CLASSPATH.

The -classpath (or -cp) option for the java and javac tools can also specify the classpath. The classpath specified in this way takes precedence over the CLASSPATH setting.

Compilation[3] [top]

The command to use is javac:

      javac argument
    

But the argument to use depends on the current working directory. It must specify the java file.

The working directory can be any directory as long as you specify the location of the java file.

Location of Generated Class Files [4] [top]

If the BankAccount.java is in

      c:\user\csc224\bankproj\src\bank
    

and the current directory is c:\user\csc224, the command

      javac bankproj/src/bank/BankAccount.java
    

will generate the BankAccount.class file in the same directory as BankAccount.java.

To override this default location and place the BankAccount.class in the bin directory:

      c:\user\csc224\bankproj\bin\bank
    

use the javac option -d

      javac -d bankproj/bin bankproj/src/bank/BankAccount.java
    

Note:

Execution, classpath, and packages[5] [top]

The java program (i.e., the java virtual machine) expects its argument to be a fully qualified class name.

      java options fully_qualified_class_name
    

If the class is not in a package (i.e., no package statement), the fully qualified name is just the class name.

If the class is in a package, the fully qualified name includes the package and the class name.

So in the example, the fully qualified name is

bank.Bank

Note that unlike the argument to the javac command, the argument to java is not the relative or absolute path to the .class file!

Inheritance Introduction[6] [top]

1. Inheritance can be viewed as a technique for creating a new class from
an existing class.

2. Classes define "types". If B is a new class created by
inheritance from an existing class A, the informally:

  - "type" B is a kind of A
  - B is a subtype of A
  
    

Example (from text, with a few changes)[7] [top]


public class BankAccount
{
  private double balance;

  public BankAccount()
  {
    balance = 0.0;
  }
  public BankAccount(double initialAmount) {
    balance = initialAmount;
  }
  public void deposit(double amount)
  {
    balance += amount;
  }
  public double getBalance()
  {
    return balance;
  }
  public void withdraw(double amount) {
    if (balance >= amount) {
      balance -= amount;
    } else {
      throw new IllegalArgumentException("Insufficent funds");
    }
  }
  public String toString()
  {
    return String.format("BankAccount(balance = %.2f)", balance);
  }
}

    

Creating a subclass: SavingsAccount[8] [top]

public class SavingsAccount extends BankAccount
{
  private double interestRate;
  
  public SavingsAccount(double amount, double rate)
  {
    super(amount);
    interestRate = rate;
  }

  public void postInterest()
  {
    double balance = getBalance();
    double interest = balance * interestRate/100;
    deposit(interest);
  }
  public double getRate()
  {
    return interestRate;
  }
  void setRate(double newRate)
  {
    interestRate = newRate;
  }

  public String toString()
  {
    return String.format("Savings Account(balance = %.2f, rate = %.3f)",
			       getBalance(), interestRate);
  }
}

    

Example: BankApp.java[9] [top]


public class BankApp
{
  
  public static void main(String[] args)
  {
    BankAccount b1 = new BankAccount(1000);
    BankAccount b2 = new BankAccount(20000);
    SavingsAccount s1 = new SavingsAccount(500, 2.5);

    b1.deposit(100);
    b2.deposit(1000);
    s1.postInterest();
    s1.deposit(100);

    System.out.println(b1);
    System.out.println(b2);
    System.out.println(s1);

  }

Sample run:

java BankApp
BankAccount(balance = 1100.00)
BankAccount(balance = 21000.00)
Savings Account(balance = 612.50, rate = 2.500)

}

    

Compatibility of Types[10] [top]

A SavingsAccount is a kind of BankAccount. It has a balance,
etc.
public class BankApp2
{
  
  public static void printAccounts(BankAccount[] acct)
  {
    for(int i = 0; i < acct.length; i++) {
      System.out.println(acct[i]);
    }
  }

  public static void main(String[] args)
  {
    BankAccount[] b = new BankAccount[3];
    BankAccount b1 = new BankAccount(1000);
    BankAccount b2 = new BankAccount(20000);
    SavingsAccount s1 = new SavingsAccount(500, 2.5);

    b[0] = b1;
    b[1] = b2;
    b[2] = s1;

    System.out.println("Original Balances:");
    printAccounts(b);

    // Make deposits:
    for(int i = 0; i < b.length; i++) {
      b[i].deposit(100);
    }

    System.out.println("\nNew Balances:");
    printAccounts(b);

  }

}

Sample run:
java BankApp2
Original Balances:
BankAccount(balance = 1000.00)
BankAccount(balance = 20000.00)
Savings Account(balance = 500.00, rate = 2.500)

New Balances:
BankAccount(balance = 1100.00)
BankAccount(balance = 20100.00)
Savings Account(balance = 600.00, rate = 2.500)


    

"Down casting"[11] [top]

Suppose we wish to add a half point to the interest rate for b[2].

We can't write
	BankAccount[] b = new BankAccount[3];

	double newRate = b[2].getRate() + 0.5;
	b[2].setRate( newRate);

We get errors at both b[2].getRate() and b[2].setRate because the
declared type of b[2] is BankAccount, not SavingsAccount.

    Any SavingsAccount is  a kind of BankAccount

but
    
    A BankAccount is not necessarily a SavingsAccount.

We can try a cast:

	((SavingsAccount) b[2]).getRate() 

This is called a "downcast" because SavingsAccount is a
subclass of BankAccount and we are casting the type to a subtype.


              BankAccount
                  |
                  |
              SavingsAccount
    

Cast Exceptions[12] [top]

The cast above works, but this one will not:

	((SavingsAccount) b[1]).getRate() 

because at runtime, b[1] is not a SavingsAccount, it is only a
BankAccount. An exception is thrown:

	ClassCastException
    

Polymorphic Types[13] [top]

  BankAccount a;

What type can be assigned to a?

Answer(s): BankAccount and SavingsAccount

E.g., both of these are allowed:

	a = new BankAccount(50000);         // right sides is the same type
or
	a = new SavingsAccount(3000, 2.75); // right side is a subtype

We say that BankAccount is a polymorphic type because at run
time it can hold references of different types (at different times).

    

Checking the runtime type[14] [top]

With polymorphic types, you might expect to want to check the runtime
type:

Here is a new version of the printAccounts function:

  public static void printAccounts2(BankAccount[] acct)
  {
    String str = null;
    for(int i = 0; i < acct.length; i++) {
      if ( acct[i] instanceof SavingsAccount ) {
	str = String.format("Savings Account(balance = %.2f, rate = %.3f)",
			       getBalance(), interestRate);
      } else if (acct[i] instanceof BankAccount) {
	str = String.format("BankAccount(balance = %.2f)", balance);
      } 
      System.out.println(str);
    }
  }

Note: This is not as "reusable" as this version:

  public static void printAccounts(BankAccount[] acct)
  {
    String str = null;
    for(int i = 0; i < acct.length; i++) {
      str = acct[i].toString();	 // Which toString()?
      System.out.println(str);
    }
  }

The selection of which toString() at run time is called

	dynamic dispatch

dynamic => runtime

dispatch => select which implementation (of toString)

The dynamic dispatch feature of object oriented languages is also
called polymorphism. The reason this is the same idea as use
for polymorphic types is that the same expression can have different
meanings at run time:

 BankAccount a;
	
  Expression  Meaning
 a            At run time can reference type BankAccount 
                     or SavingsAccount

 a.toString() At run time can use the implementation of 
                     toString() in BankAccount or the one in
                     SavingsAccount depending on the run time
                     type of a.
	

Interfaces[15] [top]

Recall that an Interface looks like a class, but there are no data
members, and no implementations of methods  - just the method
prototypes (return type, name, list of argument types).

An Interface still defines a "type", but we cannot directly
create instances of an interface.

Example:

interface Comparable<T>
{
   public int compareTo(T other);
}

What does it mean for a class toimplement an
interface?

class X implements Comparable<X>
{


}

It means 

a. class X must include the  method int compareTo(X other) and with
an implementation.

b. It means that for any variable of type X can be compared to any
      other variable of type X.

Inheritance, Classes, and Interfaces[16] [top]

A class can inherit from exactly one other class. 

E.g., SavingsAccount inherits from (extends) BankAccount:

public class SavingsAccount extends BankAccount ...

This form of inheritance means that 

1. A SavingsAccount instance has each one of the data members defined
in BankAccount (all data members are "inherited")

2. A SavingsAccount instance inherits all the methods (and their
implementations) defined in BankAccount.

Java Facts:
a. A class can inherit from only one class
b. A class can inherit from multiple interfaces 
c. A class can do both a. and b.

E.g., 

public class Y extends X implements I, J
{


}

X and Y are classes
I and J are interfaces

    

Iterator interface[17] [top]

interface Iterator
{
  public boolean hasNext();
  public Object next();
  public void remove();
}
    

Fibonacci Example[18] [top]

This will be developed in the lecture.