预备知识:
函数式编程,就是充分利用已经写好的函数,重点在于如何用这些函数实现功能
传统的命令式编程,就是要思考如何用一行行的代码实现功能
可以这样理解,传统的命令式编程就是在用代码实现函数功能,函数式编程是直接调用函数实现功能。
例子:命令式编程:通过for循环进行累加求和,这样就是去实现了求和的逻辑
函数式编程:直接通过excel的求和工具一步到位,不用关注求和的细节
函数式编程首先要了解lambda表达式,可以让代码更加简短。
Lambda表达式
实例:
匿名内部类省去了创建类的过程,而lambda表达式可以使这一过程更加简洁
public static void main(String args[]) { new Thread(){ @Override public void run() { System.out.pringln("OK"); } }.start(); 或 new Thread(new Runnable(){ @Override public void run() { System.out.println("ok"); } }).start(); } 使用lambda表达式: public static void main(String args[]) { new Thread(()-> System.out.println("ok")).start(); }
lambda本质是一个函数接口,用于快捷的创建一个接口实例并实现接口方法
表达式为:
()-> 方法
等价于
public class A implements B(){
@Override
public void kk() {
。。。;
}
}
有几个注意事项:
①lambda仅适用于只有一个需要重写方法的接口,这种接口也可称为函数式接口,如果有多个方法需要重写,就不能明确lambda表达式所编写的方法是重写的哪一个方法,只有一个需要重写方法的概念用下面的例子解释
下例中,虽然接口A有两个方法,但是仅有一个write方法需要重写,read为default方法不需要重写,所以A接口仍然可以用lambda表达式实现。
public class test {
public static void main(String[] args) {
A a = ()-> System.out.println("write");
a.read();
a.write();
}
}
//对于函数式接口,可以加上@FunctionalInterface 加以标注,以表示其为一个函数式接口
@FunctionalInterface
interface A {
public void write();
default void read(){
System.out.println("read");
}
}
②lambda表达式实际返回的是实现接口的实例,所以必须用函数接口接收lambda表达式返回值
上例中,如果使用
Object a = ()-> System.out.println("write");
则会报错,因为Object并不是一个函数接口
可以强转类型解决
Object a = (A)()-> System.out.println("write");
以实现Runnable接口为例:
public class test {
public static void main(String[] args) {
Object r = (Runnable) ()-> System.out.println("run");
Thread t = new Thread((Runnable)r);//传入的r也需要转换为Runnable类型
t.start();
}
}
下面介绍实现方法有参数的情况 该如何编写lambda表达式:
public class test {
public static void main(String[] args) {
//如果参数只有一个可以不写括号
A a = (i, str)-> System.out.println(i + str);
a.write(10,"strstr");
//如果实现的方法体中有多行代码就需要加上{}
A a = (i, str)-> {
System.out.println(i + str);
System.out.println("aa");
};
//方法有返回值,如果代码仅有一行 可以省略return,直接写返回值
B b1 = (i, str)-> i * 2;
System.out.println(b.read(20, "read"));
//方法有返回,且有多行
B b = (i, str)-> {
System.out.println(str);
return 2;
};
b.read(2, "read");
}
}
interface A {
public void write(int i,String str);
}
interface B {
public int read(int i, String str);
}
Jdk1.8的函数接口
使用lambda表达式可以很快捷的创建接口实例,但是对于一个函数接口,我们更关注接口方法实现什么样的功能,关注接口内函数的输入和输出,而不必关注接口叫什么名字,因此可以考虑是否可以不创建接口就得到一个接口的实例!
jdk1.8提供了很多函数接口,不需要定义特定的接口,只需要创建出具备输入输出的函数就可以
public static void main(String[] args) {
//创建了一个接口实例a,所实现的方法是输入一个Integer类型的参数
//输出为一个String类型的参数
Function<Integer, String> a = (i)-> "aaa" + i;
//使用apply方法调用所实现的接口方法
String apply = a.apply(4);
System.out.println(apply);
}
//
Function<T, R> 可以用于创建一个输入为T类型,输出为R类型的函数
Customer<T> 用于创建一个有输入没输出的函数,消费者函数接口
Supplier<T> 用于创建一个有输出没输入的函数,生产者函数接口
参照上表以此类推
方法引用
若Lambda 体
中的功能,已经有方法提供了实现,可以使用方法引用,即Lambda的另外一种表现形式。方法引用所引用的方法的参数列表与返回值类型
,需要与函数式接口中抽象方法的参数列表和返回值类型
保持一致。
① 对象引用::实例方法名
@Test public void testT1() { // 创建一个User类,属性为name和age User user = new User("LCY", 22); // Lambda表达式 Supplier<String> sup1 = () -> user.getName(); System.out.println(sup1.get()); // 方法引用方式 Supplier<Integer> sup2 = user::getAge; System.out.println(sup2.get()); }
②类名::静态方法名
@Test public void testT2() { Comparator<Integer> com1 = (x,y) -> Integer.compare(x, y); System.out.println(com1.compare(1, 2)); Comparator<Integer> com2 = Integer::compare; System.out.println(com2.compare(7, 6)); }
③类名::实例方法名
@Test public void testT3() { BiPredicate<String, String> bp1 = (x,y) -> x.equals(y); System.out.println(bp1.test("abcd", "dcba")); // 参数1是调用equals的,参数2是equals方法的参数 // 因此可以使用类名::方法名 BiPredicate<String, String> bp2 = String::equals; System.out.println(bp2.test("123", "123")); }
类型判断:
package lambda; @FunctionalInterface interface IMath { int add(int x, int y); } @FunctionalInterface interface IMath2 { int sub(int x, int y); } public class TypeDemo { public static void main(String[] args) { // 变量类型定义 IMath lambda = (x, y) -> x + y; // 数组里 IMath[] lambdas = { (x, y) -> x + y }; // 强转 Object lambda2 = (IMath) (x, y) -> x + y; // 通过返回类型 IMath createLambda = createLambda(); TypeDemo demo = new TypeDemo(); // 当有方法重写,存在二义性的时候,使用强转对应的接口解决 demo.test( (IMath2)(x, y) -> x + y); } public void test(IMath math) { } public void test(IMath2 math) { } public static IMath createLambda() { return (x, y) -> x + y; } }
Jdk8 新特性Stream流编程
概念:通过Stream流的方法实现更高级的特性
外部迭代 和 内部迭代
实现数组内数字的累加
外部迭代:
int[] nums = {1, 2, 3};
int sum = 0;
for (int num :nums) {
sum += num;
}
内部迭代:
int sum = IntStream.of(nums).sum();
通过流实现的方法更快捷的实现一些需求
中间操作/终止操作,惰性求值
int sum2 = IntStream.of(nums).map(i-> i * 2).sum();
System.out.println(sum2);
map就是一个中间操作返回一个Stream操作, sum就是一个终止操作,返回一个结果
public static void main(String[] args) {
int[] nums = {1, 4, 3};
IntStream intStream = IntStream.of(nums).map(i -> i * 2);
System.out.println(intStream.sum());
}
返回流的是中间操作, 返回结果的是终止操作
使用Stream操作的步骤:
①创建流
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 从集合创建
list.stream();
list.parallelStream();//并行流
// 从数组创建
Arrays.stream(new int[] { 2, 3, 5 });
// 创建数字流
IntStream.of(1, 2, 3);
IntStream.rangeClosed(1, 10);
// 使用random创建一个无限流
new Random().ints().limit(10);
Random random = new Random();
// 自己产生流
Stream.generate(() -> random.nextInt()).limit(20);
}
②中间操作:
③终止操作
分为短路和非短路操作
短路操作:需要将整个流执行完,返回一个最终结果
短路操作:在执行流的过程中找到符合条件的结果并返回