Java 重学系列之JDK新特性汇总(一文就够了)

Posted amcomputer

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 重学系列之JDK新特性汇总(一文就够了)相关的知识,希望对你有一定的参考价值。

JDK新特性汇总,收集网上各种资源整理。

目录

背景

JDK每个版本的特性太多了,虽然经常用到是JDK 7 or 8。故想用这篇博客来记录每个版本新内容,来当一个速出手册,并且增加一些Demo。

JDK 2

JDK 3

JDK 4

JDK 5

JDK 6

JDK 7

JDK7的新特性速查速记:

  1. 二进制字面量
  2. 数字字面量可以出现下划线
  3. switch 语句可以用字符串
  4. 泛型简化(即泛型推断)
  5. 异常的多个catch合并
  6. try-with-resources 语句
  7. 使用ForkJoin,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行。

1. 二进制字面量

JDK7开始,终于可以用二进制来表示整数(byte,short,intlong)。
使用二进制字面量的好处是,可以使代码更容易被理解。
语法非常简单,只要在二进制数值前面加 0b或者0B
举例:
int x = ob110110;

2. 数字字面量可以出现下划线

为了增强对数值的阅读性,如我们经常把数据用逗号分隔一样。JDK7提供了_对数据分隔。
举例:
int x = 100_1000;
注意事项:
不能出现在进制标识和数值之间
不能出现在数值开头和结尾
不能出现在小数点旁边

int a = 0b100_100;
int b = 0b_100_100;
int c = 0b100_100_;
float d = 12.34_56f;
float e = 12._34_56f;

你好奇底层是如何实现的吗?

3. switch 语句可以用字符串

以前只能用基本数据类型,现在可以使用String

4. 泛型简化(即泛型推断)

泛型实例的创建可以通过类型推断来简化,可以去掉后面new部分的泛型类型,只用<>就可以了。
List<String> list = new ArrayList<>();

5. 异常的多个catch合并

Java 7中,catch代码块得到了升级,用以在单个catch块中处理多个异常。如果你要捕获多个异常并且它们包含相似的代码,使用这一特性将会减少代码重复度。

try 
    //xxx
 catch (A_Exception | B_Exception e) 
    e.printStackTrace();
 

6. try-with-resources 语句

以前打开的文件需要显示关闭,现在不用了,自动关闭资源。

格式:
try(必须是java.lang.AutoCloseable的子类对象)
好处:
资源自动释放,不需要close()了
把需要关闭资源的部分都定义在这里就ok了

private static void method() 
		// try-with-resources 语句
		// try(必须是java.lang.AutoCloseable的子类对象)…

		try 
			FileReader fr = new FileReader("a.txt");
			FileWriter fw = new FileWriter("b.txt");
			int ch = 0;
			while ((ch = fr.read()) != -1) 
				fw.write(ch);
			
			fw.close();
			fr.close();
		 catch (IOException e) 
			e.printStackTrace();
		

		// 改进版的代码
		try (
		FileReader fr = new FileReader("a.txt");
		FileWriter fw = new FileWriter("b.txt");) 
			int ch = 0;
			while ((ch = fr.read()) != -1) 
				fw.write(ch);
			
		 catch (IOException e) 
		   // 不需要关闭了,类似于python里面的with,上下文管理器
			e.printStackTrace();
		
	

7. 使用ForkJoin

如何使用Fork/Join对大数据进行并行求和

public class Main 
    public static void main(String[] args) throws Exception 
        // 创建2000个随机数组成的数组:
        long[] array = new long[2000];
        long expectedSum = 0;
        for (int i = 0; i < array.length; i++) 
            array[i] = random();
            expectedSum += array[i];
        
        System.out.println("Expected sum: " + expectedSum);
        // fork/join:
        ForkJoinTask<Long> task = new SumTask(array, 0, array.length);
        long startTime = System.currentTimeMillis();
        Long result = ForkJoinPool.commonPool().invoke(task);
        long endTime = System.currentTimeMillis();
        System.out.println("Fork/join sum: " + result + " in " + (endTime - startTime) + " ms.");
    

    static Random random = new Random(0);

    static long random() 
        return random.nextInt(10000);
    


class SumTask extends RecursiveTask<Long> 
    static final int THRESHOLD = 500;
    long[] array;
    int start;
    int end;

    SumTask(long[] array, int start, int end) 
        this.array = array;
        this.start = start;
        this.end = end;
    

    @Override
    protected Long compute() 
        if (end - start <= THRESHOLD) 
            // 如果任务足够小,直接计算:
            long sum = 0;
            for (int i = start; i < end; i++) 
                sum += this.array[i];
                // 故意放慢计算速度:
                try 
                    Thread.sleep(1);
                 catch (InterruptedException e) 
                
            
            return sum;
        
        // 任务太大,一分为二:
        int middle = (end + start) / 2;
        System.out.println(String.format("split %d~%d ==> %d~%d, %d~%d", start, end, start, middle, middle, end));
        SumTask subtask1 = new SumTask(this.array, start, middle);
        SumTask subtask2 = new SumTask(this.array, middle, end);
        invokeAll(subtask1, subtask2);
        Long subresult1 = subtask1.join();
        Long subresult2 = subtask2.join();
        Long result = subresult1 + subresult2;
        System.out.println("result = " + subresult1 + " + " + subresult2 + " ==> " + result);
        return result;
    


JDK 8

JDK8的新特性速查速记:

1.Lambda表达式
2.新的日期API
3.引入Optional
4.使用Base64
5.接口的默认方法和静态方法
6.新增方法引用格式
7.新增Stream类
8.注解相关的改变
9.支持并行(parallel)数组
10.对并发类(Concurrency)的扩展。

1.Lambda表达式

这个特性个人觉得第一重要。因为使用频率和看到频率都太高了。

Lambda表达式是Java8中非常重要的一个新特性,其基于函数式编程的思想,支持将代码作为方法参数进行使 用。可以把Lambda表达式理解为通过一种更加简洁的方式表示可传递的匿名函数。 它本身没有名称,而且不像方法那样属于某一个类,但是可以有参数列表、代码体、返回值。使用了Lambda表达 式之后就不需要再去编写匿名类了

Lambda基础格式:

(参数列表)>  
    方法体      

参数列表:即匿名方法的形参 -> :Lambda运算符 方法体:用于执行业务逻辑。可以是单一语句,也可以是语句块。如果是单一语句,可以省略花括号。当需要返回 值,如果方法体中只有一条语句,可以省略return,会自动根据结果进行返回。


public class Test 
    public static void main(String[] args) 
        ArrayList<String> list = new ArrayList<>();
        list.add("zhangsan");
        list.add("lisi");
        list.add("wangwu");
        list.add("zhaoliu");
        //for循环遍历
        for (String s : list) 
            System.out.println(s);
        
        //lambda表达式遍历
        list.forEach(s -> System.out.println(s));//匿名内部类
        Runnable runnable = new Runnable() 
            @Override
            public void run() 
                System.out.println("I'm running");
            
        ;
        //lambda表达式
        Runnable runnable1 = () -> System.out.println("I'm running");
    

2.新的日期API

Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。在旧版的 Java 中,日期时间 API 存在诸多问题,比如:

    1.非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。

    2.设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。

    3.时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。

Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:

       1.Local(本地) − 简化了日期时间的处理,没有时区的问题。

       2.Zoned(时区) − 通过制定的时区处理日期时间

新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。

3.引入Optional

用于尽量避免空指针异常

Optional类实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional 类的引入很好的解决空指针异常。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。尽量避免在程序中直接调用Optional对象的get()和isPresent()方法,避免使用Optional类型声明实体类的属性。

   Optional.of(T t) : 创建一个 Optional 实例
   Optional.empty() : 创建一个空的 Optional 实例
   Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例      
   isPresent() : 判断是否包含值
   orElse(T t) :  如果调用对象包含值,返回该值,否则返回
   torElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值    
   map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
   flatMap(Function mapper):与 map 类似,要求返回值必须是Optional

Optional类示例

1.创建Optional类
( 1)使用empty()方法创建一个空的Optional对象:

Optional<String> empty = Optional.empty();

(2)使用of()方法创建Optional对象:

String name = "hhh";
Optional<String> opt = Optional.of(name);
assertEquals("Optional[hhh]", opt.toString());

传递给of()的值不可以为空,否则会抛出空指针异常。例如,下面的程序会抛出空指针异常。

String name = null;
Optional<String> opt = Optional.of(name);

如果我们需要传递一些空值,那我们可以使用下面的示例所示。

String name = null;
Optional<String> opt = Optional.ofNullable(name);

使用ofNullable()方法,则当传递进去一个空值时,不会抛出异常,而只是返回一个空的Optional对象,如同我们用Optional.empty()方法一样。

2.isPresent

我们可以使用这个isPresent()方法检查一个Optional对象中是否有值,只有值非空才返回true。

Optional<String> opt = Optional.of("binghe");
assertTrue(opt.isPresent());

opt = Optional.ofNullable(null);
assertFalse(opt.isPresent());


在Java8之前,我们一般使用如下方式来检查空值。

if(name != null)
    System.out.println(name.length);

在Java8中,我们就可以使用如下方式来检查空值了。

Optional<String> opt = Optional.of("binghe");
opt.ifPresent(name -> System.out.println(name.length()));

3.orElse和orElseGet

(1)orElse

orElse()方法用来返回Optional对象中的默认值,它被传入一个“默认参数‘。如果对象中存在一个值,则返回它,否则返回传入的“默认参数”。

String nullName = null;
String name = Optional.ofNullable(nullName).orElse("hhh");
assertEquals("hhh", name);

(2)orElseGet
与orElse()方法类似,但是这个函数不接收一个“默认参数”,而是一个函数接口。

String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(() -> "hhh");
assertEquals("hhh", name);

(3)二者有什么区别?

要想理解二者的区别,首先让我们创建一个无参且返回定值的方法。

public String getDefaultName() 
    System.out.println("Getting Default Name");
    return "hhh";

接下来,进行两个测试看看两个方法到底有什么区别。

String text;
System.out.println("Using orElseGet:");
String defaultText = Optional.ofNullable(text).orElseGet(this::getDefaultName);
assertEquals("hhh", defaultText);

System.out.println("Using orElse:");
defaultText = Optional.ofNullable(text).orElse(getDefaultName());
assertEquals("hhh", defaultText);


在这里示例中,我们的Optional对象中包含的都是一个空值,让我们看看程序执行结果:

Using orElseGet:
Getting default name...
Using orElse:
Getting default name...

两个Optional对象中都不存在value,因此执行结果相同。

那么,当Optional对象中存在数据会发生什么呢?我们一起来验证下。

String name = "hhh";

System.out.println("Using orElseGet:");
String defaultName = Optional.ofNullable(name).orElseGet(this::getDefaultName);
assertEquals("hhh", defaultName);

System.out.println("Using orElse:");
defaultName = Optional.ofNullable(name).orElse(getDefaultName());
assertEquals("hhh", defaultName);


运行结果如下所示。

Using orElseGet:
Using orElse:
Getting default name...

可以看到,当使用orElseGet()方法时,getDefaultName()方法并不执行,因为Optional中含有值,而使用orElse时则照常执行。所以可以看到,当值存在时,orElse相比于orElseGet,多创建了一个对象。如果创建对象时,存在网络交互,那系统资源的开销就比较大了,这是需要我们注意的一个地方。

4.orElseThrow

orElseThrow()方法当遇到一个不存在的值的时候,并不返回一个默认值,而是抛出异常。

tring nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow( IllegalArgumentException::new);

5.get

get()方法表示是Optional对象中获取值。

Optional<String> opt = Optional.of("hhh");
String name = opt.get();
assertEquals("hhh", name);

使用get()方法也可以返回被包裹着的值。但是值必须存在。当值不存在时,会抛出一个NoSuchElementException异常。

Optional<String> opt = Optional.ofNullable(null);
String name = opt.get();

6.filter

接收一个函数式接口,当符合接口时,则返回一个Optional对象,否则返回一个空的Optional对象。

String name = "hhh";
Optional<String> nameOptional = Optional.of(name);
boolean isBinghe = nameOptional.filter(n -> "hhh".equals(name)).isPresent();
assertTrue(isBinghe);
boolean ishhh = nameOptional.filter(n -> "hhh".equ

以上是关于Java 重学系列之JDK新特性汇总(一文就够了)的主要内容,如果未能解决你的问题,请参考以下文章

《一文说透数据结构》系列之什么是堆?看这一篇就够了

java8之lambda表达式看这一篇就够了

一文带你深度解析JVM虚拟机,看这份阿里架构师文档就够了

一文带你深度解析JVM虚拟机,看这份阿里架构师文档就够了

Java-lambda表达式入门看这一篇就够了

Java-lambda表达式入门看这一篇就够了