1.方法引用
方法引用也是一个语法糖,可以进一步简化Lambda表达式。并不是所有的Lambda表达式和匿名内部类都可以转换为方法引用。
1.1 使用场景
在一个匿名内部类中,如果方法体中仅仅是一个方法的调用,或者是一个构造方法,那么它很可能就可以改造成为方法引用。
例:
private static void test38() {
getAuthors().stream()
.map(author -> author.getName())
.forEach(authorName -> System.out.println(authorName));
}
对应的方法引用。
private static void test38() {
getAuthors().stream()
.map(Author::getName)
.forEach(System.out::println);
}
1.2 语法详解(了解)
(1)引用类的静态方法
基本格式 类名::方法名 。
在一个匿名内部类中,如果方法体重写的方法中仅仅是一个某个类的静态方法的调用,并且,将要重写的抽象方法中所有参数都按照顺序传入到这个方法中。比如上例中的println 方法。
(2)引用对象的实例方法
基本格式 对象名::方法名 。
在一个匿名内部类中,如果方法体重写的方法中仅仅是一个某个对象的成员方法的调用,并且,将要重写的抽象方法中所有参数都按照顺序传入到这个方法中。
例:
private static void test39() {
StringBuilder sb = new StringBuilder();
getAuthors().stream()
.map(Author::getName)
.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
sb.append(s);
}
});
System.out.println(sb);
}
其对应的方法引用。
private static void test39() {
StringBuilder sb = new StringBuilder();
getAuthors().stream()
.map(Author::getName)
.forEach(sb::append);
System.out.println(sb);
}
(3)引用类的实例方法。
基本格式 类名::方法名
如果我们在重写方法时,方法体中只有一行代码,并且这行代码调用了第一个参数的成员方法,并且我们把抽象方法中剩余的参数按照顺序传入这个成员方法中,这个时候就可以使用类的实例方法。
例:
public class MethodDemo {
interface UseString {
String use(String str, int start, int length);
}
public static String subAuthorName(String str, UseString us) {
int start = 0;
int length = 1;
return us.use(str, start, length);
}
public static void main(String[] args) {
subAuthorName("半旧518", new UseString() {
@Override
public String use(String str, int start, int length) {
return str.substring(start, length);
}
});
}
}
其方法引用为。
public class MethodDemo {
interface UseString {
String use(String str, int start, int length);
}
public static String subAuthorName(String str, UseString us) {
int start = 0;
int length = 1;
return us.use(str, start, length);
}
public static void main(String[] args) {
String subName = subAuthorName("半旧518", String::substring);
System.out.println(subName);
}
}
实际上,我们最开始的Author::getName 就是第三中方法引用。
当然,记不住还是可以在Idea中用alt 键和enter 键来快速的实现转换。
(4)构造器引用
如果匿名内部类在重写方法时,方法体中只有一行代码,并且这行代码就是调用某个构造方法,就可以使用构造器引用。
例:
private static void test40() {
getAuthors().stream()
.map(author -> author.getName())
.map(name -> new StringBuilder(name))
.map(sb -> sb.append("---"))
.forEach(System.out::println);
}
方法引用对应如下。
private static void test40() {
getAuthors().stream()
.map(Author::getName)
.map(StringBuilder::new)
.map(sb -> sb.append("---"))
.forEach(System.out::println);
}
2.Stream流的高级用法
2.1 基本数据类型的优化
参考如下代码。
private static void test41() {
getAuthors().stream()
.map(author -> author.getAge() + 10)
.filter(age -> age > 28)
.forEach(System.out::println);
}
看上去好像没有什么问题。转换为匿名内部类看看。
private static void test41() {
getAuthors().stream()
.map(new Function<Author, Integer>() {
@Override
public Integer apply(Author author) {
return author.getAge() + 10;
}
})
.filter(new Predicate<Integer>() {
@Override
public boolean test(Integer age) {
return age > 18;
}
})
.forEach(System.out::println);
}
apply 与test 中参数类型都包含基本数据类型Integer ,在进行运算时,会先自动拆箱,再自动装箱,如果操作的数据元素特别多,这会造成不小的时间损耗.
java8对于基本数据类型的操作提供优化的方法:mapToInt,mapToLong…可以把流中的数据类型转换为基本数据类型,对上面的例子优化如下.
private static void test41() {
getAuthors().stream()
.mapToInt(Author::getAge)
.map(age -> age + 10)
.filter(age -> age > 18)
.forEach(System.out::println);
}
2.2 并行流
我们之前操作的流都是以串行的方式完成,对于大数据量的情况,串行的方式时间损耗会较大.java8提供了并行流,将数据的处理分配到多个线程进行处理.而且这种方式比自己实现多线程更加的轻量级,也不要考虑头疼的线程安全问题。使用parallel 即可实现并行流。
例:
private static void test42() {
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer intNum = integerStream.filter(num -> num > 5)
.reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer num) {
return result + num;
}
}).get();
System.out.println(intNum);
}
并行流的方式如下。
private static void test42() {
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer intNum = integerStream
.parallel()
.filter(num -> num > 5)
.reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer num) {
return result + num;
}
}).get();
System.out.println(intNum);
}
并行流的机制其实类似与流水线,比如前5个元素在线程1中完成过滤,后5个线程会在第2个线程中完成过滤.
3.3 调试
我们可以使用peek 方法帮助我们进行调试,它不会像终结方法一样将流废弃。
private static void test42() {
Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer intNum = integerStream
.parallel()
.peek(integer -> System.out.println(integer + " in " + Thread.currentThread()))
.filter(num -> num > 5)
.reduce((result, num) -> result + num).get();
System.out.println(intNum);
}
输出如下。
9 in Thread[ForkJoinPool.commonPool-worker-13,5,main]
4 in Thread[ForkJoinPool.commonPool-worker-15,5,main]
2 in Thread[ForkJoinPool.commonPool-worker-11,5,main]
8 in Thread[ForkJoinPool.commonPool-worker-2,5,main]
7 in Thread[ForkJoinPool.commonPool-worker-4,5,main]
6 in Thread[main,5,main]
1 in Thread[ForkJoinPool.commonPool-worker-6,5,main]
3 in Thread[ForkJoinPool.commonPool-worker-9,5,main]
5 in Thread[ForkJoinPool.commonPool-worker-8,5,main]
30
|