java -- 函数式编程

函数式编程

面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是怎么做
有时只是为了做某事情而不得不创建一个对象,而传递一段代码才是我们真正的目的。

Lambda

Lambda是一个匿名函数,可以理解为一段可以传递的代码。
当需要启动一个线程去完成任务时, 通常会通过java.lang.Runnable接口来定义任务内容,并使用java.lang.Thread类来启动该线程
传统写法,代码如下:

public class Demo {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("多线程任务执行!");
            }
        }).start();
    }
}

借助Java 8的全新语法,上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式达到同样的效果:

public class Demo04LambdaRunnable {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("多线程任务执行!")).start(); // 启动线程
    }
}

Lambda的优点 简化匿名内部类的使用,语法更加简单。

前提条件

必须是接口, 接口中有且只有一个抽象方法

有且仅有一个抽象方法的接口,称为函数式接口

格式

Lambda表达式的标准格式为:

() -> {}
() 参数列表,无参数留空
-> 固定写法, 代表指向动作
{} 方法体

省略规则
在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 参数类型可省略
  2. 如果只有一个参数 ()可以省略
  3. 如果方法体只有一句话 return 大括号 分号都可省略, 但必须同时省略
new Thread(() -> System.out.println("省略格式开启线程")).start();

原理

  1. 在匿名方法所在类中新增一个方法,方法体为Lambda表达式中的代码
  2. 运行时形成一个新的类,并实现对应接口
  3. 重写方法的方法体中调用匿名方法所在类中新生成的方法.

函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口
函数式接口,即适用于函数式编程场景的接口。而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambda使用的接口。

从应用层面来讲,Java中的Lambda可以看做是匿名内部类的简化格式,但是二者在原理上不同。

格式

只要确保接口中有且仅有一个抽象方法即可:

修饰符 interface 接口名称 {
    public abstract 返回值类型 方法名称(可选参数信息);
}

由于接口当中抽象方法的public abstract是可以省略的,所以定义一个函数式接口很简单:

public interface MyFunctionalInterface {
    void myMethod();
}

@FunctionalInterface

@FunctionalInterface 该注解可用于一个接口的定义上:

@FunctionalInterface
public interface MyFunctionalInterface {
    void myMethod();
}

一旦使用该注解来定义接口,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。不过,即使不使用该注解,只要满足函数式接口的定义,这仍然是一个函数式接口

常用函数式接口

Supplier接口

java.util.function.Supplier<T>接口,它意味着"供给" , 对应的Lambda表达式需要“对外提供”一个符合泛型类型的对象数据。
抽象方法:
T get() 用来获取一个泛型参数指定类型的对象数据
求数组元素最大值
使用Supplier接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值

public class supplierInterface {
    public static void main(String[] args) {
        int[] arr = {3,24,346,4,13};
        method(() -> {
            Arrays.sort(arr);
            return arr[arr.length - 1];
        });
    }

    public static void method(Supplier<Integer> s) {
        Integer max = s.get();
        System.out.println(max);
    }
}
Consumer接口

java.util.function.Consumer<T> 接口不生产数据,而是消费一个数据,其数据类型由泛型参数决定
抽象方法
void accept(T t),意为消费一个指定泛型的数据
默认方法
default Consumer<T> andThen(Consumer<? super T> after)

public class _4_consumerInterface {
    public static void main(String[] args) {
        method("Hello World", (String s) -> {
            System.out.println(s.toUpperCase());
        });

        method("HEllO WorlD", s -> System.out.println(s.toLowerCase()));

        System.out.println("==========================");
        method("HEllO WorlD", (String s) -> {
            System.out.println(s.toUpperCase());
        }, (String s) -> {
            System.out.println(s.toLowerCase());
        });
        method("HEllO WorlD",
                s -> System.out.println(s.toUpperCase()),
                s -> System.out.println(s.toLowerCase())
        );
    }

    public static void method(String s, Consumer<String> c) {
        c.accept(s);
    }
    public static void method(String s, Consumer<String> c1, Consumer<String> c2) {
//        c1.accept(s);
//        c2.accept(s);
        // andThen c1.accept(s)后执行c2.accept(s) 等同于上面的写法
        c1.andThen(c2).accept(s);
    }
}
Function接口

java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件
抽象方法:
R apply(T t) 根据类型T的参数获取类型R的结果

public class Test {
    public static void main(String[] args) {
        Function<String,Integer> f = new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                return Integer.parseInt(s);
            }
        };

        Integer apply = f.apply("100");
        System.out.println(apply);

        Function<String,Integer> f2 = s -> Integer.parseInt(s);
        System.out.println(f2.apply("200"));
    }
}

默认方法:andThen

Function接口中有一个默认的andThen方法,用来进行组合操作,与Consumer接口相同

public class Test {
    public static void main(String[] args) {
        method5("10", (String s) -> {
            return Integer.parseInt(s);
        }, (Integer i) -> {
            return i * 10;
        });
        method5("100", s -> Integer.parseInt(s), i -> i * 10);
        method5("1000", Integer::parseInt, i -> i * 10);
    }
    private static void method5(String s, Function<String, Integer> f1, Function<Integer, Integer> f2) {
//        Integer i = f1.apply(s);
//        Integer n = f2.apply(i);
        Integer n = f1.andThen(f2).apply(s);
        System.out.println(n);
    }
}

Function的前置条件泛型和后置条件泛型可以相同

Predicate接口

java.util.function.Predicate 判断型接口
抽象方法: boolean test(T t) 返回boolean

public class predicateInterface {
    public static void main(String[] args) {
        method("HelloWorld.java", (String s) -> {
            return s.toLowerCase().endsWith(".java");
        });

        method("Hello.java", s -> s.toLowerCase().endsWith(".java"));

    }
    public static void method(String filename, Predicate<String> p) {
        boolean b = p.test(filename);
        System.out.println(b);
    }
}

默认方法
Predicate<T> and(Predicate<? super T> other) 并且, 底层使用 &&
Predicate<T> or(Predicate<? super T> other) 或者, 底层使用 ||
Predicate<T> negate() 取反, 底层使用 !

public class Test {
    public static void main(String[] args) {
        method("Helloworld" ,s -> s.contains("H"), s -> s.contains("W"));
    }
    private static void method(String str ,Predicate<String> one, Predicate<String> two) {
        boolean b1 = one.test(str);
        boolean b2 = two.test(str);
        System.out.println("字符串符合要求吗:" + (b1 && b2));

        boolean isValid = one.and(two).test(str);
        System.out.println("字符串符合要求吗:" + isValid);
    }
}

public class Test {
    public static void main(String[] args) {
        method("Helloworld" ,s -> s.contains("H"), s -> s.contains("W"));
    }
    private static void method(String str ,Predicate<String> one, Predicate<String> two) {
        boolean b1 = one.test(str);
        boolean b2 = two.test(str);
        System.out.println("字符串符合要求吗:" + (b1 || b2));

        boolean isValid = one.or(two).test(str);
        System.out.println("字符串符合要求吗:" + isValid);
    }
}


public class Test {
    public static void main(String[] args) {
       isLong("aaa", new Predicate<String>() {
           @Override
           public boolean test(String s) {
               return  s.length()<5;
           }
       });
       isLong("bbbaa",s -> s.length()>=5);
    }
    public static void isLong(String s , Predicate<String> p){
        boolean test = p.test(s);
        System.out.println(!test);
        boolean b2 =  p.negate().test(s);
        System.out.println(b2);
    }
}

方法引用

前提

Lambda表达式中只有一句话时 可能使用

格式

符号表示 : ::
符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用
推导与省略 : ** 如果使用Lambda,那么根据“可推导就是可省略**”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导

应用Lambda表达式 , 在accept方法中接收字符串 , 目的就是为了打印显示字符串 , 那么通过Lambda来使用它的代码很简单:

public class DemoPrintSimple {
    private static void printString(Consumer<String> data, String str) {
        data.accept(str);
    }
    public static void main(String[] args) {
      	printString(s -> System.out.println(s), "Hello World");
    }
}

使用方法引用进行简化

public class DemoPrintRef {
    private static void printString(Consumer<String> data, String str) {
        data.accept(str);
    }
    public static void main(String[] args) {
      	printString(System.out::println, "HelloWorld");
    }
}

其他引用

public class _5_functionInterface {
    public static void main(String[] args) {
        method("100", (String s) -> {
            return Integer.parseInt(s);
        });
        method("10", s -> Integer.parseInt(s));
        /*
            类名引用静态方法
                类名::方法名
         */
        method("1000", Integer::parseInt);
        /*
            类名引用构造方法
                类名::new
         */
        method2("张三", Person::new);
        method2("李四", Person::new);
        method2("王五", s -> new Person(s));
        method3(Person::new);
         /*
            数组引用构造方法
                数据类型[]::new
         */
        method4(5,int[]::new);
        method4(3, (Integer i) -> {
            return new int[i];
        });
        method4(1, i -> new int[i]);

    }

    public static void method(String s, Function<String, Integer> f) {
        Integer n = f.apply(s);
        System.out.println(n);
    }
    private static void method2(String s, Function<String, Person> f) {
        Person p = f.apply(s);
        System.out.println(p);
    }
    private static void method3(Supplier<Person> su) {
        Person p = su.get();
        System.out.println(p);
    }
    private static void method4(Integer i, Function<Integer, int[]> f) {
        int[] arr = f.apply(i);
        System.out.println(Arrays.toString(arr));
    }
}
class Person {
    private String name;

    public Person() {}
    public Person(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

热门相关:地球第一剑   天启预报   第一神算:纨绔大小姐   刺客之王   天启预报