學完本章你將能夠:
宣告變數、初始化變數,並依照命名慣例取出好名字。
說出 Java 八種基本型別的用途、大小與範圍。
分辨自動型別提升與強制轉型,並避開溢位與資料遺失的陷阱。
使用 final 宣告常數,並理解 var 型別推斷的使用時機。
初步分辨基本型別與參考型別的差異。
使用 Scanner 讀取使用者輸入,寫出會互動的程式。
上一章我們讓程式成功「說話」了,但那個程式只會印出寫死的文字。真正的程式 必須能記住東西——玩家的血量、購物車的金額、使用者輸入的名字。這些被記住的 資料就存放在「變數」(variable)裡。本章先學會建立與命名變數,接著認識 Java 為不同種類的資料準備的八種基本型別,最後讓程式第一次「聽」懂你從 鍵盤輸入的內容。
Java 是「靜態型別」語言:每個變數在出生時就必須說清楚自己裝的是哪一種 資料,而且終生不能改變。這個規定初期會讓你覺得囉嗦,但它讓編譯器能在 程式執行「之前」就抓出一大堆錯誤——這正是大型系統偏愛 Java 的原因之一。
變數:帶標籤的盒子
把記憶體想像成一整面置物櫃。宣告一個變數,就是向 Java 申請一個大小合適的 格子,並在上面貼一張標籤(變數名稱)。之後你隨時可以透過標籤找到這個格子, 把值放進去或取出來。宣告的語法是「型別 名稱」,例如 |int age| 表示「給我一個裝整數的格子,標籤寫 age」。
/** * 變數的宣告、初始化與重新指定值。 */ public class VariableBasics
public static void main(String[] args)
// 宣告:申請一個放整數的盒子,標籤叫 age int age;
// 指定值:把 18 放進盒子。第一次指定值稱為「初始化」 age = 18; System.out.println("年齡:" + age);
// 宣告的同時就初始化,是實務上最常見的寫法 int height = 172; System.out.println("身高:" + height);
// 變數的值可以被新值覆蓋——這正是「變」數的意義 age = 19; // 過了一年,長大一歲 System.out.println("新年齡:" + age);
// 同一行可宣告多個同型別變數(建議少用,可讀性較差) int x = 1, y = 2; System.out.println("x + y = " + (x + y));
年齡:18 身高:172 新年齡:19 x + y = 3
留意輸出裡的 |"年齡:" + age|:加號在這裡的作用是把文字與變數的值 「串接」成一個字串,這是輸出時最常用的技巧,下一章會深入它的規則。
區域變數(宣告在方法裡的變數)在使用前必須先指定值。只宣告 不初始化就拿來用,例如 |int a;| 之後直接 |System.out.println(a);|,會得到編譯錯誤「variable a might not have been initialized」。Java 寧可擋下你,也不讓一個內容不明的盒子 流入程式——這是它嚴謹性格的展現。
命名規則與慣例
變數名稱有「規則」與「慣例」兩個層次。規則是編譯器強制的法律,違反就 編譯失敗;慣例是業界共同的默契,違反雖能編譯,但會被同事皺眉。
規則只有幾條:名稱由字母、數字、底線與錢字符號組成;不能以數字開頭; 不能使用 Java 的保留字(如 |class|、|int|、|public|); 大小寫視為不同名稱。
慣例則以「駝峰式命名」(camelCase)為核心:變數與方法名稱第一個單字 全小寫,之後每個單字的字首大寫,例如 |totalPrice|、 |studentCount|。類別名稱則每個單字字首都大寫(PascalCase), 例如 |HelloWorld|、|BankAccount|。
| 名稱 | 合法嗎 | 說明 |
|---|---|---|
| |userName| | 合法 | 標準 camelCase,推薦寫法 |
| |totalPrice2| | 合法 | 數字可以出現在開頭以外的位置 |
| |_temp| | 合法但不建議 | 底線開頭慣例上留給特殊用途 |
| |2ndPlace| | 不合法 | 不能以數字開頭 |
| |user-name| | 不合法 | 連字號不是名稱的合法字元 |
| |class| | 不合法 | 保留字不能當名稱 |
名稱是最便宜的文件。|int s = 90;| 三個月後沒人記得 s 是什麼; |int studentScore = 90;| 則一眼就懂。取名的原則:寧可長而清楚, 不要短而神祕。變數命名用名詞(|orderCount|),布林變數用 is、has 開頭(|isFinished|、|hasTicket|),讀起來就像 一句話。
八種基本型別
Java 內建八種「基本型別」(primitive type),依用途分為四類:整數 (|byte|、|short|、|int|、|long|)、浮點數 (|float|、|double|)、字元(|char|)與布林 (|boolean|)。全貌如下表:
| 型別 | 位元數 | 範圍/說明 | 預設值 | 字面值範例 |
|---|---|---|---|---|
| |byte| | 8 | \(-128\) 到 \(127\) | |0| | |(byte) 100| |
| |short| | 16 | \(-32768\) 到 \(32767\) | |0| | |(short) 30000| |
| |int| | 32 | 約 \(\pm 21\) 億 | |0| | |123| |
| |long| | 64 | 約 \(\pm 9.2\times 10^{18}\) | |0L| | |123L| |
| |float| | 32 | 約 7 位有效數字 | |0.0f| | |3.14f| |
| |double| | 64 | 約 15 位有效數字 | |0.0| | |3.14| |
| |char| | 16 | 單一 Unicode 字元 | |’0̆000’| | |’A’| |
| |boolean| | — | 真或假 | |false| | |true| |
表中「預設值」是指欄位與陣列元素未指定值時自動獲得的值(第 5 章與第 8 章 會再遇到);區域變數沒有預設值,如前一節所述必須自行初始化。實務上的 選擇很簡單:整數用 |int|,數字可能超過 21 億才用 |long|; 小數一律用 |double|;|byte|、|short|、|float| 多半只在節省記憶體或對接特定格式時才登場。
整數型別與字面值
程式碼中直接寫出的值稱為「字面值」(literal),例如 |123|。 整數字面值有幾種讓程式更好讀的寫法:
/** * 整數字面值的各種寫法:底線分隔、long 後綴、二進位與十六進位。 */ public class IntegerLiterals
public static void main(String[] args)
// 整數字面值預設是 int 型別 int population = 23_000_000; // 底線只是分隔,方便人眼閱讀 System.out.println("台灣人口約:" + population);
// 超過 int 範圍的字面值必須加 L 後綴,存入 long long worldPop = 8_000_000_000L; System.out.println("世界人口約:" + worldPop);
// 二進位字面值:0b 開頭(b 是 binary 的縮寫) int flags = 0b1010; // 等於十進位的 10 System.out.println("0b1010 = " + flags);
// 十六進位字面值:0x 開頭,常見於顏色與底層資料 int color = 0xFF; // 等於十進位的 255 System.out.println("0xFF = " + color);
台灣人口約:23000000 世界人口約:8000000000 0b1010 = 10 0xFF = 255
注意兩件事:底線只是給人看的,印出來時不會出現;寫 |8_000_000_000| 而不加 |L| 會直接編譯錯誤,因為這個字面值本身已經超過 |int| 的極限。
為什麼 |int| 的範圍是 \(-2^{31}\) 到 \(2^{31}-1\)(大約正負 21 億)? 因為它固定佔 32 個位元,其中 1 個位元表示正負號,剩下 31 個位元表示 數值。你可以用 |Integer.MAX_VALUE| 與 |Integer.MIN_VALUE| 取得精確的邊界值:2147483647 與 \(-2147483648\)。
浮點型別
帶小數點的字面值預設是 |double|;想存進 |float| 必須加上 |f| 後綴。兩者的差別在精度:
/** * double 與 float 的精度差異。 */ public class FloatingPointDemo
public static void main(String[] args)
// double:雙精度,約可保留 15 位有效數字 double pi = 3.141592653589793; System.out.println("pi = " + pi);
// float:單精度,只能保留約 7 位有效數字, // 字面值必須加 f 後綴,否則編譯器當它是 double float f = 1.23456789f; System.out.println("f = " + f); // 尾數被捨入了!
// 科學記號寫法:1.5e-3 表示 1.5 乘以 10 的負 3 次方 double tiny = 1.5e-3; System.out.println("tiny = " + tiny);
pi = 3.141592653589793 f = 1.2345679 tiny = 0.0015
觀察輸出:我們存入 |1.23456789|,印出來卻是 |1.2345679|—— |float| 的精度只有約 7 位,塞不下的部分被捨入了。這不是臭蟲, 是型別天生的限制。實務上小數一律先選 |double|;至於「連 double 都存不準」的情況(例如金額計算),第 3 章會示範並給出解法。
char 與 Unicode
|char| 存放「恰好一個」字元,字面值用單引號。Java 的字元 採用 Unicode 編碼,所以中文字、日文字都是合法的 |char|:
/** * char 的本質:一個 0 到 65535 的整數,對應一個 Unicode 字元。 */ public class CharDemo
public static void main(String[] args)
// char 字面值用單引號,只能放一個字元 char grade = ’A’; System.out.println("等第:" + grade);
char word = ’中’; // 中文字也是一個 char System.out.println("字元:" + word);
// 把 char 轉成 int,就能看到它背後的 Unicode 編號 System.out.println("它的編號:" + (int) word);
// 也可以用 Unicode 跳脫序列來表示同一個字 char same = ’4̆E2D’; // 與 ’中’ 完全相同 System.out.println("same = " + same);
// 字元可以參與運算——因為它骨子裡是整數 char next = (char) (grade + 1); System.out.println("A 的下一個字母:" + next);
等第:A 字元:中 它的編號:20013 same = 中 A 的下一個字母:B
單引號與雙引號在 Java 中意義完全不同:|’A’| 是字元 (|char|),|"A"| 是只有一個字的字串(|String|)。 寫 |char c = "A";| 會編譯錯誤。這與某些語言(如 Python) 兩者通用的習慣不同,從其他語言轉來的讀者請特別注意。
boolean
|boolean| 只有 |true| 與 |false| 兩種值,專門回答 「是非題」。它是下一章比較運算與第 4 章流程控制的靈魂:
/** * boolean:程式世界的是非題。 */ public class BooleanDemo
public static void main(String[] args)
boolean isAdult = true; boolean hasTicket = false; System.out.println("成年了嗎?" + isAdult); System.out.println("有票嗎?" + hasTicket);
// boolean 最常見的來源:比較運算的結果 int age = 16; boolean canVote = age >= 18; // 16 >= 18 不成立 System.out.println("可以投票嗎?" + canVote);
成年了嗎?true 有票嗎?false 可以投票嗎?false
型別轉換
不同型別之間的值經常需要互相流動:整數存進浮點數、大範圍塞進小範圍。 Java 把轉換分成兩種:安全的「自動提升」與有風險的「強制轉型」。
自動提升:小杯倒大杯
把小範圍型別的值放進大範圍型別,就像把小杯的水倒進大杯,絕不會溢出, 所以 Java 自動放行,不需任何額外語法。提升的方向是:
|byte| 到 |short| 到 |int| 到 |long| 到 |float| 到 |double|(由小到大單向流動)。
|char| 也可以提升為 |int| 以上的型別。
/** * 自動型別提升:小範圍流向大範圍,安全且不需要任何語法。 */ public class WideningDemo
public static void main(String[] args)
int i = 100; long l = i; // int 自動提升為 long double d = l; // long 自動提升為 double
System.out.println("i = " + i); System.out.println("l = " + l); System.out.println("d = " + d); // 變成小數形式的 100.0
i = 100 l = 100 d = 100.0
強制轉型:大杯倒小杯的風險
反過來,把大範圍的值塞進小範圍型別,可能裝不下。Java 不會自動放行, 你必須寫出「轉型運算子」|(型別)| 明確表態:「我知道有風險, 後果自負」。風險有兩種:小數被截斷,以及數值溢位。
/** * 窄化轉型的兩種風險:小數截斷與數值溢位。 */ public class NarrowingDemo
public static void main(String[] args)
// 風險一:小數轉整數時,小數部分被「無條件捨去」 double price = 3.99; int dollars = (int) price; // 不是四捨五入! System.out.println("3.99 轉 int = " + dollars);
// 風險二:超出目標型別的範圍時,數值會「繞圈」失真 int big = 130; byte small = (byte) big; // byte 只能到 127 System.out.println("130 轉 byte = " + small);
// 更隱蔽的溢位:int 運算結果超過 21 億,不會報錯, // 只會默默給你一個錯得離譜的值 int a = 2_000_000_000; int sum = a + a; System.out.println("20 億 + 20 億 = " + sum);
// 解法:先把其中一個運算元提升為 long 再相加 long safeSum = (long) a + a; System.out.println("正確結果 = " + safeSum);
3.99 轉 int = 3 130 轉 byte = -126 20 億 + 20 億 = -294967296 正確結果 = 4000000000
130 為什麼變成 \(-126\)?因為 |byte| 只有 8 個位元,130 的二進位 超出了它能表示的範圍,多出來的位元被硬生生砍掉,剩下的位元恰好是 \(-126\) 的表示法。你不需要現在就會算,但要記住現象:窄化轉型 與整數溢位都不會報錯,只會安靜地給錯值。
「安靜的錯誤」比「大聲的錯誤」危險一百倍。歷史上不少重大軟體事故 源自整數溢位——系統沒有當機,只是算出了錯誤的金額或座標。防禦方式: 數值可能很大時提早用 |long|;轉型前先確認值域;金額計算避免 用浮點數(第 3 章詳述)。
final 常數
有些值一旦設定就不該再變:稅率、圓周率、遊戲人數上限。在宣告前加上 |final|,這個變數就只能被指定一次值,之後任何修改都會被編譯器 擋下。慣例上常數名稱使用全大寫,單字之間用底線分隔:
/** * final 常數:只能指定一次值,天塌下來都不能改。 */ public class FinalDemo
public static void main(String[] args)
// 常數命名慣例:全大寫、單字用底線分隔 final double TAX_RATE = 0.05; final int MAX_PLAYERS = 4;
double price = 1000; double total = price * (1 + TAX_RATE); System.out.println("含稅價:" + total); System.out.println("人數上限:" + MAX_PLAYERS);
// TAX_RATE = 0.08; // 取消註解會編譯錯誤: // cannot assign a value to final variable TAX_RATE
含稅價:1050.0 人數上限:4
程式裡憑空出現的數字稱為「魔術數字」(magic number),例如 |if (count > 4)| 的 4 是什麼意思?沒人知道。把它命名成 |MAX_PLAYERS| 之後,程式自己會說話,而且將來規則改成 6 人時 只需要改一個地方。看到重複出現、有業務意義的數字,就該考慮宣告成 final 常數。
var:讓編譯器幫你寫型別
Java 10 引入了 |var|:宣告區域變數時,讓編譯器從右邊的初始值 「推斷」型別,你就不必重複寫一次。務必理解:|var| 不是「動態 型別」,型別在編譯的當下就已經確定,之後同樣不能改變。
/** * var 區域變數型別推斷:型別由右邊的初始值決定。 */ public class VarDemo
public static void main(String[] args)
var count = 10; // 推斷為 int var name = "Java"; // 推斷為 String var ratio = 0.5; // 推斷為 double
System.out.println(count + ", " + name + ", " + ratio);
// 型別推斷完就固定了:var 不是「什麼都能裝」的魔法盒 // count = "十"; // 編譯錯誤:不能把字串塞進 int 變數
10, Java, 0.5
|var| 的限制與使用建議:
只能用於區域變數,欄位、方法參數、回傳型別都不能用。
宣告時必須同時給初始值,否則編譯器無從推斷。
初始值不能是 |null|——推斷不出型別。
使用時機:右邊已經把型別寫得清清楚楚時(例如 |var sc = new Scanner(System.in);|),用 var 可減少 重複;右邊型別不明顯時,老實寫出型別對讀者更友善。
基本型別與參考型別初探
Java 的型別世界分成兩大陣營。基本型別就是本章的八種,變數的 盒子裡直接放「值本身」;除此之外的一切型別——字串 |String|、 陣列、以及你未來自己定義的類別——都是參考型別(reference type),變數的盒子裡放的不是資料本身,而是資料所在位置的「參考」, 就像盒子裡放的不是電視機,而是電視的遙控器。
現在先記住兩個立即有感的差別:
基本型別的變數互相指定時,複製的是「值」;參考型別複製的是 「遙控器」,兩個變數會遙控同一台電視。這件事在第 5 章的陣列 與第 6 章的方法傳參會產生重要影響。
|String| 開頭是大寫、不在八種基本型別之列——它是參考 型別,所以字串比較不能用 |==|,細節留到第 7 章。
記憶體的角度:基本型別的值輕巧固定,直接存放在變數所在的位置; 參考型別的實際資料住在一塊叫「堆積」(heap)的記憶體區域,變數只 保存它的地址。這個模型現在只需要有個畫面,等學到陣列與物件時, 我們會反覆回來使用它。
用 Scanner 讀取使用者輸入
到目前為止,程式的資料都是我們寫死的。|Scanner| 是 Java 內建的 輸入工具,能讀取使用者從鍵盤輸入的內容,讓程式真正動起來。使用它需要 三步:第一,在檔案開頭 |import java.util.Scanner;| 引入;第二, 用 |new Scanner(System.in)| 建立掃描器;第三,依照想讀的型別 呼叫對應方法——|nextInt()| 讀整數、|nextDouble()| 讀小數、 |nextLine()| 讀一整行文字。
import java.util.Scanner;
/** * 讀取使用者輸入:詢問姓名、年齡與身高,再組合成回覆。 */ public class ScannerDemo
public static void main(String[] args)
// System.in 代表鍵盤輸入,把它交給 Scanner 處理 Scanner scanner = new Scanner(System.in);
System.out.print("請輸入你的名字:"); String name = scanner.nextLine(); // 讀一整行文字
System.out.print("請輸入你的年齡:"); int age = scanner.nextInt(); // 讀一個整數
System.out.print("請輸入你的身高(公尺):"); double height = scanner.nextDouble(); // 讀一個小數
System.out.println("你好," + name + "!"); System.out.println("明年你就 " + (age + 1) + " 歲了。"); System.out.println("你的身高是 " + height + " 公尺。");
scanner.close(); // 用完關閉,釋放系統資源
請輸入你的名字:小明 請輸入你的年齡:18 請輸入你的身高(公尺):1.72 你好,小明! 明年你就 19 歲了。 你的身高是 1.72 公尺。
範例中的提示文字用 |print| 而不是 |println|,讓游標停在 同一行等待輸入,這是比較好的使用體驗。另外注意 |(age + 1)| 的 括號:先做數學加法,再參與字串串接(原因見第 3 章)。
經典陷阱:|nextInt()| 只讀走「數字」,使用者按下的 Enter (換行字元)還留在輸入緩衝區裡。如果下一步呼叫 |nextLine()|, 它會立刻讀到那個殘留的換行,回傳空字串,看起來就像「程式跳過了 輸入」。解法:在 |nextInt()| 之後、真正要讀文字之前,先補一次 |scanner.nextLine()| 把殘留的換行吃掉。
本章小結
變數是帶標籤的記憶體盒子;宣告語法為「型別 名稱」,區域變數 使用前必須初始化。
命名規則:不能數字開頭、不能用保留字、大小寫敏感;慣例上 變數用 camelCase,類別用 PascalCase,常數全大寫。
八種基本型別:整數選 |int|(超過 21 億用 |long|),小數選 |double|,字元 |char| 用單引號,是非題用 |boolean|。
整數字面值可用底線分隔、|0b| 二進位與 |0x| 十六進位;long 字面值要加 |L|,float 要加 |f|。
小範圍到大範圍自動提升;反向必須強制轉型,可能發生小數截斷 與溢位,而且不會報錯。
|final| 讓變數變成只能指定一次的常數,消滅魔術數字。
|var| 只能用在有初始值的區域變數,型別在編譯期就固定。
八種基本型別存「值」,其他型別(含 |String|)存 「參考」。
|Scanner| 搭配 |nextInt|、|nextDouble|、 |nextLine| 讀取輸入;小心 nextInt 之後殘留的換行字元。
本章練習
觀念題:下列名稱哪些是合法的變數名稱?|myScore|、 |3dModel|、|user_name|、|int|、 |priceInNTD|。不合法的請說明原因。(提示:數字開頭 與保留字是兩大殺手。)
觀念題:|int| 的最大值是 2147483647,請問再加 1 會 發生什麼事?程式會當機嗎?(提示:溢位會「繞圈」回到 最小值,而且不會報錯。)
實作題:宣告八種基本型別的變數各一個,指定合理的值並全部 印出;long 與 float 記得加後綴。(提示:字面值後綴 L 與 f 忘記加會編譯錯誤。)
實作題:寫一個攝氏轉華氏的程式:用 Scanner 讀入攝氏溫度 (整數),印出華氏溫度。公式為攝氏乘以 9 除以 5 再加 32。 測試輸入 37 應得到 98.6。(提示:小心整數除法!9 / 5 會 變成 1,把其中一個數字寫成 9.0 試試。)
實作題:用 Scanner 連續讀入你的姓名(一行文字)與幸運數字 (整數),先讀數字再讀姓名,觀察是否踩到換行殘留的陷阱, 並修正它。(提示:nextInt 之後補一次 nextLine。)
觀念題:|var text = "hello";| 之後,能不能執行 |text = 42;|?為什麼?(提示:var 的型別在編譯期 就固定了。)