前言:太早的就不在去深挖了,就从里程碑式的1.5开始探索。
一、Java 1.5
Java 1.5的主要新特性:
- 泛型
- 注解(annotation)
- 自动装箱和拆箱
- 静态导入
- 枚举
- 增强的for-each风格的for循环
- 可变长度参数(varargs)
- 格式化的I/O
- 内省(Introspector)
- Java Web Start
1.1 泛型
1.2 注解
详细介绍参考:
1.3 自动装箱和拆箱
详细介绍参考:
图书《编写高质量代码-改善Java程序的151个建议》中
建议26:堤防包装类型的null值
建议27:谨防包装类型的大小比较
虽然学习类型的书籍有很多,但是还是强烈推荐推荐这本书,大家可以下载电子版的浏览一下:
编写高质量代码:改善Java程序的151个建议(详细标签).pdf
1.4 静态导入
静态导入的形式:
import static 包路径.*;
与普通的导入方式多了个static,还有就是ClassName(类名)后面多了个 .* ,意思是导入这个类里的静态方法。当然,也可以只导入某个静态方法,只要把 .* 换成静态方法名就行了。然后在类中,就可以直接用方法名调用静态方法,而不必用ClassName.方法名 的方式来调用,同样静态的变量也可以直接使用。
注意:这样的写法虽然可以简化代码的书写,但是代码的可读性下降。
1.5 枚举
详细介绍参考:
1.6 for-each
for-each语句格式为:for ( variable : collection ) statement
这是一种循环结构,可以用来依次处理数组中的每个元素(其他类型的元素集合亦可)而不必为指定下标值而分心。定义一个变量(variable)用于暂存集合中的每个元素,并执行相应的语句(statement),collection这个集合表达式必须是一个数组或者是一个实现了Iterable接口的类对象。
例如:
for(int element : a ){作用:打印数组a的每一个元素,一个元素占一行。也可以称为“循环a中的每一个元素”。
System.out.println(element);
}
优点:与传统的循环相比语句更加简洁、更不易出错(不必为下标的起始位置和终止值而操心)。
缺点:for-each结构会遍历集合中的所有元素,如果希望遍历部分元素或者在循环内部需要使用下标值,就力不从心了,传统的循环就显示出优势了。
1.7 可变长度参数
在编写功能相同,但是参数数量不同的函数的时候,作为一名程序员,我是有想偷懒的想法,可变长度参数便是为此提供的一种简化的方式。我们只需要编写内部的方法即可,函数可以接收数量未知的参数。
格式:Object...
此处的Object可以是任何类型只不过在后面添加三个点就可以了,表明可以接收任意数量的该对象,上面的Object...参数类型与Object[]完全一样。
示例:
/**注意:可变参数必须是参数列表的最后一个参数。
计算若干个数值的最大值
*/
public static double max(double... values){
double largest = Double.MIN_VALUE;
for(double v : values){
if (v>largest) {
largest=v;
}
}
return largest;
}
1.8 格式化的I/O
对于了解C语言的人来说,printf这个输出并不陌生,Java也沿用了这种方法。
示例:
public class ceshi{每一个以%字符开始的格式说明符都用相应的参数替换。格式说明符尾部的转换符将指示被格式化的数值类型。
public static void main(String[] arg){
display("fxb1",32);
display("fxb2",33);
display("fxb3",34);
}
public static void display(String name,int age){
System.out.printf("你好,%s.下一年你就%d\n",name,age);
System.out.println("------------------------------");
}
}/*输出:
你好,fxb1.下一年你就32
------------------------------
你好,fxb2.下一年你就33
------------------------------
你好,fxb3.下一年你就34
------------------------------
*/
用于printf的转换符:
- d:十进制整数
- x:十六进制整数
- o:八进制整数
- f:定点浮点数
- e:指数浮点数
- g:通用浮点数
- a:十六进制浮点数
- s:字符串
- c:字符
- b:布尔值
- h:散列码
- tx:日期时间(以t开头后面的值有很多)-可参考《Java核心技术 卷一》的58页
- %:百分号
- n:与平台有关的行分隔符
System.out.printf("%+,.2f %+,.2f", -10000.0 / 3.0, 10000.0 / 3.0);用于printf的标志:
/*输出结果:
* -3,333.33 +3,333.33
*/
- + :打印整数和负数的符号
- 空格 :在整数之前添加空格
- 0 :数字前面补0
- - :左对齐
- ( :添加分组分隔符
- #(对于f格式) :包含小数点
- #(对于x或0格式):添加前缀0x或0
- $:给定被格式化的参数索引。例如,%1$d,%1$x将以十进制和十六进制格式打印第一个参数
- <:格式化前面说明的数值。例如,%d%<x以十进制和十六进制打印同一个值
1.9 内省
1.10 Java Web Star
详细介绍参考:
  
二、Java 1.6
Java 1.6的主要新特性:
- Desktop类和SystemTray类
其他特性可以参考: JDK1.6的九大新特性
详细介绍参考:
2.1 Desktop类和SystemTray类
这两个类都为工厂类,其构造函数是private,这也就意味着我们需要调用其静态方法来获取此类的实例。
2.1.1 Desktop
Desktop 类允许 Java 应用程序启动已在本机桌面上注册的关联应用程序,以处理 URI 或文件。
支持的操作包括:
- 启动用户默认浏览器来显示指定的 URI;
- 启动带有可选 mailto URI 的用户默认邮件客户端;
- 启动已注册的应用程序,以打开、编辑或打印指定的文件。
- 此类提供与这些操作对应的方法。这些方法查找在当前平台上注册的关联应用程序,并启动该应用程序来处理 URI 或文件。如果没有关联应用程序或关联应用程序无法启动,则抛出异常。
- 应用程序被注册为 URI 或文件类型;例如,"sxi" 文件扩展名通常注册为 StarOffice。注册、访问和启动关联应用程序的机制与平台有关。
注:当调用一些动作和执行关联应用程序时,将在与启动 Java 应用程序相同的系统上执行它们。
与Desktop类具有类似功能的类有Runtime类。
2.1.2 SystemTray
SystemTray 类表示桌面的系统托盘。在 Microsoft Windows 上,它被称为“任务栏状态区域 (Taskbar Status Area)”,在 Gnome 上,它被称为“通知区域 (Notification Area)”,在 KDE 上,它被成为“系统托盘 (System Tray)”。系统托盘由运行在桌面上的所有应用程序共享。
在某些平台上,可能不存在或不支持系统托盘,在这种情况下,getSystemTray() 将抛出 UnsupportedOperationException。要检查系统托盘是否受支持,可以使用 isSupported()。
SystemTray 可以包含一个或多个 TrayIcon,可以使用 add(java.awt.TrayIcon) 方法将它们添加到托盘,当不再需要托盘时,使用 remove(java.awt.TrayIcon) 移除它。TrayIcon 由图像、弹出菜单和一组相关侦听器组成。
每个 Java 应用程序都有一个 SystemTray 实例,在应用程序运行时,它允许应用程序与桌面系统托盘建立连接。SystemTray 实例可以通过 getSystemTray() 方法获得。应用程序不能创建自己的 SystemTray 实例。
示例:
TrayIcon trayIcon = null;
if (SystemTray.isSupported()){
// get the SystemTray instance
SystemTray tray = SystemTray.getSystemTray();
// load an image
Image image = Toolkit.getDefaultToolkit.getImage(...);
// create a action listener to listen for default action executed on the tray icon
ActionListener listener = new ActionListener(){
public void actionPerformed(ActionEvent e) {
// execute default action of the application
// ...
}
};
// create a popup menu
PopupMenu popup = new PopupMenu();
// create menu item for the default action
MenuItem defaultItem = new MenuItem(...);
defaultItem.addActionListener(listener);
popup.add(defaultItem);
/// ... add other items
// construct a TrayIcon
trayIcon = new TrayIcon(image, "Tray Demo", popup);
// set the TrayIcon properties
trayIcon.addActionListener(listener);
// ...
// add the tray image
try{
tray.add(trayIcon);
}
catch (AWTException e){
System.err.println(e);
}
// ...
}
else{
// disable tray option in your application or
// perform other actions
...
}
// ...
// some time later
// the application state has changed - update the image
if (trayIcon != null){
trayIcon.setImage(updatedImage);
}
// ...
  
三、Java 1.7
Java 1.7的主要新特性:
- String现在能够控制switch语句
- 二进制整型字面值
- 数值字面值中的下划线
- 扩展的try语句,称为带资源的try(try-with-resources)语句,这种try语句支持自动资源管理。
- 对异常处理进行了增强,单个catch子句能够捕获两个或更多个异常(multi-catch),并且对重新抛出的异常提供了更好的类型检查。
- 对与某些方法(参数的长度可变)类型关联的编译器警告进行了改进,尽管语法没有发生变化,并且警告具有更大的控制权。
- 构造泛型实例时的类型推断
- 新增一些取环境信息的工具方法
3.1 String现在能够控制switch语句
switch语句支持了对字符串String的判定,这对于开发来说是非常便利的改进,非常容易理解,不再解释。3.2 二进制整型字面值
在旧版的java中,字面值只支持十进制、八进制、十六进制3种类型,在Java7中又多了一种二进制,它的前缀是0B,配合需要位运算的场景特别合适,尤其是跟下划线组合使用:int i = 0B1010_1100_0010_1100_0000_1111_0001_1011;3.3 数值字面值中的下划线
数值型的字面值中的数字之间可以出现任何数量的下划线。 例如,这个特性可以让你将数值型的字面值中的数字分隔成组,这样可以提高代码的可读性。long creditCardNumber = 1234_5678_9012_3456L;注意:只能将下划线置于数字之间; 以下地方不能放置下划线:数字的开头或结尾;浮点数中靠近小数点的位置;F 或 L 后缀之前期望放置一串数字的地方。
long socialSecurityNumber = 999_99_9999L;
float pi = 3.14_15F;
long hexBytes = 0xFF_EC_DE_5E;
long hexWords = 0xCAFE_BABE;
long maxLong = 0x7fff_ffff_ffff_ffffL;
byte nybbles = 0b0010_0101;
long bytes = 0b11010010_01101001_10010100_10010010;
3.4 扩展的try语句
详细请参考: Java 1.7新特性之(try-with-resources)3.5 对异常处理进行了增强
3.5.1 Multi-catch
Multicatch:开发者现在能够在一个catch代码块中捕获多个异常类型;原来我们捕获多个异常的时候常用的写法是:
} catch (FirstException ex) {这种写法除了冗长外没有什么优点。可能我们想到的一个解决办法是找出这两个异常类型的共同子类型,只对其进行捕获并抛出。但是这种方法通常会捕获一些你并不需要的异常。Muti-catch就解决了此类问题,借助于这个新增的功能,我们可以使用以下代码:
logger.error(ex);
throw ex;
} catch (SecondException ex) {
logger.error(ex);
throw ex;
}
} catch (FirstException | SecondException ex) {这种代码看上去要简洁清晰的多。
logger.error(ex);
throw ex;
}
3.5.2 Final Rethrow(代码中对捕获再抛出异常时的处理--即异常类型推断)
Final Rethrow:它可以让开发者捕获一个异常类型及其子类型,并且无需向方法声明中增加抛出子句,就能重新将其抛出。假如开发过程中希望在捕获所有异常后,进行必要的几个操作后,然后再将其抛出。就代码编写而言并不是一件难事,但是我们必须在方法声明中增加一个抛出子句,来管理自己代码发出的新异常。
class SubException1 extends Exception {}在 JDK7 下报错为:Unreachable catch block for App.SubException2. This exception is never thrown from the try statement body
class SubException2 extends Exception {}
public void testThrow() throws Exception {
try{
throw new SubException1();
}catch(Exception e){
try{
throw e; //1
}catch(SubException2 e2){ //JDK6 可编译通过,JDK7 下无法通过编译
}
}
}
JDK7 编译器在 1 处能推断出抛出的异常类型是 SubException1, 底下的 catch(SubException2 e2) 就别白费心思啦。
JDK 7改进后的写法:
public void doSomething() /*throws Exception*/{在 JDK6 下 doSomething() 方法必须声明 throws Exception 抛出 Exception 类型的异常才成,因为它只简单的看到像是在 throw e:Exeption。而 JDK7 编译器在 try doSomethingElse() 推断出 catch(Exception e) 就是一个 RuntimeException 非检测异常类型,所以 doSomething() 方法中可以省去 throws Exception。
try {
doSomethingElse();
} catch (Exception e) {
throw e; //JDK6 下报 Unhandled exception type Exception 错误,必须声明抛出 Exception
}
}
public void doSomethingElse(){
throw new RuntimeException();
}
下面的代码对 JDK7 来说可以通过,糊弄不了它的,也无须为 doSomething() 方法声明 throws Exception。
public void doSomething() {但 JDK7 看到如下的代码同样会傻眼:
try {
doSomethingElse();
} catch (Exception e) {
throw e;
}
}
public void doSomethingElse(){
doAnotherThing();
}
public void doAnotherThing(){
throw new RuntimeException();
}
public void doSomething() /*JDK7 下也必须加上 throws Exception*/{在 JDK7 下也必须为 doSomething() 加上 throws Exception 声明,只看到 doSomethingElse() 方法的 throws Exception 声明就认定它抛出的是 Exception 类型,而跳过的 throw new RuntimeException() 内容的具体推断。
try {
doSomethingElse();
} catch (Exception e) {
throw e;
}
}
public void doSomethingElse() throws Exception{
throw new RuntimeException();
}
因此当我们在为 catch(Exception e) { throw e; } 后要不要为所在方法加上 throws 声明时,可以查查 try 块中调用的方法有没有声明抛出需检测的异常。
当然,有现代化的 IDE 根本不担心这个,按错误提示来办事,通常只需一个简单的快捷键就帮你做好了,但任何时候理解是万岁。
3.6 改进使用带泛型可变参数的方法时的编译器警告和错误提示
详见:(————》》》赞不明白) Java SE 7新特性:改进使用带泛型可变参数的方法时的编译器警告和错误提示3.7 构造泛型实例时的类型推断
List<String> tempList = new ArrayList<>(); 我们没必要在后面再重新再写一遍了。3.8 新增一些取环境信息的工具方法
java.nio.file.FileSystem是一个抽象方法,API的解释为:Provides an interface to a file system and is the factory for objects to access files and other objects in the file system.(提供一个文件系统的接口,并且是对象访问文件或文件系统中的其他对象的一个工厂)我们可以通过 FileSystems.getDefault()方法来获取一个实例。这里对这连个类不详细叙述。——————》》》
针对这一部分的讲解与网上所说的有些差异:
File System.getJavaIoTempDir() // IO临时文件夹网上很多这种代码示例,但是我始终没有搞清楚在哪里。
File System.getJavaHomeDir() // JRE的安装目录
File System.getUserHomeDir() // 当前用户目录
File System.getUserDir() // 启动java进程时所在的目录
四、Java 1.8
Java 1.8的主要新特性:- 允许在接口中有默认方法实现
- 函数式接口
- 内置函数式接口
- Lambda表达式
- 方法和构造函数引用
- Streams
- Parallel Streams
- Map
- 时间日期API
- Annotations
4.1 允许在接口中有默认方法实现
Java 8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法。该非抽象的方法实现可以在子类中直接调用,子类只需要实现所有的抽象方法即可。public interface Formula {测试方法:
public double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
}
public class Ceshi {
public static void main(String[] args) {
Formula formula = new Formula() {
@Override
public double calculate(int a) {
return sqrt(a * 100);
}
};
System.out.println(formula.calculate(100));// 100.0
System.out.println(formula.sqrt(16));// 4.0
}
}
官网资料可参考:Default Methods
4.2 函数式接口
函数式接口定义且只定义了一个抽象方法。通俗来说,函数式接口就是只定义一个抽象方法的接口。重点:接口,一个抽象方法(注意:继承也会增加当前接口中的抽象方法的数量)。@FunctionalIterface注解用于表示该接口会设计成一个函数式接口,如果你用@FunctionalIterface定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。
为什么将这个特殊的接口拿出来说呢?其实重点是与Lambda表达式的结合。 详细的介绍可以参考菜鸟的《Java 8 函数式接口》 JDK 1.8之前已有的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
- java.util.function
序号 | 接口 & 描述 |
---|---|
1 |
BiConsumer<T,U> 代表了一个接受两个输入参数的操作,并且不返回任何结果 |
2 |
BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果 |
3 |
BinaryOperator<T> 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果 |
4 |
BiPredicate<T,U> 代表了一个两个参数的boolean值方法 |
5 |
BooleanSupplier 代表了boolean值结果的提供方 |
6 |
Consumer<T> 代表了接受一个输入参数并且无返回的操作 |
7 |
DoubleBinaryOperator 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。 |
8 |
DoubleConsumer 代表一个接受double值参数的操作,并且不返回结果。 |
9 |
DoubleFunction<R> 代表接受一个double值参数的方法,并且返回结果 |
10 |
DoublePredicate 代表一个拥有double值参数的boolean值方法 |
11 |
DoubleSupplier 代表一个double值结构的提供方 |
12 |
DoubleToIntFunction 接受一个double类型输入,返回一个int类型结果。 |
13 |
DoubleToLongFunction 接受一个double类型输入,返回一个long类型结果 |
14 |
DoubleUnaryOperator 接受一个参数同为类型double,返回值类型也为double 。 |
15 |
Function<T,R> 接受一个输入参数,返回一个结果。 |
16 |
IntBinaryOperator 接受两个参数同为类型int,返回值类型也为int 。 |
17 |
IntConsumer 接受一个int类型的输入参数,无返回值 。 |
18 |
IntFunction<R> 接受一个int类型输入参数,返回一个结果 。 |
19 |
IntPredicate :接受一个int输入参数,返回一个布尔值的结果。 |
20 |
IntSupplier 无参数,返回一个int类型结果。 |
21 |
IntToDoubleFunction 接受一个int类型输入,返回一个double类型结果 。 |
22 |
IntToLongFunction 接受一个int类型输入,返回一个long类型结果。 |
23 |
IntUnaryOperator 接受一个参数同为类型int,返回值类型也为int 。 |
24 |
LongBinaryOperator 接受两个参数同为类型long,返回值类型也为long。 |
25 |
LongConsumer 接受一个long类型的输入参数,无返回值。 |
26 |
LongFunction<R> 接受一个long类型输入参数,返回一个结果。 |
27 |
LongPredicate R接受一个long输入参数,返回一个布尔值类型结果。 |
28 |
LongSupplier 无参数,返回一个结果long类型的值。 |
29 |
LongToDoubleFunction 接受一个long类型输入,返回一个double类型结果。 |
30 |
LongToIntFunction 接受一个long类型输入,返回一个int类型结果。 |
31 |
LongUnaryOperator 接受一个参数同为类型long,返回值类型也为long。 |
32 |
ObjDoubleConsumer<T> 接受一个object类型和一个double类型的输入参数,无返回值。 |
33 |
ObjIntConsumer<T> 接受一个object类型和一个int类型的输入参数,无返回值。 |
34 |
ObjLongConsumer<T> 接受一个object类型和一个long类型的输入参数,无返回值。 |
35 |
Predicate<T> 接受一个输入参数,返回一个布尔值结果。 |
36 |
Supplier<T> 无参数,返回一个结果。 |
37 |
ToDoubleBiFunction<T,U> 接受两个输入参数,返回一个double类型结果 |
38 |
ToDoubleFunction<T> 接受一个输入参数,返回一个double类型结果 |
39 |
ToIntBiFunction<T,U> 接受两个输入参数,返回一个int类型结果。 |
40 |
ToIntFunction<T> 接受一个输入参数,返回一个int类型结果。 |
41 |
ToLongBiFunction<T,U> 接受两个输入参数,返回一个long类型结果。 |
42 |
ToLongFunction<T> 接受一个输入参数,返回一个long类型结果。 |
43 |
UnaryOperator<T> 接受一个参数为类型T,返回值类型也为T。 |
4.4 Lambda表达式
参见:Java 1.8 新特性之(Lambda表达式)入门可以参考上面的译文,下面直接上干货。 重点:利用行为参数化来传递代码有助于应对不断变化的需求。
4.4.1 语法
格式:(参数列表) ->{主体代码} 注意:- 如果只有一个参数且没有参数类型的时候(只有变量的形参)可以省略括号
- 控制语句代码必须使用花括号进行括起来
- 如果主体代码只是一个表达式(如:{“Ironman”;})而不是一个语句,我们需要去掉分号以及花括号,或者显示返回语句(如:{return “Ironman”;})
-
参数类型可以省略–绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型
4.4.2 使用
函数式接口:定义且只定义一个抽象方法的接口。 函数式接口的抽象方法的签名基本上就是Lambda表达式的签名,这种抽象方法叫做函数描述符。 我们用函数式接口可以干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体来说,是函数式接口一个具体实现的实例)。我们用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后在直接内联将它实例化。如下:public static void process(Runnable r){通俗的理解是Lambda表达式的代码补充掉其调用处的接口所调用地方的抽象方法的代码,如上面的代码中r1代码添加到掉了r.run()的执行代码中,如果有参数就在此调用处进行实参传入。并且Lambda表达式的参数无论是数量还是类型都应与接口的抽象方法一致。 使用Lambda表达式这样做的优点就是行为的分离,我们想利用process执行另一种行为,只需要在调用处将行为代码进行传递就可以了,大大降低了代码的依赖性,同时也简化了代码量,如果我们使用匿名内部类,如上r2的声明中的开头与结尾完全重复的代码还得再书写一遍,利用Lambda就可以省去这部分重复的代码。
r.run();
}
public static void main(String[] arg) {
Runnable r1=()-> System.out.println("Hello World 1");//使用Lambda
Runnable r2=new Runnable() {//使用匿名类
@Override
public void run() {
System.out.println("Hello World 2");
}
};
process(r1);
process(r2);
process(()-> System.out.println("Hello World 3"));
}
Lambda的这种方式就是行为参数化,使用函数式接口来传递行为。Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。
4.4.3 原始类型特化
Java类型要么是引用类型(比如Byte、Integer、Object、List),要么是原始类型(比如int、double、byte、char)。但是泛型(比如Consumer<T>中的T)只能绑定到引用类型,这是由泛型内部的实现方式造成的。不过Java提供了自动装箱技术,这个自动的转变是虚拟机自动转换的,很有意义,但是在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆中。因此装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。Java 8为我们前面所说函数式接口带来了一个专门的版本,以便在输入和输出都是原始类型时避免自动装箱的操作。
例如:使用IntPerdicate就避免了对值进行装箱操作,但要是用Predicate<Integer>就会把参数装箱成Integer对象中。
import java.util.function.Predicate;一般来说,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如DoublePerdicate。Function接口还有针对输出参数类型的变种:ToIntFunction<T>、IntToDoubleFunction等。 注意:Java API提供了最常用的函数式接口及其函数描述符(java.util.function包中以及只有一个抽象方法的接口),如果有需要,我们完全可以自己设计一个。
public interface IntPredicate{//JDK中已经定义,再次只是强调说明
boolean test (int t);
}
IntPredicate evenNumbers = (int i)->i%2==0;
evenNumbers.test(1000);//无自动装箱
Predicate<Integer> oddNumbers=(Integer i)->i%2==1;
oddNumbers.test(1000);//自动装箱
函数式接口 | 函数描述符 | 原始类型特化 |
---|---|---|
Predicate<T> | T->boolean | IntPredicate,LongPredicate,DoublePredicate |
Consumer<T> | T->void | IntConsumer,LongConsumer,DoubleConsumer |
Function<T> | T->R | IntFunction<R>, IntToDoubleFunction, IntToLongFunction, LongFunction, LongToDoubleFunction, LongToIntFunction, DoubleFunction<R>, ToIntFunction<R>, ToDoubleFunction<T>, ToLongFunction<T>, |
Supplier<T> | ()->T | BooleanSupplier,IntSupplier,LongSupplier, DoubleSupplier |
UnaryOperator<T> | T->T | IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator |
BinaryOperator<T> | (T,T)->T | IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator |
BiPredicate<L,R> | (L,R)->boolean | |
BigConsumer<T,U> | (T,U)->void | ObjIntConsumer<T>, ObjLongConsumer<T>, ObjDoubleConsumer<T> |
BiFunction<T,U,R> | (T,U)->R | ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U>, |
4.4.4 异常
注意:任何函数式接口都不允许抛出受检异常(checked exception)。如果我们需要Lambda表达式来抛出异常,我们有两种办法:
- 定义一个自己的函数式接口,并声明受检异常。
- 把Lambda包在一个try/catch块中。
注意:如果Lambda表达式抛出一个异常,那么抽象方法所声明的throws语句也必须与之匹配。
4.4.5 类型检查、类型推断以及限制
当我们初步接触Lambda表达式时,说它可以为函数式接口生成一个实例。然而,Lambda表达式本身并不包含它在实现那个函数式接口的信息。为了全面了解Lambda表达式,你应该知道Lambda的实际类型是什么。Lambda的类型是从使用Lambda的上下文推断出来的。上下文(比如,接受它传递的方法的参数,或接受它的值得局部变量)中Lambda表达式需要的类型称为目标类型。
有了目标类型的概念,同一个Lambda表达式就可以与不同的函数式接口联系起来,只要它们与抽象方法签名能够兼容。
特殊的void兼容规则:如果一个Lambda的主体是一个语句表达式,他就和一个返回void的函数描述符兼容(当然需要参数列表也兼容)。例如,函数式接口的函数描述符为T->void,但是Lambda表达式的函数描述符为T->boolean,这样的匹配同样合法。
Lambda表达式可以从赋值的上下文、方法调用的上下文(参数和返回值),以及类型转换的上下文中获得目标类型。
Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着他也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到。这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型。
4.4.6 使用局部变量
通常情况下我们所使用的的Lambda表达式都只用到了其主体里面的参数。但Lambda表达式允许使用*变量(不是参数,而是在外层作用域中定义的变量),就像匿名类一样。它们被称作捕获Lambda。例如:int portNumber=524;尽管如此,还有一点点小麻烦:关于能对这些变量做什么有一些限制。Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。(对于变量的概念可以参考:全局变量、局部变量、静态变量和实例变量的区别)
Runnable r=() -> System.out.println(portNumber);
局部变量必须显示声明为final或事实上是final。换句话说,Lambda表达式只能捕获指派给他们的局部变量一次。(注:不或变量可以被看作捕获最终局部变量this)
例如(下面的代码无法编译,因为portNumber变量被赋值两次):
int portNumber=548;
Runnable r=()->System.out.println(portNumber);
portNumber=1452;
4.4.7 复合Lambda表达式
Java 8的好几个函数式接口都有为方便而设计的方法。具体而言,许多函数式接口,比如用于传递Lambda表达式的Comparator、Function和Predicate都提供了允许你进行复合的方法。在实践中,这意味着你可以把多个简单的Lambda复合成复杂的表达式。比如,你可以让两个谓词之间做一个or操作,组合成一个更大的谓词。而且,你还可以让一个函数的结果成为另一个函数的输入。这些方法都是默认方法,没有破坏函数式接口的定义规则。
- 比较器复合:逆序,比较器链
- 谓词复合:negate、and和or
- 函数复合:例如Function接口为此配了andThen和compose两个默认方法,它们都会返回Function的一个实例。
4.5 方法和构造函数引用
4.5.1 方法引用
方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。在一些情况下,比起使用Lambda表达式,它们似乎更易读,感觉也更自然。我们可以把方法引用看作针对仅仅涉及单一方法的Lambda的语法糖,因为我们表达同样的事情要写的代码更少了。
方法引用可以被看作仅仅调用特定方法的lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用他,而不是去描述如何调用它。事实上,方法引用就是让你根据已有的方法实现来创建Lambda表达式。但是,显示地指明方法的名称,你的代码的可读性会更好。
当需要使用方法引用时,目标引用放在分隔符::前,方法的名称放在后面。例如Apple::getWeight就是引用了Apple类中定义的方法getWeight。请记住,不需要括号,因为你没有实际调用这个方法。方法引用就是Lambda表达式(Apple a)-> a.getWeight()的快捷写法。
方法引用主要分类:
- 指向静态方法的方法引用(例如Integer的parseInt方法,写作Integer::ParseInt)
- 指向任意类型实例方法的方法引用(例如String的length方法,写作String::length)
- 指向现有对象的实例方法的方法引用(假设你有一个局部变量expensiveTransaction用于存放Transaction类型的对象,它支持实例方法getValue,那么你就可以写expensiveTransaction::getValue)
第二种和第三种方法引用看起来可能乍看起来有点儿晕。类似于String::length的第二种方法引用的思想就是你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。例如,Lambda表达式(String s)->s.toUppeCase()可以写作String::toUpperCase。但第三种方法引用指的是,你在Lambda中调用一个已经存在的外部对象中的方法。l例如,lambda表达式()->expensiveTransaction.getValue()可以写作expensiveTransaction::getValue。
依照一些简单的方子,我们就可以将Lambda表达式重构为等价的方法引用,如下:
4.5.2 构造函数引用
对于一个现有的构造函数,可以利用它的名称和关键字new来创建它的一个引用:Classname::new。它的功能与指向静态方法的引用类似。
例如:
Supplier<Apple> c=Apple::new;
Apple a=c.get();
等价于:
Supplier<apple> c = () -> new Apple();如果构造函数的签名是Apple(Integer weight),那么它就适合Function接口的签名,于是我们可以这样写:
Apple a=c.get();
Function<Integer,Apple> c=Apple::new;
Apple a=c.apply(111);
等价于:
Function<Integer,Apple> c=(weight) -> new Apple(weight);注意:函数式接口的签名需要与构造函数引用的签名匹配。
Apple a=c.apply(111);
  
详细介绍参考:
参考资料:
《Java 8实战》
JDK 7 代码中对捕获再抛出异常时的处理--即异常类型推断
最后修改时间:2017年3月29日16:27:19
********************************************************************************结束语********************************************************************************************
我在写这篇博客的时候也是一名初学者,有任何疑问或问题请留言,或发邮件也可以,邮箱为:fanxiaobin.fxb@qq.com,我会尽早的进行更正及更改。
在我写过的博客中有两篇博客是对资源的整理,可能对大家都有帮助,大家有兴趣的话可以看看!!
下载资料整理——目录:http://blog.csdn.net/fanxiaobin577328725/article/details/51894331
这篇博客里面是我关于我见到的感觉不错的好资源的整理,里面包含了书籍及源代码以及个人搜索的一些资源,如果有兴趣的可以看看,我会一直对其进行更新和添加。
优秀的文章&优秀的学习网站之收集手册:http://blog.csdn.net/fanxiaobin577328725/article/details/52753638
这篇博客里面是我对于我读过的,并且感觉有意义的文章的收集整理,纯粹的个人爱好,大家感觉有兴趣的可以阅读一下,我也会时常的对其进行更新。
********************************************************************************感谢********************************************************************************************