【读书笔记】《写给大忙人看的Java SE 8》——Java8新特性总结
接口中的默认方法和静态方法
先考虑一个问题,如何向Java中的集合库中增加方法?例如在Java 8中向Collection接口中添加了一个forEach方法。
如果在Java 8之前,对于接口来说,其中的方法必须都为抽象方法,也就是说接口中不允许有接口的实现,那么就需要对每个实现Collection接口的类都需要实现一个forEach方法。
但这就会造成在给接口添加新方法的同时影响了已有的实现,所以Java设计人员引入了接口默认方法,其目的是为了解决接口的修改与已有的实现不兼容的问题,接口默认方法可以作为库、框架向前兼容的一种手段。
默认方法就像一个普通Java方法,只是方法用default关键字修饰。
下面来举一个简单的例子
public interface Person {
//默认方法
default String getName(String name) {
return name;
}
}
///////////////////////////////////////////////////////////////////////
public class Student implements Person { }
//////////////////////////////////////////////////////////////////////
public class Test {
public static void main(String[] args) {
Person p = new Student();
String name = p.getName("小李");
System.out.println(name);
}
}
我们定义了一个Person接口,其中getName是一个默认方法。接着编写一个实现类,可以从结果中看到,虽然Student是空的,但是仍然可以实现getName方法。
显然默认接口的出现打破了之前的一些基本规则,使用时要注意几个问题。
考虑如果接口中定义了一个默认方法,而另外一个父类或者接口中又定义了一个同名的方法,该选择哪个?
1. 选择父类中的接口。如果一个父类提供了具体的实现方法,那么接口中具有相同名称和参数的默认方法会被忽略。
2. 接口冲突。如果一个父接口提供了一个默认方法,而另一个接口也提供了具有相同名称和参数类型的方法(不管该方法是否是默认方法),那么必须通过覆盖方法来解决。
记住一个原则,就是“类优先”,即当类和接口都有一个同名方法时,只有父类中的方法会起作用。
“类优先”原则可以保证与Java 7的兼容性。如果你再接口中添加了一个默认方法,它对Java 8以前编写的代码不会产生任何影响。
下面来说说静态方法。
静态方法就像一个普通Java静态方法,但方法的权限修饰只能是public或者不写。
默认方法和静态方法使Java的功能更加丰富。
在Java 8中Collection接口中就添加了四个默认方法,stream()、parallelStream()、forEach()和removeIf()。Comparator接口也增加了许多默认方法和静态方法。
函数式接口和Lambda表达式
函数式接口(Functional Interface)是只包含一个方法的抽象接口。
比如Java标准库中的java.lang.Runnable,java.util.concurrent.Callable就是典型的函数式接口。
在Java 8中通过@FunctionalInterface注解,将一个接口标注为函数式接口,该接口只能包含一个抽象方法。
@FunctionalInterface注解不是必须的,只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。
一般建议在接口上使用@FunctionalInterface注解进行声明,以免他人错误地往接口中添加新方法,如果在你的接口中定义了第二个抽象方法的话,编译器会报错。
函数式接口是为Java 8中的lambda而设计的,lambda表达式的方法体其实就是函数接口的实现。
为什么要使用lambda表达式?
“lambda表达式”是一段可以传递的代码,因为他可以被执行一次或多次。我们先回顾一下之前在Java中一直使用的相似的代码块。
当我们在一个线程中执行一些逻辑时,通常会将代码放在一个实现Runnable接口的类的run方法中,如下所示:
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 10; i++)
System.out.println("Without Lambda Expression");
}}).start();
然后通过创建实例来启动一个新的线程。run方法内包含了一个新线程中需要执行的代码。
再来看另一个例子,如果想利用字符串长度排序而不是默认的字典顺序排序,就需要自定义一个实现Comparator接口的类,然后将对象传递给sort方法。
class LengthComparator implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
}
Arrays.sort(strings, new LengthComparator());
按钮回调是另一个例子。将回调操作放在了一个实现了监听器接口的类的一个方法中。
JButton button = new JButton("click"); button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("Without Lambda Expression");
}
});
这三个例子中,出现了相同方式,一段代码被传递给其他调用者——一个新线程、是一个排序方法或者是一个按钮。这段代码会在稍后被调用。
在Java中传递代码并不是很容易,不可能将代码块到处传递。你不得不构建一个类的对象,由他的某个方法来包含所需的代码。
而lambda表达式实际上就是代码块的传递的实现。其语法结构如下:
(parameters) -> expression 或者 (parameters) -> {statements;}
括号里的参数可以省略其类型,编译器会根据上下文来推导参数的类型,你也可以显式地指定参数类型,如果没有参数,括号内可以为空。
方法体,如果有多行功能语句用大括号括起来,如果只有一行功能语句则可以省略大括号。
new Thread(() -> {
for (int i = 0; i < 100; i++)
System.out.println("Lambda Expression");
}).start();
Comparator<String> c = (s1, s2) -> Integer.compare(s1.length(), s2.length());
button.addActionListener(e -> System.out.println("Lambda Expression"));
可以看到lambda表达式使代码变得简单,代替了匿名内部类。
下面来说一下方法引用,方法引用是lambda表达式的一种简写形式。 如果lambda表达式只是调用一个特定的已经存在的方法,则可以使用方法引用。
使用“::”操作符将方法名和对象或类的名字分隔开来。以下是四种使用情况:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
- 类::new
Arrays.sort(strings, String::compareToIgnoreCase);
// 等价于
Arrays.sort(strings, (s1, s2) -> s1.compareToIgnoreCase(s2));
上面的代码就是第三种情况,对lambda表达式又一次进行了简化。
Stream API
当处理集合时,通常会迭代所有元素并对其中的每一个进行处理。例如,我们希望统计一个字符串类型数组中,所有长度大于3的元素。
String[] strArr = { "Java8", "new", "feature", "Stream", "API" };
int count = 0;
for (String s : strArr) {
if (s.length() > 3)
count++;
}
通常我们都会使用这段代码来统计,并没有什么错误,只是它很难被并行计算。这也是Java8引入大量操作符的原因,在Java8中,实现相同功能的操作符如下所示:
long count = Stream.of(strArr).filter(w -> w.length() > 3).count();
stream方法会为字符串列表生成一个Stream。filter方法会返回只包含字符串长度大于3的一个Stream,然后通过count方法计数。
一个Stream表面上与一个集合很类似,允许你改变和获取数据,但实际上却有很大区别:
- Stream自己不会存储元素。元素可能被存储在底层的集合中,或者根据需要产生出来。
- Stream操作符不会改变源对象。相反,他们返回一个持有新结果的Stream。
- Stream操作符可能是延迟执行的。意思是它们会等到需要结果的时候才执行。
Stream相对于循环操作有更好的可读性。并且可以并行计算:
long count = Arrays.asList(strArr).parallelStream().filter(w -> w.length() > 3).count();
只需要把stream方法改成parallelStream,就可以让Stream去并行执行过滤和统计操作。
Stream遵循“做什么,而不是怎么去做”的原则。只需要描述需要做什么,而不用考虑程序是怎样实现的。
Stream很像Iterator,单向,只能遍历一遍。但是Stream可以只通过一行代码就实现多线程的并行计算。
当使用Stream时,会有三个阶段:
- 创建一个Stream。
- 在一个或多个步骤中,将初始Stream转化到另一个Stream的中间操作。
- 使用一个终止操作来产生一个结果。该操作会强制他之前的延迟操作立即执行。在这之后,该Stream就不会在被使用了。
从着三个阶段来看,对应着三种类型的方法,首先是Stream的创建方法。
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();
中间操作包括:map (mapToInt, flatMap 等)、 filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。
终止操作包括:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。
关于Stream的每个方法如何使用就不展开了,更详尽的介绍看这篇文章:https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
新的日期和时间 API
Java8 引入了一个新的日期和时间API,位于java.time包下。
新的日期和时间API借鉴了Joda Time库,其作者也为同一人,但它们并不是完全一样的,做了很多改进。
下面来说一下几个常用的类。首先是Instant,一个Instant对象表示时间轴上的一个点。
Instant.now()会返回当前的瞬时点(格林威治时间)。Instant.MIN和Instant.MAX分别为十亿年前和十亿年后。
如下代码可以计算某算法的运行时间:
Instant start = Instant.now();
runAlgorithm();
Instant end = Instant.now();
Duration timeElapsed = Duration.between(start, end);
long millis = timeElapsed.toMillis();
Duration对象表示两个瞬时点间的时间量。可以通过不同的方法,换算成各种时间单位。
上面所说的绝对时间并不能应用到生活中去,所以新的Java API中提供了两种人类时间,本地日期/时间和带时区的时间。
LocalDate是一个带有年份、月份和天数的日期。创建他可以使用静态方法now或者of。
LocalDate today = LocalDate.now();
LocalDate myBirthday = LocalDate.of(1994, 03, 15);
// use Enum
myBirthday = LocalDate.of(1994, Month.MARCH, 15); System.out.println(today); // 2017-10-23
System.out.println(myBirthday); // 1994-03-15
下面是LocalDate中的一些常用方法:
LocalTime表示一天中的某个时间,同样可以使用now或者of来创建实例。
LocalTime rightNow = LocalTime.now();
LocalTime bedTime = LocalTime.of(2, 0);
System.out.println(rightNow); // 01:26:17.139
System.out.println(bedTime); // 02:00
LocalDateTime表示一个日期和时间,用法和上面类似。
上面几种日期时间类都属于本地时间,下面来说一下带时区的时间。
ZonedDateTime通过设置时区的id来创建一个带时区的时间。
ZonedDateTime beijingOlympicOpenning = ZonedDateTime.of(2008, 8, 8, 20, 0, 0, 0, ZoneId.of("Asia/Shanghai"));
System.out.println(beijingOlympicOpenning); // 2008-08-08T20:00+08:00[Asia/Shanghai]
更新后的API同样加入了新的格式化类DateTimeFormatter。DateTimeFormatter提供了三种格式化方法来打印日期/时间:
- 预定义的标准格式
String formattered = DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(beijingOlympicOpenning);
System.out.println(formattered); // 2008-08-08T20:00:00
DateTimeFormatter类提供了多种预定义的标准格式可供使用。
- 语言环境相关的格式
标准格式主要用于机器可读的时间戳。为了让人能够读懂日期和时间,你需要使用语言环境相关的格式。
Java8提供了4种风格,SHORT、MEDIUM、LONG、FULL。
String formattered = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.FULL).format(beijingOlympicOpenning);
System.out.println(formattered); //2008年8月8日 星期五 下午08时00分00秒 CST
- 自定义的格式
或者你也可以自定义日期和时间的格式。
String formattered = DateTimeFormatter.ofPattern("E yyyy-MM-dd HH:mm").format(beijingOlympicOpenning);
System.out.println(formattered); // 星期五 2008-08-08 20:00
下图中为一些常用的模式元素。
新的API提供了从字符串解析出日期/时间的parse静态方法和与遗留类(java.util.Date、java.sql.Time和java.txt.DateFormat等)互相转换的方法。
杂项改进
Java8在String类中只添加了一个新方法,就是join,该方法实现了字符串的拼接,可以把它看作split方法的逆操作。
String joined = String.join(".", "www", "cnblogs", "com");
System.out.println(joined); // www.cnblogs.com
数字包装类提供了BYTES静态方法,以byte为单位返回长度。
所有八种包装类都提供了静态的hashCode方法。
Short、Integer、Long、Float和Double这5种类型分别提供了了sum、max和min,用来在流操作中作为聚合函数使用。
集合类和接口中添加的方法:
Java8为使用流读取文件行及访问目录项提供了一些简便的方法(Files.lines和Files.list)。同时也提供了进行Base64编码/解码的方法。
Java8对GUI编程(JavaFX)、并发等方面也做了改进,本文没有一一列出。
转载请注明原文链接:http://www.cnblogs.com/justcooooode/p/7701260.html
参考资料
《写给大忙人看的Java SE 8》
http://study.163.com/course/introduction/1003856028.htm
https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/
【读书笔记】《写给大忙人看的Java SE 8》——Java8新特性总结的更多相关文章
- 《写给大忙人看的java se 8》笔记
现在才来了解java8,是不是后知后觉了点? 新的编程技术,个人不喜欢第一时间跟进. 待社区已有实践积淀再切入似乎更划算些? 一点点精明的考虑. 不多说,上代码. //读<写给大忙人看的java ...
- 《写给大忙人看的Java SE 8》——Java8新特性总结
阅读目录 接口中的默认方法和静态方法 函数式接口和Lambda表达式 Stream API 新的日期和时间 API 杂项改进 参考资料 回到顶部 接口中的默认方法和静态方法 先考虑一个问题,如何向Ja ...
- 《写给大忙人看的Java核心技术》 勘误
先附上十分讨喜的封面.这应该是爱丽丝梦游仙境里的那只兔子吧? 勘误表基于原版勘误表制作 链接 截止日期 2017-02-09 对应<写给大忙人看的Java核心技术>2016年1月第1次印刷 ...
- Java系列 - 用Java8新特性进行Java开发太爽了
本人博客文章网址:https://www.peretang.com/using-java8s-new-features-to-coding-is-awesome/ 前言 从开始写博客到现在已经过去3个 ...
- Java学习之==>Java8 新特性详解
一.简介 Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级.Java 8是 Java 自 Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库. ...
- Java基础之java8新特性(1)Lambda
一.接口的默认方法.static方法.default方法. 1.接口的默认方法 在Java8之前,Java中接口里面的默认方法都是public abstract 修饰的抽象方法,抽象方法并没有方法实体 ...
- 【Java基础】Java8 新特性
Java8 新特性 Lambda 表达式 Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递).使用它可以写出更简洁.更灵活的代码. L ...
- Java系列 – 用Java8新特性进行Java开发太爽了(续)
本人博客文章网址:https://www.peretang.com/using-java8s-new-features-to-coding-is-awesome-2/ 前言 上周, 我们谈论了关于Ja ...
- 《写给大忙人看的java》笔记--基本的编程结构
1.字符串是UTF-16编码中的Unicode编码点的序列 2.绑定System.in的Scanner可以读取终端输入: Scanner sc = new Scanner(System.in); 3. ...
随机推荐
- 201521123069 《Java程序设计》 第11周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多线程相关内容. 1.线程间的互斥访问(线程竞争):一些同时运行的线程需要访问共享数据,互斥访问是保证共享资源完整性的手段.实现方式 ...
- 201521123116 《java程序设计》第十二周学习总结
1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 2. 书面作业 Q1.字符流与文本文件:使用 PrintWriter(写),BufferedReader(读) ...
- JavaSE集合(十)之Map
前面给大家介绍了集合家族中的Collection家族,这一篇给大家分享的是集合中的另一个家族就是Map家族.以前的时候学习Map的时候没有很认真的去学习,我觉得很多东西还是不是很清楚. 这次我将总结的 ...
- Java 最常用类(前100名)来自一万个开源项目
大部分的 Java 软件开发都会使用到各种不同的库.近日我们从一万个开源的 Java 项目中进行分析,从中提取出最常用的 Java 类,这些类有来自于 Java 的标准库,也有第三方库.每个类在同一个 ...
- Java: 类继承中 super关键字
super 关键字的作用有两个: 1)在子类中调用超类的构造器,完成实例域参数的初始化,调用构造器的语句只能作为另一个构造器(通常指的是子类构造器)的第一条语句出现, 2)在子类中调用超类的方法,如: ...
- 银河麒麟操作系统打开VMware报vmmon无法编译
使用银河麒麟操作系统打开VMware可能会报vmmon无法编译 这个时候... 将/usr/src/linux-headers-xxx/include/miscdevice.h第71行void改为in ...
- 用List传递学生信息
集合在程序开发中经常用到,例如,在业务方法中将学生信息.商品信息等存储到集合中,然后作为方法的返回值返回给调用者,以此传递大量的有序数据. 本实例将使用List集合在方法之间传递学生的信息.实例效果如 ...
- ng-options的使用
参考:官方文档.zhx1991 select 无默认选择一项 <select name="" id="" class="form-control ...
- [AHOI2001]彩票摇奖
[AHOI2001]彩票摇奖 题目描述 为了丰富人民群众的生活.支持某些社会公益事业,北塔市设置了一 项彩票.该彩票的规则是: (1) 每张彩票上印有 7 个各不相同的号码,且这些号码的取指范围为 1 ...
- React获得真实的DOM操作
真实的DOM操作 ------------------------------------------------------------------------------------------- ...