JAVA8新特性(上)

1. 简介

毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。

在本文中我们将学习这些新特性,并用实际的例子说明在什么场景下适合使用。

这个教程包含Java开发者经常面对的几类问题:

  • 语言
  • 编译器
  • 工具
  • 运行时(JVM)

2. Java语言的新特性

Java 8是Java的一个重大版本,有人认为,虽然这些新特性领Java开发人员十分期待,但同时也需要花不少精力去学习。在这一小节中,我们将介绍Java 8的大部分新特性。

2.1 Lambda表达式和函数式接口

  Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。

(parameters参数) -> expression表达式或方法体

paramaters:类似方法中的形参列表,这里的参数是函数式接口里的参数     ->:可理解为“被用于”的意思

Lambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。

Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同:

String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );

final String separator = ",";
Arrays.asList( "a", "b", "d" ).forEach( 
    ( String e ) -> System.out.print( e + separator ) );

 

匿名传统内部类与使用lambad比较

// 使用 java 7 排序(传统的使用匿名内部类)
    private void sortUsingJava7(List<String> names) {
        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        });
    }

    // 使用 java 8 排序(使用lambad)
    private void sortUsingJava8(List<String> names) {
        Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
}

公共定义的函数式接口

a.功能性接口:Function<T, R>,有输入参数,有返回值,是对接收一个T类型参数,返回R类型的结果的方法的抽象,通过调用apply方法执行内容

需求:给定一个字符串,返回字符串长度

public class Main {
    public static void main(String[] args) {
        String str = "helloworld";
        int length = testFun(str, (s) -> s.length());
        System.out.println(length);
    }
    public static int testFun(String str, Function<String, Integer> fun) {
        // 执行
        Integer length = fun.apply(str);
        return length;
    }

}

实例方法引用

排序后输出

b.消费型接口:Consumer<T>

有输入参数,没返回值

对应的方法类型为接收一个参数,没有返回值

一般来说使用Consumer接口往往伴随着一些期望状态的改变

或者事件的发生,典型的forEach就是使用的Consumer接口

虽然没有任何的返回值,但是向控制台输出结果

Consumer 使用accept对参数执行行为

public class Main {
    public static void main(String[] args) {
        String str = "helloworld";
        testCon(str,(s)->System.out.println(s));
    }

    public static void testCon(String str, Consumer<String> con) {
        // 执行
        con.accept(str);
    }

}

c.供给型接口:Supplier<T>,无传入参数,有返回值,该接口对应的方法类型不接受参数,但是提供一个返回值,使用get()方法获得这个返回值

public class Main {
    public static void main(String[] args) {
        String str = "helloworld";
        String resultStr = testSup(()->str);
        System.out.println(resultStr);
    }

    public static String testSup(Supplier<String> sup) {
        // 执行
        String s = sup.get();
        return s;
    }
}

d.断言型接口:Predicate<T>

有传入参数,有返回值Boolean

该接口对应的方法为接收一个参数,返回一个Boolean类型值

多用于判断与过滤,使用test()方法执行这段行为

需求:输入字符串,判断长度是否大于0

public class Main {
    public static void main(String[] args) {
        String str = "helloworld";
        Boolean flg = testSup(str,(s)->str.length()>0);
        if (flg){
            System.out.println("字符串大于0");
        }else {
            System.out.println("字符串小于0");
        }
    }
    public static Boolean testSup(String str,Predicate<String> pre) {
        // 执行
        Boolean b = pre.test(str);
        return b;
    }
}

2.2 lambad表达式优点

a.极大的减少代码冗余,同时可读性也好过冗长的匿名内部类

b.与集合类批处理操作结合,实现内部迭代,并充分利用现代多核CPU进行并行计算。之前集合类的迭代都是外部的,即客户代码。而内部迭代意味着由Java类库来进行迭代,而不是客户代码

流的性能测试:https://mp.weixin.qq.com/s/-p-N-K_oY-vGtvBIKUqShA

2. Java官方库的新特性

Java 8增加了很多新的工具类(date/time类),并扩展了现存的工具类,以支持现代的并发编程、函数式编程等。

2.1 Optional

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

接下来看一点使用Optional的例子:可能为空的值或者某个类型的值:

Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );        
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); 
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

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

上述代码的输出结果如下:

Full Name is set? false
Full Name: [none]
Hey Stranger!

2.2 Streams

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
        strings.forEach(s -> System.out.println(s));//全部输出
        List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());//经过过滤后输出
        filtered.forEach(s -> System.out.println(s));

A.流中使用forEach来迭代流中的每个数据

Random random = new Random();
random.ints().limit(10).forEach(System.out :: println);
List<String> strings1 = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
long count = strings1.stream().filter(s -> s.isEmpty()).count();

B.Filter方法用于通过设置的条件过滤出元素(见上面程序)

C.limit 方法用于获取指定数量的流见下面程序(获取10个数)

Random random2 = new Random();
random2.ints().limit(10).sorted().forEach(System.out::println);

D.sorted 方法用于对流进行排序,见上面程序

List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
Long count2 = strings.parallelStream().filter(string -> string.isEmpty()).count();
System.out.println("并行(parallel)程序空字符串的数量" + count2);

E.并行处理字符串(与fork/join原理一样,利用多核CPU)

List<String> strings3 = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered1  = strings3.stream().filter(s -> !s.isEmpty()).collect(Collectors.toList());
System.out.println("筛选列表: " + filtered1);

//将流中的元素放置到一个列表集合中去。这个列表默认为ArrayList

 String mergedString  = strings3.stream().filter(s -> !s.isEmpty()).collect(Collectors.joining(","));
 System.out.println("合并字符串: " + mergedString);

//joining的目的是将流中的元素全部以字符序列的方式连接到一起,可以指定连接符,甚至是结果的前后缀。

2.3 Nashorn JavaScript引擎

Java 8提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本,这类Script引擎遵循相同的规则,允许Java和JavaScript交互使用,例子代码如下:

ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName( "JavaScript" );
System.out.println( engine.getClass().getName() );
System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );

这个代码的输出结果如下:

jdk.nashorn.api.scripting.NashornScriptEngine
Result: 2

2.4 Base64

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

package com.javacodegeeks.java8.base64;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class Base64s {
    public static void main(String[] args) {
        final String text = "Base64 finally in Java 8!";
        final String encoded = Base64
            .getEncoder()
            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
        System.out.println( encoded );
        final String decoded = new String( 
            Base64.getDecoder().decode( encoded ),
            StandardCharsets.UTF_8 );
        System.out.println( decoded );
    }
}

这个例子的输出结果如下:

QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
Base64 finally in Java 8!

新的Base64API也支持URL和MINE的编码解码。
(Base64.getUrlEncoder() / Base64.getUrlDecoder()Base64.getMimeEncoder() / Base64.getMimeDecoder())。

BASE64不是用来加密的,是BASE64编码后的字符串,例如:在上传图片时候就会将图片转成base64,之前用过的freemark生成world文档就是。全部都是由标准键盘上面的常规字符组成,这样编码后的字符串在网关之间传递不会产生UNICODE字符串不能识别或者丢失的现象。你再仔细研究下EMAIL就会发现其实EMAIL就是用base64编码过后再发送的。然后接收的时候再还原。

2.5 并行数组

Java8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法:

package com.javacodegeeks.java8.parallel.arrays;
import java.util.Arrays;
import java.util.concurrent.ThreadLocalRandom;
public class ParallelArrays {
    public static void main( String[] args ) {
        long[] arrayOfLong = new long [ 20000 ];        
        Arrays.parallelSetAll( arrayOfLong, 
            index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );//取前10位数
        System.out.println();
        Arrays.parallelSort( arrayOfLong );        
        Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
            i -> System.out.print( i + " " ) );//排序后取前10位数
        System.out.println();
    }
}

上述这些代码使用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是:

Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 
Sorted: 39 220 263 268 325 607 655 678 723 793

3. 新的Java工具

Java 8提供了一些新的命令行工具,这部分会讲解一些对开发者最有用的工具。

3.1 Nashorn引擎:jjs

jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。例如,我们写一个func.js文件,内容如下:

function f() { 
     return 1; 
}; 

print( f() + 1 );

可以在命令行中执行这个命令:jjs func.js,控制台输出结果是:

2

如果需要了解细节,可以参考官方文档

3.2 类依赖分析器:jdeps

jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。

我们可以利用jedps分析下Spring Framework库,为了让结果少一点,仅仅分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar

jdeps org.springframework.core-3.0.5.RELEASE.jar

这个命令会输出很多结果,我们仅看下其中的一部分:依赖关系按照包分组,如果在classpath上找不到依赖,则显示"not found".

org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
   org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.io                                            
      -> java.lang                                          
      -> java.lang.annotation                               
      -> java.lang.ref                                      
      -> java.lang.reflect                                  
      -> java.util                                          
      -> java.util.concurrent                               
      -> org.apache.commons.logging                         not found
      -> org.springframework.asm                            not found
      -> org.springframework.asm.commons                    not found
   org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
      -> java.lang                                          
      -> java.lang.annotation                               
      -> java.lang.reflect                                  
      -> java.util

 

 

其他补充

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

@FunctionalInterface
public interface Functional {
    void method();
}

不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

@FunctionalInterface
public interface FunctionalDefaultMethods {
    void method();

    default void defaultMethod() {            
    }        
}

Lambda表达式作为Java 8的最大卖点,它有潜力吸引更多的开发者加入到JVM平台,并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节,可以参考官方文档

 

Date/Time API(JSR 310)

Java 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。

1、本地化日期时间 API

public class Main {

    public static void main(String[] args) {
        Main java8tester = new Main();
        java8tester.testLocalDateTime();
    }

    public void testLocalDateTime(){

        // 获取当前的日期时间
        LocalDateTime currentTime = LocalDateTime.now();
        System.out.println("当前时间: " + currentTime);

        LocalDate date1 = currentTime.toLocalDate();
        System.out.println("date1: " + date1);

        Month month = currentTime.getMonth();
        int day = currentTime.getDayOfMonth();
        int seconds = currentTime.getSecond();

        System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);

        LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
        System.out.println("date2: " + date2);

        // 12 december 2014
        LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
        System.out.println("date3: " + date3);

        // 22 小时 15 分钟
        LocalTime date4 = LocalTime.of(22, 15);
        System.out.println("date4: " + date4);

        // 解析字符串
        LocalTime date5 = LocalTime.parse("20:15:30");
        System.out.println("date5: " + date5);
    }
}

输出结果

2、使用时区的日期时间API

public class Main {

    public static void main(String[] args) {
        Main java8tester = new Main();
        java8tester.testZonedDateTime();
    }

    public void testZonedDateTime(){

        // 获取当前时间日期
        ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
        System.out.println("date1: " + date1);

        ZoneId id = ZoneId.of("Europe/Paris");
        System.out.println("ZoneId: " + id);

        ZoneId currentZone = ZoneId.systemDefault();
        System.out.println("当期时区: " + currentZone);
    }
}

待分析:

方法引用

重复注解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值