摘要:首先我們定義一個有兩個不同控制器的然后,我們創建一個特定的工廠接口來創建新的對象不需要手動的去繼承實現該工廠接口,我們只需要將控制器的引用傳遞給該接口對象就好了的控制器會自動選擇合適的構造器方法。這種指向時間軸的對象即是類。
本文為翻譯文章,原文地址 這里
歡迎來到本人對于Java 8的系列介紹教程,本教程會引導你一步步領略最新的語法特性。通過一些簡單的代碼示例你即可以學到默認的接口方法、Lambda表達式、方法引用以及重復注解等等。本文的最后還提供了譬如Stream API之類的詳細的介紹。
Default Methods for InterfacesJava 8 允許我們利用default關鍵字來向接口中添加非抽象的方法作為默認方法。下面是一個小例子:
interface Formula { double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } }
在接口Formula中,除了抽象方法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.0Lambda表達式
首先以簡單的字符串排序為例來展示:
Listnames = Arrays.asList("peter", "anna", "mike", "xenia"); Collections.sort(names, new Comparator () { @Override public int compare(String a, String b) { return b.compareTo(a); } });
靜態的工具類方法Collections.sort接受一個列表參數和一個比較器對象來對于指定列表中的元素進行排序。我們常常需要創建匿名比較器并且將他們傳遞給排序方法。而Java 8中提供的一種更短的Lambda表達式的方法來完成該工作:
Collections.sort(names, (String a, String b) -> { return b.compareTo(a); });
可以發現用如上的方法寫會更短并且更加的可讀,并且可以更短:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
這種寫法就完全不需要{}以及return關鍵字,再進一步簡化的話,就變成了:
names.sort((a, b) -> b.compareTo(a));Functional Interfaces
Lambda表達式是如何適配進Java現存的類型系統的呢?每個Lambda表達式都會關聯到一個由接口確定的給定的類型。這種所謂的函數式接口必須只能包含一個抽象方法,而每個該類型的Lambda表達式都會關聯到這個抽象方法。不過由于默認方法不是抽象的,因此可以隨便添加幾個默認的方法到函數式接口中。我們可以將任意的接口作為Lambda表達式使用,只要該接口僅含有一個抽象方法即可。為了保證你的接口滿足這個需求,應該添加@FunctionalInterface這個注解。編譯器會在你打算向某個函數式接口中添加第二個抽象方法時候報錯。
//聲明 @FunctionalInterface interface ConverterMethod and Constructor References{ T convert(F from); } //使用 Converter converter = (from) -> Integer.valueOf(from); Integer converted = converter.convert("123"); System.out.println(converted); // 123
上述的代碼可以使用靜態方法引用而更加的簡化:
Converterconverter = Integer::valueOf; Integer converted = converter.convert("123"); System.out.println(converted); // 123
Java 8允許將方法或則構造器的引用通過::關鍵字進行傳遞,上述的例子是演示了如何關聯一個靜態方法,不過我們也可以關聯一個對象方法:
class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } }
Something something = new Something(); Converterconverter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J"
接下來看 :: 關鍵字怎么在構造器中起作用。首先我們定義一個有兩個不同控制器的Java Bean:
class Person { String firstName; String lastName; Person() {} Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } }
然后,我們創建一個特定的Person工廠接口來創建新的Person對象:
interface PersonFactory{ P create(String firstName, String lastName); }
不需要手動的去繼承實現該工廠接口,我們只需要將控制器的引用傳遞給該接口對象就好了:
PersonFactorypersonFactory = Person::new; Person person = personFactory.create("Peter", "Parker");
Java的控制器會自動選擇合適的構造器方法。
Lambda Scopes從Lambda表達式中訪問外部作用域中變量非常類似于匿名對象,可以訪問本地的final變量、實例域以及靜態變量。
Accessing local variables在匿名對象中,我們可以從Lambda表達式的域中訪問外部的final變量。
final int num = 1; ConverterstringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3
但是不同于匿名對象只能訪問final變量,Lambda表達式中可以訪問final變量:
int num = 1; ConverterstringConverter = (from) -> String.valueOf(from + num); stringConverter.convert(2); // 3
不過需要注意的是,盡管變量不需要聲明為final,但是也是隱式的不可變:
int num = 1; ConverterstringConverter = (from) -> String.valueOf(from + num); num = 3;
譬如如上的寫法就會被報錯。
Accessing fields and static variables不同于本地變量,我們可以在Lambda表達式中任意的讀寫:
class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { ConverterAccessing Default Interface MethodsstringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
注意,Lambda表達式中是不可以訪問默認方法的:
Formula formula = (a) -> sqrt( a * 100);
上述代碼是編譯通不過的。
Built-in Functional InterfacesJDK 1.8 的API中包含了許多的內建的函數式接口,其中部分的譬如Comparator、Runnable被改寫成了可以由Lambda表達式支持的方式。除此之外,Java 8還添加了許多來自于Guava中的依賴庫,并將其改造為了Lambda接口。
PredicatesPredicates是包含一個參數的返回為布爾值的接口,接口包含了許多的默認方法來進行不同的復雜的邏輯組合:
PredicateFunctionspredicate = (s) -> s.length() > 0; predicate.test("foo"); // true predicate.negate().test("foo"); // false Predicate nonNull = Objects::nonNull; Predicate isNull = Objects::isNull; Predicate isEmpty = String::isEmpty; Predicate isNotEmpty = isEmpty.negate();
Functions接口接受一個參數并且產生一個結果,同樣提供了部分默認的方法來鏈式組合不同的函數(compose,andThen)。
FunctionSupplierstoInteger = Integer::valueOf; Function backToString = toInteger.andThen(String::valueOf); backToString.apply("123"); // "123"
SupplierConsumerspersonSupplier = Person::new; personSupplier.get(); // new Person
ConsumerComparatorsgreeter = (p) -> System.out.println("Hello, " + p.firstName); greeter.accept(new Person("Luke", "Skywalker"));
Comparators類似于舊版本中的用法,Java 8是添加了一些默認的用法。
ComparatorOptionalscomparator = (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
Optionals并不是一個函數式接口,但是非常有用的工具類來防止NullPointerException。Optional是一個簡單的容器用于存放那些可能為空的值。對于那種可能返回為null的方法可以考慮返回Optional而不是null:
OptionalStreamsoptional = Optional.of("bam"); optional.isPresent(); // true optional.get(); // "bam" optional.orElse("fallback"); // "bam" optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
一個Java的Stream對象代表了一系列可以被附加多個操作的元素的序列集合。常用的Stream API分為媒介操作與終止操作。其中終止操作會返回某個特定類型的值,而媒介操作則會返回流本身以方便下一步的鏈式操作。Streams可以從java.util.Collection的數據類型譬如lists或者sets(不支持maps)中創建。而Streams的操作可以順序執行也可以并發地執行。
Stream.js是一個利用JavaScript實現的Java 8的流接口。
首先我們創建待處理的數據:
ListstringCollection = new ArrayList<>(); stringCollection.add("ffffd2"); stringCollection.add("aaa2"); stringCollection.add("bbb1"); stringCollection.add("aaa1"); stringCollection.add("bbb3"); stringCollection.add("ccc"); stringCollection.add("bbb2"); stringCollection.add("ffffd1");
接下來可以利用Collection.stream() or Collection.parallelStream()來轉化為一個流對象。
FilterFilter會接受一個Predicate對象來過濾流中的元素,這個操作屬于媒介操作,譬如可以在該操作之后調用另一個流操作(forEach)。ForEach操作屬于終止操作,接受一個Consumer對象來對于過濾之后的流中的每一個元素進行操作。
stringCollection .stream() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa2", "aaa1"Sorted
Sorted操作屬于一個媒介操作,會將流對象作為返回值返回。元素會默認按照自然的順序返回,除非你傳入了一個自定義的Comparator對象。
stringCollection .stream() .sorted() .filter((s) -> s.startsWith("a")) .forEach(System.out::println); // "aaa1", "aaa2"
需要記住的是,Sorted操作并不會改變流中的元素的順序,只會創建一個經過排序的視圖,譬如:
System.out.println(stringCollection); // ffffd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ffffd1Map
map操作也是媒介操作的一種,可以通過給定的函數將每個元素映射到其他對象。下面的代碼示例就是將所有的字符串轉化為大寫字符串。不過map操作是可以將任意對象轉化為任意類型,流返回的泛型類型取決于傳遞給map的函數的返回值。
stringCollection .stream() .map(String::toUpperCase) .sorted((a, b) -> b.compareTo(a)) .forEach(System.out::println); // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"Match
Java 8提供了一些列的匹配的終止操作符來幫助開發者判斷流當中的元素是否符合某些判斷規則。所有的匹配類型的操作都會返回布爾類型。
boolean anyStartsWithA = stringCollection .stream() .anyMatch((s) -> s.startsWith("a")); System.out.println(anyStartsWithA); // true boolean allStartsWithA = stringCollection .stream() .allMatch((s) -> s.startsWith("a")); System.out.println(allStartsWithA); // false boolean noneStartsWithZ = stringCollection .stream() .noneMatch((s) -> s.startsWith("z")); System.out.println(noneStartsWithZ); // trueCount
Count 也是終止操作的一種,它會返回流中的元素的數目。
long startsWithB = stringCollection .stream() .filter((s) -> s.startsWith("b")) .count(); System.out.println(startsWithB); // 3Reduce
該操作根據指定的方程對于流中的元素進行了指定的減少的操作。結果是Optional類型。
OptionalParallel Streamsreduced = stringCollection .stream() .sorted() .reduce((s1, s2) -> s1 + "#" + s2); reduced.ifPresent(System.out::println); // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ffffd1#ffffd2"
正如上文中提及的,流可以是順序的也可以是并行的。所有在順序流上執行的流操作都是在單線程中運行的,而在并行流中進行的操作都是在多線程中運行的。如下的代碼演示了如何利用并行流來提供性能,首先我們創建一個待比較的序列:
int max = 1000000; ListSequential Sortvalues = new ArrayList<>(max); for (int i = 0; i < max; i++) { UUID uuid = UUID.randomUUID(); values.add(uuid.toString()); }
long t0 = System.nanoTime(); long count = values.stream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("sequential sort took: %d ms", millis)); // sequential sort took: 899 msParallel Sort
long t0 = System.nanoTime(); long count = values.parallelStream().sorted().count(); System.out.println(count); long t1 = System.nanoTime(); long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0); System.out.println(String.format("parallel sort took: %d ms", millis)); // parallel sort took: 472 msMaps
正如上文所說,Map并不支持流操作,但是也提供了很多有用的方法來進行通用的操作。
Mapmap = new HashMap<>(); for (int i = 0; i < 10; i++) { map.putIfAbsent(i, "val" + i); } map.forEach((id, val) -> System.out.println(val));
上述的代碼中,putIfAbsent避免了寫太多額外的空檢查。forEach會接受一個Consumer參數來遍歷Map中的元素。下面的代碼演示如何進行計算:
map.computeIfPresent(3, (num, val) -> val + num); map.get(3); // val33 map.computeIfPresent(9, (num, val) -> null); map.containsKey(9); // false map.computeIfAbsent(23, num -> "val" + num); map.containsKey(23); // true map.computeIfAbsent(3, num -> "bam"); map.get(3); // val33
還有,Map提供了如何根據給定的key,vaue來刪除Map中給定的元素:
map.remove(3, "val3"); map.get(3); // val33 map.remove(3, "val33"); map.get(3); // null
還有一個比較有用的方法:
map.getOrDefault(42, "not found"); // not found
同時,Map還提供了merge方法來幫助有效地對于值進行修正:
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); // val9concatDate API
Java 8包含了一個全新的日期與時間的API,在java.time包下,新的時間API集成了Joda-Time的庫。
ClockClock方便我們去讀取當前的日期與時間。Clocks可以根據不同的時區來進行創建,并且可以作為System.currentTimeMillis()的替代。這種指向時間軸的對象即是Instant類。Instants可以被用于創建java.util.Date對象。
Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.DateTimezones
Timezones以ZoneId來區分。可以通過靜態構造方法很容易的創建,Timezones定義了Instants與Local Dates之間的轉化關系:
System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids ZoneId zone1 = ZoneId.of("Europe/Berlin"); ZoneId zone2 = ZoneId.of("Brazil/East"); System.out.println(zone1.getRules()); System.out.println(zone2.getRules()); // ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00]LocalTime
LocalTime代表了一個與時間無關的本地時間,譬如 10pm 或者 17:30:15。下述的代碼展示了根據不同的時間軸創建的不同的本地時間:
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.println(minutesBetween); // -239
LocalTime提供了很多的工廠方法來簡化創建實例的步驟,以及對于時間字符串的解析:
LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59 DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN); LocalTime leetTime = LocalTime.parse("13:37", germanFormatter); System.out.println(leetTime); // 13:37LocalDate
LocalDate代表了一個獨立的時間類型,譬如2014-03-11。它是一個不可變的對象并且很類似于LocalTime。下列代碼展示了如何通過增減時間年月來計算日期:
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.println(dayOfWeek); // FRIDAY
從字符串解析得到LocalDate對象也像LocalTime一樣簡單:
DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter); System.out.println(xmas); // 2014-12-24LocalDateTime
LocalDateTime代表了時間日期類型,它組合了上文提到的Date類型以及Time類型。LocalDateTime同樣也是一種不可變類型,很類似于LocalTime以及LocalDate。
LocalDateTime sylvester = LocalDateTime.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 instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant(); Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
從格式化字符串中解析獲取到數據對象,也是非常簡單:
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:13Annotations
Java 8中的注解現在是可以重復的,下面我們用例子直接說明。首先,創建一個容器注解可以用來存儲一系列真實的注解:
@interface Hints { Hint[] value(); } @Repeatable(Hints.class) @interface Hint { String value(); }
通過添加 @Repeatable注解,就可以在同一個類型中使用多個注解。
Variant 1: Using the container annotation (old school)@Hints({@Hint("hint1"), @Hint("hint2")}) class Person {}Variant 2: Using repeatable annotations (new school)
@Hint("hint1") @Hint("hint2") class Person {}
Hint hint = Person.class.getAnnotation(Hint.class); System.out.println(hint); // null Hints hints1 = Person.class.getAnnotation(Hints.class); System.out.println(hints1.value().length); // 2 Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); System.out.println(hints2.length); // 2Further Reading
Java 8 Stream Tutorial
Java 8 Nashorn Tutorial
Java 8 Concurrency Tutorial: Threads and Executors
Java 8 Concurrency Tutorial: Synchronization and Locks
Java 8 Concurrency Tutorial: Atomic Variables and ConcurrentMap
Java 8 API by Example: Strings, Numbers, Math and Files
Avoid Null Checks in Java 8
Fixing Java 8 Stream Gotchas with IntelliJ IDEA
Using Backbone.js with Java 8 Nashorn
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/64730.html
摘要:大家好,我是冰河有句話叫做投資啥都不如投資自己的回報率高。馬上就十一國慶假期了,給小伙伴們分享下,從小白程序員到大廠高級技術專家我看過哪些技術類書籍。 大家好,我是...
摘要:大家好,我是樂字節的小樂,上一次我們說到了核心特性之函數式接口,接下來我們繼續了解又一核心特性方法引用。方法引用是一種更簡潔易懂的表達式。感謝光臨閱讀小樂的,敬請關注樂字節后續將繼續講述等前沿知識技術。 大家好,我是樂字節的小樂,上一次我們說到了Java8核心特性之函數式接口,接下來我們繼續了解Java8又一核心特性——方法引用。 showImg(https://segmentfaul...
摘要:大家好,上一篇小樂給大家講述了樂字節核心特性表達式,點擊回顧。接下來繼續核心特性之函數式接口。感謝大家欣賞小樂帶來的核心特性之函數式接口,接下來還會更多核心技術講解,請關注樂字節如需要視頻課程,請搜索樂字節騰訊課堂 大家好,上一篇小樂給大家講述了《樂字節-Java8核心特性-Lambda表達式》,點擊回顧。接下來繼續:Java8核心特性之函數式接口。 什么時候可以使用Lambda?通常...
摘要:使用表達式,使得應用變得簡潔而緊湊。很多語言等從設計之初就支持表達式。表達式的參數與函數式接口內方法的參數,返回值類型相互對應。更多教程和資料請上騰訊課堂樂字節 showImg(https://segmentfault.com/img/bVbtotg?w=935&h=345); Java8 引入Lambda表達式,允許開發者將函數當成參數傳遞給某個方法,或者把代碼本身當作數據進行處理。...
摘要:實戰高并發程序設計推薦豆瓣評分書的質量沒的說,推薦大家好好看一下。推薦,豆瓣評分,人評價本書介紹了在編程中條極具實用價值的經驗規則,這些經驗規則涵蓋了大多數開發人員每天所面臨的問題的解決方案。 很早就想把JavaGuide的書單更新一下了,昨晚加今天早上花了幾個時間對之前的書單進行了分類和補充完善。雖是終極版,但一定還有很多不錯的 Java 書籍我沒有添加進去,會繼續完善下去。希望這篇...
閱讀 3570·2023-04-25 14:20
閱讀 1188·2021-09-10 10:51
閱讀 1150·2019-08-30 15:53
閱讀 456·2019-08-30 15:43
閱讀 2313·2019-08-30 14:13
閱讀 2791·2019-08-30 12:45
閱讀 1203·2019-08-29 16:18
閱讀 1159·2019-08-29 16:12