JAVA8 最实用的新特性详解!
接口的默认方法(Default Methods for Interfaces)
在JDK8之前,接口不能定义任何实现,这意味着之前所有的JAVA版本中,接口制定的方法是抽象的,不包含方法体。从JKD8开始,添加了一种新功能-默认方法。默认方法允许接口方法定义默认实现,而所有子类都将拥有该方法及实现。
用default修饰,这类方法就是默认方法。
例子:
/**
* 在Java8中,允许在接口中包含带有具体实现的方法,使用default修饰,这类方法就是默认方法。
*/
public interface defaultMethodDemo {
public abstract int add(int i);
//default 关键字向接口添加非抽象方法实现
default int multiply(int i,int b){
return i*b;
}
}
接口中除了抽象方法 add() 还定义了默认方法 multiply() 。 实现该接口的类只需要实现抽象方法add() 。 默认方法multiply() 可以直接使用。当然你也可以直接通过接口创建对象,然后实现接口中的默认方法就可以了,我们通过代码演示一下这种方式。
public class test {
public static void main(String[] args) {
//通过匿名内部类来访问接口
defaultMethodDemo defaultMethodDemo = new defaultMethodDemo() {
@Override
public int add(int i) {
return multiply(i, 100);
}
};
int add = defaultMethodDemo.add(12);
System.out.println(add); //1200
//直接调用默认方法
int multiply = defaultMethodDemo.multiply(4, 5);
System.out.println(multiply); //20
}
}
注:以上代码是以匿名内部类来定义接口的实现。大家也可以定义一个类实现接口 实现接口的抽象方法 然后直接new 实现类调用。
Lambda表达式(Lambda expressions)
有的小伙伴可能会问为什么我们要使用lambda表达式,lambda表达式采用的是函数式编程,无需关心如何实现,并且代码非常的简洁,清晰易懂,下面我们就通过一个例子来看下传统方式和使用lambda表达式实现线程
传统的模式使用线程:
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println("我执行了");
}
}
};
new Thread(runnable).start();
System.out.println("提前结束");
用lambda实现线程:
new Thread(()-> {System.out.println("我执行了");}).start();
//其实还可以写的更简单
//对于函数体只有一行代码的,你可以去掉大括号{}以及return关键字
new Thread(()-> System.out.println("我执行了")).start();
可以从上面的例子中看到,传统模式写的代码很多,比较麻烦,而使用lambda来实现,非常的简单,也非常的清晰,并且是面向函数式的,无须关心它实现了内部的接口。
函数式接口(Functional Interfaces)
Java 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持Lambda。最终采取的方法是:增加函数式接口的概念。“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。 像这样的接口,可以被隐式转换为lambda表达式。java.lang.Runnable 与 java.util.concurrent.Callable 是函数式接口最典型的两个例子。Java 8增加了一种特殊的注解@FunctionalInterface,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。
一般建议在接口上使用@FunctionalInterface 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的,如下图所示。
正确实例:
方法和构造函数引用(Method and Constructor References)
先给大家写个例子:
@FunctionalInterface
public interface Demo<R>{
R test(String value);
}
public static void main(String[] args) {
//lambda表达式写法
Demo<Integer> test1 = (value) -> Integer.valueOf(value);
System.out.println(test1.test("121412"));
//lambda表达式静态方法引用
Demo<Integer> test2 = Integer::valueOf;
System.out.println(test1.test("121412"));
}
Java 8允许您通过 :: 关键字传递方法或构造函数的引用。 上面的示例显示了如何引用静态方法。 但我们也可以引用对象方法:
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
@FunctionalInterface
public interface Demo<R>{
R test(String value);
}
public static void main(String[] args) {
Something something = new Something();
Demo<String> demo=something::startsWith;
String test = demo.test("112412");
System.out.println(test);
}
接下来看看构造函数是如何使用::关键字来引用的,首先我们定义一个包含多个构造函数的简单类:
public class Person {
String name;
String age;
Person() {}
public Person(String name, String age) {
this.name = name;
this.age = age;
}
}
@FunctionalInterface
interface PersonFactory<P extends Person> {
P create(String name, String age);
}
public static void main(String[] args) {
PersonFactory<Person> personFactory = Person::new;
Person person = personFactory.create("JAVA8新特性", "20");
}
我们只需要使用 Person::new 来获取Person类构造函数的引用,Java编译器会自动根据PersonFactory.create方法的参数类型来选择合适的构造函数。
Lamda 表达式作用域
局部变量:
我们可以直接在 lambda 表达式中访问外部的局部变量
@FunctionalInterface
public interface Demo<R>{
R test(String value);
}
public static void main(String[] args) {
final int num=1;
Demo<Integer> test = (value) -> Integer.valueOf(value+num);
Integer test1 = test.test("11");
}
这里的变量num可以不用声明为final,该代码同样正确:
//具有隐性的final
int num=1;
Demo<Integer> test = (value) -> Integer.valueOf(value+num);
Integer test1 = test.test("11");
//具有隐性的final
int num=1;
Demo<Integer> test = (value) ->Integer.valueOf(value+num);
Integer test1 = test.test("11");
num=3;
//编译会报错
//Error:(102, 64) java: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量
访问字段和静态变量:
与局部变量相比,我们对lambda表达式中的实例字段和静态变量都有读写访问权限。 该行为和匿名对象是一致的。
class Lambda {
static int outerStaticNum;
int outerNum;
void testScopes() {
Demo<String> stringConverter1 = (from) -> {
outerNum = 23;
return String.valueOf(from);
};
Demo<String> stringConverter2 = (from) -> {
outerStaticNum = 72;
return String.valueOf(from);
};
}
@FunctionalInterface
public interface Demo<R>{
R test(String value);
}
}
访问默认接口方法:
无法从 lambda 表达式中访问默认方法,故以下代码无法编译
匿名内部类可以:
内置函数式接口
JDK 1.8 API包含许多内置函数式接口。 其中一些借口在老版本的 Java 中是比较常见的比如: Comparator 或Runnable,这些接口都增加了@FunctionalInterface注解以便能用在 lambda 表达式上。
但是 Java 8 API 同样还提供了很多全新的函数式接口来让你的编程工作更加方便,有一些接口是来自 Google Guava 库里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上使用的。
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer | T | void | 对类型T参数操作,无返回结果,包含方法 void accept(T t) |
Supplier | 无 | T | 返回T类型参数,方法时 T get() |
Function | T | R | 对类型T参数操作,返回R类型参数,包含方法 R apply(T t) |
Predicate | T | boolean | 断言型接口,对类型T进行条件筛选操作,返回boolean,包含方法 boolean test(T t) |
Predicate:
Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。
//predicate<T> 断言型接口
//boolean test(T t)
//接受一个参数 返回boolean
public void test1(){
boolean b = testPredicate("1231242", (str) -> str.length() > 3);
System.out.println(b); //true
}
public boolean testPredicate(String str,Predicate<String> predicate){
return predicate.test(str);
};
Functions:
Function 接口接受一个参数并生成结果。
//Function<T,R> 函数型接口
//R apply(T t) 接口接受一个参数并生成结果。
public void test2(){
String test = test2Function("这是JAVA8新特性", (str) -> str.substring(2));
System.out.println(test); //JAVA8新特性
}
public String test2Function(String str,Function<String,String> function){
return function.apply(str);
};
Consumer:
接口表示要对单个输入参数执行的操作。
//Consumer<T> 消费型接口
//void accept(T t)
//没有返回值
@Test
public void test4(){
//不使用
test4Consumer("aaa",(str)-> System.out.println(str));
//使用方法引用
test4Consumer("aaa",System.out::println);
}
public void test4Consumer(String str,Consumer<String> consumer){
consumer.accept(str);
};
Suppliers:
Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。
@Test
//Supplier<T>:供给型接口
// T get();
//场景:产生指定个整数,并放入集合中
public void test3(){
List<Integer> list= test3Supplier(10, ()->(int)(Math.random()*100));
//forEach(Consumer<T>) forEach调用的就是消费型接口 调用了方法accept
list.forEach(System.out::println);
}
public List<Integer> test3Supplier(int str,Supplier<Integer> supplier){
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < str; i++) {
list.add(supplier.get());
}
return list;
};
Streams(流)
java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection 的子类,List 或者 Set, Map 不支持。Stream 的操作可以串行执行或者并行执行。
首先看看Stream是怎么用,首先创建实例代码的用到的数据List:
List<String> stringList = new ArrayList<>();
stringList.add("aaa");
stringList.add("bbb");
stringList.add("ccc");
stringList.add("ddd");
stringList.add("eee");
stringList.add("fff");
stringList.add("ggg");
stringList.add("hhh");
stringList.add("aaa");
stringList.add("bbb");
Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。下面几节将详细解释常用的Stream操作:
Filter(过滤)
过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作。
// 测试 Filter(过滤)
//filter(predicate<T>) 方法使用了断言型函数 只返回true or false
//aaa aaa1 过滤掉为false的结果
stringList
.stream()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
forEach 是为 Lambda 而设计的,保持了最紧凑的风格。而且 Lambda 表达式本身是可以重用的,非常方便。
Sorted(排序)
排序是一个 中间操作,返回的是排序好后的 Stream。如果你不指定一个自定义的 Comparator 则会使用默认排序。
// 测试 Sorted(排序)
//sorted 需要提供 Comparator接口 不指定则会默认排序
//aaa
//aaa1
//bbb
//bbb
//ccc
//ddd
//eee
//fff
//ggg
//hhh
stringList.stream().sorted(String::compareTo).forEach(System.out::println);
需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:
Map(映射)
中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
下面的示例展示了将字符串转换为大写字符串。你也可以通过map来将对象转换成其他类型,map返回的Stream类型是根据你map传递进去的函数的返回值决定的。
//Map(映射)
//中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
stringList.stream().map(String::toUpperCase).sorted().forEach(System.out::println);
Match(匹配)
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是 最终操作 ,并返回一个 boolean 类型的值。
//Map(映射)
//中间操作 map 会将元素根据指定的 Function 接口来依次将元素转成另外的对象。
stringList.stream().map(String::toUpperCase).sorted().forEach(System.out::println);
// 测试 Match (匹配)操作
//方法使用了断言型函数 predicate
//allMatch表示,判断条件里的元素,所有的都是,返回true
boolean test1 = stringList.stream().allMatch((str) ->
str.startsWith("a"));
System.out.println(test1); //false
//anyMatch表示,判断的条件里,任意一个元素成功,返回true
boolean test2 = stringList.stream().anyMatch((str) ->
str.startsWith("a"));
System.out.println(test2); //true
//noneMatch跟allMatch相反,判断条件里的元素,所有的都不是,返回true
boolean test3 = stringList.stream().anyMatch((str) ->
str.startsWith("a"));
System.out.println(test3); //true
Count(计数)
计数是一个 最终操作,返回Stream中元素的个数,返回值类型是 long。
//测试 Count (计数)操作
long b = stringList.stream().filter((s) -> s.startsWith("b")).count();
System.out.println(b); //2