接口中的默认方法和静态方法

先考虑一个问题,如何向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方法会为字符串列表生成一个Streamfilter方法会返回只包含字符串长度大于3的一个Stream,然后通过count方法计数。

一个Stream表面上与一个集合很类似,允许你改变和获取数据,但实际上却有很大区别:

  1. Stream自己不会存储元素。元素可能被存储在底层的集合中,或者根据需要产生出来。
  2. Stream操作符不会改变源对象。相反,他们返回一个持有新结果的Stream。
  3. Stream操作符可能是延迟执行的。意思是它们会等到需要结果的时候才执行。

Stream相对于循环操作有更好的可读性。并且可以并行计算:

long count = Arrays.asList(strArr).parallelStream().filter(w -> w.length() > 3).count();

只需要把stream方法改成parallelStream,就可以让Stream去并行执行过滤和统计操作。

Stream遵循“做什么,而不是怎么去做”的原则。只需要描述需要做什么,而不用考虑程序是怎样实现的。

Stream很像Iterator,单向,只能遍历一遍。但是Stream可以只通过一行代码就实现多线程的并行计算。

当使用Stream时,会有三个阶段:

  1. 创建一个Stream。
  2. 在一个或多个步骤中,将初始Stream转化到另一个Stream的中间操作
  3. 使用一个终止操作来产生一个结果。该操作会强制他之前的延迟操作立即执行。在这之后,该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.linesFiles.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新特性总结的更多相关文章

  1. 《写给大忙人看的java se 8》笔记

    现在才来了解java8,是不是后知后觉了点? 新的编程技术,个人不喜欢第一时间跟进. 待社区已有实践积淀再切入似乎更划算些? 一点点精明的考虑. 不多说,上代码. //读<写给大忙人看的java ...

  2. 《写给大忙人看的Java SE 8》——Java8新特性总结

    阅读目录 接口中的默认方法和静态方法 函数式接口和Lambda表达式 Stream API 新的日期和时间 API 杂项改进 参考资料 回到顶部 接口中的默认方法和静态方法 先考虑一个问题,如何向Ja ...

  3. 《写给大忙人看的Java核心技术》 勘误

    先附上十分讨喜的封面.这应该是爱丽丝梦游仙境里的那只兔子吧? 勘误表基于原版勘误表制作 链接 截止日期 2017-02-09 对应<写给大忙人看的Java核心技术>2016年1月第1次印刷 ...

  4. Java系列 - 用Java8新特性进行Java开发太爽了

    本人博客文章网址:https://www.peretang.com/using-java8s-new-features-to-coding-is-awesome/ 前言 从开始写博客到现在已经过去3个 ...

  5. Java学习之==>Java8 新特性详解

    一.简介 Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级.Java 8是 Java 自 Java 5(发布于2004年)之后的最重要的版本.这个版本包含语言.编译器.库. ...

  6. Java基础之java8新特性(1)Lambda

    一.接口的默认方法.static方法.default方法. 1.接口的默认方法 在Java8之前,Java中接口里面的默认方法都是public abstract 修饰的抽象方法,抽象方法并没有方法实体 ...

  7. 【Java基础】Java8 新特性

    Java8 新特性 Lambda 表达式 Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递).使用它可以写出更简洁.更灵活的代码. L ...

  8. Java系列 – 用Java8新特性进行Java开发太爽了(续)

    本人博客文章网址:https://www.peretang.com/using-java8s-new-features-to-coding-is-awesome-2/ 前言 上周, 我们谈论了关于Ja ...

  9. 《写给大忙人看的java》笔记--基本的编程结构

    1.字符串是UTF-16编码中的Unicode编码点的序列 2.绑定System.in的Scanner可以读取终端输入: Scanner sc = new Scanner(System.in); 3. ...

随机推荐

  1. 201521145048《Java程序设计》第4周学习总结

    1. 本章学习总结 学会了如何去设计一个类,尽量用private修饰属性,public修饰方法. 了解继承的目的. 了解继承和多态的关系. 了解关键字extends super final overr ...

  2. 201521123051《Java程序设计》第十三周学习总结

    1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu ...

  3. java控制台输入输出

    一.比较传统的输入方法用输入流,得到字符串后要另行判断.转换 案例 import java.io.BufferedReader; import java.io.IOException; import ...

  4. Oracle函数之chr

    chr()函数将ASCII码转换为字符:字符 –> ASCII码:ascii()函数将字符转换为ASCII码:ASCII码 –> 字符: 在oracle中chr()函数和ascii()是一 ...

  5. Python循环列表删除元素问题

    有人会遇到这种问题,遍历列表,想删除列表中的某几个元素,执行后发现有些并没有删除到, 比如以下代码 a=[1,2,3,4,5,6]print(a) for i in a: if i==3 or i== ...

  6. Linux SSH 安装Tomcat

    tomcat的安装 1. 下载tomcat 从tomcat官网(http://tomcat.apache.org/download-70.cgi)下载tomcat的压缩包apache-tomcat-7 ...

  7. MonoDeveloper 快捷键

    注:环境是Unity3D 5.0.2f1自带的MonoDevelop Ctrl+X 剪切功能.另外,光标放在一行的任意位置(不选中任何内容),使用快捷键,将把这一行剪切并删除此行,这个特性非常好用 C ...

  8. 使用VUE模仿BOSS直聘APP

    一.碎碎念: 偶尔在群里看到一个小伙伴说:最近面试的人好多都说用vue做过一个饿了么.当时有种莫名想笑. 为何不知道创新一下?于是想写个DEMO演练一下.那去模仿谁呢?还是BOSS直聘(跟我没关系,不 ...

  9. 简洁灵活的前端框架------BootStrap

      前  言 Bootstrap,来自 Twitter,是目前很受欢迎的前端框架.Bootstrap 是基于 HTML.CSS.JAVASCRIPT 的,它简洁灵活,使得 Web 开发更加快捷.[1] ...

  10. ArrayList 和 LinkedList 的实现与区别

    (转载请标明出处) 1.ArrayLis t的实现 2.LinkedLis t的实现 3.ArrayList 和 LinkedList 的区别 ArrayList 的实现: 1.MyArrayList ...