在 Android 中使用 Java8 的特性!

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

Lambda 表达式

方法引用

默认和静态接口方法

重复注解

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

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 官方文档教程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容