本教程逐一列出了Java8的新特性,并将使用简单的代码示例来指导您如何使用默认的接口方法、lambda表达式、方法引用和多重注释。之后,您将了解最新的API改进,如stream、函数接口、Map和新的date API。
“Java仍未消亡——人们开始意识到这一点。”
本教程将使用带注释的简单代码来描述新特性,你不会看到大量吓人的词汇。
第一,默认的接口方法
Java允许我们向接口添加一个非抽象的方法实现,只需要使用default关键字。这个特性也叫扩展方法,例子如下:复制代码如下:接口公式{ Double Calculate(Inta);
默认double sqrt(int a){ return math . sqrt(a);}}公式接口除了calculate方法还定义了sqrt方法,实现公式接口的子类只需要实现一个calculate方法。默认方法sqrt可以直接在子类中使用。复制代码如下:formula formula=new formula(){ @ override public double calculate(int a){ return sqrt(a * 100);}};
formula . calculate(100);//100.0 formula . sqrt(16);//4.0中的公式实现为匿名类的实例。代码非常容易理解,6行代码实现了sqrt(a * 100)的计算。在下一节中,我们将看到实现单个方法接口的更简单的方法。
译者注:在Java中,只有单一继承。如果一个类要被赋予新的特性,通常是通过使用接口来实现的。在C中,支持多重继承,允许一个子类同时拥有多个父类的接口和函数。在其他语言中,使一个类同时拥有其他可重用代码的方法称为mixin。新Java 8的这个特性在编译器实现方面更接近Scala的特性。C#中还有一个概念叫扩展方法,允许你将方法扩展到现有类型,这和Java 8中的语义不同。
第二,表达式
首先看一下旧版Java中字符串是如何排列的:复制代码如下:liststringnames=arrays . aslist(' Peter ',' Anna ',' Mike ',' Xenia ');
Collections.sort(names,new comparator String(){ @ Override public int compare(String a,String b){ return b . compare to(a);}});您只需要向静态方法Collections.sort传递一个List对象和一个比较器,就可以按照指定的顺序排列它们。通常,会创建一个匿名比较器对象,然后将其传递给sort方法。
你没有必要在Java 8中使用这种传统的匿名对象方式,它提供了更简洁的语法。lambda表达式:复制代码如下:collections.sort (names,(string a,string b)-{ return b.com帕累托(a);});看到了吧,代码变得更加分段,可读性更强,但其实可以写得更短:复制代码如下:collections.sort (names,(string a,string b)-b . com Pareto(a));如果函数体只有一行代码,可以去掉花括号{}和return关键字,但也可以写得短一些:复制代码如下:collections.sort (names,(a,b)-b . compare to(a));Java编译器可以自动推导出参数类型,这样你就不用再写类型了。接下来,让我们看看lambda表达式还能让什么更方便:
三、函数式接口Lambda表达式在java的类型系统中是如何表示的?每个lambda表达式对应一个类型,通常是一个接口类型。“函数接口”指的是只包含一个抽象方法的接口,这种类型的每一个lambda表达式都会匹配到这个抽象方法。因为默认方法不是抽象方法,所以您也可以将默认方法添加到函数接口中。
我们可以把lambda expression当作任何一个只包含一个抽象方法的接口类型,从而保证你的接口必须满足这个要求。你只需要在你的接口上添加@FunctionalInterface注释,如果编译器发现你注释的接口有不止一个抽象方法,就会报错。
例子如下:复制代码如下:@ functional interface接口转换器f,t { tconvert(f from);}ConverterString,整数转换器=(from)-Integer . value of(from);integer converted=converter . convert(' 123 ');System.out.println(已转换);//123需要注意的是,如果没有指定@FunctionalInterface,上面的代码也是正确的。
译者注:将lambda表达式映射到单方法接口,这是Java 8之前的其他语言实现的,比如Rhino JavaScript解释器。如果一个函数参数接收一个单方法接口,并且您传递了一个函数,Rhino解释器将自动使单接口适配器的一个实例起作用。典型的应用场景包括org.w3c.dom.events.EventTarget的addEventListener的第二个参数EventListener.
四、方法与构造函数引用上一节的代码也可以用静态方法引用表示:复制代码code如下:converterstring,integer converter=integer:value of;integer converted=converter . convert(' 123 ');System.out.println(已转换);//123Java 8允许使用:关键字传递方法或构造函数引用。上面的代码显示了如何引用静态方法。我们也可以参考一个对象的方法:复制代码code如下:converter=something:starts with;string converted=converter . convert(' Java ');System.out.println(已转换);//'J '接下来,我们来看看如何使用:关键字引用构造函数。首先我们定义一个简单的类,有多个构造函数:复制代码如下:class Person { String firstName字符串lastName
Person() {}
Person(String firstName,String last name){ this . first name=first name;this.lastName=姓氏;}}接下来我们指定一个用于创建Person对象的对象工厂接口:复制代码如下:接口Person factory p Extensions Person { p Create(string first name,string last name);}这里我们用构造函数引用来关联它们,而不是实现一个完整的工厂:复制代码代码如下:personfactoryperson person factory=person:new;person person=person factory . create(' Peter ',' Parker ');我们只需要使用Person:new获取Person类的构造函数的引用,Java编译器会根据Personfactory.create方法的签名自动选择合适的构造函数。
五、Lambda 作用域在lambda表达式中访问外部作用域的方式类似于旧版本中匿名对象的方式。您可以直接访问标记为final的外部局部变量,或者实例和静态变量的字段。
六、访问局部变量
我们可以在lambda表达式中直接访问外层的局部变量:复制代码如下:final int num=1;ConverterInteger,String String converter=(from)-String . value of(from num);
string converter . convert(2);//3但与匿名对象不同的是,这里的变量num可以声明为final,代码也是正确的:复制代码code如下:int num=1;ConverterInteger,String String converter=(from)-String . value of(from num);
string converter . convert(2);//3但是,这里的num一定不能被下面的代码修改(也就是说,它隐含了最终的语义)。比如不能编译如下:复制代码如下:int num=1;ConverterInteger,String String converter=(from)-String . value of(from num);num=3;试图在lambda表达式中修改num也是不允许的。
七、访问对象字段与静态变量
与局部变量不同,lambda可以读写实例和静态变量的字段。该行为与匿名对象一致:复制代码如下:class lambda 4 { static int outerstaticnum;int outerNum
void test scopes(){ converter integer,String String converter 1=(from)-{ outer num=23;返回string . value of(from);};
ConverterInteger,String String converter 2=(from)-{ outerStaticNum=72;返回string . value of(from);};}}
八、访问接口的默认方法还记得第一节公式的例子吗?接口formula定义了一个默认方法sqrt,Formula的实例可以直接访问它,包括匿名对象,但是这在lambda表达式中是不可能的。Lambda表达式中不能访问默认方法,下面的代码不会被编译:Copy code代码如下:Formula Formula=(a)-sqrt(a * 100);内置函数接口JDK 1.8 API包含了很多内置函数接口,比如比较器或者Runnable接口,这些都是老Java常用的接口。这些接口添加了@FunctionalInterface注释,因此它们可以在lambda上使用。Java API还提供了许多新的功能接口,使工作更加方便。部分界面来自谷歌番石榴库。即使你对这些很熟悉,也有必要看看这些是如何扩展到lambda的。
Predicate接口
谓词接口只有一个参数,并返回布尔值。这个接口包含了很多将predict组合成其他复杂逻辑(如AND、OR、NOT)的默认方法:复制代码如下:predicate testring predict=(s)-s . length()0;
predicate . test(' foo ');//truepredicate.negate()。测试(' foo ');//假
predicate boolean nonNull=Objects:nonNull;predicate boolean is null=Objects:is null;
predicate String isEmpty=String:isEmpty;predicate string is notempty=isempty . negate();
Function 接口
函数接口有一个参数,返回一个结果,有一些默认的方法(compose,和Then)可以和其他函数结合使用:复制代码如下:functionstring,integer to integer=integer:value of;FunctionString,String back to String=to integer . and then(String:value of);
back tostring . apply(' 123 ');//'123'
Supplier 接口供应商接口返回任意值。与函数接口不同,该接口没有参数。复制代码如下:供应商人员人员供应商=人员:新建;person supplier . get();//新的人
Consumer 接口用户界面表示对单个参数执行操作。复制代码如下:消费人greeter=(p)-system . out . println(' hello,' p . first name);greeter.accept(新人('卢克','天行者'));
Comparator 接口Comparator是旧Java中的经典接口,Java 8在上面增加了多种默认方法:复制代码如下:Comparator Person Comparator=(P1,P2)-P1 . first name . compare to(p2 . first name);
人员p1=新人员(' John ',' Doe ');人物p2=新人('爱丽丝','仙境');
comparator.compare(p1,p2);//0comparator.reversed()。比较(p1,p2);//0
Optional 接口
Optional不是一个函数而是一个接口,它是一个辅助类型,用于防止NullPointerException异常。这是下一节课要用到的一个重要概念。现在让我们简单看看这个接口能做什么:
Optional被定义为一个简单的容器,它的值可以为空,也可以不为空。在Java 8之前,一般来说,函数应该返回非空对象,但偶尔也可能返回null。在Java 8中,不建议您返回null,而是可选的。复制代码如下:可选字符串optional=optional . of(' BAM ');
optional . is present();//true optional . get();//'bam '可选. or else(' fallback ');//'砰'
可选. if present((s)-system . out . println(s . charat(0)));//'b '
Stream 接口
Stream表示可以应用于一组元素并一次执行的操作序列。流操作可以分为中间操作或最终操作。最终操作返回特定类型的计算结果,而中间操作返回流本身,这样就可以依次将多个操作串在一起。创建Stream需要指定数据源,比如java.util.Collection的子类,List或Set,Map不支持。流操作可以串行或并行执行。
首先,看看Stream是如何使用的。首先创建实例代码使用的数据列表:复制代码如下:liststring string collection=newArrayList();string collection . add(' DD D2 ');string collection . add(' AAA 2 ');string collection . add(' bb B1 ');string collection . add(' AAA 1 ');string collection . add(' bb B3 ');string collection . add(' CCC ');string collection . add(' bb B2 ');string collection . add(' DD D1 ');Java扩展了collection类,可以通过Collection.stream()或者Collection.parallelStream()创建流。以下部分将详细解释常见的流操作:
Filter 过滤
通过谓词接口过滤,只过滤和保留符合条件的元素。这个操作是一个中间操作,所以我们可以对过滤后的结果应用其他流操作(比如forEach)。ForEach需要一个函数来依次执行筛选出的元素。forEach是一个final操作,所以我们不能在ForEach之后执行其他流操作。复制代码如下:stringcollection.stream()。filter ((s)-s.startswith ('a '))。foreach(system . out:println);
//'aaa2 ',' aaa1 '
Sort 排序
排序是一个中间操作,它返回排序后的流。如果不指定自定义比较器,将使用默认排序。复制代码如下:stringcollection.stream()。已排序()。filter ((s)-s.startswith ('a '))。foreach(system . out:println);
//'aaa1 ',' aaa2 '需要注意的是,排序只是创建一个已排序的流,不会影响原始数据源。排序后不会修改原始数据stringCollection:复制代码如下:system . out . println(string collection);//ddd2,aaa2,bbb1,aaa1,bbb3,ccc,bbb2,ddd1
Map 映射中级操作图会按照指定的函数接口依次把元素变成其他对象。下面的示例显示了字符串到大写字符串的转换。你也可以说说通过map把对象转换成其他类型,map返回的流的类型是由你通过map传入的函数的返回值决定的。复制代码如下:stringcollection.stream()。map (string: toupper case)。已排序((a,b)-b.compareto (a))。foreach(system . out:println);
//'DDD2 ',' DDD1 ',' CCC ',' BBB3 ',' BBB2 ',' AAA2 ',' AAA1 '
Match 匹配
Stream提供了各种匹配操作,允许您检测指定的谓词是否匹配整个流。的所有匹配操作都是最终的,并返回一个布尔值。复制代码如下:boolean any start witha=string collection . stream()。any match((s)-s . starts with(' a '));
system . out . println(any starts witha);//真
boolean allstarts witha=string collection。流()。all match((s)-s . starts with(' a '));
system . out . println(allStartsWithA);//假
boolean noneStartsWithZ=string collection。流()。none match((s)-s . starts with(' z '));
system . out . println(noneStartsWithZ);//真
Count 计数计数是最后一个操作,它返回流中元素的个数。返回值类型为长整型。复制代码如下:long starts with b=string collection . stream()。filter ((s)-s.startswith ('b '))。count();
system . out . println(starts with b);//3
Reduce 规约
这是最后的操作,它允许流中的多个元素被指定的函数缩减为一个元素。调节后的结果通过可选接口表示:复制代码如下:可选string reduced=string collection . stream()。已排序()。减少((S1,S2)-S1 ' # ' S2);
reduced . if present(system . out:println);//' AAA 1 # AAA 2 # bb B1 # bb B2 # bb B3 # CCC # DD D1 # DD D2 '
并行Streams
如前所述,有两种流:串行流和并行流。串行流上的操作在一个线程中完成,而并行流同时在多个线程上执行。
以下示例显示了如何通过并行流来提高性能:
首先我们创建一个没有重复元素的大表:复制代码如下:int max=1000000ListString values=new ArrayList(max);for(int I=0;i maxI){ UUID uuid=uuid . randomuuid();values . add(uuid . tostring());}那我们来算算这个流排序需要多长时间。串行排序:Copy code代码如下:long t0=system . nano time();
long count=values.stream()。已排序()。count();system . out . println(count);
long t1=system . nano time();
长毫秒=时间单位。纳秒.托米利斯(t1-t0);system . out . println(string . format('顺序排序耗时:%d毫秒',毫秒));
//串行耗时:899 ms并行排序:copy code代码如下:long t0=system . nano time();
long count=values . parallel stream()。已排序()。count();system . out . println(count);
long t1=system . nano time();
长毫秒=时间单位。纳秒.托米利斯(t1-t0);system . out . println(string . format('并行排序耗时:%d毫秒',毫秒));
//耗时并行排序:472 ms以上两个代码差不多,但并行版快50%。唯一需要做的更改是将stream()更改为parallelStream()。
Map
如前所述,Map类型不支持stream,但是Map提供了一些新的有用的方法来处理一些日常任务。复制代码如下:MapInteger,String Map=new hashmap();
for(int I=0;i 10i ) { map.putIfAbsent(i,' val ' I);}
map.forEach((id,val)-system . out . println(val));上面的代码很容易理解。putIfAbsent不需要我们做额外的存在检查,而forEach接收一个消费者接口来操作map中的每个键值对。
下面的例子展示了map上其他有用的函数:复制代码如下:map.computeifpresent (3,(num,val)-valnum);map . get(3);//val33
map.computeIfPresent(9,(num,val)-null);map . contains key(9);//假
map . computeifaxine(23,num-' val ' num);map . contains key(23);//真
map . computeifavent(3,num-' bam ');map . get(3);//val33接下来,展示如何在Map中删除一个键值全部匹配的条目:复制代码如下:map.remove(3,' val 3 ');map . get(3);//val33
map.remove(3,' val 33 ');map . get(3);//null另一个有用的方法:复制代码如下:map.getOrDefault(42,'未找到');//not found也让合并Map的元素变得很容易:复制代码如下:map.merge (9,' val 9 ',(value,new value)-value . concat(new value));map . get(9);//val9
map.merge(9,' concat ',(value,new value)-value . concat(new value));map . get(9);VAL9 Concat Merge做的是如果键名不存在就插入键名,否则合并原键名对应的值重新插入映射。
九、Date APIJava 8在包java.time下包含了一组新的时间和日期API,新的日期API类似于开源的Joda-Time库,但并不完全相同。以下示例展示了这个新API的一些最重要的部分:
Clock 时钟
Clock类提供了一种访问当前日期和时间的方法。Clock对时区敏感,可用于代替System.currentTimeMillis()来获取当前的微秒数。一个特定的时间点也可以由Instant类表示,它也可以用来创建旧的java.util.Date对象。复制代码如下:clock clock=clock . systemdefaultzone();long millis=clock . millis();
instant instant=clock . instant();Date legacyDate=Date.from(即时);//旧版java.util.Date
Timezones 时区
在新的API中,时区由ZoneId表示。时区可以通过使用。时区定义了与UTS时间的时差,这在将即时点对象转换为本地日期对象时非常重要。复制代码如下:system . out . println(zoneid . getavailablezoneids());//打印所有可用的时区id
ZoneId zone1=ZoneId.of('欧洲/柏林');ZoneId zone2=ZoneId.of('巴西/东部');system . out . println(zone 1 . get rules());system . out . println(zone 2 . get rules());
//zone rules[currentStandardOffset=01:00]//zone rules[currentStandardOffset=-03:00]
LocalTime 本地时间
Local定义一个没有时区信息的时间,如10点或17:30:15点。下面的示例使用前面的代码创建的时区创建两个本地时间。然后比较时间,用小时和分钟计算两个时间的时差:复制代码如下:local time now 1=local time . now(zone 1);local time now 2=local time . now(zone 2);
system . out . println(now 1 . is before(now 2));//假
长时间间隔=计时单位。小时间隔(现在1,现在2);长分钟间隔=计时单位。分钟间隔(现在1,现在2);
系统。出去。println(小时间隔);//-3System.out.println(分钟间隔);//-239本地时间提供了多种工厂方法来简化对象的创建,包括解析时间字符串。复制代码代码如下:LocalTime late=LocalTime.of(23,59,59);System.out.println(后期);//23:59:59
日期时间格式器德语格式器=日期时间格式器.ofLocalizedTime(FormatStyle .短)。带区域设置(区域设置。德语);
当地时间leet时间=当地时间。parse(' 13:37 ',德语格式化程序);系统。出去。println(leetTime);//13:37
LocalDate 本地日期
本地日期表示了一个确切的日期,比如2014-03-11。该对象值是不可变的,用起来和本地时间基本一致。下面的例子展示了如何给日期对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。复制代码代码如下:今天的当地日期=当地日期。now();LocalDate tomorrow=today.plus(1,计时单位。天);当地日期昨天=明天。减去天数(2);
本地日期独立日=本地日期。(2014年,月。7月四日);独立日。getday of week();系统。出去。星期几;//星期五从字符串解析一个本地日期类型和解析本地时间一样简单:复制代码代码如下:日期时间格式器德语格式器=日期时间格式器.ofLocalizedDate(FormatStyle .中等)。带区域设置(区域设置。德语);
本地日期xmas=本地日期。解析(' 24。12 .2014 ',德国格式器);系统。出去。println(圣诞节);//2014-12-24
LocalDateTime 本地日期时间
本地日期时间同时表示了时间和日期,相当于前两节内容合并到一个对象上了本地日期时间和本地时间还有本地日期一样,都是不可变的本地日期时间提供了一些能访问具体字段的方法。复制代码代码如下:本地日期时间Sylvester=本地日期时间。(2014年,月。12月31、23、59、59);
每周一天=西尔维斯特。getday of week();系统。出去。星期几;//星期三
月月=西尔维斯特。getmonth();System.out.println(月);//十二月
一天中最长的一分钟=西尔维斯特。变长(时空场.一天中的分钟);系统。出去。println(一天中的分钟);//1439只要附加上时区信息,就可以将其转换为一个时间点瞬间对象,即时时间点对象可以很容易的转换为老式的java.util.Date。复制代码代码如下:Instant instant=西尔威斯特atZone(ZoneId.systemDefault())。to instant();
日期遗留日期=日期.从(即时);系统。出去。println(遗留日期);//欧洲中部时间2014年1 2月31日星期三23时59分59秒格式化本地日期时间和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:复制代码代码如下:日期时间格式化程序=日期时间格式化程序.ofPattern('MMM dd,yyyy-HH:mm ');
本地日期时间已解析=本地日期时间。解析(' 2014年11月03日07:13 ',格式化程序);String string=formatter.format(已解析);系统。出去。println(字符串);//2014年11月3日07:13和Java。文字。数字格式不一样的是新版的日期时间格式化程序是不可变的,所以它是线程安全的。关于时间日期格式的详细信息:http://下载。Java。net/JDK 8/docs/API/Java/time/format/datetime格式化程序。超文本标记语言
十、注释注解
在Java 8中支持多重注解了,先看个例子来理解一下是什么意思。首先定义一个包装类暗示注解用来放置一组具体的暗示注解:复制代码代码如下:@接口提示{ Hint[]value();}
@ Repeatable(提示。类别)@接口提示{ String value();}Java 8允许我们把同一个类型的注解使用多次,只需要给该注解标注一下@可重复即可。
1:使用包装类作为容器来存储多个注释(旧方法)。复制代码如下:@Hints({@Hint('hint1 ')),@Hint('hint2')})class Person {}示例2:复制带有多个注释的代码(new method)如下:@ Hint(' Hint 1 ')@ Hint(' Hint 2 ')class Person { }第二个示例中,java编译器会隐式地为您定义@Hints注释,知道这将有助于您通过反射获得这些信息:复制代码如下:Hint Hint=Person . class . get annotation(Hint . class);System.out.println(提示);//null
hints hints 1=person . class . get annotation(hints . class);system . out . println(hints 1 . value()。长度);//2
hint[]hints 2=person . class . getannotationsbytype(hint . class);system . out . println(hints 2 . length);//2即使我们没有在Person类上定义@Hints批注,我们仍然可以通过getAnnotation(Hints.class)得到@Hints批注。更方便的方法是使用getAnnotationsByType直接获取所有的@ hints注释。另外,Java 8新增了两个目标的注释:复制代码如下:@ target ({elementtype。类型参数,元素类型。type _ use}) @ interface我的批注{}以上就是Java 8的新特性。肯定还有更多的功能有待发现。JDK 1.8里有很多有用的东西,比如数组。并行排序、戳锁、CompletableFuture等等。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。