介绍Java 8以后引入的 函数式编程 的各个方面。包括接口函数,Lambda写法,方法引用,组合函数,流式写法等等。
什么是函数式编程?
函数式编程具有以下一些特点:
- 相同的输入,相同的输出,不管执行多少次
- 无状态
- 没有可变量和赋值,没有循环
- 并行处理友好
- 没有竞争,不需要同步
- 只通过返回值通信
Java新的接口
Java8以上版本接口可以这样写
public interface Scalable { // 隐式public的抽象方法 void setScale(double scale); //隐式public静态final字段 double DEFAULT_SCALE = 1.0; //隐式public static boolean isScalable(Object obj) { return obj instanceof Scalable; } // 隐式public default void resetScale() { setScale(DEFAULT_SCALE); } }
接口可以有 静态方法 和默认方法
其中的变量是隐式public和static的,静态方法和默认方法也是隐式public的。
应用到新版 JDK 中的Comparator中:
import java.util.Comparator; public class Interfaces { public static void main(String[] args) { Comparator<String> byName = new Comparator<String>() { public int compare(String a, String b) { return a.compareTo(b); } }; System.out.println(byName.compare("i", "k")); try { System.out.println(byName.compare("s", null)); } catch (NullPointerException e) { System.out.println(e); } // Comparator中的静态方法 Comparator<String> byStringThenNull = Comparator.nullsLast(byName); System.out.println("Then null:"); System.out.println(byStringThenNull.compare("d", "w")); System.out.println(byStringThenNull.compare("g", null)); // Comparator中的默认方法 Comparator<String> nullThenByDecreasingString= byStringThenNull.reversed(); System.out.println("Reversed:"); System.out.println(nullThenByDecreasingString.compare("ww", "hh")); System.out.println(nullThenByDecreasingString.compare("fv", null)); }
函数式接口 function al Interface
函数式接口的特点
- 具有唯一一个抽象方法的接口就是函数式接口
- 可以有多个静态方法或者默认方法
- 以前版本的JDK就有这样的接口(Runnable,Comparable,Comparator,Iterable)
- 函数式接口是用来被无状态的类实现
- 函数式接口在流式( Stream s)写法中扮演重要角色
- @FunctionnalInterface注释标志 纯粹 的函数式接口
- 在Java9中有40多个纯函数式接口,位于包java.util.function中
JDK中的Consumer函数接口:
package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Consumer<T> {
void accept (T var1);
default Consumer<T> andThen(Consumer<? super T> var1) {
Objects.requireNonNull(var1);
return (var2) -> {
this.accept(var2);
var1.accept(var2);
};
}
}
Lambda
- Lambda是一种实现函数式接口的简写方式,不再使用匿名类的方式
- 写法简洁
- 不会产生额外的类文件,匿名类会编译成额外的class文件
- 只能在函数式接口中使用
示例 :
import java.util.Comparator; import java.util.function.Consumer; public class Lambda { public static void main(String[] args) { // 老式写法 Comparator<String> by length = new Comparator<String>() { public int compare(String a, String b) { return Integer .compare(a.length(), b.length()); } }; // lambda表达式 Comparator<String> byLengthLambda1 = (String a, String b) -> { return Integer.compare(a.length(), b.length()); }; // 移除参数类型 Comparator<String> byLengthLambda2 = (a, b) -> { return Integer.compare(a.length(), b.length()); }; // 只有单行,所以可以移除花括号和return Comparator<String> byLengthLambda3 = (a, b) -> Integer.compare(a.length(), b.length()); //没有参数的写法 Runnable r = ()->System.out.println("A compact runnable."); Thread t= new Thread(r); //不需要显示定义runnable Thread t1=new Thread(()->System.out.println("A compact runnable.")); //只有一个参数,可以去掉小括号 Consumer<String> lengthPrinter = s -> System.out.println(s.length()); // 编译器足够聪明,虽然循环5次,但只生成一个Consumer实例 for (int i=0; i<5; i++) { Consumer<String> myPrinter = msg -> System.out.println("Consuming " + msg); myPrinter.accept(myPrinter.toString()); } } }
方法引用
- Java8以后提供一种方法引用而不是执行的机制
- 像C/C++中的函数指针
- 比反射机制更高效
示例:
import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; public class MethodReferences2 { interface ThreadSupplier { Thread giveMeAThread(); } @SuppressWarnings("unused") public static void main(String[] args) { // 静态方法引用 Supplier s1 = Thread::currentThread; ThreadSupplier ts = Thread::currentThread; // 实例方法引用 Employee frank = new Employee("Frank", 3000); Supplier<Integer> s2 = frank::getSalary; System.out.println(s2.get()); Consumer<String> c1 = System.out::println; // 实例方法,但不用指定实例 Function<Employee, Integer> f1 = Employee::getSalary; Integer frankSalary = f1.apply(frank); } }
组合函数
import java.io.FileNotFoundException; import java.io.PrintWriter; import java.util.function.Consumer; import java.util.function.Function; public class ComposingFunctions { public static void main(String[] args) throws FileNotFoundException { PrintWriter writer = new PrintWriter("test.txt"); Consumer<String> logger = writer::println; Consumer<String> screener = System.out::println; Consumer<String> both = logger.andThen(screener); both.accept("Test composing functions."); writer.close(); Employee john=new Employee("John", 5000); Function<Employee, String> getName = Employee::getName; Function<String,Character> getFirstLetter =name->name.charAt(0); //andThen是从左到右 Function<Employee,Character> bothFunction1=getName.andThen(getFirstLetter); bothFunction1.apply(john); //compose是从右到左 Function<Employee,Character> botFounction2=getFirstLetter.compose(getName); botFounction2.apply(john); } }
Streams
Java中的流有以下特性:
- 流用来处理序列数据
- 内置迭代器,对所有数据应用相同的操作进行处理
- 接口java.util.Stream<T> //有30多个实例方法,7个静态方法
- Stream可以是有序或无序的
- Stream可以是串行处理或并行处理
- Stream只能遍历一次
创建stream的几种方法
- 静态方法Stream.of(1,2,4);
- 通过数组创建Arrays.stream(names)
- 从集合collection创建stream
- 通过计算获得stream Stream.generate() Stream.iterate()
Stream的操作
- 过滤
- 基于内容 filter,takeWhile,dropWhile
- 数量过滤 limit
- 唯一性过滤 distinct
import java.util.Random; import java.util.stream.Stream; public class FilterStream { public static void main(String[] args) { //打印10个大于0的随机整数 final Random random = new Random(); Stream<Integer> randoms = Stream.generate(random::nextInt); randoms.filter(n->n>0) .distinct() .limit(10) .forEach(System.out::println); } }
- 变换
- 变换类型
- 排序
- 不排序
import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.stream.Stream; public class TransformStream { public static void main(String[] args) { //按工资降序打印10个雇员的名字 List<Employee> listEmployee = new ArrayList<>(); listEmployee.add(new Employee("Mike", 6000)); listEmployee.add(new Employee("John", 7000)); Stream<Employee> emps = listEmployee.stream(); emps.sorted(Comparator.comparingInt(Employee::getSalary).reversed()) .limit(10) .map(Employee::getName) .forEachOrdered(System.out::println); } }
- 终止操作
- 提取单个元素 findAny findFirst min max
- 所有数据放到一个数组 toArray
- 计数 count
- 条件判断 allMatch anyMath noneMatch
- 针对每调数据执行别的操作
原始类型的Stream
List<Employee> listEmployee = new ArrayList<>(); listEmployee.add(new Employee("Mike", 6000)); listEmployee.add(new Employee("John", 7000)); Stream<Employee> emps = listEmployee.stream(); OptionalDouble avgSalary = emps.mapToInt(Employee::getSalary).average();
并行处理
需要避免有状态对象,否则使用并行处理有可能出错或者速度很慢
import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; public class ParallelDemo { public static void main(String[] args) { List<Employee> listEmployee = new ArrayList<>(); listEmployee.add(new Employee("Mike", 6000)); listEmployee.add(new Employee("John", 7000)); listEmployee.add(new Employee("Alice", 7000)); listEmployee.add(new Employee("Bob", 7000)); listEmployee.add(new Employee("Dick", 7000)); Stream<Employee> emps = listEmployee.stream(); emps.parallel().map(Employee::getName).forEach(System.out::println); } }
函数式的思考方式
使用不变对象 :
- 使用final field
- 所有reference fields都指向不变对象,比如string,integer
- 当需要更新对象的时候,不是更新,而是新建一个对象
使用无状态函数
- 避免修改实例属性
- 避免修改静态属性
- 避免修改参数状态
- 同样的参数返回的结果总是一样