java定义苹果类Apple_Java开发笔记(七十)Java8新增的几种泛型接口
由于泛型存在某種不確定的類型,因此很少直接運用于拿來即用的泛型類,它更經常以泛型接口的面目出現。例如幾種基本的容器類型Set、Map、List都被定義為接口interface,像HashSet、TreeMap、LinkedList等等只是實現了對應容器接口的具體類罷了。泛型的用途各式各樣,近的不說,遠的如數組工具Arrays的sort方法,它在排序時用到的比較器Comparator就是個泛型接口。別看Comparator.java的源碼洋洋灑灑數百行,其實它的精華部分僅僅下列寥寥數行:
//數組排序需要的比較器主要代碼,可見它是個泛型接口
public interface Comparator {
int compare(T o1, T o2);
}
當然系統提供的泛型接口不止是Comparator一個,從Java8開始,又新增了好幾個系統自帶的泛型接口,它們的適用范圍各有千秋,接下來便分別加以介紹。
1、斷言接口Predicate
之前介紹方法引用的時候,要求從一個字符串數組中挑選出符合條件的元素生成新數組,為此定義一個過濾器接口StringFilter,該接口聲明了字符串匹配方法isMatch,然后再利用該過濾器編寫字符串數組的篩選方法,進而由外部通過Lambda表達式或者方法引用來進行過濾。可是StringFilter這個過濾器只能用于篩選字符串,不能用來篩選其它數據類型。若想讓它支持所有類型的數據篩選,勢必要把數據類型空泛化,Java8推出的斷言接口Predicate正是這種用于匹配校驗的泛型接口。
在詳細說明Predicate之前,先定義一個蘋果類Apple,本文的幾個泛型接口都準備拿蘋果類練手,它的類定義代碼如下所示:
//定義一個蘋果類
public class Apple {
private String name; // 名稱
private String color; // 顏色
private Double weight; // 重量
private Double price; // 價格
public Apple(String name, String color, Double weight, Double price) {
this.name = name;
this.color = color;
this.weight = weight;
this.price = price;
}
// 為節省篇幅,此處省略每個成員屬性的get/set方法
// 獲取該蘋果的詳細描述文字
public String toString() {
return String.format("\n(name=%s,color=%s,weight=%f,price=%f)", name,
color, weight, price);
}
// 判斷是否紅蘋果
public boolean isRedApple() {
return this.color.toLowerCase().equals("red");
}
}
接著構建一個填入若干蘋果信息的初始清單,幾種泛型接口準備對蘋果清單磨刀霍霍,清單數據的構建代碼示例如下:
// 獲取默認的蘋果清單
private static List getAppleList() {
// 數組工具Arrays的asList方法可以把一系列元素直接賦值給清單對象
List appleList = Arrays.asList(
new Apple("紅蘋果", "RED", 150d, 10d),
new Apple("大蘋果", "green", 250d, 10d),
new Apple("紅蘋果", "red", 300d, 10d),
new Apple("大蘋果", "yellow", 200d, 10d),
new Apple("紅蘋果", "green", 100d, 10d),
new Apple("大蘋果", "Red", 250d, 10d));
return appleList;
}
然后當前的主角——斷言接口終于登場了,別看“斷言”二字似乎很嚇人,其實它的關鍵代碼也只有以下幾行,真正有用的就是校驗方法test:
public interface Predicate {
boolean test(T t);
}
再定義一個清單過濾的泛型方法,輸入原始清單和斷言實例,輸出篩選后符合條件的新清單。過濾方法的處理邏輯很簡單,僅僅要求遍歷清單的所有元素,一旦通過斷言實例的test方法檢驗,就把該元素添加到新的清單。具體的過濾代碼如下所示:
// 利用系統自帶的斷言接口Predicate,對某個清單里的元素進行過濾
private static List filterByPredicate(List list, Predicate p) {
List result = new ArrayList();
for (T t : list) {
if (p.test(t)) { // 如果滿足斷言的測試條件,則把該元素添加到新的清單
result.add(t);
}
}
return result;
}
終于輪到外部調用剛才的過濾方法了,現在要求從原始的蘋果清單中挑出所有的紅蘋果,為了更直觀地理解泛型接口的運用,先通過匿名內部類方式來表達Predicate實例。此時的調用代碼是下面這樣的:
// 測試系統自帶的斷言接口Predicate
private static void testPredicate() {
List appleList = getAppleList();
// 第一種調用方式:匿名內部類實現Predicate。挑出所有的紅蘋果
List redAppleList = filterByPredicate(appleList, new Predicate() {
@Override
public boolean test(Apple t) {
return t.isRedApple();
}
});
System.out.println("紅蘋果清單:" + redAppleList.toString());
}
運行上述的測試代碼,從輸出的日志信息可知,通過斷言接口正確篩選到了紅蘋果清單:
紅蘋果清單:[
(name=紅蘋果,color=RED,weight=150.000000,price=10.000000),
(name=紅蘋果,color=red,weight=300.000000,price=10.000000),
(name=大蘋果,color=Red,weight=250.000000,price=10.000000)]
顯然匿名內部類的實現代碼過于冗長,改寫為Lambda表達式的話僅有以下一行代碼:
// 第二種調用方式:Lambda表達式實現Predicate
List redAppleList = filterByPredicate(appleList, t -> t.isRedApple());
或者采取方法引用的形式,也只需下列的一行代碼:
// 第三種調用方式:通過方法引用實現Predicate
List redAppleList = filterByPredicate(appleList, Apple::isRedApple);
除了挑選紅蘋果,還可以挑選大個的蘋果,比如要挑出所有重量大于半斤的蘋果,則采取Lambda表達式的的調用代碼見下:
// Lambda表達式實現Predicate。挑出所有重量大于半斤的蘋果
List heavyAppleList = filterByPredicate(appleList, t -> t.getWeight() >= 250);
System.out.println("重蘋果清單:" + heavyAppleList.toString());
以上的代碼演示結果,充分說明了斷言接口完全適用于過濾判斷及篩選操作。
2、消費接口Consumer
斷言接口只進行邏輯判斷,不涉及到數據修改,若要修改清單里的元素,就用到了另一個消費接口Consumer。譬如下館子消費,把肚子撐大了;又如去超市消費,手上多了裝滿商品的購物袋;因此消費行為理應伴隨著某些屬性的變更,變大或變小,變多或變少。Consumer同樣屬于泛型接口,它的核心代碼也只有以下區區幾行:
public interface Consumer {
void accept(T t);
}
接著將消費接口作用于清單對象,意圖修改清單元素的某些屬性,那么得定義泛型方法modifyByConsumer,根據輸入的清單數據和消費實例,從而對清單執行指定的消費行為。詳細的修改方法示例如下:
// 利用系統自帶的消費接口Consumer,對某個清單里的元素進行修改
private static void modifyByConsumer(List list, Consumer c) {
for (T t : list) {
// 根據輸入的消費指令接受變更,所謂消費,通俗地說,就是女人花錢打扮自己。
// 下面的t既是輸入參數,又允許修改。
c.accept(t); // 如果t是String類型,那么accept方法不能真正修改字符串
}
}
消費行為仍然拿蘋果清單小試牛刀,外部調用modifyByConsumer方法之時,傳入的消費實例要給蘋果名稱加上“好吃”二字。下面便是具體的調用代碼例子,其中一塊列出了匿名內部類與Lambda表達式這兩種寫法:
// 測試系統自帶的消費接口Consumer
private static void testConsumer() {
List appleList = getAppleList();
// 第一種調用方式:匿名內部類實現Consumer。在蘋果名稱后面加上“好吃”二字
modifyByConsumer(appleList, new Consumer() {
@Override
public void accept(Apple t) {
t.setName(t.getName() + "好吃");
}
});
// 第二種調用方式:Lambda表達式實現Consumer
modifyByConsumer(appleList, t -> t.setName(t.getName() + "好吃"));
System.out.println("好吃的蘋果清單" + appleList.toString());
}
運行上面的調用代碼,可見輸入的日志記錄果然給蘋果名稱補充了兩遍“好吃”:
好吃的蘋果清單[
(name=紅蘋果好吃好吃,color=RED,weight=150.000000,price=10.000000),
(name=大蘋果好吃好吃,color=green,weight=250.000000,price=10.000000),
(name=紅蘋果好吃好吃,color=red,weight=300.000000,price=10.000000),
(name=大蘋果好吃好吃,color=yellow,weight=200.000000,price=10.000000),
(name=紅蘋果好吃好吃,color=green,weight=100.000000,price=10.000000),
(name=大蘋果好吃好吃,color=Red,weight=250.000000,price=10.000000)]
不過單獨使用消費接口的話,只能把清單里的每個元素全部修改過去,不加甄別的做法顯然太粗暴了。更好的辦法是挑出符合條件的元素再做變更,如此一來就得聯合運用斷言接口與消費接口,先通過斷言接口Predicate篩選目標元素,再通過消費接口Consumer處理目標元素。于是結合兩種泛型接口的泛型方法就變成了以下這般代碼:
// 聯合運用Predicate和Consumer,可篩選出某些元素并給它們整容
private static void selectAndModify(List list, Predicate p, Consumer c) {
for (T t : list) {
if (p.test(t)) { // 如果滿足斷言的條件要求,
c.accept(t); // 就把該元素送去美容院整容。
}
}
}
針對特定的記錄再作調整,正是實際業務場景中的常見做法。比如現有一堆蘋果,因為每個蘋果的質量參差不齊,所以要對蘋果分類定價。一般的蘋果每公斤賣10塊錢,如果是紅彤彤的蘋果,則單價提高50%;如果蘋果個頭很大(重量大于半斤),則單價也提高50%;又紅又大的蘋果想都不要想肯定特別吃香,算下來它的單價足足是一般蘋果的1.5*1.5=2.25倍了。那么調整蘋果定價的代碼邏輯就得先后調用兩次selectAndModify方法,第一次用來調整紅蘋果的價格,第二次用來調整大蘋果的價格,完整的價格調整代碼如下所示:
// 聯合測試斷言接口Predicate和消費接口Consumer
private static void testPredicateAndConsumer() {
List appleList = getAppleList();
// 如果是紅蘋果,就漲價五成
selectAndModify(appleList, t -> t.isRedApple(), t -> t.setPrice(t.getPrice() * 1.5));
// 如果重量大于半斤,再漲價五成
selectAndModify(appleList, t -> t.getWeight() >= 250, t -> t.setPrice(t.getPrice() * 1.5));
System.out.println("漲價后的蘋果清單:" + appleList.toString());
}
運行以上的價格調整代碼,從以下輸出的日志結果可知,每個蘋果的單價都經過計算重新改過了:
漲價后的蘋果清單:[
(name=紅蘋果,color=RED,weight=150.000000,price=15.000000),
(name=大蘋果,color=green,weight=250.000000,price=15.000000),
(name=紅蘋果,color=red,weight=300.000000,price=22.500000),
(name=大蘋果,color=yellow,weight=200.000000,price=10.000000),
(name=紅蘋果,color=green,weight=100.000000,price=10.000000),
(name=大蘋果,color=Red,weight=250.000000,price=22.500000)]
3、函數接口Function
剛才聯合斷言接口和消費接口,順利實現了修改部分元素的功能,然而這種做法存在問題,就是直接在原清單上面進行修改,一方面破壞了原始數據,另一方面仍未抽取到新清單。于是Java又設計了泛型的函數接口Function,且看它的泛型接口定義代碼:
public interface Function {
R apply(T t);
}
從Function的定義代碼可知,該接口不但支持輸入某個泛型變量,也支持返回另一個泛型變量。這樣的話,把輸入參數同輸出參數區分開,就避免了二者的數據處理操作發生干擾。據此可編寫新的泛型方法recycleByFunction,該方法輸入原始清單和函數實例,輸出處理后的新清單,從而滿足了數據抽取的功能需求。詳細的方法代碼示例如下:
// 利用系統自帶的函數接口Function,把所有元素進行處理后加到新的清單里面
private static List recycleByFunction(List list, Function f) {
List result = new ArrayList();
for (T t : list) {
R r = f.apply(t); // 把原始材料t加工一番后輸出成品r
result.add(r); // 把成品r添加到新的清單
}
return result;
}
接下來由外部去調用新定義的recycleByFunction方法,照舊采取匿名內部類與Lambda表達式同時進行編碼,輪番對紅蘋果和大蘋果漲價,修改后的調用代碼例子見下:
// 測試系統自帶的函數接口Function
private static void testFunction() {
List appleList = getAppleList();
List appleRecentList;
// 第一種調用方式:匿名內部類實現Function。把漲價后的蘋果放到新的清單之中
appleRecentList = recycleByFunction(appleList,
new Function() {
@Override
public Apple apply(Apple t) {
Apple apple = new Apple(t.getName(), t.getColor(), t.getWeight(), t.getPrice());
if (apple.isRedApple()) { // 如果是紅蘋果,就漲價五成
apple.setPrice(apple.getPrice() * 1.5);
}
if (apple.getWeight() >= 250) { // 如果重量大于半斤,再漲價五成
apple.setPrice(apple.getPrice() * 1.5);
}
return apple;
}
});
// 第二種調用方式:Lambda表達式實現Function
appleRecentList = recycleByFunction(appleList, t -> {
Apple apple = new Apple(t.getName(), t.getColor(), t.getWeight(), t.getPrice());
if (apple.isRedApple()) { // 如果是紅蘋果,就漲價五成
apple.setPrice(apple.getPrice() * 1.5);
}
if (apple.getWeight() >= 250) { // 如果重量大于半斤,再漲價五成
apple.setPrice(apple.getPrice() * 1.5);
}
return apple;
});
System.out.println("漲價后的新蘋果清單:" + appleRecentList.toString());
}
注意到上面的例子代碼中,函數接口的入參類型為Apple,而出參類型也為Apple。假設出參類型不是Apple,而是別的類型如String,那該當若何?其實很簡單,只要函數接口的返回參數改成其它類型就好了。譬如現在無需返回蘋果的完整清單,只需返回蘋果的名稱清單,則調用代碼可調整為下面這樣:
// 返回的清單類型可能與原清單類型不同,比如只返回蘋果名稱
List colorList = recycleByFunction(appleList,
t -> t.getName() + "(" + t.getColor() + ")");
System.out.println("帶顏色的蘋果名稱清單:" + colorList.toString());
運行以上的調整代碼,果然打印了如下的蘋果名稱清單日志:
帶顏色的蘋果名稱清單:[紅蘋果(RED), 大蘋果(green), 紅蘋果(red), 大蘋果(yellow), 紅蘋果(green), 大蘋果(Red)]
總結
以上是生活随笔為你收集整理的java定义苹果类Apple_Java开发笔记(七十)Java8新增的几种泛型接口的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html ui windows 风格,5
- 下一篇: switch语句表达式和执行流程