[Java] 抽象类和接口

1 简介

超类定义相关子类的共同行为,而接口可以用来定义多个类(包括不相关的类)的共同行为。

虽然可以使用java.util.Arrays.sort方法对一组数字或一组字符串进行排序,但是能用同样的sort方法对一组几何对象进行排序吗?为了写出这样的代码, 必须使用接口。接口可以用来定义多个类 (包括不相关的类) 的共同行为。在讨论接口之前,先介绍一个密切相关的主题:抽象类。

2 抽象类

抽象类不能用于创建对象,一个抽象类可以包含抽象方法,抽象方法在具体的子类中实现。

在继承的体系结构中,类在子类中变得更为具体。如果沿着子类逐级向上查看超类,各个类的具体化程度将逐渐降低,越来越泛化。类设计应该保证超类包含子类的共同特征。有时超类过于抽象,不能用于创建任何具体的实例,这种类称为抽象类。

在第11章中,GeometricObject 被定义为CircleRectangle的超类。GeometricObject 模型包含了几何对象的共同特征,CircleRectangle 都包含了getArea()getPerimeter() 方法用于计算圆和矩形的面积和周长。因为你能为所有几何对象计算面积和周长,所以将getArea()getPerimeter()这两个方法放到GeometricObject类里会更好。然而这两个方法不能在GeometricObject类中实现,因为他们的实现依赖于几何对象的具体类型。这样的方法称为抽象方法,通过在方法头中用abstract修饰符表示。当你在GeometricObject中定义了这些方法后,这个类就成了一个抽象类。抽象类是用在类头中加abstract修饰符表示的。在UML图形符号中,抽象类的名称及其抽象方法用斜体表示。下面是新的GeometricObject类:

// GeometricObject.java
// 注意这里的 abstract 关键字,抽象类!
public abstract class GeometricObject {  
    private String color = "white";
    private boolean filled;
    private java.util.Date dateCreated;

    /** Construct a default geometric object */
    protected GeometricObject() {
        dateCreated = new java.util.Date();
    }
    /** Construct a geometric object with color and filled value */
    protected GeometricObject(String color, boolean filled) {
        dateCreated = new java.util.Date();
        this.color = color;
        this.filled = filled;
    }
    /** Return color */
    public String getColor () {
        return color;
    }
    /** Set a new color */
    public void setColor(String color) {
        this.color = color;
    }
    /** Return filled. Since filled is boolean,
    * the get method is named isFilled */
    public boolean isFilled() {
        return filled;
    }
    /** Set a new filled */
    public void setFilled(boolean filled) {
        this.filled = filled;
    }
    /** Get dateCreated */
    public java.util.Date getDateCreated() {
        return dateCreated;
    }
    @Override
    public String toString() {
        return "created on " + dateCreated + "\ncolor: " + color + " and filled: " + filled;
    }
    /** 抽象方法 getArea */
    public abstract double getArea();

    /** 抽象方法 getPerimeter */
    public abstract double getPerimeter();
}

抽象类和常规类相像,但是不能用new操作符创建抽象类的实例。 抽象方法只有定义没有实现,实现由子类提供。 一个含有抽象方法的类必须定义为abstract
抽象类的构造函数定义为protected,因为只有子类会调用它。 当创建一个具体子类的实例时,超类的构造函数被调用,以初始化超类的数据域。GeometricObject抽象类为几何对象定义了共同特征(即数据和方法),并提供了适当的构造函数。 因为没办法计算几何对象的面积和周长,所以getArea()getPerimeter()都被定义为抽象方法。这些方法在子类中实现。CircleRectangle的实现现在使用extends关键字继承了GeometricObject类:

// Circle.java
public class Circle extends GeometricObject {
    //...
}

// Rectangle.java
public class Rectangle extends GeometricObject {
    //... 
}

2.1 抽象类的要点

  • 非抽象类不能含有抽象方法。如果抽象超类的子类没有实现所有的抽象方法,则子 类必须定义为抽象类。换句话说, 继承了抽象类的非抽象子类必须实现所有的抽象方法。抽象方法非静态。
  • 抽象类不能用new操作符实例化,但仍可定义构造函数,子类的构造函数会调用它。例如GeometricObject的构造函数被CircleRectangle类调用。
  • 包含了抽象方法的类必须是抽象类,但抽象类却可以不包含抽象方法。这种情况下不能用new操作符创建类的实例,此类用于继承。
  • 子类可以重写超类中的方法并将其定义为抽象方法,虽然非常罕见, 但如果超类中的方法的实现在子类中变得非法时,这种写法就有用。这种情况下,子类必须定义为抽象类。
  • 即使超类是具体类,子类也可以是抽象类。例如虽然Object类是具体类,但是它的子类如GeometricObject可以是抽象类。
  • 不能用new操作符创建抽象类的实例,但是抽象类可用作数据类型。因此,如下创建以GeometricObject为元素类型的数组的语句是正确的。
GeometricObject[] objects = new GeometricObject[10];

2.2 例子:抽象类Number

Number是抽象类,它是数字包装类BigDecimalBigInteger的基类。
Number含有6个抽象方法: intValue(), longValue(), doubleValue(),floatValue(), 另外两个方法shortValue, byteValue不是抽象方法,但是实现依赖于抽象方法的实现:

public short shortValue() {
    return (Short)intValue();
}
public byte byteValue() {
    return (byte)intValue();
}

下面的例子,返回4个数,分别为整数,浮点,BigDecimalBigInteger中最大的数,代码实现如下:

package largestnumbers;
import java.util.ArrayList;
import java.math.*;
public class LargestNumbers {
    public static void main(String[] args) {
        ArrayList<Number> list = new ArrayList();
        list.add(45); // Add an integer
        list.add(3445.53); // Add a double
        // Add a BigInteger
        list.add(new BigInteger("3432323234344343101"));
        // Add a BigDecimal
        list.add(new BigDecimal("2.0909090989091343433344343"));
        System.out.println("The largest number is " + getLargestNumber(list));
    }
    public static Number getLargestNumber(ArrayList<Number> list) {
        if (list == null || list.size() == 0)
            return null;
        Number number = list.get(0);
        for (int i = 1; i < list.size(); i++)
            if (number.doubleValue() < list.get(i).doubleValue()) // 这里取浮点
                number = list.get(i);
        return number;
    }
}

number.doubleValue()为抽象方法,如果数字为Integer对象,将调用IntegerdoubleValue(), 如果数字为BigDecimal对象,调用的是BigDecimal的对象。因为doubleValue()定义在类Number中,因此才有可能查找不同数据类型中的最大数。

2.3 例子:类CalendarGregorianCalendar

Calendar: 日历
GregorianCalendar: 格里高利历,简称格里历,即公历
Calendar为抽象基类,GregorianCalendar为具体子类。
Calendar 可以实现多个具体子类,例如格里历,阴历,犹太历等,Java目前对格里历的实现只有java.util.GregorianCalendar

可以通过域常量提取Calendar对象的日期和时间信息, Calendar类的域常量:

Constant Description
YEAR The year of the calendar.
MONTH The month of the calendar, with 0 for January.
DATE The day of the calendar.
HOUR The hour of the calendar (12-hour notation).
HOUR_OF_DAY The hour of the calendar (24-hour notation).
MINUTE The minute of the calendar.
SECOND The second of the calendar.
DAY_OF_WEEK The day number within the week, with 1 for Sunday.
DAY_OF_MONTH Same as DATE.
DAY_OF_YEAR The day number in the year, with 1 for the first day of the year.
WEEK_OF_MONTH The week number within the month, with 1 for the first week.
WEEK_OF_YEAR The week number within the year, with 1 for the first week.
AM_PM Indicator for AM or PM (0 for AM and 1 for PM).

下面的例子即为测试:

import java.util.*;
public class TestCalendar {
    public static void main(String[] args) {
        // Construct a Gregorian calendar for the current date and time
        Calendar calendar = new GregorianCalendar();
        System.out.println("Current time is " + new Date());
        System.out.println("YEAR: " + calendar.get(Calendar.YEAR));
        System.out.println("MONTH: " + calendar.get(Calendar.MONTH));
        System.out.println("DATE: " + calendar.get(Calendar.DATE));
        System.out.println("HOUR: " + calendar.get(Calendar.HOUR));
        System.out.println("HOUR_OF_DAY: " + calendar.get(Calendar.HOUR_OF_DAY));
        System.out.println("MINUTE: " + calendar.get(Calendar.MINUTE));
        System.out.println("SECOND: " + calendar.get(Calendar.SECOND));
        System.out.println("DAY_OF_WEEK: " +     calendar.get(Calendar.DAY_OF_WEEK));
        System.out.println("DAY_OF_MONTH: " + calendar.get(Calendar.DAY_OF_MONTH));
        System.out.println("DAY_OF_YEAR: " + calendar.get(Calendar.DAY_OF_YEAR));
        System.out.println("WEEK_OF_MONTH: " + calendar.get(Calendar.WEEK_OF_MONTH));
        System.out.println("WEEK_OF_YEAR: " + calendar.get(Calendar.WEEK_OF_YEAR));
        System.out.println("AM_PM: " + calendar.get(Calendar.AM_PM));

        // Construct a calendar for September 11, 2001
        Calendar calendar1 = new GregorianCalendar(2001, 8, 11);
        String[] dayNameOfWeek = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
         System.out.println("September 11, 2001 is a " +33 dayNameOfWeek[calendar1.get(Calendar.DAY_OF_WEEK) - 1]);
     }
 }

运行结果:

//运行时间点为: 2018年6月12号,星期二
Current time is Tue Jun 12 16:26:06 CST 2018
YEAR: 2018
MONTH: 5 // Month从0开始,所以实际是六月
DATE: 12 // 6月12号
HOUR: 4 // 12小时制
HOUR_OF_DAY: 16  // 24小时制
MINUTE: 26
SECOND: 6
DAY_OF_WEEK: 3  // 因为星期天为1, 3就是星期二
DAY_OF_MONTH: 12
DAY_OF_YEAR: 163
WEEK_OF_MONTH: 3
WEEK_OF_YEAR: 24
AM_PM: 1 // 1 表示下午,0 表示上午

3 接口

接口是只包含了常量和抽象方法的类似于类的结构,为了和类区分开,使用interface关键字,语法:

modifier interface InterfaceName {
    /** Constant declarations */
    /** Abstract method signatures */
}

具体的例子:

public interface Edible {
    /** Describe how to eat */
    public abstract String howToEat();
}

接口在很多方面和抽象类是十分相似的,但接口的目的是为了为相关联类对象或不相关的类对象指定共同行为。接口在Java中被当做特殊类看待,例如会像常规类一样被编译成字节码,可以用作引用变量的数据类型,和抽象类一样,不能用new关键字创建实例。

public class TestEdible {
    public static void main(String[] args) {
        Object[] objects = {new Tiger(), new Chicken(), new Apple()};
        for (int i = 0; i < objects.length; i++) {
            if (objects[i] instanceof EdibleInterface)
                System.out.println(((EdibleInterface)objects[i]).howToEat());
            if (objects[i] instanceof Animal) {
                System.out.println(((Animal)objects[i]).sound());
            } // end if
        } // end for
    } // end main   
} // end TestEdible

abstract class Animal { //抽象类Animal
    /** Return animal sound */
    public abstract String sound();
}
class Chicken extends Animal implements EdibleInterface { //Chicken继承Animal实现EdibleInterface
    @Override
    public String howToEat() {
        return "Chicken: Fry it";
    }
    @Override
    public String sound() { //重写Sound()
        return "Chicken: cock-a-doodle-doo";
    }
}
class Tiger extends Animal { //Tiger继承 Animal 
    @Override
    public String sound() {  //重写sound()
        return "Tiger: RROOAARR";
    }
}
abstract class Fruit implements EdibleInterface {  //Fruit实现接口EidbleInterface
    // Data fields, constructors, and methods omitted here
}
class Apple extends Fruit { // Apple继承Fruit
    @Override
    public String howToEat() {  //重写howToEat()
        return "Apple: Make apple cider";
    }
}
class Orange extends Fruit {  // Orange继承Fruit
    @Override
    public String howToEat() {  //重写howToEat()
        return "Orange: Make orange juice";
    }
}

// 如果接口定义和以上代码放在同一个文件中,加了关键字public就必须放在另一个文件中
// 否则编译无法通过,省略关键字public,编译ok
/**public*/ interface EdibleInterface {  //定义接口,注意关键字是interface
    public abstract String howToEat();  //抽象方法howToEat()
}

代码输出:

Tiger: RROOAARR
Chicken: Fry it
Chicken: cock-a-doodle-doo
Apple: Make apple cider

接口里的所有数据域为public static final, 所有方法为public abstract,Java 因此允许省略这些修饰符,所以,如下的接口定义等价:

public interface T {
    public static final int K = 1;
    public abstract void p();
}

等价于:

public interface T {
    int K = 1;
    void p();
}

3.1 Comparable接口

这个接口的目的,是为了比较两个对象,例如比较两个学生、两个日期、两个圆、两个矩形或者两个正方形等。类似于C++中的操作符 <, >, =的重载。
用于比较对象的接口java.lang.Comparable的接口定义如下:

// Interface for comparing objects, defined in java.lang
package java.lang;
public interface Comparable<E> {
    public int compareTo(E o);
}

E表示泛型, compareTo表示该对象和对象o进行比较,如果小于o返回负整数,等于o返回零,大于o返回正整数。

Java 中的类Byte, Short, Integer, Long, Float, Double, Character, BigInteger, BigDecimal, Calendar, String, 以及Date都实现了Comparable接口,下面是4个类IntegerBigInteger, StringDate 在Java API中的定义:

// Class Integer 定义
public class Integer extends Number implements Comparable<Integer> {
    // class body omitted
    @Override
    public int compareTo(Integer o) {
        // Implementation omitted
    }
}
// Class BigInteger 定义
public class BigInteger extends Number implements Comparable<BigInteger> {
    // class body omitted
    @Override
    public int compareTo(BigInteger o) {
        // Implementation omitted
    }
}
// Class String 定义
public class String extends Object implements Comparable<String> {
    // class body omitted
    @Override
    public int compareTo(String o) {
        // Implementation omitted
    }
}
// Class Date 定义
public class Date extends Object implements Comparable<Date> {
    // class body omitted
    @Override
    public int compareTo(Date o) {
        // Implementation omitted
    }
}

因此,数字,字符串和日期都是可以比较的,如下面的代码:

System.out.println(new Integer(3).compareTo(new Integer(5)));
System.out.println("ABC".compareTo("ABE"));
java.util.Date date1 = new java.util.Date(2013, 1, 1);
java.util.Date date2 = new java.util.Date(2012, 1, 1);
System.out.println(date1.compareTo(date2));

将会输出:

-1  // 3 < 5
-2  // "ABC" < "ABE"
1  // date1 > date2

假定nInteger对象, sString对象, dDate对象,下列所有的表达式都为true:

n instanceof Integer
n instanceof Object
n instanceof Comparable
/** --------------- */
s instanceof String
s instanceof Object
s instanceof Comparable
/** --------------- */
d instanceof java.util.Date
d instanceof Object
d instanceof Comparable

下面是比较矩形的一个类:

package InterfaceStudy;
import myJavaPackage1.*;
public class ComparableRectangle extends Rectangle implements Comparable<ComparableRectangle>  {
    ComparableRectangle(double width, double length) {
        super(width, length);
    }

    @Override
    public int compareTo(ComparableRectangle obj) {
        if (getArea() < obj.getArea())
            return -1;
        else if (getArea() > obj.getArea())
            return 1;
        return 0;
    }

    @Override
    public String toString() {
        return "The Area is " + getArea();
    }  

    public static void main(String[] args) {
        ComparableRectangle obj1 = new ComparableRectangle(2, 2);
        ComparableRectangle obj2 = new ComparableRectangle(2, 10);
        System.out.println(obj1.toString());
        System.out.println(obj2.toString());
        System.out.println(obj1.compareTo(obj2));
        System.out.println(obj2.compareTo(obj1));
    }
}

这一节的最后一道练习题:

public class Exer {
    public static void main(String[] args) {
        Person[] persons = {new Person(3), new Person(4), new Person(1)};
        java.util.Arrays.sort(persons);
        for (Person p : persons) {
            System.out.print(p.getId() + "  ");
        }
    }
}
class Person implements Comparable<Person> {
    private int id;

    Person(int id) {
        this.id = id;
    }

    @Override
    public int compareTo(Person p) {
        if (this.id > p.id)
            return 1;
        else if (this.id < p.id)
            return -1;
        return 0;
    }

    public int getId() {
        return id;
    }
}

注意抽象方法名不是toCompare, 而是 compareTo,运行结果:

1  3  4

3.2 Cloneable接口

Cloneable接口指明对象可复制。

通常需要创建一个对象的副本,要做到这一点,需要使用clone方法并理解Cloneable接口。一个接口包含常量和抽象方法,但Cloneable接口很特殊,是空的java.lang 包中的Cloneable接口定义如下:

// java.lang.Cloneable 接口
package java.lang; // Cloneable 定义在 java.lang 中
    public interface Cloneable {
}

这个接口是空的, 定义体为空的接口称为marker interface(标记接口), 标记接口不包含常量或方法,它被用来表示一个类具有某些被期望的属性。 实现了Cloneable接口的类被标记为cloneable,其对象可以使用Object中定义的clone()方法进行复制。
Java库中的许多类(例如DateCalendarArrayList)都实现了Cloneable接口。 因此,这些类的实例可以被复制。 例如,下面的代码:

Calendar calendar = new GregorianCalendar(2013, 2, 1);
Calendar calendar1 = calendar;
Calendar calendar2 = (Calendar)calendar.clone();
System.out.println("calendar == calendar1 is " +
(calendar == calendar1));
System.out.println("calendar == calendar2 is " +
(calendar == calendar2));
System.out.println("calendar.equals(calendar2) is " +
calendar.equals(calendar2));

将显示:

calendar == calendar1 is true
calendar == calendar2 is false
calendar.equals(calendar2) is true

上面的代码首先是calendar的引用被复制到calendar1, 所以 calendarcalendar1指向同一Calendar对象。
接下来创建了一个新的对象即calendar的克隆, 然后新对象的引用赋给calendar2calendar2calendar是内容相同的不同对象。
另一个例子:

ArrayList<Double> list1 = new ArrayList<>();
list1.add(1.5);
list1.add(2.5);
list1.add(3.5);
ArrayList<Double> list2 = (ArrayList<Double>)list1.clone();
ArrayList<Double> list3 = list1;
list2.add(4.5);
list3.remove(1.5);
System.out.println("list1 is " + list1);
System.out.println("list2 is " + list2);
System.out.println("list3 is " + list3);

将显示:

list1 is [2.5, 3.5]
list2 is [1.5, 2.5, 3.5, 4.5]
list3 is [2.5, 3.5]

上面第5行代码创建了一个新的对象,它是list1的克隆, 并将这个新对象的引用赋给 list2list1list2是内容相同的不同对象。第6行将list1的引用复制给list3, 因此list1list3指向相同的ArrayList对象,第7行将 4.5 添加到list2. 第8行将 1.5 从list3中移除,因为list1list3指向同一 ArrayList, 第9行和第11行显示相同的内容。

如果普通的类要实现Cloneable接口,就必须重写Object类中的clone()方法。下面的代码定义了一个实现了CloneableComparable的叫做House的类。

public class House implements Cloneable {
    private java.util.Date whenBuilt;
    private int id;
    private double area;
    public House(int id, double area, java.util.Date date) {
        this.id = id;
        this.area = area;
        this.whenBuilt = date;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    public static void main(String[] args) throws CloneNotSupportedException {
        java.util.Date date = new java.util.Date();
        House h1 = new House(1, 1750.50, date);
        House h2 = (House)h1.clone();  
        System.out.println("h1 == h2 ? :" + (h1 == h2) );
        System.out.println("h1 equals h2 ? :" + (h1.equals(h2)));
    }
}

运行结果:

h1 == h2 ? :false
h1 equals h2 ? :false // Object中的方法equals()需要重写,才会比较内容。

浅拷贝和深拷贝:简而言之,如果复制的对象的内容完全独立,为深拷贝。反之,若有数据域复制的是引用,那么两个对象的数据域指向相同的内存,则为浅拷贝,浅拷贝的结果: 一个对象改变,有可能自动引起另一个对象的变化。为了实现深拷贝,必须重写默认的clone方法。

Object 类中定义的clone()方法头:

protected native Object clone() throws CloneNotSupportedException;

关键字native 表示这个方法不是写在Java里的,而是为原生平台实现在JVM中. The keyword protected则使这个方法仅限于在相同的包或子类中访问。出于这个原因,House类必须重写这个方法,将protected改为public, 这样任何包都可以访问。

实现深拷贝的代码,下面两种写法都可行:

// 写法1
@Override
public Object clone() throws CloneNotSupportedException {
    House HouseClone = (House)super.clone();
    HouseClone.whenBuilt = (java.util.Date)(whenBuilt.clone());
    return HouseClone;
}
// 写法2
@Override
public Object clone() {
    try {
        House HouseClone = (House)super.clone();
        HouseClone.whenBuilt = (java.util.Date)(whenBuilt.clone());
        return HouseClone;
    }
    catch (CloneNotSupportedException ex) {
        return null;
    }
}

4 接口 vs. 抽象类

变量 构造函数 方法
抽象类 无限制 构造函数被子类调用,不能实例化 无限制
接口 必须为public static final 没有构造函数,不能实例化 必须为public abstract instance methods

一个类可以实现多个接口,但是只能继承一个超类:

public class NewClass extends BaseClass implements Interface1, ..., InterfaceN {
     // ...
}

接口可以用extends关键字继承其他接口,这种接口叫子接口。例如,下列代码中的 NewInterfaceInterface1, … 和InterfaceN的子接口。

public interface NewInterface extends Interface1, ... , InterfaceN {
    // constants and abstract methods
}

实现NewInterface的类必须实现定义在NewInterface, Interface1, … , InterfaceN 中的抽象方法。 接口可以继承其他接口,不能继承类。一个类可以继承超类并实现多个接口。所有类共有一个单一根类Object,但是接口没有单一根类。和类相似,接口也定义了一个类型,接口类型的变量可以引用实现了该接口的类的任意实例。如果一个类实现了一个接口,接口就相当于该类的超类。你可以将接口用作数据类型,将接口类型的变量cast为它的子类,反之亦然。

一般而言,尽量多用接口,少用抽象类,因为一个接口可以为不相关的类定义共同的超类,接口比类更加灵活,考虑如下的Animal类, 假定howToEat方法定义在Animal类中。

abstract class Animal {
    public abstract String howToEat();
}

同时定义了Animal的两个子类ChickenDuck

class Chicken extends Animal {
    @Override
    public String howToEat() {
        return "Fry it";
    }
}
class Duck extends Animal {
    @Override
    public String howToEat() {
        return "Roast it";
    }
}

给定这种继承层次结构,多态允许你使用Animal类型的变量引用Chicken对象或Duck 对象。如下代码:

public static void main(String[] args) {
    Animal animal = new Chicken();
    eat(animal);
    animal = new Duck();
    eat(animal);
}
public static void eat(Animal animal) {
    animal.howToEat();
}

JVM 依据实际的对象动态决定调用哪一个howToEat方法。 你可以定义Animal的子类,但是有一个限制:子类必须是另一种animal的子类,例如Turkey。接口则没有这个限制,它比类提供了更大的灵活性,它不要求什么都要符合一种类类型。你可以在接口中定义howToEat, 让它用作其他类的supertype (超级类型),例如:

public static void main(String[] args) {
    Edible stuff = new Chicken();
    eat(stuff);
    stuff = new Duck();
    eat(stuff);
    stuff = new Broccoli();
    eat(stuff);
}

public static void eat(Edible stuff) {
    stuff.howToEat();
}
interface Edible {
    public String howToEat();
}
class Chicken implements Edible {
    @Override
    public String howToEat() {
        return "Fry it";
    }
}
class Duck implements Edible {
    @Override
    public String howToEat() {
        return "Roast it";
    }
}
class Broccoli implements Edible {
    @Override
    public String howToEat() {
        return "Stir-fry it";
    }
}

5 例子: Rational

// TestRationalClass.java
public class TestRationalClass {
    public static void main(String[] args) {
        Rational r1 = new Rational(4, 2);
        Rational r2 = new Rational(2, 3);
        System.out.println(r1 + " + " + r2 + " = " + r1.add(r2));
        System.out.println(r1 + " - " + r2 + " = " + r1.subtract(r2));
        System.out.println(r1 + " * " + r2 + " = " + r1.multiply(r2));
        System.out.println(r1 + " / " + r2 + " = " + r1.divide(r2));
        System.out.println(r2 + " is " + r2.doubleValue());
    }
}
// Rational.java
public class Rational extends Number implements Comparable<Rational> {
    // Data fields for numerator and denominator
    private long numerator = 0;
    private long denominator = 1;

    /** Construct a rational with default properties */
    public Rational() {
        this(0, 1);
    }

    /** Construct a rational with specified numerator and denominator */
    public Rational(long numerator, long denominator) {
        long gcd = gcd(numerator, denominator);
        this.numerator = ((denominator > 0) ? 1 : -1) * numerator / gcd;
        this.denominator = Math.abs(denominator) / gcd;
    }

    /** Find GCD of two numbers */
    private static long gcd(long n, long d) {
        long n1 = Math.abs(n);
        long n2 = Math.abs(d);
        int gcd = 1;

        for (int k = 1; k <= n1 && k <= n2; k++) {
            if (n1 % k == 0 && n2 % k == 0)
                gcd = k;
        }
        return gcd;
    }

    /** Return numerator */
    public long getNumerator() {
        return numerator;
    }

    /** Return denominator */
    public long getDenominator() {
        return denominator;
    }

    /** Add a rational number to this rational */
    public Rational add(Rational secondRational) {
        long n = numerator * secondRational.getDenominator() + 
                denominator * secondRational.getNumerator();
        long d = denominator * secondRational.getDenominator();
        return new Rational(n, d);
    }

    /** Subtract a rational number from this rational */
    public Rational subtract(Rational secondRational) {
        long n = numerator * secondRational.getDenominator() - 
                denominator * secondRational.getNumerator();
        long d = denominator * secondRational.getDenominator();
        return new Rational(n, d);
    }

    /** Multiply a rational number by this rational */
    public Rational multiply(Rational secondRational) {
        long n = numerator * secondRational.getNumerator();
        long d = denominator * secondRational.getDenominator();
        return new Rational(n, d);
    }

    /** Divide a rational number by this rational */
    public Rational divide(Rational secondRational) {
        long n = numerator * secondRational.getDenominator();
        long d = denominator * secondRational.numerator;
        return new Rational(n, d);
    }

    @Override
    public String toString() {
        if (denominator == 1)
            return numerator + "";
        else
            return numerator + "/" + denominator;
    }

    @Override // Override the equals method in the Object class
    public boolean equals(Object other) {
        if ((this.subtract((Rational)(other))).getNumerator() == 0)
            return true;
        else
            return false;
    }

    @Override // Implement the abstract intValue method in Number
    public int intValue() {
        return (int)doubleValue();
    }

    @Override // Implement the abstract floatValue method in Number
    public float floatValue() {
        return (float)doubleValue();
    }

    @Override // Implement the doubleValue method in Number
    public double doubleValue() {
        return numerator * 1.0 / denominator;
    }

    @Override // Implement the abstract longValue method in Number
    public long longValue() {
        return (long)doubleValue();
    }

    @Override // Implement the compareTo method in Comparable
    public int compareTo(Rational o) {
        if (this.subtract(o).getNumerator() > 0)
            return 1;
        else if (this.subtract(o).getNumerator() < 0)
            return -1;
        else
            return 0;
    }
 }

6 类设计

凝聚
一致
封装
清晰
完整
实例 vs. 静态
接口 vs. 抽象类


[1]Introduction to java programming 10th Chapter 13 Abstract Classes and Interfaces

猜你喜欢

转载自blog.csdn.net/ftell/article/details/80661323