Java8新特性

Java8新特性java

Java8主要的新特性涵盖:函数式接口、Lambda 表达式、集合的流式操做、注解的更新、安全性的加强、IO\NIO 的改进、完善的全球化功能等。express

一、函数式接口数组

    Java 8 引入的一个核心概念是函数式接口(Functional Interfaces)。经过在接口里面添加一个抽象方法,这些方法能够直接从接口中运行。若是一个接口定义个惟一一个抽象方法,那么这个接口就成为函数式接口。同时,引入了一个新的注解:@FunctionalInterface。能够把他它放在一个接口前,表示这个接口是一个函数式接口。这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。在接口中添加了 @FunctionalInterface 的接口,只容许有一个抽象方法,不然编译器也会报错。安全

java.lang.Runnable 就是一个函数式接口。微信

@FunctionalInterface
public interface Runnable {
public abstract void run();
}

二、Lambda 表达式数据结构

        函数式接口的重要属性是:咱们可以使用 Lambda 实例化它们,Lambda 表达式让你可以将函数做为方法参数,或者将代码做为数据对待。Lambda 表达式的引入给开发者带来了很多优势:在 Java 8 以前,匿名内部类,监听器和事件处理器的使用都显得很冗长,代码可读性不好,Lambda 表达式的应用则使代码变得更加紧凑,可读性加强;Lambda 表达式使并行操做大集合变得很方便,能够充分发挥多核 CPU 的优点,更易于为多核处理器编写代码;app

Lambda 表达式由三个部分组成:第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;第二部分为一个箭头符号:->;第三部分为方法体,能够是表达式和代码块。语法以下:dom

1. 方法体为表达式,该表达式的值做为返回值返回。ide

(parameters) -> expression

2. 方法体为代码块,必须用 {} 来包裹起来,且须要一个 return 返回值,但若函数式接口里面方法返回值是 void,则无需返回值。函数

(parameters) -> { statements; }
例如,下面是使用匿名内部类和 Lambda 表达式的代码比较。

下面是用匿名内部类的代码:

button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.print("Helllo Lambda in actionPerformed");
}
});
下面是使用 Lambda 表达式后:
button.addActionListener(
\\actionPerformed 有一个参数 e 传入,因此用 (ActionEvent e)
(ActionEvent e)-> 
System.out.print("Helllo Lambda in actionPerformed")
);

上面是方法体包含了参数传入 (ActionEvent e),若是没有参数则只需 ( ),例如 Thread 中的 run 方法就没有参数传入,当它使用 Lambda 表达式后:

Thread t = new Thread(
\\run 没有参数传入,因此用 (), 后面用 {} 包起方法体
() -> {
 System.out.println("Hello from a thread in run");
}
);
经过上面两个代码的比较能够发现使用 Lambda 表达式能够简化代码,并提升代码的可读性。
为了进一步简化 Lambda 表达式,可使用方法引用。例如,下面三种分别是使用内部类,使用 Lambda 表示式和使用方法引用方式的比较:
//1. 使用内部类
Function<Integer, String> f = new Function<Integer,String>(){
@Override
public String apply(Integer t) {
return null;
}
};
//2. 使用 Lambda 表达式
Function<Integer, String> f2 = (t)->String.valueOf(t); 
//3. 使用方法引用的方式
Function<Integer, String> f1 = String::valueOf;

要使用 Lambda 表达式,须要定义一个函数式接口,这样每每会让程序充斥着过量的仅为 Lambda 表达式服务的函数式接口。为了减小这样过量的函数式接口,Java 8 在 java.util.function 中增长了很多新的函数式通用接口。例如:

Function<T, R>:将 T 做为输入,返回 R 做为输出,他还包含了和其余函数组合的默认方法。

Predicate<T> :将 T 做为输入,返回一个布尔值做为输出,该接口包含多种默认方法来将 Predicate 组合成其余复杂的逻辑(与、或、非)。

Consumer<T> :将 T 做为输入,不返回任何内容,表示在单个参数上的操做。

例如,People 类中有一个方法 getMaleList 须要获取男性的列表,这里须要定义一个函数式接口 PersonInterface:
interface PersonInterface {
 public boolean test(Person person);
}
public class People {
 private List<Person> persons= new ArrayList<Person>();
 public List<Person> getMaleList(PersonInterface filter) {
 List<Person> res = new ArrayList<Person>();
 persons.forEach(
 (Person person) -> 
 {
 if (filter.test(person)) {//调用 PersonInterface 的方法
 res.add(person);
 }
 }
 );
 return res;
 }
}
为了去除 PersonInterface 这个函数式接口,能够用通用函数式接口 Predicate 替代以下:
class People{
 private List<Person> persons= new ArrayList<Person>();
 public List<Person> getMaleList(Predicate<Person> predicate) {
 List<Person> res = new ArrayList<Person>();
 persons.forEach(
 person -> {
 if (predicate.test(person)) {//调用 Predicate 的抽象方法 test
 res.add(person);
 }
 });
 return res;
 }
}

 

 

三、接口的加强

        Java 8 对接口作了进一步的加强。在接口中能够添加使用 default 关键字修饰的非抽象方法。还能够在接口中定义静态方法。现在,接口看上去与抽象类的功能愈来愈相似了。

默认方法

Java 8 还容许咱们给接口添加一个非抽象的方法实现,只须要使用 default 关键字便可,这个特征又叫作扩展方法。在实现该接口时,该默认扩展方法在子类上能够直接使用,它的使用方式相似于抽象类中非抽象成员方法。但扩展方法不可以重载 Object 中的方法。例如:toString、equals、 hashCode 不能在接口中被重载。

例如,下面接口中定义了一个默认方法 count(),该方法能够在子类中直接使用。

public interface DefaultFunInterface {
//定义默认方法 countdefault int count(){
return 1;
}
}
public class SubDefaultFunClass implements DefaultFunInterface {
public static void main(String[] args){
//实例化一个子类对象,改子类对象能够直接调用父接口中的默认方法 count
 SubDefaultFunClass sub = new SubDefaultFunClass();
sub.count();
}
}

静态方法

在接口中,还容许定义静态的方法。接口中的静态方法能够直接用接口来调用。

例如,下面接口中定义了一个静态方法 find,该方法能够直接用 StaticFunInterface .find() 来调用。

public interface StaticFunInterface {public static int find(){
return 1;
}
}
public class TestStaticFun {
public static void main(String[] args){
//接口中定义了静态方法 find 直接被调用
StaticFunInterface.fine();
}
}

四、集合之流式操做

Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,经过 CPU 实现计算。

串行和并行的流

流有串行和并行两种,串行流上的操做是在一个线程中依次完成,而并行流则是在多个线程上同时执行。并行与串行的流能够相互切换:经过 stream.sequential() 返回串行的流,经过 stream.parallel() 返回并行的流。相比较串行的流,并行的流能够很大程度上提升程序的执行效率。

下面是分别用串行和并行的方式对集合进行排序。

串行排序:

List<String> list = new ArrayList<String>();
for(int i=0;i<1000000;i++){
double d = Math.random()*1000;
list.add(d+"");
}
long start = System.nanoTime();//获取系统开始排序的时间点int count= (int) ((Stream) list.stream().sequential()).sorted().count();long end = System.nanoTime();//获取系统结束排序的时间点
long ms = TimeUnit.NANOSECONDS.toMillis(end-start);//获得串行排序所用的时间
System.out.println(ms+”ms”);

并行排序:

List<String> list = new ArrayList<String>();
for(int i=0;i<1000000;i++){
double d = Math.random()*1000;
list.add(d+"");
}
long start = System.nanoTime();//获取系统开始排序的时间点int count = (int)((Stream) list.stream().parallel()).sorted().count();long end = System.nanoTime();//获取系统结束排序的时间点
long ms = TimeUnit.NANOSECONDS.toMillis(end-start);//获得并行排序所用的时间
System.out.println(ms+”ms”);
串行输出为 1200ms,并行输出为 800ms。可见,并行排序的时间相比较串行排序时间要少很多。

中间操做

该操做会保持 stream 处于中间状态,容许作进一步的操做。它返回的仍是的 Stream,容许更多的链式操做。常见的中间操做有:

filter():对元素进行过滤;

sorted():对元素排序;

map():元素的映射;

distinct():去除重复元素;

subStream():获取子 Stream 等。

例如,下面是对一个字符串集合进行过滤,返回以“s”开头的字符串集合,并将该集合依次打印出来:

list.stream()
.filter((s) -> s.startsWith("s"))
.forEach(System.out::println);

这里的 filter(...) 就是一个中间操做,该中间操做能够链式地应用其余 Stream 操做。

终止操做

该操做必须是流的最后一个操做,一旦被调用,Stream 就到了一个终止状态,并且不能再使用了。常见的终止操做有:

forEach():对每一个元素作处理;

toArray():把元素导出到数组;

findFirst():返回第一个匹配的元素;

anyMatch():是否有匹配的元素等。

例如,下面是对一个字符串集合进行过滤,返回以“s”开头的字符串集合,并将该集合依次打印出来:

list.stream() //获取列表的 stream 操做对象
.filter((s) -> s.startsWith("s"))//对这个流作过滤操做
.forEach(System.out::println);

这里的 forEach(...) 就是一个终止操做,该操做以后不能再链式的添加其余操做了。

 

五、IO/NIO的改进

Java 8 对 IO/NIO 也作了一些改进。主要包括:改进了 java.nio.charset.Charset 的实现,使编码和解码的效率得以提高,也精简了 jre/lib/charsets.jar 包;优化了 String(byte[],*) 构造方法和 String.getBytes() 方法的性能;还增长了一些新的 IO/NIO 方法,使用这些方法能够从文件或者输入流中获取流(java.util.stream.Stream),经过对流的操做,能够简化文本行处理、目录遍历和文件查找。

新增的 API 以下:

BufferedReader.line(): 返回文本行的流 Stream<String>

File.lines(Path, Charset):返回文本行的流 Stream<String>

File.list(Path): 遍历当前目录下的文件和目录

File.walk(Path, int, FileVisitOption): 遍历某一个目录下的全部文件和指定深度的子目录

File.find(Path, int, BiPredicate, FileVisitOption... ): 查找相应的文件

下面就是用流式操做列出当前目录下的全部文件和目录:

Files.list(new File(".").toPath())
 .forEach(System.out::println);

 

六、全球化功能

 

Java 8 版本还完善了全球化功能:支持新的 Unicode 6.2.0 标准,新增了日历和本地化的 API,改进了日期时间的管理等。

Java 的日期与时间 API 问题由来已久,Java 8 以前的版本中关于时间、日期及其余时间日期格式化类因为线程安全、重量级、序列化成本高等问题而饱受批评。Java 8 吸取了 Joda-Time 的精华,以一个新的开始为 Java 建立优秀的 API。新的 java.time 中包含了全部关于时钟(Clock),本地日期(LocalDate)、本地时间(LocalTime)、本地日期时间(LocalDateTime)、时区(ZonedDateTime)和持续时间(Duration)的类。历史悠久的 Date 类新增了 toInstant() 方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了了日期时间和本地化的管理。

例如,下面是对 LocalDate,LocalTime 的简单应用:

//LocalDate
LocalDate localDate = LocalDate.now(); //获取本地日期
localDate = LocalDate.ofYearDay(2014, 200); // 得到 2014 年的第 200 天 
System.out.println(localDate.toString());//输出:2014-07-19
localDate = LocalDate.of(2014, Month.SEPTEMBER, 10); //2014 年 9 月 10 日 
System.out.println(localDate.toString());//输出:2014-09-10
//LocalTime
LocalTime localTime = LocalTime.now(); //获取当前时间
System.out.println(localTime.toString());//输出当前时间
localTime = LocalTime.of(10, 20, 50);//得到 10:20:50 的时间点
System.out.println(localTime.toString());//输出: 10:20:50
//Clock 时钟
Clock clock = Clock.systemDefaultZone();//获取系统默认时区 (当前瞬时时间 )
long millis = clock.millis();//
-------------------------------------------------------------------------------------------------
写点代码,写点科技。
微信公众号「软件开发资讯」,碰见了不妨就关注看看。

相关文章
相关标签/搜索