赞
踩
本篇Blog开始学习和实践Java8中的新特性,主要分为两大部分:语言新特性和库函数新特性,重点落在工作中经常会用到的几个重大特性:
接下来按照如下几个结构分别介绍和学习以上知识点:基本概念,解决问题,语法范式,实践操作,我发现虽然经常听到函数式编程这样的名词,但是好像从来不知道具体是什么,所以这次一并了解下函数式编程的概念。
什么是函数式编程?函数式编程是一种编程范式,除了函数式编程之外还有 命令式编程,声明式编程 等编程范式
SELECT * FROM collection WHERE num > 5,其实就是表达式结果。它不需要创建变量用来存储数据,它也不包含循环控制的代码如 for, while函数式编程的本质就是:函数式编程中的函数不是指计算机中的函数,而是指数学中的函数,即自变量的映射。函数的值取决于函数的参数的值,不依赖于其他状态,比如abs(x)函数计算x的绝对值,只要x不变,无论何时调用、调用次数,最终的值都是一样
函数式编程有两个特点:函数是第一等公民,函数是纯函数
// 赋值
var func1 = function func1() { }
// 函数作为参数
function func2(fn) {
fn()
}
// 函数作为返回值
function func3() {
return function() {}
}
这两个特点的示例如下:
// 是纯函数 function sum(x,y){ return x + y } // 输出不确定,不是纯函数 function random(x){ return Math.random() * x } // 有副作用,不是纯函数 function setFontSize(el,fontsize){ el.style.fontsize = fontsize ; } // 输出不确定、有副作用,不是纯函数 let count = 0; function addCount(x){ count+=x; return count; }
综合来理解就是函数式编程,不变、不变、不变,固定的输入产生固定的输出且对外部没有任何影响,那么好处显而易见,所有的操作都是幂等的:
缺点也就是命令式编程可以发挥作用的:
大致了解了函数式编程后再来看看Java是如何使用Lambda表达式来应用函数式编程这一理念的。
Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:
@FunctionalInterface
public interface Functional {
void method();
}
不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
default void defaultMethod() {
}
}
常见的函数式接口如下:

java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:

方法释义如下:




我们上文说到,函数式接口只能有一个功能函数,但是默认方法和静态方法除外,那么什么是默认方法和静态方法呢?Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。
默认方法使得开发者可以在不破坏二进制兼容性的前提下,往现存接口中添加新的方法**,即不强制那些实现了该接口的类也同时实现这个新加的方法**。
默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下
private interface Defaulable { // Interfaces now allow default methods, the implementer may or // may not implement (override) them. default String notRequired() { return "Default implementation"; } } private static class DefaultableImpl implements Defaulable { } private static class OverridableImpl implements Defaulable { @Override public String notRequired() { return "Overridden implementation"; } }
Defaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现
Java 8也允许在接口中定义静态方法
private interface DefaulableFactory {
// Interfaces now allow static methods
static Defaulable create( Supplier< Defaulable > supplier ) {
return supplier.get();
}
}
下面的代码片段整合了默认方法和静态方法的使用场景:
public static void main( String[] args ) {
Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
System.out.println( defaulable.notRequired() );
defaulable = DefaulableFactory.create( OverridableImpl::new );
System.out.println( defaulable.notRequired() );
}
输出结果如下:
Default implementation
Overridden implementation
由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性,Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中),使用 Lambda 表达式可以使代码变的更加简洁紧凑
核心解决的问题就是:让程序员使用更少的代码实现同样的功能。虽然看着很先进,其实Lambda表达式的本质只是一个语法糖,由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能
lambda表达式允许通过表达式来代替功能接口。lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体【body可以是一个表达式或一个代码块】
左侧:Lambda表达式的参数列表 -> 右侧:Lambda表达式中要执行的功能
右侧实现可以是表达式,也可以是代码块
(parameters) -> expression
或
(parameters) ->{ statements; }
对于左侧的参数列表而言:
对于右侧功能实现来说
对于右侧功能,总结而言:单行语句或表达式,可以省略return关键字和{},几个简单的例子:
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
既然一个新的事物出现,一定有其原因,也就是之前一定有痛点,那么新事物才有价值,这里只列举当前我看到的Lambda解决的问题
我们一般在一些集合操作时使用Lambda表达式,没有lambda之前:
public static void main(String[] args) {
List<String> list = Arrays.asList("t", "m", "l");
for (String s : list) {
System.out.println(s);
}
}
有了lambda之后,只需要一行代码,将参数和功能组合成一个入参。
Arrays.asList( "t", "m", "l" ).forEach(e -> System.out.println( e ) );
为什么这里可以使用lambda呢,因为这里的forEach的入参是一个功能接口:
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
再进去一步:
package java.util.function; import java.util.Objects; /** * Represents an operation that accepts a single input argument and returns no * result. Unlike most other functional interfaces, {@code Consumer} is expected * to operate via side-effects. * * <p>This is a <a href="package-summary.html">functional interface</a> * whose functional method is {@link #accept(Object)}. * * @param <T> the type of the input to the operation * * @since 1.8 */ @FunctionalInterface public interface Consumer<T> { /** * Performs this operation on the given argument. * * @param t the input argument */ void accept(T t); /** * Returns a composed {@code Consumer} that performs, in sequence, this * operation followed by the {@code after} operation. If performing either * operation throws an exception, it is relayed to the caller of the * composed operation. If performing this operation throws an exception, * the {@code after} operation will not be performed. * * @param after the operation to perform after this operation * @return a composed {@code Consumer} that performs in sequence this * operation followed by the {@code after} operation * @throws NullPointerException if {@code after} is null */ default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
如同上面说的理论能力,上面这个代码中的参数e的类型是由编译器推理得出的,e也可以自定义类型:
Arrays.asList( "t", "m", "l" ).forEach( ( String e ) -> System.out.println( e ) );
如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体:
public static void main(String[] args) {
Arrays.asList("t", "m", "l").forEach((String e) ->
{
String a = e + "帅";
System.out.println(a);
}
);
}
关于内部类可以看我的这篇Blog【Java SE基础 九】Java内部类,所谓匿名内部类就是:匿名内部类是直接使用 new 来生成一个对象的引用,匿名内部类没有类的名称,很多场景下可以简化我们代码,但是Lambda出现后告诉我们可以更简化:它只聚焦于核心实现:
public class Demo01Inner {
public static void main(String[] args) {
//使用匿名内部类的方式实现多线程。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了");
}
}).start();
//使用Lambda表达式实现多线程
new Thread(() -> System.out.println(Thread.currentThread().getName() + "执行了")).start();
}
}
同样为什么这里可以使用lambda呢:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
再进去一步:
package java.lang; /** * The <code>Runnable</code> interface should be implemented by any * class whose instances are intended to be executed by a thread. The * class must define a method of no arguments called <code>run</code>. * <p> * This interface is designed to provide a common protocol for objects that * wish to execute code while they are active. For example, * <code>Runnable</code> is implemented by class <code>Thread</code>. * Being active simply means that a thread has been started and has not * yet been stopped. * <p> * In addition, <code>Runnable</code> provides the means for a class to be * active while not subclassing <code>Thread</code>. A class that implements * <code>Runnable</code> can run without subclassing <code>Thread</code> * by instantiating a <code>Thread</code> instance and passing itself in * as the target. In most cases, the <code>Runnable</code> interface should * be used if you are only planning to override the <code>run()</code> * method and no other <code>Thread</code> methods. * This is important because classes should not be subclassed * unless the programmer intends on modifying or enhancing the fundamental * behavior of the class. * * @author Arthur van Hoff * @see java.lang.Thread * @see java.util.concurrent.Callable * @since JDK1.0 */ @FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }
有了Lambda,不需要再定义接口、不需要new实例、不需要run()的方法声明等,只care核心实现即可。这就是函数式或者声明式编程的魅力,不是万物皆对象的,有些时候实现可以如此简单。
只要是接口函数也都可以使用lambda,如果不使用lambda,我们调用参数和比较器来进行排序:
public class Person { // ... LocalDate birthday; public int getAge() { // ... } public LocalDate getBirthday() { return birthday; } public static int compareByAge(Person a, Person b) { return a.birthday.compareTo(b.birthday); } // ... }
假设我们有一个Person数组,并且想对它进行排序,这时候,我们可能会这样写
Person[] rosterAsArray = new Person[]{
new Person("003", LocalDate.of(2021,9,1)),
new Person("001", LocalDate.of(2021,2,1)),
new Person("002", LocalDate.of(2021,3,1)),
new Person("004", LocalDate.of(2021,12,1))};
class PersonAgeComparator implements Comparator<Person> {
public int compare(Person a, Person b) {
return a.getBirthday().compareTo(b.getBirthday());
}
}
Arrays.sort(rosterAsArray, new PersonAgeComparator());
其中sort的方法签名为:
static <T> void sort(T[] a, Comparator<? super T> c)
Comparator接口是一个功能接口:
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);//equals是Object中的方法,这种对Object类的方法的重新声明会让方法不再是抽象的
...
}
因此可以使用 lambda 表达式,而不是定义然后创建一个实现以下内容的类的新实例ComparatorComparator
Arrays.sort(rosterAsArray,
(Person a, Person b) -> {
return a.getBirthday().compareTo(b.getBirthday());
}
);
这种比较两个实例的出生日期的方法我们可以在Person中定义,并且已经有了,所以上述写法还可以再简化:
Arrays.sort(rosterAsArray,
(a, b) -> Person.compareByAge(a, b)
);
总而言之,通过写这篇博客刷新了一些关于函数式编程模式的一些认知,最深刻的感受就是我们可能太OOP了,有时候感觉没有对象就什么也干不成了,做一些逻辑实现时总要定义一些冗余的类,接口,其实有些时候只需要关注最核心的实现可以减少大量的代码编写,忘记实例创建、忘记方法声明、忘记new吧,只关注核心实现!当然这也不是说别OOP了,其实无论是命令式编程还是函数式编程都有其具体的应用场景,Java这样的OOP语言也不会把函数式编程模式完全隔绝出去,这才引入了Lambda。所以一切不以应用场景为前提的优劣比较都是耍流氓
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。