Open downgoon opened 6 years ago
public class UserHandler {
public void create(Command command) throws CommandException {
System.out.println("user create action");
}
}
UserHandler handler = new UserHandler();
handler.create(new Command("/user/create"));
函数指针只关心“函数签名”:输入参数(包括个数,顺序和类型)和返回值,相同即可。
public interface CommandHandler {
public void exec(Command command) throws CommandException;
}
如果一个框架,需要用到某个函数,通常都是Template-Callback
方式:
public CommandFramework location(String location, CommandHandler handler) {
// do something before
handler.exec(); // callback function pointer
// do something after
}
userHandler.create(cmd)
表示调用某个函数;而userHandler::create
表示引用某个函数,也就是引用函数指针。
UserHandler user = new UserHandler();
framework.location("/user/create", user::create);
函数指针
?C语言里面,到处是“函数指针”。为什么Java到Java8才有呢?因为Java从一开始就是面向对象的,有封装性:数据与对数据的操作(也就是函数或叫方法)就是封装在一起的;不像C,数据和函数是分离的,需要把数据传递到函数里面,才能执行,或者把函数传递到另一个函数。在这种情况下,“函数指针”就被理解为“只有方法,没有属性的类”,又因为Java要实现“继承与多态”机制,于是引入了interface
机制,顺理成章,“函数指针”就变成了interface
,尽管interface
可以定义很多个方法,而“函数指针”是 只能有一个方法,那只需要我们人为的在interface
里面“只定义一个方法”不就行了么?!
对于 只能有一个方法 的
interface
,JDK专门有一个约束的annotation
,叫java.lang.FunctionalInterface
。 An informative annotation type used to indicate that an interface type declaration is intended to be a functional interface as defined by the Java Language Specification. Conceptually, a functional interface has exactly one abstract method.
但是通过上面的例子我们可以看到,显然还是函数指针更方便一点。不然上面的UserHandler
,如果要植入到framework.location("/user/create", CommandHandler handler)
里面执行,我们必须 预先 让UserHandler implement CommandHandler
,但是UserHandler
很可能在Framework
之前就实现完了的,没法对UserHandler
做预先要求,遇到这种情况,就得用“适配器模式”,定义一个UserHandlerCommandable
的接口,把对CommandHandler
的调用,转为对UserHandler
的调用。形如:
framework.location("/user/remove", new CommandHandler() {
@Override
public void exec(Command command) throws CommandException {
user.remove(command); // adaptor pattern before Java8
}
});
- 有了“函数指针”,我们可以慢慢淡忘
匿名内部类
和适配器模式
了。- 我们可以不再 通过传递整个对象来间接传递这个对象的一个方法,而可以直接指定只传递这个对象的一个方法了。
用javascript描述播放器的行为。比如暂停播放的时候,出一个广告。结束播放的时候,加载推荐内容。代码是这样的:
player.on("pause", function() {
console.log('即将加载暂停广告...');
});
player.on("ended", function() {
console.log('即将加载内容推荐...');
});
如果换成Java8的,函数指针描述:
player.addListener("pause", new EventListener() {
@overwrite
public void handleEvent() {
logger.log("即将加载暂停广告...");
}
});
player.addListener("ended", new EventListener() { @overwrite public void handleEvent(Event event) { logger.log("即将加载内容推荐..."); } });
- Java8描述
``` java
player.on("pause", () -> {
logger.log("即将加载暂停广告...");
});
player.on("ended", event -> {
logger.log("即将加载内容推荐...");
});
如果事件不以字符来命名,而是直接的方法,也可以:
player.pause( () -> {
logger.log("即将加载暂停广告...");
});
什么是lambda
表达式?个人理解就是匿名函数
。
在Java中的函数,一般是这样的:
int sum(int x, int y) {
return x + y;
}
可见一个函数的组成是:函数名 sum,输入参数 (int x, int y),返回值 int 和 函数体 {return x + y }
。
所谓匿名函数就是“去掉函数名”:
(int x, int y) -> { return x + y; }
因为去掉了函数名,返回值不知道在哪声明,就变成自动识别的隐形类型。
官方对lambda
表达式的定义是:
a
function
(or a subroutine) defined, and possibly called, without being bound to an identifier。
因为Lambda
表达式本质是匿名函数
,它自己没有名字,但本质上还是个函数,所以能赋值给函数指针。
这点非常类似javascript
中的匿名函数,也能赋值给函数对象。
public class HelloLambda {
static interface SumFunctionPointer { // 定义函数指针
int sum(int x, int y);
}
public static void main(String[] args) {
// Lambda 表达式: 本质是一个匿名函数
SumFunctionPointer sumLambda = (int x, int y) -> { return x + y; };
SumFunctionPointer sumMethod = HelloLambda::sum;
System.out.println("call sum lambda: " + sumLambda.sum(1, 2));
System.out.println("call sum method: " + sumMethod.sum(1, 2));
}
static int sum(int x, int y) { // 传统函数实现
return x + y;
}
}
lambda
既然是一个函数,那么它的常规定义应该形如:
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
// .....
return statmentM;
}
省略类型:编译器都可以从上下文环境中推断出lambda表达式的参数类型
(param1,param2, ..., paramN) -> {
statment1;
statment2;
// .....
return statmentM;
}
所以上面的代码还可以写成:
SumFunctionPointer sumLambda = (int x, int y) -> { return x + y; };
System.out.println("call sum lambda: " + sumLambda.sum(1, 2));
// hidden types lambda
SumFunctionPointer sumHiddenTypes = (x, y) -> {return x + y; };
System.out.println("call sum lambda (hidden types): " + sumHiddenTypes.sum(1, 2));
如果只有一个参数,可以省略参数的()
符号,变为:
param1 -> {
statment1;
statment2;
// ...
return statmentM;
}
当只有一条语句的时候,可以省略函数体的{}
符合,变为:
(param1,param2, ..., paramN) -> statement;
public class HelloLambdaVariation {
static interface FPOneArgument {
int oneArgument(int x);
}
static interface FPOneStatement {
int oneStatement(int x, int y, int z);
}
static interface FPOneArgumentStatement {
int oneArgState(int x);
}
public static void main(String[] args) {
FPOneArgument fpOneArg = x -> { x = x^2; return x + 1; };
System.out.println("fpOneArg: " + fpOneArg.oneArgument(5));
FPOneStatement fpOneState = (x, y, z) -> x + y + z;
System.out.println("fpOneState: " + fpOneState.oneStatement(10, 20, 30));
FPOneArgumentStatement fpOneArgState = x -> x^2;
System.out.println("fpOneArgState: " + fpOneArgState.oneArgState(6));
}
}
输出内容:
fpOneArg: 8
fpOneState: 60
fpOneArgState: 4
上面看到的Lambda
表达式似乎是一个 “闭环”:它只需要接受输入参数,然后自己加工,并返回。似乎它不用访问外部变量。其实它可以,还可以在表达式的函数体内使用this
引用,这个this
引用指向的就是定义它的类(不是它自己,而是定义它的类)。
以下代码样例,是模拟计算一个营业员的累计月薪:基本工资,再加上每笔订单给与15%的提成。
public class HelloLambdaThisRef {
// totalToken 月薪总工资
private int totalToken = 0;
public HelloLambdaThisRef(int baseToken) {
// baseToken 员工底薪
this.totalToken += baseToken;
}
// 每次返回累计月薪
public int charge(double orderCharge) {
// 每笔订单计15%的提成
FP fp = (x) -> { this.totalToken += (int) (x * 0.15); return this.totalToken; };
// Lambda 表达式中返回 'this'
return fp.hand(orderCharge);
}
static interface FP {
int hand(double x);
}
public static void main(String[] args) {
HelloLambdaThisRef alice = new HelloLambdaThisRef(3000);
System.out.println("total: " + alice.charge(256.30));
System.out.println("total: " + alice.charge(108.50));
System.out.println("total: " + alice.charge(675.60));
}
}
上面所有例子,为了让lambda
表达式(匿名函数)有个“锚点”,我们刻意定义了一些interface来作为“函数指针”。实际上完全没有必要。原因是Java本身支持泛型,这样函数概括起来就是:输入一个参数,返回一个值;输入两个参数,返回一个值;输入3个或3个以上的参数,返回一个值。当然,你或许会说,还有无返回值的呢?的确,不过你可以当做Void
处理(注意是 首字母大写 的Void
)。
这样的话,这些函数完全可以预先定义,在JDK层面就可以预定义,然后给用户直接使用就好。事实上,JDK8就是这么做的:
package java.util.function;
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
很言简意赅,就是把输入参数T,变换成输出参数R。如果没有返回值,则R类型用Void
代替,并return null
。
当然,没有返回值,JDK8还定义了另一种接口:
package java.util.function;
public interface Consumer<T> {
void accept(T t);
}
package java.util.function;
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
就是把输入T和U,变换成R。如果没有返回值,则R类型用Void
代替,并return null
。
当然,没有返回值,JDK8还定义了另一种接口:
package java.util.function;
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
如果输入三个或多个参数呢?难道一一列出来么?当然不用。可以直接当集合看待呀,因为集合并不要求元素类型一致呀。JDK8专门为它们定义了Stream
接口。Stream
的功能就相当强大,可以专门的章节讲解。
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
输入0个,返回1个的,就是Supplier
,起名上对应Consumer
。
完整的实战代码:
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class HelloLambdaFunction {
public static void main(String[] args) {
// CASE-1: 输入1个或2个,返回1个
Function<Integer, Integer> fpOne = x -> {
x = x ^ 2;
return x + 1;
};
System.out.println("fp one argument: " + fpOne.apply(5));
BiFunction<Integer, Integer, Long> fqTwo = (x, y) -> x ^ 2 + y ^ 2 + 0L;
System.out.println("fp two arguments: " + fqTwo.apply(5, 6));
// CASE-2: 输入1个或2个,返回Void。无返回值,用Void代替。
//
Function<Integer, Void> fpOneVoid = x -> {
x = x ^ 2;
x = x + 1;
System.out.println("one argument void: " + x);
return null;
};
fpOneVoid.apply(5);
BiFunction<Integer, Integer, Void> fpTwoVoid = (x, y) -> {
long s = x ^ 2 + y ^ 2 + 0L;
System.out.println("two arguments void: " + s);
return null;
};
fpTwoVoid.apply(5, 6);
// CASE-3: 输入1个或2个,无返回。无返回值,另一种处理方式
Consumer<Integer> fpOneNone = x -> {
x = x ^ 2;
x = x + 1;
System.out.println("one argument consumer: " + x);
};
fpOneNone.accept(5);
BiConsumer<Integer, Integer> fpTwoNone = (x, y) -> {
long s = x ^ 2 + y ^ 2 + 0L;
System.out.println("two arguments consumer: " + s);
};
fpTwoNone.accept(5,6);
// CASE-4: 无输入,有输出
Supplier<Double> fpRand = () -> { return Math.random();};
System.out.println("none argument but one returned: " + fpRand.get());
// CASE-5: 无输入,无输出
Runnable fpRunnable = () -> {System.out.println("无输入,无输出");};
fpRunnable.run();
}
}
用个图表总结一下:
输入参数个数 | 返回值个数 | 通用函数指针名 |
---|---|---|
0 | 1 | java.util.function.Supplier |
1 | 1 | java.util.function.Function |
2 | 1 | java.util.function.BiFunction |
0 | 0 | - 或 java.lang.Runnable |
1 | 0 | java.util.function.Consumer |
2 | 0 | java.util.function.BiConsumer |
3+ | * | java.util.stream.Stream |
输入参数个数 | 返回值个数 | 通用函数指针名 |
---|---|---|
0 | 1 | java.util.function.Supplier |
1 | 1 | java.util.function.Function |
2 | 1 | java.util.function.BiFunction |
0 | 0 | - 或 java.lang.Runnable |
1 | 0 | java.util.function.Consumer |
2 | 0 | java.util.function.BiConsumer |
3+ | * | java.util.stream.Stream |
lambda
表达式函数引用 | 等效lambda 表达式 |
---|---|
String::valueOf | x ->String.valueOf(x) |
|
Object::toString | x ->x.toString() |
|
x::toString | ()->x.toString() |
|
ArrayList::new | () -> new ArrayList<>() |
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamSQLSelect {
public static void main(String[] args) {
// selectAll();
// 1. 投影运算
// selectNameWay1();
// selectNameWay2();
// selectNameAge();
// 2. where 运算
// selectAgeLT35();
// 3. order by
// selectAgeLT35OrderByAge();
// 4. group by
// 4.1 男员工和女员工的数量分别是多少
selectGroupByGender();
}
public static void selectGroupByGender() {
/*
* select male, count(male) as count from employees group by male order
* by count
*/
Stream<Employee> employees = genStream();
employees.collect(Collectors.groupingBy(Employee::isMale)) //
.entrySet().stream() // kv stream
// .peek(System.out::println) // debug groupby
.sorted((kv1, kv2) -> -(kv1.getValue().size() - kv2.getValue().size())) //
.map(kv -> { // count on groupBy
String gender = kv.getKey() ? "男性" : "女性";
int count = kv.getValue().size();
return String.format("%s,%d", gender, count);
}) // count on groupBy
.forEach(System.out::println);
}
public static void selectAgeLT35OrderByAge() {
/*
* select name,age from employees where age <= 35 order by age desc
*/
Stream<Employee> employees = genStream();
employees.filter(e -> e.getAge() <= 35) // where age <= 35
.sorted((e1, e2) -> -(e1.age - e2.age)) // order by age desc
.map(e -> e.getName() + "," + e.getAge()) // select name&age
.forEach(System.out::println);
}
public static void selectAgeLT35() {
/*
* select name,age from employees where age <= 35
*/
Stream<Employee> employees = genStream();
employees.filter(e -> e.getAge() <= 35) // where age <= 35
.map(e -> e.getName() + "," + e.getAge()) // select name&age
.forEach(System.out::println);
}
public static void selectNameAge() {
/*
* select name,age from employees
*/
Stream<Employee> employees = genStream();
employees.map(e -> e.getName() + "," + e.getAge()) // select name&age
.forEach(System.out::println);
}
public static void selectNameWay2() {
/*
* select name from employees
*/
Stream<Employee> employees = genStream();
employees.map(Employee::getName) // select name
.forEach(System.out::println);
}
public static void selectNameWay1() {
/*
* select name from employees
*/
Stream<Employee> employees = genStream();
employees.map(e -> e.getName()) // select name
.forEach(System.out::println);
}
public static void selectAll() {
/*
* select * from employees
*/
Stream<Employee> employees = genStream();
employees.forEach(System.out::println);
}
public static Stream<Employee> genStream() {
return Stream.of(new Employee(1, "wangyi", 28, true, 3000.85) //
, new Employee(2, "liuer", 30, true, 4058.93) //
, new Employee(3, "zhangsan", 45, false, 6000.78) //
, new Employee(4, "lisi", 35, false, 6080.78) //
, new Employee(5, "zhaowu", 41, true, 7050.78) //
, new Employee(6, "chenliu", 38, true, 6080.78) //
);
}
public static class Employee {
private int id;
private String name;
private int age;
private boolean male;
private double salary;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isMale() {
return male;
}
public void setMale(boolean male) {
this.male = male;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Employee(int id, String name, int age, boolean male, double salary) {
super();
this.id = id;
this.name = name;
this.age = age;
this.male = male;
this.salary = salary;
}
@Override
public String toString() {
return "Employee [id=" + id + ", name=" + name + ", age=" + age + ", male=" + male + ", salary=" + salary
+ "]";
}
}
}
当然我们也可以按员工的年龄段来进行统计:
public static void selectGroupByAgeGap() {
/*
* select age, count(age) as count from employees group by gap(age, 10)
* order by count
*/
Stream<Employee> employees = genStream();
String head = String.format("年龄段,员工人数");
System.out.println(head);
employees.collect(Collectors.groupingBy( //
(Employee e) -> {
return (e.getAge()/10) * 10;
}) // groupby age gap
).entrySet() // map: age -> list<Employee>
.stream() // set to stream
.sorted( (e1, e2) -> e2.getValue().size() - e1.getValue().size()) // orderby
.map(kv -> { // age, count fields
int age = kv.getKey();
int count = kv.getValue().size();
return String.format("%d~%d,%d", age, age+10, count);
}) //
.forEach(System.out::println);
}
输出结果是:
年龄段,员工人数
30~40,3
40~50,2
20~30,1
在vertx
的接口中,有非常多的接口中定义了“静态方法”:
private void setupDefaultStackHandlers(Router router) {
router.route().handler(CookieHandler.create());
router.route().handler(BodyHandler.create());
router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)));
}
其中:CookieHandler.create()
是接口的静态方法
package io.vertx.ext.web.handler;
import io.vertx.ext.web.handler.impl.CookieHandlerImpl;
public interface CookieHandler extends Handler<RoutingContext> {
static CookieHandler create() { // 接口静态方法直接返回实现类
return new CookieHandlerImpl();
}
}
package io.vertx.ext.web.handler.impl;
import io.vertx.ext.web.handler.CookieHandler;
public class CookieHandlerImpl implements CookieHandler {
}
尽管很多教程都说这样有利于使用方被强制针对接口编程,但另一方面这样导致接口层与实现层相互依赖了。而且,代码中到处散布着CookieHandler.create()
,然而内部是强制new CookieHandlerImpl();
,也就是说如果不修改接口的static方法,代码中也无法使用其他实现。
F1: forEach lambda expression
https://www.mkyong.com/java8/java-8-foreach-examples/
传统做法
尤其这个
Map.Entry<String, Integer>
写起来很费劲?!Java8的写法