一、接口的默认方法

Java8允许我们给Interface添加一个非抽象的MethodImplementation,只需要Usagedefault关键字即可,这个特征又叫做扩展Method,Example如下:

Code如下:

interface Formula{
    double calculate(int a);
    
    default double sqrt(int a){
        return Math.sqrt(a);
    }
}

FormulaInterface在拥有calculateMethod之外同时还定义了sqrtMethod,Implementation了FormulaInterface的子类只需要Implementation一个calculateMethod,默认Methodsqrt将在子类上可以直接Usage。 Code如下: Formula formula=new Formula){@Override public double calculate(int a){return sqrt(a100);}];formula.calculate(100);/100.0 formula.sqrt(16);//4.0

文中的formula被Implementation为一个匿名类的Example,该Code非常容易理解,6行Code Implementation了计算sqrt(a*100)。在下一节中,我们将会看到Implementation单MethodInterface的更简单的做法。 译者注:在Java中只有单继承,如果要让一个类赋予新的Features,通常是UsageInterface来Implementation,在C++中支持多继承,允许一个子类同时具有多个父类的Interface与功能,在其他语言中,让一个类同时具有其他的可复用Code的Method叫做mixin。新的Java8的这个特新在Compile器Implementation的角度上来说更加接近Scala的trait。在C#中也有名为扩展Method的概念,允许给已存在的Type扩展Method,Java8的这个在语义上有差别。

二、Lambda表达式

首先看看在老Version的Java中是如何排列字符串的: Code如下:

List names=Arrays.asList(“peterF”,“anna”,“mike”,“xenia”); Collections.sort(names,new Comparator){@Override public int compare(String a,String b){ return b.compareTo(a);}});

只需要给静态MethodCollections.sort传入一个List对象以及一个比较器来按指定顺序排列。通常做法都是Create一个匿名的比较器对象然后将其传递给sortMethod。 在Java8中你就没必要Usage这种传统的匿名对象的方式了,Java8提供了更简洁的Syntax,lambdaExpression: Code如下: Collections.sort(names,(String a,String b)->{return b.compareTo(a);});

看到了吧,Code变得更段且更具有可读性,但是实际上还可以写得更短: Code如下: Collections.sort(names,(String a,String b)->b.compareTo(a);

对于Function体只有一行Code的,你可以去掉大括号0以及return关键字,但是你还可以写得更短点:

Code如下: Collections.sort(names,(a,b)>b.compareTo(a);

JavaCompile器可以自动推导出ParameterType,所以你可以不用再写一次Type。接下来我们看看lambdaExpression还能作出什么更方便的东西来:

三、函数式接口

Lambda Expression是如何在java的Type系统中表示的呢?

每一个lambdaExpression都对应一个Type,通常是InterfaceType。 而“Function式Interface”是指仅仅只包含一个抽象Method的Interface,每一个该Type的lambdaExpression都会被匹配到这个抽象Method。因为默认Method不算抽象Method,所以你也可以给你的Function式Interface添加默认Method。 我们可以将lambdaExpression当作任意只包含一个抽象Method的InterfaceType,确保你的Interface一定达到这个要求,你只需要给你的Interface添加@Functionallnterface注解,Compile器如果发现你标注了这个注解的Interface有多于一个抽象Method的时候会Error的。

Example如下:

Code如下: @Functionallnterface interface Converter<F,T>{T convert(F from);}Converter<String,Integer>converter= from)->Integer.valueOf(from);Integer converted=converter.convert(“123”);System.out.printin(converted);//123

需要注意如果@Functionallnterface如果没有指定,上面的Code也是对的。 译者注将lambdaExpression映射到一个单Method的Interface上,这种做法在Java8之前就有别的语言Implementation,比如Rhino JavaScript解释器,如果一个FunctionParameter接收一个单Method的Interface而你传递的是一个function,Rhino 解释器会自动做一个单Interface的Example到function的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget的addEventListener 第二个ParameterEventListener。

四、方法与构造函数引用

前一节中的Code还可以通过静态Method引用来表示: Code如下: Converter<String,Integer>converter=Integer:valueOf;Integer converted=converter.convert(“123”);System.out.printin(converted);//123 Java8允许你Usage:关键字来传递Method或者构造Function引用,上面的Code展示了如何引用一个静态Method,我们也可以引用一个对象的Method: Code如下: converter=something::startsWith;String converted=converter.convert(“Java”);System.out.printin(converted);//” 接下来看看构造Function是如何Usage:关键字来引用的,首先我们定义一个包含多个构造Function的简单类: Code如下:

Code如下: Converter<String,Integer> converter=Integer::valueOf;Integer converted=converter.convert(“123”);System.out.println(converted);//123 Java8允许你Usage::关键字来传递Method或者构造Function引用,上面的Code展示了如何引用一个静态Method,我们也可以引用一个对象的Method: Code如下: converter=something:startsWith;String converted=converter.convert(“Java”);system.out.println(converted);/∥“y” 接下来看看构造Function是如何Usage:关键字来引用的,首先我们定义一个包含多个构造Function的简单类: Code如下: class Person{String firstName;String lastName;Person)} Person(String firstName,String lastName){this.firstName =firstName;this.lastName=lastName;}} 接下来我们指定一个用来CreatePerson对象的对象工厂Interface: Code如下: interface PersonFactory

{P create(String firstName,String lastName);} 这里我们Usage构造Function引用来将他们关联起来,而不是Implementation一个完整的工厂: Code如下:

PersonFactorypersonFactory=Person::new;Person person=personFactory.create(“Peter”, “Parker”);

我们只需要UsagePerson::new来GetPerson类构造Function的引用,JavaCompile器会自动根据PersonFactory.createMethod的签名来Choose合适的构造Function。

五、Lambda作用域

在lambdaExpression中访问外层作用域和老Version的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者Example的字段以及静态变量。

六、访问局部变量

我们可以直接在lambdaExpression中访问外层的局部变量: Code如下: final int num=1;Converter<Integer,String>stringConverter=(from)->String.valueof(from +num);stringConverter.convert(2);//3

但是和匿名对象不同的是,这里的变量num可以不用声明为final,该Code同样正确: Code如下: int num=1;Converter<Integer,String>stringConverter=(from)->String.valueof(from+num);stringConverter.conver(2);//3

不过这里的num必须不可被后面的Code修改(即隐性的具有fina的语义),例如下面的就无法Compile: Code如下: int num=1;Converter<Integer,String>stringConverter =(from)->string.valueof(from +num);num=3;

在lambdaExpression中试图修改num同样是不允许的。

七、访问对象字段与静态变量

和本地变量不同的是,lambda内部对于Example的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的: Code如下: class Lambda4{static int outerStaticNum;int outerNum;void testScopes(){Converter<Integer,String>stringConverter1=(from)->{outerNum=23;return String.valueof(from);};Converter<Integer,String>stringConverter2=(from)->{outerStaticNum=72;return String.valueof(from);};}}

八、访问接口的默认方法

还记得第一节中的formula例子么,InterfaceFormula定义了一个默认Methodsqrt可以直接被formula的Example包括匿名对象访问到,但是在lambdaExpression中这个是不行的。Lambda Expression中是无法访问到默认Method的,以下Code将无法Compile: Code如下: Formula formula=(a)->sqrt(a*100);Built-in Functional Interfaces

JDK 1.8API包含了很多内建的Function式Interface,在老Java中常用到的比如Comparator或者RunnableInterface,这些Interface都增加了@Functionallnterface注解以便能用在lambda上。Java8APl同样还提供了很多全新的Function式Interface来让工作更加方便,有一些Interface是来自Google GuavaLibrary里的,即便你对这些很熟悉了,还是有必要看看这些是如何扩展到lambda上Usage的。

PredicateInterface Predicate Interface只有一个Parameter,ReturnbooleanType。该Interface包含多种默认Method来将Predicate组合成其他复杂的逻辑(比如:与,或,非): Code如下: Predicatepredicate=(s)->s.length()>0;predicate.test(“foo”);//true predicate.negate().test(“foo”);//false Predicate nonNull =Objects:nonNull;PredicateisNull=Objects:isNull;PredicateisEmpty=String:isEmpty;PredicateisNotEmpty=isEmpty.negate();

FunctionInterface Function Interface有一个Parameter并且Return一个结果,并附带了一些可以和其他Function组合的默认Method(compose,andThen):Code如下: Function<String,Integer> tolnteger=Integer:valueOf;Function<String,String>backToString= tolnteger.andThen(String:valueOf);backToString.apply(“123”);//“123”

Supplier InterfaceSupplier InterfaceReturn一个任意范型的值,和FunctionInterface不同的是该Interface没有任何ParameterCode如下: Supplier personSupplier=Person:new;personSupplier get();//new Person

Consumer InterfaceConsumer接口表示执行在单个参数上的操作。 Code如下: Consumergreeter=(p)->System.out.println(“Hello,“+p.firstName);greeter.accept(new Person(“Luke”,“Skywalker”));

Comparator InterfaceComparator是老ava中的经典Interface,Java8在此之上添加了多种默认Method: Code如下: Comparatorcomparator =(p1,p2)->p1.firstName.compareTo(p2.firstName);Person p1=new Person(“John”,“Doe”);Person p2=new Person(“Alice”,“Wonderland”);comparator.compare(p1,p2);//>0 comparator.reversed().compare(p1,p2);//<0

optionalInterface Optional 不是Function是Interface,这是个用来防止NullPointerException异常的辅助Type,这是下一届中将要用到的重要概念,现在先简单的看看这个Interface能干什么: Optional被定义为一个简单的Container,其值可能是null或者不是null。在Java8之前一般某个Function应该Return非空对象但是偶尔却可能Return了null,而在Java8中,不推荐你Returnnull而是ReturnOptional。 Code如下: Optionaloptional=Optional.of(“bam”);optional.isPresent();//true optional.get();//“bam”optional.orElse(“fallback”);//“bam”

optional.ifPresent((s)->System.out.println(s.charAt(O));//“b”

StreamInterface java.util.Stream 表示能应用在一组元素上一次Execute的操作序列。Stream操作分为中间操作或者最终操作两种,最终操作Return一特定Type的计算结果,而中间操作ReturnStream本身,这样你就可以将多个操作依次串起来。 Stream 的Create需要指定一个Data源,比如java.util.Collection的子类,List或者Set,Map不支持。Stream的操作可以串行Execute或者并行Execute。 首先看看Stream是怎么用,首先CreateExampleCode的用到的DataList: Code如下: List stringCollection=new ArrayList<>();;stringCollection.add(“ddd2”);stringCollection.add(“aaa2”);stringCollection.add(“bbb1”);stringCollection.add(“aaa1”);stringCollection.add(“bbb3”);stringCollection.add(“ccc”);stringCollection.add(“bbb2”);stringCollection.add(“ddd1”);Java 8扩展了集合类,可以通过Collection.stream()或者Collection.parallelStream()来Create一个Stream。下面几节将详细解释常用的Stream操作:

Filter 过滤 过滤通过一个predicateInterface来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个Function来对过滤后的元素依次Execute。forEach是一个最终操作,所以我们不能在forEach之后来Execute其他Stream操作。 Code如下: stringCollection.stream().filter(s)->s.startsWith(“a”).forEach(System.out::println); //“aaa2”,“aaal”

Sort Sorting Sorting是一个中间操作,Return的是Sorting好后的Stream。如果你不指定一个自定义的Comparator则会Usage默认Sorting。 Code如下: stringCollection.stream().sorted().filter(s)->s.startsWith(“a”)).forEach(System.out:printin); //“aaa1”,“aaa2” 需要注意的是,Sorting只Create了一个排列好后的Stream,而不会影响原有的Data源,Sorting之后原Data stringCollection是不会被修改的: Code如下: System.out.printin(stringCollection);//ddd2,aaa2,bbb1,aaal,bbb3,ccc,bbb2,ddd1 Map映射中间操作map会将元素根据指定的FunctionInterface来依次将元素转成另外的对象,下面的Example展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他Type,mapReturn的StreamType是根据你map传递进去的Function的Return值决定的。 Code如下: stringCollection.stream().map(String:toUpperCase).sorted(a,b)->b.compareTo(a)) .forEach(System.out:printin); //“DDD2”,“DDD1”,“CCC”,“BBB3”,“BBB2”,“AAA2”,“AAA1”

Map映射中间操作map会将元素根据指定的FunctionInterface来依次将元素转成另外的对象,下面的Example展示了将字符串转换为大写字符串。你也可以通过map来讲对象转换成其他Type,mapReturn的StreamType是根据你map传递进去的Function的Return值决定的。 Code如下: stringCollection.stream().map(String:toUpperCase).sorted((a,b)->b.compareTo(a)) .forEach(System.out:printin); //“DDD2”,“DDD1”,“CCC”,“BBB3”,“BBB2”,“AAA2”,“AAA1”

Match 匹配 Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并Return一个booleanType的值。 Code如下: boolean anyStartsWithA=stringCollection.stream().anyMatch(s)->s.startsWith(“a”);System.out.printin(anystartsWithA);/∥true boolean allStartsWithA=stringCollection.stream().allMatch(s)->s.startsWith(“a”));System.out.printin(allStartsWithA);//false boolean noneStartsWithZ=stringCollection.stream().noneMatch(s)->s.startsWith(“z”);System.out.printin(noneStartsWithZ);//true

Count计数计数是一个最终操作,ReturnStream中元素的个数,Return值Type是long。 Code如下: long startsWithB=stringCollection.stream().filter((s)->s.startsWith(“b”)).count();System.out.printin(startsWithB);//3 Reduce规约 这是一个最终操作,允许通过指定的Function来讲stream中的多个元素规约为一个元素,规越后的结果是通过OptionalInterface表示的:

Code如下: Optional reduced=stringCollection.stream().sorted).reduce((51,52)->51+”#“+52);reduced.ifPresent(System.out::println);//“aaal#aaa2#bbb1#bbb2#bbb3#cCC#ddd1#ddd2”

并行Streams

前面提到过Stream有串行和并行两种,串行Stream上的操作是在一个Thread中依次完成,而并行Stream则是在多个Thread上同时Execute。 下面的例子展示了是如何通过并行Stream来提升Performance: 首先我们Create一个没有重复元素的大表: Code如下:

int max=1000000; List values=new Arraylist<>(max); for(inti=0;i< max;i++){ UUID uuid= UUID. randomUUID(); values. add(uuid. toString());}

然后我们计算一下Sorting这个Stream要耗时多久,串行Sorting: Code如下:

long to=System. nanoTime(); long count=values. stream(). sorted(). count(); System. out. println(count); long t1=System. nanoTime(); long millis =TimeUnit. NANOSECONDS. toMillis(t1-to); System. out. println(String. format(“sequential sort took:%d ms”, millis);

∥串行耗时:899ms并行Sorting:

Code如下: long to=System.nanoTime();long count=values.parallelStream().sorted).count();System.out.println(count);long t1=System.nanoTime();long mills=TimeUnit.NANOSECONDS.toMillis(t1-to);System.out.printin(String.format(“parallel sort took: %d ms”,millis));

//并行Sorting耗时:472ms上面两个Code几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream()。

Map前面提到过,MapType不支持stream,不过Map提供了一些新的有用的Method来处理一些日常任务。 Code如下: Map<Integer,String>map=new HashMap<>);for(inti=0;i<10;i++){map.putlfAbsent(i,“val”+i);} map.forEach(id,val)>System.out.println(val);以上Code很容易理解,putlfAbsent不需要我们做额外的存在性检查,而forEach则接收一个ConsumerInterface来对map里的每一个键值对进行操作。 下面的例子展示了map上的其他有用的Function:

Code如下:

map. computelfPresent(3,(num, val)->val+num); map. get(3);//val33 map. computelfPresent(9,(num, val)->nul); map. containsKey(9);//falsemap. computelfAbsent(23, num->“val”+num); map. containsKey(23);//true map. computelfAbsent(3, num->“bam”); map. get(3);//val33

接下来展示如何在Map里删除一个键值全都匹配的项: Code如下: map.remove(3,“val3”);map.get(3);//val33map.remove(3,“val33”);map.get(3);/∥null另外一个有用的Method: Code如下: map.getOrDefault(42,“not found”);//not found对Map的元素做合并也变得很容易了: Code如下:

map.getOrDefault(42,“not found”);//not found对Map的元素做合并也变得很容易了: Code如下: map.merge(9,“val9”,(value,newValue)->value.concat(newValue));map.get(9);//val9 map.merge(9,“concat”,(value,newValue)->value.concat(newValue);map.get(9);//val9concat Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。

九、Date API Java 8在包java.time下包含了一组全新的时间日期APl。

新的日期APl和开源Joda-TimeLibrary差不多,但又不完全一样,下面的例子展示了这组新APl里最重要的一些部分:

Clock时钟 Clock类提供了访问当前日期和时间的Method,Clock是时区敏感的,可以用来取代System.currentTimeMils0来Get当前的微秒数。某一个特定的时间点也可以UsageInstant类来表示,Instant类也可以用来Create老的java.util.Date对象。 Code如下: Clock clock=Clock.systemDefaultZone();long millis =clock.mils();Instant instant=clock.instant();Date legacyDate=Date.from(instant);//legacy java.util.Date

Timezones时区 在新API中时区UsageZoneld来表示。时区可以很方便的Usage静态Methodof来Get到。时区定义了到UTS时间的时间差,在Instant时间点对象到本地日期对象之间转换的时候是极其重要的。 Code如下: System.out.println(Zoneld.getAvailableZonelds());//prints all available timezone ids Zoneld zone1=Zoneld.of(“Europe/Berlin”);Zoneld zone2=Zoneld.of(“Brazil/East”);System.out.println(zone1.getRules();System.out.printin(zone2.getRules());

LocalTime本地时间 LocalTime 定义了一个没有时区信息的时间,例如晚上10点,或者17:30:15。下面的例子Usage前面CodeCreate的时区Create了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差: Code如下: LocalTime now1=LocalTime.now(zone1);LocalTime now2=LocalTime.now(zone2);System.out.println(now1.isBefore(now2);//false long hoursBetween=ChronoUnit.HOURS.between(now1,now2);long minutesBetween= ChronoUnit.MINUTES.between(now1,now2);System.out.println(hoursBetween);//-3 System.out.printin(minutesBetween);//-239 LocalTime 提供了多种工厂Method来简化对象的Create,包括Analysis时间字符串。 Code如下: LocalTime late =LocalTime.of(23,59,59);System.out.println(late);//23:59:59 DateTimeFormatter germanFormatter=Date TimeFormatter.ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN);LocalTime leetTime=LocalTime.parse(“13:37”,germanFormatter);System.out.printin(leetTime);//13:37

LocalDate本地日期

LocalDate表示了一个确切的日期,比如2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作Return的总是一个新Example。 Code如下:

LocalDate today=LocalDate.now();LocalDate tomorrow =today.plus(1,Chronounit.DAYS);LocalDate yesterday=tomorrow.minusDays(2);LocalDate independenceDay =LocalDate.of(2014,Month.JULY,4);DayofWeek dayofweek= independenceDay.getDayOfWeek();System.out.printin(dayofWeek);//FRIDAY 从字符串Analysis一个LocalDateType和AnalysisLocalTime一样简单: Code如下: DateTimeFormatter germanFormatter=Date TimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN);LocalDate xmas =LocalDate.parse(“24.12.2014”,germanFormatter);System.out.println(xmas);//2014-12- 24

LocalDateTime 本地日期时间 LocalDateTime同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的Method。 Code如下: LocalDate Time sylvester=LocalDate Time.of(2014,Month.DECEMBER,31,23,59,59);DayofWeek dayofWeek=sylvester.getDayofWeek();System.out.println(dayOfWeek);//WEDNESDAY Month month=sylvester.getMonth();System.out.println(month);//DECEMBER long minuteOfDay=sylvester getLong(ChronoField.MINUTE_OF_DAY);System.out.println(minuteOfDay);// 1439只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。 Code如下: Instant instant=sylvester.atZone(Zoneld.systemDefault().tolnstant();Date legacyDate=Date.from(instant);System.out.printin(legacyDate);//Wed Dec 31 23:59:59 CET 2014格式化LocalDateTime和格式化时间和日期一样的,除了Usage预定义好的格式外,我们也可以自己定义格式:

Code如下: DateTimeFormatter formatter=DateTimeFormatter.ofPattern(“MMM dd,yyyy-HH:mm”);LocalDateTime parsed=LocalDateTime.parse(“Nov 03,2014-07:13”,formatter);String string= formatter.format(parsed);System.out.println(string);//Nov 03,2014-07:13和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是ThreadSafety的。

十、Annotation 注解

在Java8中支持多重注解了,先看个例子来理解一下是什么意思。首先定义一个包装类Hints注解用来放置一组具体的Hint注解: Code如下: @interface Hints{Hint]value();} @Repeatable(Hints.class)@interface Hint{String value();} Java8允许我们把同一个Type的注解Usage多次,只需要给该注解标注一下@Repeatable即可。 例1:Usage包装类当Container来存多个注解(老Method)Code如下: @Hints({@Hint(“hint1”),@Hint(“hint2”)})class Person} 例2:Usage多重注解(新Method)

Code如下: @Hint(“hint1”)@Hint(“hint2”)class Person}

第二个例子里javaCompile器会隐性的帮你定义好@Hints注解,Understand这一点有助于你用反射来Get这些信息: Code如下:

Hint hint=Person. class. getAnnotation(Hint. class); System. out. println(hint);//null Hints hints1=Person. class. getAnnotation(Hints. class); System. out. printin(hints1. value(). length);//2

Hint[]hints2=Person.class.getAnnotationsBy Type(Hint.class);System.out.printin(hints2.length);//2即便我们没有在Person类上定义@Hints注解,我们还是可以通过 getAnnotation(Hints.class)来Get@Hints注解,更加方便的Method是UsagegetAnnotationsBy Type 可以直接Get到所有的@Hint注解。另外ava8的注解还增加到两种新的target上了: Code如下: @Target{ElementType.TYPE_PARAMETER,ElementType.TYPE_USE})@interface MyAnnotation} 关于Java8的New Features就写到这了,肯定还有更多的Features等待发掘。JDK1.8里还有很多很有用的东西,比如Arrays.parallelSort,StampedLock和CompletableFuture等等。