JAVA8新特性-函数式接口和lambda表达式
一 前言
JAVA8很早就出现了,其中有些新特性值得关注。现在之所以这么关注JAVA8是因为公司JDK整体都升级为8了,所有在这里进行一些基础到项目改造的记录过程,以此共勉。
二 函数式编程
函数式编程是JAVA8的心特性了。
什么是函数式接口?
1 函数式接口也是java中的一个普通接口
2 只包含一个接口方法的特殊接口
3 有语义化注解@FunctionalInterface
2.1 函数式接口入门demo
@FunctionalInterface
public interface UserService {
/**
* 函数式接口是Java系统的接口,
* 只包含一个接口方法的特殊接口,
* 需要注解@FunctionalInterface进行语义化检查
* @param user
* @return
*/
String User(String user);
函数式接口使用上跟普通接口的使用是一样的。
2.2函数式接口之默认方法
默认方法是JAVA8的新特性,在8之前接口内默认的都是public abstract方法,不允许出现有具体的方法。
@FunctionalInterface
public interface UserService {
/**
* 函数式接口是Java系统的接口,只包含一个接口方法的特殊接口,需要注解@FunctionalInterface进行语义化检查
* @param user
* @return
*/
String User(String user);
/**
* java8新特性:default方法,这里是给实现类提供的功能扩展(公用方法)
* @param user
* @return
*/
default String checkUser(String user) {
if(user != null){
System.out.println("----check not null");
}else {
System.out.println("----check null");
}
return user;
}
}
使用默认方法的代码
public class Test {
public static void main(String[] args) {
//1、测试函数式编程接口
UserServiceImpl userService = new UserServiceImpl();
String admin = userService.User("admin");
System.out.println("---"+admin);
//2、测试default方法
UserServiceImpl userService1 = new UserServiceImpl();
String admin1 = userService1.checkUser("admin");
System.out.println("----"+admin1);
}
}
默认方法的作用:
默认方法是提供公用的方法,所有的函数式接口的实现类均可以调用这个方法。
2.3 函数式接口之静态方法
/**
* java8新特性:static方法,这里是给实现类提供的功能扩展(公用方法)
* @param user
* @return
*/
static String checkUserStatic(String user) {
if(user != null){
System.out.println("---- checkUserStatic check not null");
}else {
System.out.println("----checkUserStatic check null");
}
return user;
}
测试代码
public class Test {
public static void main(String[] args) {
//1、测试函数式编程接口
UserServiceImpl userService = new UserServiceImpl();
String admin = userService.User("admin");
System.out.println("---"+admin);
//2、测试default方法
UserServiceImpl userService1 = new UserServiceImpl();
String admin1 = userService1.checkUser("admin");
System.out.println("----"+admin1);
//3、测试静态方法
String admin2 = UserService.checkUserStatic("admin");
System.out.println("----" + admin2);
}
}
静态方法作用:
和默认方法一样都是为接口做扩展功能使用的。
但是默认方法是用过实现类调用的,而静态方法可以直接过接口调用。
2.4 小细节
前面说过函数式接口只能有一个接口方法。如果包含多个会出现错误提示。
但是还是要注意,因为所有的类都是继承自Object,所有来自Objtect的方法是不受影响的。比如重写toString()方法是可以通过的。


三 jdk8提供的内置函数式接口
jdk8在java.util.function提供了大量函数式接口,覆盖了很多我们需要使用的场景

我们可以看出,报名是java.util.function;并且有@FunctionalInterface注解,有且只有一个方法accept(T t,U u);
3.1 Predicate 传入T对象,返回Boolean类型结果
Predicate<T> 介绍参数T对象,返回一个Boolean类型结果,适合校验类型的接口
/**
* 1、Predicate<T> 介绍参数T对象,返回一个Boolean类型结果,适合校验类型的接口
*/
Predicate<String> predicate = (String username)->{
return "admin".equals(username);
};
System.out.println(predicate.test("admin"));//true
System.out.println(predicate.test("manager"));//false
3.2 Consumer 接受参数T类型,没有返回值
Consumer 接受参数T类型,没有返回值,场景:消息发送
/**
* 2、Consumer 接受参数T类型,没有返回值
* 场景:消息发送
*/
Consumer<String> consumer = (String messge)->{
System.out.println("--send message--" + messge);
};
consumer.accept("hello world");//--send message--hello world
3.3 Function接收T对象,返回R对象
/**
* 3、Function接收T对象,返回R对象
*/
Function<String,String> function = (String username)->{
return username.equals("admin")?"admin 用户":"manager 用户";
};
System.out.println(function.apply("admin"));//admin 用户
System.out.println(function.apply("1234"));//manager 用户
3.4 Supplier不接收参数,直接返回R对象
Supplier不接收参数,直接返回R对象,适合类似产生随机数场景
/**
* 4、Supplier不接收参数,直接返回R对象
* 场景:UUID
*/
Supplier<String> supplier = ()->{
return UUID.randomUUID().toString();
};
System.out.println(supplier.get());//fab43f66-eeaf-49d6-a9e4-956d9aa21dbd
System.out.println(supplier.get());//38a25978-a3fd-4f48-99f7-5f32624c31c9
System.out.println(supplier.get());//c07d5c6d-97be-4ee3-a650-0cc857e5bb9b
3.5 UnaryOperator接收T对象,执行业务处理后返回更新后的T对象
UnaryOperator接收T对象,执行业务处理后返回更新后的T对象,适合对对象处理
/**
* 5、UnaryOperator接收T对象,执行业务处理后返回更新后的T对象
* 适合对对象处理
*/
UnaryOperator<String> unaryOperator = (String username)->{
return username + "new";
};
System.out.println(unaryOperator.apply("me"));//menew
3.6 BinaryOperator接收两个T对象,执行业务处理后返回一个T对象
BinaryOperator接收两个T对象,执行业务处理后返回一个T对象,场景:返回比赛结果
/**
* 6、BinaryOperator接收两个T对象,执行业务处理后返回一个T对象
* 场景:返回比赛结果
*/
BinaryOperator<Integer> binaryOperator = (Integer i1,Integer i2) ->{
return i1>i2?i1:i2;
};
System.out.println(binaryOperator.apply(1,2));//2
System.out.println(binaryOperator.apply(3,2));//3
感受到内置函数式接口的作用了吗,几乎包含了我们所有的场景
四 lambda表达式
4.1 lambda表达式入门demo
//4 、匿名内部类
UserService userService2 = new UserService() {
@Override
public String User(String user) {
System.out.println("=========="+user);
return "admin".equals(user)?"admin":"manger";
}
};
String user = userService2.User("admin");
System.out.println("------"+user);
//5、 lambda表达式
UserService userService3 = (String user2) -> {
return "admin".equals(user2)?"lambdaadmin":"lambdamanger";
};
System.out.println(" lambda---" + userService3.User("admin2"));
匿名内部类和lambda表达式实现的功能是一样的。但明显代码精简了很多,之保留了数据处理的部分。
4.2 lambda表达式基本语法
1、声明:lambda表达式绑定的接口类型
2、参数:包含在一对()括号内,和绑定接口中的抽象方法的参数个数和顺序一致
3、操作符:->
4、执行代码块:包含在一对{}内,出现在操作符的右侧
[接口声明]=(参数)-> {执行代码块};
下面给出各种情况下的基本用法。
//1 没有参数,没有返回值
interface Ilambda1{
void test();
}
//2 有参数,没有返回值
interface Ilambda2{
void test(String u,Integer i);
}
//3 有参数,有返回值
interface Ilambda3{
int test(int i,int y);
}
4.2.1 没有参数没有返回值
Ilambda1 i1= ()->{
System.out.println("hello world");
};
i1.test();
高级用法:如果返回值只有一行,可以省去{}
Ilambda1 i2= ()-> System.out.println("hello world");
i2.test();
4.2.2 有参数没有返回值
Ilambda2 i3= (String u,Integer i)->{
System.out.println(u+" say he is " + i + "years old");
};
//tom say he is 18years old
i3.test("tom",18);
高级用法,传参可以省去类型,执行时JVM会自动去匹配类型
Ilambda2 i4= (u,i)->{
System.out.println(u+" say he is " + i + " years old");
};
//jack say he is 18years old
i3.test("jack",18);
4.2.3 有参数有返回值
Ilambda3 i5 = (i,j)->{
return i+j;
};
//300
System.out.println(i5.test(100,200));
高级用法:如果处理数据只有一行代码,可以省去{}
Ilambda3 i6 = (i,j)-> i+j;
//400
System.out.println(i6.test(200,200));
lambda表达式基本语法总结:
1、lambda表达式,必须和接口进行绑定 2、lambda表达式的参数,可以附带0到n个,括号中的参数类型可以不指定,jvm在运行时,会自动根据绑定抽象方法匹配 3、lambda表达式的返回值,如果代码块只有一行,并且没有大括号,不用写return关键字,单行代码的执行结果会自动返回,如果添加了大括号,或者有多行代码,必须通过return关键字返回执行结果
4.3 lambda表达式变量捕获
lambda表达式内对变量的使用和原来稍有区别。
public class Test {
public static void main(String[] args) {
Test test = new Test();
test.testInnerClass();
test.testLambda();
}
String s1 = "全局变量";
//匿名内部类对变量的访问
public void testInnerClass(){
String s2 = "局部变量";
new Thread(new Runnable() {
String s3 = "内部变量";
@Override
public void run() {
//this表示当前内部类型的对象
//System.out.println(this.s1);
System.out.println(s1);
//局部变量的访问,不能对局部变量进行修改,默认final类型
System.out.println(s2);
//s2 = "123";
System.out.println(s3);
System.out.println(this.s3);
}
}).start();
}
// lambda表达式获取变量
public void testLambda(){
String s2 = "局部变量Lambda";
new Thread(()->{
String s3 = "内部变量Lambda";
//this表示当前方法所在类型的对象
System.out.println(this.s1);
System.out.println(s1);
//局部变量的访问,不能对局部变量进行修改,默认final类型
System.out.println(s2);
//s2 = "123";
s3 = "内部变量Lambda new";
System.out.println(s3);
}).start();
}
}
变量访问总结:
lambda表达式匿名内部类优化了this关键字,不在单独建立对象作用域,表达式本身就是所属类型的对象,在语法语义上更加简洁。
4.4 lambda表达式方法重载的影响
这里需要注意,在前面的例子中还没有涉及方法重载的问题,lambda表达式方式会自动匹配类型(传参可以省去类型,执行时JVM会自动去匹配类型),但是重载的需要注意类型转换。
public class Test {
/**
* 定义两个接口,里面方法名一样
*/
@FunctionalInterface
interface Param1{
void outInfo(String info);
}
@FunctionalInterface
interface Param2{
void outInfo(String info);
}
//定义重载方法
public void lambdaMethod(Param1 param1){
param1.outInfo("--param1--");
}
public void lambdaMethod(Param2 param2){
param2.outInfo("--param2--");
}
public static void main(String[] args) {
Test test = new Test();
//匿名内部类方式
test.lambdaMethod(new Param1() {
@Override
public void outInfo(String info) {
System.out.println(info);
}
});
test.lambdaMethod(new Param2() {
@Override
public void outInfo(String info) {
System.out.println(info);
}
});
//lambda表达式方式,这里需要类型提示
test.lambdaMethod((Param1) (info)->{
System.out.println(info + "--lambda--");
});
//lambda表达式方式,这里需要类型提示
test.lambdaMethod((Param2) (info)->
System.out.println(info + "--lambda--")
);
}
}
5 小结
这篇仅仅是入门级的lambda使用,后续会完成lambda表达式在集合中的使用。