为什么要学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
- BiPredicate
(T, U) -> boolean - BiConsumer
(T, U) -> void - BiFunction
(T, U) -> R
注意:Java中的数据类型可分为引用类型(Byte、Integer、List等)和原始类型(byte、int、float、char等)。Java中提供装箱和拆箱的机制,例如装箱就是把原始类型包装后放到堆上,所以装箱后需要更多的内存,所以Java8为大多数函数式接口提供了相应的版本来避免这个问题,例如:
IntPredicate、LongPredicate、DoublePredicate
IntConsumer、LongConsumer、DoubleConsumer
方法引用 ::
方法引用是什么 ?
方法引用是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。
- 作用
方法引用的唯一用途是支持Lambda的简写。
方法引用提高了代码的可读性,也使逻辑更加清晰。 -
组成
使用::操作符将方法名和对象或类的名字分隔开。
“::” 是域操作符(也可以称作定界符、分隔符)。
- 分类
- 静态方法引用
组成语法格式:ClassName::staticMethodName
String::valueOf等价于lambda表达式 (s) ->String.valueOf(s)
Math::pow等价于lambda表达式(x, y) -> Math.pow(x, y);
- 实例方法引用
这种语法与用于静态方法的语法类似,只不过这里使用的是对象引用而不是类名.
实例方法引用又分以下三种类型
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 的个数。
- 红色框是生命开始的地方,负责创建一个 Stream 实例;
- 绿色框是赋予Stream灵魂的地方,把一个 Stream 转换成另外一个 Stream,即包含所有 nums 变量的 Stream ,经过绿框的 filter 方法以后,重新生成了一个过滤掉原 nums列表所有为null的 Stream ;
-
蓝色框中的语句是丰收的地方,把 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 + "*"));