《Java核心技術 卷Ⅰ》 第6章 接口、lambda表達式與內部類前端
接口技術,這種技術主要用來描述類具備什麼功能,而並不給出每一個功能的具體實現。一個類能夠實現(implement)一個或多個接口,並在須要接口的地方,隨時使用實現了相應接口的對象。java
在Java程序設計語言中,接口不是類,是對類的一組需求的描述,這些類要聽從接口描述的統一格式進行定義。git
Arrays
類中的sort
方法承諾能夠對對象數組進行排序,但要求知足下列條件,對象所屬的類**必須實現了Comparable
接口。程序員
public interface Comparable {
int compareTo(Object other) } 複製代碼
這就是說,任何實現Comparable
接口的類都須要包含compareTo
方法,而且這個方法的參數必須是一個Object
對象,返回一個整型數值;好比調用x.compareTo(y)
時,當x
小於y
時,返回一個負數;當x
等於y
時,返回0;不然返回一個正數。github
在 Java SE 5中,Comparable
接口改進爲泛型類型。數組
public interface Comparable<T> {
int compareTo(T other); // 參數擁有類型T
}
複製代碼
例如在實現Comparable<Employee>
接口類中,必須提供int compareTo(Employee other)
方法。安全
接口中的全部方法自動地屬於public
,所以,在接口中聲明方法時,沒必要提供關鍵字public
。數據結構
提供實例域和方法實現的任務應該由實現接口的那個類來完成。閉包
在這裏能夠將接口當作是沒有實例域的抽象類。併發
如今但願用Arrays
類的sort
方法對Employee
對象數組進行排序,Employee
類必須實現Comparable
接口。
爲了讓類實現一個接口,一般須要下面兩個步驟:
將類聲明爲實現某個接口,使用關鍵字implements
:
class Employee implements Comparable {
...
public int compareTo(Object otherObject) {
Employee other = (Employee) otherObject;
return Double.compare(salary, other.salary);
}
...
}
複製代碼
這裏使用了靜態Double.compare
方法,若是第一個參數小於第二個參數,它會返回一個負值,相等返回0,不然返回一個正值。
雖然在接口聲明中,沒有將compareTo
方法聲明爲publuc
,這是由於接口中全部方法都自動地是public
,可是,在實現接口時,必須把方法聲明爲public
,不然編譯器將認爲這個方法的訪問屬性是包可見性,即類的默認訪問。
能夠爲泛型Comparable
接口提供一個類型參數。
class Employee implements Comparable<Employee> {
...
public int compareTo(Employee other) {
return Double.compare(salary, other.salary);
}
...
}
複製代碼
爲何不能再Employee
類直接提供一個compareTo
方法,而必須實現Comparable
接口呢?
主要緣由是Java是一種強類型(strongly type)語言,在調用方法時,編譯器將會檢查這個方法是否存在。
在sort方法通常會用到compareTo
方法,因此編譯器必須確認必定有compareTo
方法,若是數組元素類實現了Comparable
接口,就能夠確保擁有compareTo
方法。
接口不是類,尤爲不能用new
實例化接口:
x = new Comparable(...); // Error
複製代碼
儘管不能構造接口的對象,卻能聲明接口的變量:
Comparable x; // OK
複製代碼
接口變量必須引用實現了接口的類對象:
x = new Employee(...); // OK
複製代碼
也可使用instanceof
檢查一個對象是否實現了某個特定的接口:
if(x instanceof Comparable) { ... }
複製代碼
與類的繼承關係同樣,接口也能夠被擴展。
這裏容許存在多臺從具備較高通用性的接口到較高專用性的接口的鏈。
假設有一個稱爲Moveable
的接口:
public interface Moveable {
void move(double x, double y);
}
複製代碼
而後,能夠以它爲基礎擴展一個叫作Powered
的接口:
public interface Powered extends Moveable {
double milesPerGallon();
}
複製代碼
雖然接口中不能包含實例域或者靜態域,可是能夠定義常量:
public interface Powered extends Moveable {
double milesPerGallon();
double SPEED_LIMIT = 95;
// a public static final constant
}
複製代碼
與接口中的方法自動設置爲public
同樣,接口中的域被自動設爲public static final
。
儘管每一個類只能擁有一個超類,但卻實現多個接口。
class Employee implements Coneable, Comparable { ... }
複製代碼
你可能會問:爲何這些功能不能由一個抽象類實現呢?
由於使用抽象類表示通用屬性存在這樣的問題:每一個類只能擴展於一個類,沒法實現一個類實現多個接口的需求。
class Employee extends Person implements Comparable { ... }
複製代碼
在 Java SE 8 中,容許在接口中增長靜態方法。
雖說這沒有什麼不合法的,只是這有違接口做爲抽象規範的初衷。
一般的作法是將靜態方法放在伴隨類中。在標準庫中,有成對出現的接口和實用工具類,如Collection
/Collections
或Path
/Paths
。
雖然Java庫都把靜態方法放到接口中也是不太可能,可是實現本身接口時,不須要爲實用工具方法另外提供一個伴隨類。
能夠爲接口方法提供一個默認實現,必須用default
修飾符標記方法。
public interface Comparable<T> {
default int compareTo(T other) { return 0; }
}
複製代碼
默認方法的一個重要用法是接口演化(interface evolution)。
以Collection
接口爲例,這個接口做爲Java
的一部分已經好久了,假如好久之前提供了一個類:
public class Bag implements Collection { ... }
複製代碼
後來,在Java SE 8中,爲這個接口增長了一個stream
方法。若是stream
方法不是默認方法,那麼Bag
類將不能編譯——由於它沒有實現這個新方法。
爲接口增長一個非默認方法不能保證「源代碼兼容」(source compatible)。
若是一個接口中把方法定義爲默認方法,而後又在超類或另外一個接口中定義了一樣的方法,會發生什麼狀況?
解決這種二義性,Java的規則是:
着重看一下第二個規則,考慮另外一個包含getName
方法的接口:
interface Named {
default String getName() {
return getClass().getName() + "_" + hashCode();
}
}
複製代碼
如今有一個類同時實現了這兩個接口,這個時候須要程序員來解決這個二義性,在這個實現的方法中提供一個接口的默認getName
方法。
class Student implements Person, Named {
public String getName() {
return Person.super.getName();
}
}
複製代碼
就算Named
接口並無getName
的默認方法,一樣須要程序員去解決這個二義性問題。
上面的是兩個接口的命名衝突。
如今考慮另外一種狀況:一個類擴展了一個超類,同時實現了一個接口,並從超類和接口繼承了相同的方法。
class Student extends Person implements Named { ... }
複製代碼
這種狀況下只會考慮超類的方法,接口全部默認方法會被忽略。
回調(callback),能夠指出某個特定事件時應該採起的動做。
java.swing
包中有一個Timer
類,可使用它在到達給定的時間間隔發送通告。
在構造定時器時,須要設置一個時間間隔,並告知定時器,達到時間間隔時須要作什麼。
其中一個問題就是如何告知定時器作什麼?在不少語言中,是提供一個函數名,可是,在Java標準類庫中的類採用的是面向對象方法,它將某個類的對象傳遞給定時器,而後定時器調用這個對象的方法。
定時器須要知道調用哪個方法,並要求傳遞的對象所屬的類實現了java.awt.event
包的ActionListener
接口:
public interface ActionListener {
void actionPerformed(ActionEvent event);
}
複製代碼
當到達指定時間間隔,定時器就調用actionPerformed
方法。
使用這個接口的方法:
class TimePrinter implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.out.println(...);
...
}
}
複製代碼
其中接口方法的ActionEvent
參數提供了事件的相關信息。
接下來構造類的一個對象,並傳遞給Timer
構造器。
ActionListener listener = new TimePrinter()
Timer t = new Timer(10000, listener);
t.start(); // 啓動定時器
複製代碼
能夠對一個字符串數組排序,是由於String
類實現了Comparable<String>
,並且String.compareTo
方法能夠按字典順序比較字符串。
如今須要按長度遞增的順序對字符串進行排序,咱們確定不能對String
進行修改,就算能夠修改咱們也不能讓它用兩種不一樣的方式實現compareTo
方法。
要處理這種狀況,Arrays.sort
方法還有第二個版本,一個數組和一個比較器(comparator)做爲參數,比較器實現了Comparator
接口的類的實例。
public interface Comparator<T> {
int compare(T first, t second);
}
複製代碼
按字符串長度比較,能夠定義一個實現Comparator<String>
的類:
class LengthComparator implements Comparator<String> {
public int compare(String first, String second) {
return first.length() - second.length();
}
}
複製代碼
具體比較時,創建一個實例:
Comparator<String> comp = new LengthComparator();
// comp.compare(words[i], words[j])
Arrays.sort(friends, comp);
複製代碼
Cloneable
接口,指示一個類提供了一個安全的clone
方法。
Employee original = new Employee(...);
Employee copy = original.clone();
copy.raiseSalary(10); // no changes happen to original
複製代碼
clone
方法是Object
的一個protected
方法,代碼不能直接調用這個方法(指的是Object
的這個方法)。
固然,只有Employee
類能夠克隆Employee
對象,可是默認的克隆操做是淺拷貝,即並無克隆對象中引用的其餘對象。
淺拷貝可能會產生問題麼?這取決於具體狀況:
通常來講子對象都是可變的,因此須要定義clone
方法來創建一個深拷貝,同時克隆全部子對象。
對於每個類,須要肯定:
clone
是否知足要求clone
來修補默認clone
clone
實際上第3個選項是默認選項(這句話沒有太讀懂)。
若是選第1個或者第2個,類必須:
Cloneable
接口clone
方法,並指定public
訪問修飾符子類雖然能夠訪問Object
受保護的clone
方法,可是子類只能調用受保護的clone
方法來克隆它本身的對象。
必須從新定義clone
爲public
,才能容許全部方法克隆對象。
Cloneable
接口是一組標記接口,其餘接口通常確保一個類實現一個或一組特定的方法,標記接口不包含任何方法,它的惟一做用就是容許在類型查詢中使用instanceof
。
即時clone
的默認(淺拷貝)實現可以知足要求,仍是須要實現Cloneable
接口,將clone
從新定義爲public
,再調用super.clone()
。
class Employee implements Cloneable {
// raise visibility level to public, change return type
public Employee clone() throws CloneNotSupportedExcption {
return (Employee) super.clone();
}
}
複製代碼
與淺拷貝相比,這個clone
並無增長任何功能,只是讓方法變爲公有,要創建深拷貝。
class Employee implements Cloneable {
...
public Employee clone() throws CloneNotSupportedExcption {
// Obejct.clone()
Employee cloned = (Employee) super.clone();
//clone mutable fields
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
}
複製代碼
若是一個對象調用clone
,但這個對象類沒有實現Cloneable
接口,Object
的clone
方法就會拋出一個CloneNotSupportedExcption
,Employee
和Date
類實現了Cloneable
接口,因此不會拋出異常,可是編譯器並不知道這點,因此聲明異常最好還要加上捕獲異常。
class Employee implements Cloneable {
// raise visibility level to public, change return type
public Employee clone() throws CloneNotSupportedExcption {
try
{
Employee cloned = (Employee) super.clone();
...
}
catch(CloneNotSupportedExcption e) { return null; }
// 由於實現了Cloneable,因此這並不會發生
}
}
複製代碼
必須小心子類的克隆。
例如,一旦Employee
類定義了clone
,那麼就能夠用它來克隆Manager
對象(由於在Employee
類中的clone
已是public
了,能夠直接使用Manager.clone()
)。
但Employee
的clone
必定能完成克隆Manager
對象的工做麼?
這取決於Manager
類的域:
clone
方法讓它正常工做出於後者的緣由,在Object
類中的clone
方法聲明protected
。
一種表示在未來某個時間點執行的代碼塊的簡潔方法。
使用lambda表達式,能夠用一種精巧而簡潔的方式表示使用回調或變量行爲的代碼。
lambda表達式是一個可傳遞的代碼塊,能夠在之後執行一次或屢次。
以前的監聽器和後面的排序比較例子的共同點是:都是把一個代碼塊傳遞到某個對象(定時器或者是sort
方法),而且這個代碼塊會在未來某個時間調用。
考慮以前的按字符串長度排序例子:
first.length() - second.length()
複製代碼
Java是一種強類型語言,因此還要指定他們的類型:
(String first, String second)
-> first.length() - second.length()
// 隱式return 默認返回這個表達式的結果
複製代碼
這就是一個lambda表達式,一個代碼塊以及變量規範。
若是代碼要完成的計算不止一條語句,能夠像寫方法同樣,把代碼放在{}
中,幷包含顯式的return
語句。
(String first, String second) ->
{
if(first.length() < second.length()) return -1;
else if(first.length() > second.length()) return 1;
else return 0;
}
複製代碼
一些省略形式的表達:
須要注意的地方:
return
,那就要確保每一個分支都有一個return
,不然是不合法的Java中已經有不少封裝代碼塊的接口,好比ActionListener
或Comparator
,lambda表達式與這些接口兼容。
對於只有一個抽象方法的接口,須要這種接口的對象時,就能夠提供一個lambda表達式,這種接口稱爲函數式接口(functional interface)。
考慮以前的Arrays.sort
方法,其中第二個參數須要一個Comparator
實例,函數式接口使用:
Arrays.sort(words,
(first, second) -> first.length() - second.length());
複製代碼
在底層,Arrays.sort
方法會接收實現了Comparator<Strng>
的某個類的對象,在這個對象上調用compare
方法會執行這個lambda表達式的體。
最好把lambda表達式看做一個函數,而不是一個對象,並且要接收lambda表達式能夠傳遞到函數式接口。
lambda表達式能夠轉換爲接口,這讓lambda表達式頗有吸引力,具體的語法很簡單:
Timer t = new Timer(10000, event ->
{
System.out.println(...);
...
});
複製代碼
與使用實現了ActionListener
接口的類相比,這個代碼可讀性好不少。
實際上,在Java中,對lambda表達式所能作的也只是能轉換爲函數式接口,甚至不能把lambda表達式賦給類型爲Object
的變量,Object
不是一個函數式接口。
有時,可能已經有現成的方法能夠完成你想要傳遞到其餘代碼的某個動做。
好比只要出現一個定時器事件就打印這個事件對象:
Timer t = new Timer(10000, event -> System.out.println(event));
複製代碼
可是若是直接把println
方法傳遞給Timer
構造器就更好了:
Timer t = new Timer(10000, System.out::println);
複製代碼
表達式System.out::println
就是一個方法引用(method reference),它等價於lambda表達式x - > System.out.println(x)
。
考慮一個排序例子:
Arrays.sort(words, String::compareToIgnoreCase);
複製代碼
主要有3種狀況:
object::instanceMethod
Class::staticMethod
Class::instanceMethod
前面兩種等價於提供方法參數的lambda表達式,好比System.out::println
等價於x -> System.out.println(x)
,以及Math::power
等價於(x, y) -> Math.power(x, y)
。
對於第3種,第1個參數會成爲方法的目標,例如String::compareToIgnoreCase
等價於(x, y) -> x.compareToIgnoreCase(y)
。
能夠在方法引種中使用this
,super
也是合法的,好比super::instanceMethod
,使用this
做爲目標,會調用給定方法的超類版本。
構造器引用與方法引用相似,只不過方法名爲new
,例如Person::new
是Person
構造器的一個引用,具體選擇Person
多個構造器中的哪個,這個取決於上下文。
如今有一個字符串列表,你能夠把它轉換爲一個Person
對象數組,爲此要在各個字符串上調用構造器。
ArrayList<String> names = ...;
Stream<Person> stream = names.stream().map(Person::new);
List<Person> people = stream.collect(Collectors.toList());
複製代碼
stream
、map
和collect
方法會在卷Ⅱ的第1章討論。
如今的重點是map
方法會爲各個列表元素調用Person(String)
構造器,這裏編譯器從上下文推導出這是在對一個字符串調用構造器。
能夠用數組類型創建構造器引用,int[]::new
是一個構造器引用,有一個參數,就是數組的長度,這等價於x -> new int[x]
。
Java有一個限制:沒法構造泛型類型T的數組。
數組構造器引用對於克服這個限制頗有用。
假設須要一個Person
對象數組,Stream
接口有一個toArray
方法能夠返回Object
數組:
Object[] people = stream.toArray();
複製代碼
可是用戶想要一個Person
引用數組,流庫利用構造器引用解決了這個問題:
Person[] people = stream.toArray(Person[]::new);
複製代碼
toArray
方法調用構造器得到一個正確類型的數組,而後填充這個數組並返回。
一般可能想在lambda表達式中訪問外圍方法或類中的變量。
public static void repeatMessage(String text, int delay) {
ActionListener listener = event ->
{
System.out.println(text);
...
};
new Timer(delay, listener).start();
}
複製代碼
具體調用:
repeatMessage("Hello", 1000);
複製代碼
lambda表達式中的變量text
,並非在這個lambda表達式中定義的,可是這其實有問題,由於代碼可能會調用返回好久之後才運行,而那時這個參數變量已經不存在了,該如何保留這個變量?
重溫一下lambda表達式的3個部分:
上面的例子中有1個自由變量text
。
表示lambda表達式的數據結構必須存儲自由變量的值,也被叫作自由變量被lambda表達式捕獲(captured)。
能夠把一個lambda表達式轉換爲包含一個方法的對象,這樣自由變量的值就會複製到這個對象的實例變量中。
關於代碼塊以及自由變量有一個術語:閉包(closure),Java中lambda表達式就是閉包。
在lambda表達式中,只能引用值不會改變的變量,好比下面這種就是不合法的:
public static void countDown(int start, int delat) {
ActionListener listener = event ->
{
start--; // Error: Can't mutate captured variable
System.out.println(text);
...
};
new Timer(delay, listener).start();
}
複製代碼
若是在lambda表達式中改變變量,併發執行多個操做時就會不安全(具體要見第14章併發)。
另外若是在lambda表達式中引用變量,而且這個變量在外部改變,這也是不合法的:
public static void repeat(String text, int count) {
for(int i = 1; i <= count; i++)
{
ActionListener listener = event ->
{
System.out.println(i + ":" + text);
// Error: Can't refer to changing i
...
};
new Timer(1000, listener).start();
}
}
複製代碼
因此簡單來講規則就是:lambda表達式中捕獲的變量必須其實是最終變量(effectively final),即這個變量初始化以後就再也不賦新值。
lambda表達式的體與嵌套塊有相同的做用域,因此在lambda表達式中聲明與一個局部變量同名的參數或局部變量是不合法的。
Path first = Path.get("/usr/bin");
Comparator<String> comp =
(first, second) -> fisrt.length() - second.length();
// Error: Variable first already defined
複製代碼
固然在lambda表達式中也不能有同名的局部變量。
在lambda表達式中使用this關鍵字時,是指建立這個lambda表達式的方法的this參數。
public class Application() {
public void init() {
ActionListener listener = event ->
{
System.out.println(this.toString());
...
}
}
}
複製代碼
this.toString()
會調用Application
對象的toString
方法,而不是ActionListener
實例的方法,因此在lambda表達式中this
的使用並無什麼特殊之處。
內部類(inner class)定義在另外一個類的內部,其中的方法能夠訪問包含它們的外部類的域。
內部類主要用於設計具備相互協做關係的類集合。
使用內部類的主要緣由:
將從如下幾部分介紹內部類:
內部類的語法比較複雜。
選擇一個簡單的例子:
public class TalkingClock {
private int interval;
private boolean beep;
public TalkingClock(int interval, boolean beep) { ... }
public void start() { ... }
// an inner class
public class TimePrinter implements ActionListener {
public void actionPerformed(ActionEvent event) {
System.out.println(...);
if(beep) Toolkit.getDefaultToolkit().beep();
}
}
}
複製代碼
TimePrinter
類位於TalkingClock
類內部,並不意味着每一個TalkingClock
對象都有一個TimePrinter
實例域。
TimePrinter
類沒有實例域或者beep
變量,而是引用了外部類的域裏的beep
。
其實內部類的對象總有一個隱式引用,它指向了建立它的外部類對象,這個引用在內部類的定義中不可見。
這個引用是在對象建立內部類對象的時候傳入的this
,編譯器經過內部類的構造器傳入到內部類對象的域中。
// 由編譯器插入的語句
ActionListener listener = new TimePrinter(this);
複製代碼
TimePrinter
類能夠聲明爲私有的,這樣只有TalkingClock
方法才能構造TimePrinter
對象。只有內部類能夠是私有的,常規類只能夠是包可見和公有可見。
使用外圍類引用的語法爲OuterClass.this
。
例如以前的actionPerformed
方法:
public void actionPerformed(ActionEvent event) {
...
if(TalkingClock.this.beep) Toolkit.getDefaultToolkit().beep();
}
複製代碼
反過來,能夠用`outerObject.new InnerClass(construction parameters)更加明確地編寫內部類對象的構造器:
// ActionListener listener = new TimePrinter(this);
ActionListener listener = this.new TimePrinter();
複製代碼
一般來講this
限定詞是多餘的,可是能夠經過顯式命名將外圍類引用設置爲其餘對象,好比當TimePrinter
是一個公有內部類時,對於任意的語音時鐘均可以構造一個TimePrinter
:
TalkingClock jabberer = new TalkingClock(1000, true);
TalkingClock.ActionListener listener = jabberer.new TimePrinter();
複製代碼
上面的狀況是在外圍類的做用域以外,因此引用的方法是OuterClass.InnerClass
。
注意:內部類中聲明的全部靜態域都必須是final
,由於咱們但願一個靜態域只有一個實例。不過對於每一個外部對象,會分別有一個單獨的內部類實例,若是這個域不是final
,它可能就不是惟一的。
若是一個類只在一個方法中建立了對象,能夠這個方法中定義局部類。
public void start() {
class TimePrinter implements ActionListener {
public void actionPerformed(ActionEvent event) { ... }
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
複製代碼
局部類不能用public
或private
,它的做用域被限定在生命這個局部類的塊中。
可是有很是好的隱蔽性,除了start
方法,沒有任何方法知道TimePrinter
類的存在。
局部類還有一個優勢:他們還能訪問局部變量,可是這些局部變量必須是final
,即一旦賦值就不會改變。
下面的例子相比以前進行了一些修改,beep
再也不是外部類的一個實例域,而是方法傳入的參數變量:
public void start(int interval, final boolean beep) {
class TimePrinter implements ActionListener {
public void actionPerformed(ActionEvent event) {
...
if(beep) ...;
...
}
}
ActionListener listener = new TimePrinter();
Timer t = new Timer(interval, listener);
t.start();
}
複製代碼
先說明一下這裏的控制流程:
start(int, boolean)
TimePrinter
的構造器,初始化listener
listener
引用傳遞給Timer
構造器t
開始計時start(int, boolean)
方法結束,此時beep
參數變量不復存在actionPerformed
方法執行if(beep) ...
爲了讓actionPerformed
正常運行,TimePrinter
類在beep
域釋放以前將內部類中要用到的beep
域用start
方法的局部變量beep
進行備份(具體實現方式是編譯器給內部類添加了一個final
域用來保存beep
)。
編譯器檢測對局部變量的訪問,爲每個量創建相應的數據域,並將局部變量拷貝到構造器中,以便將這些數據域初始化爲局部變量的副本。
至於beep
參數前的final
,是由於局部類的方法只能引用定義爲final
的局部變量,從而使得局部變量與局部類中創建的拷貝保持一致。
假設只建立這個局部類的一個對象,就沒必要命名了,這種類稱爲匿名內部類(anonymous inner class)。
public void start(int interval, boolean beep) {
ActionListener listener = new ActionListener()
{
public void actionPerformed(ActionEvent event) { ... }
};
Timer t = new Timer(interval, listener);
t.start();
}
複製代碼
這種語法的含義是:建立一個實現AcitonListener
接口的類的新對象,須要實現的方法定義在括號內。
一般的語法格式爲:
new SuperType(construction parameters)
{
methods and data
}
複製代碼
SuperType
能夠是一個接口,也能夠是一個類。
因爲構造器必需要有一個名字,因此匿名類不能有構造器,取而代之的是:
SuperType
是一個超類時,將構造器參數傳遞給超類構造器SuperType
是一個接口時,不能有任何構造參數(括號()
仍是要保留的)構造一個類的新對象,和構造一個擴展這個類的匿名內部類的對象的區別:
Person queen = new Person("Mary");
Person count = new Person("Dracula") { ... };
複製代碼
多年來,Java程序員習慣用匿名內部類實現事件監聽器和其餘回調,現在最好仍是使用lambda表達式,好比:
public void start(int interval, boolean beep) {
Timer t = new Timer(interval, event -> { ... });
t.start();
}
複製代碼
可見,用lambda表達式寫會簡潔得多。
若是想構造一個數組列表,並傳遞到一個方法:
ArrayList<String> friends = new ArrayList<>();
friends.add("Harry");
friends.add("Tony");
invite(friends);
複製代碼
若是以後都沒有再須要這個數組列表,那麼最好使用一個匿名列表解決。
invite(new ArrayList<String>() {{ add("Harry"); add("Tony"); }};
複製代碼
注意這裏的雙括號:
ArrayList
的一個匿名子類有時使用內部類只是爲了把一個類隱藏在另外一個類的內部,並不須要內部類引用外圍類對象,爲此能夠將內部類聲明static
,取消產生的引用。
編寫一個方法同時計算出最大最小值:
double min = Double.POSITIV_INFINITY;
double max = Double.NEGATIVE_INFINITY;
for(double v : values)
{
if (min > v) min = v;
if (max < v) max = v;
}
複製代碼
然而必須返回兩個數值,能夠頂一個包含兩個值的類Pair
:
class Pair {
private double first;
private double second;
public Pair(double f, double s) {
first = f;
second = s;
}
public double getFirst() { return first; }
public double getSecond() { return second; }
}
複製代碼
minmax
方法能夠返回一個Pair
類型的對象。
class ArrayAlg {
public static Pair minmax(double[] values) {
...
return new Pair(min, max);
}
}
複製代碼
而後調用ArrayAlg.minmax
得到最大最小值:
Pair p = ArrayAlg.minmax(data);
複製代碼
可是Pair
是一個比較大衆化的名字,容易出現名字衝突,解決的方法是將Pair
定義爲ArrayAlg
的內部公有類,而後用ArrayAlg.Pair
訪問它:
ArrayAlg.Pair p = ArrayAlg.minmax(data);
複製代碼
不過與前面的例子不一樣,Pair
對象不須要引用任何其餘的對象,因此能夠把這個內部類聲明爲static
:
class ArrayAlg {
public static class Pair { ... }
...
}
複製代碼
只有內部類能夠聲明爲static
,靜態內部類的對象除了沒有對生成它的外圍類對象的引用特權外,其餘與全部內部類徹底同樣。
在上面的例子中,必須使用靜態內部類,這是由於返回的內部類對象是在靜態方法minmax
中構造的。
若是沒有把Pair
類聲明爲static
,那麼編譯器將會給出錯誤報告:沒有可用的隱式ArrayAlg
類型對象初始化內部類對象。
static
和public
類。代理(proxy),這是一種實現任意接口的對象。
利用代理能夠在運行時建立一個實現了一組給定接口的新類。
這種功能只有在編譯時沒法肯定須要實現哪一個接口時纔有必要使用。
對於應用程序設計人員來講,遇到的狀況不多,因此先跳過,若是後面有必要再開一個專題進行說明。
我的靜態博客: