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标准格式的基础上,使用省略写法的规则为:
- 参数类型可省略
- 如果只有一个参数 ()可以省略
- 如果方法体只有一句话 return 大括号 分号都可省略, 但必须同时省略
new Thread(() -> System.out.println("省略格式开启线程")).start();
原理
- 在匿名方法所在类中新增一个方法,方法体为Lambda表达式中的代码
- 运行时形成一个新的类,并实现对应接口
- 重写方法的方法体中调用匿名方法所在类中新生成的方法.
函数式接口
函数式接口在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 + '\'' +
'}';
}
}
热门相关:地球第一剑 天启预报 第一神算:纨绔大小姐 刺客之王 天启预报