在 Android 中使用 Java8 的特性!

根据 Android 官网的说明,在开发面向 Android N 的应用时,可以使用 Java8 语言功能。目前 Android 只支持一部分 Java8 的特性:

Lambda 表达式

方法引用

默认和静态接口方法

重复注解

其中,只有前两者可以兼容 API 23 以下的版本。

在 Android 中使用 Java8 的特性!_第1张图片

Lambda 表达式

从一个实际例子来引入 lamdba 的使用。

有一组 Person 对象(具体实现不复杂,参考这里),需要通过年龄大小来过滤出满足要求的对象,然后对其进行输出操作,实现很简单,如下:

publicstaticvoidprintPersonsOlderThan(List roster,intage){for(Person p : roster) {if(p.getAge() >= age) {            p.printPerson();        }    }}

如果我的过滤条件变更了,就必须修改这个方法的代码,比如我现在根据年龄上下限进行过滤:

publicstaticvoidprintPersonsWithinAgeRange(List roster,intlow,inthigh){for(Person p : roster) {if(low <= p.getAge() && p.getAge() <= high) {            p.printPerson();        }    }}

这样一来,过滤条件经常变更的话,需要频繁修改这个方法。根据面向对象的思想,封装变化,把经常改变的逻辑封装起来,有外部来决定。这里我把过滤条件封装到CheckPerson接口里,根据不同的过滤条件去实现这个接口即可。

@FunctionalInterfacepublicinterfaceCheckPerson{booleantest(Person p);}publicstaticvoidprintPersons(List roster, CheckPerson tester){for(Person p : roster) {if(tester.test(p)) {            p.printPerson();        }    }}// 实际使用List roster = Person.createRoster();// 制造一些数据printPersons(roster,newCheckPerson() {@Overridepublicbooleantest(Person p){returnp.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来替代,它相当于 RxJava 中的Func1,接收一个对象,返回布尔值。

publicstaticvoidprintPersonsWithPredicate(List roster, 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() <=25);

这是,我并不满足于仅仅把过滤条件封装起来,还想把过滤之后对 Person 对象的操作也封装起来,便于修改。可以用另外一个标准的函数式接口java.util.function.Consumer,它相当于 RxJava 中的Action1,接收一个对象,返回 void。

publicstaticvoidprocessPersons(List roster, 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() <=25,        p -> p.printPerson());

如果我的处理过程中有数据转换的过程,可以用java.util.function.Function将其封装起来,这个接口相当于 RxJava 中的Func1,接收一个类型的对象,返回另外个类型的对象,达到数据转换的目的。比如例子中,把 Person 转换成 String 对象。

publicstaticvoidprocessPersonsWithFunction(        List roster,        Predicate tester,Function mapper,        Consumer block) {for(Person person : roster) {if(tester.test(person)) {Stringdata = 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对象。

publicstatic void processElements(        Iterable source,        Predicate tester,Functionmapper,Consumerblock){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() <=25;Functionmapper=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() <=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 -> {returnp.getGender() ==Person.Sex.MALE&& p.getAge() >=18&& p.getAge() <=25;}

方法引用

当你使用一个 lambda 表达式的时候,如果它仅仅是调用了一下已有的方法,并没有做其他任何操作,就可以把它转换成方法引用。方法引用有四种写法,下面一一介绍。

// 先制造一些数据, 供后面的例子使用List roster = Person.createRoster();Person[] rosterAsArray = roster.toArray(newPerson[roster.size()]);

引用静态方法

首先在 Person 类中有一个静态方法,通过年龄比较大小:

// Person.javapublicstaticintcompareByAge(Person a, Person b){returna.birthday.compareTo(b.birthday);}

//MethodReferencesTest.java// 原来的写法,传入匿名类Arrays.sort(rosterAsArray,newComparator() {@Overridepublicintcompare(Person o1, Person o2){returnPerson.compareByAge(o1, o2);    }});// 写成 lambda 形式Arrays.sort(rosterAsArray, (a, b) -> Person.compareByAge(a, b));// 转换成方法引用 =>Arrays.sort(rosterAsArray, Person::compareByAge);

引用具体实例的方法

classComparisonProvider{publicintcompareByName(Person a, Person b){returna.getName().compareTo(b.getName());    }publicintcompareByAge(Person a, Person b){returna.getBirthday().compareTo(b.getBirthday());    }}ComparisonProvider comparisonProvider =newComparisonProvider();// lambda 形式Arrays.sort(rosterAsArray, (p1, p2) -> comparisonProvider.compareByAge(p1, p2));// 转换成方法引用 =>Arrays.sort(rosterAsArray, comparisonProvider::compareByName);

引用特定类型的对象的实例方法

Person 实现一下Comparable接口,会有一个compareTo(Person)方法。

publicclassPersonimplementsComparable{@OverridepublicintcompareTo(Person o){returnPerson.compareByAge(this, o);// 复用之前静态方法的逻辑}// 其他忽略}

// lambda 形式Arrays.sort(rosterAsArray, (p1, p2) -> p1.compareTo(p2));// 转换成方法引用 =>Arrays.sort(rosterAsArray,Person::compareTo);

引用构造方法

有一个transferElements方法,将SOURCE类型的集合转换成DEST类型的集合。

publicstatic, DEST extends Collection>DESTtransferElements(SOURCE sourceCollection,

Supplier collectionFactory){    DEST result = collectionFactory.get();for(T t : sourceCollection) {        result.add(t);    }returnresult;}

其中java.util.function.Supplier也是标准的函数式接口,它有一个get()方法来获取所提供的对象。

// 匿名类形式Set rosterSet = transferElements(roster,newSupplier>() {    @Override    publicSet get() {returnnewHashSet();    }});// lambda 形式Set rosterSet = transferElements(roster, () ->newHashSet<>());// 转换成方法引用 =>Set rosterSet = transferElements(roster, HashSet::new);

lambda 表达式中直接 new 了一个 HashSet,相当于调用了 HashSet 的构造方法,故可以写成HashSet::new方法引用的形式。

静态和默认接口方法

在 Java8 之前,接口不允许有默认实现,如果接口的两个实现类有同样的实现逻辑,就得写重复代码了。现在接口可以通过关键字default实现默认方法,另外接口还可以实现静态方法。

publicinterfaceSampleInterface{defaultinttest(){        System.out.println("SampleInterface default impl");returnstaticTest() +666;    }staticintstaticTest(){return100;    }}

publicclassSampleTest{publicstaticvoidmain(String[] args){inttest =newSampleInterfaceImpl1().test();        System.out.println(test);inttest2 =newSampleInterfaceImpl2().test();        System.out.println(test2);    }staticclassSampleInterfaceImpl1implementsSampleInterface{@Overridepublicinttest(){            System.out.println("SampleInterfaceImpl1 override");returnSampleInterface.staticTest() +233;        }    }staticclassSampleInterfaceImpl2implementsSampleInterface{// 不需要实现 test 方法}}

最后输出结果:

SampleInterfaceImpl1override333SampleInterfacedefaultimpl766

使用接口的默认方法可以减少代码重复,静态方法也可以方便地封装一些通用逻辑。

重复注解

重复注解就是允许在同一申明类型(类,属性,或方法)多次使用同一个注解。

@Repeatable(Schedules.class)// 指定存储 Schedule 的注解@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceSchedule {StringdayOfWeek()default"Mon";StringdayOfMonth()default"first";inthour()default12;}@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public@interfaceSchedules {    Schedule[] value();// 存储 Schedule}

使用时可以通过AnnotatedElement.getAnnotationsByType()方法来获取到注解,然后进行相应的处理。

publicclassAnnotationTest{@Schedule(dayOfMonth ="last")@Schedule(dayOfWeek ="Fri", hour =9)publicvoiddoSomethingWork(){        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();        }    }publicstaticvoidmain(String[] args){newAnnotationTest().doSomethingWork();    }}

输出如下:

doSomethingWorkSchedule: Mon,last,12Schedule: Fri, first,9

使用就是这么简单~

在 Android 中使用这些特性

在主 module (app) 的build.gradle里配置,开启 jack 编译器,使用 Java8 进行编译。 如果要体验接口的默认方法等特性,minSdkVersion 需要指定为 24 (Android N)。

android {    compileSdkVersion24buildToolsVersion"24.0.0"defaultConfig {        applicationId"me.brucezz.sharedelementdemo"minSdkVersion14targetSdkVersion24versionCode1versionName"1.0"// 开启 jack 编译jackOptions {            enabledtrue}    }    compileOptions {// 指定用 Java8 编译sourceCompatibilityJavaVersion.VERSION_1_8targetCompatibilityJavaVersion.VERSION_1_8}}

Reference

文中代码大部分来自于 Oracle 官方文档教程

你可能感兴趣的:(在 Android 中使用 Java8 的特性!)