Functional Interface
Unary 一元的
Binary 二元的
name | type | description |
---|---|---|
Consumer | Consumer< T > | 接收T对象,不返回值 |
Predicate | Predicate< T > | 接收T对象并返回boolean |
Function | Function< T, R > | 接收T对象,返回R对象 |
Supplier | Supplier< T > | 提供T对象(例如工厂),不接收值 |
UnaryOperator | UnaryOperator< T > | 接收T对象,返回T对象 |
BiConsumer | BiConsumer<T, U> | 接收T对象和U对象,不返回值 |
BiPredicate | BiPredicate<T, U> | 接收T对象和U对象,返回boolean |
BiFunction | BiFunction<T, U, R> | 接收T对象和U对象,返回R对象 |
BinaryOperator | BinaryOperator< T > | 接收两个T对象,返回T对象 |
函数式接口的使用,分为以下几个步骤:
1.定义一个类A,实现一个函数式接口,实现对应的方法a1,在这个a1方法中,编写需要的业务逻辑
2.在方法b中定义方法参数的类型是A ,在方法b中,要调用A.a1(),同时传入对应的参数
3.实际调用方法b
Consumer
1 作用
- 消费某个对象
2 使用场景
- Iterable接口的forEach方法需要传入Consumer,大部分集合类都实现了该接口,用于返回Iterator对象进行迭代。
3 设计思想
- 开发者调用ArrayList.forEach时,一般希望自定义遍历的消费逻辑,比如:输出日志或者运算处理等。
- 处理逻辑留给使用者,使用灵活多变。
- 多变的逻辑能够封装成一个类(实现Consumer接口),将逻辑提取出来。
PASS:Javascript能够将函数传递给另一个函数,这应该算是函数式编程的一个体现,java的function包中的类也是类似的。
public interface Iterable<T> {
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
4 DEMO
public class ConsumerTest {
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
String[] prefix = {"A", "B"};
for (int i = 1; i <= 10; i++)
employees.add(new Employee(prefix[i % 2] + i, i * 1000));
//其实逻辑是: 将employees中的每个元素依次交给 SalaryConsumer,执行SalaryConsumer的accept方法,将employees中的每个元素就是 accept方法的方法参数
employees.forEach(new SalaryConsumer());
employees.forEach(new NameConsumer());
}
static class Employee {
private String name;
private int salary;
public Employee() {
this.salary = 4000;
}
public Employee(String name, int salary) {
this.name = name;
this.salary = salary;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
@Override
public String toString() {
return new StringBuilder()
.append("name:").append(name)
.append(",salary:").append(salary)
.toString();
}
}
// 输出需要交税的员工
static class SalaryConsumer implements Consumer<Employee> {
@Override
public void accept(Employee employee) {
if (employee.getSalary() > 2000) {
System.out.println(employee.getName() + "要交税了.");
}
}
}
// 输出需要名字前缀是‘A’的员工信息
static class NameConsumer implements Consumer<Employee> {
@Override
public void accept(Employee employee) {
if (employee.getName().startsWith("A")) {
System.out.println(employee.getName() + " salary is " + employee.getSalary());
}
}
}
}
工作中的cosumerr写法:
1.先定义cosumer,在consumer中先确定好,执行的逻辑
2.把consumer作为参数放到 list的forEach中,因为list的forEach方法中,会显示的调用cosumer.accept,当调用cosumer.accept时,consumer中的逻辑也就真正开始执行了。
Consumer<IntentService> consumer = intentService -> execIntent(intentDetail, robotSensor, intentService);
SchedulerCore.INSTANCE.callIntentService(intentDetail.getIntentType(), consumer);
/**
* callIntentService
* @param type type
* @param intentListenerConsumer intentListenerConsumer
*/
public void callIntentService(IntentType type, Consumer<IntentService> intentListenerConsumer) {
Set<IntentService> intentServices = intentServiceMap.get(type);
if (LOG.isTraceEnabled()) {
String logFormat = "notify intent [{}] to listeners: {}. {} log denied.";
LOG.trace(logFormat, type, intentServices, CountAndTimeBasedLogFilter.logDenyCount(logFormat));
}
if (intentServices != null) {
intentServices.forEach(intentListenerConsumer);
}
synchronized (defaultIntentServices) {
defaultIntentServices.forEach(intentListenerConsumer);
}
}
上面的consumer的使用和lambda写法 主要是在回调的逻辑中。下面,再介绍下,刚开始,将监听器注册的lambda写法:
接口
/**
* RegisterMessageListener
*
* @since 2019-09-16
*/
interface RegisterMessageListener {
void register(OSController controller);
}
函数式接口作为引用:
private static final RegisterMessageListener[] REGISTER_MESSAGE_LISTENERS = {
OSController::registerAsrListener, OSController::registerStereoBodyListener,
OSController::registerObjectListener, OSController::registerStateListener,
OSController::registerTVLinkEventListener, OSController::registerHiLinkServiceListener
};
/**
* 注册ASR监听函数
*/
public void registerAsrListener() {
LOG.info("registerAsrListener()");
hriManager.registerAsrListener(asrListener);
}
真正调用:
private void registerMessageListeners(RegisterMessageListener[] listeners) {
for (RegisterMessageListener listener : listeners) {
try {
listener.register(this);
} catch (NoSuchMethodError e) {
LOG.error("", e);
}
}
}
Predicate
1 作用
- 判断对象是否符合某个条件
2 使用场景
ArrayList的removeIf(Predicate):删除符合条件的元素
如果条件硬编码在ArrayList中,它将提供无数的实现,但是如果让调用者传入条件,这样ArrayList就可以从复杂和无法猜测的业务中解放出来。
3 设计思想
- 提取条件,让条件从处理逻辑脱离出来,解耦合
4 DEMO
// employee.getSalary() > 2000 提取成一个条件类
class SalaryConsumer implements Consumer<Employee> {
@Override
public void accept(Employee employee) {
// 自行传入本地的最低交税工资
if (new SalaryPredicate(2000).test(employee)) {
System.out.println(employee.getName() + "要交税了.");
}
}
}
class SalaryPredicate implements Predicate<Employee>{
private int tax;
public SalaryPredicate(int tax) {
this.tax = tax;
}
@Override
public boolean test(Employee employee) {
return employee.getSalary() > tax;
}
}
Function
1 作用
- 实现一个”一元函数“,即传入一个值经过函数的计算返回另一个值。
2 使用场景
- V HashMap.computeIfAbsent(K , Function<K, V>) // 简化代码,如果指定的键尚未与值关联或与null关联,使用函数返回值替换。
- Stream map(Function<? super T, ? extends R> mapper); // 转换流
3 设计思想
- 一元函数的思想,将转换逻辑提取出来,解耦合
4 DEMO
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
String[] prefix = {"B", "A"};
for (int i = 1; i <= 10; i++)
employees.add(new Employee(prefix[i % 2] + i, i * 1000));
int[] expenses = ListToArray(employees, new EmployeeToExpenses());// 公司对单个员工的支出数组
int[] incomes = ListToArray(employees, new EmployeeToIncome()); // 单个员工的收入数组
System.out.println("社保+公积金+税=" + (sum(expenses) - sum(incomes)) + "元");
}
private static int[] ListToArray(List<Employee> list, Function<Employee, Integer> function) {
int[] ints = new int[list.size()];
for (int i = 0; i < ints.length; i++)
ints[i] = function.apply(list.get(i));
return ints;
}
private static int sum(int[] salarys) {
int sum = 0;
for (int i = 0; i < salarys.length; i++)
sum += salarys[i];
return sum;
}
// 公司支出
static class EmployeeToExpenses implements Function<Employee, Integer> {
@Override
public Integer apply(Employee employee) {
// 假设公司公积金和社保为工资的20%
return Double.valueOf(employee.getSalary() * (1 + 0.2)).intValue();
}
}
// 员工实际到手工资
static class EmployeeToIncome implements Function<Employee, Integer> {
@Override
public Integer apply(Employee employee) {
// 假设员工薪水 * 80% 为到手工资
return Double.valueOf(employee.getSalary() * (1 - 0.2)).intValue();
}
}
Supplier
1 作用
- 创建一个对象(工厂类)
2 使用场景
- Optional.orElseGet(Supplier<? extends T>):当this对象为null,就通过传入supplier创建一个T返回。
3 设计思想
- 封装工厂创建对象的逻辑
4 DEMO
public static void main(String[] args) {
// 生成固定工资的员工
Supplier<Employee> supplier = () -> new Employee();
Employee employee1 = supplier.get();
employee1.setName("test1");
Employee employee2 = supplier.get();
employee2.setName("test2");
System.out.println("employee1:" + employee1);
System.out.println("employee2:" + employee2);
}
UnaryOperator
1 作用
- UnaryOperator继承了Function,与Function作用相同
- 不过UnaryOperator,限定了传入类型和返回类型必需相同
2 使用场景
- List.replaceAll(UnaryOperator) // 该列表的所有元素替换为运算结算元素
- Stream.iterate(T,UnaryOperator) // 重复对seed调用UnaryOperator来生成元素
3 设计思想
- 一元函数的思想,将同类转换逻辑提取出来,解耦合
4 DEMO
public static void main(String[] args) {
ArrayList<Employee> employees = new ArrayList<>();
String[] prefix = {"B", "A"};
for (int i = 1; i <= 10; i++)
employees.add(new Employee(prefix[i % 2] + i, i * 1000));
System.o
ut.println("公司进行薪资调整...");
salaryAdjustment(employees,new SalaryAdjustment(4000));
employees.forEach(System.out::println);
}
static void salaryAdjustment(List<Employee> list, UnaryOperator<Employee> operator) {
for (int i = 0; i < list.size(); i++) {
list.set(i, operator.apply(list.get(i)));
}
}
static class SalaryAdjustment implements UnaryOperator<Employee> {
private int salary;
public SalaryAdjustment(int salary) {
this.salary = salary;
}
@Override
public Employee apply(Employee employee) {
employee.setSalary(salary);
return employee;
}
}
Comparable 与Comparator
Comparable
public interface Comparable
{
int compareTo(Object other);
}
When calling x.compareTo(y), the compareTo method must actually be able to compare
the two objects and return an indication whether x or y is larger. The method is
supposed to return a negative number if x is smaller than y, zero if they are equal,
and a positive number otherwise.
You can think of an interface as being similar
to an abstract class with no instance felds
class Employee implements Comparable<Employee>
{
public int compareTo(Employee other)
{
return Double.compare(salary, other.salary);
}
. . .
}
Employee[] staff = new Employee[3];
staff[0] = new Employee("Harry Hacker", 35000);
staff[1] = new Employee("Carl Cracker", 75000);
staff[2] = new Employee("Tony Tester", 38000);
Arrays.sort(staff);
java.util.Arrays 1.2
• static void sort(Object[] a)
sorts the elements in the array a. All elements in the array must belong to classes
that implement the Comparable interface, and they must all be comparable to each
other.
Comparator
public interface Comparator<T>
{
int compare(T first, T second);
}
class LengthComparator implements Comparator<String>
{
public int compare(String first, String second) {
return first.length() - second.length();
}
}
To actually do the comparison, you need to make an instance:
Comparator<String> comp = new LengthComparator();
if (comp.compare(words[i], words[j]) > 0) . . .
Contrast this call with words[i].compareTo(words[j]). The compare method is called on
the comparator object, not the string itself.
To sort an array, pass a LengthComparator object to the Arrays.sort method:
String[] friends = { "Peter", "Paul", "Mary" };
Arrays.sort(friends, new LengthComparator());
上面这几个描述和例子十分重要,仔细体会
-
Comparable 是Array中的每一个元素自己调用compareTo方法,
-
而Comparator 是the comparator object 调用 compare方法,Array每次出2个元素作为compare方法的参数
More about Comparator
Comparator接口,其实有个重要部分,就是compare方法。其实compare方法,可以分为2个角度来看,一个是实现compare方法,一个是调用compare方法。
int compare(T o1, T o2);
首先,怎么实现compare方法?
很简单,定义2个参数类型,然后返回一个int值,这样就实现了compare方法。
那么怎么调用compare方法呢?
使用comparator.compare,传入2个参数,这样就调用了compare方法。
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
上面的代码,其实只实现了compare方法,并没有调用compare方法。实现部分如下:
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2)
c1,c2是compare方法接收的参数,箭头后面的是compare的方法体。
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
上面的代码 不仅有实现compare部分,还含有调用compare部分,其实是2个comparactor,comparatorA的compare方法定义时,需要调用comparatorB的compare方法。
调用部分如下:
keyComparator.compare(keyExtractor.apply(c1),keyExtractor.apply(c2));
题外话:
1.可以看到,在定义一个comparactor的时候,可以直接调用已经定义好的function的apply方法,也可以调用已经定义好的另一个comparator的compare方法。
2.使用comparing方法,其实做了2件事情:1.定义了一个compactor,实现compae方法 2.实例化了刚刚定义的comparactor类的对象,怎么做到的呢?其实就是采用匿名内部类的形式得到了一个实例化对象,Comparactor是一个接口。
3.其实,如果想得到一个comparactor实例化对象,还可以采用如下的方式:
(s,t) ->{return Integer.compare(s.length(), t.length());});
使用如下:
Arrays.sort(array,(s,t) ->{
return Integer.compare(s.length(), t.length());
});
那么上面的这种方式与Comparator.comparing的区别在哪呢?
区别在与Comparator.comparing 的参数是一个Function,即先使用function.apply进行转化,转化后,再根据转化后的值进行比较,从而判定原始值的大小;而上面的这种方式,直接根据原始值的比较结果,来判断原始值的大小。
comparing method
static <T,U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T,? extends U> keyExtractor)
Accepts a function that extracts a Comparable sort key from a type T, and returns a Comparator<T> that compares by that sort key.
The returned comparator is serializable if the specified function is also serializable.
The Comparator interface has a number of convenient static methods for creating
comparators. These methods are intended to be used with lambda expressions
or method references.
The static comparing method takes a “key extractor” function that maps a type T to
a comparable type (such as String). The function is applied to the objects to be
compared, and the comparison is then made on the returned keys. For example,
suppose you have an array of Person objects. Here is how you can sort them
by name:
Arrays.sort(people, Comparator.comparing(Person::getName));
其实 Arrays.sort(people, Comparator.comparing(Person::getName));可以理解为以下代码
key extractor is a funtion
keyExtractor:Function<Person, String> function
class PersonComparator implements Comparator<Person>
{
public int compare(Person first, Person second) {
// return (Comparator<T> & Serializable)
// (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
//这个的c1可以认为是first,c2可以认为是second
return first.getName().compareTo(second.getName());
}
}
Arrays.sort(people,new PersonComparator());
也就是说,Comparator.comparing(Person::getName)相当于 定义了一个PersonComparator,并且实例化了一个PersonComparator对象,这个Comparator的泛型是Person,在compare方法中,使用person.getName().compareTo(xx)来比较大小
You can specify a comparator to
be used for the keys that the comparing and thenComparing methods extract. For example,
here we sort people by the length of their names:
Arrays.sort(people, Comparator.comparing(Person::getName,
(s, t) -> Integer.compare(s.length(), t.length())));
上面的代码可以理解为下面的
class PersonComparator implements Comparator<Person>
{
public int compare(Person first, Person second) {
//如果 只是没有(s, t) -> Integer.compare(s.length(), t.length())这部分代码,那么代表的是,纯粹比较String在字典的排名,即 return first.getName().compareTo(second.getName()),如果不想比较最纯粹的,想要实现自己的比较规则,那么就要重写compare方法
return Integer.compare(first.getName().length(),(second.getName().length());
}
}
Arrays.sort(people,new PersonComparator());
comparingInt与comparing的区别
comparingInt(ToIntFunction<? super T> keyExtractor)
Accepts a function that extracts an int sort key from a type T, and returns a Comparator<T> that compares by that sort key
区别在于:
comparingInt :extracts an int sort key 提取一个Int类型的sort key,而comparing没有指定提取的key的类型
Arrays.sort(people, Comparator.comparingInt(p -> p.getName().length()));
Comparator.reverseOrder() 和 reversed()的区别
Comparator.reverseOrder() 和 reversed()的区别:
- reverseOrder: 以某字段进行倒序排列,
- reversed: 是针对已排序数据进行处理,常常用于比较器的末尾。
在使用Stream sorted进行排序的时候,常常需要按照摸个属性进行降序排列,有时候reverseOrder() 和 reversed()都可以实现目标,那他们的区别是什么呢?
使用样例如下:
reversed: 返回一个比较器,强制获取该比较器的相反顺序结果。案例分析:按照年龄降序排列
Comparator.comparing(UserDTO::getAge).reversed();
reverseOrder: 功能是以某字段倒序方式,从而实现对数据进行排序
Comparator.comparing(类::属性, Comparator.reverseOrder());
Comparator.comparing(UserDTO::getAge, Comparator.reverseOrder());
对Map进行排序
Map<String, Integer> branchIdNumMap = deviceAlarmRecordPOExtends.stream()
.collect(Collectors.toMap(DeviceAlarmRecordPOExtend::getBranchId, DeviceAlarmRecordPOExtend::getStatsnum, (v1, v2) -> v1 + v2));
// 对map进行排序,按照value降序,取前5个
List<String> branchIdList = branchIdNumMap.entrySet().stream().sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.limit(5).map(Map.Entry::getKey).collect(Collectors.toList());
String[] :: new
String[] result = stream.toArray(String[]::new);
toArray(IntFunction<A[]> generator)
所以认为,其实String[]::new 就可以理解为 一个function。即接收一个值,然后返回一个值
Two Colon Expression
Class::instanceMethod
以Class::instanceMethod 形式为例:
<U> Optional<U> map(Function<? super T,? extends U> mapper)
If a value is present, apply the provided mapping function to it, and if the result is non-null, return an Optional describing the result.
例子:
Optional.of(allConditions)
.map(AllConditions::getPersonCondition)
.map(PersonCondition::getStrangerFaceNames)
函数式接口一般分为2步:1.实现一个函数式接口 2.调用此函数的抽象方法
此处的双引号,其实可以认为是 实现一个函数式接口,实现的代码如下:
class EmployeeToExpenses implements Function<AllConditions, PersonCondition> {
@Override
public Integer apply(AllConditions allConditions) {
return allConditions.getPersonCondition();
}
}
上面的实现代码,可以简要的使用AllConditions::getPersonCondition来代替。
调用此函数式接口的抽象方法,需要在map方法中显示调用。
上面展示的 Class::instanceMethod 理解
Class::staticMethod
下面再展示下Class::staticMethod的理解
如果上面的实现方法稍微修改下:
Optional<Boolean> map = Optional.ofNullable("zhangsan").map(StaticCla::add);
public class StaticCla {
public static boolean add(String str) {
System.out.println("result is :"+str);
return true;
}
StaticCla::add怎么理解呢?也可以认为是实现了一个函数式接口
class EmployeeToExpenses implements Function<String, Boolean> {
@Override
// 为什么这里是String类型呢?原因很简单,Optional包装的对象zhangsan是String类型
public Integer apply(String str) {
return StaticCla.add(str);
}
}
比较,可以知道,Class::instanceMethod写法,传入的对象 作为apply方法的参数,且这个参数的类型是之前的Class;Class::staticMethod写法,传入的对象 作为apply方法的参数,且这个参数的类型是staticMethod方法的参数的参数类型。
上面说的是双引号作为Function,应该怎么处理传入的值?那么除了调用已经写好需要的Function的api,如果自己想将 一个值传入到Function中呢?
package testjdk8;
public class TestFun {
public static void main(String[] args) {
new TestFun().test();
}
public void test01() {
System.out.println("result is :");
}
public void test() {
// 展示的是Class::instanceMethod写法,所以此处TestFun::test01看做是WpListener接口的实现,接收一个TestFun类型的参数,那么就要求register抽象方法定义的时候,必须要接收一个TestFun类型的方法参数
WpListener wpListener = TestFun::test01;
TestFun testFun = new TestFun();
// 真正调用这个接口的抽象方法
wpListener.register(testFun);
}
}
interface WpListener{
// 这里可以写void register();吗? 不可以,因为WpListener wpListener = TestFun::test01;,有了此行代码,就表示,必须要传入一个TestFun对象A,作为Function的apply方法的参数,在apply方法体中,调用A.test01()
void register(TestFun testFun);
}
object::instanceMethod
双冒号表达式,除了上面的Class::staticMethod 和 Class::instanceMethod ,还有一种形式,就是object::instanceMethod。
object::instanceMethod 和 Class::staticMethod 相同,即传入的对象 作为apply方法的参数,且这个参数的类型是instanceMethod 方法的参数的参数类型。
代码实例如下:
public interface ColonInterfaceArg {
public String testMethodArg(String setName);
}
public void test03() {
ColonInterfaceArg colonArg = this::test04;
ColonInterfaceArg colonArg = colonInterfaceTest::test04;
String testMethodArg = colonArg.testMethodArg("LISI");
System.out.println(testMethodArg);
}
public String test04(String setName) {
return "wp==="+setName;
}
双冒号的使用
双冒号表达式 其实就是一个Function<T,R> T表示传入类型,R表示返回类型。
其实一个双冒号表达式,就相当于重写了一个实现function接口的类,但是并不代表会自动调用这个类,需要在主方法体 显示的调用这个这个类的某个方法,从而调用这个方法,进行执行
Method Reference
1.基本的
object::instanceMethod
• Class::staticMethod
• Class::instanceMethod
2.super和this
this::instanceMethod
super::instanceMethod
具体见P320
construct Reference
For example, Person::new is a reference to a Person constructor.
Which constructor? It depends on the context. Suppose you have a list of strings.
Then you can turn it into an array of Person objects, by calling the constructor on
each of the strings, with the following invocation:
ArrayList<String> names = . . .;
Stream<Person> stream = names.stream().map(Person::new);
List<Person> people = stream.collect(Collectors.toList());
类构造器语法使用例子:
package com.wp.conreftest;
public interface InterfaceExample {
Example create();
}
package com.wp.conreftest;
public class Example {
public void test01() {
// Example::new其实就是一个Function<T,R>,InterfaceExample接口只有一个方法,是一个函数式接口,所以可以使用com来做引用变量,指向Example::new构建出的function
InterfaceExample com = Example::new;
// 调用函数式接口的唯一未实现方法
Example create = com.create();
System.out.println(create);
}
}
如果是带参数的构造器,示例如下:
package com.wp.conreftest;
public interface InterfaceExample {
Example create(String name);
}
package com.wp.conreftest;
public class Example {
private String name
public Example(String name){
this.name = name;
}
public void test01() {
// Example::new其实就是一个Function<T,R>,InterfaceExample接口只有一个方法,是一个函数式接口,所以可以使用com来做引用变量,指向Example::new构建出的function
InterfaceExample com = Example::new;
// 调用函数式接口的唯一未实现方法
Example create = com.create("jack");
System.out.println(create.name);
}
}
数组构造器语法使用例子:
import java.util.function.Function;
/**
* @author zhoufy
* @date 2019年2月20日 下午2:19:13
*/
public class Example {
public static void main(String[] args) {
Function <Integer, Example[]> function = Example[]::new;
Example[] array = function.apply(4); //这里的4是数组的大小
for(Example e:array){
System.out.println(e); //如果输出的话,你会发现会输出4个空对象(null)
}
}
}
双冒号表达式的使用场景
在方法内调用多个方法
package testjdk8;
public class TestFun {
public static void main(String[] args) {
new TestFun().testOrigin();
}
public void testOrigin() {
test01();
test02();
}
public void test01() {
System.out.println("result is :");
}
public void test02() {
System.out.println("test02 :");
}
}
在testOrigin方法中,调用多个方法,test01,test02,这种场景可以进行改进,通过使用 双冒号表达式。
改进代码如下:
package testjdk8;
public class TestFun {
public static void main(String[] args) {
new TestFun().test();
}
// 实现(定义)
WpListener[] wpListeners = { TestFun::test01, TestFun::test02 };
public void test() {
for (WpListener wpListener : wpListeners) {
// 真正调用
wpListener.register(this);
}
}
public void test01() {
System.out.println("result is :");
}
public void test02() {
System.out.println("test02 :");
}
}
interface WpListener {
void register(TestFun testFun);
}
改进后代码的好处如下:
定义(实现) 和 调用 分成了2块,可以先定义,当需要调用时才真正调用,实现了解耦合。
作为list遍历时的consumer
未使用双冒号:
public class MyTest {
public static void main(String[] args) {
List<String> a1 = Arrays.asList("a", "b", "c");
for (String a : a1) {
printValur(a);
};
a1.forEach(x -> MyTest.printValur(x));
}
public static void printValur(String str) {
System.out.println("print value : " + str);
}
}
使用后:
a1.forEach(MyTest::printValur);
Consumer<String> consumer = MyTest::printValur;
a1.forEach(x -> consumer.accept(x));
作为runnable的实现
public class TestRunnable {
ExecutorService testThreadExecutor = Executors.newSingleThreadExecutor(r -> {return new Thread(r,"test Thread");});
public static void main(String[] args) {
new TestRunnable().test();
}
public void test() {
test01(this::test02);
}
public void test01(Runnable runnable) {
testThreadExecutor.execute(runnable);
}
public void test02() {
System.out.println("=============");
}
}
将双冒号表达式作为runnable的实现,这种写法,可以实现将调用一个方法变成一个任务,将来提交给线程池。
作为Optional的map的Function参数
public void test01() {
Dog dog = null;
Optional.ofNullable(dog).map(Dog::getName).ifPresent(v ->{
System.out.println("======");
});
System.out.println("++++++++");
}
作为Comparator的实现
sort(List<T> list, Comparator<? super T> c)
Sorts the specified list according to the order induced by the specified comparator.
ArrayList<String> fileList = new ArrayList<>(Arrays.asList(files));
Collections.sort(fileList, String::compareTo);
Lambda Expression
个人理解
Arrays.sort(people, Comparator.comparing(Person::getName));
Optional.of(allConditions)
.map(AllConditions::getPersonCondition)
.map(PersonCondition::getStrangerFaceNames)
a1.forEach(x -> MyTest.printValur(x));
看上面的代码,不管是箭头表达式 还是 双冒号表达式,目的都是一个:创建一个对象
以前,我们怎么创建一个对象的?
1.先写一个接口,再写一个实现类,最后使用new 得到一个对象。
那么使用lambda表达式怎么创建对象的呢?
1.先写一个接口,有的时候接口都不用创建,java 提供了Consumer 、Predict、Runnable...
2.通过双冒号表达式 或者是箭头表达式,实现 接口的抽象方法。
一般都是只要第2步,就完成了对象的创建了。
比较以上2种创建对象的方式,我们就明白了lambda表达式的 意义和用法
一句话,lambda表达式 的结果 就是创建了一个对象。
那么引入lambda表达式,我们的写法有什么变化呢?
以前:
1..先写一个接口,再写一个实现类,最后使用new 得到一个对象
2.当对象创建后了之后,我们就可以调用对象的方法,实现指定的功能。
现在:
1.我们先在方法A中 调用 对象的抽象方法B (方法A是通用的模板方法)
2. 使用lambda表达式,创建对象,并把这个对象 赋值给方法A
可以看到,引入了lambda表达式之后,我们的写法的顺序发生了倒转。
以ArrayList的forEach方法举例:
1.我们先在forEach方法中 调用 对象的抽象方法accept (方法forEach是通用的模板方法)
@Override
public void forEach(Consumer<? super E> action) {
final int expectedModCount = modCount;
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
// 先调用action的accept抽象方法
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
2.使用lambda表达式,创建对象,并把这个对象 赋值给方法forEach
ArrayList list = new ArrayList();
list.forEach(x -> MyTest.printValur(x));
变量作用域
想理解lambda表达式,首先先清晰以下概念:
- 栈中的局部变量不需要专门的垃圾回收,方法入栈,局部变量创建,方法出栈,局部变量清除。而堆中有专门的垃圾回收机制
- 类初始化的过程中会首先在方法区(在堆中)为静态属性分配内存并赋值
在对象实例化时会对类的成员属性在堆中分配内存赋默认值。
而局部变量则不同,局部变量是存储在栈中。
其实lambda表达式 ,就是一个类的实例化对象。
lambda表达式里面执行的逻辑,其实就是 指定方法中 的方法体中的代码。
既然是对象,那么lambda表达式在读外部变量时注意,考虑到 方法在栈中出栈时,局部变量清除。而对象存储在堆中,方法已经出栈了,但是对象仍然存在,这时,如果想在对象中访问局部变量,但是局部变量已经清零了。这肯定不行。那怎么办呢?
很简单,我们知道final修饰的变量 都存储在方法区,如果我们让final修饰局部变量,将局部变量存储在方法区,这样当方法出栈,也不会让局部变量清零。
这也是为什么 lambda表达式 要求访问的局部变量 必须用final修饰 或者说 隐形的final修饰。就是为了将变量存储到方法区。
既然,局部变量使用了final修饰,那么肯定不可以修改局部变量的值,因为这个final限制的。
lambda表达式既然是一个对象,那么访问其他对象的成员变量 肯定没有问题了。因为对象 和对象的成员变量都存储在 堆中。
因为成员变量不需要final修饰,那么lambda表达式中 肯定可以修改 成员变量的值了。
下面用例子阐述下:
lambda表达式中访问外部变量时,外部变量必须满足下面任意一个条件,即
1、变量必须是final类型的
2、如果没有定义成final,那么变量在初始化以后,不允许再有任何赋值的情况出现。(其实就是隐式final限制)
满足上面任意一个条件的外部变量,lambda表达式中 才能访问
final的意思是,这个变量一旦赋值了,那么就 不可以重新赋值或者更改了。这个比较容易理解。
那么第2种情况:什么叫 隐式 final限制呢?
很简单,就是这个变量,没有使用final修饰,但是这个变量赋值以后,就没有进行更改了,这个情况下,我们 可以认为这个变量 和 有final修饰的 效果是一样的。
public void test() {
// 这里的a 虽然没有使用final修饰,但是这个变量赋值以后,就没有进行更改了,
// 这个情况下,我们 可以认为这个变量 和 有final修饰的 效果是一样的
int a = 10;
executorService.execute(() -> {
System.out.println("a is :"+a);
});
}
下面,我们在上面代码的基础上,添加一行代码:
public void test() {
int a = 10;
// 添加一行a++
a++;
executorService.execute(() -> {
System.out.println("a is :"+a);// 这里编译会报错,编译无法通过
});
}
可以看到,我们只是添加了一行 a++,编译就无法通过。
原因是:我们添加a++之后,a这个变量 就不是隐式的final修饰的了,不满足 lambda表达式 访问外部变量的 规则了
上面,我们的变量a是方法内的局部变量,那如果是 类中的成员变量呢?
看下面的例子,我们可以看到,成员变量在lambda中是可以访问的,并且成员变量的值即使修改了,在lambda表达式中 仍然 可以继续访问。
public class LamTest {
int m = 10;
ExecutorService executorService = Executors.newSingleThreadExecutor();
public void test() {
int a = 10;
executorService.execute(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 可以正常运行,这里取到的m是30
System.out.println("m is :"+m);
});
}
public static void main(String[] args) {
LamTest lamTest = new LamTest();
lamTest.m = 40;
lamTest.test();
// 修改成员变量的值
lamTest.m =30;
}
}
上面,我们看的都是,在lambda表达式中 读取外部变量,下面,我们再看看,在lambda表达式中 修改外部变量
public void test() {
int b= 5;
executorService.execute(() -> {
b++;// 编译报错
});
}
我们可以看到,方法中定义的局部变量,是不能在 lambda表达式中修改的
但是,我们可以在lambda表达式中,修改成员变量的值。
下面,我们再看看,当定义的局部变量是 引用类型变量时,lambda表达式中 也不可以更改局部变量吗?
看下示例:
ExecutorService executorService = Executors.newSingleThreadExecutor();
public void test() {
ArrayList<Object> arrayList = new ArrayList<>();
arrayList.add("zhangdsan");
arrayList.add(1000);
executorService.execute(() -> {
Object object = arrayList.get(0);
System.out.println("object:"+object);
arrayList.add("lisi");
});
System.out.println("finish===========");
}
结果显示,lambda表达式中 可以访问、修改 局部引用类型数据的。
这个是为什么呢?这个arrayList 明显不是final修饰 或者 隐式final修饰的,怎么能在lambda表达式中 访问?而且还可以修改局部变量的值呢?
原因如下:
首先,我们看下 局部引用变量的存储位置:
局部变量 | 成员变量 | |
---|---|---|
基本数据类型 | 变量名与值都存在局部变量表中(虚拟机栈的一部分) | 变量名和值都在堆内存中 |
引用数据类型 | 引用在局部变量表中,对象在堆内存中 | 引用与对象都在堆内存中 |
从上面,可以看出,局部引用变量指向的对象是存储在堆中的。
所以,lambda表达式 中肯定是可以访问 局部引用类型数据的。
3步曲
其实,在代码中,关于lambda表达式的写法分为3步:
1.定义一个接口及抽象方法
2.实现抽象方法,使用lambda表达式 或者双冒号表达式
3.显示调用接口的抽象方法,并执行代码逻辑,这个逻辑就是步骤2写的东西
下面分别展示下:
1.定义一个接口及抽象方法
interface RegisterMessageListener {
/**
* register
*
* @param controller x
*/
void register(OSController controller);
}
2.实现抽象方法,使用lambda表达式 或者双冒号表达式
private static final RegisterMessageListener[] REGISTER_MESSAGE_LISTENERS = {
OSController::registerAsrListener, OSController::registerStereoBodyListener,
OSController::registerObjectListener, OSController::registerStateListener,
OSController::registerTvLinkEventListener, OSController::registerHiLinkServiceListener,
OSController::registerNlpListener, OSController::registerSocialListener
};
/**
* 注册ASR监听函数
*/
public void registerAsrListener() {
asrInterface.registerAsrListener();
}
3.显示调用接口的抽象方法,并执行代码逻辑,这个逻辑就是步骤2写的东西
private void registerMessageListeners(RegisterMessageListener[] listeners) {
for (RegisterMessageListener listener : listeners) {
try {
listener.register(this);
} catch (NoSuchMethodError e) {
LOG.error("", e);
}
}
}