在 Android 中使用 Java8 的特性
根據 Android 官網的說明,在開發面向 Android N 的應用時,可以使用 Java8 語言功能。目前 Android 只支援一部分 Java8 的特性:
其中,只有前兩者可以相容 API 23 以下的版本。
Lambda 表示式
從一個實際例子來引入 lamdba 的使用。
有一組 Person 物件(具體實現不復雜,參考這裡),需要通過年齡大小來過濾出滿足要求的物件,然後對其進行輸出操作,實現很簡單,如下:
public static void printPersonsOlderThan(List<Person> roster, int age) { for (Person p : roster) { if (p.getAge() >= age) { p.printPerson(); } } }
如果我的過濾條件變更了,就必須修改這個方法的程式碼,比如我現在根據年齡上下限進行過濾:
public static void printPersonsWithinAgeRange(List<Person> roster, int low, int high) { for (Person p : roster) { if (low <= p.getAge() && p.getAge() <= high) { p.printPerson(); } } }
這樣一來,過濾條件經常變更的話,需要頻繁修改這個方法。根據物件導向的思想,封裝變化,把經常改變的邏輯封裝起來,有外部來決定。這裡我把過濾條件封裝到 CheckPerson
介面裡,根據不同的過濾條件去實現這個介面即可。
@FunctionalInterface public interface CheckPerson { boolean test(Person p); } public static void printPersons(List<Person> roster, CheckPerson tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } } // 實際使用 List<Person> roster = Person.createRoster(); // 製造一些資料 printPersons(roster, new CheckPerson() { @Override public boolean test(Person p) { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; } });
CheckPerson
介面是一個函式式介面(functional interface),即僅有一個抽象方法的介面。 因此實現這個介面的時候可以忽略掉方法名稱,使用 Lambda 表示式來替代匿名類。
printPersons(roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 );
其實在 Java8 的包中,已經內建了一些標準的函式式介面。比如 CheckPerson
接收一個物件,然後輸出一個 boolean 值。可以使用 java.util.function.Predicate<T>
來替代,它相當於 RxJava 中的 Func1<T, Boolean>
,接收一個物件,返回布林值。
public static void printPersonsWithPredicate(List<Person> roster, Predicate<Person> tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } } // 使用起來並沒有什麼差別 printPersonsWithPredicate(roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 );
這是,我並不滿足於僅僅把過濾條件封裝起來,還想把過濾之後對 Person 物件的操作也封裝起來,便於修改。可以用另外一個標準的函式式介面 java.util.function.Consumer<T>
,它相當於 RxJava 中的 Action1<T>
,接收一個物件,返回 void。
public static void processPersons(List<Person> roster, Predicate<Person> tester, Consumer<Person> block) { for (Person person : roster) { if (tester.test(person)) { block.accept(person); } } } // 具體使用 processPersons(roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.printPerson() );
如果我的處理過程中有資料轉換的過程,可以用 java.util.function.Function<T, F>
將其封裝起來,這個介面相當於 RxJava 中的 Func1<T, F>
,接收一個型別的物件,返回另外個型別的物件,達到資料轉換的目的。比如例子中,把 Person 轉換成 String 物件。
public static void processPersonsWithFunction( List<Person> roster, Predicate<Person> tester, Function<Person, String> mapper, Consumer<String> block) { for (Person person : roster) { if (tester.test(person)) { String data = mapper.apply(person); block.accept(data); } } } // 實際使用 processPersonsWithFunction(roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25, p -> p.getEmailAddress(), // 獲取 Person 物件的 email 字串 email -> System.out.println(email) );
最後可以把資料來源也封裝成一個 java.lang.Iterable<T>
物件。
public static <X, Y> void processElements( Iterable<X> source, Predicate<X> tester, Function<X, Y> mapper, Consumer<Y> block) { for (X x : source) { if (tester.test(x)) { Y data = mapper.apply(x); block.accept(data); } } } // 實際使用 Iterable<Person> source = roster; Predicate<Person> tester = p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; Function<Person, String> mapper = p -> p.getEmailAddress(); Consumer<String> block = email -> System.out.println(email); processElements(roster, tester, mapper, block);
在 Java8 中也可以把 Collections 物件快速轉換成 Stream 來使用方便的操作符。
roster.stream() // 獲取資料流 .filter( // 根據 Predicate 過濾資料 p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25) .map(p -> p.getEmailAddress()) // 根據 Function 轉換資料 .forEach(email -> System.out.println(email)); // 對資料執行操作(消費資料)
Lambda 寫法
基本寫法:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 // 沒有引數 () -> System.out.println("Hello lambda") // 引數多於 1 個 (x, y) -> x + y
引數列表,如果只有一個引數,可以省略掉括號,其他情況需要寫上一對括號。
需要注意的是, 箭頭 ->
後面必須是一個單獨的表示式(expression)或者是一個語句塊(statement block)。
// 表示式 p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25 // 程式碼塊 p -> { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() <= 25; }
方法引用
當你使用一個 lambda 表示式的時候,如果它僅僅是呼叫了一下已有的方法,並沒有做其他任何操作,就可以把它轉換成方法引用。方法引用有四種寫法,下面一一介紹。
// 先製造一些資料, 供後面的例子使用 List<Person> roster = Person.createRoster(); Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);
引用靜態方法
首先在 Person 類中有一個靜態方法,通過年齡比較大小:
// Person.java public static int compareByAge(Person a, Person b) { return a.birthday.compareTo(b.birthday); }
//MethodReferencesTest.java // 原來的寫法,傳入匿名類 Arrays.sort(rosterAsArray, new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return Person.compareByAge(o1, o2); } }); // 寫成 lambda 形式 Arrays.sort(rosterAsArray, (a, b) -> Person.compareByAge(a, b)); // 轉換成方法引用 => Arrays.sort(rosterAsArray, Person::compareByAge);
引用具體例項的方法
class ComparisonProvider { public int compareByName(Person a, Person b) { return a.getName().compareTo(b.getName()); } public int compareByAge(Person a, Person b) { return a.getBirthday().compareTo(b.getBirthday()); } } ComparisonProvider comparisonProvider = new ComparisonProvider(); // lambda 形式 Arrays.sort(rosterAsArray, (p1, p2) -> comparisonProvider.compareByAge(p1, p2)); // 轉換成方法引用 => Arrays.sort(rosterAsArray, comparisonProvider::compareByName);
引用特定型別的物件的例項方法
Person 實現一下 Comparable<T>
介面,會有一個 compareTo(Person)
方法。
public class Person implements Comparable<Person> { @Override public int compareTo(Person o) { return Person.compareByAge(this, o); // 複用之前靜態方法的邏輯 } // 其他忽略 }
// lambda 形式 Arrays.sort(rosterAsArray, (p1, p2) -> p1.compareTo(p2)); // 轉換成方法引用 => Arrays.sort(rosterAsArray, Person::compareTo);
引用構造方法
有一個 transferElements
方法,將 SOURCE
型別的集合轉換成 DEST
型別的集合。
public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>> DEST transferElements( SOURCE sourceCollection, Supplier<DEST> collectionFactory) { DEST result = collectionFactory.get(); for (T t : sourceCollection) { result.add(t); } return result; }
其中 java.util.function.Supplier<T>
也是標準的函式式介面,它有一個 get()
方法來獲取所提供的物件。
// 匿名類形式 Set<Person> rosterSet = transferElements(roster, new Supplier<Set<Person>>() { @Override public Set<Person> get() { return new HashSet<Person>(); } }); // lambda 形式 Set<Person> rosterSet = transferElements(roster, () -> new HashSet<>()); // 轉換成方法引用 => Set<Person> rosterSet = transferElements(roster, HashSet::new);
lambda 表示式中直接 new 了一個 HashSet,相當於呼叫了 HashSet 的構造方法,故可以寫成HashSet::new
方法引用的形式。
靜態和預設介面方法
在 Java8 之前,介面不允許有預設實現,如果介面的兩個實現類有同樣的實現邏輯,就得寫重複程式碼了。現在介面可以通過關鍵字 default
實現預設方法,另外介面還可以實現靜態方法。
public interface SampleInterface { default int test() { System.out.println("SampleInterface default impl"); return staticTest() + 666; } static int staticTest() { return 100; } }
public class SampleTest { public static void main(String[] args) { int test = new SampleInterfaceImpl1().test(); System.out.println(test); int test2 = new SampleInterfaceImpl2().test(); System.out.println(test2); } static class SampleInterfaceImpl1 implements SampleInterface { @Override public int test() { System.out.println("SampleInterfaceImpl1 override"); return SampleInterface.staticTest() + 233; } } static class SampleInterfaceImpl2 implements SampleInterface { // 不需要實現 test 方法 } }
最後輸出結果:
SampleInterfaceImpl1 override 333 SampleInterface default impl 766
使用介面的預設方法可以減少程式碼重複,靜態方法也可以方便地封裝一些通用邏輯。
重複註解
重複註解就是允許在同一申明型別(類,屬性,或方法)多次使用同一個註解。
@Repeatable(Schedules.class) // 指定儲存 Schedule 的註解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Schedule { String dayOfWeek() default "Mon"; String dayOfMonth() default "first"; int hour() default 12; } @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Schedules { Schedule[] value(); // 儲存 Schedule }
使用時可以通過 AnnotatedElement.getAnnotationsByType()
方法來獲取到註解,然後進行相應的處理。
public class AnnotationTest { @Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 9) public void doSomethingWork() { System.out.println("doSomethingWork"); try { Method method = AnnotationTest.class.getMethod("doSomethingWork"); Schedule[] schedules = method.getAnnotationsByType(Schedule.class); for (Schedule schedule : schedules) { System.out.println("Schedule: " + schedule.dayOfWeek() + ", " + schedule.dayOfMonth() + ", " + schedule.hour()); } } catch (NoSuchMethodException e) { e.printStackTrace(); } } public static void main(String[] args) { new AnnotationTest().doSomethingWork(); } }
輸出如下:
doSomethingWork Schedule: Mon, last, 12 Schedule: Fri, first, 9
使用就是這麼簡單~
在 Android 中使用這些特性
在主 module (app) 的 build.gradle
裡配置,開啟 jack 編譯器,使用 Java8 進行編譯。 如果要體驗介面的預設方法等特性,minSdkVersion 需要指定為 24 (Android N)。
android { compileSdkVersion 24 buildToolsVersion "24.0.0" defaultConfig { applicationId "me.brucezz.sharedelementdemo" minSdkVersion 14 targetSdkVersion 24 versionCode 1 versionName "1.0" // 開啟 jack 編譯 jackOptions { enabled true } } compileOptions { // 指定用 Java8 編譯 sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }
Reference
- 文中程式碼大部分來自於 Oracle 官方文件教程
- 在 Android N 預覽版中使用 Java 8 的新特性
相關文章
- Cordova在Android中的使用Android
- Java8的新特性Java
- JAVA8新特性Java
- Java8 新特性Java
- PHP trait 特性在 Laravel 中的使用個人心得PHPAILaravel
- 【譯】Dagger2在Android中的使用Android
- Java8新特性 - LambdaJava
- JAVA8新特性用法Java
- Java8特性詳解 lambda表示式(一):使用篇Java
- 【Java8新特性】關於Java8中的日期時間API,你需要掌握這些!!JavaAPI
- Java8常用的新特性總結Java
- 淺談java8中的流的使用Java
- 【Java8新特性】面試官:談談Java8中的Stream API有哪些終止操作?Java面試API
- 你還在Java8中使用迴圈語句嗎?Java
- Java8新特性實踐Java
- java8新特性stream流Java
- java8特性-lambda表示式Java
- Java8 新特性詳解Java
- Java8新特性系列-LambdaJava
- java8特性 Optional 工具類Java
- Java8新特性之:OptionalJava
- Java8新特性--Stream APIJavaAPI
- 在Java8的foreach()中不能break,如果需要continue時,可以使用returnJava
- Java8的新特性--函式式介面Java函式
- Android開發教程 - 使用Data Binding(三)在Activity中的使用Android
- Android開發教程 - 使用Data Binding(四)在Fragment中的使用AndroidFragment
- Java8特性詳解 lambda表示式(二):流式處理中的lambdaJava
- java8特性-函式式介面Java函式
- ?Java8新特性之Optional類Java
- Java8特性大全(最新版)Java
- java8 新特性之方法引用Java
- java8 新特性之Optional 類Java
- Java8 和 Java9 的主要新特性Java
- 【Java8新特性】冰河帶你看盡Java8新特性,你想要的都在這兒了!!(文字有福利)Java
- 【Java8新特性】面試官問我:Java8中建立Stream流有哪幾種方式?Java面試
- Java8新特性(一)-Lambda表示式Java
- Java8新特性探索之Stream介面Java
- Java8新特性(1):Lambda表示式Java
- Java8新特性都到碗裡來Java