Learning Objectives
- 說出八種基本型別的用途與大致範圍,依資料選擇合適的型別
- 宣告變數並給初始值,遵守命名規則;知道區域變數使用前一定要先給值,final 常數只能給一次值
- 分辨自動的寬化轉換與手動的強制轉型,預測小數被截掉與整數溢位的結果
- 說明 char 與 int 的關係,會寫 long 與 float 的字面值(L、f)
- 用 var 宣告區域變數,用 Scanner 讀入資料,避開 nextInt() 之後接 nextLine() 的陷阱
變數與型別
程式要記住資料,就要用變數(variable):替一塊記憶體取個名字,之後用名字存取裡面的值。
Java 的每個變數都有固定的型別(type),宣告時就要寫出來,之後只能放這種型別的值:
int age = 20; // age 只能放整數
age = 21; // 可以:換成另一個整數
age = "二十一"; // 編譯錯誤:String 不能放進 int
學過 Python 的人要特別注意:Python 的變數可以先放數字、再放字串;Java 不行。這個限制的好處是,型別放錯的錯誤在編譯時就會被抓出來, 不用等到程式跑到那一行才出事。型別也決定了一個值占多少記憶體、能表示多大的數字、可以做哪些運算。
八種基本型別
Java 內建八種基本型別(primitive type),變數裡直接存放值:
| 型別 | 大小 | 範圍或精確度 | 用途 |
|---|---|---|---|
byte |
8 位元 | -128~127 | 檔案、網路的原始資料 |
short |
16 位元 | -32,768~32,767 | 較少使用 |
int |
32 位元 | 約 ±21 億(-2,147,483,648~2,147,483,647) | 整數的預設選擇 |
long |
64 位元 | 約 ±9.22 × 10¹⁸ | 超過 21 億的整數,例如時間毫秒數、人口總數 |
float |
32 位元 | 約 7 位有效數字 | 記憶體很吃緊時才用 |
double |
64 位元 | 約 15~16 位有效數字 | 小數的預設選擇 |
char |
16 位元 | 一個字元(Unicode 編號 0~65535) | 單一字元,用單引號:'A'、'學' |
boolean |
不指定 | 只有 true、false |
是非判斷 |
程式裡直接寫出來的值叫字面值(literal),它也有型別:
- 整數字面值預設是
int。超過 int 範圍的數字要在後面加L:long big = 3000000000L;,不加會編譯錯誤integer number too large - 小數字面值預設是
double。要放進float必須加f:float r = 0.75f;,寫成float r = 0.75;會編譯錯誤(double 轉 float 可能損失精確度) - 長數字可以用底線分隔,方便閱讀:
int population = 23_400_000;
八種基本型別的變數
每執行一行宣告,左邊的框架就多一個變數。注意 long 的字面值後面有 L、float 的字面值後面有 f。最後兩行印出 int 與 long 能表示的最大值:Integer.MAX_VALUE、Long.MAX_VALUE。
宣告、初始化與命名
int score; // 宣告:告訴編譯器有一個 int 變數叫 score
score = 80; // 指派:把 80 放進去(第一次指派就是初始化)
int bonus = 10; // 宣告的同時初始化(最常見)
int a = 1, b = 2; // 一次宣告好幾個同型別的變數
區域變數使用前一定要先給值。 寫在方法裡的變數叫區域變數(local variable),它沒有預設值。
宣告了 int x; 就直接 System.out.println(x);,javac 會回報 variable x might not have been initialized,程式無法編譯。
命名規則(違反就無法編譯):
- 只能用英文字母、數字、底線
_、錢字號$,而且不能以數字開頭 - 不能使用關鍵字,例如
int、class、public、new - 大小寫有別:
score和Score是兩個不同的變數
命名慣例(不會報錯,但大家都這樣寫):變數與方法用小駝峰 totalPrice,類別用大駝峰 Main,常數全大寫加底線 TAX_RATE。名稱要能看出用途,price 比 p 好懂。
final 常數:宣告時加上 final,這個變數就只能給一次值,之後再指派會編譯錯誤 cannot assign a value to final variable。
適合用在不該被改的值,例如稅率、每頁筆數。
String:文字用 String 型別,值用雙引號。String 不是基本型別,而是參照型別:變數存的是指向字串物件的參照(第 08 課詳細說明物件與參照)。
它有很多好用的方法,例如 name.length() 取得字數。
宣告、指派與常數
第 4 行只宣告、沒有給值,執行後 score 出現在框架裡,但還沒有值;第 5 行指派之後才能使用。第 12 行的 TAX_RATE 加了 final,之後任何指派都會被編譯器擋下。
想一想:宣告與型別
下面的程式執行結果是?
public class Main {
public static void main(String[] args) {
int count;
System.out.println("開始");
System.out.println(count);
}
}
下面哪一個變數宣告可以通過編譯?
下面的程式會印出什麼?
public class Main {
public static void main(String[] args) {
long n = 2147483647 + 1;
System.out.println(n);
}
}
型別轉換:寬化與強制轉型
把一種型別的值放進另一種型別的變數,分成兩種情況:
寬化轉換(widening):從範圍小的型別轉到範圍大的型別,不會失去資料,Java 自動進行。
byte → short → int → long → float → double
char ↗
int count = 7;
long big = count; // 自動轉成 long
double avg = count; // 自動轉成 7.0
強制轉型(casting):反過來從大轉小,可能失去資料,Java 不會自動做,必須在值前面寫 (型別):
double price = 3.99;
int whole = (int) price; // 3:小數部分直接截掉,不是四捨五入
int neg = (int) -2.7; // -2:往 0 的方向截斷
byte b = (byte) 200; // -56:超出 byte 範圍,只留下最低 8 位元
沒寫 (int) 就把 double 放進 int,javac 會回報 incompatible types: possible lossy conversion from double to int。
要四捨五入請用 Math.round(price)(第 03 課)。
整數溢位:int 運算的結果超過範圍時,Java 不會報錯,而是繞回另一端:Integer.MAX_VALUE + 1 等於 Integer.MIN_VALUE(-2147483648)。
數字可能很大時,一開始就用 long。
寬化、截斷與溢位
前三行是自動的寬化轉換。第 10、11 行用 (int) 強制轉型,注意 -2.7 變成 -2 而不是 -3。第 15 行加 1 之後,動畫會跳出 int 溢位的提醒。
char 與 int
char 存的其實是字元在 Unicode 裡的編號(16 位元的無號整數):'A' 是 65、'a' 是 97、'0' 是 48。所以:
char可以自動轉成int:int code = 'A';得到 65char參與運算時會先變成int:'A' + 1的結果是 int 66,要變回字元得寫(char) ('A' + 1)c++和c += 1會自動轉回char,結果是下一個字元- 數字字元換成數值:
'7' - '0'等於 7
注意 'A'(char,單引號)和 "A"(String,雙引號)是不同的型別。"" + 'A' + 1 會得到字串 "A1",因為字串接任何東西都變成字串(第 03 課)。
字元的編號
比較第 14 行與第 15 行:同樣是 'A' 和 1,前面有沒有字串,結果完全不同。
var 與讀入資料
var(Java 10 起):宣告區域變數時,如果右邊的值已經清楚表明型別,可以用 var 讓編譯器推斷:
var count = 3; // 推斷為 int
var unit = 12.5; // 推斷為 double
var label = "杯"; // 推斷為 String
var 不是「什麼都能放」:推斷完型別就固定了,count = 2.5; 一樣會編譯錯誤。var 只能用在有初始值的區域變數,var x; 不行。
初學時建議先寫出完整型別,等熟悉了再用 var 省略冗長的型別名稱。
用 Scanner 讀入資料:Scanner 是 Java 標準函式庫的類別,放在 java.util 套件裡,所以檔案最上面要先 import:
import java.util.Scanner; // 寫在 public class 之前
Scanner sc = new Scanner(System.in); // System.in 是鍵盤輸入
int age = sc.nextInt(); // 讀一個整數
double h = sc.nextDouble(); // 讀一個小數
String word = sc.next(); // 讀一個詞(遇到空白就停)
String line = sc.nextLine(); // 讀一整行(含空白)
陷阱:nextInt() 只讀走數字,使用者按下的 Enter(換行)還留在輸入裡。緊接著呼叫 nextLine(),它讀到的是這個剩下的換行,結果是空字串。
解決方法是在 nextInt() 之後先多呼叫一次 sc.nextLine(); 把換行讀掉,再讀真正要的那一行。
var 與 Scanner 的陷阱
這支程式的輸入是兩行:20 和 Amy Chen。動畫中讀入的內容會顯示在輸出裡,就像在終端機打字。注意第 14 行:nextLine() 讀到的是第一行剩下的換行,rest 是空字串;第 16 行才讀到姓名。
想一想:轉型、字元與輸入
下面的程式會印出什麼?
public class Main {
public static void main(String[] args) {
double d = 9.99;
int i = (int) d;
System.out.println(i + " " + (int) -9.99);
}
}
下面的程式會印出什麼?
public class Main {
public static void main(String[] args) {
char c = 'A';
c += 2;
System.out.println(c);
System.out.println('A' + 2);
}
}
使用者輸入兩行:第一行 5,第二行 Bob。執行下面的程式碼之後,s 的值是什麼?
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
String s = sc.nextLine();
動手寫:飲料訂單
程式要依序讀入商品名稱、單價、數量,算出總價,再用強制轉型得到去掉小數的金額。把五個 TODO 的 0 或 "" 換成正確的寫法。這次的輸入是 珍珠奶茶、55.5、3(讀入的內容會出現在輸出裡)。
重點回顧
- 八種基本型別:
byte、short、int、long、float、double、char、boolean;整數預設用int、小數預設用double - 超過 int 範圍的字面值加
L,float 字面值加f - 區域變數使用前一定要先給值;
final變數只能給一次值;名稱不能以數字開頭、不能用關鍵字 String是參照型別,存文字;'A'是 char、"A"是 String- 小轉大自動寬化;大轉小要寫
(型別)強制轉型,小數會往 0 截斷,超出範圍只留下低位元 - int 運算溢位不會報錯,結果繞回另一端;
char參與運算會先變成 int var讓編譯器推斷區域變數的型別,推斷後型別固定Scanner要import java.util.Scanner;;nextInt()之後接nextLine()會讀到空字串