函数式编程
函数式编程是一种编程范式,以简单粗暴的方式来理解,可以把它理解成匿名函数的一种代替,也有人把它叫做箭头函数->
。它将对象的行为,或者说方法进行参数化传递。
演进案例
函数式编程是一种编程思想,用一个案例来描述这种思想是怎么演进的。
有一个商品类Sku
,它包含了商品种类,商品价格等多个属性。有一个购物车的类CartService
,其中cartSkuList
是一个商品的集合,并提供一系列筛选商品的方法。
/**
* 业务硬编码:
* what: 固定商品的种类查找,比如找出购物车中所有电子产品
* how bad: 一个种类一个方法?显然不可取
*
* @param cartSkuList
* @return
*/
public static List filterElectronicsSkus(
List cartSkuList) {
List result = new ArrayList();
for (Sku sku: cartSkuList) {
// 如果商品类型 等于 电子类
if (SkuCategoryEnum.ELECTRONICS.
equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
/**
* 单一维度判断条件:
* what:支持查找不同类型的上篇,根据传入商品类型参数,找出购物车中同种商品类型的商品列表
* how bad:如果还要根据价格进行筛选呢?
*
* @param cartSkuList
* @param category
* @return
*/
public static List filterSkusByCategory(
List cartSkuList, SkuCategoryEnum category) {
List result = new ArrayList();
for (Sku sku: cartSkuList) {
// 如果商品类型 等于 传入商品类型参数
if (category.equals(sku.getSkuCategory())) {
result.add(sku);
}
}
return result;
}
/**
* 多维度判断条件:
* what:支持通过商品种类和价格筛选,支持通过商品类型或总价来过滤商品
* how bad:条件再多呢?能不能再抽象一点
* @param cartSkuList
* @param category
* @param totalPrice
* @param categoryOrPrice - true: 根据商品类型,false: 根据商品总价
* @return
*/
public static List filterSkus(
List cartSkuList, SkuCategoryEnum category,
Double totalPrice, Boolean categoryOrPrice) {
List result = new ArrayList();
for (Sku sku: cartSkuList) {
// 如果根据商品类型判断,sku类型与输入类型比较
// 如果根据商品总价判断,sku总价与输入总价比较
if (
(categoryOrPrice
&& category.equals(sku.getSkuCategory())
||
(!categoryOrPrice
&& sku.getTotalPrice() > totalPrice))) {
result.add(sku);
}
}
return result;
}
public interface SkuPredicate {
/**
* 选择判断标准
* @param sku
* @return
*/
boolean test(Sku sku);
}
/**
* 对Sku的总价是否超出2000作为判断标准
*/
public class SkuTotalPricePredicate implements SkuPredicate {
@Override
public boolean test(Sku sku) {
return sku.getTotalPrice() > 2000;
}
}
@Test
public void filterSkus() {
List cartSkuList = CartService.getCartSkuList();
// 过滤商品总价大于2000的商品
List result = CartService.filterSkus(
cartSkuList, new SkuTotalPricePredicate());
System.out.println(JSON.toJSONString(
result, true));
}
@Test
public void filterSkus() {
List cartSkuList = CartService.getCartSkuList();
// 过滤商品单价大于1000的商品
List result = CartService.filterSkus(
cartSkuList, new SkuPredicate() {
@Override
public boolean test(Sku sku) {
return sku.getSkuPrice() > 1000;
}
});
System.out.println(JSON.toJSONString(
result, true));
}
@Test
public void filterSkus() {
List cartSkuList = CartService.getCartSkuList();
// 过滤商品单价大于1000的商品
List result = CartService.filterSkus(
cartSkuList,
(Sku sku) -> sku.getSkuPrice() > 1000);
System.out.println(JSON.toJSONString(
result, true));
}
(parameters) -> expression # 简单逻辑不需要 {}
(parameters) -> { statement; } # 复杂逻辑添加 {}
() -> System.out.println("Hello World!");
name -> System.out.println("Hello World" + name + "!");
() -> { System.out.println("Hello World!"); System.out.println("Hello World!"); }
BinaryOperator functionAdd = (x, y) -> x + y; Long result = functionAdd.apply(1L, 2L);
BinaryOperator functionAdd = (Long x, Long y) -> x + y; Long result = functionAdd.apply(1L, 2L);
函数式接口代表的一种契约, 一种对某个特定函数类型的契约。 在它出现的地方,实际期望一个符合契约要求的函数。 Lambda表达式不能脱离上下文而存在,它必须要有一个明确的目标类型,而这个目标类型就是某个函数式接口。
Java 8函数式接口functional interface的秘密
具体来说,Java中我们预期被
@FunctionalInterface
标记的接口是一个函数式接口。这里并不是说,没有@FunctionalInterface
的就不是函数式接口,有@FunctionalInterface
就是函数式接口。比如说
Runnable
接口在 Java8 之前就存在,它是一个函数式接口,在 Java8 中才加上@FunctionalInterface
。此外在 Java8 之前就有的函数式接口还有:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
如果我们想要自己定义一个函数式接口,可以像下面这样:
/** * 文件处理函数式接口 */ @FunctionalInterface public interface FileConsumer { /** * 函数式接口抽象方法 * @param fileContent - 文件内容字符串 */ void fileHandler(String fileContent); } /** * 文件服务类 */ public class FileService { /** * 从过url获取本地文件内容,调用函数式接口处理 * @param url * @param fileConsumer */ public void fileHandle(String url, FileConsumer fileConsumer) throws IOException { // 若干操作... // 调用函数式接口方法,将文件内容传递给lambda表达式,实现业务逻辑 fileConsumer.fileHandler(stringBuilder.toString()); } } 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) ); } }
/** * 三种方法引用 */ public class MethodReference { /** * 指向静态方法的方法引用 * * (args) -> ClassName.staticMethod(args); * ClassName::staticMethod; */ public void test1() { Consumer consumer1 = (String number) -> Integer.parseInt(number); Consumer consumer2 = Integer::parseInt; } /** * 指向任意类型实例方法的方法引用 * * (args) -> args.instanceMethod(); * ClassName::instanceMethod; */ public void test2() { Consumer consumer1 = (String str) -> str.length(); Consumer consumer2 = String::length; } /** * 指向现有对象的实例方法的方法引用 * * (args) -> object.instanceMethod(args); * object::instanceMethod; */ public void test3() { StringBuilder stringBuilder = new StringBuilder(); Consumer consumer1 = (String str) -> stringBuilder.append(str); Consumer consumer2 = stringBuilder::append; } }
方法引用是调用特定方法的Lambda表达式的一种快捷写法,可以让你重复使用现有的方法定义,并像Lambda表达式一样传递他们。
方法引用
java.util.function
中定义了几组类型的函数式接口以及针对基本数据类型的子接口。Java定义好的函数式接口
函数式接口
形式五:对参数显示声明
形式四:包含两个参数的方法
形式三:没有三处,逻辑复杂
形式二:只有一个参数
形式一:没有参数
Lambda表达式主要有两种形式:
Java8引入函数式编程风格,可以将其理解为一种匿名函数的代替,作用是将行为参数化传递,或者说将一个方法当成一个参数。
Lambda表达式
Java8之后,为我们提供了Lambda表达式。
SkuPredicate
接口只定义了一个test()
方法,我们可以用匿名内部类:
前面的代码展示了,我们将条件作为入参,让方法能够做到多条件的筛选。**那如果条件更多怎么办?**还有一种方法就是将判断逻辑进一步抽象成一个接口,接口中定义一个方法test()
判断该商品是否符合筛选条件。