转载

Java 5到Java8 的发展

Java 5到Java8 的发展

JDK 5

自动装箱与拆箱

JDK1.5 为每一个基本数据类型定义了一个封装类。使 java 中的基本数据类型也有自己的对象

int -->Integer
double --> Double
long --> Long
char --> Character
float --> Float
boolean --> Boolean
short --> Short
byte -- > Byte
int --> Integer
Integer --> int

对于 JDK1.5 之前集合总不能存放基本数据类型的问题,现在也能够解决。

枚举

枚举是 JDK1.5 推出的一个比较重要的特性。其关键字为 enum
例如:定义代表交通灯的枚举

public enum MyEnum{    
    RED,GREEN,YELLOW
}

静态导入

  • 优点:使用静态导入可以使被导入类的所有静态变量和静态方法在当前类直接可见,使用这些静态成员无需再给出他们的类名。
  • 缺点:过度使用会降低代码的可读性

可变参数

在 JDK1.5 以前,当我们要为一个方法传递多个类型相同的参数时,

我们有两种方法解决

  1. 直接传递一个数组过去
  2. 有多少个参数就传递多少个参数。

例如:

public void printColor(String red,String green,String yellow){ 
}

或者

public void printColor(String[] colors){}

这样编写方法参数虽然能够实现我们想要的效果,但是,这样是不是有点麻烦呢?

再者,如果参数个数不确定,我们怎么办呢?Java JDK1.5 为我们提供的可变参数就能够完美的解决这个问题.

例如:

public void printColor(String... colors){}

如果参数的类型相同,那么可以使用 类型+三个点 ,后面跟一个参数名称的形式。

这样的好处就是,只要参数类型相同,无论传递几个参数都没有限制

注意:可变参数必须是参数列表的最后一项(该特性对对象和基本数据类型都适用)

泛型

//给集合指定存入类型,上面这个集合在存入数据的时候必须存入String类型的数据,否则编译器会报错List<String> strs = new ArrayList<String>();

“泛型” 意味着编写的代码可以被不同类型的对象所重用。

可见泛型的提出是为了编写重用性更好的代码。

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

比如常见的集合类 LinkedList ,其实现的接口名后有个特殊的部分 <> ,而且它的成员的类型 Link 也包含一个 <> ,这个符号的就是类型参数,它使得在运行中,创建一个 LinkedList 时可以传入不同的类型,比如 new LinkedList ,这样它的成员存放的类型也是 String

For-Each 循环

例如上面这个集合我们可以通过 for-each 遍历,这样更加简单清晰。

for(String s : strs){      
    System.out.println(s); 
}

注意:使用 for-each 遍历集合时,要遍历的集合必须实现了 Iterator 接口

线程并发库

线程并发库是 Java1.5 提出的关于多线程处理的高级功能,所在包: java.util.concurrent 包括

  1. 线程互斥工具类: LockReadWriteLock
  2. 线程通信: Condition
  3. 线程池: ExecutorService
  4. 同步队列: ArrayBlockingQueue
  5. 同步集合: ConcurrentHashMapCopyOnWriteArrayList
  6. 线程同步工具: Semaphore

JDK 6

Desktop 类和 SystemTray 类

前者可以用来打开系统默认浏览器浏览指定的 URL,打开系统默认邮件客户端给指定的邮箱发邮件,用默认应用程序打开或编辑文件 (比如,用记事本打开以 txt 为后缀名的文件),用系统默认的打印机打印文档;后者可以用来在系统托盘区创建一个托盘程序。

使用 Compiler API

现在我们可以用 JDK1.6 的 Compiler API(JSR 199) 去动态编译 Java 源文件,Compiler API 结合反射功能就可以实现动态的产生 Java 代码并编译执行这些代码,有点动态语言的特征。

这个特性对于某些需要用到动态编译的应用程序相当有用,比如 JSP Web Server,当我们手动修改 JSP 后,是不希望需要重启 Web Server 才可以看到效果的,这时候我们就可以用 Compiler API 来实现动态编译 JSP 文件。

当然,现在的 JSP Web Server 也是支持 JSP 热部署的,现在的 JSP Web Server 通过在运行期间通过 Runtime.exec 或 ProcessBuilder 来调用 javac 来编译代码,这种方式需要我们产生另一个进程去做编译工作,不够优雅而且容易使代码依赖与特定的操作系统;

Compiler API 通过一套易用的标准的 API 提供了更加丰富的方式去做动态编译,而且是跨平台的。

轻量级 Http Server API

JDK1.6 提供了一个简单的 Http Server API,据此我们可以构建自己的嵌入式 Http Server,它支持 Http 和 Https 协议,提供了 HTTP1.1 的部分实现,没有被实现的那部分可以通过扩展已有的 Http Server API 来实现,程序员必须自己实现 HttpHandler 接口,HttpServer 会调用 HttpHandler 实现类的回调方法来处理客户端请求,在这里,我们把一个 Http 请求和它的响应称为一个交换,包装成 HttpExchange 类, HttpServer 负责将 HttpExchange 传给 HttpHandler 实现类的回调方法。

用 Console 开发控制台程序

JDK1.6 中提供了 java.io.Console 类专用来访问基于字符的控制台设备。

你的程序如果要与 Windows 下的 cmd 或者 Linux 下的 Terminal 交互,就可以用 Console 类代劳。

但我们不总是能得到可用的 Console,一个 JVM 是否有可用的 Console 依赖于底层平台和 JVM 如何被调用。

如果 JVM 是在交互式命令行 (比如 Windows 的 cmd) 中启动的,并且输入输出没有重定向到另外的地方,那么就可以得到一个可用的 Console 实例。

对脚本语言的支持

如:ruby,groovy,javascript。

JDK 7

数字变量对下滑线的支持

JDK1.7 可以在数值类型的变量里添加下滑线。

例如:

int num = 1234_5678_9; 
float num2 = 222_33F; 
long num3 = 123_000_111L;

注意,有几个地方是不能添加的:

  1. 数字的开头和结尾
  2. 小数点前后
  3. F 或者 L 前

switch 对 String 的支持

String status = "orderState";     
switch (status) {   
    case "ordercancel":   
        System.out.println("订单取消");   
        break;   
    case "orderSuccess":   
        System.out.println("预订成功");   
        break;   
    default:   
        System.out.println("状态未知");   
}

try-with-resource

try-with-resources
try-with-resources

可以使用 try-with-resources 的资源有: 任何实现了 java.lang.AutoCloseable 接口 java.io.Closeable 接口的对象。

例如:

public static String readFirstLineFromFile(String path) throws IOException {   

    try (BufferedReader br = new BufferedReader(new FileReader(path))) {   
        return br.readLine();   
    }   
}

在 java 7 以及以后的版本里, BufferedReader 实现了 java.lang.AutoCloseable 接口。

由于 BufferedReader 定义在 try-with-resources 声明里,无论 try 语句正常还是异常的结束,

它都会自动的关掉。而在 java7 以前,你需要使用 finally 块来关掉这个对象。

捕获多种异常并用改进后的类型检查来重新抛出异常

public static void first(){   
    try {   
        BufferedReader reader = new BufferedReader(new FileReader(""));   
        Connection con = null;   
        Statement stmt = con.createStatement();   
    } catch (IOException | SQLException e) {   
        //捕获多个异常,e就是final类型的   
        e.printStackTrace();   
    }   
}

优点:用一个 catch 处理多个异常,比用多个 catch 每个处理一个异常生成的字节码要更小更高效。

创建泛型时类型推断

只要编译器可以从上下文中推断出类型参数,你就可以用一对空着的尖括号 <> 来代替泛型参数。

这对括号私下被称为菱形 (diamond)。 在 Java SE 7 之前,你声明泛型对象时要这样

List<String> list = new ArrayList<String>();

而在 Java SE7 以后,你可以这样

List<String> list = new ArrayList<>();

因为编译器可以从前面 (List) 推断出推断出类型参数,所以后面的 ArrayList 之后可以不用写泛型参数了,只用一对空着的尖括号就行。

当然,你必须带着菱形 <> ,否则会有警告的。

Java SE7 只支持有限的类型推断:只有构造器的参数化类型在上下文中被显著的声明了,你才可以使用类型推断,否则不行。

List<String> list = new ArrayList<>();
list.add("A"); 
//这个不行 
list.addAll(new ArrayList<>()); 
// 这个可以 
List<? extends String> list2 = new ArrayList<>(); 
list.addAll(list2);

JDK 8

Lambda 表达式和函数式接口

Lambda 表达式(也称为闭包)是 Java 8 中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多 JVM 平台上的语言(Groovy、Scala 等)从诞生之日就支持 Lambda 表达式,但是 Java 开发者没有选择,只能使用匿名内部类代替 Lambda 表达式。

Lambda 的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的 Lambda 表达式可由逗号分隔的参数列表、-> 符号和语句块组成。

Lambda 的设计者们为了让现有的功能与 Lambda 表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为 Lambda 表达式。java.lang.Runnable 和java.util.concurrent.Callable 是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解 @FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了)。

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

Java 8 使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点类似 traits,不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。

默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写。

由于 JVM 上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给 java.util.Collection 接口添加新方法,如 stream()、parallelStream()、forEach() 和 removeIf() 等等。

尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果想了解更多细节,可以参考官方文档。

更好的类型推断

Java 8 编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。

参数 Value.defaultValue() 的类型由编译器推导得出,不需要显式指明。在 Java 7 中这段代码会有编译错误,除非使用 Value.defaultValue()

Optional

Java 应用中最常见的 bug 就是空指针异常。在 Java 8 之前,Google Guava 引入了 Optionals 类来解决 NullPointerException ,从而避免源码被各种 null 检查污染,以便开发者写出更加整洁的代码。Java 8 也将 Optional 加入了官方库。

Optional 仅仅是一个容易存放 T 类型的值或者 null。它提供了一些有用的接口来避免显式的 null 检查,可以参考 Java 8 官方文档了解更多细节。

如果 Optional 实例持有一个非空值,则 isPresent() 方法返回 true,否则返回 false; orElseGet() 方法,Optional 实例持有 null,则可以接受一个 lambda 表达式生成的默认值;map() 方法可以将现有的 Optional 实例的值转换成新的值;orElse() 方法与 orElseGet() 方法类似,但是在持有 null 的时候返回传入的默认值。

Stream

新增的 Stream API(java.util.stream)将生成环境的函数式编程引入了 Java 库中。这是目前为止最大的一次对 Java 库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。

Task 类有一个分数(或伪复杂度)的概念,另外还有两种状态:OPEN 或者 CLOSED。现在假设有一个 task 集合,首先看一个问题:在这个 task 集合中一共有多少个 OPEN 状态的点?

在 Java 8 之前,要解决这个问题,则需要使用 foreach 循环遍历 task 集合;但是在 Java 8 中可以利用 steams 解决:包括一系列元素的列表,并且支持顺序和并行处理。

final Collection<Task> tasks = Arrays.asList(
        new Task(Status.OPEN, 5),
        new Task(Status.OPEN, 13),
        new Task(Status.CLOSED, 8)
);

// 使用sum()计算所有 OPEN 任务
final long totalPointsOfOpenTasks = tasks
        .stream()
        .filter(task -> task.getStatus() == Status.OPEN)
        .mapToInt(Task::getPoints)
        .sum();

System.out.println("Total points: " + totalPointsOfOpenTasks);

首先,tasks 集合被转换成 steam 表示;其次,在 steam 上的 filter 操作会过滤掉所有 CLOSED 的 task;第三,mapToInt 操作基于每个 task 实例的 Task::getPoints 方法将 task 流转换成 Integer 集合;最后,通过 sum 方法计算总和,得出最后的结果。

新的日期时间 API

Java 8 引入了新的 Date-Time API(JSR 310) 来改进时间、日期的处理。时间和日期的管理一直是最令 Java 开发者痛苦的问题。java.util.Date 和后来的 java.util.Calendar 一直没有解决这个问题(甚至令开发者更加迷茫)。因为上面这些原因,诞生了第三方库 Joda-Time,可以替代 Java 的时间管理 API。

Java 8 中新的时间和日期管理 API 深受 Joda-Time 影响,并吸收了很多 Joda-Time 的精华。

第一,新的 java.time 包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的 API 认真考虑了这些类的不变性(从 java.util.Calendar 吸取的教训),如果某个实例需要修改,则返回一个新的对象。

第二,关注下 LocalDate 和 LocalTime 类。LocalDate 仅仅包含 ISO-8601 日历系统中的日期部分;LocalTime 则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用 Clock 对象构建得到。

第三,LocalDateTime 类包含了 LocalDate 和 LocalTime 的信息,但是不包含 ISO-8601 日历系统中的时区信息。这里有一些关于 LocalDate 和 LocalTime 的例子:

如果你需要特定时区的 data/time 信息,则可以使用 ZoneDateTime,它保存有 ISO-8601 日期系统的日期和时间,而且有时区信息。

Nashorn JavaScript 引擎

Java 8 提供了新的 Nashorn JavaScript 引擎,使得我们可以在 JVM 上开发和运行 JS 应用。

Nashorn JavaScript 引擎是 javax.script.ScriptEngine 的另一个实现版本,这类 Script 引擎遵循相同的规则,允许 Java 和 JavaScript 交互使用。

Base64

对 Base64 编码的支持已经被加入到 Java 8 官方库中,这样不需要使用第三方库就可以进行 Base64 编码。

欢迎转载,转载请注明出处!

独立域名博客:flywill.cn

欢迎关注公众微信号:Java小镇V

分享自己的学习 & 学习资料 & 生活

想要交流的朋友也可以加微信号备注入群:EscUpDn

原文  https://segmentfault.com/a/1190000021135595
正文到此结束
Loading...