大家好,
今天给大家分享一下可能会使用到的知识点:
Lambda表达式基本概念
1.背景介绍
Lambda表达式式Java8的重要更新,Lambda表达式支持将代码块作为方法参数,可以使用Lambda 实现以更简洁的代码来创建只有一个抽象方法的接口的实例
2.知识剖析
组成:
形参列表,如果形参列表中只有一个参数,可以将圆括号省略
箭头,由英文的中划线和大于符号组成
代码块,如果代码块中只包含一条语句,可以省略代码块的花括号
匿名内部类
适合于创建那种只需要一次使用的类
创建匿名内部类时会立即创建一个该类的实例,匿名内部类不能重复使用
必须继承一个父类或实现一个接口
Lambda表达式可用于简化匿名内部类对象,代替匿名内部类的繁琐语法
因为Lambda表达式可以将方法作为参数传递
3.常见问题
函数式接口与一般接口区别
4.解决方案
函数式接口只有一个抽象方法
5.编码实战
Java8中,你可以通过defaulte关键字,添加非抽象方法
函数式接口运行存在默认方法、静态方法
如下,抽象方法calculate旁还有一个sqrt方法, 实现类只需要实现抽象方法calculate
default方法sqrt是开箱即用的(out of the box).
注: Math.sqrt函数是计算平方根的。
@FunctionalInterface
public interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
这个formula 接口被实现为一个匿名对象,代码是很繁琐的————这么多代码仅仅为了一个简单的运算sqrt(a * 100)
在下一个环节,Java8有一种更好的方式实现单方法对象
public class test {
@Test // lambda表达式
public void lambda1(){
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a*100);
}
};
System.out.println( formula.calculate(100) );
System.out.println( formula.sqrt(16) );
}
}
注释里讲的很清楚,大家看注释吧。
/**
*让我们首先用老版本,通过对一个含字符串的list进行排序来开始
*
*静态的工具类方法Collections.sort接受一个list和比较器来对对list中的元素进行排序
*
* 创建一个匿名的比较器然后将参数传递给比较方法。
*
* compareTo
* 调用者小于、等于或大于指定对象,则分别返回负整数、零或正整数
*/
public class Lambda_expressions {
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
@Test
public void lambda2(){
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
int i = a.compareTo(b);
//System.out.println(i+"");
return i;
}
});
//降序排列,如果要升序,改为a.compareTo(b);
System.out.println(names); //[xenia, peter, mike, anna]
}
/**
* 不用再整天创建匿名对象了,Java8提供了一种更简洁的语法,即lambda表达式
*
*/
@Test
public void lambda2_1(){
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
System.out.println(names); //[xenia, peter, mike, anna]
}
/**
* 通过上面可以看到代码更加简单易读,而且可以更短
*/
@Test
public void lambda2_2(){
Collections.sort(names, (String a, String b) -> b.compareTo(a));
System.out.println(names);
}
/**
* 对于一行的方法体,你可以省略大括号和return关键字,然而,它还可以更短
*
* 因为Java8后,list接口里增加了sort方法,可以直接用,不需要再借助Collections
* 注意,要想让接口里的方法可以有方法体,要用default修饰
*
* Java编译器会推断出你的变量类型,所以也省略
*/
@Test
public void lambda2_3(){
names.sort( (a,b) -> b.compareTo(a));
System.out.println(names);
}
}
lambda表达式可以通过 :: 引用构造方法、静态方法、对象方法,具体见注释
public class test {
@Test
public void lambda3_1(){
Converter<String,Integer> converter = (input) -> Integer.valueOf(input);
Integer converted = converter.convert("123");
System.out.println(converted); // 123
}
/**
*
* 上面的例子可以通过方法引用来简化
* 类名::方法名
*
* Java8使你可以通过关键字::传递方法或构造方法的引用
*
*/
@Test
public void lambda3_2(){
Converter<String,Integer> converter = Integer::valueOf;
Integer output = converter.convert("123");
System.out.println(output); // 123
}
/**
* 上面是静态方法的引用,还可以引用对象的方法
*/
@Test
public void lambda3_3(){
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"
}
/**
* 下面我们看一下如何用::关键字引用构造方法
*
* 首先定义一个有不同构造方法的类Person
* 然后创建一个工厂接口用来创建Person对象
*
* 不需要在手动的实现工厂接口,通过构造方法的引用将它们粘合起来
*
*/
/**
* 我们通过Person::new创建了Person构造方法的引用,Java编译器会自动选择
* 正确的构造方法来匹配 personFactory.create
*
*/
@Test
public void lambda3_4(){
PersonFactory<Person> personFactory = Person::new;
Person p = personFactory.create("王","狗蛋");
System.out.println(p);
}
/******************* lambda作用域 ***************************/
/* 在Lambda表达式中访问外层作用域,这一点和旧版本的匿名对象中的方式类似。Lambda表达式中可以直接访问外部final变量,
或者实例的字段以及静态变量。
*/
@Test
public void lambda5_1(){
final int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
// num++;
stringConverter.convert(2); // 3
}
// 不需要显式的声明final,以下代码有效
@Test
public void lambda5_2(){
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
}
/* 在以前的java版本中匿名内部类的参数必须是final的,原因在于保证内部和外部类的数据一致性。
编译的时候内部类和方法在同一级别上,所以方法中的变量或参数只有为final,内部类才可以引用。
因为局部变量在方法调用之后就消失了,使用final声明的话该局部变量会存入堆中,和内部类有一样的声明周期。
然而java8中不加final,
也可以通过编译,在以前的版本中是不允许的。
*/
/****
* 这里的num 必须是隐式的final才能去编译,下面代码编译不通过。
* 在lambda表达式中对num进行更改也许不允许的。
*/
@Test
public void lambda5_3(){
int num = 1;
// Effectively final
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(num);
}
});
// num = 3;
}
/****
* 小总结: lambda表达式对方法内的局部变量只能读,不能写。 编译器是按final进行编译的。
*/
/****
* 和 局部变量不同的是,在lambda表达式内对静态变量和全局变量即可读又可写,这一点和内部类一样。
*
* 附:lambda表达式中存在一行以上代码需要加大括号{}
*/
static int outerStaticNum;
int outerNum;
@Test
public void lambda5_4(){
Converter<Integer, String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Converter<Integer, String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
Converter<Integer, String> stringConverter3 = (from) ->
String.valueOf(from);
}
/****
* 还记得第一节我们在接口Formula定义了 default 方法 sqrt,它可以被每个 Formula实例访问。
*
* 但lambda表达式之内不能访问接口中的default方法。
*/
@Test
public void lambda5_5(){
// Formula formula = (a) -> sqrt(a * 100);
}
}
上面的代码有用到类和接口,如下
/**
* 每个lambda对应一个类型,这个类型有接口指定。
*
* 函数式节诶看不学包含一个抽象方法的声明,每个对应类型lambda表达式将可以和这个抽象方法匹配
*
* default方法不是抽象的,你随便加
*
* 我们可以用任意接口作为lambda表达式,只要这个接口仅含有一个抽象方法。为了确保这一点,你可以在你的接口上加上
* @FunctionalInterface 注解,这样在你视图再次增加抽象方法是,编译器会检查报错
*
* 但是,即使 @FunctionalInterface 注解被省略代码也依然有效
*/
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
public class Something {
public String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
public class Person {
String firstName;
String lastName;
Person() {}
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return firstName+lastName;
}
}
/**
* 泛型上下限
*
* ?extends E: 接收E类型或者E的子类型。
* ? super E: 接收E类型或者E的父类型
* @param <P>
*/
interface PersonFactory<P extends Person> {
P create(String firstName, String lastName);
}
Java8还有很多内置函数式接口
/**
*
*Predicate是一个接受一个参数的布尔型方法,内置了多个默认方法,用来构成复杂的逻辑术语
* and, or, negate 且 或 取反
*
*/
public class test {
@Test // lambda表达式
public void lambda16_1(){
Predicate<String> predicate = (s) -> s.length() > 0;
System.out.println( predicate.test("foo") ); // true
System.out.println( predicate.negate().test("foo") ); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
}
/**
* Function接受一个参数并产生一个返回值,内置的默认方法可以将多个Function连起来
* Default methods can be used to chain multiple functions together (compose, andThen).
*/
@Test // lambda表达式
public void lambda16_2(){
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
//System.out.println( toInteger.apply("123456") );
System.out.println( backToString.apply("123") ); // "123"
}
@Test // 开启线程示例
public void lambda16_3(){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程1");
}
}).start();
new Thread( () -> System.out.println("线程1")).start();
}
6.扩展思考
Lambda表达式有哪些应用,毕竟现在代码中很少见到
新的框架上可以见到它的身影
比如Vert.x里大量应用
7.参考文献
https://github.com/winterbe/java8-tutorial