首页 > IT职业 > IT图书 > 在 Android 中使用 Java8 的特性
原文链接:http://www.codeceo.com/article/use-java8-in-android.html
根据 Android 官网的说明,在开发面向 Android N 的应用时,可以使用 Java8 语言功能。目前 Android 只支持一部分 Java8 的特性:
其中,只有前两者可以兼容 API 23 以下的版本。
从一个实际例子来引入 lamdba 的使用。
有一组 Person 对象(具体实现不复杂,参考这里),需要通过年龄大小来过滤出满足要求的对象,然后对其进行输出操作,实现很简单,如下:
public static void printPersonsOlderThan(Listroster, int age) { for (Person p : roster) { if (p.getAge() >= age) { p.printPerson(); } } }
如果我的过滤条件变更了,就必须修改这个方法的代码,比如我现在根据年龄上下限进行过滤:
public static void printPersonsWithinAgeRange(Listroster, int low, int high) { for (Person p : roster) { if (low 这样一来,过滤条件经常变更的话,需要频繁修改这个方法。根据面向对象的思想,封装变化,把经常改变的逻辑封装起来,有外部来决定。这里我把过滤条件封装到
CheckPerson接口里,根据不同的过滤条件去实现这个接口即可。@FunctionalInterfacepublic interface CheckPerson { boolean test(Person p); }public static void printPersons(Listroster, CheckPerson tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } }// 实际使用List roster = Person.createRoster(); // 制造一些数据printPersons(roster, new CheckPerson() { @Override public boolean test(Person p) { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge()
CheckPerson接口是一个函数式接口(functional interface),即仅有一个抽象方法的接口。 因此实现这个接口的时候可以忽略掉方法名称,使用 Lambda 表达式来替代匿名类。printPersons(roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge()其实在 Java8 的包中,已经内置了一些标准的函数式接口。比如
CheckPerson接收一个对象,然后输出一个 boolean 值。可以使用java.util.function.Predicate来替代,它相当于 RxJava 中的Func1,接收一个对象,返回布尔值。public static void printPersonsWithPredicate(Listroster, Predicate tester) { for (Person p : roster) { if (tester.test(p)) { p.printPerson(); } } }// 使用起来并没有什么差别printPersonsWithPredicate(roster, p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() 这是,我并不满足于仅仅把过滤条件封装起来,还想把过滤之后对 Person 对象的操作也封装起来,便于修改。可以用另外一个标准的函数式接口
java.util.function.Consumer,它相当于 RxJava 中的Action1,接收一个对象,返回 void。public static void processPersons(Listroster, Predicate tester, Consumer 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() p.printPerson() ); 如果我的处理过程中有数据转换的过程,可以用
java.util.function.Function将其封装起来,这个接口相当于 RxJava 中的Func1,接收一个类型的对象,返回另外个类型的对象,达到数据转换的目的。比如例子中,把 Person 转换成 String 对象。public static void processPersonsWithFunction( Listroster, Predicate tester, Function mapper, Consumer 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() p.getEmailAddress(), // 获取 Person 对象的 email 字符串 email -> System.out.println(email) ); 最后可以把数据源也封装成一个
java.lang.Iterable对象。public staticvoid processElements( Iterable source, Predicate tester, Function mapper, Consumer block) { for (X x : source) { if (tester.test(x)) { Y data = mapper.apply(x); block.accept(data); } } }// 实际使用Iterable source = roster; Predicate tester = p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() mapper = p -> p.getEmailAddress(); Consumer 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() p.getEmailAddress()) // 根据 Function 转换数据 .forEach(email -> System.out.println(email)); // 对数据执行操作(消费数据)Lambda 写法
基本写法:
p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() System.out.println("Hello lambda")// 参数多于 1 个(x, y) -> x + y参数列表,如果只有一个参数,可以省略掉括号,其他情况需要写上一对括号。
需要注意的是, 箭头
->后面必须是一个单独的表达式(expression)或者是一个语句块(statement block)。// 表达式p -> p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge() { return p.getGender() == Person.Sex.MALE && p.getAge() >= 18 && p.getAge()方法引用
当你使用一个 lambda 表达式的时候,如果它仅仅是调用了一下已有的方法,并没有做其他任何操作,就可以把它转换成方法引用。方法引用有四种写法,下面一一介绍。
// 先制造一些数据, 供后面的例子使用Listroster = Person.createRoster(); Person[] rosterAsArray = roster.toArray(new Person[roster.size()]); 引用静态方法
首先在 Person 类中有一个静态方法,通过年龄比较大小:
// Person.javapublic static int compareByAge(Person a, Person b) { return a.birthday.compareTo(b.birthday); }//MethodReferencesTest.java// 原来的写法,传入匿名类Arrays.sort(rosterAsArray, new Comparator() { @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接口,会有一个compareTo(Person)方法。public class Person implements Comparable{ @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, DEST extends Collection >DEST transferElements( SOURCE sourceCollection, Supplier collectionFactory) { DEST result = collectionFactory.get(); for (T t : sourceCollection) { result.add(t); } return result; } 其中
java.util.function.Supplier也是标准的函数式接口,它有一个get()方法来获取所提供的对象。// 匿名类形式SetrosterSet = transferElements(roster, new Supplier >() { @Override public Set get() { return new HashSet (); } });// lambda 形式Set rosterSet = transferElements(roster, () -> new HashSet());// 转换成方法引用 =>Set 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 override333SampleInterface default impl766使用接口的默认方法可以减少代码重复,静态方法也可以方便地封装一些通用逻辑。
重复注解
重复注解就是允许在同一申明类型(类,属性,或方法)多次使用同一个注解。
@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(); } }输出如下:
doSomethingWorkSchedule: Mon, last, 12Schedule: 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 官方文档教程
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/2216/viewspace-2809461/ ,如需转载,请注明出处,否则将追究法律责任。