在 Android 中使用 Java8 的特性

LouApple 7年前
   <p>根据 <a href="/misc/goto?guid=4959718299879805268" rel="nofollow,noindex">Android 官网</a> 的说明,在开发面向 Android N 的应用时,可以使用 Java8 语言功能。目前 Android 只支持一部分 Java8 的特性:</p>    <ul>     <li><a href="/misc/goto?guid=4959671977713266591" rel="nofollow,noindex">Lambda 表达式</a></li>     <li><a href="/misc/goto?guid=4958970912940962098" rel="nofollow,noindex">方法引用</a></li>     <li><a href="/misc/goto?guid=4958970912859213006" rel="nofollow,noindex">默认和静态接口方法</a></li>     <li><a href="/misc/goto?guid=4959671977857125033" rel="nofollow,noindex">重复注解</a></li>    </ul>    <p>其中,只有前两者可以兼容 API 23 以下的版本。</p>    <h2>Lambda 表达式</h2>    <p>从一个实际例子来引入 lamdba 的使用。</p>    <p>有一组 Person 对象(具体实现不复杂,参考 <a href="/misc/goto?guid=4959718300083736370" rel="nofollow,noindex">这里</a> ),需要通过年龄大小来过滤出满足要求的对象,然后对其进行输出操作,实现很简单,如下:</p>    <pre>  <code class="language-java">public static void printPersonsOlderThan(List<Person> roster, int age) {      for (Person p : roster) {          if (p.getAge() >= age) {              p.printPerson();          }      }  }</code></pre>    <p>如果我的过滤条件变更了,就必须修改这个方法的代码,比如我现在根据年龄上下限进行过滤:</p>    <pre>  <code class="language-java">public static void printPersonsWithinAgeRange(List<Person> roster, int low, int high) {      for (Person p : roster) {          if (low <= p.getAge() && p.getAge() <= high) {              p.printPerson();          }      }  }</code></pre>    <p>这样一来,过滤条件经常变更的话,需要频繁修改这个方法。根据面向对象的思想,封装变化,把经常改变的逻辑封装起来,有外部来决定。这里我把过滤条件封装到 CheckPerson 接口里,根据不同的过滤条件去实现这个接口即可。</p>    <pre>  <code class="language-java">@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;      }  });</code></pre>    <p>CheckPerson 接口是一个 <strong>函数式接口</strong> (functional interface),即 <strong>仅有一个抽象方法</strong> 的接口。 因此实现这个接口的时候可以忽略掉方法名称,使用 Lambda 表达式来替代匿名类。</p>    <pre>  <code class="language-java">printPersons(roster,          p -> p.getGender() == Person.Sex.MALE                  && p.getAge() >= 18                  && p.getAge() <= 25  );</code></pre>    <p>其实在 Java8 的包中,已经内置了一些标准的函数式接口。比如 CheckPerson 接收一个对象,然后输出一个 boolean 值。可以使用 java.util.function.Predicate<T> 来替代,它相当于 RxJava 中的 Func1<T, Boolean> ,接收一个对象,返回布尔值。</p>    <pre>  <code class="language-java">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  );</code></pre>    <p>这是,我并不满足于仅仅把过滤条件封装起来,还想把过滤之后对 Person 对象的操作也封装起来,便于修改。可以用另外一个标准的函数式接口 java.util.function.Consumer<T> ,它相当于 RxJava 中的 Action1<T> ,接收一个对象,返回 void。</p>    <pre>  <code class="language-java">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()  );</code></pre>    <p>如果我的处理过程中有数据转换的过程,可以用 java.util.function.Function<T, F> 将其封装起来,这个接口相当于 RxJava 中的 Func1<T, F> ,接收一个类型的对象,返回另外个类型的对象,达到数据转换的目的。比如例子中,把 Person 转换成 String 对象。</p>    <pre>  <code class="language-java">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)  );</code></pre>    <p>最后可以把数据源也封装成一个 java.lang.Iterable<T> 对象。</p>    <pre>  <code class="language-java">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);</code></pre>    <p>在 Java8 中也可以把 Collections 对象快速转换成 Stream 来使用方便的操作符。</p>    <pre>  <code class="language-java">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));   // 对数据执行操作(消费数据)</code></pre>    <p>Lambda 写法</p>    <p>基本写法:</p>    <pre>  <code class="language-java">p -> p.getGender() == Person.Sex.MALE       && p.getAge() >= 18      && p.getAge() <= 25    // 没有参数  () -> System.out.println("Hello lambda")    // 参数多于 1 个  (x, y) -> x + y</code></pre>    <p>参数列表,如果只有一个参数,可以省略掉括号,其他情况需要写上一对括号。</p>    <p>需要注意的是, 箭头 -> 后面必须是一个单独的表达式(expression)或者是一个语句块(statement block)。</p>    <pre>  <code class="language-java">// 表达式  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;  }</code></pre>    <h2>方法引用</h2>    <p>当你使用一个 lambda 表达式的时候,如果它仅仅是调用了一下已有的方法,并没有做其他任何操作,就可以把它转换成 <strong>方法引用</strong> 。方法引用有四种写法,下面一一介绍。</p>    <pre>  <code class="language-java">// 先制造一些数据, 供后面的例子使用  List<Person> roster = Person.createRoster();  Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);</code></pre>    <p>引用静态方法</p>    <p>首先在 Person 类中有一个静态方法,通过年龄比较大小:</p>    <pre>  <code class="language-java">// Person.java  public static int compareByAge(Person a, Person b) {      return a.birthday.compareTo(b.birthday);  }</code></pre>    <pre>  <code class="language-java">//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);</code></pre>    <p>引用具体实例的方法</p>    <pre>  <code class="language-java">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);</code></pre>    <p>引用特定类型的对象的实例方法</p>    <p>Person 实现一下 Comparable<T> 接口,会有一个 compareTo(Person) 方法。</p>    <pre>  <code class="language-java">public class Person implements Comparable<Person> {        @Override      public int compareTo(Person o) {          return Person.compareByAge(this, o); // 复用之前静态方法的逻辑      }        // 其他忽略  }</code></pre>    <pre>  <code class="language-java">// lambda 形式  Arrays.sort(rosterAsArray, (p1, p2) -> p1.compareTo(p2));  // 转换成方法引用 =>  Arrays.sort(rosterAsArray, Person::compareTo);</code></pre>    <p>引用构造方法</p>    <p>有一个 transferElements 方法,将 SOURCE 类型的集合转换成 DEST 类型的集合。</p>    <pre>  <code class="language-java">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;  }</code></pre>    <p>其中 java.util.function.Supplier<T> 也是标准的函数式接口,它有一个 get() 方法来获取所提供的对象。</p>    <pre>  <code class="language-java">// 匿名类形式  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);</code></pre>    <p>lambda 表达式中直接 new 了一个 HashSet,相当于调用了 HashSet 的构造方法,故可以写成 HashSet::new 方法引用的形式。</p>    <h2>静态和默认接口方法</h2>    <p>在 Java8 之前,接口不允许有默认实现,如果接口的两个实现类有同样的实现逻辑,就得写重复代码了。现在接口可以通过关键字 default 实现默认方法,另外接口还可以实现静态方法。</p>    <pre>  <code class="language-java">public interface SampleInterface {      default int test() {          System.out.println("SampleInterface default impl");          return staticTest() + 666;      }        static int staticTest() {          return 100;      }  }</code></pre>    <pre>  <code class="language-java">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 方法      }  }</code></pre>    <p>最后输出结果:</p>    <pre>  <code class="language-java">SampleInterfaceImpl1 override  333  SampleInterface default impl  766</code></pre>    <p>使用接口的默认方法可以减少代码重复,静态方法也可以方便地封装一些通用逻辑。</p>    <h2>重复注解</h2>    <p>重复注解就是允许在同一申明类型(类,属性,或方法)多次使用同一个注解。</p>    <pre>  <code class="language-java">@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  }</code></pre>    <p>使用时可以通过 AnnotatedElement.getAnnotationsByType() 方法来获取到注解,然后进行相应的处理。</p>    <pre>  <code class="language-java">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();      }  }</code></pre>    <p>输出如下:</p>    <pre>  <code class="language-java">doSomethingWork  Schedule: Mon, last, 12  Schedule: Fri, first, 9</code></pre>    <p>使用就是这么简单~</p>    <h2>在 Android 中使用这些特性</h2>    <p>在主 module (app) 的 build.gradle 里配置,开启 jack 编译器,使用 Java8 进行编译。 如果要体验接口的默认方法等特性,minSdkVersion 需要指定为 24 (Android N)。</p>    <pre>  <code class="language-java">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      }  }</code></pre>    <h2>Reference</h2>    <ul>     <li>文中代码大部分来自于 <a href="/misc/goto?guid=4959718300174000448" rel="nofollow,noindex">Oracle 官方文档教程</a></li>     <li><a href="/misc/goto?guid=4959718300258199872" rel="nofollow,noindex">在 Android N 预览版中使用 Java 8 的新特性</a></li>    </ul>    <p> </p>    <p>来自:http://brucezz.itscoder.com/articles/2016/10/05/use_java8_in_android/</p>    <p> </p>