在本章学习函数编程时,首先会根据一个实际场景,演示为了适应不断变化的需求,代码的多个改进版本。从而引进函数编程的演变历史。接着呢,会学习lambda表达式的简介、形式和函数式接口的相关知识。然后通过实战学习如何自定义函数式接口来完成业务逻辑功能。本章的目的是,要灵活的使用Lambda表达式实现业务处理逻辑,了解JDK给我们提供的常用的函数式接口都是什么。为后面学习流编程做好准备。话不多说,咱们开始本章的学习。
实战案例:函数编程演变史
实战:根据实际场景演示,为了应对不断变化的业务需求,代码的多个改进版本。
同事在前段时间的618往购物车里加了一堆的东西,准备疯狂的消费一把。结果呢,资金有问题,所以他需要向女朋友申请费用,然后他的女朋友就要他对加入购物车里的商品进行不同维度的查找。比如:按价钱分,超过多少钱的不给买;按种类分;事情就是这样的一个事情,咱们可以根据这个实际的案例,来进行今天函数式编程的学习。咱们先为接下来的实战准备一下数据。
/**
* 下单商品信息对象
*/
public class Sku {
// 编号
private Integer skuId;
// 商品名称
private String skuName;
// 单价
private Double skuPrice;
// 购买个数
private Integer totalNum;
// 总价
private Double totalPrice;
// 商品类型
private Enum skuCategory;
/**
* 构造函数
* @param skuId
* @param skuName
* @param skuPrice
* @param totalNum
* @param totalPrice
* @param skuCategory
*/
public Sku(Integer skuId, String skuName,
Double skuPrice, Integer totalNum,
Double totalPrice, Enum skuCategory) {
this.skuId = skuId;
this.skuName = skuName;
this.skuPrice = skuPrice;
this.totalNum = totalNum;
this.totalPrice = totalPrice;
this.skuCategory = skuCategory;
}
/**
* Get方法
* @return
*/
public Integer getSkuId() {
return skuId;
}
public String getSkuName() {
return skuName;
}
public Double getSkuPrice() {
return skuPrice;
}
public Integer getTotalNum() {
return totalNum;
}
public Double getTotalPrice() {
return totalPrice;
}
public Enum getSkuCategory() {
return skuCategory;
}
}
首先建立一个Sku的类,这个类是啥?如果有电商经验的小伙伴应该了解,它可以简单的理解为电商领域商品的一个类。它里面会标识着这个商品叫什么,单价是多少,购买的个数,总价是多少,类型。咱们都定义到Sku这个类。
咱们再来创建一个购物车的服务,用来处理和管理这些对象。我们叫它CartService。
import java.util.ArrayList;
import java.util.List;
/**
* 购物车服务类
*/
public class CartService {
// 加入到购物车中的商品信息
private static List<Sku> cartSkuList =
new ArrayList<Sku>(){
{
add(new Sku(654032, "无人机",
4999.00, 1,
4999.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(642934, "VR一体机",
2299.00, 1,
2299.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(645321, "纯色衬衫",
409.00, 3,
1227.00, SkuCategoryEnum.CLOTHING));
add(new Sku(654327, "牛仔裤",
528.00, 1,
528.00, SkuCategoryEnum.CLOTHING));
add(new Sku(675489, "跑步机",
2699.00, 1,
2699.00, SkuCategoryEnum.SPORTS));
add(new Sku(644564, "Java编程思想",
79.80, 1,
79.80, SkuCategoryEnum.BOOKS));
add(new Sku(678678, "Java核心技术",
149.00, 1,
149.00, SkuCategoryEnum.BOOKS));
add(new Sku(697894, "算法",
78.20, 1,
78.20, SkuCategoryEnum.BOOKS));
add(new Sku(696968, "TensorFlow进阶指南",
85.10, 1,
85.10, SkuCategoryEnum.BOOKS));
}
};
}
/**
* 商品类型枚举
*/
public enum SkuCategoryEnum {
CLOTHING(10, "服装类"),
ELECTRONICS(20, "数码类"),
SPORTS(30, "运动类"),
BOOKS(40, "图书类");
// 商品类型的编号
private Integer code;
// 商品类型的名称
private String name;
/**
* 构造函数
* @param code
* @param name
*/
SkuCategoryEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
}
在这里我们先定义一个静态的集合,这个集合就是所有加到购物车里的商品列表。我们用一个静态方法来初始化。我们定义了一个枚举来作为它的商品类型。
常用List集合初始化方式
常用List集合初始化方式
1. 先创建List再赋值
标准方式,先创建集合对象,然后逐个调用add
方法初始化。用起来比较繁琐,不太方便!
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
2. 使用{{}}
双大括号初始化
使用匿名内部类完成初始化。外层的{}
定义了一个ArrayList的匿名内部类,内层的{}
定义了一个实例初始化代码块。有内存泄漏的风险!不建议在生产项目中使用!
List<Integer> list3 = new ArrayList<Integer>(){
{
add(1);
add(2);
add(3);
}
};
3. 使用Arrays.asList
使用Arrays的静态方法asList
初始化。返回的list集合是不可变的!
import java.util.Arrays;
List<Integer> list = Arrays.asList(1, 2, 3);
4. 使用Stream
(JDK8以上)
使用JDK8引入的Stream的of
方法生成一个stream对象,调用collect
方法进行收集,形成一个List集合。
import java.util.stream.Stream;
List<Integer> list = Stream.of(1, 2, 3).collect(Collectors.toList());
5. 使用Google Guava工具集Lists
(需要引入Guava工具包)(Guava28.2为最新版本,对应JDK8及以上)
借助Google Guava工具集中的Lists
工具类初始化。需要引入Guava包才能用!
import com.google.common.collect.Lists;
List<Integer> list = Lists.newArrayList(1, 2, 3);
6. 使用List
(JDK9以上)
使用JDK9引入的List.of
方法完成初始化。
import com.sun.tools.javac.util.List;
List<Integer> list = List.of(1, 2, 3);
以上完成了数据的准备工作,接下来我们接受到了业务方,也就是女朋友的第一个需求:
要找出商品中电子产品有多少?都是什么?
这个可能也是有原因的,因为程序员对电子产品都是很发烧的。所以买的时候从来都不看价钱。那咱们就来完成第一个需求。
import java.util.ArrayList;
import java.util.List;
/**
* 购物车服务类
*/
public class CartService {
// 加入到购物车中的商品信息
private static List<Sku> cartSkuList =
new ArrayList<Sku>(){
{
add(new Sku(654032, "无人机",
4999.00, 1,
4999.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(642934, "VR一体机",
2299.00, 1,
2299.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(645321, "纯色衬衫",
409.00, 3,
1227.00, SkuCategoryEnum.CLOTHING));
add(new Sku(654327, "牛仔裤",
528.00, 1,
528.00, SkuCategoryEnum.CLOTHING));
add(new Sku(675489, "跑步机",
2699.00, 1,
2699.00, SkuCategoryEnum.SPORTS));
add(new Sku(644564, "Java编程思想",
79.80, 1,
79.80, SkuCategoryEnum.BOOKS));
add(new Sku(678678, "Java核心技术",
149.00, 1,
149.00, SkuCategoryEnum.BOOKS));
add(new Sku(697894, "算法",
78.20, 1,
78.20, SkuCategoryEnum.BOOKS));
add(new Sku(696968, "TensorFlow进阶指南",
85.10, 1,
85.10, SkuCategoryEnum.BOOKS));
}
};
/**
* 获取商品信息列表
* @return
*/
public static List<Sku> getCartSkuList() {
return cartSkuList;
}
/**
* Version 1.0.0
* 找出购物车中所有电子产品
* @param cartSkuList
* @return
*/
public static List<Sku> filterElectronicsSkus(
List<Sku> cartSkuList) {
List<Sku> result = new ArrayList<Sku>();
for (Sku sku: cartSkuList) {
// 如果商品类型 等于 电子类
if (SkuCategoryEnum.ELECTRONICS.
equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
}
要从这对商品中找出商品Sku列表,所以返回参数类型是一个Sku列表,方法名叫filterElectronicsSkus,在这里我统一加一个版本,版本代表函数编程的演化版本。Version 1.0.0就是最初的需求版本号。
对于大部分人来说,实现这个需求都是很简单的。先定义一个返回参数result,然后foreach循环一下购物车里的所有商品信息列表。如果商品类型 等于 电子类,就把它加到返回的result集合里。然后返回。
注:idea快捷键:Ctrl + y 整行删除
那么找出所有电子类的Sku商品这个功能就已经实现了。这是Version 1.0.0版本。
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import java.util.List;
public class Version1Test {
@Test
public void filterElectronicsSkus() {
List<Sku> cartSkuList = CartService.getCartSkuList();
// 查找购物车中数码类商品
List<Sku> result =
CartService.filterElectronicsSkus(cartSkuList);
System.out.println(
JSON.toJSONString(result, true));
}
}
第一个需求就已经完成了。
接着,业务方在第一个需求的基础上,又提出了第二个需求:
她想看一下每类的商品都有哪些?
那么很明显,咱们第一个找出购物车中所有的电子产品是不满足要求的。那咱们在业务方提出的需求上进行迭代。再实现一个根据商品类型进行查找商品的方法。
import java.util.ArrayList;
import java.util.List;
/**
* 购物车服务类
*/
public class CartService {
// 加入到购物车中的商品信息
private static List<Sku> cartSkuList =
new ArrayList<Sku>(){
{
add(new Sku(654032, "无人机",
4999.00, 1,
4999.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(642934, "VR一体机",
2299.00, 1,
2299.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(645321, "纯色衬衫",
409.00, 3,
1227.00, SkuCategoryEnum.CLOTHING));
add(new Sku(654327, "牛仔裤",
528.00, 1,
528.00, SkuCategoryEnum.CLOTHING));
add(new Sku(675489, "跑步机",
2699.00, 1,
2699.00, SkuCategoryEnum.SPORTS));
add(new Sku(644564, "Java编程思想",
79.80, 1,
79.80, SkuCategoryEnum.BOOKS));
add(new Sku(678678, "Java核心技术",
149.00, 1,
149.00, SkuCategoryEnum.BOOKS));
add(new Sku(697894, "算法",
78.20, 1,
78.20, SkuCategoryEnum.BOOKS));
add(new Sku(696968, "TensorFlow进阶指南",
85.10, 1,
85.10, SkuCategoryEnum.BOOKS));
}
};
/**
* 获取商品信息列表
* @return
*/
public static List<Sku> getCartSkuList() {
return cartSkuList;
}
/**
* Version 1.0.0
* 找出购物车中所有电子产品
* @param cartSkuList
* @return
*/
public static List<Sku> filterElectronicsSkus(
List<Sku> cartSkuList) {
List<Sku> result = new ArrayList<Sku>();
for (Sku sku: cartSkuList) {
// 如果商品类型 等于 电子类
if (SkuCategoryEnum.ELECTRONICS.
equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
/**
* Version 2.0.0
* 根据传入商品类型参数,找出购物车中同种商品类型的商品列表
* @param cartSkuList
* @param category
* @return
*/
public static List<Sku> filterSkusByCategory(
List<Sku> cartSkuList, SkuCategoryEnum category) {
List<Sku> result = new ArrayList<Sku>();
for (Sku sku: cartSkuList) {
// 如果商品类型 等于 传入商品类型参数
if (category.equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
}
实现一个根据商品类型查找的方法,叫它filterSkusByCategory,那咱们的入参就不只是商品列表了,还要有一个商品类型的入参。返回依然是一个Sku的列表。
这个需求就已经完成了,测试一下。
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import java.util.List;
public class Version2Test {
@Test
public void filterSkusByCategory() {
List<Sku> cartSkuList = CartService.getCartSkuList();
// 查找购物车中图书类商品集合
List<Sku> result = CartService.filterSkusByCategory(
cartSkuList, SkuCategoryEnum.BOOKS);
System.out.println(JSON.toJSONString(
result, true));
}
}
这样所有书籍类的商品就已经找出来了。
咱们第二版改进的好处是:根据类型参数化了这个查找的结果,你输入什么类型,我就返回什么类型对应的商品列表。
需求方在第二个版本迭代的基础上又提出了第三个需求版本变更,她现在不想根据商品类型来过滤商品了。她想根据商品总价来过滤商品,想找出商品总价大于2000的商品都有哪些?那咱们继续迭代咱们的功能。
import java.util.ArrayList;
import java.util.List;
/**
* 购物车服务类
*/
public class CartService {
// 加入到购物车中的商品信息
private static List<Sku> cartSkuList =
new ArrayList<Sku>(){
{
add(new Sku(654032, "无人机",
4999.00, 1,
4999.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(642934, "VR一体机",
2299.00, 1,
2299.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(645321, "纯色衬衫",
409.00, 3,
1227.00, SkuCategoryEnum.CLOTHING));
add(new Sku(654327, "牛仔裤",
528.00, 1,
528.00, SkuCategoryEnum.CLOTHING));
add(new Sku(675489, "跑步机",
2699.00, 1,
2699.00, SkuCategoryEnum.SPORTS));
add(new Sku(644564, "Java编程思想",
79.80, 1,
79.80, SkuCategoryEnum.BOOKS));
add(new Sku(678678, "Java核心技术",
149.00, 1,
149.00, SkuCategoryEnum.BOOKS));
add(new Sku(697894, "算法",
78.20, 1,
78.20, SkuCategoryEnum.BOOKS));
add(new Sku(696968, "TensorFlow进阶指南",
85.10, 1,
85.10, SkuCategoryEnum.BOOKS));
}
};
/**
* 获取商品信息列表
* @return
*/
public static List<Sku> getCartSkuList() {
return cartSkuList;
}
/**
* Version 1.0.0
* 找出购物车中所有电子产品
* @param cartSkuList
* @return
*/
public static List<Sku> filterElectronicsSkus(
List<Sku> cartSkuList) {
List<Sku> result = new ArrayList<Sku>();
for (Sku sku: cartSkuList) {
// 如果商品类型 等于 电子类
if (SkuCategoryEnum.ELECTRONICS.
equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
/**
* Version 2.0.0
* 根据传入商品类型参数,找出购物车中同种商品类型的商品列表
* @param cartSkuList
* @param category
* @return
*/
public static List<Sku> filterSkusByCategory(
List<Sku> cartSkuList, SkuCategoryEnum category) {
List<Sku> result = new ArrayList<Sku>();
for (Sku sku: cartSkuList) {
// 如果商品类型 等于 传入商品类型参数
if (category.equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
/**
* Version 3.0.0
* 支持通过商品类型或总价来过滤商品
* @param cartSkuList
* @param category
* @param totalPrice
* @param categoryOrPrice - true: 根据商品类型,false: 根据商品总价
* @return
*/
public static List<Sku> filterSkus(
List<Sku> cartSkuList, SkuCategoryEnum category,
Double totalPrice, Boolean categoryOrPrice) {
List<Sku> result = new ArrayList<Sku>();
for (Sku sku: cartSkuList) {
// 如果根据商品类型判断,sku类型与输入类型比较
// 如果根据商品总价判断,sku总价与输入总价比较
if (
(categoryOrPrice
&& category.equals(sku.getSkuCategory())
||
(!categoryOrPrice
&& sku.getTotalPrice() > totalPrice))) {
result.add(sku);
}
}
return result;
}
}
咱们如果既要保证现在这个根据商品类型来过滤这个功能,也要兼容根据价钱过滤的功能,那咱们就需要多加一些输入参数了。首先购物车的商品信息列表还是不能少,category这个类型也是不能少,然后还要接受一个Double类型的totalPrice总价,然后还需要传入一个Boolean类型的变量,这个变量是记录着我是根据category还是price来过滤商品。返回的还是一个商品信息列表。这是第三个迭代版本。咱们建一个测试类来测试一下。
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import java.util.List;
public class Version3Test {
@Test
public void filterSkus() {
List<Sku> cartSkuList = CartService.getCartSkuList();
// 根据商品总价过滤超过2000元的商品列表
List<Sku> result = CartService.filterSkus(
cartSkuList, null,
2000.00, false);
System.out.println(JSON.toJSONString(
result, true));
}
}
那咱们第三个需求就已经迭代完了。这个方法及支持根据商品类型进行过滤,也支持根据商品总价来过滤。但是商品还有单价,还有个数等好多的属性。如果这个方法想满足对所有属性的自定义过滤,那显然是不行的。那咱们有没有好的方法来进行不同商品属性的过滤呢?
咱们可以退一步,站在更高的层次上进行抽象,咱们刚才解决的问题,无非就是根据Sku的某些特性来判断它是否符合要求,返回一个True或False。站在更高层次上对选择标准进行建模,我们在属于中把它叫做谓词,就是一个返回Boolean类型的值的函数。那好,我们来定义第四版的迭代。
先定义一个SkuPredicate接口
/**
* Sku选择谓词接口
*/
public interface SkuPredicate {
/**
* 选择判断标准
* @param sku
* @return
*/
boolean test(Sku sku);
}
import java.util.ArrayList;
import java.util.List;
/**
* 购物车服务类
*/
public class CartService {
// 加入到购物车中的商品信息
private static List<Sku> cartSkuList =
new ArrayList<Sku>(){
{
add(new Sku(654032, "无人机",
4999.00, 1,
4999.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(642934, "VR一体机",
2299.00, 1,
2299.00, SkuCategoryEnum.ELECTRONICS));
add(new Sku(645321, "纯色衬衫",
409.00, 3,
1227.00, SkuCategoryEnum.CLOTHING));
add(new Sku(654327, "牛仔裤",
528.00, 1,
528.00, SkuCategoryEnum.CLOTHING));
add(new Sku(675489, "跑步机",
2699.00, 1,
2699.00, SkuCategoryEnum.SPORTS));
add(new Sku(644564, "Java编程思想",
79.80, 1,
79.80, SkuCategoryEnum.BOOKS));
add(new Sku(678678, "Java核心技术",
149.00, 1,
149.00, SkuCategoryEnum.BOOKS));
add(new Sku(697894, "算法",
78.20, 1,
78.20, SkuCategoryEnum.BOOKS));
add(new Sku(696968, "TensorFlow进阶指南",
85.10, 1,
85.10, SkuCategoryEnum.BOOKS));
}
};
/**
* 获取商品信息列表
* @return
*/
public static List<Sku> getCartSkuList() {
return cartSkuList;
}
/**
* Version 1.0.0
* 找出购物车中所有电子产品
* @param cartSkuList
* @return
*/
public static List<Sku> filterElectronicsSkus(
List<Sku> cartSkuList) {
List<Sku> result = new ArrayList<Sku>();
for (Sku sku: cartSkuList) {
// 如果商品类型 等于 电子类
if (SkuCategoryEnum.ELECTRONICS.
equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
/**
* Version 2.0.0
* 根据传入商品类型参数,找出购物车中同种商品类型的商品列表
* @param cartSkuList
* @param category
* @return
*/
public static List<Sku> filterSkusByCategory(
List<Sku> cartSkuList, SkuCategoryEnum category) {
List<Sku> result = new ArrayList<Sku>();
for (Sku sku: cartSkuList) {
// 如果商品类型 等于 传入商品类型参数
if (category.equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
/**
* Version 3.0.0
* 支持通过商品类型或总价来过滤商品
* @param cartSkuList
* @param category
* @param totalPrice
* @param categoryOrPrice - true: 根据商品类型,false: 根据商品总价
* @return
*/
public static List<Sku> filterSkus(
List<Sku> cartSkuList, SkuCategoryEnum category,
Double totalPrice, Boolean categoryOrPrice) {
List<Sku> result = new ArrayList<Sku>();
for (Sku sku: cartSkuList) {
// 如果根据商品类型判断,sku类型与输入类型比较
// 如果根据商品总价判断,sku总价与输入总价比较
if (
(categoryOrPrice
&& category.equals(sku.getSkuCategory())
||
(!categoryOrPrice
&& sku.getTotalPrice() > totalPrice))) {
result.add(sku);
}
}
return result;
}
/**
* Version 4.0.0
* 根据不同的Sku判断标准,对Sku列表进行过滤
* @param cartSkuList
* @param predicate - 不同的Sku判断标准策略
* @return
*/
public static List<Sku> filterSkus(
List<Sku> cartSkuList, SkuPredicate predicate) {
List<Sku> result = new ArrayList<Sku>();
for (Sku sku: cartSkuList) {
// 根据不同的Sku判断标准策略,对Sku进行判断
if (predicate.test(sku)) {
result.add(sku);
}
}
return result;
}
}
SkuPredicate接口里面只有一个选择的标准,返回boolean类型,接收的也是Sku对象。咱们可以实现这个接口,重写它的判断标准方法,来根据Sku里面的不同属性做出咱们自己的判断标准。然后咱们的方法要怎么改呢?首先它的方法定义还哦是返回一个Sku的集合,入参首先商品列表cartSkuList肯定少不了,第二个要传我们刚才为Sku定义的不同标准类型,我们可以把它看成是对Sku的不同操作行为。有点类似于设计模式中的策略模式,我们定义一组算法,把它封装起来,然后在运行时,根据传入的实际算法,来执行我们的业务逻辑。来看一看我们的实现,cartSkuList列表照样进行循环,但是If判断的时候,因为我们传入了不同的Sku判断标准策略,咱们就可以调用predicate.test(sku)的方法,将Sku传进去,它会返回一个boolean类型,如果它符合咱们的策略,就返回true,就将sku加到result返回结果集中。这样做看似有点儿灵活了。
那咱们还是来实现最开始的这个业务功能,这里创建一个新的类。
/**
* 对Sku的商品类型为图书类的判断标准
*/
public class SkuBooksCategoryPredicate implements SkuPredicate {
@Override
public boolean test(Sku sku) {
return SkuCategoryEnum.BOOKS.equals(sku.getSkuCategory());
}
}
再定义一个类
/**
* 对Sku的总价是否超出2000作为判断标准
*/
public class SkuTotalPricePredicate implements SkuPredicate {
@Override
public boolean test(Sku sku) {
return sku.getTotalPrice() > 2000;
}
}
现在咱们来测试一下
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import java.util.List;
public class Version4Test {
@Test
public void filterSkus() {
List<Sku> cartSkuList = CartService.getCartSkuList();
// 过滤商品总价大于2000的商品
List<Sku> result = CartService.filterSkus(
cartSkuList, new SkuTotalPricePredicate());
System.out.println(JSON.toJSONString(
result, true));
}
}
还是可以找出价格大于2000的商品。
咱们再来分析一下这个第四版迭代功能的实现,咱们从最开始在if判断里面的硬编码,写死的它的类型等于图书,它的类型等于电子或者总价大于多少。变成了咱们传入一个判断标准,这个判断标准会动态的sku的属性是否满足咱们的判断标准条件,来决定这个sku是否返回。咱们无形中就把判断sku是否满足标准的条件进行了参数化。咱们是通过参数传进来一个接口,接口的实现类定义了test方法的具体实现,那这就是一个重大的突破。咱们将行为参数化了,传到了方法里。这个其实也是设计模式中的策略模式。通过策略模式将数据的循环和处理区分开了,数据的循环在统一的方法中执行,处理将由不同的接口实现类自定义自己的业务处理逻辑。这就是咱们第四版的功能迭代。
在第四版程序迭代完成之后,咱们看似实现了一个比较灵活的方法,来将行为或不同的标准进行参数化传递到方法里面进行判断。那还有没有问题呢?还有没有可改进之处呢?咱们来看一看它的调用,它调用的时候需要创建一个咱们自定义的过滤规则类SkuTotalPricePredicate,这个类需要实例化并编写这么一个类。然后实例化不同的过滤规则。有过编程基础的小伙伴一定会想到这个类我们只使用一遍,是不是就没必要编写这么多了?匿名内部类可以帮助我们进行一定的优化。
那咱们使用匿名内部类来优化咱们的测试方法:
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import java.util.List;
public class Version5Test {
@Test
public void filterSkus() {
List<Sku> cartSkuList = CartService.getCartSkuList();
// 过滤商品单价大于1000的商品
List<Sku> result = CartService.filterSkus(
cartSkuList, new SkuPredicate() {
@Override
public boolean test(Sku sku) {
return sku.getSkuPrice() > 1000;
}
});
System.out.println(JSON.toJSONString(
result, true));
}
}
这里不需要建新的类了,只需要一个匿名内部类来写明咱们的判断标准就可以了。
上一个版本我们通过匿名类来筛选我们的产品,这样的好处是不用再对接口进行实现,不用再产生那么多多余的类,只使用一次也没有必要。可是我觉得匿名类里面还是有点多。接下来我们通过核心的Lambda来改写上面通过匿名类做筛选条件的代码。作为第六版本的代码。
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import java.util.List;
public class Version6Test {
@Test
public void filterSkus() {
List<Sku> cartSkuList = CartService.getCartSkuList();
// 过滤商品单价大于1000的商品
List<Sku> result = CartService.filterSkus(
cartSkuList,
(Sku sku) -> sku.getSkuPrice() > 1000);
System.out.println(JSON.toJSONString(
result, true));
}
}
伟大的Lambda帮我们简化了许多的代码,现在只需要通过下面一句就可以筛选出我所需要的单价大于2000的商品。
(Sku sku) -> sku.getSkuPrice() > 1000
这里的写法出现了很大的不同,这也就是咱们接下来要讲的主题:Lambda表达式。它是如何替代匿名类,将行为进行参数化传递到代码中,由代码动态调用。
咱们再来回顾一下函数编程的演化历史:
函数编程演化历程
- 将业务逻辑直接写死在代码里。
- 将单一维度的条件做为参数传入方法中。方法内根据参数进行业务逻辑实现。
- 将多个维度的条件做为参数传入方法中。业务实现需要根据不同的参数处理不同逻辑。
- 将业务逻辑封装为一个实体类,方法接受实体类为参数,方法内部调用实体类的处理逻辑。
- 调用方法时不在创建实体类,而是使用匿名函数的形式替代。
- 使用Lambda表达式替代匿名函数的形式,做为方法的参数。真正实现判断逻辑参数化传递。
通过上面的例子,我们可以直观的感受到Lambda表达式可以给我们带来哪些方便。相信有很多同学依旧是一脸闷逼,这是什么鬼?为什么我能用Lambda这样改写这个匿名类?咱们先别着急,咱们一定会弄明白这一点。但是咱们首先要从Lambda表达式说起。
在说Lambda表达式之前咱们需要引入一个函数式编程的思想。那什么是函数式编程?函数式编程其实是一种不同的编程思想,它定义函数作为一等公民,可以赋值给变量,作为参数或者返回值进行传递。而Lambda表达式呢,就是Java引入的函数式编程的一次革命性的尝试。我们都知道Java是面向对象的。“万物皆对象”是它一贯遵循的准则,那它这次突破了只有类作为头等公民的设计,支持将函数作为参数进行传递,在编程方式上提供了很大的便捷。
那为什么Java要引入这货呢?这就要从大环境上说了,因为在Java之后,JVM平台出现了很多其他的编程语言,比如Scale和kotlin都可以算是其中的佼佼者,这些多范式的编程语言开始受到越来越多开发者的关注和喜爱。相对来说Java完全面向对象的设计就有些死板了,所以为了更适应开发者的需要,Java8开启了探索之路,而Lambda就是它一次伟大的、革命性的尝试。从上面的案例可以看出Lambda表达式可以代替掉原来面向对象风格下的匿名函数所做的所有事情。这就是将函数作为一等公民的体现。它的本质呢,是要做到将行为参数化传递的目的。只不过以前是通过基于对象的类或者匿名类进行实现的。现在呢它是基于函数,直接将方法作为入参进行传递。这就是对Lambda的一些简单的介绍。
接下来我们来看一下Lambda表达式的构成:
主要分为两种构成:
第一种是由:参数、箭头和表达式构成。
第二种是由:参数、箭头。大括号和语句构成。
所以下次小伙伴看见这种箭头符号,就不要再问了,因为它是Lambda表达式新引入的一个标志。
那Lamda表达式有哪些重要特征呢?
第一就是可选的类型声明,我们可以不需要声明参数类型,编译器可以统一的识别参数值。
第二呢就是可选的参数圆括号,一个参数无需定义圆括号,但多个参数需要定义圆括号。
第三呢就是可选的大括号,如果主体包含了一个表达式,就不需要使用大括号;
第四呢是可选的返回关键字,如果主体只有一个关键字返回值,则编译器会自动返回值。大括号需要指明语句返回了一个数值。
下面来看一看具体的表达形式:
形式一是没有参数,没有参数时,左边的小括号是不能省略的。然后是箭头,紧接着是一句表达式。
形式二是只有一个参数的, 左边的小括号是可以省略的,直接写参数名、箭头加表达式。
形式三是没有参数,但逻辑复杂的, 没有参数,左边的小括号不能省略。因为逻辑复杂,所以需要用大括号来将它的语句括起来。
第四是含有两个参数的方法, 此时这个BinaryOperator是一个方法的引用,有x和y作为参数的Lambda,返回x+y的结果值,返回值可以不用定义,编译器会自动的判断返回值是哪个,类型是什么?在下面调用的时候,functionAdd的apply就是在调用Lambda表达式的这个方法,做一个加和的操作。
第五种,对参数做一个显示的声明,与上一种不同的是,此时的x和y作为参数作为参数,已经被强制的声明为Long型了。那这个可以声明,也可以不声明,编译器会根据Lambda表达式的组成自行去判断。
以上五种就是Lambda表达式的具体形式。
接下来我们来说一说函数式接口。如何才能自定定义一个符合Lambda表达式规范的函数式接口呢?
严格意义上讲,只有一个约束条件:接口只有一个抽象方法。
咱们可以回头看一看咱们在实战中写的接口。
/**
* Sku选择谓词接口
*/
public interface SkuPredicate {
/**
* 选择判断标准
* @param sku
* @return
*/
boolean test(Sku sku);
}
Sku选择谓词接口,它只有一个test的抽象方法,在使用的时候它是可以被Lambda作为参数进行传递的。
import com.alibaba.fastjson.JSON;
import org.junit.Test;
import java.util.List;
public class Version6Test {
@Test
public void filterSkus() {
List<Sku> cartSkuList = CartService.getCartSkuList();
// 过滤商品单价大于1000的商品
List<Sku> result = CartService.filterSkus(
cartSkuList,
(Sku sku) -> sku.getSkuPrice() > 1000);
System.out.println(JSON.toJSONString(
result, true));
}
}
那这个接口就是符合函数式接口规范的。
第二呢,就是Java8的函数式接口引入了一个新的注解,这个注解只是作为一个标注,并不是需要强制的所有的函数式接口都要实现的这么一个注解,它的目的只是帮助编译器校验是否符合函数式接口定义,也就是说是否只有一个抽象方法?只要遵循着两个限制,就可以实现自定义的函数是接口。
第三个呢,需要说一下函数式接口的抽象方法签名,我们称之为“函数描述符”,作为函数式接口入参的校验标准。下面咱们通过一个实战案例:自定义一个函数式接口。来实现读取本地文件后自定义处理逻辑的功能。
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 文件服务类
*/
public class FileService {
/**
* 从过url获取本地文件内容,调用函数式接口处理
* @param url
* @param fileConsumer
*/
public void fileHandle(String url, FileConsumer fileConsumer)
throws IOException {
}
}
咱们首先创建一个FileService的文件服务类,它需要完成什么功能呢?我们定义一个方法,我们叫它fileHandle,参数我们接收一个url,然后第二个参数我们需要接受一个函数式接口,然后通过Lambda表达式,传入接口不同的业务处理逻辑来实现对文件的操作。
那咱们先来定义自己的函数式文件接口。
/**
* 文件处理函数式接口
*/
@FunctionalInterface
public interface FileConsumer {
/**
* 函数式接口抽象方法
* @param fileContent - 文件内容字符串
*/
void fileHandler(String fileContent);
}
这里定义一个文件处理函数式接口,之前咱们讲了,如果是函数式接口,就要实现一个@FunctionalInterface注解,函数式接口叫fileHandler,它接收的是文件内容对象,是文件内容字符串。然后这个接口就已经定义完了,咱们将这个接口传入之前的文件服务类FileService的fileHandle方法中,这里我们要实现通过url获取本地文件内容,调用函数式接口处理。
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* 文件服务类
*/
public class FileService {
/**
* 从过url获取本地文件内容,调用函数式接口处理
* @param url
* @param fileConsumer
*/
public void fileHandle(String url, FileConsumer fileConsumer)
throws IOException {
// 创建文件读取流
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(
new FileInputStream(url)));
// 定义行变量和内容sb
String line;
StringBuilder stringBuilder = new StringBuilder();
// 循环读取文件内容
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + "\n");
}
// 调用函数式接口方法,将文件内容传递给lambda表达式,实现业务逻辑
fileConsumer.fileHandler(stringBuilder.toString());
}
}
咱们把获取文件内容部分完善一下,本地文件咱们需要定义一个BufferedReader,它的里面需要一个InputStreamReader,InputStreamReader里面又需要一个FileInputStream的流,这个流就是通过url建立起来的。Java流的使用就是一层套一层,比较麻烦。咱们获取到一个BufferedReader对象。接着咱们定义一个行的变量line,来记录整行。定义一个stringBuilder来记录文件信息,接着遍历,从bufferedReader中读取一行,判断这行是否为空?如果不为空,将子行添加到stringBuilder里面。如果为空跳出循环。紧接着呢,咱么调用fileConsumer的fileHandler方法来处理整个文件内容。
import org.junit.Test;
import java.io.IOException;
public class FileServiceTest {
@Test
public void fileHandle() throws IOException {
FileService fileService = new FileService();
// TODO 此处替换为本地文件的地址全路径
String filePath = "";
// 通过lambda表达式,打印文件内容
//fileService.fileHandle(filePath,
// fileContent -> System.out.println(fileContent));
fileService.fileHandle(filePath,
System.out::println);
}
}
接下来咱们来看一下常用的函数式接口及使用,咱们在前面的两个案例中,分别定义了Sku的选择谓词接口和文件处理的函数式接口,先比之下咱们的接口还是比较具象,因为咱们限制了Sku选择谓词接口传入参数一定是Sku对象,文件处理接口传入参数一定是字符串类型的对象,相比来说还是有些具象。我们还能不能再泛化一下呢?其实是可以的,JDK为我们提供了一些比较泛化的接口。这样我们就不用再自己定义接口了。
比如说举个例子,Predicate接口,这个接口就是用来判别一个对象。比如求一个人是否为男性。它这个接口与我们自己定义的SkuPredicate接口唯一的区别是,它使用了泛型将传入的参数进行了泛化,不再是限制到具体的某个类。可以说咱们自己定义的接口是完全没有必要的。咱们可以使用JDK为我们提供的这个泛化的Predicate接口。Consumer接口其实也是一样的,想我们的FileConsumer只接收一个String变成了可以接收一个泛化的T。其他的接口类似,我们可以挑着看几个,比如说这个Function接口。
package java.util.function;
import java.util.Objects;
/**
* Represents a function that accepts one argument and produces a result.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #apply(Object)}.
*
* @param <T> the type of the input to the function
* @param <R> the type of the result of the function
*
* @since 1.8
*/
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
/**
* Returns a composed function that first applies the {@code before}
* function to its input, and then applies this function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of input to the {@code before} function, and to the
* composed function
* @param before the function to apply before this function is applied
* @return a composed function that first applies the {@code before}
* function and then applies this function
* @throws NullPointerException if before is null
*
* @see #andThen(Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*
* @see #compose(Function)
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* Returns a function that always returns its input argument.
*
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
它定义了两个方法参数,一个是T,一个是R,T作为apply的输入参数,R作为apply的返回参数类型。@FunctionalInterface也标明了它是一个函数式接口,那我们就可以用它来传入一个参数类型,返回另一个参数类型的场景中来使用它。咱们再来看一个BiFunction接口。
package java.util.function;
import java.util.Objects;
/**
* Represents a function that accepts two arguments and produces a result.
* This is the two-arity specialization of {@link Function}.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #apply(Object, Object)}.
*
* @param <T> the type of the first argument to the function
* @param <U> the type of the second argument to the function
* @param <R> the type of the result of the function
*
* @see Function
* @since 1.8
*/
@FunctionalInterface
public interface BiFunction<T, U, R> {
/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
*
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*/
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
该接口有三个方法参数,apply方法提供了两个不同类型的入参,返回了一个R类型的出参。那这个接口适用的条件就是,当我需要两个参数传入,一个参数返回的时候,我需要使用这个BiFuntion接口。
在这个接口的基础上还有一个BinaryOperator接口,这个接口强制的将三个参数类型都指定为T,返回了一个T,也就是说它接收了两个同种类型的参数,输出了一个同种类型的参数。
package java.util.function;
import java.util.Objects;
import java.util.Comparator;
/**
* Represents an operation upon two operands of the same type, producing a result
* of the same type as the operands. This is a specialization of
* {@link BiFunction} for the case where the operands and the result are all of
* the same type.
*
* <p>This is a <a href="package-summary.html">functional interface</a>
* whose functional method is {@link #apply(Object, Object)}.
*
* @param <T> the type of the operands and result of the operator
*
* @see BiFunction
* @see UnaryOperator
* @since 1.8
*/
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T> {
/**
* Returns a {@link BinaryOperator} which returns the lesser of two elements
* according to the specified {@code Comparator}.
*
* @param <T> the type of the input arguments of the comparator
* @param comparator a {@code Comparator} for comparing the two values
* @return a {@code BinaryOperator} which returns the lesser of its operands,
* according to the supplied {@code Comparator}
* @throws NullPointerException if the argument is null
*/
public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;
}
/**
* Returns a {@link BinaryOperator} which returns the greater of two elements
* according to the specified {@code Comparator}.
*
* @param <T> the type of the input arguments of the comparator
* @param comparator a {@code Comparator} for comparing the two values
* @return a {@code BinaryOperator} which returns the greater of its operands,
* according to the supplied {@code Comparator}
* @throws NullPointerException if the argument is null
*/
public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) {
Objects.requireNonNull(comparator);
return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;
}
}
类似于这样的接口,我们可以在 java.util.function这个包下面找到很多jdk为我们预制的函数式接口。
有一些int类型,包括Double类型,还有一些Boolean类型的,为基础类型提供的一些函数式接口,我们在选择的时候也可以选择它们,选择基础类型的函数式接口的一个好处是不会装箱拆箱,所以在性能方面会有一些好处。而刚才咱们看的Function接口,是一个很泛化的接口,适合处理非基础类型的数据。
可能有些小伙伴是第一次看到这样的代码,会不会很疑惑?双冒号是什么鬼?这种写法好像不是Java语法吧?我是不是打开方式不对?那别急,这就是接下来我们要介绍的方法引用的内容。
对于方法引用主要分为三种方式,那下面就通过例子来体会方法引用的本质。
首先来看指向静态方法的方法引用,consumer1这个lambda表达式只是将传入的参数调用了Integer的parseInt方法,进行了一个字符串转数字的操作。当遇到这样的情况时,我们就可以使用consumer2的Integer :: parsreInt这样的方法引用来替换掉。上面的注释也写的很明白,当Lambda表达式的结构体是一个对象,并且调用它的静态方法时,咱们就可以直接使用方法引用,对象名::静态方法名。这就是第一种方法引用的情况。
第二种指向任意类型实例方法的方法引用。 consumer1它调用了传入参数的实例方法,那这样我们就可以换成 类名::实例方法名 来替换掉上面的Lambda表达式,这是方法引用的第二种形式。
第三种形式:指向现有对象的实例方法的方法引用。stringBuilder是咱们的一个外部对象,在Lambda表达式中调用这个外部对象他自己的append方法来对参数做处理,当遇到咱们就可以改写成comsumer2这种形式,直接是 外部的实例对象 :: 方法名。
以上呢就是使用方法引用来对Lambda表达式进行改写,我们在平时使用Lambda表达式简化代码量时,也要灵活使用方法引用,进一步提升我们的开发效率。
接下来我们对方法引用的知识做一个精讲,来了解一下这几种方法引用的区别,它是怎么可以怎么可以作为一个Lambda表达式来进行传递的?那本节呢我们来介绍这个问题。
首先我们往回倒一点,看看Lambda表达式的由来,可以看到这是我们以前的一个示例,最开始我们使用一个实体类来代表这个判断逻辑,我们通过创建一个实体类,在实体类里面创建一些逻辑判断来执行我们的判断逻辑。
后来我们改用了匿名类来代替,因为我们发现建立一个又一个类来写这个业务逻辑,但是每建立一个只能使用一次,不太划算,所以我们就采用这种匿名类的方式来处理我们的业务逻辑。
再后来呢,Lambda表达式来了, 我们开始使用Lambda表达式来处理我们的业务逻辑,我这个Lambda表达式呢,只想表达一个意思,就是获取sku的价格,然后跟1000做对比,这个就是这段Lambda判断的逻辑。
我们再看方法引用是怎么来的? 如果我这个Sku里面恰好有这么一个comparePrice方法,这个方法就是获取sku的价格和1000作比较,返回呢也是一个Boolean类型,那我是不是就可以改写掉上面那个Lambda表达式呢?
改写之后,我就会使用 Sku :: comparePrice 这种形式来表达上面的我用Lambda表达式写的那一堆的业务逻辑。这个呢,就是方法引用。
那上面的定义说了,方法引用可以直接表示类或者实例对应的一个方法,那我们来看一看方法引用的分类,总共分为四类。
下面我们来看一下这四种方法引用的使用场景一区别。
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* 类名称:MethodReferenceTest
* ********************************
* <p>
* 类描述:方法引用测试
*
* @author
* @date 下午10:22
*/
public class MethodReferenceTest {
static class Sku {
private String skuName;
private Integer skuPrice;
public Integer getSkuPrice() {
return this.skuPrice;
}
}
}
为了大家理解的更快,我这里再将这个Sku重写一下,重新定义一个Sku的类。这里我简化了所有的属性,我只定义一个skuName和一个skuPrice,接着呢,我为这个单价创建一个getSkuPrice方法用来获取单价,这个就是我定义的Sku的类。
接着我们来学习这四种方法运用如何使用。
我们来建立一个测试,假设这里有一个skuList,这个呢就是我们最基础的数据,接下来如果我们想对skuList中的数据进行排序,比如说我们通过他的单价进行排序,我们应该这样写它的Lambda表达式:
skuList.sort((sku1, sku2) ->
sku1.getSkuPrice() - sku2.getSkuPrice());
我通过sort里面它接收的是一个Comparator<T>接口。
package java.util;
import java.util.function.UnaryOperator;
/**
* An ordered collection (also known as a <i>sequence</i>). The user of this
* interface has precise control over where in the list each element is
* inserted. The user can access elements by their integer index (position in
* the list), and search for elements in the list.<p>
*
* Unlike sets, lists typically allow duplicate elements. More formally,
* lists typically allow pairs of elements <tt>e1</tt> and <tt>e2</tt>
* such that <tt>e1.equals(e2)</tt>, and they typically allow multiple
* null elements if they allow null elements at all. It is not inconceivable
* that someone might wish to implement a list that prohibits duplicates, by
* throwing runtime exceptions when the user attempts to insert them, but we
* expect this usage to be rare.<p>
*
* The <tt>List</tt> interface places additional stipulations, beyond those
* specified in the <tt>Collection</tt> interface, on the contracts of the
* <tt>iterator</tt>, <tt>add</tt>, <tt>remove</tt>, <tt>equals</tt>, and
* <tt>hashCode</tt> methods. Declarations for other inherited methods are
* also included here for convenience.<p>
*
* The <tt>List</tt> interface provides four methods for positional (indexed)
* access to list elements. Lists (like Java arrays) are zero based. Note
* that these operations may execute in time proportional to the index value
* for some implementations (the <tt>LinkedList</tt> class, for
* example). Thus, iterating over the elements in a list is typically
* preferable to indexing through it if the caller does not know the
* implementation.<p>
*
* The <tt>List</tt> interface provides a special iterator, called a
* <tt>ListIterator</tt>, that allows element insertion and replacement, and
* bidirectional access in addition to the normal operations that the
* <tt>Iterator</tt> interface provides. A method is provided to obtain a
* list iterator that starts at a specified position in the list.<p>
*
* The <tt>List</tt> interface provides two methods to search for a specified
* object. From a performance standpoint, these methods should be used with
* caution. In many implementations they will perform costly linear
* searches.<p>
*
* The <tt>List</tt> interface provides two methods to efficiently insert and
* remove multiple elements at an arbitrary point in the list.<p>
*
* Note: While it is permissible for lists to contain themselves as elements,
* extreme caution is advised: the <tt>equals</tt> and <tt>hashCode</tt>
* methods are no longer well defined on such a list.
*
* <p>Some list implementations have restrictions on the elements that
* they may contain. For example, some implementations prohibit null elements,
* and some have restrictions on the types of their elements. Attempting to
* add an ineligible element throws an unchecked exception, typically
* <tt>NullPointerException</tt> or <tt>ClassCastException</tt>. Attempting
* to query the presence of an ineligible element may throw an exception,
* or it may simply return false; some implementations will exhibit the former
* behavior and some will exhibit the latter. More generally, attempting an
* operation on an ineligible element whose completion would not result in
* the insertion of an ineligible element into the list may throw an
* exception or it may succeed, at the option of the implementation.
* Such exceptions are marked as "optional" in the specification for this
* interface.
*
* <p>This interface is a member of the
* <a href="{@docRoot}/../technotes/guides/collections/index.html">
* Java Collections Framework</a>.
*
* @param <E> the type of elements in this list
*
* @author Josh Bloch
* @author Neal Gafter
* @see Collection
* @see Set
* @see ArrayList
* @see LinkedList
* @see Vector
* @see Arrays#asList(Object[])
* @see Collections#nCopies(int, Object)
* @see Collections#EMPTY_LIST
* @see AbstractList
* @see AbstractSequentialList
* @since 1.2
*/
public interface List<E> extends Collection<E> {
// Query Operations
/**
* Returns the number of elements in this list. If this list contains
* more than <tt>Integer.MAX_VALUE</tt> elements, returns
* <tt>Integer.MAX_VALUE</tt>.
*
* @return the number of elements in this list
*/
int size();
/**
* Returns <tt>true</tt> if this list contains no elements.
*
* @return <tt>true</tt> if this list contains no elements
*/
boolean isEmpty();
/**
* Returns <tt>true</tt> if this list contains the specified element.
* More formally, returns <tt>true</tt> if and only if this list contains
* at least one element <tt>e</tt> such that
* <tt>(o==null ? e==null : o.equals(e))</tt>.
*
* @param o element whose presence in this list is to be tested
* @return <tt>true</tt> if this list contains the specified element
* @throws ClassCastException if the type of the specified element
* is incompatible with this list
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null and this
* list does not permit null elements
* (<a href="Collection.html#optional-restrictions">optional</a>)
*/
boolean contains(Object o);
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* @return an iterator over the elements in this list in proper sequence
*/
Iterator<E> iterator();
/**
* Returns an array containing all of the elements in this list in proper
* sequence (from first to last element).
*
* <p>The returned array will be "safe" in that no references to it are
* maintained by this list. (In other words, this method must
* allocate a new array even if this list is backed by an array).
* The caller is thus free to modify the returned array.
*
* <p>This method acts as bridge between array-based and collection-based
* APIs.
*
* @return an array containing all of the elements in this list in proper
* sequence
* @see Arrays#asList(Object[])
*/
Object[] toArray();
/**
* Returns an array containing all of the elements in this list in
* proper sequence (from first to last element); the runtime type of
* the returned array is that of the specified array. If the list fits
* in the specified array, it is returned therein. Otherwise, a new
* array is allocated with the runtime type of the specified array and
* the size of this list.
*
* <p>If the list fits in the specified array with room to spare (i.e.,
* the array has more elements than the list), the element in the array
* immediately following the end of the list is set to <tt>null</tt>.
* (This is useful in determining the length of the list <i>only</i> if
* the caller knows that the list does not contain any null elements.)
*
* <p>Like the {@link #toArray()} method, this method acts as bridge between
* array-based and collection-based APIs. Further, this method allows
* precise control over the runtime type of the output array, and may,
* under certain circumstances, be used to save allocation costs.
*
* <p>Suppose <tt>x</tt> is a list known to contain only strings.
* The following code can be used to dump the list into a newly
* allocated array of <tt>String</tt>:
*
* <pre>{@code
* String[] y = x.toArray(new String[0]);
* }</pre>
*
* Note that <tt>toArray(new Object[0])</tt> is identical in function to
* <tt>toArray()</tt>.
*
* @param a the array into which the elements of this list are to
* be stored, if it is big enough; otherwise, a new array of the
* same runtime type is allocated for this purpose.
* @return an array containing the elements of this list
* @throws ArrayStoreException if the runtime type of the specified array
* is not a supertype of the runtime type of every element in
* this list
* @throws NullPointerException if the specified array is null
*/
<T> T[] toArray(T[] a);
// Modification Operations
/**
* Appends the specified element to the end of this list (optional
* operation).
*
* <p>Lists that support this operation may place limitations on what
* elements may be added to this list. In particular, some
* lists will refuse to add null elements, and others will impose
* restrictions on the type of elements that may be added. List
* classes should clearly specify in their documentation any restrictions
* on what elements may be added.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
* @throws UnsupportedOperationException if the <tt>add</tt> operation
* is not supported by this list
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this list
* @throws NullPointerException if the specified element is null and this
* list does not permit null elements
* @throws IllegalArgumentException if some property of this element
* prevents it from being added to this list
*/
boolean add(E e);
/**
* Removes the first occurrence of the specified element from this list,
* if it is present (optional operation). If this list does not contain
* the element, it is unchanged. More formally, removes the element with
* the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>
* (if such an element exists). Returns <tt>true</tt> if this list
* contained the specified element (or equivalently, if this list changed
* as a result of the call).
*
* @param o element to be removed from this list, if present
* @return <tt>true</tt> if this list contained the specified element
* @throws ClassCastException if the type of the specified element
* is incompatible with this list
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null and this
* list does not permit null elements
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws UnsupportedOperationException if the <tt>remove</tt> operation
* is not supported by this list
*/
boolean remove(Object o);
// Bulk Modification Operations
/**
* Returns <tt>true</tt> if this list contains all of the elements of the
* specified collection.
*
* @param c collection to be checked for containment in this list
* @return <tt>true</tt> if this list contains all of the elements of the
* specified collection
* @throws ClassCastException if the types of one or more elements
* in the specified collection are incompatible with this
* list
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified collection contains one
* or more null elements and this list does not permit null
* elements
* (<a href="Collection.html#optional-restrictions">optional</a>),
* or if the specified collection is null
* @see #contains(Object)
*/
boolean containsAll(Collection<?> c);
/**
* Appends all of the elements in the specified collection to the end of
* this list, in the order that they are returned by the specified
* collection's iterator (optional operation). The behavior of this
* operation is undefined if the specified collection is modified while
* the operation is in progress. (Note that this will occur if the
* specified collection is this list, and it's nonempty.)
*
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws UnsupportedOperationException if the <tt>addAll</tt> operation
* is not supported by this list
* @throws ClassCastException if the class of an element of the specified
* collection prevents it from being added to this list
* @throws NullPointerException if the specified collection contains one
* or more null elements and this list does not permit null
* elements, or if the specified collection is null
* @throws IllegalArgumentException if some property of an element of the
* specified collection prevents it from being added to this list
* @see #add(Object)
*/
boolean addAll(Collection<? extends E> c);
/**
* Inserts all of the elements in the specified collection into this
* list at the specified position (optional operation). Shifts the
* element currently at that position (if any) and any subsequent
* elements to the right (increases their indices). The new elements
* will appear in this list in the order that they are returned by the
* specified collection's iterator. The behavior of this operation is
* undefined if the specified collection is modified while the
* operation is in progress. (Note that this will occur if the specified
* collection is this list, and it's nonempty.)
*
* @param index index at which to insert the first element from the
* specified collection
* @param c collection containing elements to be added to this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws UnsupportedOperationException if the <tt>addAll</tt> operation
* is not supported by this list
* @throws ClassCastException if the class of an element of the specified
* collection prevents it from being added to this list
* @throws NullPointerException if the specified collection contains one
* or more null elements and this list does not permit null
* elements, or if the specified collection is null
* @throws IllegalArgumentException if some property of an element of the
* specified collection prevents it from being added to this list
* @throws IndexOutOfBoundsException if the index is out of range
* (<tt>index < 0 || index > size()</tt>)
*/
boolean addAll(int index, Collection<? extends E> c);
/**
* Removes from this list all of its elements that are contained in the
* specified collection (optional operation).
*
* @param c collection containing elements to be removed from this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws UnsupportedOperationException if the <tt>removeAll</tt> operation
* is not supported by this list
* @throws ClassCastException if the class of an element of this list
* is incompatible with the specified collection
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if this list contains a null element and the
* specified collection does not permit null elements
* (<a href="Collection.html#optional-restrictions">optional</a>),
* or if the specified collection is null
* @see #remove(Object)
* @see #contains(Object)
*/
boolean removeAll(Collection<?> c);
/**
* Retains only the elements in this list that are contained in the
* specified collection (optional operation). In other words, removes
* from this list all of its elements that are not contained in the
* specified collection.
*
* @param c collection containing elements to be retained in this list
* @return <tt>true</tt> if this list changed as a result of the call
* @throws UnsupportedOperationException if the <tt>retainAll</tt> operation
* is not supported by this list
* @throws ClassCastException if the class of an element of this list
* is incompatible with the specified collection
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if this list contains a null element and the
* specified collection does not permit null elements
* (<a href="Collection.html#optional-restrictions">optional</a>),
* or if the specified collection is null
* @see #remove(Object)
* @see #contains(Object)
*/
boolean retainAll(Collection<?> c);
/**
* Replaces each element of this list with the result of applying the
* operator to that element. Errors or runtime exceptions thrown by
* the operator are relayed to the caller.
*
* @implSpec
* The default implementation is equivalent to, for this {@code list}:
* <pre>{@code
* final ListIterator<E> li = list.listIterator();
* while (li.hasNext()) {
* li.set(operator.apply(li.next()));
* }
* }</pre>
*
* If the list's list-iterator does not support the {@code set} operation
* then an {@code UnsupportedOperationException} will be thrown when
* replacing the first element.
*
* @param operator the operator to apply to each element
* @throws UnsupportedOperationException if this list is unmodifiable.
* Implementations may throw this exception if an element
* cannot be replaced or if, in general, modification is not
* supported
* @throws NullPointerException if the specified operator is null or
* if the operator result is a null value and this list does
* not permit null elements
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @since 1.8
*/
default void replaceAll(UnaryOperator<E> operator) {
Objects.requireNonNull(operator);
final ListIterator<E> li = this.listIterator();
while (li.hasNext()) {
li.set(operator.apply(li.next()));
}
}
/**
* Sorts this list according to the order induced by the specified
* {@link Comparator}.
*
* <p>All elements in this list must be <i>mutually comparable</i> using the
* specified comparator (that is, {@code c.compare(e1, e2)} must not throw
* a {@code ClassCastException} for any elements {@code e1} and {@code e2}
* in the list).
*
* <p>If the specified comparator is {@code null} then all elements in this
* list must implement the {@link Comparable} interface and the elements'
* {@linkplain Comparable natural ordering} should be used.
*
* <p>This list must be modifiable, but need not be resizable.
*
* @implSpec
* The default implementation obtains an array containing all elements in
* this list, sorts the array, and iterates over this list resetting each
* element from the corresponding position in the array. (This avoids the
* n<sup>2</sup> log(n) performance that would result from attempting
* to sort a linked list in place.)
*
* @implNote
* This implementation is a stable, adaptive, iterative mergesort that
* requires far fewer than n lg(n) comparisons when the input array is
* partially sorted, while offering the performance of a traditional
* mergesort when the input array is randomly ordered. If the input array
* is nearly sorted, the implementation requires approximately n
* comparisons. Temporary storage requirements vary from a small constant
* for nearly sorted input arrays to n/2 object references for randomly
* ordered input arrays.
*
* <p>The implementation takes equal advantage of ascending and
* descending order in its input array, and can take advantage of
* ascending and descending order in different parts of the same
* input array. It is well-suited to merging two or more sorted arrays:
* simply concatenate the arrays and sort the resulting array.
*
* <p>The implementation was adapted from Tim Peters's list sort for Python
* (<a href="http://svn.python.org/projects/python/trunk/Objects/listsort.txt">
* TimSort</a>). It uses techniques from Peter McIlroy's "Optimistic
* Sorting and Information Theoretic Complexity", in Proceedings of the
* Fourth Annual ACM-SIAM Symposium on Discrete Algorithms, pp 467-474,
* January 1993.
*
* @param c the {@code Comparator} used to compare list elements.
* A {@code null} value indicates that the elements'
* {@linkplain Comparable natural ordering} should be used
* @throws ClassCastException if the list contains elements that are not
* <i>mutually comparable</i> using the specified comparator
* @throws UnsupportedOperationException if the list's list-iterator does
* not support the {@code set} operation
* @throws IllegalArgumentException
* (<a href="Collection.html#optional-restrictions">optional</a>)
* if the comparator is found to violate the {@link Comparator}
* contract
* @since 1.8
*/
@SuppressWarnings({"unchecked", "rawtypes"})
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray();
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
/**
* Removes all of the elements from this list (optional operation).
* The list will be empty after this call returns.
*
* @throws UnsupportedOperationException if the <tt>clear</tt> operation
* is not supported by this list
*/
void clear();
// Comparison and hashing
/**
* Compares the specified object with this list for equality. Returns
* <tt>true</tt> if and only if the specified object is also a list, both
* lists have the same size, and all corresponding pairs of elements in
* the two lists are <i>equal</i>. (Two elements <tt>e1</tt> and
* <tt>e2</tt> are <i>equal</i> if <tt>(e1==null ? e2==null :
* e1.equals(e2))</tt>.) In other words, two lists are defined to be
* equal if they contain the same elements in the same order. This
* definition ensures that the equals method works properly across
* different implementations of the <tt>List</tt> interface.
*
* @param o the object to be compared for equality with this list
* @return <tt>true</tt> if the specified object is equal to this list
*/
boolean equals(Object o);
/**
* Returns the hash code value for this list. The hash code of a list
* is defined to be the result of the following calculation:
* <pre>{@code
* int hashCode = 1;
* for (E e : list)
* hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
* }</pre>
* This ensures that <tt>list1.equals(list2)</tt> implies that
* <tt>list1.hashCode()==list2.hashCode()</tt> for any two lists,
* <tt>list1</tt> and <tt>list2</tt>, as required by the general
* contract of {@link Object#hashCode}.
*
* @return the hash code value for this list
* @see Object#equals(Object)
* @see #equals(Object)
*/
int hashCode();
// Positional Access Operations
/**
* Returns the element at the specified position in this list.
*
* @param index index of the element to return
* @return the element at the specified position in this list
* @throws IndexOutOfBoundsException if the index is out of range
* (<tt>index < 0 || index >= size()</tt>)
*/
E get(int index);
/**
* Replaces the element at the specified position in this list with the
* specified element (optional operation).
*
* @param index index of the element to replace
* @param element element to be stored at the specified position
* @return the element previously at the specified position
* @throws UnsupportedOperationException if the <tt>set</tt> operation
* is not supported by this list
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this list
* @throws NullPointerException if the specified element is null and
* this list does not permit null elements
* @throws IllegalArgumentException if some property of the specified
* element prevents it from being added to this list
* @throws IndexOutOfBoundsException if the index is out of range
* (<tt>index < 0 || index >= size()</tt>)
*/
E set(int index, E element);
/**
* Inserts the specified element at the specified position in this list
* (optional operation). Shifts the element currently at that position
* (if any) and any subsequent elements to the right (adds one to their
* indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws UnsupportedOperationException if the <tt>add</tt> operation
* is not supported by this list
* @throws ClassCastException if the class of the specified element
* prevents it from being added to this list
* @throws NullPointerException if the specified element is null and
* this list does not permit null elements
* @throws IllegalArgumentException if some property of the specified
* element prevents it from being added to this list
* @throws IndexOutOfBoundsException if the index is out of range
* (<tt>index < 0 || index > size()</tt>)
*/
void add(int index, E element);
/**
* Removes the element at the specified position in this list (optional
* operation). Shifts any subsequent elements to the left (subtracts one
* from their indices). Returns the element that was removed from the
* list.
*
* @param index the index of the element to be removed
* @return the element previously at the specified position
* @throws UnsupportedOperationException if the <tt>remove</tt> operation
* is not supported by this list
* @throws IndexOutOfBoundsException if the index is out of range
* (<tt>index < 0 || index >= size()</tt>)
*/
E remove(int index);
// Search Operations
/**
* Returns the index of the first occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the lowest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*
* @param o element to search for
* @return the index of the first occurrence of the specified element in
* this list, or -1 if this list does not contain the element
* @throws ClassCastException if the type of the specified element
* is incompatible with this list
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null and this
* list does not permit null elements
* (<a href="Collection.html#optional-restrictions">optional</a>)
*/
int indexOf(Object o);
/**
* Returns the index of the last occurrence of the specified element
* in this list, or -1 if this list does not contain the element.
* More formally, returns the highest index <tt>i</tt> such that
* <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>,
* or -1 if there is no such index.
*
* @param o element to search for
* @return the index of the last occurrence of the specified element in
* this list, or -1 if this list does not contain the element
* @throws ClassCastException if the type of the specified element
* is incompatible with this list
* (<a href="Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified element is null and this
* list does not permit null elements
* (<a href="Collection.html#optional-restrictions">optional</a>)
*/
int lastIndexOf(Object o);
// List Iterators
/**
* Returns a list iterator over the elements in this list (in proper
* sequence).
*
* @return a list iterator over the elements in this list (in proper
* sequence)
*/
ListIterator<E> listIterator();
/**
* Returns a list iterator over the elements in this list (in proper
* sequence), starting at the specified position in the list.
* The specified index indicates the first element that would be
* returned by an initial call to {@link ListIterator#next next}.
* An initial call to {@link ListIterator#previous previous} would
* return the element with the specified index minus one.
*
* @param index index of the first element to be returned from the
* list iterator (by a call to {@link ListIterator#next next})
* @return a list iterator over the elements in this list (in proper
* sequence), starting at the specified position in the list
* @throws IndexOutOfBoundsException if the index is out of range
* ({@code index < 0 || index > size()})
*/
ListIterator<E> listIterator(int index);
// View
/**
* Returns a view of the portion of this list between the specified
* <tt>fromIndex</tt>, inclusive, and <tt>toIndex</tt>, exclusive. (If
* <tt>fromIndex</tt> and <tt>toIndex</tt> are equal, the returned list is
* empty.) The returned list is backed by this list, so non-structural
* changes in the returned list are reflected in this list, and vice-versa.
* The returned list supports all of the optional list operations supported
* by this list.<p>
*
* This method eliminates the need for explicit range operations (of
* the sort that commonly exist for arrays). Any operation that expects
* a list can be used as a range operation by passing a subList view
* instead of a whole list. For example, the following idiom
* removes a range of elements from a list:
* <pre>{@code
* list.subList(from, to).clear();
* }</pre>
* Similar idioms may be constructed for <tt>indexOf</tt> and
* <tt>lastIndexOf</tt>, and all of the algorithms in the
* <tt>Collections</tt> class can be applied to a subList.<p>
*
* The semantics of the list returned by this method become undefined if
* the backing list (i.e., this list) is <i>structurally modified</i> in
* any way other than via the returned list. (Structural modifications are
* those that change the size of this list, or otherwise perturb it in such
* a fashion that iterations in progress may yield incorrect results.)
*
* @param fromIndex low endpoint (inclusive) of the subList
* @param toIndex high endpoint (exclusive) of the subList
* @return a view of the specified range within this list
* @throws IndexOutOfBoundsException for an illegal endpoint index value
* (<tt>fromIndex < 0 || toIndex > size ||
* fromIndex > toIndex</tt>)
*/
List<E> subList(int fromIndex, int toIndex);
/**
* Creates a {@link Spliterator} over the elements in this list.
*
* <p>The {@code Spliterator} reports {@link Spliterator#SIZED} and
* {@link Spliterator#ORDERED}. Implementations should document the
* reporting of additional characteristic values.
*
* @implSpec
* The default implementation creates a
* <em><a href="Spliterator.html#binding">late-binding</a></em> spliterator
* from the list's {@code Iterator}. The spliterator inherits the
* <em>fail-fast</em> properties of the list's iterator.
*
* @implNote
* The created {@code Spliterator} additionally reports
* {@link Spliterator#SUBSIZED}.
*
* @return a {@code Spliterator} over the elements in this list
* @since 1.8
*/
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, Spliterator.ORDERED);
}
}
package java.util;
import java.io.Serializable;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.function.ToDoubleFunction;
import java.util.Comparators;
/**
* A comparison function, which imposes a <i>total ordering</i> on some
* collection of objects. Comparators can be passed to a sort method (such
* as {@link Collections#sort(List,Comparator) Collections.sort} or {@link
* Arrays#sort(Object[],Comparator) Arrays.sort}) to allow precise control
* over the sort order. Comparators can also be used to control the order of
* certain data structures (such as {@link SortedSet sorted sets} or {@link
* SortedMap sorted maps}), or to provide an ordering for collections of
* objects that don't have a {@link Comparable natural ordering}.<p>
*
* The ordering imposed by a comparator <tt>c</tt> on a set of elements
* <tt>S</tt> is said to be <i>consistent with equals</i> if and only if
* <tt>c.compare(e1, e2)==0</tt> has the same boolean value as
* <tt>e1.equals(e2)</tt> for every <tt>e1</tt> and <tt>e2</tt> in
* <tt>S</tt>.<p>
*
* Caution should be exercised when using a comparator capable of imposing an
* ordering inconsistent with equals to order a sorted set (or sorted map).
* Suppose a sorted set (or sorted map) with an explicit comparator <tt>c</tt>
* is used with elements (or keys) drawn from a set <tt>S</tt>. If the
* ordering imposed by <tt>c</tt> on <tt>S</tt> is inconsistent with equals,
* the sorted set (or sorted map) will behave "strangely." In particular the
* sorted set (or sorted map) will violate the general contract for set (or
* map), which is defined in terms of <tt>equals</tt>.<p>
*
* For example, suppose one adds two elements {@code a} and {@code b} such that
* {@code (a.equals(b) && c.compare(a, b) != 0)}
* to an empty {@code TreeSet} with comparator {@code c}.
* The second {@code add} operation will return
* true (and the size of the tree set will increase) because {@code a} and
* {@code b} are not equivalent from the tree set's perspective, even though
* this is contrary to the specification of the
* {@link Set#add Set.add} method.<p>
*
* Note: It is generally a good idea for comparators to also implement
* <tt>java.io.Serializable</tt>, as they may be used as ordering methods in
* serializable data structures (like {@link TreeSet}, {@link TreeMap}). In
* order for the data structure to serialize successfully, the comparator (if
* provided) must implement <tt>Serializable</tt>.<p>
*
* For the mathematically inclined, the <i>relation</i> that defines the
* <i>imposed ordering</i> that a given comparator <tt>c</tt> imposes on a
* given set of objects <tt>S</tt> is:<pre>
* {(x, y) such that c.compare(x, y) <= 0}.
* </pre> The <i>quotient</i> for this total order is:<pre>
* {(x, y) such that c.compare(x, y) == 0}.
* </pre>
*
* It follows immediately from the contract for <tt>compare</tt> that the
* quotient is an <i>equivalence relation</i> on <tt>S</tt>, and that the
* imposed ordering is a <i>total order</i> on <tt>S</tt>. When we say that
* the ordering imposed by <tt>c</tt> on <tt>S</tt> is <i>consistent with
* equals</i>, we mean that the quotient for the ordering is the equivalence
* relation defined by the objects' {@link Object#equals(Object)
* equals(Object)} method(s):<pre>
* {(x, y) such that x.equals(y)}. </pre>
*
* <p>Unlike {@code Comparable}, a comparator may optionally permit
* comparison of null arguments, while maintaining the requirements for
* an equivalence relation.
*
* <p>This interface is a member of the
* <a href="{@docRoot}/../technotes/guides/collections/index.html">
* Java Collections Framework</a>.
*
* @param <T> the type of objects that may be compared by this comparator
*
* @author Josh Bloch
* @author Neal Gafter
* @see Comparable
* @see java.io.Serializable
* @since 1.2
*/
@FunctionalInterface
public interface Comparator<T> {
/**
* Compares its two arguments for order. Returns a negative integer,
* zero, or a positive integer as the first argument is less than, equal
* to, or greater than the second.<p>
*
* In the foregoing description, the notation
* <tt>sgn(</tt><i>expression</i><tt>)</tt> designates the mathematical
* <i>signum</i> function, which is defined to return one of <tt>-1</tt>,
* <tt>0</tt>, or <tt>1</tt> according to whether the value of
* <i>expression</i> is negative, zero or positive.<p>
*
* The implementor must ensure that <tt>sgn(compare(x, y)) ==
* -sgn(compare(y, x))</tt> for all <tt>x</tt> and <tt>y</tt>. (This
* implies that <tt>compare(x, y)</tt> must throw an exception if and only
* if <tt>compare(y, x)</tt> throws an exception.)<p>
*
* The implementor must also ensure that the relation is transitive:
* <tt>((compare(x, y)>0) && (compare(y, z)>0))</tt> implies
* <tt>compare(x, z)>0</tt>.<p>
*
* Finally, the implementor must ensure that <tt>compare(x, y)==0</tt>
* implies that <tt>sgn(compare(x, z))==sgn(compare(y, z))</tt> for all
* <tt>z</tt>.<p>
*
* It is generally the case, but <i>not</i> strictly required that
* <tt>(compare(x, y)==0) == (x.equals(y))</tt>. Generally speaking,
* any comparator that violates this condition should clearly indicate
* this fact. The recommended language is "Note: this comparator
* imposes orderings that are inconsistent with equals."
*
* @param o1 the first object to be compared.
* @param o2 the second object to be compared.
* @return a negative integer, zero, or a positive integer as the
* first argument is less than, equal to, or greater than the
* second.
* @throws NullPointerException if an argument is null and this
* comparator does not permit null arguments
* @throws ClassCastException if the arguments' types prevent them from
* being compared by this comparator.
*/
int compare(T o1, T o2);
/**
* Indicates whether some other object is "equal to" this
* comparator. This method must obey the general contract of
* {@link Object#equals(Object)}. Additionally, this method can return
* <tt>true</tt> <i>only</i> if the specified object is also a comparator
* and it imposes the same ordering as this comparator. Thus,
* <code>comp1.equals(comp2)</code> implies that <tt>sgn(comp1.compare(o1,
* o2))==sgn(comp2.compare(o1, o2))</tt> for every object reference
* <tt>o1</tt> and <tt>o2</tt>.<p>
*
* Note that it is <i>always</i> safe <i>not</i> to override
* <tt>Object.equals(Object)</tt>. However, overriding this method may,
* in some cases, improve performance by allowing programs to determine
* that two distinct comparators impose the same order.
*
* @param obj the reference object with which to compare.
* @return <code>true</code> only if the specified object is also
* a comparator and it imposes the same ordering as this
* comparator.
* @see Object#equals(Object)
* @see Object#hashCode()
*/
boolean equals(Object obj);
/**
* Returns a comparator that imposes the reverse ordering of this
* comparator.
*
* @return a comparator that imposes the reverse ordering of this
* comparator.
* @since 1.8
*/
default Comparator<T> reversed() {
return Collections.reverseOrder(this);
}
/**
* Returns a lexicographic-order comparator with another comparator.
* If this {@code Comparator} considers two elements equal, i.e.
* {@code compare(a, b) == 0}, {@code other} is used to determine the order.
*
* <p>The returned comparator is serializable if the specified comparator
* is also serializable.
*
* @apiNote
* For example, to sort a collection of {@code String} based on the length
* and then case-insensitive natural ordering, the comparator can be
* composed using following code,
*
* <pre>{@code
* Comparator<String> cmp = Comparator.comparingInt(String::length)
* .thenComparing(String.CASE_INSENSITIVE_ORDER);
* }</pre>
*
* @param other the other comparator to be used when this comparator
* compares two objects that are equal.
* @return a lexicographic-order comparator composed of this and then the
* other comparator
* @throws NullPointerException if the argument is null.
* @since 1.8
*/
default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
}
/**
* Returns a lexicographic-order comparator with a function that
* extracts a key to be compared with the given {@code Comparator}.
*
* @implSpec This default implementation behaves as if {@code
* thenComparing(comparing(keyExtractor, cmp))}.
*
* @param <U> the type of the sort key
* @param keyExtractor the function used to extract the sort key
* @param keyComparator the {@code Comparator} used to compare the sort key
* @return a lexicographic-order comparator composed of this comparator
* and then comparing on the key extracted by the keyExtractor function
* @throws NullPointerException if either argument is null.
* @see #comparing(Function, Comparator)
* @see #thenComparing(Comparator)
* @since 1.8
*/
default <U> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
return thenComparing(comparing(keyExtractor, keyComparator));
}
/**
* Returns a lexicographic-order comparator with a function that
* extracts a {@code Comparable} sort key.
*
* @implSpec This default implementation behaves as if {@code
* thenComparing(comparing(keyExtractor))}.
*
* @param <U> the type of the {@link Comparable} sort key
* @param keyExtractor the function used to extract the {@link
* Comparable} sort key
* @return a lexicographic-order comparator composed of this and then the
* {@link Comparable} sort key.
* @throws NullPointerException if the argument is null.
* @see #comparing(Function)
* @see #thenComparing(Comparator)
* @since 1.8
*/
default <U extends Comparable<? super U>> Comparator<T> thenComparing(
Function<? super T, ? extends U> keyExtractor)
{
return thenComparing(comparing(keyExtractor));
}
/**
* Returns a lexicographic-order comparator with a function that
* extracts a {@code int} sort key.
*
* @implSpec This default implementation behaves as if {@code
* thenComparing(comparingInt(keyExtractor))}.
*
* @param keyExtractor the function used to extract the integer sort key
* @return a lexicographic-order comparator composed of this and then the
* {@code int} sort key
* @throws NullPointerException if the argument is null.
* @see #comparingInt(ToIntFunction)
* @see #thenComparing(Comparator)
* @since 1.8
*/
default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
return thenComparing(comparingInt(keyExtractor));
}
/**
* Returns a lexicographic-order comparator with a function that
* extracts a {@code long} sort key.
*
* @implSpec This default implementation behaves as if {@code
* thenComparing(comparingLong(keyExtractor))}.
*
* @param keyExtractor the function used to extract the long sort key
* @return a lexicographic-order comparator composed of this and then the
* {@code long} sort key
* @throws NullPointerException if the argument is null.
* @see #comparingLong(ToLongFunction)
* @see #thenComparing(Comparator)
* @since 1.8
*/
default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
return thenComparing(comparingLong(keyExtractor));
}
/**
* Returns a lexicographic-order comparator with a function that
* extracts a {@code double} sort key.
*
* @implSpec This default implementation behaves as if {@code
* thenComparing(comparingDouble(keyExtractor))}.
*
* @param keyExtractor the function used to extract the double sort key
* @return a lexicographic-order comparator composed of this and then the
* {@code double} sort key
* @throws NullPointerException if the argument is null.
* @see #comparingDouble(ToDoubleFunction)
* @see #thenComparing(Comparator)
* @since 1.8
*/
default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
return thenComparing(comparingDouble(keyExtractor));
}
/**
* Returns a comparator that imposes the reverse of the <em>natural
* ordering</em>.
*
* <p>The returned comparator is serializable and throws {@link
* NullPointerException} when comparing {@code null}.
*
* @param <T> the {@link Comparable} type of element to be compared
* @return a comparator that imposes the reverse of the <i>natural
* ordering</i> on {@code Comparable} objects.
* @see Comparable
* @since 1.8
*/
public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
return Collections.reverseOrder();
}
/**
* Returns a comparator that compares {@link Comparable} objects in natural
* order.
*
* <p>The returned comparator is serializable and throws {@link
* NullPointerException} when comparing {@code null}.
*
* @param <T> the {@link Comparable} type of element to be compared
* @return a comparator that imposes the <i>natural ordering</i> on {@code
* Comparable} objects.
* @see Comparable
* @since 1.8
*/
@SuppressWarnings("unchecked")
public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
}
/**
* Returns a null-friendly comparator that considers {@code null} to be
* less than non-null. When both are {@code null}, they are considered
* equal. If both are non-null, the specified {@code Comparator} is used
* to determine the order. If the specified comparator is {@code null},
* then the returned comparator considers all non-null values to be equal.
*
* <p>The returned comparator is serializable if the specified comparator
* is serializable.
*
* @param <T> the type of the elements to be compared
* @param comparator a {@code Comparator} for comparing non-null values
* @return a comparator that considers {@code null} to be less than
* non-null, and compares non-null objects with the supplied
* {@code Comparator}.
* @since 1.8
*/
public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
/**
* Returns a null-friendly comparator that considers {@code null} to be
* greater than non-null. When both are {@code null}, they are considered
* equal. If both are non-null, the specified {@code Comparator} is used
* to determine the order. If the specified comparator is {@code null},
* then the returned comparator considers all non-null values to be equal.
*
* <p>The returned comparator is serializable if the specified comparator
* is serializable.
*
* @param <T> the type of the elements to be compared
* @param comparator a {@code Comparator} for comparing non-null values
* @return a comparator that considers {@code null} to be greater than
* non-null, and compares non-null objects with the supplied
* {@code Comparator}.
* @since 1.8
*/
public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(false, comparator);
}
/**
* Accepts a function that extracts a sort key from a type {@code T}, and
* returns a {@code Comparator<T>} that compares by that sort key using
* the specified {@link Comparator}.
*
* <p>The returned comparator is serializable if the specified function
* and comparator are both serializable.
*
* @apiNote
* For example, to obtain a {@code Comparator} that compares {@code
* Person} objects by their last name ignoring case differences,
*
* <pre>{@code
* Comparator<Person> cmp = Comparator.comparing(
* Person::getLastName,
* String.CASE_INSENSITIVE_ORDER);
* }</pre>
*
* @param <T> the type of element to be compared
* @param <U> the type of the sort key
* @param keyExtractor the function used to extract the sort key
* @param keyComparator the {@code Comparator} used to compare the sort key
* @return a comparator that compares by an extracted key using the
* specified {@code Comparator}
* @throws NullPointerException if either argument is null
* @since 1.8
*/
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
/**
* Accepts a function that extracts a {@link java.lang.Comparable
* Comparable} sort key from a type {@code T}, and returns a {@code
* Comparator<T>} that compares by that sort key.
*
* <p>The returned comparator is serializable if the specified function
* is also serializable.
*
* @apiNote
* For example, to obtain a {@code Comparator} that compares {@code
* Person} objects by their last name,
*
* <pre>{@code
* Comparator<Person> byLastName = Comparator.comparing(Person::getLastName);
* }</pre>
*
* @param <T> the type of element to be compared
* @param <U> the type of the {@code Comparable} sort key
* @param keyExtractor the function used to extract the {@link
* Comparable} sort key
* @return a comparator that compares by an extracted key
* @throws NullPointerException if the argument is null
* @since 1.8
*/
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
/**
* Accepts a function that extracts an {@code int} sort key from a type
* {@code T}, and returns a {@code Comparator<T>} that compares by that
* sort key.
*
* <p>The returned comparator is serializable if the specified function
* is also serializable.
*
* @param <T> the type of element to be compared
* @param keyExtractor the function used to extract the integer sort key
* @return a comparator that compares by an extracted key
* @see #comparing(Function)
* @throws NullPointerException if the argument is null
* @since 1.8
*/
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
/**
* Accepts a function that extracts a {@code long} sort key from a type
* {@code T}, and returns a {@code Comparator<T>} that compares by that
* sort key.
*
* <p>The returned comparator is serializable if the specified function is
* also serializable.
*
* @param <T> the type of element to be compared
* @param keyExtractor the function used to extract the long sort key
* @return a comparator that compares by an extracted key
* @see #comparing(Function)
* @throws NullPointerException if the argument is null
* @since 1.8
*/
public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
}
/**
* Accepts a function that extracts a {@code double} sort key from a type
* {@code T}, and returns a {@code Comparator<T>} that compares by that
* sort key.
*
* <p>The returned comparator is serializable if the specified function
* is also serializable.
*
* @param <T> the type of element to be compared
* @param keyExtractor the function used to extract the double sort key
* @return a comparator that compares by an extracted key
* @see #comparing(Function)
* @throws NullPointerException if the argument is null
* @since 1.8
*/
public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
}
}
这个Comparator接口它是一个函数式接口,它里面是一个int compae(T o1, T o2)方法,它接收两个参数,都是泛型T类型的,返回一个int。我们刚才写的Lambda同样符合这两个要求。
skuList.sort((sku1, sku2) ->
sku1.getSkuPrice() - sku2.getSkuPrice());
首先呢,两个参数是同种类型,接着呢,他们两个相减返回一个int类型,所以它能够作为sort的Lambda表达式来使用。
那现在如果我在Sku里面定义一个静态方法staticComparePrice(Sku sku1, Sku sku2)。
static class Sku {
private String skuName;
private Integer skuPrice;
public Integer getSkuPrice() {
return this.skuPrice;
}
public static int staticComparePrice(Sku sku1, Sku sku2) {
return sku1.getSkuPrice() - sku2.getSkuPrice();
}
}
现在我在Sku这个类里面有定义了一个静态的比较单价的方法,这个方法也接收两个参数,sku1和sku2,同样也返回一个int类型。那它的内部实现是不是和我在下面写的Lambda表达式是完全一样致的?逻辑是一致的,出入参数也是一致的。那我们就可以替换掉Lambda表达式。
skuList.sort((sku1, sku2) ->
sku1.getSkuPrice() - sku2.getSkuPrice());
// 类名::静态方法名
skuList.sort(Sku::staticComparePrice);
然后我们再来看,如果把它写开了会是什么样?
// 展开
skuList.sort((Sku sku1, Sku sku2) -> {
return Sku.staticComparePrice(sku1, sku2);
});
我想上面的形式展开写,还是接收两个参数,然后返回一个int类型,只不过它的比较方法我不再这么写了。
sku1.getSkuPrice() - sku2.getSkuPrice()
而是直接调用Sku它的静态方法,把参数传进去,它就会返回比较结果。
Sku.staticComparePrice(sku1, sku2)
这种写法就是上面的 类名::静态方法名 写法的展开形式。
接着我们再建一个PriceComparator的类。
class PriceComparator {
public int instanceComparePrice(Sku sku1, Sku sku2) {
return sku1.getSkuPrice() - sku2.getSkuPrice();
}
}
PriceComparator priceComparator = new PriceComparator();
// 对象::实例方法名
skuList.sort(priceComparator::instanceComparePrice);
这就是第二种 对象::实例方法名 来完成比较的这个操作。
这种的展开形式:
// 展开
skuList.sort((Sku sku1, Sku sku2) -> {
return priceComparator.instanceComparePrice(sku1, sku2);
});
接着我们再来看第三种。我们将实例方法放到Sku里面。
static class Sku {
private String skuName;
private Integer skuPrice;
public Integer getSkuPrice() {
return this.skuPrice;
}
public static int staticComparePrice(Sku sku1, Sku sku2) {
return sku1.getSkuPrice() - sku2.getSkuPrice();
}
public int instanceComparePrice(Sku sku) {
return this.getSkuPrice() - sku.getSkuPrice();
}
}
我们来看看第三种方法引用怎么写?这里注意区分!!!
// 类名::实例方法名
skuList.sort(Sku::instanceComparePrice);
这里有的同学就要问了,这里在Sku里面定义的实例方法instanceComparePrice,它只接收一个Sku参数,那为什么放到这里接收两个参数的Lambda表达式来用呢?我们还是展开写一下,大家就清楚了。
// 展开
skuList.sort((Sku object, Sku sku) -> {
return object.instanceComparePrice(sku);
});
那大家一眼就能看出来了,我的这个instanceComparePrice实例方法的调用者是我的第一个参数object,也就是第一个Sku,我的第一个Sku调用它自己的实例方法,然后将Sku作为第二个参数传进来,来返回一个int类型,来满足我这个函数式接口的要求。
我们的 类名:: 实例方法 只是一个表示,但是它内部还是会调用 对象.实例方法,然后第二个参数才作为真正的参数传递进来。
这样呢,经过我们的拆解,我们这三种方法引用都可以拆解成我们可以理解的形式,下面的那种展开形式写起来比较啰嗦,所以大家就发明了一种更简单的形式。所以才有这么多奇奇怪怪的,类名、实例名、方法名互相掺和到一起的四种方法引用。
说道四种呢,还有最后一种指向构造方法的形式没讲。这种形式比较好理解。比如我们想对skuList做防空处理。就可以这样写。
// 构造方法
Optional.ofNullable(skuList)
.orElseGet(ArrayList::new);
那其实我们调用的就是指向构造方法的方法引用(ArrayList::new),目的就是创建一个ArrayList对象。
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* 类名称:MethodReferenceTest
* ********************************
* <p>
* 类描述:方法引用测试
*
* @author
* @date 下午10:22
*/
public class MethodReferenceTest {
static class Sku {
private String skuName;
private Integer skuPrice;
public Integer getSkuPrice() {
return this.skuPrice;
}
public static int staticComparePrice(Sku sku1, Sku sku2) {
return sku1.getSkuPrice() - sku2.getSkuPrice();
}
public int instanceComparePrice(Sku sku) {
return this.getSkuPrice() - sku.getSkuPrice();
}
}
class PriceComparator {
public int instanceComparePrice(Sku sku1, Sku sku2) {
return sku1.getSkuPrice() - sku2.getSkuPrice();
}
}
public void test() {
List<Sku> skuList = new ArrayList();
skuList.sort((sku1, sku2) ->
sku1.getSkuPrice() - sku2.getSkuPrice());
// 类名::静态方法名
skuList.sort(Sku::staticComparePrice);
// 展开
skuList.sort((Sku sku1, Sku sku2) -> {
return Sku.staticComparePrice(sku1, sku2);
});
PriceComparator priceComparator = new PriceComparator();
// 对象::实例方法名
skuList.sort(priceComparator::instanceComparePrice);
// 展开
skuList.sort((Sku sku1, Sku sku2) -> {
return priceComparator.instanceComparePrice(sku1, sku2);
});
// 类名::实例方法名
skuList.sort(Sku::instanceComparePrice);
// 展开
skuList.sort((Sku object, Sku sku) -> {
return object.instanceComparePrice(sku);
});
// 构造方法
Optional.ofNullable(skuList)
.orElseGet(ArrayList::new);
}
}
以上就是四种方法引用及其使用场景。
在这里再强调一点:判断一个方法引用的使用场景符不符合要求的时候,只需要判断它们的出参和入参满不满足我们的函数式接口的要求就好了。上面的四种方式我们不管怎么变换,目的都是接收两个入参,返回一个出参,满足sort方法中对函数式接口的要求。