JAVA8 新特性
在学习JAVA8 Lambda之前,必须先了解一下JAVA8中与Lambda相关的新特性,不然对于一些概念会感到比较陌生。
1、 接口的默认方法和静态方法
Java 8允许我们给接口添加一个默认方法,用default
修饰即可。默认方法可以重写,也可以不用重写。这就是和抽象方法的区别,在用法上,没有其他区别。
public interface IMyInterface {
void onMethond(String str);//这是一个抽象方法
default String onDefalutMethod(){//这是一个默认方法
return "这是一个默认方法";
}
}
//重写默认方法
public class MyImpl1 implements IMyInterface {
@Override
public void onMethond(String str) {
// TODO Auto-generated method stub
}
@Override
public String onDefalutMethod() {
return "重写默认方法";
}
}
//不重写默认方法
public class MyImpl2 implements IMyInterface {
@Override
public void onMethond(String str) {
// TODO Auto-generated method stub
}
}
此外Java 8还允许我们给接口添加一个静态方法,用static
修饰即可。
public interface IMyInterface {
void onMethond(String str);//这是一个抽象方法
default String onDefalutMethod(){//这是一个默认方法
return "这是一个默认方法";
}
static String onStaticMethod(){
return "这是一个静态方法";
}
}
2、 函数式接口(Functional Interface)
什么叫函数式接口?他和普通接口有什么区别?
“函数式接口”是指仅仅只包含一个抽象方法的接口(可以包含默认方法和静态方法),其他特征和普通接口没有任何区别,Java中Runnalbe,Callable等就是个函数式接口;。我们可以给一个符合函数式接口添加@FunctionalInterface
注解,这样就显式的指明该接口是一个函数式接口,如果不是,编译器会直接提示错误。当然你也可以不用添加此注解。添加的好处在于,由于Lambda表达式只支持函数式接口,如果恰好这个接口被应用于Lambda表达式,某天你手抖不小心添加了个抽象方法,编译器会提示错误。
//显式指明该接口是函数式接口
@FunctionalInterface
public interface IMyInterface {
void onMethond(String str);//这是一个抽象方法
default String onDefalutMethod(){//这是一个默认方法
return "这是一个默认方法";
}
static String onStaticMethod(){
return "这是一个静态方法";
}
}
3、方法与构造函数引用
Java 8 允许你使用::
关键字来引用已有Java类或对象的方法或构造器。::
的诞生和Lambda一样都是来简化匿名内部类的写法的,所以::
必须配合函数式接口一起用。使用::
操作符后,会返回一个函数式接口对象,这个接口可以自己定义,也可以直接使用系统提供的函数式接口,系统提供的后面会单独介绍。请注意,这一节的内容会让你觉得这些用法蛋疼无比,这是因为还没有开始介绍Lambda表达式,::
和Lambda结合使用才是王道,希望你还能坚持到介绍Lambda表达式^_^。
假如有个Person类如下,以下的例子都以这个Person类为基础讲解。
public class Person {
private String name;
private int age;
public Person(){
}
public Person(String name,int age){
this.name=name;
this.age=age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public static String show(String str){
System.out.println("----->输入"+str);
return "----->返回"+str;
}
}
- 使用
::
关键字初始化构造函数,返回的是一个函数式接口,这个接口可以自己定义,也可以直接使用系统提供的函数式接口。现在先来定义一个函数式接口
@FunctionalInterface
public interface PersonSupply {
Person get();
}
PersonSupply sp=Person::new;
Person person=sp.get();
PersonSupply sp=Person::new;
等价于下面
PersonSupply sp=new PersonSupply{
public Person get(){
Person person=new Person();
return person;
}
}
怎么定义这个函数式接口呢?首先我们要看::
后面,可知,默认构造不需要参数,所以我们的get()定义为无参,但是我们需要Person对象,所以返回值为Person。
当然为了使这个接口更通用,我们可以定义成如下形式
@FunctionalInterface
public interface PersonSupply<T> {
T get();
}
PersonSupply<Person> sp=Person::new;
Person person=sp.get();
java自带Supplier
的接口就是这样的,后面会做介绍。直接使用Supplier
如下
Supplier<Person> sp=Person::new;
Person person=sp.get();
怎么初始化带参构造函数?
首先写一个函数式接口,由于需要参数所以get()里面输入参数,返回Person,如下。
@FunctionalInterface
public interface PersonSupply<P extends Person> {
P get(String name, int age);
}
PersonSupply<Person> sp=Person::new;
Person person=sp.get("maplejaw",20);
PersonSupply<Person> sp=Person::new;
等价于下面
PersonSupply<Person> sp=new PersonSupply<Person>{
public Person get(String name, int age);{
Person person=new Person(name,age);
return person;
}
}
- 使用
::
关键字引用静态方法同样,写一个函数式接口,该静态方法需要参数,所以get()里传入参数,需要返回参数,所以返回String。
@FunctionalInterface
public interface PersonFactory {
String get(String str);
}
PersonFactory pf=Person::show;
pf.show("哈哈哈");
PersonFactory pf=Person::show;
等价于下面
PersonFactory pf=new PersonFactory{
public String get(String str){
return Person.show(str);
}
}
- 使用
::
关键字引用普通方法比较特殊。如果要调用的方法没有参数,可以用
Class::method
形式调用,但是这时需要传入一个Person实例,这时函数式接口这样写,在get()中传入Person类的实例,返回String。
@FunctionalInterface
public interface PersonFactory {
String get(Person person);
}
PersonSupply<Person> sp=Person::new;
Person person=sp.get("maplejaw", 20);
PersonFactory pf=Person::getName;
System.out.println("--->"+sp.get(penson));
PersonFactory pf=Person::getName;
等价于下面
PersonFactory pf=new PersonFactory{
public String get(Person person){
return person.getName();
}
}
也可以以instance::method
形式调用。这时get()不需要传参。返回String。
@FunctionalInterface
public interface PersonFactory {
String get();
}
PersonSupply<Person> sp=Person::new;
Person person=sp.get("maplejaw", 20);
PersonFactory pf=person::getName;
System.out.println("--->"+pf.get());
PersonFactory pf=person::getName;
等价于下面
Person person=new Person("maplejaw",20);
PersonFactory pf=new PersonFactory{
public String get( ){
return person.getName();
}
}
如果要调用的方法有参数,必须用instance::method
形式调用,这时函数式接口这样写,set传入参数。
@FunctionalInterface
public interface PersonFactory {
void set(String name);
}
PersonSupply<Person> sp=Person::new;
Person person=sp.get("maplejaw", 20);
PersonFactory pf=person::setName;
pf.set("maplejaw");
PersonFactory pf=person::setName;
等价于下面
Person person=new Person("maplejaw",20);
PersonFactory pf=new PersonFactory{
public void set(String name){
return person.setName(name);
}
}
4、JAVA8 API内建的函数式接口
还记得前面提到的Supplier
函数式接口吗,它就是API内建的函数式接口之一,下面将介绍一些常用的内建函数式接口。
- Supplier
Supplier 提供者,不接受参数,有返回值
Supplier<Person> Supplier=new Supplier<Person>() {
@Override
public Person get() {
return new Person();
}
};
Supplier<Person> sp=Person::new;
- Function 一个参数,一个返回值。常用于数据处理。
Function<String, Integer> function=new Function<String, Integer>(){
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
} ;
Function<String, Integer> function=Integer::parseInt;
- Consumer 消费者,只有一个参数,没有返回值
Consumer<String> consumer=new Consumer<String>() {
@Override
public void accept(String t) {
}
};
- Comparator 比较类
Comparator<Integer> comparator=new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// TODO Auto-generated method stub
return o1-o2;
}
};
- Predicate
Predicate 接口,抽象方法只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):
Predicate<String> predicate=new Predicate<String>() {
@Override
public boolean test(String t) {
return t.startsWith("h");
}
};
boolean b=predicate.test("hahaha");//判断是否符合条件
Predicate<String> predicate = String::isEmpty;
boolean b=predicate.test("hahaha");//判断是否符合条件
- UnaryOperator 接收一个参数,返回一个参数,且参数类型相同
UnaryOperator<String> unaryOperator=new UnaryOperator<String>() {
@Override
public String apply(String s) {
// TODO Auto-generated method stub
return s;
}
};
- BinaryOperator 接收两个参数,返回一个参数,且参数类型相同
BinaryOperator<String> binaryOperator=new BinaryOperator<String>() {
@Override
public String apply(String t, String u) {
// TODO Auto-generated method stub
return t+u;
}
};
5、三个API
- Optional
Optional 这是个用来防止NullPointerException异常而引入的。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
Optional<String> optional = Optional.of("给你一个值");
optional.isPresent(); //判断是否为空值
optional.get(); //获取值 ,如果空值直接抛异常。
optional.orElse("返回空值"); //获取值 ,如果空值返回指定的值。
- Stream(流)
最新添加的Stream API(java.util.stream)把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
接触过RxJava的可能对下面的代码风格比较眼熟,Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。
怎么用Stream?Stream必须有数据源。那就给一个数据源。
List<String> list = new ArrayList<>(); list.add("ddd2"); list.add("aaa2"); list.add("bbb1"); list.add("aaa1"); list.add("aaa3"); list.add("bbb3"); list.add("ccc"); list.add("bbb2"); list.add("ddd1");
- forEach list新增的for循环方法,forEach是一个最终操作(只能放在最后)
//遍历打印数据 list.forEach(new Consumer<String>() { @Override public void accept(String t) { System.out.println("---->"+t); } });
打印结果如下
---->ddd2 ---->aaa2 ---->bbb1 ---->aaa1 ---->aaa3 ---->bbb3 ---->ccc ---->bbb2 ---->ddd1
- filter 过滤
list.stream() .filter(new Predicate<String>() { @Override public boolean test(String t) { return t.startsWith("a"); } }) .forEach(new Consumer<String>() { @Override public void accept(String t) { System.out.println("---->"+t); } });
打印结果如下
---->aaa2 ---->aaa1 ---->aaa3
- Sort 排序
list.stream() .sorted()//排序,如果不实现Comparator接口,则按默认规则排序 .filter(new Predicate<String>() { @Override public boolean test(String t) { return t.startsWith("a"); } }) .forEach(new Consumer<String>() { @Override public void accept(String t) { System.out.println("---->"+t); } }); list.stream() .sorted(new Comparator<String>() { @Override public int compare(String o1, String o2) { // TODO Auto-generated method stub return o1.compareTo(o2); } }) .filter(new Predicate<String>() { @Override public boolean test(String t) { return t.startsWith("a"); } }) .forEach(new Consumer<String>() { @Override public void accept(String t) { System.out.println("---->"+t); } });
- map
list.stream() .sorted(new Comparator<String>() { @Override public int compare(String o1, String o2) { // TODO Auto-generated method stub return o1.compareTo(o2); } }) .filter(new Predicate<String>() { @Override public boolean test(String t) { return t.startsWith("a"); } }) .map(new Function<String, String>() { @Override public String apply(String t) { // TODO Auto-generated method stub return t+"--->被我处理过了"; } }) .forEach(new Consumer<String>() { @Override public void accept(String t) { System.out.println("---->"+t); } });
---->aaa1--->被我处理过了 ---->aaa2--->被我处理过了 ---->aaa3--->被我处理过了
- Match 匹配,是一个最终操作
boolean b= list.stream() .anyMatch(new Predicate<String>() { @Override public boolean test(String t) { // TODO Auto-generated method stub return t.startsWith("a"); } }); System.out.println("----->"+b);//true boolean b= list.stream() .allMatch(new Predicate<String>() { @Override public boolean test(String t) { // TODO Auto-generated method stub return t.startsWith("a"); } }); System.out.println("----->"+b);//false
- Count 计数,是一个最终操作
long count = list.stream() .filter(new Predicate<String>() { @Override public boolean test(String t) { return t.startsWith("a"); } }) .count(); System.out.println(count); // 3
- Reduce 规约,最终操作
Optional<String> optional = list .stream() .sorted() .reduce(new BinaryOperator<String>() { @Override public String apply(String t, String u) { return t+u; } }); System.out.println("---->"+optional.get()); //---->aaa1aaa2aaa3bbb1bbb2bbb3cccddd1ddd2
- findFirst 提取第一个,最终操作
Optional<String> optional = list .stream() .sorted() .findFirst(); System.out.println("---->"+optional.get()); //aaa1
- forEach list新增的for循环方法,forEach是一个最终操作(只能放在最后)
- parallelStream(并行流)
并行化之后和之前的代码区别并不大。并且并行操作下,速度会比串行快。但是需要注意的是不用在并行流下排序,并行流做不到排序。
list.parallelStream()
.filter(new Predicate<String>() {
@Override
public boolean test(String t) {
// TODO Auto-generated method stub
return t.startsWith("a");
}
})
.forEach(new Consumer<String>() {
@Override
public void accept(String t) {
// TODO Auto-generated method stub
System.out.println("--->"+t);
}
});
list.parallelStream()
.sorted()
.forEach(new Consumer<String>() {
@Override
public void accept(String t) {
// TODO Auto-generated method stub
System.out.println("--->"+t);
}
});
//打印出来的并没有排序
Lambda 表达式
在上面你是不是觉得::
有时候挺好用的?可以不用再new接口,再也不用写烦人的匿名内部类了,比如
Supplier<Person> sp=Person::new;
但是::
的使用场景还是比较有限的。Lambda表达式的诞生就是为了解决匿名内部类中饱受诟病的问题的。
- 什么是Lambda表达式
Lambda表达式是Java8的一个新特性,它提供了一种更加清晰和简明的方式使用函数式接口(以前被叫作单一方法接口)。使用Lambda表达式能够更加方便和简单的使用匿名内部类,比如对于集合的遍历、比较、过滤等等。
- Lambda表达式格式
(type1 arg1, type2 arg2…) -> { body }
每个lambda都包括以下三个部分:
参数列表:(type1 arg1, type2 arg2…)
箭头: ->
方法体:{ body }
方法体既可以是一个表达式,也可以是一个语句块:
- 表达式:表达式会被执行然后返回执行结果。
- 语句块:语句块中的语句会被依次执行,就像方法中的语句一样,return语句会把控制权交给匿名方法的调用者。
表达式函数体适合小型lambda表达式,它消除了return关键字,使得语法更加简洁。
以下是一些例子
(int a, int b) -> { return a + b; }
( a, b) -> { return a + b; }
( a, b) -> a+b
() -> System.out.println("s")
(String s) -> { System.out.println(s); }
- 一个 Lambda 表达式可以有零个或多个参数
- 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)与(a)效果相同
- 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b) 或 (int a, int b)
- 空圆括号代表参数集为空。例如:()->{System.out.println(“s”);};
- 当只有一个参数,且其类型可推导时,圆括号()可省略。
- 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略,且不能以分号结尾
- 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中,每条语句必须以分号结尾。
Lambda表达式可以用来简化内部类写法,比如
//常规代码
Runnable runable=new Runnable() {
@Override
public void run() {
System.out.println("--->");
}
};
//Lambda表达式
Runnable runable=()->{System.out.println("--->");};
//启动一个线程
new Thread(()->{System.out.println("--->");}).start();
还记得前面介绍的Predicate接口吗?现在我们再来用Lambda表达式写一遍。
//一般写法
Predicate<String> predicate=new Predicate<String>() {
@Override
public boolean test(String t) {
return t.startsWith("h");
}
};
//Lambda写法
Predicate<String> predicate=(String s)->{s.startsWith("h");};
你现在是不是觉得Lambda表达式太神奇了?居然可以写出这么简洁的代码。
还记得内部类使用局部变量时需要把变量声明为final吗,Lambda表达式则不需要。不过虽然不用声明final,但也不允许改变值。
String s="sss";
new Thread(()->{
System.out.println(s);
}).start();
此外,内部类引用外部类也不用使用MainActivity.this,这种操蛋的代码了。
在Lambda表达式中this,指的就是外部类,因为根本就没有内部类的概念啊。
btn.setOnClickListener(()->{
Toast.makeText(this,"xxx",Toast.LENGTH_SHORT).show();
});
现在回过头来把前面Stream中的代码用Lambda表达式再写一遍。
list.stream()
.sorted((s1,s2)->s1.compareTo(s2))
.filter((s)->s.startsWith("a"))
.map((s)->s+"被我处理过了")
.forEach(s->System.out.println(s));
代码简洁的简直让人窒息。但是能不能更简洁一点呢?当然是可以的,首先我们检查一下哪里可以替换成::
关键字,然后作如下替换,是不是更简洁了。关于替换规则,请看前面的介绍。
list.stream()
.sorted((s1,s2)->s1.compareTo(s2))
.filter((s)->s.startsWith("a"))
.map((s)->s+"被我处理过了")
.forEach(System.out::println);
打印结果如下
aaa1被我处理过了
aaa2被我处理过了
aaa3被我处理过了
- 显示指定目标类型
如果Lambda表达式的目标类型是可推导的,就不用指定其类型,如果Lambda表达式的参数类型是可以推导,就不用指定参数类型。因为编译器可以根据上下文自动推导出其类型,然后进行隐式转换。但是,有些场景编译器是没法推导的。比如下面这样的,如果不显示指定类型,编译器就会提示错误
//接口一
public interface IMyInterface1 {
void opreate(String str);
}
//接口二
public interface IMyInterface2 {
void opreate(int i);
}
//Person类
public class Person类 {
private String name;
private int age;
public Test(String name, int age) {
this.name = name;
this.age = age;
}
public void opreate(IMyInterface1 inter){
inter.opreate(name);
}
public void opreate(IMyInterface2 inter){
inter.opreate(age);
}
}
//这样写是错误的,因为编译器无法推导出其目标类型
new Test("maplejaw",20).opreate((name)->System.out.println(name));
解决办法有两个
一、指定参数类型,但是如果两个接口的参数类型是一样的,就只能显示指定目标类型。
new Test("maplejaw",20).opreate((String name)->System.out.println(name));
二、指定目标类型
new Test("maplejaw",20).opreate((IMyInterface1) (s)->System.out.println(s));
new Test("maplejaw",20).opreate((IMyInterface1) System.out::println);
由于目标类型必须是函数式接口,所以如果想赋值给Object对象时,也必须显示转型。
Object runnable=(Runnable)()->{System.out.print("--->");};
关于Lambda表达式的介绍到此为止,想更深入了解推荐【深入理解Java 8 Lambda】这篇文章。
最后
当初学习Lambda表达式的时候,由于网上的资料比较零散,且直接用了JAVA8的新API来做演示,由于对新API不是很熟导致学习的时候走了一些弯路,看得一头雾水。所以决定把我的学习路线给记录下来,或许可以帮助部分人。
Lambda表达式是把双刃剑,让代码简洁的同时,降低了代码的可读性。但是作为程序员,追逐新技术的脚步不能停下。
本文参考了【深入理解Java 8 Lambda】和【JAVA8 十大新特性详解】两篇文章