让你介绍lambda表达式:介绍+语法+举例(后面不需要介绍)
lambda表达式介绍
- lambda表达式重写了函数式接口中的唯一抽象方法。
- 函数内部实现中以接口.方法的形式调用,只是外部传入时可以无需理会对象约束,直接传入方法的实现。
lambda表达式语法
(参数)->{方法体}
- 参数的类型名可以省略(会在调用时候进行类型推导)
- 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略,且不需要写'return'关键字
- 有'return'关键字,必须加花括号
lambda表达式示例-与函数式接口关系
// JDK7 匿名内部类写法,非Lambda表达式写法
new Thread(new Runnable(){// 接口名
@Override
public void run(){// 方法名
System.out.println("Thread run()");
}
}).start();
Runnable r = () -> System.out.println("hello world");
new Thread(
() -> System.out.println("hello world")
).start();
//Thread内部仍然需要在实现中以接口.方法的形式调用
private Runnable target;
target.run()
常用用法
//排序
employees.sort((o1, o2) -> o1.getName().compareTo(o2.getName()));
employees.sort(Comparator.comparing(Employee::getName));
//输出
stream.foreach(System.out::println)
comparator排序,对于一个容器,比如set,你想实现自定义排序规则的话怎么做
java集合框架 自定义排序规则:Collections.sort(List<T> list, Comparator<? super T> c)
TreeSet<String> mmp=newTreeSet<String>( (Object o1, Object o2) ->o1.toString().compareTo(o2.toString());
大于0表示o1大于o2,等于0表示等于,小于0表示小于
lambda表达式,每个用户有个黑名单标识位,如何用lamda表达式来实现把黑名单标识位为true的过滤出来并得出String的形式
lambda底层实现
Java 8 Lambda实现原理分析
- 编译器会根据Lambda表达式生成一个私有的静态函数lambda$0,在该函数中执行你的逻辑
- 那么怎么来调用这个lambda$0函数呢,有一个类叫metafactory,它会为Lambda表达式生成了一个内部实现类,并调用lambda$0函数
- 形式如下:
@FunctionalInterface
interface Print<T> {
public void print(T x);
}
public class Lambda {
public static void PrintString(String s, Print<String> print) {
print.print(s);
}
private static void lambda$0(String x) {
System.out.println(x);
}
final class $Lambda$1 implements Print{
@Override
public void print(Object x) {
lambda$0((String)x);
}
}
public static void main(String[] args) {
PrintString("test", new Lambda().new $Lambda$1());
}
}
Stream
Stream特点
- 链式调用,Internal的操作会返回新的stream继续操作
- 单向流动,同一个stream不能操作两次,必须使用新的stream
- 函数式编程,不存储任何元素,不会修改数据源
- 延迟性,只有到终止操作的语句才会把之前的中间语句执行(所以才要单向流动)1.创
Stream步骤
1.创建Stream
一个数据源(如: 集合、数组), 获取一个流。
如:
通过Collection系列集合提供的stream()方法
List<String> list = Arrays.asList("JAVA", "J2EE", "Spring", "Hibernate");
Stream<String> stream3 = list.stream();
通过Stream类的静态方法of()获取多个值
Stream<String> streamOfArray = Stream.of("a", "b", "c");
通过Arrays中的静态方法stream()获取数组流
String[] arr = new String[] { "a", "b", "c" };
Stream<String> streamOfArrayFull = Arrays.stream(arr);
2.中间操作
一个中间操作链,对数据源的数据进行处理。
比较常用的是map函数,将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同
List<Integer> l = Stream.of('a','b','c')
.map( c -> c.hashCode())
.collect(Collectors.toList());
System.out.println(l); //[97, 98, 99]
3.终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果 。
如sum()求和,count()计数
方法引用(了解了解)
当我们想要实现一个函数式接口的那个抽象方法,但是已经有类实现了我们想要的功能,这个时候我们就可以用方法引用来直接使用现有类的功能去实现
- 要求实现抽象方法的参数列表和返回参数,必须与引用方法保持一致
- 当方法体中只有一句调用语句,则此时可以改写为方法引用
- 默认传入参数为lambda表达式的参数体。只有类名::实例方法名特殊另外说明
Consumer<String> consumer = str -> System.out.println(str);
consumer.accept("This is Major Tom");
lambda表达式中的实现已有相关类帮我们实现该方法
Consumer<String> consumer = System.out::println;
consumer.accept("This is Major Tom");
- 语法格式对象
- 引用对象::实例方法名
- 类名::静态方法名
- 类名::new
- 类名::实例方法名
当使用 类名::实例方法名 方法引用时,一定是lambda表达式所接收的第一个参数来调用实例方法,如果lambda表达式接收多个参数,其余的参数作为方法的参数传递进去,若无参则实例方法也无参即可。
当方法体内存在参数.方法名的时候,就可以使用类名::实例方法
students.sort((o1, o2) -> o1.getScore() - o2.getScore());
//会变为(当然如果无参,该形式与其他形式无异)
o1.getScore(o2)
那么如果想要用类名::实例方法,则需要
public int compareByScore(Student student){
return this.getScore() - student.getScore();
}
students.sort(Student::compareByScore);
方法引用看不懂的话,学会将它转为lambda表达式,转lambda表达式要注意盯着的不是接口,而是抽象方法。传入参数是当前调用的类!!!
常用函数式接口
Stream
Stream定义
Stream:a sequence of element from a source that supports data processing operation.
a sequence of element: 元素,即source内的内容
source:容器,承载element,如Collection,Array,I/O
Data processing operation:map,filter等数据处理操作
操作方法
intermediate operations
中间操作会返回一个新的流,并且操作是延迟执行的(lazy),它不会修改原始的数据源,而且是由在终点操作开始的时候才真正开始执行。
filter
返回的流中只包含满足断言(predicate)的数据。
List<String>strings = Arrays.asList("abc", "", "bc","efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
distinct
保证输出的流中包含唯一的元素,它是通过Object.equals(Object)来检查是否包含相同的元素。
List<String> l = Stream.of("a","b","c","b").distinct().collect(Collectors.toList());
System.out.println(l); //[a, b, c]
limit
获取指定数量的流
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map
将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同
List<Integer> l = Stream.of('a','b','c')
.map( c -> c.hashCode())
.collect(Collectors.toList());
System.out.println(l); //[97, 98, 99]
flatmap
混合了map+flattern的功能,它将映射后的流的元素全部放入到一个新的流中。规定返回值为stream,然后将stream合在一起。
String[] word = new String[]{"Hello","world"};
//{{h,e,l,l,o},{w,o,r,l,d}}==>{h,e,l,l,o,w,o,r,l,d}
Arrays.stream(words).map(w->w.split("")).flatMap(Arrays::Stream);
sorted
将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable,则终点操作执行时会抛出java.lang.ClassCastException异常。sorted(Comparator<? super T> comparator)可以指定排序的方式
String[] arr = new String[]{"b_123","c+342","b#632","d_123"};
List<String> l = Arrays.stream(arr)
.sorted((s1,s2) -> {
if (s1.charAt(0) == s2.charAt(0))
return s1.substring(2).compareTo(s2.substring(2));
else
return s1.charAt(0) - s2.charAt(0);
})
.collect(Collectors.toList());
System.out.println(l); //[b_123, b#632, c+342, d_123]
terminal operations
Stream只有遇到终止操作,它的源才开始执行遍历操作,而且只会进行一次遍历,而不是每个操作都执行一次遍历。
Match
- allMatch只有在所有的元素都满足断言时才返回true,否则flase,流为空时总是返回true
- anyMatch只有在任意一个元素满足断言时就返回true,否则flase
- noneMatch只有在所有的元素都不满足断言时才返回true,否则flase
public boolean allMatch(Predicate<? super T> predicate)
public boolean anyMatch(Predicate<? super T> predicate)
public boolean noneMatch(Predicate<? super T> predicate)
System.out.println(Stream.of(1,2,3,4,5).allMatch( i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).anyMatch( i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0)); //false
count
count方法返回流中的元素的数量。它实现为
mapToLong(e -> 1L).sum();
find
findAny()用来get满足条件的特定元素(一般就剩1/0,不会用到任意取),findFirst()用来获取首个满足条件元素
- findAny()返回任意一个元素,如果流为空,返回空的Optiona
- findFirst()返回第一个元素,如果流为空,返回空的Optional
Arrays.stream(SwitchEnum.values()).filter(switchEnum -> switchEnum.getType() == type).findAny()
.orElse(null);
Arrays.stream(SwitchEnum.values()).filter(switchEnum -> switchEnum.getName().equals(name)).findFirst()
.orElse(null);
reduce
- 第一个方法使用流中的第一个值作为初始值,后面两个方法则使用一个提供的初始值。
- accumulator是累计逻辑,其运行流程如下
pubic Optional<T> reduce(BinaryOperator<T> accumulator)
pubic T reduce(T identity, BinaryOperator<T> accumulator)
pubic <U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
Optional<Integer> total = Stream.of(1,2,3,4,5).reduce( (x, y) -> x +y);
Integer total2 = Stream.of(1,2,3,4,5).reduce(0, (x, y) -> x +y);
forEach、forEachOrdered
- forEach遍历流的每一个元素,执行指定的action,不保证按照容器中元素原有顺序执行尤其是在并行运算中。
- forEachOrdered,保证按照容器中元素原有顺序执行。
Stream.of(1,2,3,4,5).forEach(System.out::println);
collect
<R,A> R collect(Collector<? super T,A,R> collector)
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner)
使用一个collector执行mutable reduction操作。辅助类Collectors提供了很多的collector,可以满足我们日常的需求,你也可以创建新的collector实现特定的需求。
理解这里需要理解Collector的原理
第二个方法提供了更底层的功能,它的逻辑类似下面的伪代码:
R result = supplier.get();
for (T element : this stream)
accumulator.accept(result, element);
return result;
List<String> asList = stringStream.collect(ArrayList::new, ArrayList::add,
ArrayList::addAll);
String concat = stringStream.collect(StringBuilder::new, StringBuilder::append,
StringBuilder::append)
.toString();
类型转换
toArray():将流中的元素放入到一个数组中。
collect(Collectors.toXXX):转换为List或MAP类型
Numeric Stream
stream中还存在着IntStream,DoubleStream..,这些Stream规定了元素的基本类型。好处:
- 每个元素占用空间更小,Integer内存空间要比int大
常用方法
构造方法与Stream一样,操作方法也与Stream一样,特色:
可以替代一些for,和简单的数组生成
- IntStream.range(1, 5)
- IntStream.generate(() -> ThreadLocalRandom.current().nextInt(10)).limit(3);