函数式接口与lambda表达式--初级Java开发学习笔记
函数式编程的一种解释
之前看到一篇文章这样来解释“函数式编程”:
函数:函数式接口
式:lamda表达式
这种说法明显比较牵强,有点强行解释的感觉,但进一步学习后感觉从函数式接口和lamda表达式这两个方面切入stream的学习也是不错的。
函数式接口
先不谈函数式接口的定义,直接来看两个函数式接口
Consumer接口
其定义如下:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
//这个方法用来继续调用其他消费者(暂时忽略这个方法)
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
函数式接口的重点是其中的抽象方法,Consumer的重点是他的accept方法,传入一个T类型,没有返回值;
BiFunction接口
其定义如下:
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
//暂时忽略这个方法
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
BiFunction接口的apply方法被定义为可以传入两个不同类型的参数,返回另一个类型的返回值;
比较两个函数式接口
相同点:
- 都被@FunctionalInterface修饰
- 都有一个抽象方法(apply)
不同点:
- 接口定义的泛型不同
- 抽象方法的参数与返回值数量不同
函数式接口的定义
从两个函数式接口的相同点来看,函数式接口的特点已经非常明显了,下面来看函数式接口的定义:
被@FunctionalInterface修饰,有且只有一个抽象方法的接口,接口中可以包含其他的方法,包括默认方法,静态方法,私有方法。
函数式接口的作用
函数式接口最重要的作用就是:将方法作为参数被传递。
直接上代码:
public static void testConsumer(Object input, Consumer<String> f) {
f.accept(input.toString());
}
public static void main(String[] args) {
Person person = new Person();
//调用
testConsumer(new StringBuffer("test"), System.out::println);
testConsumer(new StringBuffer("Tom"),person::setName);
}
testConsumer方法可以将input.toString()传递给方法f作为参数被执行,而方法f只要是一个没有返回值,传入参数为一个String的方法即可,String具体如何执行对方法testConsumer来说不重要。
方法的参数是有类型的如String、int、Object...方法作为参数也一样,在js中所有方法被归于Function类中,方法被作为参数被传递时无需关心方法的参数返回值和具体定义。
而java中,各种方法根据参数和返回值进行“分类”:
Java内置四大核心
其他常用接口
lambda表达式
lambda 表达式的语法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
(参数1,参数2) -> (表达式1;表达式2;表达式3;)
以下是lambda表达式的重要特征:
- 可选类型声明: 不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号: 一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号: 如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字: 如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。
那么lambda与函数式编程的关系是什么呢?来看下面的代码:
声明一个消费者:
Consumer<Person> personConsumer1 = new Consumer<Person>() {
@Override
public void accept(Person t) {
t.setName("Tom");
}
};
这么多行代码,有用的实际上只有第四行。
再看相同效果的lambda写法:
Consumer<Person> personConsumer2 = (e) -> {e.setName("Tom");};
Consumer<Person> personConsumer3 = e -> e.setName("Tom");
明显的简洁了。
Person排序实例
我们需要将personList排序,排序规则是area为New York的排在前;为Washington排在后(area的值只能取这两个);
首先是常规写法:
public static List<Person> sortPerson(List<Person> personList) {
LinkedList<Person> rtn = new LinkedList();
for (Person person : personList) {
if("New York".equals(person.getArea())) {
rtn.addFirst(person);
}
else {
rtn.add(person);
}
}
return rtn;
}
然后是结合了函数式接口的写法:
//Comparator是比较器接口,传入两个同类型数据,返回正数则参数一大于参数二,
//返回负数则表示参数二大于参数一,返回0则表示两个数据相等
Comparator<Person> personComparator = new Comparator<Person>()
{
@Override
public int compare(Person o1, Person o2) {
if ("New York".equals(o1.getArea())&&"Washington".equals(o2.getArea())) {
return 1;
}
else if ("Washington".equals(o1.getArea())&&"New York".equals(o2.getArea())) {
return -1;
}
else {
return 0;
}
}
};
personList.sort(personComparator);
这怎么比常规写法还复杂,下面加上lambda表达式:
personList.sort((o1,o2) -> {
if ("New York".equals(o1.getArea())&&"Washington".equals(o2.getArea())) {
return 1;
}
else if ("Washington".equals(o1.getArea())&&"New York".equals(o2.getArea())) {
return -1;
}
else {
return 0;
}
});
如果再加上三元运算符:
personList.sort((o1,o2) -> {
return o1.getArea().equals(o2.getArea()) ? 0 : ("New York".equals(o1.getArea()) ? 1 : -1);
});
基本上算是一行代码完成了排序;