java lambda表达式学习笔记
lambda是函数式编程(FP,functional program),在java8中引入,而C#很早之前就有了。在java中lambda表达式是'->',在C#中是‘=>’。
杜甫说:射人先射马,擒贼先擒王。学习一个库要学习它的入口类。lambda的入口类是Stream,一看Stream中的函数就会发现Function,Predicate等lambda元素。
一.几个概念
函数式接口 Functional Interface,除了static和default类型的方法外,只有一个函数的接口。以前,接口中的一切方法都是public的,现在接口中可以包含default类型的实现方法了。java中没有函数指针的概念,C#中有delegate委托相当于函数指针,但java也是有办法的,用一个类,类里面有一个函数,这个类就相当于函数指针。这么整实现简单,理解简单,但是代码比较冗长。
谓词 Predicate, 简单来说,谓词就是条件。正规来说,谓词就是一个函数boolean f(x1,x2...),表示变量x1,x2...是否满足条件f。在java中谓词的定义就是一个函数式接口。
函数(映射) Function,将一种类型的对象映射为另一种或同种类型的对象,它就是一个函数ObjectA f(ObjectB)。在java中映射的定义也是一个函数式接口。
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
可见,除了apply()函数以外,其余default方法在外部都是不可见的。所以定义函数式接口的时候需要把其他函数声明称static或者default类型的。
Optional这个值表示一个对象,这个对象可能为空也可能不为空,对于它可以产生许多行为:
如果它是null,该怎么做orElse()和orElseGet()
如果不为null,该怎么做ifPresent()
判断是否为null,isPresent()
这个类看上去十分鸡肋,但用处十分广泛
package aaa;
import java.util.NoSuchElementException;
import java.util.Optional;
public class OptionalDemo {
public static void main(String[] args) {
//创建Optional实例,也可以通过方法返回值得到。
Optional<String> name = Optional.of("Sanaulla");
//创建没有值的Optional实例,例如值为'null'
Optional<Object> empty = Optional.ofNullable(null);
//isPresent方法用来检查Optional实例是否有值。
if (name.isPresent()) {
//调用get()返回Optional值。
System.out.println(name.get());
}
try {
//在Optional实例上调用get()抛出NoSuchElementException。
System.out.println(empty.get());
} catch (NoSuchElementException ex) {
System.out.println(ex.getMessage());
}
//ifPresent方法接受lambda表达式参数。
//如果Optional值不为空,lambda表达式会处理并在其上执行操作。
name.ifPresent((value) -> {
System.out.println("The length of the value is: " + value.length());
});
//如果有值orElse方法会返回Optional实例,否则返回传入的错误信息。
System.out.println(empty.orElse("There is no value present!"));
System.out.println(name.orElse("There is some value!"));
//orElseGet与orElse类似,区别在于传入的默认值。
//orElseGet接受lambda表达式生成默认值。
System.out.println(empty.orElseGet(() -> "Default Value"));
System.out.println(name.orElseGet(() -> "Default Value"));
try {
//orElseThrow与orElse方法类似,区别在于返回值。
//orElseThrow抛出由传入的lambda表达式/方法生成异常。
empty.orElseThrow(Exception::new);
} catch (Throwable ex) {
System.out.println(ex.getMessage());
}
//map方法通过传入的lambda表达式修改Optonal实例默认值。
//lambda表达式返回值会包装为Optional实例。
Optional<String> upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("No value found"));
//flatMap与map(Funtion)非常相似,区别在于lambda表达式的返回值。
//map方法的lambda表达式返回值可以是任何类型,但是返回值会包装成Optional实例。
//但是flatMap方法的lambda返回值总是Optional类型。
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));
//filter方法检查Optiona值是否满足给定条件。
//如果满足返回Optional实例值,否则返回空Optional。
Optional<String> longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));
//另一个示例,Optional值不满足给定条件。
Optional<String> anotherName = Optional.of("Sana");
Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
System.out.println(shortName.orElse("The name is less than 6 characters"));
}
}
二.lambda表达式
有两个作用
* 作为函数指针
* 替代匿名内部类,替代函数式接口(FunctionalInterface)
lambda不是语法糖,它在内部实现上也跟匿名内部类不同,匿名内部类需要进行类文件加载,而lambda表达式不用,所以lambda表达式效率比匿名内部类高。
三种用法
用'(x1,x2,x3)'表示传入参数
如果无参写作’()‘
参数类型可以指明,也可以不指明,java会根据后半部分函数形参自动推断出来。
List<String> a = Arrays.asList("we i di ao is great".split(" "));
a.forEach((s) -> System.out.println(s));// 表达式
a.forEach((String s) -> {
System.out.println(s);
});// 语句块
a.forEach(System.out::println);// 函数
方法引用
object::fun()
className::fun()静态方法引用
className::new 构造函数引用
public class LambdaIntro {
// functional interface 函数式接口
public static interface ItemWithIndexVisitor<E> {
public void visit(E item, int index);
}
public static <E> void eachWithIndex(List<E> list,
ItemWithIndexVisitor<E> visitor) {
for (int i = 0; i < list.size(); i++) {
visitor.visit(list.get(i), i);
}
}
// 一个普通函数,用作函数指针
public static <E> void printItem(E value, int index) {
String output = String.format("%d -> %s", index, value.toString());
System.out.println(output);
}
public static void main(String[] args) {
List<String> list = Arrays.asList("A", "B", "C");
// 第一种方式
eachWithIndex(list, (value, index) -> {
String output = String.format("%d -> %s", index, value);
System.out.println(output);
});
// 第二种方式
eachWithIndex(list, LambdaIntro::printItem);
}
}
三.使用Stream
创建Stream的两种方式
* Stream接口的工厂方法
* 集合框架的stream()函数
首先来了解使用Stream接口来创建Stream,可以创建三种流:普通枚举流,产生器,迭代器。
//of:通过枚举方式创建流
Stream<Integer> one = Stream.of(1, 2, 3);
//流是可以拼接的,从而产生新流
Stream<Integer> two = Stream.concat(one, Stream.of(4, 5, 6));
two.forEach(System.out::println);
//逐个加入,那就用Builder构建器来实现
Builder<Integer> builder = Stream.builder();
Stream<Integer> three = builder.add(3).add(4).build();
three.forEach((s) -> System.out.println(s));
//产生器流generator
Random random = new Random();
Stream<Integer> four = Stream.generate(() -> random.nextInt());
four.limit(10).forEach(System.out::println);
//迭代器iterator,UnaryOperator一元运算符可以通过lambda表达式来创建
Stream<Integer>five=Stream.iterate(2, new UnaryOperator<Integer>() {
@Override
public Integer apply(Integer t) {
return t = (t * 5 + 7) % 13;
}
});
five.limit(10).forEach(System.out::println);
注意产生器generator和迭代器iterator是无限输出的,可以用limit来约束之。
集合框架都继承了Collection接口,而Collection接口就有一个stream()函数。所以剩下的任务就是如何利用流的强大特性来写出优雅的代码来。
要想深刻的了解Stream的一些函数,那就先不要使用lambda表达式,一旦了解它的普通实现,很容易改写成lambda的形式。
流Stream中的函数明显分为两类,一类返回值还是Stream,可以继续用流来处理,另一类返回值不是Stream,不能再用流中函数处理了。
Stream filter(Predicate)删除掉流中不满足条件的元素并返回新的Stream
Stream map(Function)映射,把流中的元素映射一下变成一个新流,还有mapToInt(),mapToLong(),mapToDouble()等函数,它们终究还是映射,只是映射结果更单一。map是一对一映射,flatMap是一对多映射。把一个元素映射成多个元素
Arrays.asList(1, 2, 3).stream()
.flatMap(new Function<Integer, Stream<Integer>>() {
@Override
public Stream<Integer> apply(Integer t) {
return Arrays.asList(t, t + 10, t + 100).stream();
}
}).forEach(System.out::println);
输出为1 11 101 2 12 102 3 13 103 中间我省略了换行符
distinct()去除流中重复元素
sorted()和sorted(Comparator cmp)对流中元素排序
peek()弹出一个元素
Stream.of("one", "two", "three", "four").filter(e -> e.length() > 3)
.peek(e -> System.out.println("Filtered value: " + e))
.map(String::toUpperCase)
.peek(e -> System.out.println("Mapped value: " + e))
.collect(Collectors.toList());
执行结果
Filtered value: three Mapped value: THREE Filtered value: four Mapped value: FOUR
limit(int cnt)只返回cnt个元素,skip(int cnt)跳过cnt个元素。
forEach(Consumer consumer)对于每一个元素都执行某种操作
reduce()将多个值映射为一个值,实现多对一映射
Stream.of("one", "two", "three", "four")
.reduce(new BinaryOperator<String>() {
@Override
public String apply(String t, String u) {
System.out.println(t+":"+u);
return t + "," + u;
}
}).ifPresent(System.out::println);
输出为
one:two one,two:three one,two,three:four one,two,three,four
可见,apply(t,u)函数中的t表示当前总量,u表示当前元素。
reduce(T identity,BinaryOperator<T>f)表示带初始值的reduce,比如求和函数,如果identity=9,表示一开始sum=9,此函数返回具体的对象。
String s = Stream.of("one", "two", "three", "four").reduce("baga",
new BinaryOperator<String>() {
@Override
public String apply(String t, String u) {
return t +","+ u;
}
});
System.out.println(s);
输出:
baga,one,two,three,four
collect()
Map<String, Map<String, List<Person>>> peopleByStateAndCity
= personStream.collect(Collectors.groupingBy(Person::getState,
Collectors.groupingBy(Person::getCity)));
String s=Stream.of("one", "two", "three", "four")
.collect(Collectors.joining(","));
System.out.println(s);
输出one,two,three,four
Collectors包含许多有用的静态方法
聚集函数min(),max(),count()很像sql中的聚集函数
匹配函数allMatch(Predicate p),anyMatch(Predicate p),noneMath(Predicate p)流中全部匹配,部分匹配,完全不匹配,返回布尔值
java lambda表达式学习笔记的更多相关文章
- C# Lambda表达式学习笔记
本笔记摘抄自:https://www.cnblogs.com/leslies2/archive/2012/03/22/2389318.html,记录一下学习过程以备后续查用. 一.Lambda ...
- lambda 表达式学习笔记
在Java中传递一个代码段并不容易,不能直接传递代码段.Java是一个面向对象语言,所以必须构造一个对象,这个对象的类需要一个方法能包含所需的代码.lambda的出现有效的解决这个问题,让代码变得更加 ...
- python函数和lambda表达式学习笔记
1. python函数 不同于其他语言,python支持函数返回多个值 为函数提供说明文档:help(函数名)或者函数名.__doc__ def str_max(str1, str2): ''' 比较 ...
- Lambda表达式学习笔记
Lambda基础语法 Java8中引入了一个新的操作符" -> ",该操作符被称为箭头操作符或Lambda操作符,箭头操作符将Lambda表达式拆分成两部分: 左侧:Lamb ...
- java 8 中lambda表达式学习
转自 http://blog.csdn.net/renfufei/article/details/24600507 http://www.jdon.com/idea/java/10-example-o ...
- Java Lambda表达式初探
Java Lambda表达式初探 前言 本文受启发于Trisha Gee在JavaOne 2016的主题演讲Refactoring to Java 8. Java 8已经发行两年多,但很多人仍然在使用 ...
- Java Lambda表达式入门[转]
原文链接: Start Using Java Lambda Expressions http://blog.csdn.net/renfufei/article/details/24600507 下载示 ...
- 【Java】「深入理解Java虚拟机」学习笔记(1) - Java语言发展趋势
0.前言 从这篇随笔开始记录Java虚拟机的内容,以前只是对Java的应用,聚焦的是业务,了解的只是语言层面,现在想深入学习一下. 对JVM的学习肯定不是看一遍书就能掌握的,在今后的学习和实践中如果有 ...
- 《深入理解Java虚拟机》学习笔记
<深入理解Java虚拟机>学习笔记 一.走近Java JDK(Java Development Kit):包含Java程序设计语言,Java虚拟机,JavaAPI,是用于支持 Java 程 ...
随机推荐
- android 关于appcompat v7出错问题与解决
1.appcompat_v7:应用兼容包,V7说的是版本7,即android2.1,这个兼容包支持2.1版本以上系统2.最近谷歌官方将兼容jar包与某些资源文件单独拿出来建立了一个android工程, ...
- android raw与assets区别
*res/raw和assets的相同点: 1.两者目录下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制. *res/raw和assets的不同点:1.res/raw中的文件会被映射到R ...
- JAVA-android 更改APP名称与图标
首先要在你的资源文件放入你想换的图标图片拖到drawable-XX文件夹下,然后你打开AndroidManifest.xml这个配置清单文件找到application标签里的这句android:ico ...
- Linux vim编辑命令
yum install -y vim-enhanced 安装vim 命令 一般模式 shift 4 行尾 shift 6 或 0行首 gg 顶部 G 下部 复制 yy 剪切几行 数字 dd p粘贴 ...
- NYOJ-756重建二叉树
重建二叉树 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描述 题目很简单,给你一棵二叉树的后序和中序序列,求出它的前序序列(So easy!). 输入 输入有多组数据(少于 ...
- BestCoder Round #87 1002 Square Distance[DP 打印方案]
Square Distance Accepts: 73 Submissions: 598 Time Limit: 4000/2000 MS (Java/Others) Memory Limit ...
- HDU2955 Robberies[01背包]
Robberies Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total S ...
- 初识Struts2
一.Struts2入门案例 ①引入jar包 ②在src下创建struts.xml配置文件 <?xml version="1.0" encoding="UTF-8&q ...
- AC日记——合法C标识符 openjudge 1.7 06
06:合法 C 标识符 总时间限制: 1000ms 内存限制: 65536kB 描述 给定一个不包含空白符的字符串,请判断是否是C语言合法的标识符号(注:题目保证这些字符串一定不是C语言的保留字 ...
- U3D的飞船太空射击例子中,使用coroutine
coroutine 协同程序与线程差不多,也就是一条执行序列,拥有自己独立的栈,局部变量和指令指针,同时又与其它协同程序共享全局变量和其它大部分东西.线程与协同程序的主要区别在于,一个具有多线程的程序 ...