Java技能树之“Java8的特性”-1
知识体系
函数编程
面向对象编程是对数据进行抽象;函数式编程是对行为进行抽象。
- Lambda 表达式的特点
- Lambda 表达式使用和Stream下的接口
- 函数接口定义和使用,四大内置函数接口Consumer,Function,Supplier, Predicate.
- Comparator排序为例贯穿所有知识点。
Optional类
这是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
- Optional类的意义
- Optional类有哪些常用的方法
- Optional举例贯穿所有知识点
- 多重类嵌套Null值判断
default方法
默认方法给予我们修改接口而不破坏原来的实现类的结构提供了便利,目前java 8的集合框架已经大量使用了默认方法来改进了,当我们最终开始使用Java 8的lambdas表达式时,提供给我们一个平滑的过渡体验。
- 为什么会出现默认方法?
- 接口中出现默认方法,且类可以实现多接口的,那和抽象类有啥区别?
- 多重实现的默认方法冲突怎么办?
类型注解
那充满争议的类型注解究竟是什么? 复杂还是便捷?
- 注解在JDK哪个版本中出现的,可以在哪些地方用注解?
- 什么是类型注解?
- 类型注解的作用是什么?
- 为什么会出现类型注解(JSR308)?
重复注解
- Java8之前对重复注解是怎么做的?
- Java8对重复注解添加了什么支持?
类型推断
导致类型间互相转换的问题困扰着每个java程序员,通过编译器自动推断类型的东西可以稍微缓解一下类型转换太复杂的问题。
- 什么是泛型?
- Java7对泛型推断做了哪些优化?
- Java8对此有做了哪些优化?
JRE 精简
模块化特性是javaer所期待的特性, 一个占用资源少的JRE运行环境,紧凑的JRE特性的出现,能带来以后的物联网的发展,甚至还是会有大量的java应用程序出现在物联网上面。
- 为什么精简Java8 JRE,及好处是啥?
- 紧凑的JRE分3种,分别是compact1、compact2、compact3,他们的关系是?
- 在不同平台上如何编译等?
LocalDate/LocalDateTime
Date/Calendar槽点, java8对其进行了重写。
- Java8之前的Date有哪些槽点? (Calendar的所有属性都是可变的,SimpleDateFormat的线程不安全性等)
- Java8之前使用哪些常用的第三方时间库?
- Java8关于时间和日期有哪些类和方法,变比Java8之前它的特点是什么?
- 其它语言时间库?
JavaFX
JavaFX主要致力于富客户端开发,以弥补swing的缺陷,主要提供图形库与media库,支持audio,video,graphics,animation,3D等,同时采用现代化的css方式支持界面设计。同时又采用XUI方式以XML方式设计UI界面,达到显示与逻辑的分离。
- javaFX发展历程?
- Java8对其增加了哪些特性?
PermGen移除
PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。PermGen space是Oracle-Sun Hotspot才有,JRockit以及J9是没有这个区域。
- Java8之前 “java.lang.OutOfMemoryError: PermGen space”是怎么引起的,怎么解决的?
- 新增加的元空间(Metaspace)包含哪些东西,画出图
- 元空间(Metaspace)和PermGen对比
StampedLock
- 为什么会引入StampedLock
- 用Lock写悲观锁和乐观锁举例
- 用StampedLock写悲观锁和乐观锁举例
- 性能对比
其它更新
- Java8 还有哪些其它更新
- 字符串
- Base64
- Random
- Nashorn
- ...
Java 8 - 函数编程(lambda表达式)
简介
在Java世界里面,面向对象还是主流思想,对于习惯了面向对象编程的开发者来说,抽象的概念并不陌生。面向对象编程是对数据进行抽象,而函数式编程是对行为进行抽象。现实世界中,数据和行为并存,程序也是如此,因此这两种编程方式我们都得学。
这种新的抽象方式还有其他好处。很多人不总是在编写性能优先的代码,对于这些人来说,函数式编程带来的好处尤为明显。程序员能编写出更容易阅读的代码——这种代码更多地表达了业务逻辑,而不是从机制上如何实现。易读的代码也易于维护、更可靠、更不容易出错。
在写回调函数和事件处理器时,程序员不必再纠缠于匿名内部类的冗繁和可读性,函数式编程让事件处理系统变得更加简单。能将函数方便地传递也让编写惰性代码变得容易,只有在真正需要的时候,才初始化变量的值。
面向对象编程是对数据进行抽象;函数式编程是对行为进行抽象。
核心思想: 使用不可变值和函数,函数对一个值进行处理,映射成另一个值。
对核心类库的改进主要包括集合类的API和新引入的流Stream。流使程序员可以站在更高的抽象层次上对集合进行操作。
# lambda表达式
-
lambda表达式仅能放入如下代码: 预定义使用了 @Functional 注释的函数式接口,自带一个抽象函数的方法,或者SAM(Single Abstract Method 单个抽象方法)类型。这些称为lambda表达式的目标类型,可以用作返回类型,或lambda目标代码的参数。例如,若一个方法接收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 + "*"));
事实上,可以省略这里的lambda参数的类型声明,编译器可以从列表的类属性推测出来。
-
lambda内部可以使用静态、非静态和局部变量,这称为lambda内的变量捕获。
-
Lambda表达式在Java中又称为闭包或匿名函数,所以如果有同事把它叫闭包的时候,不用惊讶。
-
Lambda方法在编译器内部被翻译成私有方法,并派发 invokedynamic 字节码指令来进行调用。可以使用JDK中的 javap 工具来反编译class文件。使用 javap -p 或 javap -c -v 命令来看一看lambda表达式生成的字节码。大致应该长这样:
private static java.lang.Object lambda$0(java.lang.String);
- lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量。
List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { factor++; });
Compile time error : "local variables referenced from a lambda expression must be final or effectively final" 另外,只是访问它而不作修改是可以的,如下所示:
List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { System.out.println(factor*element); });
# 分类
# 惰性求值方法
lists.stream().filter(f -> f.getName().equals("p1"))
如上示例,这行代码并未做什么实际性的工作,filter只是描述了Stream,没有产生新的集合。
如果是多个条件组合,可以通过代码块{}
# 及早求值方法
List<Person> list2 = lists.stream().filter(f -> f.getName().equals("p1")).collect(Collectors.toList());
如上示例,collect最终会从Stream产生新值,拥有终止操作。
理想方式是形成一个惰性求值的链,最后用一个及早求值的操作返回想要的结果。与建造者模式相似,建造者模式先是使用一系列操作设置属性和配置,最后调用build方法,创建对象。
# stream & parallelStream
# stream & parallelStream
每个Stream都有两种模式: 顺序执行和并行执行。
顺序流:
List <Person> people = list.getStream.collect(Collectors.toList());
并行流:
List <Person> people = list.getStream.parallel().collect(Collectors.toList());
顾名思义,当使用顺序方式去遍历时,每个item读完后再读下一个item。而使用并行去遍历时,数组会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。
# parallelStream原理:
List originalList = someData;
split1 = originalList(0, mid);//将数据分小部分
split2 = originalList(mid,end);
new Runnable(split1.process());//小部分执行操作
new Runnable(split2.process());
List revisedList = split1 + split2;//将结果合并
大家对hadoop有稍微了解就知道,里面的 MapReduce 本身就是用于并行处理大数据集的软件框架,其 处理大数据的核心思想就是大而化小,分配到不同机器去运行map,最终通过reduce将所有机器的结果结合起来得到一个最终结果,与MapReduce不同,Stream则是利用多核技术可将大数据通过多核并行处理,而MapReduce则可以分布式的。
# stream与parallelStream性能测试对比
如果是多核机器,理论上并行流则会比顺序流快上一倍,下面是测试代码
long t0 = System.nanoTime();
//初始化一个范围100万整数流,求能被2整除的数字,toArray()是终点方法
int a[]=IntStream.range(0, 1_000_000).filter(p -> p % 2==0).toArray();
long t1 = System.nanoTime();
//和上面功能一样,这里是用并行流来计算
int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray();
long t2 = System.nanoTime();
//我本机的结果是serial: 0.06s, parallel 0.02s,证明并行流确实比顺序流快
System.out.printf("serial: %.2fs, parallel %.2fs%n", (t1 - t0) * 1e-9, (t2 - t1) * 1e-9);
# Stream中常用方法如下:
stream()
,parallelStream()
filter()
findAny()
findFirst()
sort
forEach
voidmap(), reduce()
flatMap()
- 将多个Stream连接成一个Streamcollect(Collectors.toList())
distinct
,limit
count
min
,max
,summaryStatistics
# 常用例子
# 匿名类简写
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();
// 用法
(params) -> expression
(params) -> statement
(params) -> { statements }
# forEach
// forEach
List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");
features.forEach(n -> System.out.println(n));
// 使用Java 8的方法引用更方便,方法引用由::双冒号操作符标示,
features.forEach(System.out::println);
# 方法引用
构造引用
// Supplier<Student> s = () -> new Student();
Supplier<Student> s = Student::new;
对象::实例方法 Lambda表达式的(形参列表)与实例方法的(实参列表)类型,个数是对应
// set.forEach(t -> System.out.println(t));
set.forEach(System.out::println);
类名::静态方法
// Stream<Double> stream = Stream.generate(() -> Math.random());
Stream<Double> stream = Stream.generate(Math::random);
类名::实例方法
// TreeSet<String> set = new TreeSet<>((s1,s2) -> s1.compareTo(s2));
/* 这里如果使用第一句话,编译器会有提示: Can be replaced with Comparator.naturalOrder,这句话告诉我们
String已经重写了compareTo()方法,在这里写是多此一举,这里为什么这么写,是因为为了体现下面
这句编译器的提示: Lambda can be replaced with method reference。好了,下面的这句就是改写成方法引用之后:
*/
TreeSet<String> set = new TreeSet<>(String::compareTo);
# Filter & Predicate
常规用法
public static void main(args[]){
List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp");
System.out.println("Languages which starts with J :");
filter(languages, (str)->str.startsWith("J"));
System.out.println("Languages which ends with a ");
filter(languages, (str)->str.endsWith("a"));
System.out.println("Print all languages :");
filter(languages, (str)->true);
System.out.println("Print no language : ");
filter(languages, (str)->false);
System.out.println("Print language whose length greater than 4:");
filter(languages, (str)->str.length() > 4);
}
public static void filter(List names, Predicate condition) {
names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {
System.out.println(name + " ");
});
}
多个Predicate组合filter
// 可以用and()、or()和xor()逻辑函数来合并Predicate,
// 例如要找到所有以J开始,长度为四个字母的名字,你可以合并两个Predicate并传入
Predicate<String> startsWithJ = (n) -> n.startsWith("J");
Predicate<String> fourLetterLong = (n) -> n.length() == 4;
names.stream()
.filter(startsWithJ.and(fourLetterLong))
.forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is : " + n));
# Map&Reduce
map将集合类(例如列表)元素进行转换的。还有一个 reduce() 函数可以将所有值合并成一个
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get();
System.out.println("Total : " + bill);
# Collectors
// 将字符串换成大写并用逗号链接起来
List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada");
String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
System.out.println(G7Countries);
- Collectors.joining(", ")
- Collectors.toList()
- Collectors.toSet() ,生成set集合
- Collectors.toMap(MemberModel::getUid, Function.identity())
- Collectors.toMap(ImageModel::getAid, o -> IMAGE_ADDRESS_PREFIX + o.getUrl())
# flatMap
将多个Stream连接成一个Stream
List<Integer> result= Stream.of(Arrays.asList(1,3),Arrays.asList(5,6)).flatMap(a->a.stream()).collect(Collectors.toList());
结果: [1, 3, 5, 6]
# distinct
去重
List<LikeDO> likeDOs=new ArrayList<LikeDO>();
List<Long> likeTidList = likeDOs.stream().map(LikeDO::getTid)
.distinct().collect(Collectors.toList());
# count
计总数
int countOfAdult=persons.stream()
.filter(p -> p.getAge() > 18)
.map(person -> new Adult(person))
.count();
# Match
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
# min,max,summaryStatistics
最小值,最大值
List<Person> lists = new ArrayList<Person>();
lists.add(new Person(1L, "p1"));
lists.add(new Person(2L, "p2"));
lists.add(new Person(3L, "p3"));
lists.add(new Person(4L, "p4"));
Person a = lists.stream().max(Comparator.comparing(t -> t.getId())).get();
System.out.println(a.getId());
如果比较器涉及多个条件,比较复杂,可以定制
Person a = lists.stream().min(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
if (o1.getId() > o2.getId()) return -1;
if (o1.getId() < o2.getId()) return 1;
return 0;
}
}).get();
summaryStatistics
//获取数字的个数、最小值、最大值、总和以及平均值
List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest prime number in List : " + stats.getMax());
System.out.println("Lowest prime number in List : " + stats.getMin());
System.out.println("Sum of all prime numbers : " + stats.getSum());
System.out.println("Average of all prime numbers : " + stats.getAverage());
# peek
可以使用peek方法,peek方法可只包含一个空的方法体,只要能设置断点即可,但有些IDE不允许空,可以如下文示例,简单写一个打印逻辑。
注意,调试完后要删掉。
List<Person> lists = new ArrayList<Person>();
lists.add(new Person(1L, "p1"));
lists.add(new Person(2L, "p2"));
lists.add(new Person(3L, "p3"));
lists.add(new Person(4L, "p4"));
System.out.println(lists);
List<Person> list2 = lists.stream()
.filter(f -> f.getName().startsWith("p"))
.peek(t -> {
System.out.println(t.getName());
})
.collect(Collectors.toList());
System.out.println(list2);
- 点赞
- 收藏
- 关注作者
评论(0)