目录

OOP and Algorithms

Course II notes


Writing Classes

两个重要的组成部分:

  1. 对象的行为(behaviors)
  2. 对应的数据(data)

Instance Variables

class defination

instant variable 的声明位置应该在紧接 class header

public class Insect {
    //instance variables here
}
简单的例子:
public class Insect {
    private double weight;
    private int x;
    private int y;
}

Visibility Modifiers

Visibility Modifiers 指在类/instance variable 声明的最前面的关键字。该关键字用于控制类对 instance variable 的管理权限。一些概念:

Testing Objects

通常对类进行测试的手段一般是在类中直接新建一个 main 来测试:

public class Insect {
    private double weight;
    private int x;
    private int y;

    public static void main(String[] args) {
        Insect buzz1 = new Insect();
        Insect buzz2 = new Insect();
    }
}

The Constructor

Default constructor
定义 constructor

构造函数的调用(Object 的实例化)需要配合自定义构造函数的参数列表:

//cstr
public Insect(double initWeight, int initX, int initY) {
    weight = initWeight;
    x = initX;
    y = initY;
}

//calling cstr
public static void main(String[] args) {
    Insect bug1 = new Insect(5.0, 1, 2);
    Insect bug2 = new Insect(3.0, 1, 3);
}

Constructor Overloading

与其他 method 一样,constructor 也可以通过重载适应不同的参数列表:

public static final int DEFAULT_X = 0;
public static final int DEFAULT_Y = 0;

//cstr overloading
public Insect(double initWeight) 
{
    weight = initWeight;
    x = DEFAULT_X;
    y = DEFAULT_Y;
    count ++;
}

Constructor Chaining

//cstr chain
public Insect(double initWeight)
{
    this(initWeight, DEFAULT_X, DEFAULT_Y);
}

//cstr
public Insect(double initWeight, int initX, int initY) 
{
    weight = initWeight;
    x = initX;
    y = initY;
    count ++;
}

instances methods

Instances methods 概念上类似 C++ 的成员函数。分为两类:

//interface
public
void move(int newX, int newY)
{
    double distance = calDistance(x, y, newX, newY);
    System.out.println(distance);
    if (distance > 0) {
        x = newX;
        y = newY;
        weight = weight * distance;
        System.out.printf("Moved %.2f units\n", distance);
    }
    else {
        System.out.println("Staying put");
    }
}

//helper
private static 
double calDistance(double x_1, 
                    double y_1, 
                    double x_2, 
                    double y_2) {
                    
    return Math.sqrt(Math.pow(y_1-y_2, 2) + Math.pow(x_1-x_2, 2));
}

//call
bug1.move(1, 10);
bug2.move(10, -300);

Static Variables and Constants

class 的定义中通常会出现一些各个 Instance 都会共享的常量:

public static final double aConstant = 0.0001;

如果不是常量的话,需要设置为 private,避免除了 instances 以外的数据对其进行修改。比如我们在构造函数内部添加一个计数属性用于统计总共创建的对象数:

private static int count = 0;
public Insect {
    ..../
    count ++;
}

只有在外部访问类成员的时候,才需要 className.classMember 这样的方式。

Static Methods
Math.random()

Math.random() 返回 $[0,1)$ 之间的数。可以使用下面的表达式来返回 (min, max) 之间的 int

(int)(Math.random() * ((max - min)  + 1)) + min;

Using "this" as a reference

this 关键字表示的是当前 object 的引用。Java 中可以在类的内部使用 this.attribute / this.method() 的方式来访问(调用)类成员。比如之前的构造函数可以改写成:

public Insect(double weight, int x, int x) 
{
    this.weight = weight;
    this.x = x;
    this.y = y;
}
注意这里的 weight, x, y 是构造函数的局部变量;真正的 instance variable weight, x, y 是通过 this 来取得的。

Accessor & Mutator

某些情况下,我们可能需要在类外访问或者修改类中的私有变量。这种情况下我们是无法通过 className.classMember 的方式进行访问的。

Accessor

访问解决方法是,在类中创建 public 的 method,通过该 method 去访问对应的私有变量。我们将这样的 method 称为 AccessorGetter,比如:

class Insect {
//instance variable
private int count = 0;
private static final int aConstant = 1;
//Accessor
public int getCount() {
    return count;
}
//call
Insect bee = new insect(....);
bee.getCount();
类中的私有常量可以通过 public static 类型的 Accessor 来访问:
public static getStaticConstant() {
    return aConstant;
}
//call by using class name instead of instance name:
Insect.getStaticConstant();
Accessor 的一些常见特点以及注意事项:

Mutator

MutatorSetter),用于在类外修改类中的私有变量。其实现方式与 Accessor 一致。几个 converntion:

public void setCount(int newCount) {
    if(isLegalCount(newCount) {
        count = newCount;
    }
}

toString Method

toString() 是 Java 自带的,所有 Object 都会继承的,一个将当前对象信息转化为 String 的 method。默认情况下,toString() 的输出结果为:

#className@instanceHASH
Insect@7699a589
我们通常会在类中 overload 这个 method 来跟踪打印类中的成员。比如 Insect 类,如果需要查看类中 3 个 Instance variable 的内容:
public String toString() {
    return "weight: " + weight + " x: " + x + " y: " + y;
}
//call
System.out.println(bug3.toString());
由于 println method 默认情况下打印的就是 toString() 的返回结果,因此上面的内容打印可以直接简化为:
System.out.println(bug3);

实例:扔骰子

几个重点:

  1. 结构:
    1. 负责存储骰子数据的数据类 Die ,以及骰子的行为 roll 的实现方法
    2. 负责运行游戏的游戏逻辑类 Craps
    3. 负责启动游戏的启动类 CrapLauncher
  2. 注意事项:
    1. Method 的 visibility 取决于在哪里使用
    2. 访问私有变量一定要通过 Accessor
    3. 除了 Math.Random(),Java 还提供了 util.Random 供使用。本例使用了 Random.nexInt(bound) 实现了骰子的随机结果。
      1. Random.nexInt(bound) 返回的区域是 $[0, bound)$

Source code with comments: craps.zip

Inheritance

Hierarchies

Wrting a subclass

The protected Modifier

在 Java 的继承机制中:

因此,如果一个 subclass,希望继承来自 Parent class 的私有成员,但又不希望这些成员被该 subclass 以外的类使用的话,使用 protected 关键字定义 parent class 中的成员:

public class Canine {
    protected double size;
    public Canine(double size) {
        this.size = size;
    }
}

注:Java 中的 protected 成员,允许被同 package 下的其他 class 使用。

访问权限表格:

ModifierClassPackageSubclassWorld
publicYYYY
protectedYYYN
privateYNNN
Declaring Subclasses and Instance Variables

public class Canine {
    protected double size;
    public Canine(double size) {
        this.size = size;
    }
}

Subclass Constructors

public class Dog extends Canine {
    //for the subclasses under the dog, e.g. Husky
    protected String name;
    //cstr
    public Dog(String name, double size) {
        //refer to "size" in the parent class "Canine"
        super(size);
        this.name = name;
    }
}

super 的调用在子类里是强制的。如果我们没有显式的为子类调用 super 以及对应的私有变量,Java 会自动调用一个无参数super。这种调用不会考虑到已继承的变量,因此通常会出错:

//the Dog constructor is not going to work due to the above rule
//Canine() cannot apply to Dog()

public class Dog extends Canine {
    //....
    private size = 0;
    //cstr
    public Dog(String name, double size) {
        this.size = size;
        this.name = name;
    }
}

Overriding Methods

Java 中的重写只需要在子类中重新定义需要重写的函数即可:

public class Canine {
    //....
    public void bark() {
        System.out.println("wof wof");
    }
}

public Dog(String name, double size) {
    super(size);
    this.name = name;
}
//super bark override
public void bark() {
    for (int i = 0; i < 2; ++i) {
        super.bark();
    }
}
//call
Dog spot = new Dog("Spot", 9.6);
spot.bark();

//result
wof wof
wof wof
注意上面的重写部分,如果希望以父类函数为基础进行重写,只需要使用 super 调用父类函数即可,比如这里的 super.bark()

调用重写函数的过程的概念就是 C++ 里的动态绑定;也就是根据 instance 指向的 class 类型来决定(动态类型,运行期决定)。

可以在函数前加上 @Override 来确保重写的函数名是否与父类中的函数名相同。

final method

Abstract class

abstract method

什么是 abstract method? 在模拟现实生活的过程中,有一些行为的一致性是很低的。比如犬类给自己顺毛的行为,狼,狗,家狗,野狗,都不一样;这种情况下,在父类 Canine 中定义一个标准的 groom() method 是很难的。

我们处理的方式是,声明这个方法,但不实例化,将实现交给子类自行解决。这个要求是强制的;也就是说,在继承了 abstract method (class) 后,子类必须对对应的 abstract method 进行具体的实现。

abstract Modifier

定义抽象类 / 抽象方法只需要添加 abstract 关键字即可:

public abstract class Canine {
    //....
    public abstract void groom();
    //....
}

Java Object Class

Object.equals() 的重写

Object 中有一个 method 叫 equals(之前用过,String 类中对齐进行了重载)。默认情况下,Object.equals() 的作用是判断两个实例引用是否指向同一个实例。根据之前讨论的内容,设想一种情况:在不知道 Object 的实例类型具体是哪种子类的情况下,我们要如何使用该对象与其某种特定类型的子类进行比较呢?

一个可行的判断依据是,只有两个子类是同一类型的对象时,才能进行比较。以 Dog 为例,只有两边都是狗的时候,我们才能依据一些条件来进行比较。因此,首先需要做两种判断:

Java 提供了 instanceof 运算符将上述的两个操作合二为一了。因此,要检测是不是同一类型的对象,有:

if (!(o instanceof Dog)) {
        return false;
    }
有了这层建议,我们就可以在 Dog 类中将 equals 按照我们的规则进行重写了,比如:
public boolean equals(Object o) {
    if (!(o instanceof Dog)) {
        return false;
    }
    Dog doggy = (Dog) o;
    return ((doggy.size == size) && (doggy.name.equals(name)));
}
需要注意的是,即便证明了 oDog 类型的对象,但使用 o 依然无法访问 Dog 重写的 equals。因此,我们需要使用另外一个 Dog 类型的引用来指向 o,从而间接达到用 o 访问 Dog.equal() 的效果。我们将该引用命名为 doggy,其值为将 o 强制转换Dog 类型后的值:
Dog doggy = (Dog) o;
这样,即便不清楚 o 的具体类型的情况下,我们也能安全的将其与 Dog 类型的实例进行比较了。

Interfaces and Algorithms

interface

在实际的应用中会出现这么一种关系,某一类的对象具有某个行为上的共同点,但对象本身并不构成继承的关系。比如狗与车,都有 “洗” 的行为;但狗和车显然不构成 IS 的关系。

这种情况是无法使用子类重写的方式解决的。因为子类重写的方式要求两点:

比如如果都是犬类所属,那对象的 group 定义为犬类即可:

public class GroomEverything {
    public static void main(String[] args) {
        Canine[] groomer = {
            new Wolf(17,3),
            new Poodle("richie", 2,"Pantene", "Bodyshop")
        };
    
        for (Canine c : groomer) { 
            c.groom();
            }     
    }
}
但显然车对象是无法放进 grommer 的,因为车的父类并不是 Canine。因此,我们需要一种新的方式来解决这个问题:如果将没有继承关系,但又具有某些相同行为的不同对象放入到一个 group 中进行遍历调用?

Java 提供了 Interface 来解决这个问题。interface 是一种类型(与 class 类似),但更像是一个方法声明的集合。他主要做的是:

Interface 的定义

与 class 的定义类似,interface 也需要单开一个与 Interface 名称相同的文件。在 interface 中声明 method 的方式与 class 一致。在 interface 中,moidifer 的默认有两点:

//in Groomable.java
public interface Groomable {
    //lists of abstract methods
    public void groom();
}
编译的结果依然是 .class 文件。

绑定和使用 Interface

//do it as well to other classes that need to be added into groommer
public class Car implements Groomable {
    //...
}

public class Car implements Groomable {
    //...
     public void groom() {
        if (speed == 0) {
            System.out.println("Car grooming!");
        }
    }
}

public class GroomEverything {
    public static void main(String[] args) {
        Groomable[] groomer = {
            new Wolf(17,3),
            new Poodle("richie", 2,"Pantene", "Bodyshop"),
            new Car("Honda", "Civic", 2004)
        };
    
        for (Groomable c : groomer)
            {
                c.groom();
            }     
    }  
}
默认情况下,所有希望一起使用的 class 都需要添加 implements 关键字来绑定 interface。但如果父类是抽象类,我们只需要绑定父类即可;父类下的所有子类都会继承这一绑定。规则与之前一样,抽象类要有方法声明,子类要有方法实现:
public abstract class Canine implements Groomable{
    //...
     public abstract void groom();
}

//implements keyword is not needed for subclasses
public class Wolf extends Canine {
    public void groom() {
        System.out.println("wolf groom");
    }
}

Generic Sorting Method

Sorting 在 OOP 中有着重要的作用;但 Sorting 是一件具体的事情。以 Wolf 对象为例,我们可以根据其 rank 的值设计一个排序方法:

public wolfSort(Wolf[] wolfArry) {
    //...
    //condition
    return wolf1.getRank() > wolf2.getRank();
}
但如果比较的对象不是 Wolf 对象,那么我们需要重写该方法的两部分:header(包括接收的对象)和 condition。这样做非常繁琐,因为每一个新类型的对象都需要写一个新的排序方法。

由于 Object class 的存在,Java 能够提供一种新的方法:

我们将这个方法命名为 CompereTo()。由于所有的类都是 Object 的子类,因此通用排序方法可以改写为:

public genericSort(Object[] arry) {
    //...
    //condition
    return obj1.compareTo(obj2);
}
也就是说,我们只需要在具体类中实现 compareTo() 就可以完成比较,而不是针对每一个类都去设计一个 sorting 的 method。

CompareTo 的简单结构
Java 对以上内容的支持

(Comparable).arr[index].compareTo(arr[anotherIndex]);

使用上述支持实现自定义 compareTo()

Comparable 与 之前自定义 Interface 的使用方法一致,将其至于 implements 关键字后面。如果有多个 Interface,以逗号隔开:

public class Wolf extends Canine implements Groomable, Comparable {
    //...
}
接下来需要将 Wolf 对象使用的 compareTo() 实现:
public int compareTo(Object anotherWolf) {
    return -(rank - ((Wolf)anotherWolf).rank);
}

修复在 runitime 时对象类型转换失败的问题

上述的解决方案中,由于 casting 是在 runtime 时进行的,因此 anotherWolf 的类型直到被转换的那一刻才会被检验。这个过程使得不正确的输入类型会导致 runtime error。

解决这个问题的方式是将条件判断转移到编译期。Java 提供了 InterfaceType Parameter 来解决这个问题。Type Parameter 可以让 compareTo() 在编译期就可以将真正需要的对象类型确定下来。具体写法:

public class Wolf extends Canine implements Comparable<Wolf> {
    /...
}
对应的,compareTo 中的实现也需要将 Object 类型改为 Wolf 类型;但因为类型已经确定,casting 的过程也可以省略了:
public int compareTo(Wolf anotherWolf) {
    return -(rank - anotherWolf.rank);
}

类型检测尽量放到编译期去验证。

Arrays.sort() 的使用

实现了 compareTo() 之后,我们就可以利用 Arrays.sort()Wolf 对象进行排序了。使用前需要导入 Array package:

//import
import java.util.Arrays;

//rewrited toString for output:
public String toString() {
    return String.format("Rank: %d", rank);
}

//call in main
public static void main(String[] args) {
    Wolf[] wolfPack = {
        new Wolf(17.1, 2),
        new Wolf(3, 10),
        new Wolf(9.2, 7),
        new Wolf(17.01, 3),
        new Wolf(5, 9)
    };
    Arrays.sort(wolfPack);
    System.out.println("Sorted: " + Arrays.toString(wolfPack));
}
默认的排序是升序,因此 rank 为 10 的会排在最前面:
Sorted: [Rank: 10, Rank: 9, Rank: 7, Rank: 3, Rank: 2]

Sorting Algorithm

Selection Sort
  1. 查找最小数,标记 index
  2. 与当前子数列第一位交换

//int version
public static void selectionSort(int[] arr)
{
    int minIdx = 0;
    for (int i = 0; i < arr.length-1; ++i)
        for(int j = i+1; j < arr.length; ++j)
        {
            minIdx = i;
            if (arr[j] < arr[minIdx])
            {
                minIdx = j;
            }
        int temp = arr[i];
        arr[i] = arr[minIdx];
        arr[minIdx] = temp;
        }
}

//Object version
public static void selectSort(Comparable[] arr)
{
    int minIdx = 0;
    for (int i = 0; i < arr.length-1; ++i)
        for(int j = i+1; j < arr.length; ++j)
        {
            minIdx = i;
            if (arr[j].compareTo(arr[minIdx]) < 0)
            {
                minIdx = j;
            }
        Comparable temp = arr[i];
        arr[i] = arr[minIdx];
        arr[minIdx] = temp;
        }
}

//call
Comparable[] groomer = {
            new Wolf(17,3),
            new Wolf(17,6),
            new Wolf(17,7),
            new Wolf(17,1),
            new Wolf(17,4),
};
    selectSort(groomer);

Selection Sort 的优化

loop 中的交换:

Comparable temp = arr[i];
arr[i] = arr[minIdx];
arr[minIdx] = temp;
只需要在最小值不处于比较区域的最开头时才进行。
if (minIndex != i) {
    Comparable temp = arr[i];
    arr[i] = arr[minIdx];
    arr[minIdx] = temp;
}

Merge Sort

public class Merge {
    
    //[start, end)
    public static int[] copyRange(int[] arr, int start, int end)
    {
        int[] slicedArray = new int[end - start];
        for (int i = start, j = 0;i < end; ++i, ++j) {
            slicedArray[j] = arr[i];
        }
        return slicedArray;
    }
    
    public static void mergeArrays(int[] mergedArray, int[] left, int[] right)
    {
        int lLen = left.length;
        int rLen = right.length;

        int leftIdx = 0;
        int rightIdx = 0;
        int mergedIdx = 0;

        //sorting 
        while (leftIdx != lLen && rightIdx != rLen)
        {
            if (left[leftIdx] < right[rightIdx]) {
                mergedArray[mergedIdx++] = left[leftIdx++];
            }
            else {
                mergedArray[mergedIdx++] = right[rightIdx++];
            }
        }
        // merge if there is any left
        while(leftIdx < lLen) {
            mergedArray[mergedIdx++] = left[leftIdx++];
        }
        while(rightIdx < rLen) {
            mergedArray[mergedIdx++] = right[rightIdx++];
        }
    }

    public static void mergeSort(int arr[]) {
        int currArrayLen = arr.length;
        if (currArrayLen < 2) {
            return;
        }
        else {
            int midIndex = currArrayLen / 2;
        
            int[] left = copyRange(arr, 0, midIndex);
            int[] right = copyRange(arr, midIndex, currArrayLen);

            mergeSort(left);
            mergeSort(right);
            mergeArrays(arr, left, right);
        }     
    }
}
如果需要修改为支持 Comparable interface,只需要修改:

Source Code: mergecomparable.java

Insertion Sort

Java 中的实现关键点:

public static void insertionSort(int[] arr) {

    for (int unSortIdx = 1; unSortIdx < arr.length; ++unSortIdx)
    {
       int currVal =  arr[unSortIdx];
       int sortIdx = unSortIdx - 1;
       
       while(sortIdx >= 0 && arr[sortIdx] > currVal)
       {
            arr[sortIdx + 1] = arr[sortIdx];
            sortIdx--;
       }
       arr[sortIdx + 1] = currVal;
    }        
}

Complexity Analysis

Stopwatch evaluation

使用 System.nanoTime() 来记录算法运行的时间:

long start = System.nanoTime();
selectionSort(input);
long end = System.nanoTime();
System.out.println("Elapsed time in ns:" + (end - start));

Constant Time

public static int linearSearch(Comparable key, Comparable[] elementLst)
    {
        for (int i = 0; i < elementLst.length; ++i) {
            if (elementLst[i].compareTo(key) == 0) {
                return i;
            }
        }
        return -1;
    }

Quadratic Time
linearithmic Time
如何估算复杂度

Growth Rates and Big-O Notation

Growth RateBig-O notation
Constant$O(1)$
Logarithmic$O(log(n))$
Linear$O(n)$
Linearithmic$O(nlog(n))$
Quadratic$O(n^2)$
Cubic$O(n^3)$
Exponential$O(a^n)$



public static int binarySearch(int[] arr, int key)
    {
        int minIdx = 0;
        int maxIdx = arr.length - 1;
        int midIdx;
        
        while (minIdx <= maxIdx)
        {
            midIdx = (minIdx + maxIdx) / 2;
            
            if (arr[midIdx] == key) {
                return midIdx;
            }
            //key in [0:mid)
            else if (arr[midIdx] > key) {
                maxIdx = midIdx - 1;
            }
            else if (arr[midIdx] < key) {
                minIdx = minIdx + 1;
            }
        }
        return - 1;
    }

More About Interfaces

Default Methods

这是一种允许在 interface 中直接定义 Method 的方式。主要适应的场景是快速为 interface 下的各种类型添加新的共同 Method。比如之前例子中,动物与车都有“洗”的行为;如果希望给这个“洗”加一个付款 pay(),如果不使用 default method,那么只能在 interface 中声明虚函数,再去每个类里单独实现了。 有了 default method 以后,则可以直接在 interface 里写;所有 interface 下的实现类都会自动得到该 Method:

public interface Groomable {
    public void groom();
    //public, by default
    default void pay() {
        System.out.println("All should pay!");
    }
}

default method 的重写

重写与一般重写一致:

public void pay() {
        System.out.println("I am the wolf and I don't need to pay!");
    }

Static Methods

如果不要求重写,那么没有必要给每一个 interface 的参与类都添加一个 Method。为此,Interface 中也提供了用于定义 static method 的方法:

public interface Groomable {
    //...
    static String calTip(double price, double percentage) {
        double rawTip = price *(percentage / 100);
        return String.format("%.2f" , rawTip);
    }
}
调用的时候按正常的 className.methodName 写法调用即可:
Groomable.rawTip(9.99, 20);

Constants in Interfaces

public interface Mascot {
    public static final double MAX_CELEBRATION_SEC = 30;
    //equivalent
    double MAX_CELEBRATION_SEC = 30;
}
为什么 interface 里的变量必须是常量?

相比起在 interface 中存放常量,选择在类中存放是更好的选择。因为 class 中同时允许 instance variable 的存在。

Constant Interfaces

Constant Interfaces 指 Interface 中包含的成员均为常量。其应用场景为:程序需要使用很多常量。将这些常量全放在一个地方,可以方便读取。

这样使用固然很方便,但我们还是应该记得:interface 是用于声明行为(concept)的。constant interface 并不属于这一概念,因此尽量不要使用。

Interface Hierarchies

UML

Polymorphism

多态Polymorphism)允许我们使用一段代码灵活吃处理不同类型的对象。

Java 处理多态的过程

Canine pixy;
pixy = new Poodle(..);
pixy.bark();

  1. Java 会首先查找声明中的类型(declar type)
  2. 找到后,再查找实例化的类型(new 后面那个类型,instantiate type)
  3. 找到这两者之后,编译器会考虑以下两点:
    1. declar type 的引用能指向具有 instantiate type 的对象吗?
    2. 如果我们想通过该对象访问某些方法,那么这些方法在 declar type 中有没有声明且定义?

如果上述条件都不满足,Java 编译器会认为该 assignment 是不合法的。

Java 会对针对第一个问题进行所谓的 Relationshio test

比如:

//ok, pixy is a Poodle, Poodle is a dog
Dog pixy;
pixy = new Poodle();

//error, pixy is a Dog, a Dog may not be a Poodle
Poodle pixy;
pixy = new Dog(...);
这种 Is a 的规则同样应用于将 argument 传递给 parameter 的过程中。这种情况下:

比如:

public class Human {
    public void playFetch(Dog myPet) {
        //....
    }
}
Dog richie = new Dog(...);
Poodle pixy = new Poodle(...);

//ok, richie is a Dog
owner.playFetch(richie);
//ok, pixy is a Poodle, Poodle is a Dog
owner.playFetch(pixy);

声明为相同 interface 类型的两个对象很可能不是互通的;比如车不能实例化为狗,即便车和狗都有“洗”的行为。

Method Calls

第二个需要检查的是 method 的调用:

//Canine defines bark()
//Poodle defines enterDogShow()

//declar type
Canine pixy;
Poodle richie = new Poodle(...);
pixy = richie;

//ok, bark() is defined in Canine
pixy.bark();

//error, Canine doesn't define enterDogShow, even if pixy's instantiate type is Poodle
pixy.enterDogShow();

method call 只考虑声明类型中是否存在该函数的定义,不考虑对象类型;除非对象类型中存在着该 method 的 overriding(详情见之后的动态绑定)

Casting

上述例子中,如果我们希望使用 pixy 直接访问 enterDogShow(),我们需要将其转换为 Poodle 类型:

//ok, in case of using enterDogshow(), we need casting 
// !!!notice the praentheses!!!
((Poodle)pixy).enterDogShow();
Casting 可以在继承关系树上向上,或是向下进行(注意不是 is-a 或者 has-a 的关系不行!),得到的结果是一个临时的转换后的 Object 类型(的引用)。我们只需要确保 Casting 之后的类型可以访问被调用的方法即可。

Casting 只是生成了一个临时的,对应的 Object 类型的引用。pixy 永远都是指向 Canine 类型的引用。

Casting 潜在的问题

Casting 带来了一个问题。来看以下例子:

Canine dg;
dg = new Dog(...);

//is thi legal?
((Poodle)dg).enterDogShow();
这种情况下, dg 的 object type 是 Dog,而不是 Poodle。Casting 是可以进行的;但是在调用 method 的时候,JVM 会做 Is a 的检测。上面的例子中,我们调用的是 Poodle 类型实现的 method,在调用之前,编译器会询问:调用者的 object type 是不是 method 实现所在的 object type? (此处就是在问:DogPoodle 吗?)如果不是,那该 method call 就无法通过编译。

Dynamic Binding

在检测 method call 是否合法的同时,JVM 还会同时在运行期检测哪一个版本最适合当前的 call。处理该调用匹配的过程被称为动态绑定Dynamic Binding)。总的来说:

//if Canine & Poodle both have a bark() implementation
Canine pixy;  
pixy = new Poodle(...); 

//call Poodle.bark()
pixy.bark();

//if Canine & Dog both have a bark() implementation, but Poodle dosen't
Canine pixy;  
pixy = new Poodle(...); 

//call Dog.bark()
pixy.bark();