由于泛型存在某种不确定的类型,因此很少直接运用于拿来即用的泛型类,它更经常以泛型接口的面目出现。例如几种基本的容器类型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新增的几种泛型接口相关推荐

  1. java定义苹果类Apple_定义一个水果接口Fruit,里面定义一个表示吃水果的eat方法。定义一个苹果类Apple和一个橘子...

    importjava.util.Scanner;interfaceFruit{publicvoideat();//创建一个Fruit接口,里面包含一个eat方法}classAppleimplement ...

  2. qml开发笔记(七):输入元素鼠标输入MouseArea和键盘输入Keys

    若该文为原创文章,未经允许不得转载 原博主博客地址:https://blog.csdn.net/qq21497936 原博主博客导航:https://blog.csdn.net/qq21497936/ ...

  3. java定义计算机类并模拟其操作

    java定义计算机类并模拟其操作 /*** 定义计算机类并模拟其操作*/ public class Computer {//成员变量private String cpu = "Intel&q ...

  4. Java定义People类

    Java定义People类,它具有以下成员变量:String name,int age,它有两个构造方法! package testclass;public class PeopleClass {pu ...

  5. windows内核开发笔记七:内核开发OVERLAPPED结构体详解

    windows内核开发笔记七:内核开发OVERLAPPED结构体详解 typedef struct _OVERLAPPED {   DWORD Internal;   DWORD InternalHi ...

  6. Android开发笔记(十八)书籍翻页动画PageAnimation

    前面几节的动画都算简单,本文就介绍一个复杂点的动画--书籍翻页动画.Android有自带的翻页动画ViewPager,不过ViewPager只实现了平移效果.即便使用补间组合动画或者属性动画,也只是把 ...

  7. 【Visual C++】游戏开发笔记四十六 浅墨DirectX教程十四 模板测试与镜面特效专场

    本系列文章由zhmxy555(毛星云)编写,转载请注明出处.   文章链接: http://blog.csdn.net/zhmxy555/article/details/8632184 作者:毛星云( ...

  8. MLX90640开发笔记(十)成果展示-红眼睛相机

    最终的成果是一个微型的USB接口红外成像模块(微型红外成像仪30*30mm),可以连接到Android手机或者计算机的USB接口,实时显示热像视频,和手机相机差不多,只不过它是热红外成像,所以叫&qu ...

  9. Hi3516开发笔记(十):Qt从VPSS中获取通道图像数据存储为jpg文件

    若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/123536470 红胖子(红模仿)的博文大全:开发技术集 ...

  10. MLX90640 红外热成像仪测温传感器模块开发笔记(十) 成果展示-红眼睛相机

    MLX90640 红外热成像仪测温传感器模块开发笔记(十) 成果展示-红眼睛相机 现在自己在做红外成像仪的越来越多了,两年前有个井下机电设备运行状态的科研项目,当时使用了 AMG8833(8*8 像素 ...

最新文章

  1. uniapp 的使用
  2. 计算机房做法图集,万科建筑标准工程做法通用图集(全套)
  3. HTTP协议学习笔记
  4. HZOJ 斐波那契(fibonacci)
  5. python多线程读取文件夹下的文件_是否可以使用python多线程从文件夹数读取文件数,并处理这些文件以获得组合结果?...
  6. vue怎么截取时间年月_Vue + Element 获取标准时间、时间戳进行转换与操作(年月日)...
  7. 风险预测模型_随访78个月,仁济医院完成世界首个间质性肺病风险预测模型
  8. python sublime 提示补全_【原创】Sublime+Verilator建立强大的verilog编写环境
  9. 世界互联网大会:华三发安全平台天机
  10. ArcGIS API For JS实现动态点扩散
  11. php 什么是 cookie? 会话 cookie 与持久性 cookie 之间 有何区别?
  12. 从智能门锁,看3D视觉的安全性突围
  13. SSM框架利用Filter实现页面不登陆拦截,禁止跳过登录页面不登陆强制访问
  14. 低代码平台对程序员产生的内卷,零代码、低代码系列之一「对于零代、低代码平台的思考」
  15. 使用gui来初始化参数matlab,MATLAB GUI参数传递方式
  16. 【机器学习】机器学习中无意识偏见的分析与预防
  17. springboot整合postgresql
  18. Excel#整体增加相同行高的宏命令#
  19. Chrome实现独立代理
  20. 【ultraedit编辑器中怎么取消文件自动备份】

热门文章

  1. 教师综合素质5--地理名胜
  2. 暴走欧洲之在德国的反思
  3. 英语六级 Java_过英语六级算什么,你过了Java25级了吗!
  4. uniapp带视频和图片的轮播swiper(带全屏播放附源码)
  5. java编写投票功能需求分析
  6. 网站刷IP?活不过三秒
  7. 渗透测试的介绍和防范
  8. html 插入 flv,HTML中嵌入FLV视频文件
  9. ado连接mysql方式_用ADO 连接mysql数据库的方法
  10. JQuery-layer web弹窗层