<接口的預設方法>
解決的問題:在java8 之前的版本,在修改已有的接口的時候,需要修改實作該接口的實作類。
作用:解決接口的修改與現有的實作不相容的問題。在不影響原有實作類的結構下修改新的功能方法。
java 8抽象類和接口的差別
相同點:
Ø 都是抽象類型
Ø 都可以有實作方法(在java8之前是不可以的)
Ø 都可以不需要實作類或者繼承者去實作所有方法,(以前不行,現在接口中預設方法不需要實作者實作)
不同點:
Ø 抽象類不可以多重繼承,接口可以(無論是多重類型繼承還是多重行為繼承);
Ø 抽象類和接口所反映出的設計理念不同。其實抽象類表示的是"is-a"關系,接口表示的是"like-a"關系;
Ø 接口中定義的變量預設是publicstatic final 型,且必須給其初值,是以實作類中不能改變其值;抽象類中的變量預設是 friendly 型,其值可以在子類中重新定義,也可以重新指派。
<Lambda表達式>
解決問題:為 Java 添加了缺失的函數式程式設計特點,
作用:使程式更簡潔便于可讀可維護。
文法:
(arg1, arg2...) -> { body }
(type1 arg1, type2 arg2...) -> { body }
例子:
(int a, int b) -> { return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> 42
() -> { return 3.1415 };
l lambda表達式的結構:
Ø 一個 Lambda 表達式可以有零個或多個參數
Ø 參數的類型既可以明确聲明,也可以根據上下文來推斷。
Ø 例如:(int a)與(a)效果相同
所有參數需包含在圓括号内,參數之間用逗号相隔。例如:(a, b) 或 (int a, int b) 或 (String a, int b, float c)
Ø 空圓括号代表參數集為空。例如:() -> 42
Ø 當隻有一個參數,且其類型可推導時,圓括号()可省略。例如:a -> return a*a
Ø Lambda 表達式的主體可包含零條或多條語句
Ø 如果 Lambda 表達式的主體隻有一條語句,花括号{}可省略。匿名函數的傳回類型與該主體表達式一緻
Ø 7.如果 Lambda 表達式的主體包含一條以上語句,則表達式必須包含在花括号{}中(形成代碼塊)。匿名函數的傳回類型與代碼塊的傳回類型一緻,若沒有傳回則為空
<Lambda範圍>
l 通路本地變量
我們可以通路在lambda表示式之外的本地final變量:
final int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
但是和匿名變量不同的是變量num不必強制的被聲明為final。下面的代碼依然是合法的:
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num);
stringConverter.convert(2); // 3
但是實際上,變量num在編譯期是被隐式的轉換為fianl類型的。下面的代碼是不能被成功的編譯的:
int num = 1;
Converter<Integer, String> stringConverter =
(from) -> String.valueOf(from + num); //這裡的num會編譯報錯
num = 3;
在lambda表達式内部向變量num寫入值同樣是不允許的。
l 通路對象字段和靜态變量
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);
};
}
}
l 通路接口預設方法
Ø 接口預設方法不能被Lambda表達式的内部代碼所通路
<功能性接口>
Ø JDK1.8包括了許多功能性接口。它們中的一些是老版本中被熟知的接口,例如Comparator和Runnable。這些已存在的接口已經通過@FunctionalInterface注解擴充為支援Lambda表達式。
Ø 在 Java 中,Marker(标記)類型的接口是一種沒有方法或屬性聲明的接口,簡單地說,marker 接口是空接口。相似地,函數式接口是隻包含一個抽象方法聲明的接口
Ø 每個 Lambda 表達式都能隐式地指派給函數式接口,當不指明函數式接口時,編譯器會自動解釋這種轉化
Ø 函數式接口例子:
Consumer<Integer> c = (int x) -> { System.out.println(x) };
BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);
Predicate<String> p = (String s) -> { s == null };
l 相關接口:
1. 斷言接口(Predicates) 是隻擁有一個參數的Boolean型功能的接口。這個接口擁有多個預設方法用于構成predicates複雜的邏輯術語
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
2. 功能接口(Functions)Functions接受一個參數并産生一個結果,預設方法能夠用于将多個函數連結在一起。
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, Integer> backToString = toInteger.addThen(String::valueOf);
backToString.apply(“5120”);
3. 供應接口(Supplines) 對于給定的泛型類型産生一個執行個體。不同于Functions,Suppliers不需要任何參數。
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
4. 消費接口(Consumers)代表在隻有一個輸入參數時操作被如何執行
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
5. 比較接口(Comparators)
Comparator<Person> comparator=
(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
6.選項接口(Optionals)一種特殊的工具用來解決NullPointerException
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s)->System.out.println(s.charAt(0))); // "b"
7. 流接口(Streams)
java.util.Stream代表着一串你可以在其上進行多種操作的元素。流操作既可以是連續的也可以是中斷的。中斷操作傳回操作結果。而連續操作傳回流本身,這樣你就可以在該行上繼續操作。流是建立在資料源上的,例如:java.util.Collection、list集合和set集合。流操作既可以順序執行也可以并行執行
首先建立一個字元串的數組
List<String> 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");
Java8的Collections類已經被擴充了,你可以簡單的調用Collection.stream()或者Collection.parallelSteam()來建立流。下面部分将介紹大部分流操作。
Filter
Filter接受一個predicate來過濾流中的所有元素。這個操作是連續的,它可以讓我們在結果上繼續調用另外一個流操作forEach。ForEach接受一個consumer,它被用來對過濾流中的每個元素執行操作。ForEach是一個中斷操作,是以我們不能在ForEach後調用其他流操作。
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隻建立了流的排序結果,它并沒有改變集合中元素的排序位置。stringCollection中元素排序是沒有改變的。
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
Map
連續性操作map通過指定的Function将流中的每個元素轉變為另外的對象。下面的示例将每個字元串轉換為大寫的字元串。此外,你也可以使用map将每個元素的類型改變為其它類型。轉換後流的泛型類型依賴于你傳入的Function的泛型類型。
stringCollection
.stream()
.map(String::toUpperCase)
.sorted((a, b) -> b.compareTo(a))
.forEach(System.out::println);
// "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"
Match
各種比對操作可以用來檢測是否某種predicate和流中元素相比對。所有的這些操作是中斷的并傳回一個boolean結果。
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); // true
Count
Count是中斷型操作,它傳回流中的元素數量。
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
Reduce
這個中斷性操作使用指定的function對流中元素實施消減政策。此操作的傳回值是一個包括所有被消減元素的Optional。
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
Parallel Streams
在前面部分我們提到流可以是順序的也可以是并行的。順序流的操作是在單線程上執行的,而并行流的操作是在多線程上并發執行的。
随後的例子我們展示了并行流可以多麼容易的提高性能。
首先,我們建立一個包含唯一進制素的大容器:
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
現在我們開始測試排序這些元素需要多長時間。
Sequential Sort
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 ms
Parallel 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 ms
你會觀察到這兩種模式的代碼基本上是一緻的,但是并行排序所花費的時間大約是順序排序的一半。
Map
我們已經提到maps不支援流。然而現在maps包括了許多新的非常有用的方法用于執行通用任務。
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));
上述的代碼應該很清晰了:putIfAbsent使得我們不用寫是否為null值的檢測語句;forEach使用consumer來對map中的每個元素進行操作。下面的例子向我們展示使用功能性函數在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中的值想等:
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
其他一些幫助性方法:
map.getOrDefault(42, "not found"); // not found
合并map中的實體是十分容易的:
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
如果map不存在指定的鍵,那麼它将把該鍵值對key/value加入map中。反而,如果存在,它将調用function來進行合并操作。
l Date API 新的date和time 的api,與joda-Time庫是相容的。
Ø Clock
提供了通路目前日期和時間的方法。Clock是時區敏感的并且它可以被用來替代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.Date
Ø TimeZones
被用來表示ZoneId。它們可以通過靜态工廠方法通路。TImeZones定義了時差,它在instants和本地日期時間轉換上十分重要。
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
本地時間代表了一個和時區無關的時間,e.g. 10pm or 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:37
Ø LocalDate
LocalDate代表了一個可區分日期,e.g.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
解析字元串的時間格式:
DateTimeFormatter germanFormatter =
DateTimeFormatter
.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 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。Instants可以被容易的轉換為遺留的java.util.Date類型。
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
格式date-time的過程和格式date和time基本上是一樣的。在使用系統自帶的定義格式時,我們也可以定義我們自己的格式:
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
<方法和構造函數引用>
解決問題:簡化靜态方法的引用
使用方式:通過關鍵字::來傳遞方法和構造函數的引用
例子:
靜态方法:
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = Integer::valueOf;
Integer converted = converter.convert("123");
System.out.println(converted); // 123
對象方法:
class Something {
String startsWith(String s) {
return String.valueOf(s.charAt(0));
}
}
Something something = new Something();
Converter<String, String> converter = something::startsWith;
String converted = converter.convert("Java");
System.out.println(converted); // "J"
<Annotations>
支援多重注解,在注解聲明時使用@Repeatable。
例子:
我們首先定義一個包裝注解
@interface Hints {
Hint[] value();
}
@Repeatable(Hints.class)
@interface Hint {
String value();
}
使用1:容器注解
@Hints({@Hint("hint1"), @Hint("hint2")})
class User {}
使用2:重複注解
@Hint("hint1")
@Hint("hint2")
class User {}
特别注意:
在原有注解使用範圍基礎之上,如下:
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE
Java8注解的使用範圍新增兩種類型:
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
@interface MyAnnotation {}
其他特性:Arrays.parallelSort,StampedLock,CompletableFuture