前言....
本節我們將介紹Java程序的基礎知識,包括:
-
Java程序基本結構
-
變量和數據類型
-
整數運算
-
浮點數運算
-
布爾運算
-
字符和字符串
-
數組類型
Java程序基本結構
我們先剖析一個完整的Java程序,它的基本結構是什么:
** * 可以用來自動創建文檔的注釋 */ public class Hello { public static void main(String[] args) { // 向屏幕輸出文本: System.out.println("Hello, world!"); /* 多行注釋開始 注釋內容 注釋結束 */ } } // class定義結束
因為Java是面向對象的語言,一個程序的基本單位就是class
,class
是關鍵字,這里定義的class
名字就是Hello
:
public class Hello { // 類名是Hello // ... } // class定義結束
類名要求:
- 類名必須以英文字母開頭,后接字母,數字和下划線的組合
- 習慣以大寫字母開頭
要注意遵守命名習慣,好的類命名:
- Hello
- NoteBook
- VRPlayer
不好的類命名:
- hello
- Good123
- Note_Book
- _World
注意到public
是訪問修飾符,表示該class
是公開的。
不寫public
,也能正確編譯,但是這個類將無法從命令行執行。
在class
內部,可以定義若干方法(method):
public class Hello { public static void main(String[] args) { // 方法名是main // 方法代碼... } // 方法定義結束 }
這里的方法名是main
,返回值是void
,表示沒有任何返回值。
我們注意到public
除了可以修飾class
外,也可以修飾方法。而關鍵字static
是另一個修飾符,它表示靜態方法,后面我們會講解方法的類型,目前,我們只需要知道,Java入口程序規定的方法必須是靜態方法,方法名必須為main
,括號內的參數必須是String數組。
方法名也有命名規則,命名和class
一樣,但是首字母小寫:
在方法內部,語句才是真正的執行代碼。Java的每一行語句必須以分號結束:
public class Hello { public static void main(String[] args) { System.out.println("Hello, world!"); // 語句 } }
在Java程序中,注釋是一種給人閱讀的文本,不是程序的一部分,所以編譯器會自動忽略注釋。
Java有3種注釋,第一種是單行注釋,以雙斜線開頭,直到這一行的結尾結束:
// 這是注釋...
而多行注釋以/*
星號開頭,以*/
結束,可以有多行:
/* 這是注釋 blablabla... 這也是注釋 */
還有一種特殊的多行注釋,以/**
開頭,以*/
結束,如果有多行,每行通常以星號開頭:
/** * 可以用來自動創建文檔的注釋 * * @auther liaoxuefeng */ public class Hello { public static void main(String[] args) { System.out.println("Hello, world!"); } }
這種特殊的多行注釋需要寫在類和方法的定義處,可以用於自動創建文檔。
變量和數據類型
變量
什么是變量?
變量就是初中數學的代數的概念,例如一個簡單的方程,x,y都是變量:
y=x^2+1y=x2+1
在Java中,變量分為兩種:基本類型的變量和引用類型的變量。
我們先討論基本類型的變量。
在Java中,變量必須先定義后使用,在定義變量的時候,可以給它一個初始值。例如:
int x = 1;
上述語句定義了一個整型int
類型的變量,名稱為x
,初始值為1
。
來看一個完整的定義變量,然后打印變量值的例子:
public class Main { public static void main(String[] args) { int x = 100; // 定義int類型變量x,並賦予初始值100 System.out.println(x); // 打印該變量的值,觀察是否為100 x = 200; // 重新賦值為200 System.out.println(x); // 打印該變量的值,觀察是否為200 } }
100 200
注意到第一次定義變量x
的時候,需要指定變量類型int
,因此使用語句int x = 100;
。而第二次重新賦值的時候,變量x
已經存在了,不能再重復定義,因此不能指定變量類型int
,必須使用語句x = 200;
。
變量不但可以重新賦值,還可以賦值給其他變量。讓我們來看一個例子:
//變量之間賦值
public class Main { public static void main(String[] args) { int n = 100; // 定義變量n,同時賦值為100 System.out.println("n = " + n); // 打印n的值 n = 200; // 變量n賦值為200 System.out.println("n = " + n); // 打印n的值 int x = n; // 變量x賦值為n(n的值為200,因此賦值后x的值也是200) System.out.println("x = " + x); // 打印x的值 x = x + 100; // 變量x賦值為x+100(x的值為200,因此賦值后x的值是200+100=300) System.out.println("x = " + x); // 打印x的值 System.out.println("n = " + n); // 再次打印n的值,n應該是200還是300? } }
我們一行一行地分析代碼執行流程:
執行int n = 100;
,該語句定義了變量n
,同時賦值為100
,因此,JVM在內存中為變量n
分配一個“存儲單元”,填入值100
:
n
│
▼
┌───┬───┬───┬───┬───┬───┬───┐
│ │100│ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┘
執行n = 200;
時,JVM把200
寫入變量n
的存儲單元,因此,原有的值被覆蓋,現在n
的值為200
:
n
│
▼
┌───┬───┬───┬───┬───┬───┬───┐
│ │200│ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┘
執行int x = n;
時,定義了一個新的變量x
,同時對x
賦值,因此,JVM需要新分配一個存儲單元給變量x
,並寫入和變量n
一樣的值,結果是變量x
的值也變為200
:
n x
│ │
▼ ▼
┌───┬───┬───┬───┬───┬───┬───┐
│ │200│ │ │200│ │ │
└───┴───┴───┴───┴───┴───┴───┘
執行x = x + 100;
時,JVM首先計算等式右邊的值x + 100
,結果為300
(因為此刻x
的值為200
),然后,將結果300
寫入x
的存儲單元,因此,變量x
最終的值變為300
:
n x
│ │
▼ ▼
┌───┬───┬───┬───┬───┬───┬───┐
│ │200│ │ │300│ │ │
└───┴───┴───┴───┴───┴───┴───┘
可見,變量可以反復賦值。注意,等號=
是賦值語句,不是數學意義上的相等,否則無法解釋x = x + 100
。
基本數據類型
基本數據類型是CPU可以直接進行運算的類型。Java定義了以下幾種基本數據類型:
-
整數類型:byte,short,int,long
-
浮點數類型:float,double
-
字符類型:char
-
布爾類型:boolean
Java定義的這些基本數據類型有什么區別呢?要了解這些區別,我們就必須簡單了解一下計算機內存的基本結構。
計算機內存的最小存儲單元是字節(byte),一個字節就是一個8位二進制數,即8個bit。它的二進制表示范圍從00000000
~11111111
,換算成十進制是0~255,換算成十六進制是00
~ff
。
內存單元從0開始編號,稱為內存地址。每個內存單元可以看作一間房間,內存地址就是門牌號。
0 1 2 3 4 5 6 ...
┌───┬───┬───┬───┬───┬───┬───┐
│ │ │ │ │ │ │ │...
└───┴───┴───┴───┴───┴───┴───┘
一個字節是1byte,1024字節是1K,1024K是1M,1024M是1G,1024G是1T。一個擁有4T內存的計算機的字節數量就是:
4T = 4 x 1024G = 4 x 1024 x 1024M = 4 x 1024 x 1024 x 1024K = 4 x 1024 x 1024 x 1024 x 1024 = 4398046511104
不同的數據類型占用的字節數不一樣。我們看一下Java基本數據類型占用的字節數:
┌───┐
byte │ │
└───┘
┌───┬───┐
short │ │ │
└───┴───┘
┌───┬───┬───┬───┐
int │ │ │ │ │
└───┴───┴───┴───┘
┌───┬───┬───┬───┬───┬───┬───┬───┐
long │ │ │ │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┘
┌───┬───┬───┬───┐
float │ │ │ │ │
└───┴───┴───┴───┘
┌───┬───┬───┬───┬───┬───┬───┬───┐
double │ │ │ │ │ │ │ │ │
└───┴───┴───┴───┴───┴───┴───┴───┘
┌───┬───┐
char │ │ │
└───┴───┘
byte
恰好就是一個字節,而long
和double
需要8個字節。
整型
對於整型類型,Java只定義了帶符號的整型,因此,最高位的bit表示符號位(0表示正數,1表示負數)。各種整型能表示的最大范圍如下:
byte:-128 ~ 127 short: -32768 ~ 32767 int: -2147483648 ~ 2147483647 long: -9223372036854775808 ~ 9223372036854775807
//定義整形
public class Main { public static void main(String[] args) { int i = 2147483647; int i2 = -2147483648; int i3 = 2_000_000_000; // 加下划線更容易識別 int i4 = 0xff0000; // 十六進制表示的16711680 int i5 = 0b1000000000; // 二進制表示的512 long l = 9000000000000000000L; // long型的結尾需要加L } }
特別注意:同一個數的不同進制的表示是完全相同的,例如15
=0xf
=0b1111
。
浮點型
浮點類型的數就是小數,因為小數用科學計數法表示的時候,小數點是可以“浮動”的,如1234.5可以表示成12.345x102,也可以表示成1.2345x103,所以稱為浮點數。
下面是定義浮點數的例子:
float f1 = 3.14f; float f2 = 3.14e38f; // 科學計數法表示的3.14x10^38 double d = 1.79e308; double d2 = -1.79e308; double d3 = 4.9e-324; // 科學計數法表示的4.9x10^-324
對於float
類型,需要加上f
后綴。
浮點數可表示的范圍非常大,float
類型可最大表示3.4x1038,而double
類型可最大表示1.79x10308。
布爾類型
布爾類型boolean
只有true
和false
兩個值,布爾類型總是關系運算的計算結果:
boolean b1 = true; boolean b2 = false; boolean isGreater = 5 > 3; // 計算結果為true int age = 12; boolean isAdult = age >= 18; // 計算結果為false
Java語言對布爾類型的存儲並沒有做規定,因為理論上存儲布爾類型只需要1 bit,但是通常JVM內部會把boolean
表示為4字節整數
字符類型
字符類型char
表示一個字符。Java的char
類型除了可表示標准的ASCII外,還可以表示一個Unicode字符:
inal double PI = 3.14; // PI是一個常量 double r = 5.0; double area = PI * r * r; PI = 300; // compile error!
常量在定義時進行初始化后就不可再次賦值,再次賦值會導致編譯錯誤。
常量的作用是用有意義的變量名來避免魔術數字(Magic number),例如,不要在代碼中到處寫3.14
,而是定義一個常量。如果將來需要提高計算精度,我們只需要在常量的定義處修改,例如,改成3.1416
,而不必在所有地方替換3.14
。
根據習慣,常量名通常全部大寫。
定義變量時,要遵循作用域最小化原則,盡量將變量定義在盡可能小的作用域,並且,不要重復使用變量名。
小結
Java提供了兩種變量類型:基本類型和引用類型
基本類型包括整型,浮點型,布爾型,字符型。
變量可重新賦值,等號是賦值語句,不是數學意義的等號。
常量在初始化后不可重新賦值,使用常量便於理解程序意圖
整數運算
Java的整數運算遵循四則運算規則,可以使用任意嵌套的小括號。四則運算規則和初等數學一致。例如:
public class Main { public static void main(String[] args) { int i = (100 + 200) * (99 - 88); // 3300 int n = 7 * (5 + (i - 9)); // 23072 System.out.println(i); System.out.println(n); } }
整數的數值表示不但是精確的,而且整數運算永遠是精確的,即使是除法也是精確的,因為兩個整數相除只能得到結果的整數部分:
int x = 12345 / 67; // 184
求余運算使用%
:
int y = 12345 % 67; // 12345÷67的余數是17
特別注意:整數的除法對於除數為0時運行時將報錯,但編譯不會報錯。
溢出
要特別注意,整數由於存在范圍限制,如果計算結果超出了范圍,就會產生溢出,而溢出不會出錯,卻會得到一個奇怪的結果
public class Main { public static void main(String[] args) { int x = 2147483640; int y = 15; int sum = x + y; System.out.println(sum); // -2147483641 } }
要解釋上述結果,我們把整數2147483640
和15
換成二進制做加法:
0111 1111 1111 1111 1111 1111 1111 1000 + 0000 0000 0000 0000 0000 0000 0000 1111 ----------------------------------------- 1000 0000 0000 0000 0000 0000 0000 0111
由於最高位計算結果為1
,因此,加法結果變成了一個負數。
要解決上面的問題,可以把int
換成long
類型,由於long
可表示的整型范圍更大,所以結果就不會溢出:
long x = 2147483640; long y = 15; long sum = x + y; System.out.println(sum); // 2147483655
還有一種簡寫的運算符,即+=
,-=
,*=
,/=
,它們的使用方法如下:
n += 100; // 3409, 相當於 n = n + 100; n -= 100; // 3309, 相當於 n = n - 100;
自增/自減
Java還提供了++
運算和--
運算,它們可以對一個整數進行加1和減1的操作:
public class Main { public static void main(String[] args) { int n = 3300; n++; // 3301, 相當於 n = n + 1; n--; // 3300, 相當於 n = n - 1; int y = 100 + (++n); // 不要這么寫 System.out.println(y); } }
注意++
寫在前面和后面計算結果是不同的,++n
表示先加1再引用n,n++
表示先引用n再加1。不建議把++
運算混入到常規運算中,容易自己把自己搞懵了。
移位運算
在計算機中,整數總是以二進制的形式表示。例如,int
類型的整數7
使用4字節表示的二進制如下:
00000000 0000000 0000000 00000111
可以對整數進行移位運算。對整數7
左移1位將得到整數14
,左移兩位將得到整數28
:
int n = 7; // 00000000 00000000 00000000 00000111 = 7 int a = n << 1; // 00000000 00000000 00000000 00001110 = 14 int b = n << 2; // 00000000 00000000 00000000 00011100 = 28 int c = n << 28; // 01110000 00000000 00000000 00000000 = 1879048192 int d = n << 29; // 11100000 00000000 00000000 00000000 = -536870912
左移29位時,由於最高位變成1
,因此結果變成了負數。
類似的,對整數28進行右移,結果如下:
int n = 7; // 00000000 00000000 00000000 00000111 = 7 int a = n >> 1; // 00000000 00000000 00000000 00000011 = 3 int b = n >> 2; // 00000000 00000000 00000000 00000001 = 1 int c = n >> 3; // 00000000 00000000 00000000 00000000 = 0
如果對一個負數進行右移,最高位的1
不動,結果仍然是一個負數
int n = -536870912; int a = n >> 1; // 11110000 00000000 00000000 00000000 = -268435456 int b = n >> 2; // 10111000 00000000 00000000 00000000 = -134217728 int c = n >> 28; // 11111111 11111111 11111111 11111110 = -2 int d = n >> 29; // 11111111 11111111 11111111 11111111 = -1
還有一種不帶符號的右移運算,使用>>>
,它的特點是符號位跟着動,因此,對一個負數進行>>>
右移,它會變成正數,原因是最高位的1
變成了0
:
int n = -536870912; int a = n >>> 1; // 01110000 00000000 00000000 00000000 = 1879048192 int b = n >>> 2; // 00111000 00000000 00000000 00000000 = 939524096 int c = n >>> 29; // 00000000 00000000 00000000 00000111 = 7 int d = n >>> 31; // 00000000 00000000 00000000 00000001 = 1
對byte
和short
類型進行移位時,會首先轉換為int
再進行位移。
仔細觀察可發現,左移實際上就是不斷地×2,右移實際上就是不斷地÷2。
位運算
位運算是按位進行與、或、非和異或的運算。
與運算的規則是,必須兩個數同時為1
,結果才為1
:
n = 0 & 0; // 0 n = 0 & 1; // 0 n = 1 & 0; // 0 n = 1 & 1; // 1
或運算的規則是,只要任意一個為1
,結果就為1
:
n = 0 | 0; // 0 n = 0 | 1; // 1 n = 1 | 0; // 1 n = 1 | 1; // 1
非運算的規則是,0
和1
互換:
n = ~0; // 1 n = ~1; // 0
異或運算的規則是,如果兩個數不同,結果為1
,否則為0
:
n = 0 ^ 0; // 0 n = 0 ^ 1; // 1 n = 1 ^ 0; // 1 n = 1 ^ 1; // 0
對兩個整數進行位運算,實際上就是按位對齊,然后依次對每一位進行運算。例如:
public class Main { public static void main(String[] args) { int i = 167776589; // 00001010 00000000 00010001 01001101 int n = 167776512; // 00001010 00000000 00010001 00000000 System.out.println(i & n); // 167776512 } }
上述按位與運算實際上可以看作兩個整數表示的IP地址10.0.17.77
和10.0.17.0
,通過與運算,可以快速判斷一個IP是否在給定的網段內。
運算優先級
在Java的計算表達式中,運算優先級從高到低依次是:
()
!
~
++
--
*
/
%
+
-
<<
>>
>>>
&
|
+=
-=
*=
/=
類型自動提升與強制轉型
在運算過程中,如果參與運算的兩個數類型不一致,那么計算結果為較大類型的整型。例如,short
和int
計算,結果總是int
,原因是short
首先自動被轉型為int
:
public class Main { public static void main(String[] args) { short s = 1234; int i = 123456; int x = s + i; // s自動轉型為int short y = s + i; // 編譯錯誤! } }
也可以將結果強制轉型,即將大范圍的整數轉型為小范圍的整數。強制轉型使用(類型)
,例如,將int
強制轉型為short
:
int i = 12345; short s = (short) i; // 12345
要注意,超出范圍的強制轉型會得到錯誤的結果,原因是轉型時,int
的兩個高位字節直接被扔掉,僅保留了低位的兩個字節
public class Main { public static void main(String[] args) { int i1 = 1234567; short s1 = (short) i1; // -10617 System.out.println(s1); int i2 = 12345678; short s2 = (short) i2; // 24910 System.out.println(s2); } }
因此,強制轉型的結果很可能是錯的。
整數運算的結果永遠是精確的;
運算結果會自動提升;
可以強制轉型,但超出范圍的強制轉型會得到錯誤的結果;
應該選擇合適范圍的整型(int
或long
),沒有必要為了節省內存而使用byte
和short
進行整數運算。