JAVA lambda 表达式
概要
Java8已经出来好几年了,现在依然有很多人在用java7,在技术方面固步自封自然不是好事,接受新的事物,与时俱进才不会被淘汰。面向对象编程,这么多年一直很火热,但是受制于语法的复杂,在近年来出现了一些优秀的函数是编程,在函数是编程的概念里,方法也是甚至可以当作参数传递。Java8的lambda表达式其实就是想函数编程靠近的一种新的编程方式,它的语法简单,减少了代码的冗余,相较与以往的内部类写起来更简单,下面我们来一起看看lambda的使用方式。
学习Lambda表达式必须得了解的概念:函数式接口。
在函数式编程语言中,Lambda表达式的类型是函数。
而在Java中,Lambda表达式是对象,它们必须依附于一类特别的对象类型——函数式接口(Functional Interface)。
接下来我们看下函数式接口的定义:
- 如果一个接口中,有且只有一个抽象的方法(Object类中的方法不包括在内),
- 那这个接口就可以被看做是函数式接口,它被@FunctionalInterface修饰。
@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();
}
上面是Runnable接口的声明,在Java8后,Runnable接口多了一个FunctionalInterface注解,表示该接口是一个函数式接口。但是如果我们不添加FunctionalInterface注解的话,如果接口中有且只有一个抽象方法时,编译器也会把该接口当做函数式接口看待。
2.1自定义一个只有一个方法的接口如下
public interface MyLambda<T> {
public T getData(T t);
}
2.2接口的实现调用
- 以前的实现方式,是直接用匿名内部类的方式
public class Main {
public static void main(String[] args) {
MyLambda<String> myLambda=new MyLambda<String>() {
@Override
public String getData(String s) {
return s+" test";
}
};
System.out.println(myLambda.getData( "hello" ));
}
}
- 替换成 Lambda
public class Main {
public static void main(String[] args) {
MyLambda<String> myLambda= s -> s+" test";
System.out.println(myLambda.getData( "hello" ));
}
}
由此可见,你可以简单将lambda表达式看成匿名内部类的简写形式。我们来分析一下,仔细看上面两部分的代码,对比一下,第一部分的getData(String S),和第二部分的 s ->。其实s ->
就是对getData(String S)的实现,既然如此那么我们当然可以把Lambda部分直接写成 (String s)-> s+" test";或者是(s) -> s+" test";这个时候相比你以及该看出来了,”->”左侧是方法的参数,右侧是方法的实现。之所以左侧可以省略类型,是因为接口中只有一个方法,java内部推断机制,可以直接推断出类型,因此在实现的时候根本不需要指定类型。一般lambda有以下几种类型。
- ()-> 66接口中的方法不需要参数,并且该方法的返回值为 66
- (x)->x+”test” 该方法接受一个参数x,最后返回了一个x+”test”的返回值
- ()-> {
总结:->右侧是对方法的实现,因此这部分是方法体的代码,当方法体只有一句话的时候大括号可以省略,当多行的时候你需要加上大括号。
}
熟悉scala的同学,肯定对forEach不陌生。它可以迭代集合中所有的对象,java8中也提供了forEach()迭代功能。下面是java的集合类遍历集合的操作,在这里我们来探讨一下ForEach()。
List<String> list = new ArrayList<>( );
list.add( "北京" );
list.add( "欢迎" );
list.add( "你" );
list.forEach( s -> System.out.println(s) );
看到这里相比你就明白了,forEach里面其实就是Lambda表达式,我们打开源码看。
public interface Iterable<T> {
/**
* Returns an iterator over elements of type {@code T}.
*
* @return an Iterator.
*/
Iterator<T> iterator();
/**
* Performs the given action for each element of the {@code Iterable}
* until all elements have been processed or the action throws an
* exception. Unless otherwise specified by the implementing class,
* actions are performed in the order of iteration (if an iteration order
* is specified). Exceptions thrown by the action are relayed to the
* caller.
*
* @implSpec
* <p>The default implementation behaves as if:
* <pre>{@code
* for (T t : this)
* action.accept(t);
* }</pre>
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
}
看见没,forEach方法里面的参数是一个Consumer<T> action接口,接着在for循环里遍历了Iterator<T>,然后将遍历出的每一个元素传递到了action里面的accept()方法中,下面打开Consumer<T>
//Consumer明显就是一个函数是接口
@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); };
}
}
四:Lambda方法的传递
前面说过,函数是编程语言,方法可以当作参数传递,javaLambda虽然没能完全的实现这样的逻辑,但是也有了基本的应用,下面介绍一下Lambda “::”进行方法的传递。
- 首先我们自定义一个符合Lambda的函数式接口
@FunctionalInterface
public interface MyLambda<T> {
public T accept(T t);
}
该接口有一个自定义的方法,这个方法返回一个T类型的数据。这个接口中的方法必须要被实现才可以用,这个大家都知道的。其实并不需要你每一次都再用的时候去实现,你可以去别的方法里面直接拿一个和accept方法特性一样(特性一样的意思是,你不能将没有返回值的方法传递给有返回值的接口,饭之亦然)的方法直接将其当作accept来用,这就相当于将别的类的方法传递给了MyLambda<T>这个接口。话不多说,下面我们自定义一个Test类。
- class Test {
// static methods
static String startsWith(String s) {
return s;
}
public String endWith(String s) {
return s;
}
}
- 实现普通方法传递:
public class Main {
public static void main(String[] args) {
Test test = new Test();
//将test类中的方法当成accept方法的实现体,myLambda.accept()实际上调用的就是test中的//endwith方法
MyLambda<String> myLambda = test::endWith;
System.out.println( myLambda.accept( "lambda lambda lambda lambda" ) );
}
}
- 实现静态方法的传递
public class Main {
public static void main(String[] args) {
MyLambda<String> myLambda = Test::startsWith;
System.out.println( myLambda.accept( "lambda lambda lambda lambda" ) );
}
}