cv.cm

編碼 · 程式語言

Java 入門教學

閱讀約 9 分鐘

簡答

Java 是靜態型別的物件導向語言,編譯成位元組碼後在 Java 虛擬機器(JVM)上執行,可跨作業系統運作。安裝 JDK 後,將類別存成 Hello.java,再執行 java Hello.java 即可。核心概念包括類別與物件、基本型別與參考型別、集合框架(List、Map、Set)、受檢例外,以及 record、var 與模式比對等較新的功能。

開啟正規測試

Java 是什麼,用來做什麼

Java 由 Sun Microsystems 於 1995 年發布,現在由開放的 OpenJDK 專案開發,Oracle 是主要維護者。它的承諾是「一次撰寫,到處執行」:編譯器產生位元組碼,再由 JVM 在 Windows、macOS 與 Linux 上執行。Java 用於大型企業後端(常搭配 Spring Boot)、Android 應用程式(與 Kotlin 並行)、Apache Kafka 與 Hadoop 等大數據工具,以及許多桌面與嵌入式系統。新版本每六個月發布一次,正式環境通常選用 Java 21 這類長期支援(LTS)版本。

安裝 JDK 並執行 Hello, world

請安裝 JDK(Java Development Kit),例如 Eclipse Temurin 這類 OpenJDK 發行版或 Oracle 的 JDK,並用 java -version 確認。每個 Java 程式都存在於類別中,執行從 main 開始。

public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world");
    }
}
java Hello.java        # run a single source file directly
# Hello, world
javac Hello.java       # or compile to Hello.class first
java Hello

檔名必須與 public 類別名稱相同(class Hello 對應 Hello.java)。

變數與基本型別

Java 有八種基本型別(primitive type),直接以值儲存,另外還有參考型別(例如 String 這類物件與陣列)。每個變數都有固定的型別;在方法內,var 可讓編譯器根據初始值推論型別。

型別大小範例
int32 位元int n = 42;
long64 位元long big = 10_000_000_000L;
double64 位元浮點數double pi = 3.14159;
booleantrue/falseboolean ok = true;
char16 位元 UTF-16 單元char c = 'A';
byte, short, float8、16、32 位元較少使用
String參考型別String s = "hi";
int count = 7;
double avg = count / 2;          // 3.0, integer division happens first
double avg2 = count / 2.0;       // 3.5
var name = "Ada";                // inferred as String
String msg = name + " has " + count + " items";
System.out.println(msg);         // Ada has 7 items
System.out.println(Integer.parseInt("12") + 1); // 13

這只是程式片段:請把這類陳述式放進 main 之類的方法中。

流程控制

int score = 72;
if (score >= 90) {
    System.out.println("A");
} else if (score >= 70) {
    System.out.println("B");     // B
} else {
    System.out.println("C");
}
for (int i = 0; i < 3; i++) {
    System.out.println(i);       // 0, 1, 2
}
String day = "SAT";
String kind = switch (day) {     // switch expression
    case "SAT", "SUN" -> "weekend";
    default -> "weekday";
};
System.out.println(kind);        // weekend

Java 還有 while、do/while,以及走訪陣列與集合的增強式 for (String s : list) 迴圈。

方法、類別與 record

類別把欄位(資料)與方法(行為)組合在一起。static 方法屬於類別本身;其他方法則要在以 new 建立的物件上呼叫。record 是宣告不可變資料類別的簡潔寫法:編譯器會自動產生建構子、存取方法、equals、hashCode 與 toString。

public class Shapes {
    static double square(double x) {
        return x * x;
    }
    static class Counter {
        private int value;
        void increment() { value++; }
        int get() { return value; }
    }
    record Point(int x, int y) {
        double distance() { return Math.sqrt(x * x + y * y); }
    }
    public static void main(String[] args) {
        System.out.println(square(4));        // 16.0
        Counter c = new Counter();
        c.increment();
        c.increment();
        System.out.println(c.get());          // 2
        Point p = new Point(3, 4);
        System.out.println(p + " " + p.distance()); // Point[x=3, y=4] 5.0
    }
}

類別可以繼承一個父類別(extends),並實作多個介面(implements)。欄位通常是 private,透過方法來存取。

集合:List、Map 與 Set

java.util 中的集合框架提供可調整大小的資料結構。集合存放的是物件,所以基本型別會自動裝箱(int 變成 Integer)。

import java.util.*;
public class Collections101 {
    public static void main(String[] args) {
        List<Integer> nums = new ArrayList<>(List.of(3, 1, 2));
        nums.add(5);
        Collections.sort(nums);
        System.out.println(nums + " " + nums.get(0));  // [1, 2, 3, 5] 1
        Map<String, Integer> ages = new HashMap<>();
        ages.put("ann", 31);
        ages.put("bob", 25);
        System.out.println(ages.getOrDefault("zed", 0)); // 0
        Set<String> tags = new TreeSet<>(List.of("b", "a", "b"));
        System.out.println(tags);                        // [a, b]
        List<Integer> evensSquared = nums.stream()
            .filter(n -> n % 2 == 0)
            .map(n -> n * n)
            .toList();
        System.out.println(evensSquared);                // [4]
    }
}

最後一個陳述式使用了串流(stream)與 lambda,這是轉換集合的函數式風格。List.of 會建立不可變的 list;需要修改時,請用 new ArrayList<>(...) 包起來。

招牌特色:靜態型別、介面與模式比對

介面定義了許多類別都能實作的契約,這是大多數 Java 設計的基礎。現代 Java 加入了模式比對:instanceof 與 switch 可以在同一步驟中測試型別並綁定變數。密封介面(sealed interface,Java 17 起)會列出所有允許的實作,因此編譯器能檢查 switch 是否涵蓋所有情況。

public class Areas {
    sealed interface Shape permits Circle, Rect {}
    record Circle(double r) implements Shape {}
    record Rect(double w, double h) implements Shape {}
    static double area(Shape s) {
        return switch (s) {
            case Circle c -> Math.PI * c.r() * c.r();
            case Rect r -> r.w() * r.h();
        };
    }
    public static void main(String[] args) {
        System.out.println(area(new Rect(2, 3)));  // 6.0
    }
}

這樣的 switch 模式比對在 Java 21 LTS 及之後已是標準功能。

例外處理

錯誤即例外。受檢例外(checked exception,例如 IOException)必須被捕捉或以 throws 宣告;非受檢例外(RuntimeException 的子類別,例如 NullPointerException)則不需要。try-with-resources 會自動關閉檔案與連線。

import java.io.*;
import java.nio.file.*;
public class ReadFirst {
    static int parseAge(String text) {
        int age = Integer.parseInt(text);   // may throw NumberFormatException
        if (age < 0) throw new IllegalArgumentException("negative age");
        return age;
    }
    public static void main(String[] args) {
        try {
            System.out.println(parseAge("abc"));
        } catch (NumberFormatException e) {
            System.out.println("not a number");  // not a number
        }
        try (BufferedReader in = Files.newBufferedReader(Path.of("notes.txt"))) {
            System.out.println(in.readLine());
        } catch (IOException e) {
            System.out.println("cannot read: " + e.getMessage());
        }
    }
}

完整的小程式:單字計數器

import java.io.IOException;
import java.nio.file.*;
import java.util.*;
public class WordCount {
    public static void main(String[] args) throws IOException {
        if (args.length != 1) {
            System.err.println("usage: java WordCount.java FILE");
            System.exit(1);
        }
        String text = Files.readString(Path.of(args[0])).toLowerCase();
        Map<String, Integer> counts = new HashMap<>();
        for (String word : text.split("[^a-z']+")) {
            if (!word.isEmpty()) counts.merge(word, 1, Integer::sum);
        }
        counts.entrySet().stream()
            .sorted(Map.Entry.<String, Integer>comparingByValue().reversed())
            .limit(5)
            .forEach(e -> System.out.printf("%5d  %s%n", e.getValue(), e.getKey()));
    }
}

以 java WordCount.java notes.txt 執行。它會印出出現次數最多的五個單字及其次數。

初學者常見錯誤

  • 整數除法:5 / 2 的結果是 2。讓其中一邊是 double 才會得到 2.5。
  • 對 null 呼叫方法會拋出 NullPointerException。請檢查 null,或對可能缺少的值使用 Optional。
  • 試圖修改以 List.of 建立的 list,會拋出 UnsupportedOperationException。
  • 忘記陣列與 List 的索引從 0 開始,所以最後一個元素在 length - 1。

接下來學什麼

跟著 Java 官方開發者網站 dev.java 上的學習路徑學習,並隨時打開 Java SE API 文件當作參考。接著學習建置工具(Maven 或 Gradle)、使用 JUnit 進行單元測試,再學習 Spring Boot 等框架來開發網路服務。同樣執行在 JVM 上的 Kotlin 是很自然的下一個語言。

常見問題

Java 很難學嗎?

Java 比 Python 更冗長,因為你要宣告型別並把程式碼放進類別,但它的規則一致,編譯器也會及早指出錯誤。許多大學把它當作第一門程式語言。

JDK 和 JVM 有什麼差別?

JVM 負責執行編譯好的位元組碼。JDK 則是完整的開發工具包:包含 JVM、編譯器 javac、標準函式庫與各種工具。開發者要安裝的是 JDK。

應該學哪個 Java 版本?

請學目前的長期支援版本,例如 Java 21 或更新的 LTS。舊程式碼仍能在新的 JVM 上執行,所以學會現代寫法也能應用在既有專案上。

Java 和 JavaScript 一樣嗎?

不一樣。雖然名稱相似,但它們是兩種不同的語言,型別系統、執行環境與用途都不同。

現在還有人用 Java 嗎?

有。它仍然是企業後端、Android 與資料基礎架構中最常用的語言之一。

全部編碼教學