JDK8 新特性

为什么要学Java8

  • Java8让你的编程变得更容易
  • 充分稳定的利用计算机硬件资源

Lambda

lambda 是什么 ?

"Lambda 表达式 "(lambda expression) 是一个匿名函数,Lambda 表达式基于数学中的 λ 演算得名,直接对应于其中的 lambda 抽象(lambda abstraction) ,是一个匿名函数,即没有函数名的函数,所以试着使用匿名函数的方式来理解:

(params) -> expression // 函数名呢 ? 没有 !!!
(params) -> statement // 函数名呢 ? 没有 !!!
(params) -> { statements } // 函数名呢 ? 没有 !!!
语法是什么 ?
  • 一个括号:括号内用逗号分隔的形式参数(参数是函数式接口里面方法的参数)

  • 一个箭头符号: 箭头符号 “->” 指向方法体,方法体是函数式接口里面需要实现的方法。

//方法体只有一行表达式的时候可以省略{}
textView.setOnClickListener((view)-> textView.setText("1"));
//方法体中有一行以上代码就不能省略{}
textView.setOnClickListener((view) -> {
            textView.setText("一行代码");
            textView.setText("二行代码");
        });

Lambda 表达式是在 JDK 8 中开始支持的一种函数式推导语言,能够大量减少匿名内部类那种冗余的代码。在 Android 中,可以大量使用在设置监听,设置异步回调等场景。

Lambda 表达式 vs 匿名类

既然 lambda 表达式即将正式取代 Java 代码中的匿名内部类,那么有必要对二者的不同点做一个比较分析.

  • 关键字 this 。
    匿名类的 this 关键字指向匿名类
    lambda 表达式的 this关键字指向包围 lambda 表达式的
    类。
  • 编译方式。
    Java 编译器将 lambda 表达式编译成类的私有方法。
快速开始
  • 在module中build.gradle中
    android节点上面添加
/*lambda表达式*/
apply plugin: 'me.tatarka.retrolambda'

在android节点中添加

compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

在dependencies节点上面android节点外面添加:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        /*lambda表达式*/
        classpath 'me.tatarka:gradle-retrolambda:3.2.0'
    }
}
实例
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = (TextView) findViewById(R.id.tv_text);
        textView.setOnClickListener((view)-> textView.setText("lambda表达式实现的点击事件"));
        
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                textView.setText(" 内部类实现的点击事件");
            }
        });
    }
}
小结

使用 lambda 表达式设计的代码会更加简洁,而且还可读.

函数式接口

函数式接口是只包含一个抽象方法的接口。函数式接口有时候被称为SAM类型,意思是单抽象方法(Single Abstract Method)。新版的Java API带有@FunctionInterface注解的接口表示该接口被设计为函数式接口,如果用@FunctionInterface定义了一个接口却有多个抽象方法,那么编译器会报错。

使用函数式接口

新版的Java API已经为我们提供了很多函数式接口,在java.util.function包下,下面我们举例介绍几个常用的接口。

  • Predicate (java.util.function.Predicate<T>)
    Predicate定义了一个名为test的抽象方法,接收T,返回boolean值.
/** 接口定义 */
@FunctionInterface
public interface Predicate<T>{
    boolean test(T t);
}
/** Example */
public static <T> List<T> process(List<T> mList, Predicate<T> condition) {
    List<T> results = new ArrayList<>();
    for (T t : mList) {
        if (condition.test(t)) {
            results.add(t);
        }
    }
    return results;
}
  • Consumer (java.util.function.Consumer<T>)
    Consumer定义了一个名为accept的抽象方法接收T,返回值(void)
/** 接口定义 */
@FunctionInterface
public interface Consumer<T>{
    void accept(T t);
}
/** Example */
public static <T> void process(List<T> mList, Consumer<T> c) {
    for (T t : mList) {
        c.accept(t);
    }
}
  • Function (java.util.function.Function<T, R>)
    Function定义了一个名为apply的抽象方法,接收T, R
/** 接口定义 */
@FunctionInterface
public interface Function <T, R>{
    R apply(T t);
}
/** Example */
public static <T, R> List<R> process(List<T> mList, Function<T, R> f) {
    List<R> results = new ArrayList<>();
    for (T t : mList) {
        results.add(f.apply(t));
    }
    return results;
}
  • Supplier (java.util.function.Supplier<T>)
    Supplier定义了一个名为get的抽象方法,不接受任何参数,返回T
/** 接口定义 */
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
/** Example */
Supplier<String> supplier = () -> {return "Hello Java8";};
System.out.println(supplier.get()); //Hello Java8

还有很多有兴趣的可以自己查阅:

  • BinaryOperator
    (T, T) -> T
  1. BiPredicate
    (T, U) -> boolean
  2. BiConsumer
    (T, U) -> void
  3. BiFunction
    (T, U) -> R

注意:Java中的数据类型可分为引用类型(Byte、Integer、List等)和原始类型(byte、int、float、char等)。Java中提供装箱和拆箱的机制,例如装箱就是把原始类型包装后放到堆上,所以装箱后需要更多的内存,所以Java8为大多数函数式接口提供了相应的版本来避免这个问题,例如:
IntPredicate、LongPredicate、DoublePredicate
IntConsumer、LongConsumer、DoubleConsumer

方法引用 ::

方法引用是什么 ?

方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。

当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。

  • 作用
    方法引用的唯一用途是支持Lambda的简写。
    方法引用提高了代码的可读性,也使逻辑更加清晰。
  • 组成
    使用::操作符将方法名和对象或类的名字分隔开。
    “::” 是域操作符(也可以称作定界符、分隔符)。


  • 分类
  1. 静态方法引用
    组成语法格式:ClassName::staticMethodName
String::valueOf等价于lambda表达式 (s) ->String.valueOf(s) 
Math::pow等价于lambda表达式(x, y) -> Math.pow(x, y);
  1. 实例方法引用
    这种语法与用于静态方法的语法类似,只不过这里使用的是对象引用而不是类名.
    实例方法引用又分以下三种类型
    a.实例上的实例方法引用
    组成语法格式:instanceReference::methodName
    b.超类上的实例方法引用
    组成语法格式:super::methodName
    eg:
super::name //通过super指向父类方法
this :: equals等价于lambda表达式  x -> this.equals(x) //通过this指向本类方法

c.类型上的实例方法引用
组成语法格式:ClassName::methodName(不推荐使用)
3)构造方法引用
构造方法引用又分构造方法引用和数组构造方法引用。
a.构造方法引用 (也可以称作构造器引用)
组成语法格式:Class::new
构造函数本质上是静态方法,只是方法名字比较特殊,使用的是new 关键字。
eg:

String::new, 等价于lambda表达式 () -> new String() 

b.数组构造方法引用:
组成语法格式:TypeName[]::new
eg:
int[]::new是一个含有一个参数的构造器引用,这个参数就是数组的长度。

int[]::new等价于lambda表达式  x -> new int[x]。
假想存在一个接收int参数的数组构造方法
IntFunction<int[]> arrayMaker = int[]::new;
int[] array = arrayMaker.apply(10);  //创建数组 int[10]

forEach

forEach 内部迭代以前 Java 集合是不能够表达内部迭代的,而只提供了一种外部迭代的方式,也就是 for 或者 while 循环。
jdk8之前

final List<String> mList = Arrays.asList("1111", "2222", "3333", "4444");  
//普通for循环
for (int i = 0; i < mList.size(); i++) {  
    System.out.println(mList.get(i));
}   
//增强for循环:
for(String str : mList) {  
    System.out.println(str);
} 

Java8中Iterable接口拥有一个forEach方法用来实现内部遍历器:

//java8之后
mList.forEach(new Consumer<String>() {  
    public void accept(final String str) {
        System.out.println(str);
    }
});

结合Lambda:

mList.forEach(str -> System.out.println(str));
mList.forEach(System.out::println);  

Stream 初体验

Stream是元素的集合,这点让 Stream 看起来用些类似 Iterator;
可以支持顺序和并行的对原 Stream 进行汇聚的操作;

  • Stream 是什么?高级迭代器
    大家可以把 Stream 当成一个高级版本的 Iterator 。
    Iterator, 用户只能一个一个的遍历元素并对其执行某些操作;Stream用户只要给出需要对其包含的元素执行什么操作
    比如 “ 过滤掉长度大于 10 的字符串 ” 、 “ 获取每个字符串的首字母 ” 等,具体这些操作如何应用到每个元素上,就给 Stream 就好了
List<Integer> nums = Arrays.asList(1,null,3,4,null,6);
nums.stream()// 创建 stream 实例
      .filter(num -> num != null)// 使用条件进行过滤
      .forEach(n->System.out.println(n));// 遍历集合元素

上面这段代码是获取一个 List 中,元素不为 null 的个数。


  1. 红色框是生命开始的地方,负责创建一个 Stream 实例;
  2. 绿色框是赋予Stream灵魂的地方,把一个 Stream 转换成另外一个 Stream,即包含所有 nums 变量的 Stream ,经过绿框的 filter 方法以后,重新生成了一个过滤掉原 nums列表所有为null的 Stream ;
  3. 蓝色框中的语句是丰收的地方,把 Stream 的里面包含的内容按照某种算法来汇聚成一个值


  • 使用步骤
    在此我们总结一下使用 Stream 的基本步骤:创建 Stream;转换 Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换); 对 Stream 进行聚合(Reduce)操作,获取想要的结果;
  • 操作符
//创建Integer类型的List
List<Integer> nums = Arrays.asList(1,1,null,2,3,4,null,5,6,7,8,9,10);
System.out.println("sum is:"+
nums.stream()// 获取其对应的 Stream 对象
.filter(num -> num != null)// 使用条件过滤
.distinct()// 去重
.mapToInt(num -> num * 2)// 每个元素乘以 2
.peek(System.out::println)// 每个元素被消费的时候打印自身
.skip(2)// 跳过前两个元素
.limit(4)// 返回前四个元素
.sum());// 加和运算

所有转换操作都是 lazy 的,多个转换操作只会在汇聚操作的时候融合起来,一次循环完成。我们可以这样简单的理解, Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

经典案例

例 1 、用 lambda 表达式实现 Runnable

private void testRunnable() {
        // Java 8 之前:
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("在Java8之前,需要些很多代码");
            }
        }).start();
        //Java 8 方式:
        new Thread(() -> System.out.println("在java8中简单的一句")).start();
    }

例 2 、使用 Java 8 lambda 表达式进行事件处理,给一个按钮添加监听器:

private void testAddListener() {
        // Java 8 之前:
        mBnt = (Button) findViewById(R.id.main_btn_click);
        mBnt.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ToastUtil.showShort(MainActivity.this, " 我被点击了");
            }
        });
        //Java 8 方式:
        mBnt.setOnClickListener(v -> ToastUtil.showShort(MainActivity.this, "我被点击了"));
    }

例 3 、使用 forEach+lambda 表达式对集合进行迭代

protected static void testForEach() {
        // Java 8 之前:
        List<String> list = Arrays.asList("java web", "ios", "android", "h5");
        for (Object item : list) {
            System.out.println(item);
        }
        //Java8方式:
        list.forEach(System.out::println);
    }

例 4 、 Java 8 中使用 lambda 表达式的 Map示例

protected static void test() {
        // jdk8之前
        List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
        for (Integer cost : costBeforeTax) {
            double price = cost + 0.12 * cost;
            System.out.println(price);
        }
        // jdk8
        List<Integer> costBeforeTax2 = Arrays.asList(100, 200, 300, 400, 500);
        costBeforeTax2.stream()// 获取 Stream 对象
                      .map((cost) -> cost + 0.12 * cost)// 对每一个元素加上 12% 的税
                      .forEach(System.out::println);// 遍历
    }

例 5 Java 8 中使用 lambda 表达式的 Map 和 Reduce 示例

protected static void test() {
        //  为每个订单加上 12% 的税
        // JDK8 之前
        List<Integer> costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
        double total = 0;
        for (Integer cost : costBeforeTax) {
            double price = cost + .12 * cost;
            total = total + price;
        }
        System.out.println("Total : " + total);
        // JDK8
        List<Integer> costBeforeTax2 = Arrays.asList(100, 200, 300, 400, 500);
        double bill = costBeforeTax2.stream()// 获取 stream 对象
                .map((cost) -> cost + 0.12 * cost)// 每一个元素 进行增加
                .reduce((sum, cost) -> sum + cost)//sun(a,b)
                .get();// 返回结果
        System.out.println("Total : " + bill);
    }

例 6 、通过过滤创建一个 String 列表过滤是 Java 开发者在大规模集合上的一个常用操作,而现在使用 lambda 表达式和流 API 过滤大规模数据集合是惊人的简单。流提供了一个 filter() 方法,接受一个 Predicate 对象,即可以传入一个 lambda 表达式作为过滤逻辑。

private static void test() {
        //创建一个字符串列表,每个字符串长度大于 2
        List<String> strList = Arrays.asList("Java", "Scala", "C", "Haskell", "Lisp");
        List<String> filtered = strList.stream()// 获取 Stream 对象
                                       .filter(x -> x.length() > 2)// 过滤
                                       .collect(Collectors.toList());// 返回集合
        System.out.printf(" 原集合 : %s, 过滤后 : %s %n", strList, filtered);
        // 原集合 : [Java, Scala, C, Haskell, Lisp], 过滤后 : [Java, Scala, Haskell, Lisp]
    }

例 7 、对列表的每个元素应用函数
我们通常需要对列表的每个元素使用某个函数,例如逐一乘以某个数、除以某个数或者做其它操作。这些操作都很适合用 map() 方法,可以将转换逻辑以lambda 表达式的形式放在 map() 方法里,就可以对集合的各个元素进行转换了,如下所示。

private static void testStream7() {
        //  将字符串换成大写并用逗号链接起来
        List<String> list = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.", "Canada");
        String listToString = list.stream()// 获取 stream 对象
                .map(x -> x.toUpperCase())// 格式修改 转成大写
                .collect(Collectors.joining(", "));// 每个字符串拼接
        System.out.println(listToString);
    }

例 8 、复制不同的值,创建一个子列表
本例展示了如何利用流的 distinct() 方法来对集合进行去重。
private static void testStream8() {
// 用所有不同的数字创建一个正方形列表
List<Integer> numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);
List<Integer> distinct = numbers.stream()// 获取 Stream 对象
.map( i -> i*i)// 求平方
.distinct()// 去重
.collect(Collectors.toList());// 聚集:转成集合
System.out.printf(" 原有集合 : %s, 去重后 : %s %n", numbers, distinct);
}
例 9 、计算集合元素的最大值、最小值、总和以及平均值获取常用统计值

private static void testStream9() {
        //  获取数字的个数、最小值、最大值、总和以及平均值
        List<Integer> list = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
        //IntSummaryStatistics: 集合概要例如 : count, min, max, sum, and average.
        IntSummaryStatistics stats = list//
                .stream()// 获取 Stream 对象
                .mapToInt((x) -> x)// 格式转换
                .summaryStatistics();//
        System.out.println(" 最大值 : " + stats.getMax());
        System.out.println(" 最小值 : " + stats.getMin());
        System.out.println(" 求和 : " + stats.getSum());
        System.out.println(" 平均值 : " + stats.getAverage());
    }

总结
若一个方法接收 Runnable 、 Comparable 或者 Callable 接口,都有单个抽象方法,可以传入 lambda 表达式。
类似的java.util.function包内的接口,例如 Predicate 、 Function 、 Consumer 或 Supplier ,那么可以向其传 lambda 表达式。
lambda 表达式内可以使用方法引用,仅当该方法不修改 lambda 表达式提供的参数。本例中的 lambda 表达式可以换为方法引用,因为这仅是一个参数相同的
简单方法调用。

list.forEach(n -> System.out.println(n));
list.forEach(System.out::println); //  使用方法引用

若对参数有任何修改,则不能使用方法引用,而需键入完整地 lambda 表达

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

推荐阅读更多精彩内容

  • 基础语法 Lambda需要函数式接口支持 接口用@Functiona1Interface修饰,就是函数式接口 La...
    小草丶body阅读 489评论 0 0
  • 官方新特性说明地址 下面对几个常用的特性做下重点说明。 一、Lambda表达式 1.1 函数式编程 百科介绍:h...
    丘八老爷阅读 947评论 0 6
  • Java8是Java5以来最具革命性的版本。为Java语言、类库、开发工具与JVM带来了大量新特性。以下是新特性的...
    quantumcs阅读 183评论 0 3
  • lambda表达式(又被成为“闭包”或“匿名方法”)方法引用和构造方法引用扩展的目标类型和类型推导接口中的默认方法...
    183207efd207阅读 1,457评论 0 5
  • 原链接:http://www.cnblogs.com/langtianya/p/3757993.html JDK各...
    把爱放下会走更远阅读 1,104评论 0 10