構造器方法名與類名相同,不需要符合首字母小寫的編程風格。
在 Java 中,對象的創建與初始化是統一的概念,二者不可分割。
【類】:class DataOnly {
int i;
double d;
boolean b;
} /*這個類除了存儲數據外什么也不能做,但是我們仍然可以通過下面的代碼來創建它的一個對象*/
【對象】: String s = new String("asdf");
賦值符號左側是 對象的引用即s,右側是創建一個對象。
(關鍵字:new,創建一個對象)
(暫時理解到這里。。)
DataOnly data = new DataOnly();
(PS:通過這個對象的引用來指定字段值 data.i = 47;
data.d = 1.1;
data.b = false;
)
【方法】:
[返回類型] [方法名](/*參數列表*/){
// 方法體
}
比如:(自己寫的不知道對不對)
void increment(i = 1) {
i++;
}
【調用一個對象的方法】:
[對象引用].[方法名](參數1, 參數2, 參數3);
(暫時不知道怎么舉例子)
【構造器】:
class Rock {
Rock() { // 這是一個構造器
System.out.print("Rock ");
}
}
如果一個類有構造器,那么 Java 會在用戶使用對象之前(即對象剛創建完成)自動調用對象的構造器方法,從而保證初始化
注意:構造器名稱與類名相同。
舉個例子:(書上的)
// housekeeping/SimpleConstructor.java // Demonstration of a simple constructor class Rock { Rock() { // 這是一個構造器 System.out.print("Rock "); } } public class SimpleConstructor { public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Rock(); } } }
輸出:
Rock Rock Rock Rock Rock Rock Rock Rock Rock Rock
現在,當創建一個對象時:new Rock() ,內存被分配,構造器被調用。
構造器保證了對象在你使用它之前進行了正確的初始化。
因此,在 Java 中,對象的創建與初始化是統一的概念。
//構造器也可以接受參數
例子:
// housekeeping/SimpleConstructor2.java // Constructors can have arguments class Rock2 { Rock2(int i) { System.out.print("Rock " + i + " "); } } public class SimpleConstructor2 { public static void main(String[] args) { for (int i = 0; i < 8; i++) { new Rock2(i); } } }
輸出:
Rock 0 Rock 1 Rock 2 Rock 3 Rock 4 Rock 5 Rock 6 Rock 7
// 構造器方法-構造方法
構造器沒有返回值,它是一種特殊的方法。
書中例子:
// housekeeping/Overloading.java
// Both constructor and ordinary method overloading
class Tree {
int height;
Tree() {
System.out.println("Planting a seedling");
height = 0
} //一個類中只有一個構造器名
Tree(int initialHeight) {
height = initialHeight;
System.out.println("Creating new Tree that is " + height + " feet tall");
} //方法的重載(重復上面的內容 構造器是一種方法)
void info() {
System.out.println("Tree is " + height + " feet tall");
} //方法-info()方法 //方法: [返回類型][方法名](參數列表)
void info(String s) {
System.out.println(s + ": Tree is " + height + " feet tall");
} // info()方法的重載
}
public class Overloading {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Tree t = new Tree(i);
t.info();
t.info("overloaded method");
}
new Tree();
}
}
//不能通過【方法的返回值】來區分方法,而是要通過【類名和參數列表】
因為:如果你想調用一個方法且忽略返回值
比如這個:
void f(){} int f() {return 1;}
現我直接調用f(),Java編譯器也不知道我想要調用哪個方法
無參構造器
(就是不接收參數的構造器)
如果你創建一個類,類中沒有構造器,那么編譯器就會自動為你創建一個無參構造器。
更進一步解釋:
當類中沒有構造器時,編譯器會說"你一定需要構造器,那么讓我為你創建一個吧"。
但是如果類中有構造器,編譯器會說"你已經寫了構造器了,所以肯定知道你在做什么,如果你沒有創建默認構造器,說明你本來就不需要"。
舉例:
// housekeeping/DefaultConstructor.java
class Bird {}
public class DefaultConstructor { public static void main(String[] args) { Bird bird = new Bird(); // 默認的 } }
表達式 new Bird() 創建了一個新對象,調用了無參構造器
//盡管在 Bird 類中並沒有顯式的定義無參構造器。
// housekeeping/NoSynthesis.java
class Bird2 { Bird2(int i) {} Bird2(double d) {} }
public class NoSynthesis { public static void main(String[] args) { //- Bird2 b = new Bird2(); // No default Bird2 b2 = new Bird2(1); Bird2 b3 = new Bird2(1.0); } }
如果你調用了 new Bird2() ,編譯器會提示找不到匹配的構造器。
//this 關鍵字只能在非靜態方法內部使用。
如果你在一個類的方法里調用其他該類中的方法,不要使用 this,直接調用即可,this 自動地應用於其他方法上了。
比如:
// housekeeping/Apricot.java public class Apricot { void pick() { /* ... */ } void pit() { pick(); //你可以使用 this.pick(),但是沒有必要。編譯器自動為你做了這些。 /* ... */ } }
當你調用一個對象的方法時,this 生成了一個對象引用。
你可以像對待其他引用一樣對待這個引用。
// housekeeping/Leaf.java // Simple use of the "this" keyword public class Leaf { int i = 0; Leaf increment() { //一個對象的方法,對象的創建在main方法里 i++; return this; //個人理解: return this 是return這個方法中的內容? } void print() { System.out.println("i = " + i); } public static void main(String[] args) { Leaf x = new Leaf(); x.increment().increment().increment().print(); } }
-----------這是個我暫時理解不了的例子---------------
// housekeeping/PassingThis.java class Person { public void eat(Apple apple) { Apple peeled = apple.getPeeled(); System.out.println("Yummy"); } } public class Peeler { static Apple peel(Apple apple) { // ... remove peel return apple; // Peeled } } public class Apple { Apple getPeeled() { return Peeler.peel(this); //引用靜態方法可以直接 類名.靜態方法; } } public class PassingThis { public static void main(String[] args) { new Person().eat(new Apple()); } }
【調用一個對象的方法】:
[對象引用].[方法名](參數1, 參數2, 參數3);
-----------這是個我暫時理解不了的例子---------------
@ 意味着這是一個注解,注解是關於代碼的額外信息。(@Override)
System.gc() 用於強制進行終結動作。
初始化
利用構造器初始化
// housekeeping/Counter.java public class Counter { int i; Counter() { i = 7; } // ... }
i 首先會被初始化為 0,然后變為 7。
對於所有的基本類型和引用,包括在定義時已明確指定初值的變量,這種情況都是成立的。
因此,編譯器不會強制你一定要在構造器的某個地方或在使用它們之前初始化元素——初始化早已得到了保證。
初始化的順利
中有個例子不理解 不應該是只輸出f()嗎?
// housekeeping/OrderOfInitialization.java
// Demonstrates initialization order
// When the constructor is called to create a
// Window object, you'll see a message:
class Window {
Window(int marker) {
System.out.println("Window(" + marker + ")");
}
}
class House {
Window w1 = new Window(1); // Before constructor
House() {
// Show that we're in the constructor:
System.out.println("House()");
w3 = new Window(33); // Reinitialize w3
}
Window w2 = new Window(2); // After constructor
void f() {
System.out.println("f()");
}
Window w3 = new Window(3); // At end
}
public class OrderOfInitialization {
public static void main(String[] args) {
House h = new House();
h.f(); // Shows that construction is done
}
}
輸出:
Window(1)
Window(2)
Window(3)
House()
Window(33)
f()
靜態數據的初始化
static 關鍵字不能應用於局部變量
如果一個字段是靜態的基本類型,你沒有初始化它,那么它就會獲得基本類型的標准初值。
如果它是對象引用,那么它的默認初值就是 null。
還是個不理解的例子,但是作者有給解釋,只能說大概理解吧,具體的也不太理解
// housekeeping/StaticInitialization.java
// Specifying initial values in a class definition
class Bowl {
Bowl(int marker) {
System.out.println("Bowl(" + marker + ")");
}
void f1(int marker) {
System.out.println("f1(" + marker + ")");
}
}
class Table {
static Bowl bowl1 = new Bowl(1);
Table() {
System.out.println("Table()");
bowl2.f1(1);
}
void f2(int marker) {
System.out.println("f2(" + marker + ")");
}
static Bowl bowl2 = new Bowl(2);
}
class Cupboard {
Bowl bowl3 = new Bowl(3);
static Bowl bowl4 = new Bowl(4);
Cupboard() {
System.out.println("Cupboard()");
bowl4.f1(2);
}
void f3(int marker) {
System.out.println("f3(" + marker + ")");
}
static Bowl bowl5 = new Bowl(5);
}
public class StaticInitialization {
public static void main(String[] args) {
System.out.println("main creating new Cupboard()");
new Cupboard();
System.out.println("main creating new Cupboard()");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
輸出:
Bowl(1)
Bowl(2)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
main creating new Cupboard()
Bowl(3)
Cupboard()
f1(2)
main creating new Cupboard()
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
初始化的順序先是靜態對象(如果它們之前沒有被初始化的話),然后是非靜態對象,從輸出中可以看出。
要執行 main() 方法,必須加載 StaticInitialization 類,
它的靜態屬性 table 和 cupboard 隨后被初始化,這會導致它們對應的類也被加載,
而由於它們都包含靜態的 Bowl 對象,所以 Bowl 類也會被加載。
因此,在這個特殊的程序中,
所有的類都會在 main() 方法之前被加載。
實際情況通常並非如此,因為在典型的程序中,不會像本例中所示的那樣,將所有事物通過 static 聯系起來。
作者對創建過程的概括:
概括一下創建對象的過程,假設有個名為 Dog 的類:
- 即使沒有顯式地使用 static 關鍵字,構造器實際上也是靜態方法。所以,當首次創建 Dog 類型的對象或是首次訪問 Dog 類的靜態方法或屬性時,Java 解釋器必須在類路徑中查找,以定位 Dog.class。
- 當加載完 Dog.class 后(后面會學到,這將創建一個 Class 對象),有關靜態初始化的所有動作都會執行。因此,靜態初始化只會在首次加載 Class 對象時初始化一次。
- 當用
new Dog()創建對象時,首先會在堆上為 Dog 對象分配足夠的存儲空間。 - 分配的存儲空間首先會被清零,即會將 Dog 對象中的所有基本類型數據設置為默認值(數字會被置為 0,布爾型和字符型也相同),引用被置為 null。
- 執行所有出現在字段定義處的初始化動作。
- 執行構造器。你將會在"復用"這一章看到,這可能會牽涉到很多動作,尤其當涉及繼承的時候。
顯式的靜態初始化
將一組靜態初始化動作放在類里面一個特殊的"靜態子句"(靜態塊)
// housekeeping/Spoon.java public class Spoon { static int i; static { i = 47; } }
看起來像個方法,但實際上它只是一段跟在 static 關鍵字后面的代碼塊。
與其他靜態初始化動作一樣,這段代碼僅執行一次:
當首次創建這個類的對象或首次訪問這個類的靜態成員(甚至不需要創建該類的對象)時。
來咯,又是看不太懂作者想表達什么
// housekeeping/ExplicitStatic.java // Explicit static initialization with "static" clause class Cup { Cup(int marker) { System.out.println("Cup(" + marker + ")"); } void f(int marker) { System.out.println("f(" + marker + ")"); } } class Cups { static Cup cup1; static Cup cup2; static { cup1 = new Cup(1); cup2 = new Cup(2); } Cups() { System.out.println("Cups()"); } } public class ExplicitStatic { public static void main(String[] args) { System.out.println("Inside main()"); Cups.cup1.f(99); // [1] } // static Cups cups1 = new Cups(); // [2] // static Cups cups2 = new Cups(); // [2] }
輸出:
Inside main
Cup(1)
Cup(2)
f(99)
無論是通過標為 [1] 的行訪問靜態的 cup1 對象,還是把標為 [1] 的行去掉,讓它去運行標為 [2] 的那行代碼(去掉 [2] 的注釋),Cups 的靜態初始化動作都會執行。、
如果同時注釋 [1] 和 [2] 處,那么 Cups 的靜態初始化就不會進行。此外,把標為 [2] 處的注釋都去掉還是只去掉一個,靜態初始化只會執行一次。
非靜態實例初始化
Java 提供了被稱為實例初始化的類似語法,用來初始化每個對象的非靜態變量,例如
// housekeeping/Mugs.java
// Instance initialization
class Mug {
Mug(int marker) {
System.out.println("Mug(" + marker + ")");
}
}
public class Mugs {
Mug mug1;
Mug mug2;
{ // [1]
mug1 = new Mug(1);
mug2 = new Mug(2);
System.out.println("mug1 & mug2 initialized");
}
Mugs() {
System.out.println("Mugs()");
}
Mugs(int i) {
System.out.println("Mugs(int)");
}
public static void main(String[] args) {
System.out.println("Inside main()");
new Mugs();
System.out.println("new Mugs() completed");
new Mugs(1);
System.out.println("new Mugs(1) completed");
}
}
輸出:
Inside main
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs()
new Mugs() completed
Mug(1)
Mug(2)
mug1 & mug2 initialized
Mugs(int)
new Mugs(1) completed
作者的話:
看起來它很像靜態代碼塊,只不過少了 static 關鍵字。
這種語法對於支持"匿名內部類"(參見"內部類"一章)的初始化是必須的,
但是你也可以使用它保證某些操作一定會發生,而不管哪個構造器被調用。
從輸出看出,實例初始化子句是在兩個構造器之前執行的。
自己現在:理解不了。。
數組初始化
數組:
int[] a1;
int a1[];
這兩種含義是一樣的
作者說:前一種格式或許更合理,畢竟它表明類型是"一個 int 型數組"。本書中采用這種格式。
編譯器不允許指定數組的大小。
作者說:
這又把我們帶回有關"引用"的問題上。
你所擁有的只是對數組的一個引用(你已經為該引用分配了足夠的存儲空間),
但是還沒有給數組對象本身分配任何空間。
為了給數組創建相應的存儲空間,必須寫初始化表達式。
對於數組,初始化動作可以出現在代碼的任何地方,
但是也可以使用一種特殊的初始化表達式,它必須在創建數組的地方出現。
這種特殊的初始化是由一對花括號括起來的值組成。
這種情況下,存儲空間的分配(相當於使用 new) 將由編譯器負責。例如:
int[] a1 = {1, 2, 3, 4, 5};
那么為什么在還沒有數組的時候定義一個數組引用呢?
int[] a2;
在 Java 中可以將一個數組賦值給另一個數組,所以可以這樣:
a2 = a1;
其實真正做的只是復制了一個引用,就像下面演示的這樣:
// housekeeping/ArraysOfPrimitives.java public class ArraysOfPrimitives {
public static void main(String[] args) { int[] a1 = {1, 2, 3, 4, 5}; int[] a2; a2 = a1; for (int i = 0; i < a2.length; i++) { a2[i] += 1; } for (int i = 0; i < a1.length; i++) { System.out.println("a1[" + i + "] = " + a1[i]); } }
}
輸出:
a1[0] = 2;
a1[1] = 3;
a1[2] = 4;
a1[3] = 5;
a1[4] = 6;
a1 初始化了,但是 a2 沒有;這里,a2 在后面被賦給另一個數組。
由於 a1 和 a2 是相同數組的別名,因此通過 a2 所做的修改在 a1 中也能看到。
作者還說:
所有的數組(無論是對象數組還是基本類型數組)都有一個固定成員 length,告訴你這個數組有多少個元素,你不能對其修改。
與 C 和 C++ 類似,Java 數組計數也是從 0 開始的,所能使用的最大下標數是 length - 1。超過這個邊界,C 和 C++ 會默認接受,允許你訪問所有內存,許多聲名狼藉的 bug 都是由此而生。
但是 Java 在你訪問超出這個邊界時,會報運行時錯誤(異常),從而避免此類問題。
動態數組創建
作者說:
如果在編寫程序時,不確定數組中需要多少個元素,那么該怎么辦呢?你可以直接使用 new 在數組中創建元素。
下面例子中,盡管創建的是基本類型數組,new 仍然可以工作(不能用 new 創建單個的基本類型數組):
//對於括號里面的我是這樣理解的:不能用new創建基本類型數組里面只有一個數據的數組
// housekeeping/ArrayNew.java // Creating arrays with new import java.util.*; public class ArrayNew { public static void main(String[] args) { int[] a; Random rand = new Random(47); a = new int[rand.nextInt(20)]; System.out.println("length of a = " + a.length); System.out.println(Arrays.toString(a)); } }
輸出:
length of a = 18
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
本書解釋:
數組的大小是通過 Random.nextInt() 隨機確定的,這個方法會返回 0 到輸入參數之間的一個值。
由於隨機性,很明顯數組的創建確實是在運行時進行的。
此外,程序輸出表明,數組元素中的基本數據類型值會自動初始化為空值
(對於數字和字符是 0;對於布爾型是 false)。
Arrays.toString() 是 java.util 標准類庫中的方法,會產生一維數組的可打印版本。
本例中,數組也可以在定義的同時進行初始化:
int[] a = new int[rand.nextInt(20)];
如果可能的話,應該盡量這么做。
如果你創建了一個非基本類型的數組,那么你創建的是一個引用數組。以整型的包裝類型 Integer 為例,它是一個類而非基本類型:
// housekeeping/ArrayClassObj.java // Creating an array of nonprimitive objects import java.util.*; public class ArrayClassObj { public static void main(String[] args) { Random rand = new Random(47); Integer[] a = new Integer[rand.nextInt(20)]; System.out.println("length of a = " + a.length); for (int i = 0; i < a.length; i++) { a[i] = rand.nextInt(500); // Autoboxing } System.out.println(Arrays.toString(a)); } }
輸出:
length of a = 18
[55, 193, 361, 461, 429, 368, 200, 22, 207, 288, 128, 51, 89, 309, 278, 498, 361, 20]
這里,即使使用 new 創建數組之后:
Integer[] a = new Integer[rand.nextInt(20)];
它只是一個引用數組,直到通過創建新的 Integer 對象(通過自動裝箱),並把對象賦值給引用,初始化才算結束:
a[i] = rand.nextInt(500);
//如果忘記了創建對象,但試圖使用數組中的空引用,就會在運行時產生異常。
也可以用花括號括起來的列表來初始化數組,有兩種形式:
// housekeeping/ArrayInit.java // Array initialization import java.util.*; public class ArrayInit { public static void main(String[] args) { Integer[] a = { 1, 2, 3, // Autoboxing }; Integer[] b = new Integer[] { 1, 2, 3, // Autoboxing }; System.out.println(Arrays.toString(a)); System.out.println(Arrays.toString(b)); } }
輸出:
[1, 2, 3]
[1, 2, 3]
//在這兩種形式中,初始化列表的最后一個逗號是可選的(這一特性使維護長列表變得更容易)。
盡管第一種形式很有用,但是它更加受限,因為它只能用於數組定義處。第二種和第三種形式可以用在任何地方,甚至用在方法的內部。
例如,你創建了一個 String 數組,將其傳遞給另一個類的 main() 方法,如下:
// housekeeping/DynamicArray.java // Array initialization public class DynamicArray { public static void main(String[] args) { Other.main(new String[] {"fiddle", "de", "dum"}); } } class Other { public static void main(String[] args) { for (String s: args) { //我的解釋:將DynamicArray類中的args數組一個一個放進前面定義的String s變量然后迭代(迭代:即循環) System.out.print(s + " "); } } }
輸出:
fiddle de dum
作者說:
Other.main() 的參數是在調用處創建的,因此你甚至可以在方法調用處提供可替換的參數。
//不太懂自己想象了一波。。
可變參數列表
// housekeeping/VarArgs.java
// Using array syntax to create variable argument lists
class A {}
public class VarArgs {
static void printArray(Object[] args) {
for (Object obj: args) {
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String[] args) {
printArray(new Object[] {47, (float) 3.14, 11.11});
printArray(new Object[] {"one", "two", "three"});
printArray(new Object[] {new A(), new A(), new A()});
}
}
輸出:
47 3.14 11.11
one two three
A@15db9742 A@6d06d69c A@7852e922
作者的解釋:
printArray() 的參數是 Object 數組,使用 for-in 語法遍歷和打印數組的每一項。
標准 Java 庫能輸出有意義的內容,但這里創建的是類的對象,打印出的內容是類名,后面跟着一個 @ 符號以及多個十六進制數字。
因而,默認行為(如果沒有定義 toString() 方法的話,后面會講這個方法)就是打印類名和對象的地址
(邊看作者的解釋邊自己理解還行)
你可能看到像上面這樣編寫的 Java 5 之前的代碼,它們可以產生可變的參數列表。
在 Java 5 中,這種期盼已久的特性終於添加了進來,就像在 printArray() 中看到的那樣:
// housekeeping/NewVarArgs.java
// Using array syntax to create variable argument lists
public class NewVarArgs {
static void printArray(Object... args) {
for (Object obj: args) {
System.out.print(obj + " ");
}
System.out.println();
}
public static void main(String[] args) {
// Can take individual elements:
printArray(47, (float) 3.14, 11.11);
printArray(47, 3.14F, 11.11);
printArray("one", "two", "three");
printArray(new A(), new A(), new A());
// Or an array:
printArray((Object[]) new Integer[] {1, 2, 3, 4});
printArray(); // Empty list is OK
}
}
輸出:
47 3.14 11.11
47 3.14 11.11
one two three
A@15db9742 A@6d06d69c A@7852e922
1 2 3 4
作者:
有了可變參數,你就再也不用顯式地編寫數組語法了,當你指定參數時,編譯器實際上會為你填充數組。
你獲取的仍然是一個數組,這就是為什么 printArray() 可以使用 for-in 迭代數組的原因。
但是,這不僅僅只是從元素列表到數組的自動轉換。
注意程序的倒數第二行,一個 Integer 數組(通過自動裝箱創建)被轉型為一個 Object 數組(為了移除編譯器的警告),並且傳遞給了 printArray()。
顯然,編譯器會發現這是一個數組,不會執行轉換。
因此,如果你有一組事物,可以把它們當作列表傳遞,而如果你已經有了一個數組,該方法會把它們當作可變參數列表來接受。
//程序的最后一行表明,可變參數的個數可以為 0。
當具有可選的尾隨參數時,這一特性會有幫助:(不太懂,但是例子能看的懂)
// housekeeping/OptionalTrailingArguments.java
public class OptionalTrailingArguments {
static void f(int required, String... trailing) {
System.out.print("required: " + required + " ");
for (String s: trailing) {
System.out.print(s + " ");
}
System.out.println();
}
public static void main(String[] args) {
f(1, "one");
f(2, "two", "three");
f(0);
}
}
輸出:
required: 1 one
required: 2 two three
required: 0
//這段程序展示了如何使用除了 Object 類之外類型的可變參數列表。
這里,所有的可變參數都是 String 對象。可變參數列表中可以使用任何類型的參數,包括基本類型。
下面例子展示了可變參數列表變為數組的情形,並且如果列表中沒有任何元素,那么轉變為大小為 0 的數組:
// housekeeping/VarargType.java
public class VarargType {
static void f(Character... args) {
System.out.print(args.getClass());
System.out.println(" length " + args.length);
}
static void g(int... args) {
System.out.print(args.getClass());
System.out.println(" length " + args.length)
}
public static void main(String[] args) {
f('a');
f();
g(1);
g();
System.out.println("int[]: "+ new int[0].getClass());
}
}
輸出:
class [Ljava.lang.Character; length 1
class [Ljava.lang.Character; length 0
class [I length 1
class [I length 0
int[]: class [I
getClass() 方法屬於 Object 類,將在"類型信息"一章中全面介紹。
它會產生對象的類,並在打印該類時,看到表示該類類型的編碼字符串。
前導的 [ 代表這是一個后面緊隨的類型的數組,I 表示基本類型 int;
為了進行雙重檢查,我在最后一行創建了一個 int 數組,打印了其類型。
這樣也驗證了使用可變參數列表不依賴於自動裝箱,而使用的是基本類型。
//雖然作者的話有點浮在雲中飄,但是還是看出了大概。可以繼續閱讀。
可變參數列表與自動裝箱可以和諧共處
// housekeeping/AutoboxingVarargs.java
public class AutoboxingVarargs {
public static void f(Integer... args) {
for (Integer i: args) {
System.out.print(i + " ");
}
System.out.println();
}
public static void main(String[] args) {
f(1, 2);
f(4, 5, 6, 7, 8, 9);
f(10, 11, 12);
}
}
1 2
4 5 6 7 8 9
10 11 12
作者的話:
注意嗎,你可以在單個參數列表中將類型混合在一起,自動裝箱機制會有選擇地把 int 類型的參數提升為 Integer。
我:沒懂作者,因為不知道integer是什么,記得書中前面是說是類,但是這里描述的將int類型的參數提升為integer,還真是蒙在鼓里頭
可變參數列表使得方法重載更加復雜了,盡管乍看之下似乎足夠安全:
// housekeeping/OverloadingVarargs.java
public class OverloadingVarargs {
static void f(Character... args) {
System.out.print("first");
for (Character c: args) {
System.out.print(" " + c);
}
System.out.println();
}
static void f(Integer... args) {
System.out.print("second");
for (Integer i: args) {
System.out.print(" " + i);
}
System.out.println();
}
static void f(Long... args) {
System.out.println("third");
}
public static void main(String[] args) {
f('a', 'b', 'c');
f(1);
f(2, 1);
f(0);
f(0L);
//- f(); // Won's compile -- ambiguous
}
}
輸出:
first a b c
second 1
second 2 1
second 0
third
//在每種情況下,編譯器都會使用自動裝箱來匹配重載的方法,然后調用最明確匹配的方法。
(方法的重載:粗俗的了解就是 兩個方法命名相同,但接收的參數不同。)
但是如果調用不含參數的 f(),編譯器就無法知道應該調用哪個方法了。盡管這個錯誤可以弄清楚,但是它可能會使客戶端程序員感到意外。
作者說:
你可能會通過在某個方法中增加一個非可變參數解決這個問題:
(但其實我看完也沒解決這個問題啊。。就是調用f(),不含參數的那個)
// housekeeping/OverloadingVarargs2.java // {WillNotCompile} public class OverloadingVarargs2 { static void f(float i, Character... args) { System.out.println("first"); } static void f(Character... args) { System.out.println("second"); } public static void main(String[] args) { f(1, 'a'); f('a', 'b'); } }
這里會出現一些問題:作者給的:
{WillNotCompile} 注釋把該文件排除在了本書的 Gradle 構建之外。如果你手動編譯它,會得到下面的錯誤信息:
輸出:
OverloadingVarargs2.java:14:error:reference to f is ambiguous f('a', 'b');
\^
both method f(float, Character...) in OverloadingVarargs2 and method f(Character...) in OverloadingVarargs2 match 1 error
作者說:如果你給這兩個方法都添加一個非可變參數,就可以解決問題了:
// housekeeping/OverloadingVarargs3 public class OverloadingVarargs3 { static void f(float i, Character... args) { System.out.println("first"); } static void f(char c, Character... args) { System.out.println("second"); } public static void main(String[] args) { f(1, 'a'); f('a', 'b'); } }
輸出:
first
second
枚舉類型
// housekeeping/Spiciness.java public enum Spiciness { NOT, MILD, MEDIUM, HOT, FLAMING }
這里創建了一個名為 Spiciness 的枚舉類型,它有5個值。由於枚舉類型的實例是常量,因此按照命名慣例,它們都用大寫字母表示(如果名稱中含有多個單詞,使用下划線分隔)。
要使用 enum,需要創建一個該類型的引用,然后將其賦值給某個實例:
// housekeeping/SimpleEnumUse.java public class SimpleEnumUse { public static void main(String[] args) { Spiciness howHot = Spiciness.MEDIUM; System.out.println(howHot); } }
輸出:MEDIUM
在你創建 enum 時,編譯器會自動添加一些有用的特性。
例如,它會創建 toString() 方法,以便你方便地顯示某個 enum 實例的名稱,這從上面例子中的輸出可以看出。
編譯器還會創建 ordinal() 方法表示某個特定 enum 常量的聲明順序,
static values() 方法按照 enum 常量的聲明順序,生成這些常量值構成的數組:
// housekeeping/EnumOrder.java
public class EnumOrder {
public static void main(String[] args) {
for (Spiciness s: Spiciness.values()) {
System.out.println(s + ", ordinal " + s.ordinal());
}
}
}
輸出:
NOT, ordinal 0
MILD, ordinal 1
MEDIUM, ordinal 2
HOT, ordinal 3
FLAMING, ordinal 4
盡管 enum 看起來像是一種新的數據類型,
但是這個關鍵字只是在生成 enum 的類時,產生了某些編譯器行為,因此在很大程度上你可以將 enum 當作其他任何類。
事實上,enum 確實是類,並且具有自己的方法。
enum 有一個很實用的特性,就是在 switch 語句中使用:
// housekeeping/Burrito.java
public class Burrito {
Spiciness degree;
public Burrito(Spiciness degree) {
this.degree = degree;
}
public void describe() {
System.out.print("This burrito is ");
switch(degree) {
case NOT:
System.out.println("not spicy at all.");
break;
case MILD:
case MEDIUM:
System.out.println("a little hot.");
break;
case HOT:
case FLAMING:
default:
System.out.println("maybe too hot");
}
}
public static void main(String[] args) {
Burrito plain = new Burrito(Spiciness.NOT),
greenChile = new Burrito(Spiciness.MEDIUM),
jalapeno = new Burrito(Spiciness.HOT);
plain.describe();
greenChile.describe();
jalapeno.describe();
}
}
輸出:
This burrito is not spicy at all.
This burrito is a little hot.
This burrito is maybe too hot.
本章小結:
構造器能保證進行正確的初始化和清理(沒有正確的構造器調用,編譯器就不允許創建對象),所以你就有了完全的控制和安全。
第六章筆記----結束
該書閱讀網址:https://github.com/LingCoder/OnJava8
MEDIUM
