函数式编程和面向对象编程
百度百科上定义函数式编程:
函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。 [1]
和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。
和过程化编程相比,函数式编程里函数的计算可随时调用。
以此我们可以判断函数式编程和面向对象编程的区别是:面向对象编程参数接收的是对象,而函数式编程参数接受的是动作。刚开始接触java函数式编程可能不太好理解这个概念,那么我们就可以暂时理解为这个动作也是一个对象引用,类似与匿名对象。(这个是个坑。)
函数式接口
jdk8提供了大量的函数式接口,都在java.util.function包下。
定义
知识点一:该接口下有且仅有一个抽象方法。
知识点二:如果接口中声明了一个抽象方法,该抽象方法覆盖了Object中的public的方法,比如:
@FunctionalInterface
interface Test {
void test();
String toString();
}
虽然是两个抽象方法但是这个接口仍然可以看作为函数式接口。因为所有的对象的父类是Object因而所有的对象中默认会有Object中的方法。因此,此处的toString()只能看作是覆盖Object中的toString(),并不能作为影响函数接口有且仅有一个接口的定义,即接口数量并不会加1。
知识点三:函数式接口可以有一下三种方式来实现。
1、使用Lambda表达式。2、方法引用。3、构造器引用。
Lambda表达式
Lambda表达式的基本构成:
(item) -> { }
参数 + 箭头符号 + 执行体
使用Lambda表达式需要注意一下几点:
如果没有参数,那么“()”不能省略!
如果只有一个参数,那么“()” 可以省略!
如果有两个及以上参数,那么“()” 不能省略!
理解 :上面我们提到了Lambda只是函数式接口实现的一种方式,那么我们Lambda所实现的就是函数式接口中唯一的抽象方法,只是此时我们并没有关注函数式接口中抽象方法的名字!
接下来我们使用一段代码来诠释:
一、传统编程
List<String> list = Arrays.asList("hello","world","bins");
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
我们使用list的forEach进行内部迭代遍历list的内容并且输出list中的值。forEach(Consumer c)接收一个Consumer 对象,如果我们把Consumer 对象作为参数传给forEach方法,那么我们必须实现Consumer 中的方法,而Consumer 就是一个函数式接口,因此我们也可以使用Lambda表达式来实现!
二、Lambda方式
List<String> list = Arrays.asList("hello","world","bins");
list.forEach(items -> System.out.println(items));
哇哦,代码瞬间清晰明朗很多,犹如雨后彩虹!在这个Lambda中并没有体现方法名字,但是它就是作为Consumer接口中accept的实现!
方法引用
方法引用的表示形式比较特殊,因此也容易记忆
方法引用一共可以分为4中:
1、类名::静态方法名。
2、引用名::实例方法名。
3、类名::实例方法名。(这种方式一般会接受两个参数,参数1会调用实例方法,参数2会作为实例方法的参数。)
4、类名::new。(这种方法也叫做构造器引用,也就是我们上文提到的实现函数式接口的三大形式。)
举个栗子
类名::静态方法名
list.stream().map(Math::decrementExact).collect(Collectors.toList());
类名::实例方法名
List<String> list = Arrays.asList("hello ", "world", "bins9", "beijing");
list.sort(String::compareToIgnoreCase);
引用名::实例方法名
List<String> list = Arrays.asList("hello","world","bins");
list.forEach(System.out :: println);
类名::new
Supplier<Student> supplier = Student::new;
System.out.println(supplier.get().getName());
下面这个例子是使用自己创建的方法来实现 引用名::实例方法名
package com.bins9.test;
import java.util.function.BinaryOperator;
public class MyTest {
public static void main(String[] args) {
MyTest myTest = new MyTest();
String test = myTest.test("bins","9",String::concat);
System.out.println(test);
}
public String test(String s, String str, BinaryOperator<String> binaryOperator){
return binaryOperator.apply(s,str);
}
}
函数式接口
接下来我们来介绍一下java提供的函数式接口
1、Consumer
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
Consumer,接受一个参数,没有返回值,类似于消费者。
2、BiConsumer<T, U>
@FunctionalInterface
public interface BiConsumer<T, U> {
/**
* Performs this operation on the given arguments.
*
* @param t the first input argument
* @param u the second input argument
*/
void accept(T t, U u);
BiConsumer<T, U>,接受两个参数,没有返回值,这个也是消费者。
3、Function<T, R>
@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);
Function<T, R>,接受T类型的值,返回R类型的值。适合做适配器或者做映射。
4、BiFunction<T, U, R>
@FunctionalInterface
public interface BiFunction<T, U, R> {
/**
* Applies this function to the given arguments.
*
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
BiFunction<T, U, R>,接受两个参数t,u返回R类型的值。
5、BinaryOperator
@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T,T,T>
这个也是一个函数式接口,接受的值和返回的值是同一个类型,比较特殊。
6、Predicate
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
Predicate ,类似与断言,接受一个参数,返回一个布尔值。
7、Supplier
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
Supplier,不接受值,返回一个值,这个类似于生产者。
8、jdk8提供的函数式接口还有很多,上面罗列的是一些基本常用的,剩下的是一些变形,可以防止数据的拆箱和装箱可以提高运行性能,因此也非常重要,请自行查看!
比如:DoubleConsumer、IntConsumer等,这个就没有泛型了,因为值的类型已经确定了。
Lambda表达式和匿名方法的区别
Runnable r1 = () -> System.out.println(this);
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println(this);
}
};
public static void main(String[] args) {
LambdaTest lambdaTest = new LambdaTest();
Thread t1 = new Thread(lambdaTest.r1);
t1.start();
System.out.println("--------");
Thread t2 = new Thread(lambdaTest.r2);
t2.start();
}
这个例子,我们定义了两个Runnable。并且当它们执行的时候,我们让它输出this
--------
com.bins9.lambdaTest.LambdaTest@4295c176
com.bins9.lambdaTest.LambdaTest$1@6108a591
Process finished with exit code 0
这段是输出内容。
匿名类的输出方式是:所对应的对象是当前使用匿名内部类的对象 + $ + 数字 @ +hash值
数字代表这个匿名对象是当前对象的第几个匿名对象。
因此我们可以读懂上面这段输入内容,第一个this即Lambda表达式中的this表示的是当前对象。
而匿名对象中的this表示的是一个全新的作用域。这个就是Lambda和t匿名对象的最大区别!