Java
介绍Java以后引入的 函数式编程 的各个方面。包括接口函数,Lambda写法,方法引用,组合函数,流式写法等等。
什么是函数式编程?
函数式编程具有以下一些特点:
- 相同的输入,相同的输出,不管执行多少次
- 无状态
- 没有可变量和赋值,没有循环
- 并行处理友好
- 没有竞争,不需要同步
- 只通过返回值通信
Java新的接口
Java8以上版本接口可以这样写
public interface Scalable {
// 隐式public的抽象方法
void setScale(double scale);
//隐式public静态final字段
double DEFAULT_SCALE =.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> var) {
Objects.requireNonNull(var);
return (var) -> {
this.accept(var);
var.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> byLengthLambda = (String a, String b) -> {
return Integer.compare(a.length(), b.length());
};
// 移除参数类型
Comparator<String> byLengthLambda = (a, b) -> {
return Integer.compare(a.length(), b.length());
};
// 只有单行,所以可以移除花括号和return
Comparator<String> byLengthLambda = (a, b) -> Integer.compare(a.length(), b.length());
//没有参数的写法
Runnable r = ()->System.out.println("A compact runnable.");
Thread t= new Thread(r);
//不需要显示定义runnable
Thread t=new Thread(()->System.out.println("A compact runnable."));
//只有一个参数,可以去掉小括号
Consumer<String> lengthPrinter = s -> System.out.println(s.length());
// 编译器足够聪明,虽然循环次,但只生成一个Consumer实例
for (int i=; 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 MethodReferences {
interface ThreadSupplier {
Thread giveMeAThread();
}
@SuppressWarnings("unused")
public static void main(String[] args) {
// 静态方法引用
Supplier s = Thread::currentThread;
ThreadSupplier ts = Thread::currentThread;
// 实例方法引用
Employee frank = new Employee("Frank",);
Supplier<Integer> s = frank::getSalary;
System.out.println(s.get());
Consumer<String> c = System.out::println;
// 实例方法,但不用指定实例
Function<Employee, Integer> f = Employee::getSalary;
Integer frankSalary = f.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",);
Function<Employee, String> getName = Employee::getName;
Function<String,Character> getFirstLetter =name->name.charAt();
//andThen是从左到右
Function<Employee,Character> bothFunction=getName.andThen(getFirstLetter);
bothFunction.apply(john);
//compose是从右到左
Function<Employee,Character> botFounction=getFirstLetter.compose(getName);
botFounction.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) {
//打印个大于0的随机整数
final Random random = new Random();
Stream<Integer> randoms = Stream.generate(random::nextInt);
randoms.filter(n->n>)
.distinct()
.limit()
.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) {
//按工资降序打印个雇员的名字
List<Employee> listEmployee = new ArrayList<>();
listEmployee.add(new Employee("Mike",));
listEmployee.add(new Employee("John",));
Stream<Employee> emps = listEmployee.stream();
emps.sorted(Comparator.comparingInt(Employee::getSalary).reversed())
.limit()
.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",));
listEmployee.add(new Employee("John",));
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",));
listEmployee.add(new Employee("John",));
listEmployee.add(new Employee("Alice",));
listEmployee.add(new Employee("Bob",));
listEmployee.add(new Employee("Dick",));
Stream<Employee> emps = listEmployee.stream();
emps.parallel().map(Employee::getName).forEach(System.out::println);
}
}
函数式的思考方式
使用不变对象 :
- 使用final field
- 所有reference fields都指向不变对象,比如string,integer
- 当需要更新对象的时候,不是更新,而是新建一个对象
使用无状态函数
- 避免修改实例属性
- 避免修改静态属性
- 避免修改参数状态
- 同样的参数返回的结果总是一样