hello-world icon indicating copy to clipboard operation
hello-world copied to clipboard

Java8 新特性集锦

Open downgoon opened this issue 8 years ago • 5 comments

F1: forEach lambda expression

https://www.mkyong.com/java8/java-8-foreach-examples/

传统做法

Map<String, Integer> items = new HashMap<>();
items.put("A", 10);
items.put("B", 20);
items.put("C", 30);
items.put("D", 40);
items.put("E", 50);
items.put("F", 60);

for (Map.Entry<String, Integer> entry : items.entrySet()) {
	System.out.println("Item : " + entry.getKey() + " Count : " + entry.getValue());
}

尤其这个Map.Entry<String, Integer> 写起来很费劲?!

Java8的写法

Map<String, Integer> items = new HashMap<>();
items.put("A", 10);
items.put("B", 20);
items.put("C", 30);
items.put("D", 40);
items.put("E", 50);
items.put("F", 60);

items.forEach((k,v)->System.out.println("Item : " + k + " Count : " + v));

items.forEach((k,v)->{
	System.out.println("Item : " + k + " Count : " + v);
	if("E".equals(k)){
		System.out.println("Hello E");
	}
});

downgoon avatar Oct 18 '17 07:10 downgoon

F2: 函数指针

现有类的方法

  • 类定义:类与方法
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

如果一个框架,需要用到某个函数,通常都是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
        }
});

编程风格影响

  • 有了“函数指针”,我们可以慢慢淡忘匿名内部类适配器模式了。
  • 我们可以不再 通过传递整个对象来间接传递这个对象的一个方法,而可以直接指定只传递这个对象的一个方法了。

Java8与JavaScript越来越像

用javascript描述播放器的行为。比如暂停播放的时候,出一个广告。结束播放的时候,加载推荐内容。代码是这样的:

player.on("pause", function() {
  console.log('即将加载暂停广告...');
});

player.on("ended", function() {
  console.log('即将加载内容推荐...');
});

如果换成Java8的,函数指针描述:

  • 传统Java描述
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描述
player.on("pause", () -> {
  logger.log("即将加载暂停广告...");
});

player.on("ended", event -> {
  logger.log("即将加载内容推荐...");
});

如果事件不以字符来命名,而是直接的方法,也可以:

player.pause( () -> {
  logger.log("即将加载暂停广告...");
});

downgoon avatar Oct 20 '17 00:10 downgoon

F3: lambda 表达式

本质是匿名函数

什么是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表达式

因为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));

简化模式1:只有一个参数

如果只有一个参数,可以省略参数的()符号,变为:

param1 -> {
  statment1;
  statment2;
  // ...
  return statmentM;
}

简化模式2:只有一条语句

当只有一条语句的时候,可以省略函数体的{}符合,变为:

(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

表达式内部访问'this'

上面看到的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就是这么做的:

  • Function接口:输入一个,返回一个
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);
}
  • BiFunction接口:输入两个,返回一个
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);
}
  • Stream接口:输入三个或多个,返回一个

如果输入三个或多个参数呢?难道一一列出来么?当然不用。可以直接当集合看待呀,因为集合并不要求元素类型一致呀。JDK8专门为它们定义了Stream接口。Stream的功能就相当强大,可以专门的章节讲解。

  • Supplier接口:输入0个,返回1个。
@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

downgoon avatar Oct 21 '17 04:10 downgoon

备忘录

预定义函数指针:lambda表达式与预定义函数

输入参数个数 返回值个数 通用函数指针名
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<>()

downgoon avatar Oct 21 '17 07:10 downgoon

F4: Stream API & SQL Select

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

downgoon avatar Oct 22 '17 13:10 downgoon

F5: 接口的静态方法

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方法,代码中也无法使用其他实现。

downgoon avatar Oct 23 '17 01:10 downgoon