美文网首页
Android Studio下使用JAVA8和Lambda表达式

Android Studio下使用JAVA8和Lambda表达式

作者: 黄旭翔 | 来源:发表于2017-08-13 22:21 被阅读0次

    时间: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的版本
      imagimag
    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的位置出现灰色提示

    imageimage
    直接使用ALT+ENTER快捷键出现提示
    imageimage
    回车确认
    imageimage
    Android Studio智能的帮我们完成了一次lambda表达式的修改 继续修改匿名Runnable接口
    imageimage

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

    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 );
    

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

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

    相关文章

      网友评论

          本文标题:Android Studio下使用JAVA8和Lambda表达式

          本文链接:https://www.haomeiwen.com/subject/lhxyrxtx.html