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