Java8新特性
1.接口
1)增加default方法和static方法,这两种方法都可以有方法体
interface Interface1 {
static void st1() {System.out.println("static Interface1.st1()");}
default void df1() {System.out.println("default Interface1.df1()");}
}
2)default方法属于实例(对象访问),static方法属于类(接口,只可以通过接口名访问)
interface Interface1 {
static void st1() {System.out.println("static Interface1.st1()");}
default void df1() {System.out.println("default Interface1.df1()");}
}
public class Jdk8Test {
public static void main(String[] args) {
//static方法属于类,接口名访问
Interface1.st1();
//报错:default方法不可以通过接口名访问
//Cannot make a static reference to the non-static method df1() from the type Interface1
//Interface1.df1();
Interface1 obj1 = new Interface1() {};
//default方法属于实例,可以通过对象访问
obj1.df1();
//报错:static方法只可以通过接口名访问
//This static method of interface Interface1 can only be accessed as Interface1.st1
//obj1.st1();
}
}
3)接口里面的static方法不会被继承,static变量可以被继承,default方法可以被继承
interface Interface1 {
static void st1() {System.out.println("static Interface1.st1()");}
static String st_str1 = "ststr1";
default void df1() {System.out.println("default Interface1.df1()");}
}
interface Interface2 extends Interface1 {
static void st2() {System.out.println("static Interface2.st2()");}
default void df2() {System.out.println("default Interface2.df2()");}
}
public class Jdk8Test {
public static void main(String[] args) {
//报错,static方法不会被继承
//The method st1() is undefined for the type Interface2
//Interface2.st1();
//static 变量则可以被继承,且可以通过接口名与变量名访问
System.out.println(Interface2.st_str1);
System.out.println(new Interface2() {}.st_str1);;
//default方法可以被继承
new Interface2() {}.df1();
}
}
4)一个类实现多个具有相同签名的default方法的接口,如果这些接口间没有继承关系,则报错,如果不想让它报错,可以重写这些同名方法
实例1: interface Interface1 { default void df() {System.out.println("default Interface1.df1()");} } interface Interface2 { default void df() {System.out.println("default Interface2.df2()");} } //报错 //Duplicate default methods named df with the parameters () and () are inherited from the types Interface2 and Interface1 class Class1 implements Interface1,Interface2{} 实例2: interface Interface1 { default void df() {System.out.println("default Interface1.df1()");} } interface Interface2 extends Interface1{ default void df() {System.out.println("default Interface2.df2()");} } //上面加了继承,不再报错子接口的default方法覆盖父接口同名default方法 class Class1 implements Interface1,Interface2{} 实例3: interface Interface1 { default void df() {System.out.println("default Interface1.df1()");} } interface Interface2{ default void df() {System.out.println("default Interface2.df2()");} } //重写接口同名方法,将不会再报错,如果需要访问其中的一个default方法,可以在子类总使用<接口名>.super.方法名的方法指定访问哪一个 class Class1 implements Interface1,Interface2{ public void df() { //指定访问Interface1的default方法 Interface1.super.df(); } public void df2() { //指定访问Interface2的default方法 Interface2.super.df(); } } 实例4:接口的继承亦是如此 interface Interface1 { default void df() {System.out.println("default Interface1.df1()");} } interface Interface2{ default void df() {System.out.println("default Interface2.df2()");} } //重写接口同名方法,将不会再报错,如果需要访问其中的一个default方法,可以在子类总使用<接口名>.super.方法名的方法指定访问哪一个 interface Interface3 extends Interface1,Interface2{ default void df() { //指定访问Interface1的default方法 Interface1.super.df(); } default void df2() { //指定访问Interface2的default方法 Interface2.super.df(); } } //不重写将会报错 //Duplicate default methods named df with the parameters () and () are inherited from the types Interface2 and Interface1 interface Interface4 extends Interface1,Interface2{} 总之:上面的解决方法就是指定访问哪一个重名了的default方法,不过指定有一定的语法要求:
接口名.supper.重复的方法名;
5)如果一个接口只有一个抽象方法(包括继承的),那么这个接口是一个函数式接口
函数式接口可以使用Lambda表达式实现
如果接口使用了@FunctionalInterface 注解,表明这个接口是一个函数式接口,接口内必须有且只可以有一个抽象方法。
6)抽象方法可以在接口中实现(接口继承时在子接口的默认方法中实现,不可以在静态方法中实现)。
2.Lambda表达式
Lambda表达式只可以对函数式接口使用。
2.1 Lambda表达式三部分:
() :表示参数列表,不需要指定参数类型,会自动推断
-> :表示连接符
{} :表示方法体
如下代码:
@FunctionalInterface
interface UserTest{
void test();
}
public class TestLambda {
public static void main(String[] args) {
//java8前匿名内部类实现
UserTest ut = new UserTest() {
@Override
public void test() {
System.out.println("匿名内部类实现");
}
};
ut.test();
//java8后使用Lambda表达式实现
//() :表示参数列表,不需要指定参数类型,会自动推断
//-> :表示连接符
//{} :表示方法体
UserTest ut2 = () -> {
System.out.println("使用Lambda表达式实现");
};
ut2.test();
}
}
如果方法体只有一句话,{}可以简化掉,甚至有返回值的时候连return也可省略:
@FunctionalInterface
interface UserTest{
void test();
}
@FunctionalInterface
interface UserTest2{
String test();
}
public class TestLambda {
public static void main(String[] args) {
UserTest ut1 = () -> System.out.println("使用Lambda表达式实现UserTest");
ut1.test();
UserTest2 ut2 = () -> "使用Lambda表达式实现UserTest2";
System.out.println(ut2.test());
}
}
如果只有一个参数,()可以省去,两个及以上不可以省略():
@FunctionalInterface
interface UserTest3{
int test(int x);
}
public class TestLambda {
public static void main(String[] args) {
UserTest3 ut3 = x -> ++x;
System.out.println(ut3.test(1));
}
}
输出结果:
2
Lambda表达式访问的外部变量是final的。
Lambda的带来的优点:
可以把java代码作为参数传入,有助于提高代码内聚,Lambda实际上遵循匿名内部类的规则,但不是匿名内部类(Lambda表达式编译后不会生成class文件)
2.2 方法的引用
1)引用实例方法:
方法引用时会自动把调用方法的时候的参数,全部全给引用的方法
<函数式接口> <变量名> = <实例> ::<实例方法名>
<变量名>.<函数式接口方法名>([实参]);
2)引用类方法:
方法引用时会自动把调用方法的时候的参数,全部全给引用的方法
<函数式接口> <变量名> = <类> ::<类方法名>
<变量名>.<函数式接口方法名>([实参]);
简单示例代码:
import java.util.Arrays;
interface MethodRef{
void test(String s);
}
interface MethodRef2{
void test(int[] arr);
}
public class TestFunctionRef {
public static void main(String[] args) {
//Lambda表达式
MethodRef m1 = s -> System.out.println(s);
m1.test("字符串的");
//使用方法引用 :引用实例方法
//这里System.out是一个实例
MethodRef m2 = System.out::println;
m2.test("方法的引用");
//引用类的方法
//这里sort方法是Arrays工具类的静态方法。
MethodRef2 m2_1 = Arrays::sort;
int[] arr = new int[] {3,2,4,7,1,6,5};
m2_1.test(arr);
System.out.println(Arrays.toString(arr));
}
}
3)引用类的实例方法
定义、调用接口方法的时候,需要多传入一个参数,并且参数的类型和引用示例方法的类型必须一致
把第一个参数作为引用的实例,后面的每个参数全部传给引用的方法
interface <函数式接口> {
<返回值类型> <方法名> (<类1> <名称>[,其他参数 ...]);
}
<函数式接口> <变量名> = <类1>::<实例方法名>; //注意两个类1要是一样的或者兼容的
<变量名> . <方法名>(<类1的实例 [,其他参数]>);
简单代码示例:
import java.io.PrintStream;
interface MethodRefX{
void test(PrintStream ps,String s);
}
public class TestFunctionRef2 {
public static void main(String[] args) {
MethodRefX rx = PrintStream::println;
rx.test(System.out, "第二参数");
}
}
上面代码含义为:用类型为PrintStream的实例调用方法名为println的方法,参数为test方法的第二个【及以后】的参数,System.out为PrintStream的一个实例。
4)构造器引用
引用构造器,根据函数式接口的方法名来推断引用哪一个构造器
interface FunctionRefY{
String test(char[] chars);
}
public class TestFunctionRefy {
public static void main(String[] args) {
//引用String类型的public String(char value[])构造器
FunctionRefY fy = String :: new;
String ok = fy.test(new char[] {'o','k'});
System.out.println(ok);
}
}
3.Stream
Stream API是Java 8中加入的一套新的API,主要用于处理集合操作。
Stream(流)是一个来自数据源的元素队列并支持聚合操作
元素:是特定类型的对象,形成一个队列,Java中的Stream并不会存储元素,而是按需计算。
数据源:流的来源,可以是集合,数组,I/O channel,产生器generator 等。
聚合操作,类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代:以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,通过访问者模式(Visitor)实现。
Stream API:
1)创建流
stream() 为集合创建流。
parallelStream() 为集合创建并行流。
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> stream = list.parallelStream();
并行流:
对集合进行相同的操作,使用并行流一般会比普通遍历要快一些,快的程度与集合长度和每一次操作耗时均有关系。如下代码:
List ls = new ArrayList();
for(int i = 1;i <= 100;i ++)
ls.add(i);
Stream stream = ls.parallelStream();
long t1 = System.currentTimeMillis();
stream.forEach(a -> {
try{
Thread.sleep(100);
}catch(Exception e){}
});
long t2 = System.currentTimeMillis();
for(int i : ls){
try{
Thread.sleep(100);
}catch(Exception e){}
}
long t3 = System.currentTimeMillis();
System.out.println((t3-t2)/(double)(t2-t1));
最后输出结果约等于7.5
2)forEach
Stream 提供新的方法‘forEach’来迭代每一个数据
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
3)map
map方法用于映射每一个元素到对应的结果,并返回一个新的流。如下代码:
List<Integer> ls = new ArrayList<>();
for(int i = 0;i < 5; i ++) ls.add(i);
Stream<Integer> stream = ls.stream();
Stream<Integer> stream2 = stream.map(n -> n*n);
List<Integer> ls2 = stream2.collect(Collectors.asList());
print(ls);
print(ls2);
最后输出:
ls:0,1,2,3,4
ls2:0,1,4,9,16
4)filter
filter 发发发用于通过过滤条件过滤元素,返回一个新的流。如下代码:
List<String> ls = Arrays.asList("aa","bb","","cc");
Stream stream = ls.stream();
Stream stream2 = stream.filter(str -> !str.isEmpty());
List<String> ls2 = stream2.collect(Collectors.asList());
print(ls);
最后输出结果:
ls:"aa","bb","cc"
5)limit
limit方法用于获取指定数量的流
List<Integer> ls = new ArrayList<>();
for(int i = 0;i < 50; i ++) ls.add(i);
Stream<Integer> stream = ls.stream();
Stream<Integer> stream2 = stream.limit(5);
print(stream2)
Stream<Integer> stream3 = stream.limit(5);
print(stream3)
最后输出结果:
stream2:0,1,2,3,4
Exception 。。。
第二次调用报错说明同一个流只可以调用limit方法一次
6)sorted
sorted对流进行排序,返回排序后的流
Random r = new Random();
r.ints().limit(10).sorted().forEach(System.out::println);
7)skip
跳过前面几个元素
List<Integer> ls = new ArrayList<>();
for(int i = 0;i < 30; i ++) ls.add(i);
Stream<Integer> stream = ls.stream();
Stream<Integer> skip= stream.skip(5);
skip.forEach(System.out::println);
syso("------------------");
stream .forEach(System.out::println);
输出:
10 ... 19
--------------
illegalStateException:stream has already closed
由报错信息可以确定,skip返回的还是原来的流,只不过读取位置跳过了10个元素
8)toArray 转换为数组
9)reduce
规约操作,将流中元素进行合并,形成一个新的值,常见规约有求和运算,如下求取总价:
List<Book> bks = new ArrayList<>();
for(int i = 0;i < 10;i ++)bks.add("bk-"+i,new Random().nextInt());
Optional<Inteter> opt = bks.stream().map(Book::getPrice).reduce((m,n) -> m+n);
syso(opt.get());
//opt.get()可以获取到规约后的总价
10)查询匹配
anyMatch:查询是否有符合指定匹配规则的,返回布尔值
allMatch:查询是否全部匹配指定匹配规则,返回布尔值
noneMatch:查询是否都不匹配指定规则,返回布尔值
boolean hasMatch = Stream.of("Java", "C#", "PHP", "C++", "Python").anyMatch(s -> s.equals("Java"));
findFirst(),findAny()返回的都是第一个元素,建议使用findAny()
Optional<String> element = Stream.of("Java", "C#", "PHP", "C++", "Python").findAny();
syso(element.get());//输出java
11)数据收集
数据收集是流式数据处理的终端处理,与中间处理不同,终端处理会消耗流,终端处理之后,流会关闭。
数据收集主要使用collect方法
该方法也属于归约操作,像reduce()方法那样可以接收各种做法作为参数,将流中的元素累积成一个汇总结果,具体的做法是通过定义新的Collector接口来定义的。
规约汇总:
取最值,计数等操作。
分组:
和关系型数据库类似,流也提供了类似数据库的group by的特性,由Collectors.groupingBy()方法提供
groupingBy()方法还支持多级分组,他有一个重载方法,除了接收一个Function类型的参数外,还接收一个Collector类型的参数
示例代码:
package Test_C02; import java.util.ArrayList; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import com.alibaba.fastjson.JSON; public class StreamGroup { static class Book{ private String name; private Integer price; private Integer tag; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getPrice() { return price; } public void setPrice(Integer price) { this.price = price; } public Integer getTag() { return tag; } public void setTag(Integer tag) { this.tag = tag; } public Book(String name,Integer price,Integer tag) { this.name = name; this.price = price; this.tag = tag; } } public static void main(String[] args) { List<Book> bks = new ArrayList<StreamGroup.Book>(); for(int i = 0;i < 10;i++) bks.add(new Book("nm-"+i, i, i%3)); //计数 Long count = bks.stream().filter(bk -> bk.getPrice()>5).collect(Collectors.counting()); System.out.println("价格大约5的数量:"+count); //最贵的书 Book maxPriceBk = bks.stream().collect(Collectors.maxBy(Comparator.comparing(Book::getPrice))).get(); System.out.println("最贵的书:"+JSON.toJSONString(maxPriceBk)); //最便宜的书 Book minPriceBk = bks.stream().collect(Collectors.minBy(Comparator.comparing(Book::getPrice))).get(); System.out.println("最便宜的书:"+JSON.toJSONString(minPriceBk)); //分组 Map<Integer, List<Book>> group = bks.stream().collect(Collectors.groupingBy(Book::getTag)); System.out.println("以tag进行分组:"+JSON.toJSONString(group)); //多级分组 Map<Integer, Map<String, List<Book>>> group2 = bks.stream().collect(Collectors.groupingBy(Book::getTag,Collectors.groupingBy(bk -> bk.getPrice() > 5 ? "A" : "B"))); System.out.println("多级分组:"+JSON.toJSONString(group2)); } }