函数式编程
函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。
和命令式编程相比,函数式编程强调函数的计算比指令的执行重要。
和声明式编程相比,函数式编程里函数的计算可随时调用。
函数式编程与命令式编程不同
命令式编程的代码由一系列改变全局状态的语句构成;
函数式编程将计算过程抽象成表达式求值。表达式由纯数学函数构成,而这些数学函数是第一类对象且没有副作用。由于没有副作用,函数式编程可以更容易做到线程安全,因此特别适合于并发编程。
函数式编程是可以直接支持并行的模型。
private static void programmatically() {
//命令式编程
//关注计算机执行的步骤
//一步一步告诉计算机怎么做
List nums = new ArrayList(Arrays.asList(1,2,3,4,5,6,7,8,9,10));
//第一步 创建一个存储结果的集合
List results = new ArrayList();
//第二步 遍历集合
for (Integer num : nums) {
//第三步 一个一个判断是否小于7,如果小于7就加入到结果集合中
if(num < 7) {
results.add(num);
}
}
//声明书编程
//以数据结构的形式来表达程序执行的逻辑
//只告诉计算机应该做什么,而不具体指定怎么做。
//select num from nums where num < 7;
//函数式编程
//和声明书编程 编程一样值关注做什么,而不去关心怎么做
//函数式编程最重要的特点是“函数第一位”,即函数可以出现在任何地方,比如你可以把函数作为参数传递给另一个函数,还可以将函数作为返回值
results = nums.stream().filter(num -> num < 7).collect(Collectors.toList());
Predicate predicate = new Predicate() {
@Override
public boolean test(Integer num) {
return num < 7;
}
};
results = nums.stream().filter(predicate).collect(Collectors.toList());
results.forEach(System.out::println);
}
函数式接口
函数式接口只能有一个抽象方法,而不是只能有一个方法。
Java8新增了接口的默认方法,接口也可以包含若干个实例方法。在Java8中,使用default关键字,可以在接口内定义实例方法,这个方法并非抽象方法,而是拥有特定逻辑的具体实例方法。
java8内置的四大核心函数式接口
BiXX类型接口
BiConsumer、BiFunction、BiPrediate 是 Consumer、Function、Predicate 的扩展,可以传入多个参数。
XXYY类型接口
用 XX来表示 int、long、double、boolean,用 YY来表示 Consumer、Function、Predicate、Supplier 。JDK8就内置了此四种基本数据类型的消费,构建,操作,断言以及他们因此而产生的一系列其他接口 。
UnaryOperator与BinaryOperator
UnaryOperator:代表了一个作用于一个类型的操作,并且返回了同类型的结果
BinaryOperator:代表了一个作用于于两个同类型的操作,并且返回了同类型的结果
XXUnaryOperator:代表了一个作用于一个XX数据类型的操作,并且返回了同类型数据的结果
XXBinaryOperator:代表了一个作用于两个XX数据类型的操作,并且返回了同类型数据的结果
ToXXFunction与ToXXBiFunction类型接口
ToXXFunction:接受一个输入参数,返回一个XX类型结果
ToXXBiFunction:接受两个输入参数,返回一个XX类型结果
Consumer消费数据函数式接口
源码如下,有注解FunctionalInterface。提供了一个accept抽象方法(传入一个参数,无返回值),另有一个由default修饰的实列方法andThen,该方法可传入一个Consumer函数,并返回一个Consumer函数(先执行原函数的accept,再执行传入函数的accept)。
@FunctionalInterface
public interface Consumer {
void accept(T t);
default Consumer andThen(Consumer super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
演示
private static void consumerDetail() {
Consumer c = new Consumer() {
@Override
public void accept(String t) {
System.out.println(t);
}
};
c.accept("samir");
}
可以用lambda表达式优化(详细lambda语法在本文中会专门讲解)
private static void consumer() {
/**
* 输入: -> 前面部分
* 函数体 : -> 后面部分
*/
Consumer c = (x) -> {
System.out.println(x);
};
//只有一个输入参数, 可以去掉() , 当函数体中只有一个语句时,可以去掉{}进一步简化
c = x -> System.out.println(x);
//然而这还不是最简的,由于此处只是进行打印,调用了System.out中的println实列方法对输入参数直接进行打印,因此可以简化成以下写法
c = System.out::println;
c.accept("samir");
}
输出
samir
注:default修饰的实列方法,是可以重写的。如下
private static void consumerDetail() {
Consumer c = new Consumer() {
@Override
public void accept(String t) {
System.out.println("C1-"+t);
}
@Override
public Consumer andThen(Consumer super String> after) {
return (String t) -> {
accept(t);
System.out.println("todo");
after.accept(t);
};
}
};
Consumer
输出
注:c.andThen(c2)返回的是一个新的函数(此函数就是c的andThen方法return之后的函数,先执行C的accept,再打印todo,再执行C2的accept)
C1-samir
todo
C2-samir
supplier生产数据函数式接口
源码如下,有注解FunctionalInterface。只有一个抽象方法get(无入参,有返回值)。
@FunctionalInterface
public interface Supplier {
T get();
}
演示
private static void supplierDetail() {
Supplier supplier = new Supplier() {
@Override
public String get() {
return "samir";
}
};
System.out.println(supplier.get());
}
lambda优化
private static void supplier() {
Supplier supplier = () -> "samir";
System.out.println(supplier.get());
}
输出
samir
Predicate判断函数式接口
源码如下,有注解FunctionalInterface。提供一个抽象方法test(一个入参,返回一个boolean),三个default修饰的实例方法,and(与)、negate(非)、or(或),一个静态方法isEqual(返回一个函数,判断是否相等的函数)。
@FunctionalInterface
public interface Predicate {
boolean test(T t);
default Predicate and(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate negate() {
return (t) -> !test(t);
}
default Predicate or(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static Predicate isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
演示
private static void predicateDetail() {
Predicate p = new Predicate() {
@Override
public boolean test(String t) {
return t.equals("samir");
}
};
System.out.println(p.test("samir"));
}
lambda优化
private static void predicate() {
Predicate p = o -> o.equals("samir");
Predicate p2 = o -> o.equals("wu");
//p
System.out.println("test=" + p.test("samir"));
//p和p2都执行,结果and
System.out.println("and=" + p.and(p2).test("samir"));
//p和p2都执行,结果or
System.out.println("or=" + p.or(p2).test("samir"));
//p,结果非
System.out.println("negate=" + p.negate().test("samir"));
Predicate s = Predicate.isEqual("samir");
System.out.println("isEqual=" + s.test("samir"));
}
输出
test=true
and=false
or=true
negate=false
isEqual=false
Function函数式接口
源码如下,有注解FunctionalInterface。提供一个抽象方法apply(一个入参,返回一个参数),两个default修饰的实例方法,compose(先执行传进来的函数,再执行原来的函数)、andThen(先执行原来的函数,再执行传入的函数),一个静态方法identity(返回一个输出跟输入一样的Lambda表达式对象)。
@FunctionalInterface
public interface Function {
R apply(T t);
default Function compose(Function super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default Function andThen(Function super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static Function identity() {
return t -> t;
}
}
演示
private static void functionDetail() {
Function f = new Function() {
@Override
public Object apply(Object t) {
return t + "----";
}
};
System.out.println(f.apply("samir"));
}
lambda优化
private static void function() {
Function f = o -> o + 10;
Function f2 = o -> o * 10;
//f 13 + 10 = 23
System.out.println(f.apply(13));
//先执行f2 13 * 10 = 130 ,然后把结果130传给f2,f2再执行 , 130 + 10 = 140
System.out.println(f.compose(f2).apply(13));
//先执行f 13 + 10 = 23 , 然后把结果23传给f2,f2再执行 , 23 * 10 = 230
System.out.println(f.andThen(f2).apply(13));
//不进行任何处理
System.out.println(Function.identity().apply(13));
}
输出
23
140
230
13
lambda表达式
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像参数一样进行传递,称为行为参数化)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
Lambda重要特征
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
Lambda方法引用
一、是什么?
方法引用是用来直接访问类或者实例已经存在的方法或者构造方法。
二、哪里能用?
当Lambda表达式中只是执行一个方法调用时。
类::静态方法
类::实列方法
对象引用::实例方法名
引用构造器
引用数组
private static void lambda() {
//不需要参数,返回值3
Supplier s = () -> 3;
//接受一个参数,返回其两倍值
Function f = x -> 2 * x;
//接受一个参数,返回其两倍值
IntFunction i = x -> 2 * x;
//接受两个参数,返回其之和
BiFunction b = (x,y) -> x + y;
IntBinaryOperator i2 = (x, y) -> x + y;
BinaryOperator b2 = (x,y) -> x + y;
//如加上{},有返回值,需加return
IntBinaryOperator i3 = (x,y) -> {return x + y;};
//类::静态方法
Function stringToInt1 = x -> Integer.parseInt(x);
Function stringToInt2 = Integer::parseInt;
//类::实列方法
BiPredicate b3 = (x,y) -> x.equals(y);
BiPredicate b4 = String::equals;
//对象引用::实例方法名
Consumer c1 = x -> System.out.println(x);
Consumer c2 = System.out::println;
//引用构造器
Function f1 = n -> new StringBuffer(n);
Function f2 = StringBuffer::new;
StringBuffer sbf = f2.apply(10);
//引用数组
Function f3 = n -> new int[n];
Function f4 = int[]::new;
int[] i4 = f4.apply(10);
//属于什么?
BiPredicate, String> contains1 = (list, element) -> list.contains(element);
BiPredicate, String> contains2 = List::contains;
List strs = new ArrayList<>();
contains2.test(strs, "samir");
}
疑问: (list, element) -> list.contains(element); 这个属于什么呢?
类::静态方法
类::实列方法
对象引用::实例方法名
引用构造器
引用数组
contains是接口List的抽象方法,不属于上面任何一种,那为什么不报错。
我们再来看下面一段代码,点contains进去,这里的contains是类ArrayList的实列方法。
很明显,这里是属于 类::实列方法。也就是上面List::contains;这种写法,其实在调用时,最终还是通过contains的实现方法来的。
ArrayList strs2 = new ArrayList<>();
BiPredicate, String> contains3 = ArrayList::contains;
contains3.test(strs2, "samir");
Stream
java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
什么是Stream
Stream(流)是一个来自数据源的元素队列并支持聚合操作
元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
如上所说,Stream就如同查询sql一样来操作java。
流的创建
演示:
private static void streamCreate() {
//创建空的Stream
Stream empty = Stream.empty();
//通过集合中的stream()和parallelStream()创建
List list = Arrays.asList("a","b","c","d","e");
//串行
Stream stream = list.stream();
//并行
Stream pstream = list.parallelStream();
stream.forEach(System.out::print);
pstream.forEach(System.out::print);
//通过Stream.of创建
Stream s = Stream.of("samir");
Stream s2 = Stream.of("s","a","m","i","r");
s.forEach(System.out::println);
s2.forEach(System.out::println);
//通过Stream.iterate创建 无限有序值 一般通过limit限制
Stream s3 = Stream.iterate(1, x -> x+2).limit(10);
s3.forEach(System.out::println);
//通过Stream.generate创建 无限无序值 一般通过limit限制
Stream s4 = Stream.generate(() -> Math.random()).limit(10);
s4.forEach(System.out::println);
}
输出
abcdecaedbsamir
s
a
m
i
r
1
3
5
7
9
11
13
15
17
19
0.6039120906429292
0.3088897418801203
0.03897174513445234
0.78025515188639
0.7347193387953834
0.27960273810401304
0.09422833272405962
0.8460545152461284
0.31310439370164334
0.8181407076324397
准备数据
private static List users = new ArrayList();
static {
User user = new User(1,"张三",1,18,new BigDecimal(5000));
User user2 = new User(2,"李四",2,22,new BigDecimal(7000));
User user3 = new User(3,"王五",1,20,new BigDecimal(10000));
users.add(user);
users.add(user2);
users.add(user3);
}
filter 方法用于通过设置的条件过滤出元素
private static void filter() {
List nusers = users.stream().filter(user -> user.getGender() == 1).collect(Collectors.toList());
nusers.forEach(System.out::println);
System.out.println();
}
输出
User [id=1, name=张三, gender=1, age=18, salary=5000]
User [id=3, name=王五, gender=1, age=20, salary=10000]
sorted 方法用于对流进行排序
private static void sort() {
//java8之前排序
users.sort(new Comparator() {
@Override
public int compare(User o1, User o2) {
return o2.getAge() - o1.getAge();
}
});
//java8之后排序
List nusers = users.stream().sorted(new Comparator() {
@Override
public int compare(User o1, User o2) {
return o2.getAge() - o1.getAge();
}
}).collect(Collectors.toList());
nusers.forEach(System.out::println);
System.out.println();
//java8 lambda优化
//不改变原集合
List nusers2 = users.stream().sorted((o1,o2) -> o1.getAge() - o2.getAge()).collect(Collectors.toList());
nusers2.forEach(System.out::println);
System.out.println();
//原集合已排序
users.sort((o1,o2) -> o1.getAge() - o2.getAge());
users.forEach(System.out::println);
}
输出
User [id=2, name=李四, gender=2, age=22, salary=7000]
User [id=3, name=王五, gender=1, age=20, salary=10000]
User [id=1, name=张三, gender=1, age=18, salary=5000]
User [id=1, name=张三, gender=1, age=18, salary=5000]
User [id=3, name=王五, gender=1, age=20, salary=10000]
User [id=2, name=李四, gender=2, age=22, salary=7000]
User [id=1, name=张三, gender=1, age=18, salary=5000]
User [id=3, name=王五, gender=1, age=20, salary=10000]
User [id=2, name=李四, gender=2, age=22, salary=7000]
map、mapToXXX 、flatMap、reduce
map 方法用于映射每个元素到对应的结果
mapToXXX 流转换为数值流
flatMap流的扁平化
reduce 归约 中文含义为:减少、缩小;而Stream中的Reduce方法干的正是这样的活:根据一定的规则将Stream中的元素进行计算后返回一个唯一的值。
演示
private static void map() {
//抽取对象属性
List names = users.stream().map(user -> "samir " + user.getName()).distinct().collect(Collectors.toList());
names.forEach(System.out::println);
//流转换为数值流
//求和
int sum = users.stream().mapToInt(user -> user.getAge()).sum();
//最大值
int max = users.stream().mapToInt(user -> user.getAge()).max().getAsInt();
//最小值
int min = users.stream().mapToInt(user -> user.getAge()).min().getAsInt();
System.out.println("sum="+sum);
System.out.println("max="+max);
System.out.println("min="+min);
//flatMap 和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中;
List nnames = users.stream().flatMap(user -> Arrays.stream(user.getName().split(""))).collect(Collectors.toList());
nnames.forEach(System.out::println);
//归约
int sum2 = users.stream().map(user -> user.getAge()).reduce(Integer::sum).get();
int max2 = users.stream().map(user -> user.getAge()).reduce(Integer::max).get();
int min2 = users.stream().map(user -> user.getAge()).reduce(Integer::min).get();
System.out.println("sum2="+sum2);
System.out.println("max2="+max2);
System.out.println("min2="+min2);
BigDecimal salarySum = users.stream().map(user -> user.getSalary()).reduce(BigDecimal::add).get();
System.out.println("salarySum="+salarySum);
}
输出
samir 张三
samir 李四
samir 王五
sum=60
max=22
min=18
张
三
李
四
王
五
sum2=60
max2=22
min2=18
salarySum=22000
collect 负责收集流
演示
private static void collect() {
//List转Map
//k:属性 v:属性
Map umap = users.stream().collect(Collectors.toMap(User::getId, User::getName));
umap.forEach((k,v) -> System.out.println("k="+k + "---v="+v));
//k:属性 v:类
Map amap = users.stream().collect(Collectors.toMap(User::getId, user -> user));
amap.forEach((k,v) -> System.out.println("k="+k + "---v="+v));
//分组
Map> gmap = users.stream().collect(Collectors.groupingBy(User::getGender));
gmap.forEach((k,v) -> System.out.println("k="+k + "---v="+v.toString()));
}
输出
k1=1---v1=张三
k1=2---v1=李四
k1=3---v1=王五
k2=1---v2=User [id=1, name=张三, gender=1, age=18, salary=5000]
k2=2---v2=User [id=2, name=李四, gender=2, age=22, salary=7000]
k2=3---v2=User [id=3, name=王五, gender=1, age=20, salary=10000]
k3=1---v3=[User [id=1, name=张三, gender=1, age=18, salary=5000], User [id=3, name=王五, gender=1, age=20, salary=10000]]
k3=2---v3=[User [id=2, name=李四, gender=2, age=22, salary=7000]]