Android Studio下使用JAVA8和Lambda表达式

时间:2017/08/09 21:34:36
其实JDK1.8 发布已经有相当长的时间了,网上也有很多对于JAVA8的解释新特性的文章,但是在实际开发过程中,个人还没有正式使用JAVA8进行开发。所以对此关心的较少,今天在开发任务较少的情况下,发现了JAVA8的新特性文章,还是惊讶于JAVA8的巨大升级。

1.简介

目前来看,JAVA8(发布于2014年3月19日)是自JAVA5(发布于2004年)以来,最为重大的一次版本升级。这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。但同时虽然这些新特性领Java开发人员十分期待,但同时也需要花不少精力去学习。

  • 接口的默认方法
  • Lambda 表达式
  • 函数式接口
  • 方法与构造函数引用
  • Lambda 作用域
  • 访问局部变量
  • 访问对象字段与静态变量
  • 访问接口的默认方法
  • Date API
  • Annotation 注解

以上就是JAVA8的十大新特性。在此,我只对实际使用中对我们影响较大的几个特性做一些介绍。如果对详情感兴趣可以借鉴
JAVA8 十大新特性详解

2.Lambda表达式的使用

2.1介绍

其实在网上的一些代码之中已经有了很多lambda表达式使用的例子,非常的酷炫,但是具体是什么样的?
先来一段简单的代码:

Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return b.compareTo(a);
    }
}); 

这是一段很简单的排序代码,但是涉及到一个Java匿名内部类的书写之痛,排序方法真正核心的是compare方法内的实现,但是为了实现b.compareTo(a),我们写了很多不必要的代码,lambda表达式正是这种类型代码的救星。

下面我们再来看看同样的代码,使用lambda表达式的写法:

Collections.sort(names, (String a, String b) -> {
    return b.compareTo(a);
});  

代码具有可读性,并且更短,但其实这段代码lambda表达式还可以写的更短:

Collections.sort(names, (String a, String b) -> {b.compareTo(a);});

甚至方法体中,只有一句代码的,我们可以省去大括号:

Collections.sort(names, (String a, String b) -> b.compareTo(a));

由于JAVA8中可以根据上下文直接推导出参数类型,去掉参数类型也可以:

Collections.sort(names, (a,b) -> b.compareTo(a)); 

最终:我们的代码由原来的七行代码变为一行代码,虽然可读性降低了,但是逼格瞬间就提升了很多。

但是Lambda表达式的好处显而易见,弊端或者说是缺点也是有的。

2.2 使用限制

我们来分析一下Lambda实际使用的所需条件和使用规定:

  • 匿名内部类;
  • 类中所要重写的方法只能有一个;
  • Lambda的作用域:在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量(在访问外层局部变量时虽然没有添加final关键字,但是我们可以理解为该变量为隐形final),或者实例的字段以及静态变量。即局部变量在lambda表达式中只可读不可写,而成员变量和静态变量既可读又可写;

2.3 使用场景

我们再实际使用过程中经常可以用到的场景:

  1. runnable接口
  2. 单实现的接口
  3. 条目点击事件
  4. 封装网络请求的回调
  5. 与RxJava的链式调用
            //dosomething
        }, 2000);
    
     2.view.setOnClickListener(v -> {
            //dosomething
        });

     3.  listview.setOnItemClickListener((parent, view, position, id) -> {
            //dosomething
        });  

     4.例如通过封装OkHttp或者Retrofit的回调方法,将其转变成单实现的回调接口进行调用  

     5.Integer[] list = {1,2,3,4,5};
Observable
    .from(list)
    .filter(integer->integer%2==0)//挑选出偶数
    .map(integer -> "number is"+integer)//转换成String
    .subscribe(s->System.out.println(s));//相当于forEach(s->System.out.println(s));
    //forEach是同步的 subscribe是异步的

2.4 Android Studio中使用Lambda的配置

  1. 首先确定你的AndroidStudio中使用的是jdk1.8的版本
    imag
    imag
  2. 在项目的build.gradle文件中添加
classpath 'me.tatarka:gradle-retrolambda:3.2.5'
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.3'
        classpath 'me.tatarka:gradle-retrolambda:3.2.5'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
} 

3.在你的module目录下的build.gradle下添加
apply plugin: 'me.tatarka.retrolambda'

    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }

如下:

apply plugin: 'me.tatarka.retrolambda'

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.3"

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    defaultConfig {
        applicationId "ex.hxx.com.daggertest"
        minSdkVersion 16
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}
repositories {
    flatDir{
        dirs 'libs'
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.android.support.constraint:constraint-layout:1.0.2'
    testCompile 'junit:junit:4.12'} 

4.使用成功
可以看到编译完成之后再可使用lambda的位置出现灰色提示

image
image

直接使用ALT+ENTER快捷键出现提示
image
image

回车确认
image
image

Android Studio智能的帮我们完成了一次lambda表达式的修改 继续修改匿名Runnable接口
image
image

顿时感觉代码清爽了很多。

3. JAVA8

3.1 Sream接口

Java 8 通过增加大量新类,扩展已有类的功能的方式来改善对并发编程、函数式编程、日期/时间相关操作以及其他更多方面的支持。而Stream API极大简化了集合框架的处理。是个人认为在JAVA8中最为重大的一个补充。

Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。 Stream 的另外一大特点是,数据源本身可以是无限的。

3.1.1 Stream的构造

当前的stream对象的构造可以有以下几种方式:

Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.stream();  
//4.parallelStream;
List<String> list = Arrays.asList(strArray);
stream = list.parallelStream(); 

需要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:
IntStream、LongStream、DoubleStream。当然我们也可以用 Stream<Integer>、Stream<Long>、Stream<Double>,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。
Java 8 中还没有提供其它数值型 Stream,因为这将导致扩增的内容较多。而常规的数值型聚合运算可以通过上面三种 Stream 进行。

IntStream.range(1, 3).forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println); 

3.2.2 Stream的操作符

Stream的操作符可以分为中间操作类和终止类,从字面意思来说,中间操作符代表操作完成仍然会返回该Stream对象并可以继续操作,终止符则代表完成该操作后关闭流,无法继续操作。
常见的中间操作符有:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered
终止符有:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator 、groupingBy、partitioningBy
其实从操作符的字面意思大概就能看出所代表的操作功能。过滤、去重、排序、跳过、遍历、集合、最大、最小、总数等等,都是我们集合和数组操作常见的功能。

下面我们来看一下一个例子:

 
        private final String title;
        private final String author;
        private final List<String> tags;
 
        private Article(String title, String author, List<String> tags) {
            this.title = title;
            this.author = author;
            this.tags = tags;
        }
 
        public String getTitle() {
            return title;
        }
 
        public String getAuthor() {
            return author;
        }
 
        public List<String> getTags() {
            return tags;
        }
    } 

这是一个书籍的实体类,里面包含的字段有title:标题、author:作者、tags:标签这三个,然后对其进行一系列的操作:

例子1. 我们要在集合中查找包含“Java”标签的第一篇文章。

首先来看我们的一般写法:

 
    for (Article article : articles) {
        if (article.getTags().contains("Java")) {
            return article;
        }
    }
 
    return null;
}  

然后是我们通过Stream的写法:

        .filter(article -> article.getTags().contains("Java"))
        .findFirst();

一句代码就已经完成。

例子2. 获取所有匹配的元素

还是先使用for循环进行:

 
    for (Article article : articles) {
        if (article.getTags().contains("Java")) {
            result.add(article);
        }
    }

然后是Stream操作:

        .filter(article -> article.getTags().contains("Java"))
        .collect(Collectors.toList()); 

例子3.根据作者来把所有的文章分组

for循环:

 
    for (Article article : articles) {
        if (result.containsKey(article.getAuthor())) {
            result.get(article.getAuthor()).add(article);
        } else {
            ArrayList<Article> articles = new ArrayList<>();
            articles.add(article);
            result.put(article.getAuthor(), articles);
        }
    }

Stream操作:

        .collect(Collectors.groupingBy(Article::getAuthor)); 

Stream的操作还有很多可以开发的潜力,对于代码的简洁性有着巨大的提升,然而有优点,必然也有缺点,当前Stream的操作在网上的讨论中,对比for循环,Stream操作的性能较低,在一些核心代码中不建议使用。但这仍然不应该阻碍Stream操作对于我们平常代码的提升。

3.2 接口的默认方法与静态方法

3.2.1 接口的默认方法

Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。默认方法使接口有点像Traits(Scala中特征(trait)类似于Java中的Interface,但它可以包含实现代码,也就是目前Java8新增的功能),但与传统的接口又有些不一样,它允许在已有的接口中添加新方法,而同时又保持了与旧版本代码的兼容性。

默认方法其实就是在接口的中添加一个且只能有一个以default标注的方法。

默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求。相反,每个接口都必须提供一个所谓的默认实现,这样所有的接口实现者将会默认继承它(如果有必要的话,可以覆盖这个默认实现)。让我们看看下面的例子:

    // Interfaces now allow default methods, the implementer may or 
    // may not implement (override) them.
    default String notRequired() { 
        return "Default implementation"; 
    }        
}
         
private static class DefaultableImpl implements Defaulable {
}
     
private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
} 

Defaulable接口用关键字default声明了一个默认方法notRequired(),Defaulable接口的实现者之一DefaultableImpl实现了这个接口,并且让默认方法保持原样。Defaulable接口的另一个实现者OverridableImpl用自己的方法覆盖了默认方法。

Java 8带来的另一个有趣的特性是接口可以声明(并且可以提供实现)静态方法。例如:

    // Interfaces now allow static methods
    static Defaulable create( Supplier< Defaulable > supplier ) {
        return supplier.get();
    }
} 

下面的一小段代码片段把上面的默认方法与静态方法黏合到一起。

    Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
    System.out.println( defaulable.notRequired() );
         
    defaulable = DefaulableFactory.create( OverridableImpl::new );
    System.out.println( defaulable.notRequired() );
} 

最终控制台的结果如下:代码的运行逻辑还是清晰的。

Overridden implementation 

3.2.2 方法引用

    public static Car create( final Supplier< Car > supplier ) {
        return supplier.get();
    }              
         
    public static void collide( final Car car ) {
        System.out.println( "Collided " + car.toString() );
    }
         
    public void follow( final Car another ) {
        System.out.println( "Following the " + another.toString() );
    }
         
    public void repair() {   
        System.out.println( "Repaired " + this.toString() );
    }
} 

构造方法引用

它的语法是Class::new,或者更一般的Class< T >::new。请注意构造方法没有参数。

Car car = Car::new ; 

静态方法引用

第二种方法引用是静态方法引用,它的语法是Class::static_method。请注意这个方法接受一个Car类型的参数。

cars.forEach( Car::collide );

这么看有点难以理解,那么我们把它展开:其实也比较容易理解,forEach中为匿名内部类中提供了一个car的参数,而Car.collide刚好需要这个car对象作为参数,所以,双方同时省去 car这个对象,就形成了上文中的代码语义。

            @Override
            public void accept(Car car) {
                Car.collide(car);
            }
        }); 

这种静态方法的引用,其方法所需要的参数数量及参数类型,需要和上下文相一致。即不一定非要规定是一个参数。如:

    public enum Sex{
        MALE,FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    public String getEmailAddress() {
        return emailAddress;
    }

    public Sex getGender() {
        return gender;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public String getName() {
        return name;
    }

    public static int compareByAge(Person a,Person b){
        return a.birthday.compareTo(b.birthday);
    }

} 

该静态方法中所需要的参数为两个,上下文中提供了两个,所以语义正确

Arrays.sort(persons, Person::compareByAge);

我们把该方法展开:sort方法提供了person o1,和person o2两个参数,而这两个参数则刚好是Person.compareByAge这个方法所需要的两个参数,所以双方同时省去,形成上文中的代码,语义相同。

Arrays.sort(persons, new Comparator<Person>() {
        @Override
        public int compare(Person o1, Person o2) {
            return Person.compareByAge(o1, o2);
        }
    }); 

特定类的任意对象的方法引用

它的语法是Class::method。请注意,这个方法没有参数。

cars.forEach( Car::repair );

如果不是静态方法,这种调用方式,method必须为无参的方法。

特定对象的方法引用

它的语法是instance::method。请注意,这个方法接受一个Car类型的参数

cars.forEach( car::follow );

对象的方法引用和静态类的方法引用规则是一样的,则提供的参数类型和数量必须和方法的参数类型和方法相吻合。参数数量不定,但提供方和消费方必须等同起来。

当前这几种方法引用的使用次数不是太多,也不是很常见,但如果遇到,也要知道是什么意思。

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

推荐阅读更多精彩内容

  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,213评论 1 2
  • 原文http://www.codeceo.com/article/learn-java-lambda.html L...
    与我常在1053阅读 1,118评论 1 7
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 原文链接: Lambdas 原文作者: shekhargulati 译者: leege100 lambda表达式是...
    忽来阅读 6,563评论 8 129
  • 很多人都爱上了徐司白。她们感动于他为了苏眠放弃了信仰,放弃了并肩作战的伙伴,放弃了s。他为苏眠营造了一个他自以为美...
    文艺少女伪文艺阅读 3,113评论 0 2