1.Lambda表达式
2.函数式接口
3.Stream API
一、Lambda表达式
1.引入
package com.xj.Test;
class Test {
public static void main(String[] args) {
test();
}
private static void test() {
MyInter mi = new MyInter() {
@Override
public void test() {
System.out.println("test方法被调用了");
}
};
mi.test();//test方法被调用了
}
}
interface MyInter{
void test();
}
本着“万物皆对象”的思想,创建一个MyInter接口的内部类对象来指定内容,再调用方法,这种做法是OK的。但是……
我们真的想创建一个匿名内部类对象吗?
不。我们只是为了做这件事情而不得不创建一个对象。而创建对象只是受限于面向对象语法而不得不采取的一种手段方式。
那,有没有更加简单的办法?
如果我们将关注点从“怎么做”回归到“做什么”的本质上,就会发现只要能够更好地达到目的,过程与形式其实并不重要。
2.Lambda语法和使用
Lambda表达式就是对匿名内部类对象的一种格式的简化。
Java8中引入了一个新的操作符“->”,称为箭头运算符,或者Lambda运算符
作用:就是用于分隔前后两部分的:
左边:表示Lambda表达式的参数列表(接口中,定义的抽象方法的参数)
右边:表示的是方法的方法体,Lambda体
语法格式1:没有参数,也没有返回值
() -> System.out.println(“test方法被调用了”);
代码示例
package com.xj.Test;
class Test {
public static void main(String[] args) {
test();
}
private static void test() {
MyInter mi = ()->System.out.println("第一种格式:test方法被调用了");
mi.test();
}
}
interface MyInter{
void test();
}
语法格式2:有一个参数,没有返回值
注:如果只有一个参数,那么小括号可以省略
(x) -> System.out.println(x * x);
代码示例
package com.xj.Test;
class Test {
public static void main(String[] args) {
test();
}
private static void test() {
MyInter mi1 = (x)->System.out.println(x*x);
mi1.test(3);
//如果只有一个参数的时候,小括号可以省略不写
MyInter mi2 = y->System.out.println(y*y);
mi2.test(3);
}
}
interface MyInter{
void test(int x);
}
语法格式3:有多个参数,没有返回值,格式和语法格式和2相同
(x, y) -> System.out.println(x + y);
代码示例
package com.xj.Test;
class Test {
public static void main(String[] args) {
test();
}
private static void test() {
MyInter mi = (m,n)->System.out.println(m+n);
mi.test(1, 2);
}
}
interface MyInter{
void test(int x,int y);
}
语法格式4:接口中需要重写的方法,方法内容有多句,需要给多句加上大括号
(x, y) -> {int result = x + y; return result;}
注:如果Lambda体中语句只有一句,那么大括号也可以省略不写;如果大括号中只有一条语句,并且是return语句,那么return关键字也可以省略不写(如果要省略return关键字,就必须省略大括号)。
例如:(x,y)->x+y
代码示例
package com.xj.Test;
class Test {
public static void main(String[] args) {
test();
}
private static void test() {
MyInter mi1 = (m,n)->{
int sum=m+n;
return sum;
};
int sum = mi1.test(1, 2);
System.out.println("sum="+sum);
MyInter mi2 = (m,n)->{
return m+n;
};
System.out.println(mi2.test(2, 3));
//如果只有一句话,可以省略大括号,如果是return语句,就可以把return关键字省略掉
MyInter mi3 = (m,n)->m+n;
System.out.println(mi3.test(3, 4));
}
}
interface MyInter{
int test(int x,int y);
}
二、函数式接口
1、概述
Lambda表达式使用的前提,就是接口必须是一个函数式接口。
定义:如果在接口中,只有一个抽象方法,那么这个接口就是函数式接口。
格式:使用注解来检查当前接口是否是一个函数式接口。
@FunctionalInterface
如果不是函数式接口,则编译报错。
作用:
想表达的是一个方法的内容,由于方法不在任何类中,所以称为函数。
接口其实想表达的就是一个函数的声明。
将来使用这个接口的实现类对象,来表达一个函数的实现。
Java中不支持将函数作为一个数据,也就不能将这个函数进行各种传递,也就不能作为对象的成员变量存在。
只能在方法外加一层接口的声明,将来可以传递方法所在接口的实现类对象,来间接的传递方法内容。
函数和方法的区别:函数式独立存在的,不存在任何一个类中;方法是存在一个类中。
代码示例
package com.xj.Test;
class Test {
public static void main(String[] args) {
Inter1 i = new Inter1(){
public void test1(){
System.out.println("hello");
}
};
}
// Inter1的对象作为参数
public static void method1(Inter1 i){
i.test1();
}
//Inter1的对象作为返回值
public static Inter1 method2(){
/*
Inter1 i = new Inter1(){
public void test1(){
System.out.println("hello");
}
};
*/
Inter1 i = ()->System.out.println("hello");
return i;
}
}
@FunctionalInterface
interface Inter1{
void test1();
}
/*@FunctionalInterface
interface Inter2{//没有方法 报错
}
@FunctionalInterface
interface Inter3{//有多个方法 报错
void test1();
void test2();
}
*/
2.常用内置函数式接口
Java8中提供了一些常用的函数式接口,在使用类似功能的时候,不需要额外定义接口,直接使用jdk中提供的即可。
函数式接口 | 参数类型 | 返回类型 | 说明 |
Consumer<T> 消费型接口 | T | void | 对类型为T的对象操作, 方法:void accept(T t) |
Supplier<T> 供给型接口 | 无 | T | 返回类型为T的对象, 方法:T get();可用作工厂 |
Function<T, R> 函数型接口 | T | R | 对类型为T的对象操作,并返回结果是R类型的对象。 方法:R apply(T t); |
Predicate<T> 断言型接口 | T | boolean | 判断类型为T的对象是否满足条件,并返回boolean 值。 方法boolean test(T t); |
(1)消费型接口Consumer<T>
抽象方法:void accept(T t);
作用:当某个函数可以接收一个数据,并且处理这个数据,处理完成之后,不需要返回任何数据,这个函数需要当做数据来进行传递,就使用消费型接口。
代码示例
package com.xj.Test;
import java.util.function.Consumer;
class Test {
public static void main(String[] args) {
/*Consumer<String> con = new Consumer<String>() {
public void accept(String str) {
System.out.println(str);
}
};
test(con,800,"买书");*/
Consumer<String> con = (str)->System.out.println(str);
test(con,800,"买书");
}
public static void test(Consumer<String> con,int money,String str) {
System.out.println("花了"+money+"块钱");
con.accept(str);
}
}
(2)方法引用
写一个函数式接口时,方法的实现(lambda体),已经被某个其他的对象实现了,就不需要在Lambda体中,再次调用这个实现,而可以直接使用那个已经定义好的方法。
格式:
函数式接口 名称 = 对象名 :: 方法名称
函数式接口 名称 = 类名 :: 静态方法名
作用:
把已经实现的方法,作为一个数据,作为一个引用,赋值给某个函数式接口的引用。
可以把这个引用当做方法的返回值,也可以作为方法的实际参数进行传递。
本质:
可以把任意一个方法,作为函数式接口的一个实现类对象。
package com.xj.Test;
class Test {
public static void main(String[] args) {
test01();
}
private static void test01() {
/*PrinterString ps = x->System.out.println(x);
ps.print("zxc");*/
PrinterString ps2 = System.out::println;
ps2.print("方法的引用");
}
}
interface PrinterString {
abstract void print(String str);
}
(3)供给型接口Supplier<T>
抽象方法:T get()
作用:
如果需要定义函数,可以生产一个需要的数据,这个函数需要当做数据来进行传递,那么就可以使用供给型接口。
以前我们只能传递数据,现在可以传递生产数据的方式。
代码示例:返回一集合,集合存放30-80之间的随机数
package com.xj.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
class Test {
public static void main(String[] args) {
Random r = new Random();
List<Integer> list = getNum(10,()->r.nextInt(51)+30);
System.out.println(list);
}
private static List<Integer> getNum(int n, Supplier<Integer> sup) {
List<Integer> list = new ArrayList();
for(int i=0;i<n;i++) {
list.add(sup.get());
}
return list;
}
}
(4)Function<T, R>
抽象方法:R apply(T t)
作用:
如果需要定义一个函数,接收一个数据,将数据进行处理,完成之后,还能返回一个结果,就可以使用函数型接口。
以前我们只能传递处理好之后的数据,或者将原始数据传入方法。
现在可以传入原始数据,并且还可以传入处理方式。
提供功能:andThen(Function f):在调用者处理方式之后,再进行参数的处理方式处理。
代码示例
package com.xj.Test;
import java.util.function.Function;
class Test {
public static void main(String[] args) {
//Function<String,Integer> fun = x->Integer.parseInt(x);
Function<String,Integer> fun = Integer::parseInt;
int x = parse("20",fun);
System.out.println(x);
Function<Integer,Integer> fun1 = y-> y*=10;
int result = method(5,fun1);
System.out.println(result);
System.out.println(method01("7",fun,fun1));//70 传入一个String的变量, 把字符串转换成数字,并乘10
}
public static int parse(String str,Function<String,Integer> fun) {
return fun.apply(str);
}
public static int method(int i,Function<Integer,Integer> fun) {
return fun.apply(i);
}
public static int method01(String str,Function<String,Integer> fun1,Function<Integer,Integer> fun2) {
return fun1.andThen(fun2).apply(str);
}
}
(5)断言型接口Predicate<T>
抽象方法:boolean test(T t)
作用:
如果需要定义一个函数,接收一个数据,判断数据是否合法,返回一个boolean结果,就可以使用断言型接口。
以前我们只能传入处理好的数据,到方法中。
将原始数据传入,将过滤的条件也传入。
提供的功能:
default Predicate<T> | and(Predicate<? super T> other) 返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑AND。 | ||
default Predicate<T> | or(Predicate<? super T> other) 返回一个组合的谓词,表示该谓词与另一个谓词的短路逻辑或。 | ||
default Predicate<T> | negate() 返回表示此谓词的逻辑否定的谓词。 | ||
boolean | test(T t) 在给定的参数上评估这个谓词。 |
代码示例
package com.xj.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
class Test {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(10);
list.add(23);
list.add(34);
list.add(35);
list.add(43);
list.add(20);
list.add(50);
list.add(31);
list.add(-30);
list.add(0);
// 过滤list留下偶数
List<Integer> list1 = filterEven(list, (x) -> x % 2 == 0);
System.out.println(list1);
// 过滤list留下正数
List<Integer> list2 = filterZheng(list, (x) -> x > 0);
System.out.println(list2);
// 使用and连接两个Predicate
List<Integer> list3 = filterEvenAndZheng(list, (x) -> x % 2 == 0, (x) -> x > 0);
System.out.println(list3);
//使用or连接两个Predicate
List<Integer> list4 = filterEvenOrZheng(list,(x) -> x % 2==0, (x) -> x > 0);
System.out.println(list4);
//取反negate
List<Integer> list5 = filterEvenNeg(list,(x) -> x % 2 == 0);
System.out.println(list5);
}
// 过滤list留下偶数
public static List<Integer> filterEven(List<Integer> list,Predicate<Integer> pre){
List<Integer> result = new ArrayList<Integer>();
for(Integer x:list) {
if(pre.test(x)) {
result.add(x);
}
}
return result;
}
// 过滤list留下正数
public static List<Integer> filterZheng(List<Integer> list, Predicate<Integer> pre) {
List<Integer> result = new ArrayList<Integer>();
for (Integer x : list) {
if (pre.test(x)) {
result.add(x);
}
}
return result;
}
// 使用and连接两个Predicate
public static List<Integer> filterEvenAndZheng(List<Integer> list, Predicate<Integer> pre1,Predicate<Integer> pre2) {
List<Integer> result = new ArrayList<Integer>();
for (Integer x : list) {
if (pre1.and(pre2).test(x)) {
result.add(x);
}
}
return result;
}
//使用or连接两个Predicate
public static List<Integer> filterEvenOrZheng(List<Integer> list,Predicate<Integer> pre1,Predicate<Integer> pre2){
List<Integer> result = new ArrayList<Integer>();
for(Integer x:list) {
if(pre1.or(pre2).test(x)) {
result.add(x);
}
}
return result;
}
//取反negate
public static List<Integer> filterEvenNeg(List<Integer> list,Predicate<Integer> pre){
List<Integer> result = new ArrayList<>();
for(Integer x:list) {
if(pre.negate().test(x)) {
result.add(x);
}
}
return result;
}
}
(6)其它接口介绍
JDK提供了大量常用的函数式接口以丰富Lambda的典型使用场景,它们主要在java.util.function包中被提供。
函数式接口 | 参数类型 | 返回类型 | 用途 |
BiFunction<T,U,R> | T,U | R | 对类型为T,U参数应用操作,返回R类型的结果。 包含方法为Rapply(Tt,Uu); |
UnaryOperator<T> (Function子接口) | T | T | 对类型为T的对象进行一元运算,并返回T类型的结果。 包含方法为Tapply(Tt); |
BinaryOperator<T> (BiFunction子接口) | T,T | T | 对类型为T的对象进行二元运算,并返回T类型的结果。 包含方法为Tapply(Tt1,Tt2); |
BiConsumer<T,U> | T,U | void | 对类型为T,U参数应用操作。 包含方法为void accept(Tt,Uu) |
ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T> | T | int long double | 分别计算int、long、double、值的函数 |
IntFunction<R> LongFunction<R> DoubleFunction<R> | int long double | R | 参数分别为int、long、double类型的函数 |
三、Stream API
1.概述
在jdk1.8中,提供了一个Stream类型,可以对数据进行过滤。
好处:比不断的自定义循环,要简单很多。
代码示例
package com.xj.Test;
import java.util.ArrayList;
class Test {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("张飞");
names.add("周瑜");
names.add("赵云");
names.add("鬼谷子");
names.add("上官婉儿");
names.add("东皇太一");
names.add("百里守约");
names.add("百里玄策");
test0(names);
test1(names);
}
//不使用stream获取集合中,性百里四个字名字,获取集合之后,还要打印集合的内容
public static void test0(ArrayList<String> names) {
ArrayList<String> result = new ArrayList<>();
for(String name:names) {
if(name.startsWith("百里") && name.length()==4) {
result.add(name);
}
}
for(String name:result) {
System.out.println(name);
}
}
//使用stream获取集合中,性百里四个字名字
private static void test1(ArrayList<String> names) {
names.stream().filter(x->x.startsWith("百里")).filter(x->x.length()==4).forEach(System.out::println);
}
}
2.Stream类型数据的获取
Collection的获取:调用stream()方法即可,返回Stream类型的对象。
Map的获取:不能直接获取Stream类型。
1、keySet().stream()
2、values().stream()
3、entrySet().stream()
数组的获取:Stream中的of方法,Stream.of(数组)
代码示例
package com.xj.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
class Test {
public static void main(String[] args) {
//Collectionn直接调用stream()
Collection col = new ArrayList();
Stream s1 = col.stream();
List list = new ArrayList();
Stream s2 = list.stream();
Set set = new HashSet();
Stream s3 = set.stream();
Map map = new HashMap();
Stream s4 = map.keySet().stream();
Stream s5 = map.values().stream();
Stream s6 = map.entrySet().stream();
String[] strs = {"abc","sa","sdsf","sdfsd"};
Stream<String> s7 = Stream.of(strs);
s7.filter(x->x.length()==3).forEach(System.out::println);
}
}
3.Stream中的常用方法
Stream接口中的常用方法,用于对流中的数据,进行过滤、筛选或者操作。
分类:
1、终结方法:调用完成之后,返回值不再是Stream类型本身,无法继续调用Stream中的方法。
例如:forEach、count
2、延迟方法:调用完成之后,返回值还是Stream类型,可以继续调用Stream中的各种方法。
例如:除了终结方法,全都是延迟方法。
Stream<T> | filter(Predicate<? super T> predicate) 返回由与此给定谓词匹配的此流的元素组成的流。 | ||
Stream<T> | limit(long maxSize) 返回由此流的元素组成的流,截短长度不能超过 maxSize 。 | ||
Stream<T> | skip(long n) 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。 | ||
<R> Stream<R> | map(Function<? super T,? extends R> mapper) 返回由给定函数应用于此流的元素的结果组成的流。 | ||
void | forEach(Consumer<? super T> action) 对此流的每个元素执行操作。 | ||
long | count() 返回此流中的元素数。 |
代码示例
package com.xj.Test;
import java.util.ArrayList;
class Test {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("张飞");
names.add("周瑜");
names.add("赵云");
names.add("鬼谷子");
names.add("上官婉儿");
names.add("东皇太一");
names.add("百里守约");
names.add("百里玄策");
names.add("李白");
//名字长度小于3
//names.stream().filter(x->x.length()<3).forEach(System.out::println);
//名字长度小于3的前三个
//names.stream().filter(x->x.length()<3).limit(3).forEach(System.out::println);
//跳过前三个
//names.stream().skip(3).forEach(System.out::println);
//统计有多少个
//long l = names.stream().map(x->x.length()).count();
//System.out.println(l);
//返回每个名字的长度
names.stream().map(x->x.length()).forEach(System.out::println);
}
}
练习
有两个Arraylist集合,存储队伍中的多个成员姓名,使用Stream方式,对以下步骤进行操作:
第一个队伍只要名字为3个字的成员姓名
第一个队伍只要筛选之后的前三个人
第二个队伍只要姓张的
第二个队伍筛选之后不要前两个人
将两个队伍合并成一个队伍
合并之后的队伍中的所有人的Person(自定义类型)对象,存储到一个ArrayList集合中
队伍1:宫本武藏、宋公明、苏有朋、石头人、时传祥、李耳、庄子、洪七公
队伍2:帕瓦罗蒂、张三疯、赵薇薇、张自忠、孛儿只斤铁木真、张天爱、张翠花
package com.xj.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
class Test {
public static void main(String[] args) {
String str1 = "宫本武藏、宋公明、苏有朋、石头人、时传祥、李耳、庄子、洪七公";
String str2 = "帕瓦罗蒂、张三疯、赵薇薇、张自忠、孛儿只斤铁木真、张天爱、张翠花";
String[] ss1 = str1.split("、");
String[] ss2 = str2.split("、");
List<String> list1 = Arrays.asList(ss1);
List<String> list2 = Arrays.asList(ss2);
Stream<String> s1 = list1.stream().filter(x->x.length()==3).limit(3);
Stream<String> s2 = list2.stream().filter(x->x.startsWith("张")).skip(2);
Stream<String> ss = Stream.concat(s1, s2);
List<Person> personList = new ArrayList<>();
ss.forEach(x->{
Person p = new Person(x);
personList.add(p);
});
System.out.println(personList);
}
}
class Person{
private String name;
public Person() {
super();
}
public Person(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
}