一.函数式编程思想
- 面向对象思想主要是关注对象能完成什么事情,函数式编程思想就像函数式,主要是针对数据操作;
- 代码简洁容易理解,方便于并发编程,不需要过分关注线程安全问题
二.lambda表达式
1.概念
lambda表达式是Java8的一个新特性,从本质上来讲是一个匿名函数,可以使用这个匿名函数实现接口中的方法,并且非常简洁。
通常来讲,使用 lambda表达式 是为了简化接口实现的。关于接口实现,可以有很多种方式来实现。例如设计接口的实现类、使用匿名内部类。但是lambda表达式比这两种方式都简单。可以把lambda看成JDK8中的一个语法糖。
2.Lambda表达式对接口的要求
并不是所有的接口都可以使用lambda表达式来简洁实现的。lambda表达式毕竟只是一个匿名方法。当实现的接口中的方法过多或者过少的时候,lambda表达式都是不适用的。
lambda表达式只能实现函数式接口。即一个接口中,要求实现类必须实现的抽象方法,有且只有一个,这样的接口就是函数式接口。
3.Lambda表达式的语法
(参数)->{方法体}
- 参数部分︰方法的参数列表,要求和实现的接口中的方法参数部分一致,包括参数的数量和类型。
- 方法体部分∶方法的实现部分,如果接口中定义的方法有返回值,则在实现的时候,注意返回值的返回。
- ->:分隔参数部分和方法体部分。
- 因为接口中规定了方法参数类型,所以实际写lambda表达式的时候可以省略 参数类型。如果只有一个参数,可以省略()
- 另外如果方法体里只有一个语句,那么可以省略掉 {},如果该语句是return语句,需要把return也省略掉
省略如下:
4.函数引用
lambda表达式是为了简化接口的实现的。在lambda表达式中,不应该出现比较复杂的逻辑。如果在lambda表达式中出现了过于复杂的逻辑,会对程序的可读性造成非常大的影响。
如果在lambda表达式中需要处理的逻辑比较复杂,一般情况会单独的写一个方法。在lambda表达式中直接引用这个方法即可。或者,在有些情况下,我们需要在lambda表达式中实现的逻辑,在另外一个地方已经写好了。此时我们就不需要再单独写一遍,只需要直接引用这个已经存在的方法即可。
函数引用:引用一个已经存在的方法,使其替代lambda表达式完成接口的实现。
4.1引用一个静态方法
接口依然是上面的MultipleParametersReturn,引用如下
需要注意的是引用的 方法的参数 和 返回值类型 要与 接口 对应
4.2引用一个非静态方法
4.3引用构造方法
Person类
public class Person {private Integer age;private String name;public Person() {}public Person(Integer age) {this.age = age;}public Person(Integer age, String name) {this.age = age;this.name = name;}@Overridepublic String toString() {return "Person{" +"age=" + age +", name='" + name + '\'' +'}';}
}
PersonWithNoParam接口
@FunctionalInterface
public interface PersonWithNoParam {Person getPersonWithNoParam();
}
PersonWithOneParam接口
@FunctionalInterface
public interface PersonWithOneParam {Person getPersonWithOneParam(Integer age);
}
PersonWithDoubleParams接口
@FunctionalInterface
public interface PersonWithDoubleParams {Person getPersonWithDoubleParams(Integer age,String name);
}
Demo
public class Demo1 {public static void main(String[] args) {//lambda表达式实现PersonWithNoParam接口PersonWithNoParam t1=Person::new;//无参构造函数引用System.out.println(t1.getPersonWithNoParam());//lambda表达式实现PersonWithOneParam接口PersonWithOneParam t2=Person::new;//单参构造函数引用System.out.println(t2.getPersonWithOneParam(10));//lambda表达式实现PersonWithDoubleParams接口PersonWithDoubleParams t3=Person::new;//多参构造函数引用System.out.println(t3.getPersonWithDoubleParams(10,"小王"));}}
lambda表达式会自动根据 方法的参数类型 匹配Person类中对应的构造方法
5.特殊的函数引用
如果在使用lambda表达式实现某些接口的时候,lambda表达式的参数列表中包含了某一个对象,此时方法体中,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑,并且其他的参数可以作为调用方法的参数。此时,可以对这种实现进行简化。
Person类
public class Person {private Integer age;private String name;public Person() {}public Person(Integer age) {this.age = age;}public Person(Integer age, String name) {this.age = age;this.name = name;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Person{" +"age=" + age +", name='" + name + '\'' +'}';}
}
PersonSpecial接口
public interface PersonSpecial {void setAge (Person person,Integer age);
}
Demo2
public class Demo2 {public static void main(String[] args) {//简化前PersonSpecial t=(person,age)->person.setAge(age);//用对象的特殊引用简化后PersonSpecial t2=Person::setAge;Person person = new Person();t2.setAge(person,13);System.out.println(person);}
}
6.闭包
如果某一个局部变量被用在了某一个代码块中,这其实就形成了对这个局部变量的包围,我们称"闭包"。这个时候这个变量不允许被改变,即final
如下编译器会报错:
如果使用的是全局变量则不会出现闭包问题
三.Stream流
Java8的 使用的是函数式编程模式,可以被用来对集合或数组进行链状流式的操作。有别于IO流,这里是针对集合操作数据的流
创建流
单列集合: 集合对象.stream()
List<String>s=new ArrayList<>();
Stream<String> stream = s.stream();
数组:`Arrays.stream(数组) `或者使用`Stream.of`来创建
Integer[] arr={1,2,3,4,5};
//创建流 法一
Stream<Integer>stream=Arrays.stream(arr);
//创建流 法二
Stream<Integer>stream=Stream.of(arr);
双列集合:转换成单列集合后再创建
Map<String,Integer> map = new HashMap<>();
map.put("蜡笔小新",19);
map.put("黑子",17);
map.put("日向翔阳",16);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
Map的entrySet() 方法返回一个实现Map.Entry接口的对象集合。集合中每个对象都是底层Map中一个特定的键/值对。一般情况下,要输出Map中的key 和 value 是先得到key的集合keySet(),然后再迭代(循环)由每个key得到每个value。values()方法是获取集合中的所有值,不包含键,没有对应关系。而Entry可以一次性获得这两个值。Map.Entry中的常用方法如下所示:
- Object getKey(): 返回条目的关键字
- Object getValue(): 返回条目的值
- Object setValue(Object value): 将相关映像中的值改为value,并且返回旧值
中间操作
filter
可以对流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
例如:留下所有字符串长度大于3的
我们可以先用匿名内部类的方式实现看看
List<String>strings=new ArrayList<>();
strings.add("abcde");
strings.add("ac");
strings.stream().filter(new Predicate<String>() {@Overridepublic boolean test(String s) {return s.length()>2;}
})
我们会发现Predicate 接口的泛型参数和集合是一样的,并且所实现的方法参数也是一样的
集合会把每个元素传给test方法,如果test方法返回true,该元素就会被保留,返回false则过滤掉
现在我们可以试着用lambda表达式简化一下
strings.stream().filter(s-> s.length()>2)