實現一個線程安全的分數類,支持加減乘除運算與自動約分。採用不可變模式:所有運算返回新實例,原對象不變,天然線程安全。
a * b / GCD(a, b)package net.zj.fraction;
import java.io.IOException;
public final class Fraction {
private final int numerator; // 分子
private final int denominator; // 分母
// 構造函數:自動約分並規範化符號
public Fraction(int numerator, int denominator) {
if (denominator == 0) throw new ArithmeticException("分母不能為零");
// 符號統一到分子
if (denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
int gcd = gcd(Math.abs(numerator), denominator);
this.numerator = numerator / gcd;
this.denominator = denominator / gcd;
}
public Fraction(int numerator) {
this(numerator, 1);
}
// 靜態工廠方法:從字符串解析
public static Fraction parse(String s) {
String[] parts = s.split("/");
try {
if (parts.length == 2) {
return new Fraction(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
} else if (parts.length == 1) {
return new Fraction(Integer.parseInt(parts[0]));
}
} catch (NumberFormatException e) {
throw new IllegalArgumentException("無效分數格式: " + s);
}
throw new IllegalArgumentException("無效分數格式: " + s);
}
// 四則運算:返回新實例
public Fraction add(Fraction other) {
int lcm = lcm(this.denominator, other.denominator);
int n1 = this.numerator * (lcm / this.denominator);
int n2 = other.numerator * (lcm / other.denominator);
return new Fraction(n1 + n2, lcm);
}
public Fraction subtract(Fraction other) {
int lcm = lcm(this.denominator, other.denominator);
int n1 = this.numerator * (lcm / this.denominator);
int n2 = other.numerator * (lcm / other.denominator);
return new Fraction(n1 - n2, lcm);
}
public Fraction multiply(Fraction other) {
return new Fraction(this.numerator * other.numerator, this.denominator * other.denominator);
}
public Fraction divide(Fraction other) {
if (other.numerator == 0) throw new ArithmeticException("除數不能為零");
return new Fraction(this.numerator * other.denominator, this.denominator * other.numerator);
}
// 靜態輔助方法
private static int gcd(int a, int b) {
while (b != 0) {
int temp = a % b;
a = b;
b = temp;
}
return a;
}
private static int lcm(int a, int b) {
return a / gcd(a, b) * b; // 避免溢出
}
// 輸出格式化
@Override
public String toString() {
return denominator == 1 ? String.valueOf(numerator) : numerator + "/" + denominator;
}
// 交互式主程序
public static void main(String[] args) throws IOException {
System.out.println("輸入格式:");
System.out.println(" 單個分數(自動約分):3/6");
System.out.println(" 運算表達式:1/2 + 1/3");
System.out.println("支持運算符:+ - * /");
System.out.println("輸入 'exit' 退出\n");
java.util.Scanner scanner = new java.util.Scanner(System.in);
while (true) {
System.out.print("> ");
String line = scanner.nextLine().trim();
if ("exit".equalsIgnoreCase(line)) break;
if (line.isEmpty()) continue;
try {
String[] tokens = line.split("\\s+");
if (tokens.length == 1) {
System.out.println(Fraction.parse(tokens[0]));
} else if (tokens.length == 3) {
Fraction f1 = Fraction.parse(tokens[0]);
Fraction f2 = Fraction.parse(tokens[2]);
Fraction result = switch (tokens[1]) {
case "+" -> f1.add(f2);
case "-" -> f1.subtract(f2);
case "*" -> f1.multiply(f2);
case "/" -> f1.divide(f2);
default -> throw new IllegalArgumentException("未知運算符: " + tokens[1]);
};
System.out.println(result);
} else {
System.out.println("格式錯誤,請重新輸入");
}
} catch (Exception e) {
System.out.println("錯誤: " + e.getMessage());
}
}
}
}
> 3/6
1/2
> 1/2 + 1/3
5/6
> 5/4 - 1/2
3/4
> 2/3 * 3/4
1/2
> 3/4 / 2/3
9/8
> -2/4
-1/2
> 3/-6
-1/2
| 特性 | 實現方式 | 優勢 |
|---|---|---|
| 不可變性 | final 欄位、無 setter、運算返回新對象 | 線程安全、可安全共享、適合作為 Map 鍵 |
| 自動約分 | 構造函數中調用 GCD | 保證唯一標準形式、避免分子分母溢出 |
| 符號規範化 | 負號統一移至分子 | 消除 -1/-2 vs 1/2 等等價表示差異 |
| 防溢出 LCM | a / gcd * b 而非 a * b / gcd | 減少中間結果溢出風險 |
| 豐富靜態工廠 | parse() 支持整數與分數字符串 | API 友好、易於集成解析邏輯 |
Comparable<Fraction> 支持排序BigInteger 版本支持任意精度doubleValue()、floatValue() 轉換方法Serializable 支持持久化