文章目录
1 简介
超类定义相关子类的共同行为,而接口可以用来定义多个类(包括不相关的类)的共同行为。
虽然可以使用java.util.Arrays.sort
方法对一组数字或一组字符串进行排序,但是能用同样的sort
方法对一组几何对象进行排序吗?为了写出这样的代码, 必须使用接口。接口可以用来定义多个类 (包括不相关的类) 的共同行为。在讨论接口之前,先介绍一个密切相关的主题:抽象类。
2 抽象类
抽象类不能用于创建对象,一个抽象类可以包含抽象方法,抽象方法在具体的子类中实现。
在继承的体系结构中,类在子类中变得更为具体。如果沿着子类逐级向上查看超类,各个类的具体化程度将逐渐降低,越来越泛化。类设计应该保证超类包含子类的共同特征。有时超类过于抽象,不能用于创建任何具体的实例,这种类称为抽象类。
在第11章中,GeometricObject
被定义为Circle
和Rectangle
的超类。GeometricObject
模型包含了几何对象的共同特征,Circle
和 Rectangle
都包含了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()
都被定义为抽象方法, 这两个方法将在子类中实现。子类Circle
和Rectangle
使用extends
关键字继承GeometricObject
类:
// Circle.java
public class Circle extends GeometricObject {
//...
}
// Rectangle.java
public class Rectangle extends GeometricObject {
//...
}
2.1 抽象类的要点
- 非抽象类不能含有抽象方法。如果抽象超类的子类没有实现所有的抽象方法,则子类必须定义为抽象类。换句话说, 继承了抽象类的非抽象子类必须实现所有的抽象方法。抽象方法非静态。
- 抽象类不能用
new
操作符实例化,但仍可定义构造函数,子类的构造函数会调用它。例如GeometricObject
的构造函数被Circle
和Rectangle
类调用。 - 包含了抽象方法的类必须是抽象类,但抽象类却可以不包含抽象方法。这种情况下不能用
new
操作符创建类的实例,此类用于继承。 - 子类可以重写超类中的方法并将其定义为抽象方法,虽然非常罕见, 但如果超类中的方法的实现在子类中变得非法时,这种写法就有用。这种情况下,子类必须定义为抽象类。
- 即使超类是具体类,子类也可以是抽象类。例如虽然
Object
类是具体类,但是它的子类如GeometricObject
可以是抽象类。 - 不能用
new
操作符创建抽象类的实例,但是抽象类可用作数据类型。因此,如下创建以GeometricObject
为元素类型的数组的语句是正确的。
GeometricObject[] objects = new GeometricObject[10];
2.2 例子:抽象类Number
类Number
是抽象类,它是数字包装类BigDecimal
和BigInteger
的基类。
Number
含有6个抽象方法: intValue()
, longValue()
, doubleValue()
,floatValue()
, 另外两个方法shortValue
, byteValue
不是抽象方法,但是实现依赖于intValue()
的实现:
public short shortValue() {
return (Short)intValue();
}
public byte byteValue() {
return (byte)intValue();
}
下面的例子返回4个数 (整数 + 浮点 + BigDecimal
+BigInteger
) 中最大的数,代码实现如下:
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
对象,将调用Integer
的doubleValue()
, 如果数字为BigDecimal
对象,调用的是BigDecimal
的doubleValue()
。因为doubleValue()
定义在类Number
中,因此才有可能查找不同数据类型中的最大数。
2.3 例子:类Calendar
和GregorianCalendar
Calendar
: 日历
GregorianCalendar
: 格里高利历,简称格里历,即公历
Calendar
为抽象基类,GregorianCalendar
为具体子类。
Calendar
可以实现多个具体子类,例如格里历,阴历,犹太历等,Java目前对格里历的实现只有java.util.GregorianCalendar
。
可以通过域常量提取Calendar
对象的日期和时间信息, Calendar
类的域常量:
常量 | 描述 |
---|---|
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 " + 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 表示上午
September 11, 2001 is a Tuesday
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
interface EdibleInterface { //定义接口,注意关键字是interface
public abstract String howToEat(); //抽象方法howToEat()
}
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";
}
}
代码输出:
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个类Integer
,BigInteger
, String
和 Date
在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
假定n
为Integer
对象, s
为String
对象, d
为Date
对象,下列所有的表达式都为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库中的许多类(例如Date
,Calendar
和ArrayList
)都实现了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
, 所以 calendar
和calendar1
指向同一Calendar
对象。
接下来创建了一个新的对象即calendar
的克隆, 然后新对象的引用赋给calendar2
, calendar2
和calendar
是内容相同的不同对象。
另一个例子:
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
的克隆, 并将这个新对象的引用赋给 list2
, list1
和list2
是内容相同的不同对象。第6行将list1
的引用复制给list3
, 因此list1
和list3
指向相同的ArrayList
对象,第7行将 4.5 添加到list2
. 第8行将 1.5 从list3
中移除,因为list1
和list3
指向同一 ArrayList
, 第9行和第11行显示相同的内容。
如果普通的类要实现Cloneable
接口,就必须重写Object
类中的clone()
方法。下面的代码定义了一个实现了Cloneable
和Comparable
的叫做House
的类。
public class House implements Cloneable, Comparable<House> {
private int id;
private double area;
private java.util.Date whenBuilt;
public House(int id, double area) {
this.id = id;
this.area = area;
whenBuilt = new java.util.Date();
}
public int getId() {
return id;
}
public double getArea() {
return area;
}
public java.util.Date getWhenBuilt() {
return whenBuilt;
}
@Override /** Override the protected clone method defined in
the Object class, and strengthen its accessibility */
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override // Implement the compareTo method defined in Comparable
public int compareTo(House o) {
if (area > o.area)
return 1;
else if (area < o.area)
return -1;
else
return 0;
}
}
浅拷贝和深拷贝:简而言之,如果复制的对象的内容完全独立,为深拷贝。反之,若有数据域复制的是引用,那么两个对象的数据域指向相同的内存,则为浅拷贝,浅拷贝的结果: 一个对象改变,有可能自动引起另一个对象的变化。为了实现深拷贝,必须重写默认的clone
方法。
Object
类中定义的clone()
方法头:
protected native Object clone() throws CloneNotSupportedException;
关键字native
表示这个方法不是写在Java里的,而是为原生平台实现在JVM中,关键字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
关键字继承其他接口,这种接口叫子接口。例如,下列代码中的 NewInterface
是Interface1
, … 和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
的两个子类Chicken
和Duck
:
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
的子类,但是有一个限制:子类必须是另一种动物,例如说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