Lambda、函数式编程、Stream流、方法引用
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参,则小括号可以省略;
- 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
1.使用Lambda标准格式(无参无返回)
public interface Cook {
public abstract void makeFood();
}
public static void main(String[] args) {
//匿名内部类
invokeCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭了");
}
});
//lambda表达式
invokeCook(() -> {
System.out.println("吃饭了");
});
//改版后
invokeCook(()->System.out.println("吃饭了"));
}
public static void invokeCook(Cook cook) {
cook.makeFood();
}
2.使用Lambda标准格式(有参有返回)
public interface Calculator {
public abstract int calc(int a,int b);
}
public static void main(String[] args) {
invokeCalc(120, 130, new Calculator() {
@Override
public int calc(int a, int b) {
return a+b;
}
});
invokeCalc(120,130,(int a,int b)->{
return a+b;
});
//改良
invokeCalc(120,130,( a, b)-> a+b);
}
private static void invokeCalc(int a,int b,Calculator calculator){
int sum= calculator.calc(a,b);
System.out.println("结果是:"+sum);
}
1.使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
2.使用Lambda必须具有上下文推断
2.自定义函数式接口
1.@FunctionalInterface注解
@FunctionalInterface
public interface MyFunctionalInterface {
void myMethod();
}
2.自定义函数式接口
@FunctionalInterface
public interface FunctionTest {
void method();
}
public class FunctionTestImpl implements FunctionTest{
@Override
public void method() {
}
}
public static void show(FunctionTest myInter){
myInter.method();
}
public static void main(String[] args) {
show(new FunctionTestImpl());
show(new FunctionTest() {
@Override
public void method() {
System.out.println("使用匿名内部类重写接口中的抽象方法!");
}
});
//函数式接口
show(()->{
System.out.println("使用Lambda表达式");
});
//升级
show(()-> System.out.println("使用Lambda表达式"));
}
3.函数式编程
1.Lambda的延迟执行
只有当级别满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
System.out.println(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
String msgC = "Java";
log(2, () -> {
System.out.println("Lambda执行!");
return msgA + msgB + msgC;
});
}
2.使用Lambda作为参数和返回值
//参数
public static void start(Runnable run){
//开启线程
new Thread(run).start();
}
public static void main(String[] args) {
start(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 线程开启了!");
}
});
//lambda优化
start(()->System.out.println(Thread.currentThread().getName()+" Lambda线程开启了!"));
}
//返回值
private static Comparator<String> newComparator() {
return (a, b) -> b.length() - a.length();
}
public static void main(String[] args) {
String[] array = { "abc", "ab", "abcd" };
System.out.println(Arrays.toString(array));
Arrays.sort(array, newComparator());
System.out.println(Arrays.toString(array));
}
4.常用函数式接口
1.Supplier接口
java.util.function.Supplier<T>
接口仅包含一个无参的方法:T get()
。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
求最大值
public class MaxSupplier {
public static int getMax(Supplier<Integer> sup){
return sup.get();
}
public static void main(String[] args) {
int[] arr = {100,15,45,86,45};
int max2 = getMax(()->{
int max = arr[0];
for (int i : arr) {
if(i> max){
max = i;
}
}
return max;
});
System.out.println("max2 = " + max2);
}
}
2.Consumer接口
java.util.function.Consumer<T>
接口则正好与Supplier接口相反,它不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。
抽象方法:accept
默认方法:andThen
public static void con(String[] str , Consumer<String> con01,Consumer<String> con02){
//con01.accept(str);
//con02.accept(str);
for (String s : str) {
con01.andThen(con02).accept(s);
}
}
public static void main(String[] args) {
String[] str = {"张三,男","李四,女","王五,男"};
con(str,
s1-> System.out.print(s1.split(",")[0]),
s2-> System.out.println(s2.split(",")[1])
);
}
3.Predicate接口
有时候我们需要对某种类型的数据进行判断,从而得到一个boolean值结果。这时可以使用java.util.function.Predicate<T>
接口。
抽象方法:test
Predicate接口中包含一个抽象方法:
boolean test(T t)`。用于条件判断的场景:
默认方法:and与、or或、negate非
筛选姓名长度为4且为女
public static List<String> method(String[] arrat, Predicate<String> pre01, Predicate<String> pre02) {
ArrayList<String> list = new ArrayList<>();
for (String s : arrat) {
boolean bb = pre01.and(pre02).test(s);
if(bb){
list.add(s);
}
}
return list;
}
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男", "赵丽颖,女" };
List<String> list = method(array,
s1->s1.split(",")[1].equals("女"),
s2->s2.split(",")[0].length()==4);
System.out.println("list = " + list);
}
4.function接口
java.util.function.Function<T,R>
接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。
抽象方法:apply
默认方法:andThen
String str = “夏龙,15”;
- 将字符串截取数字年龄部分,得到字符串;
- 将上一步的字符串转换成为int类型的数字;
- 将上一步的int数字累加100,得到结果int数字。
public static int get(String str, Function<String,String> fun01,Function<String,Integer> fun02,Function<Integer,Integer> fun03){
int integer = fun01.andThen(fun02).andThen(fun03).apply(str);
return integer;
}
public static void main(String[] args) {
String str ="夏龙,15";
int i = get(str,str1->str1.split(",")[1],str2->Integer.parseInt(str2),str3->str3+100);
System.out.println("i = " + i);
}
5.Stream流
java.util.stream.Stream<T>
是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)
获取一个流非常简单,有以下几种常用的方式:
- 所有的
Collection
集合都可以通过stream
默认方法获取流; Stream
接口的静态方法of
可以获取数组对应的流。
1.逐一处理:forEach
2.过滤:filter
3.映射:map
4.统计个数:count
5.取用前几个:limit
6.跳过前几个:skip
7.组合:concat
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("张无忌");
list.add("周芷若");
list.add("赵敏");
list.add("张强");
list.add("张三丰");
//1filter 2forEach
list.stream().filter(s->s.startsWith("张"))
.filter(s->s.length()==3)
.forEach(s-> System.out.print(s));//张无忌 张三丰
//3map
Stream<String> stream2 = Stream.of("1", "2", "3", "4");
Stream<Integer> stream1 = stream2.map( s-> Integer.parseInt(s));
stream1.forEach(i-> System.out.print(i));//1 2 3 4
//4count
Stream<String> stream = Stream.of("张", "2", "3", "4");
Stream<String> z = stream.filter(s->s.startsWith("张"));
System.out.println(z.count());//1
//5limit
Stream<String> limit = stream.limit(3);
limit.forEach(s-> System.out.print(s));//张23
//6skip
Stream<String> skip = stream.skip(2);
skip.forEach(s-> System.out.print(s));//3 4
//7concat
String[] arr ={"4","5","6"};
Stream<String> arr1 = Stream.of(arr);
Stream<String> concat = Stream.concat(arr1, stream);
concat.forEach(s-> System.out.print(s));//456张234
}
练习:
public static void main(String[] args) {
//第一支队伍
ArrayList<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("石破天");
one.add("石中玉");
one.add("老子");
one.add("庄子");
one.add("洪七公");
//1. 第一个队伍只要名字为3个字的成员姓名;存储到一个新集合中。
//2. 第一个队伍筛选之后只要前3个人;存储到一个新集合中。
Stream<String> streamOne = one.stream().filter(s -> s.length() == 3).limit(3);
//第二支队伍
ArrayList<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("赵丽颖");
two.add("张三丰");
two.add("尼古拉斯赵四");
two.add("张天爱");
two.add("张二狗");
//3. 第二个队伍只要姓张的成员姓名;存储到一个新集合中。
//4. 第二个队伍筛选之后不要前2个人;存储到一个新集合中。
Stream<String> streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2);
//5. 将两个队伍合并为一个队伍;存储到一个新集合中。
//6. 根据姓名创建Person对象;存储到一个新集合中。
//7. 打印整个队伍的Person对象信息。
Stream.concat(streamOne,streamTwo).map(name->new Person(name)).forEach(s-> System.out.println(s));
}
代码结果:
Person{name='宋远桥'}
Person{name='苏星河'}
Person{name='石破天'}
Person{name='张天爱'}
Person{name='张二狗'}
6.方法引用
双冒号::
为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
1.通过对象名引用成员方法
//函数式接口
@FunctionalInterface
public interface Printable {
void print(String str);
}
//成员方法
public class Method {
public void printUp(String str){
System.out.println(str.toUpperCase());
}
}
//测试
public class MethodTest {
private static void print(Printable lambda){
lambda.print("Hello");
}
public static void main(String[] args) {
Method method = new Method();
//通过对象名引用成员方法
print(method::printUp);//HELLO
//lambda
print(s->method.printUp(s));//HELLO
}
}
- Lambda表达式:
s->method.printUp(s)
- 方法引用:
method::printUp
2.通过类名称引用静态方法
public interface Calcable {
int abs(int num);
}
public class MethodTest02 {
public static int method(int num,Calcable c){
return c.abs(num);
}
public static void main(String[] args) {
int number = method(-10,s-> Math.abs(s));
System.out.println("number = " + number);
int method = method(-10, Math::abs);
System.out.println("method = " + method);
}
}
- Lambda表达式:
n -> Math.abs(n)
- 方法引用:
Math::abs
3.通过super引用成员方法
public interface Greetable {
void greet();
}
public class Fu{
public void hello(){
System.out.println("我是父");
}
}
public class ZiTest extends Fu{
@Override
public void hello() {
System.out.println("我是子");
}
public void method(Greetable g){
g.greet();
}
public void show(){
method(()->{
Fu fu = new Fu();
fu.hello();
});
method(()-> super.hello());
method(super::hello);
}
public static void main(String[] args) {
new ZiTest().show();
}
}
- Lambda表达式:
() -> super.sayHello()
- 方法引用:
super::sayHello
4.通过this引用成员方法
public interface Richable {
void buy();
}
//定义方法
public void buyHouse(){
System.out.println("买东西!");
}
//传递接口
public void marry(Richable r){
r.buy();
}
//定义方法
public void soHouse(){
marry(()->{
this.buyHouse();
});
//使用方法
marry(this::buyHouse);
}
public static void main(String[] args) {
new ThisTest().soHouse();
}
- Lambda表达式:
() -> this.buyHouse()
- 方法引用:
this::buyHouse
5.类的构造器引用
public interface PersonBuilder {
Person builder(String name);
}
//定义方法
public static void method(String name,PersonBuilder pb){
Person builder = pb.builder(name);
System.out.println(builder.getName());
}
public static void main(String[] args) {
//调用
method("张三",(String name)->{
return new Person(name);
});
//使用方法
method("李四",Person::new);
}
- Lambda表达式:
name -> new Person(name)
- 方法引用:
Person::new
6.数组的构造器引用
public interface ArrayBuilder06 {
int [] arr(int length);
}
//定义方法
public static int[] method(int length, ArrayBuilder06 ab) {
return ab.arr(length);
}
public static void main(String[] args) {
int[] method = method(10, (len) -> {
return new int[len];
});
System.out.println(method.length);
//方法
int[] method1 = method(10, int[]::new);
System.out.println(Arrays.toString(method1));
System.out.println(method1.length);
}
- Lambda表达式:
length -> new int[length]
- 方法引用:
int[]::new