Java 8的一个大亮点是引入Lambda表达式,使用它设计的代码会更加简洁。当开发者在编写Lambda表达式时,也会随之被编译成一个函数式接口。下面先上代码,让你先感受一下lambda表达式的霸道之处:
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
public class Demo1 {
@Test
public void fun1() {
String[] atp = {"东方不败", "马可露露",
"任盈盈", "风清扬","张飞",
"杨玉环","貂蝉", "西施"};
List<String> actors = Arrays.asList(atp);
//以前的循环方式 --->高级for循环
for (String actor : actors) {
System.out.print(actor + "; ");
}
// 使用 lambda 表达式以及函数操作(functional operation)
actors.forEach((actor) -> System.out.print(actor + "; "));
// 在 Java 8 中使用双冒号操作符(double colon operator)
actors.forEach(System.out::println);
}
}
上面就是lambda表达式的一个简单演示,和传统方法比较,是否颠覆你的想象呢?下面将介绍lambda表达式的语法:
lambda表达式由三部分组成:
1、parameters:类似于方法中的形参列表,这里的参数是函数式接口里的参数。
这里的参数可以明确的声明也可以不声明有虚拟机隐性的推断 当只有一个参数类型时可以省略圆括号
2、->:可以理解为“被用于”
3、方法体:可以是表达式,也可以是代码块。实现函数式接口中的方法。这个方法体可以有 返回值也可以没有返回值
示例:
1.不接受参数,直接返回1
()->1
2.接受两个int类型的参数,返回这两个参数的和
(int x,int y )-> x+y
3.接受x,y两个参数,JVM根据上下文推断参数的类型,返回两个参数的和
(x,y)->x+y
4.接受一个字符串,打印该字符串,没有返回值
(String name)->System.out.println(name)
5.接受一个参数,JVM根据上下文推断参数的类型,打印该参数,没有返回值,只有一个参数可以省略圆括号
name->System.out.prinln(name)
6.接受两个String类型参数,分别输出,没有返回值
(String name,String sex)->{System.out.println(name);System.out.println(sex)}
7.接受呀一个参数,返回它本身的2倍
x->2*x
下面看一个示例:
import org.junit.Test;
/**
* 旧写法与Lambda写法
* @author maye_C
*
*/
public class Demo2 {
//传统的方式实现接口
Transform<String, Integer> transform1 = new Transform<String, Integer>(){
@Override
public Integer tansform(String a) {
return Integer.valueOf(a);
}
};
//lambda方式实现接口
Transform<String, Integer> transform2 = (s)->Integer.valueOf(s);
//-----------------------------------------------------------------------
//传统方式实现Runnable
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我是传统方式的Runnable");
}
});
//Lambda方式实现Runnable
Thread thread2 = new Thread(()-> System.out.println("我是Lambda方式的Runnable"));
@Test
public void fun1() {
new Demo2().thread1.start();
new Demo2().thread2.start();
}
}
/**
* 函数式接口
* @param <A>
* @param <B>
*/
@FunctionalInterface
interface Transform<A, B> {
B tansform(A a);
}
看完之后,有没有发现lambda表达式的魅力呢。在这里解释一下什么是函数式接口:
所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。这种类型的接口也称为SAM接口,即Single Abstract Method interfaces。
1.1 函数式接口里允许定义默认方法:
函数式接口里是可以包含默认方法,因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的;
1.2 函数式接口里允许定义静态方法:
函数式接口里是可以包含静态方法,因为静态方法不能是抽象方法,是一个已经实现了的方法,所以是符合函数式接口的定义的;
1.3 函数式接口里允许定义java.lang.Object里的public方法:
函数式接口里是可以包含Object里的public方法,这些方法对于函数式接口来说,不被当成是抽象方法(虽然它们是抽象方法);因为任何一个函数式接口的实现,默认都继承了Object类,包含了来自java.lang.Object里对这些抽象方法的实现;
1.4 函数式接口里允许子接口继承多个父接口,但每个父接口中都只能存在一个抽象方法,且必须的相同的抽象方法。
1.5 @FunctionalInterface标记在接口上,“函数式接口”是指仅仅只包含一个抽象方法的接口。
1.6 我们常用的一些接口如Callable、comparator、Runnable等在jdk8都加上了@FunctionalInterface注解
比如下面这个接口就是一个正确的函数式接口:
/**
* 函数式接口
* @param <A>
* @param <B>
*/
@FunctionalInterface
interface Transform<A, B> {
//此方法为抽象方法
B tansform(A a);
// default不是抽象方法
public default void defaultMethod(){
}
//静态方法也不是抽象方法
public static void staticMethod(){
}
// java.lang.Object中的方法不是抽象方法
public boolean equals(Object value);
}
下面再看一个例子
import org.junit.Test;
/**
* 方法引用之实例引用
* @author maye_C
*
*/
public class Demo3 {
//传统的方式实现接口
Transform<String, Integer> transform1 = new Transform<String, Integer>(){
@Override
public Integer tansform(String a) {
return Integer.valueOf(a);
}
};
//lambda表达式
Transform<String, Integer> transform2 = new Obj() :: strToInt;
/**
* 函数式接口
* @param <A>
* @param <B>
*/
@FunctionalInterface
interface Transform<A, B> {
B tansform(A a);
}
/**
*
*实例对象类
*/
static class Obj {
public int strToInt(String str) {
return Integer.valueOf(str);
}
}
@Test
public void fun1() {
int result1 = transform1.tansform("100");
System.out.println(result1);
int result2 = transform1.tansform("200");
System.out.println(result2);
}
}
所谓方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。
注意方法引用是一个Lambda表达式,其中方法引用的操作符是双冒号"::"。
下面介绍lambda表达式的构造引用
**
* 方法引用之构造方法引用
* @author maye_C
*/
public class Demo4 {
@Test
public void fun1() {
//传统方式
Factory<Parent> factory1 = new Factory<Parent>() {
@Override
public Parent create(String name, int age) {
return new Boy(name, age);
}
};
Boy boy = (Boy) factory1.create("张三", 18);
boy.doSome();
factory1 = new Factory<Parent>() {
@Override
public Parent create(String name, int age) {
return new Girl(name, age);
}
};
Girl girl = (Girl) factory1.create("任盈盈", 24);
girl.doSome();
//-----------------------------------------------------------------------
//lambda方式
Factory<Boy> boyFactory = Boy::new;
Boy boy1 = boyFactory.create("风清扬", 22);
boy1.doSome();
Factory<Girl> girlFactory = Girl::new;
Girl girl1 = girlFactory.create("蓝凤凰", 28);
girl1.doSome();
}
}
下面是构造包下的内容:
/**
* 工厂接口
* @author maye_C
*
* @param <T>
*/
public interface Factory<T extends Parent> {
T create(String name, int age);
}
public class Parent {
private String name ;
private int age;
public Parent() {}
public Parent(String name, int age) {
this.name = name;
this.age = age;
}
public void doSome(){
}
}
/**
*
* @author maye_C
*
*/
//男孩类
public class Boy extends Parent {
public Boy(String name, int age) {
super(name, age);
}
@Override
public void doSome() {
System.out.println("我是个男孩");
}
}
女孩类实现同上。
下面是静态方法的引用代码:
import org.junit.Test;
/**
* 在lambda表达式中,方法引用是一种简化写法,引用的方法就是Lambda表达式的方法体的实现
* 语法结构:ObjectRef:: methodName
* 左边是类名或者实例名,中间的“::”是方法引用符号,右边是相应的方法名
* 方法引用一般分为三类:
* 静态方法引用,实例方法引用,构造方法引用
* @author maye_C
*
*/
public class Demo5 {
@Test
public void fun1() {
//传统方式
Transform<String ,Integer> transform1 = new Transform<String, Integer>() {
@Override
public Integer transform(String s) {
return Demo5.strToInt(s);
}
};
int result1 = transform1.transform("100");
System.out.println(result1);
//Lambda方式
Transform<String,Integer> transform2 = Demo5 ::strToInt;
int result2 = transform2.transform("200");
System.out.println(result2);
}
//静态方法
static int strToInt(String str){
return Integer.valueOf(str);
}
/**
* 函数式接口
* @param <A>
* @param <B>
*/
@FunctionalInterface
interface Transform<A,B>{
B transform(A a);
}
}
下面是lambda表达式的两大权限:
import org.junit.Test;
/**
* 访问权限之局部变量
*
* 在lambda表达式的使用中,lambda表达式外边的局部变量会被jvm隐式的编译成final类型 ,lambda表达式只能内部访问,不能被修改
* Lambda表达式内部对静态变量是和成员变量是可读可写的
* lambda不能访问函数接口的默认方法,在函数接口中增加default关键字定义默认的方法
* @author maye_C
*
*/
public class Demo6 {
@Test
public void fun1() {
int num = 6;//局部变量
// num = 9;//这里编译出错
Sum sum = (value)->{
return value + num;
};
System.out.println(sum.add(8));
Mul mul = (value)->{
return value*num;
};
System.out.println(mul.mul(8));
}
/**
* 函数式接口
* @FunctionalInterface标记在接口上,“函数式接口”是指仅仅只包含一个抽象方法的接口。
*/
@FunctionalInterface
interface Sum {
int add(int value);
}
interface Mul {
int mul(int value);
}
}
Java8 的Lambda表达式访问局部变量时虽然没有硬性规定要声明为final,但实际上是和java7一样的。
总之一个局部变量如果要在Java7/8的匿名类或是Java8 的lambda表达式中访问,那么这个局部变量就必须是
final的,即使没有final修饰,它也是final的。
注意:并不是Lambda开始访问时哪个局部变量才变成final,这是编译器的需求
import org.junit.Test;
/**
* 访问权限之成员——静态变量
* @author maye_C
*
*/
public class Demo7 {
@Test
public void fun1() {
System.out.println(new Demo7().getSum());
}
public int num1 = 6;
public static int num2 = 8;
private int getSum() {
Sum sum = value->{
num1 = 10;
num2 = 10;
return num1 + num2;
};
return sum.add(1);
}
/**
* 函数式接口
*/
@FunctionalInterface
interface Sum {
int add(int value);
}
}
总结:lambda表达式可以捕获外围中作用域的变量的值,有如下两条限制:
1.要确保所捕获的值是明确定义的
2.要确保所捕获的变量必须是实际上的最终变量,即final关键字所修饰的或隐式修饰的变量。