总的来说,JDK在以下方面具有新特性:
1. 速度更快 – 红黑树
2. 代码更少 – Lambda
3. 强大的Stream API – Stream
4. 便于并行 – Parallel
5. 最大化减少空指针异常 – Optional
HashMap中链长度大于8时采取红黑树的结构存储。
红黑树,除了添加,效率高于链表结构。
Jdk1.7时隔壁级别CocnurrentLevel(锁分段机制)默认为16。
JDK1.8采取了CAS算法
Jdk1.8没有永久区,取而代之的是MetaSpace元空间,用的是物理内存。
1、Lambda表达式的基础语法:Java8引入了一个新的操作符“->”,该操作符成为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分
左侧:Lambda表达式的参数列表
右侧:Lambda表达式中所需执行的功能,即Lambda体。
语法格式一:无参数,无返回值
Runnable r2 = () -> System.out.println("hello lambda");
r2.run();
语法格式二:有一个参数,并且无返回值
(x) -> System.out.print(x);
语法格式三:若只有一个参数,小括号可以省略不写
x -> System.out.print(x);
语法格式四:有两个以上的参数,有返回值,并且Lambda体中有多条语句
Comparator c1 = (x, y) -> {
System.out.print(Integer.compare(x, y)+"函数式接口");
return Integer.compare(x, y);
} ;
c1.compare(1, 2);
语法格式五:若Lambda体中只有一条语句,return和大括号都可以省略不写
Comparator c1 = (x,y) -> Integer.compare(x,y);
语法格式六:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器可以通过上下文进行类型推断出数据类型,既“类型推断”。
(Integer x,Integer y) -> Integre.compare(x,y);
总结:
左右遇一括号省,左侧推断类型省, 能省则省。
2、Lambda表达式的函数式接口
一些基本的使用示例
List list = Arrays.asList(
new Employee("张三", "上海", 5000, 22),
new Employee("李四", "北京", 4000, 23),
new Employee("c五", "日本", 6000, 50),
new Employee("b七", "香港", 7000, 50),
new Employee("赵六", "纽约", 1000, 8)
);
/**
*需求1:lambda表达式的使用:
* 调用COllections.sort方法,通过定制排序比较两个Employee(先按年龄比较,年龄相同按姓名比),使用
* Lambda作为参数传递。
*/
@Test
public void test1(){
Collections.sort(list,(x,y)->{
if(x.getAge()!=y.getAge())
return Integer.compare(x.getAge(),y.getAge());
else
return x.getName().compareTo(y.getName());
});
for (Employee employee : list) {
System.out.println(employee);
}
}
/**
* 需求2:
* 1.声明函数式接口,接口中声明抽象方法,public String getvalue(String str();
* 2.声明类TestLambda,类中编写方法使用接口作为参数,讲一个字符串转换成大写,并作为方法的返回值。
*/
@Test
public void test2(){
String str = getvalue("hello world", x -> x.toUpperCase());
System.out.print(str);
}
public String getvalue(String str,MyFunction1 my){
return my.getValue(str);
}
@FunctionalInterface
public interface MyFunction1{
public String getValue(String str);
}
/**
* 需求3:
* 1.声明一个带两个泛型的函数式接口,泛型类型是,T为参数,R为返回值。
* 2.接口中声明对应抽象方法
* 3.在TestLambda类中声明方法,使用接口作为参数,计算两个long型参数的和
* 4.在计算两个long型参数的乘积
*/
@Test
public void test3(){
Long r = getR(25l,30l, (t1,t2) -> t1 * t2);
System.out.print(r);
Long r1 = getR(25l, 23l, (t1, t2) -> t1 + t2);
System.out.print(r1);
}
public R getR(T t1,T t2,MyFUnction2 mf){
return mf.method(t1,t2);
}
public interface MyFUnction2<T,R>{
public R method(T t1,T t2);
}
/*
Consumer :消费型接口
void accept(T t);
Supplier :供给型接口
T get();
Function :函数型接口
R apply(T t);
Predicate :断言型接口
boolean test(T t);
*/
Lambda表达式的
一、方法引用:
若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”(可以理解为方法引用是Lambda表达式的另外一种表现形式)
主要语法:
对象::实例方法名
类::静态方法名
类::实例方法名
注意:
①Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型一致。
②若Lambda参数列表中的第一参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method进行调用。
二、构造器引用
格式:
ClassName::new
注意:需要引用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。
三、数组引用
格式:
Type[]::new
一系列流水线式的中间操作。
流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
注意:
①Stream自己不会存储元素。
②Stream不会改变源对象。相反,会返回持有新结果的新Stream。
③Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
操作的三个步骤:
1、创建一个流Stream
2、中间操作
3、终止操作
创建:
@Test
public void test1(){
//1. 可以通过Collections系列集合提供的stream()或parallelStream()
List list = new ArrayList<>();
Stream stream = list.stream();
//2. 通过Arrays中的静态方法stream()获取数组流
Employee[] emps = new Employee[10];
Stream stream1 = Arrays.stream(emps);
//3. 通过Stream类中的静态方法of()
Stream stream3 = Stream.of("", "", "");
//4. 创建无限流
//迭代
Stream.iterate(0,x -> x+2).limit(10).forEach(System.out::println);
//生成
Stream generate = Stream.generate(() -> Math.random());
generate.limit(5).forEach(System.out::println);
}
中间操作:API
多个中间操作可以连接起来形成一个流水线,除非流水想上出发终止操作。否则中间操作不会执行任何的处理,而在终止操作时一次性全部处理,成为“惰性求值”。
筛选和切片
/**
* 筛选和切片
*
* filter -- 接受Lambda,从流中排除某些元素
* limit -- 截断流,使其元素不超过某个给定数量
* skip -- 跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit互补。
* distinct -- 去重,通过hashcode和equals去重。
*/
List list = Arrays.asList(
new Employee("张三", "上海", 5000, 22),
new Employee("李四", "北京", 4000, 23),
new Employee("c五", "日本", 6000, 50),
new Employee("b七", "香港", 7000, 50),
new Employee("赵六", "纽约", 1000, 8)
);
@Test
public void test1(){
Stream stream = list.stream();//创建流
stream
.filter(e -> e.getAge()>25)//过滤符合条件的流元素
.limit(5)//只取5个
.skip(4)//跳过4个
.distinct()//去重,需重写hashcode和equals方法
.forEach(System.out::println);//终止操作,获取流
}
映射
接受Lambda,将元素转换成其他形式或提取信息,接受一个函数作为参数, 该函数会被应用到每个元素上,并将其映射成一个新的元素。
public class StreamTest3 {
//中间操作
/**
* 映射
* map -- 接受Lambda,将元素转换成其他形式或提取信息,接受一个函数作为参数,
* 该函数会被应用到每个元素上,并将其映射成一个新的元素。
*
* flatmap -- 接受一个函数做为参数,将流中的每个值都转换成另一个流,然后将所有流连接成一个流,
*/
List emps = Arrays.asList(
new Employee("张三", "上海", 5000, 22),
new Employee("李四", "北京", 4000, 23),
new Employee("c五", "日本", 6000, 50),
new Employee("b七", "香港", 7000, 50),
new Employee("赵六", "纽约", 1000, 8)
);
@Test
public void test2(){
emps
.stream()//创建流
.map(employee -> employee.getName())//中间操作:映射
.forEach(System.out::println);//终止流
}
@Test
public void test3(){
List list = Arrays.asList("aaa","bbb","ccc","ddd");
Stream> streamStream = list
.stream()
.map(StreamTest3::getCharacter);//流中还是流
streamStream
.forEach(sm->sm
.forEach(System.out::println));
System.out.println("-------------------------------------");
list.stream()
.flatMap(StreamTest3::getCharacter)//大流中直接包含的是流元素,相当于add和addAll的区别。
.forEach(System.out::println);
}
public static Stream getCharacter(String str){
List list = new ArrayList<>();
for (char c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
}
public class StreamTest4 {
//中间操作
/**
* 排序
*
* sorted -- 自然排序(Comparable)
* sorted(Comparator com) -- 定制排序(Comparator)
*/
@Test
public void test1(){
List list =
Arrays.asList("ddd", "ccc", "ggg", "bbb", "aaa");
list.stream()
.sorted()//自然排序
.forEach(System.out::println);
System.out.println("------------------------------");
emps.stream()
.sorted((e1,e2) -> {//定制排序
if(e1.getSalary() == e2.getSalary())
return e1.getName().compareTo(e2.getName());
else
return e1.getSalary() - e2.getSalary();
}).forEach(System.out::println);
}
List emps = Arrays.asList(
new Employee("张三", "上海", 5000, 22),
new Employee("李四", "北京", 4000, 23),
new Employee("c五", "日本", 6000, 50),
new Employee("b七", "香港", 6000, 50),
new Employee("赵六", "纽约", 1000, 8)
);
}
Stream的终止操作
- allMatch – 检查是否匹配所有元素
- anyMatch – 检查是否至少匹配一个元素
- noneMatch – 检查是否没有匹配所有元素
- findFirst – 返回第一个元素
- count – 返回流中元素的总个数
- max – 返回流中最大值
- min – 返回流中最小值
@Test
public void test1(){
boolean b = emps.stream()
.allMatch(emp -> emp.getStatus().equals(Status.FREE));
System.out.println(b);
boolean b1 = emps.stream().
anyMatch(emp -> emp.getStatus().equals(Status.BUSY));
System.out.println(b1);
boolean b2 = emps.stream()
.noneMatch(emp -> emp.getSalary() > 16000);
System.out.println(b2);
Optional any = emps.parallelStream()//并行流
.findAny();
System.out.println(any.get());
}
List emps = Arrays.asList(
new Employee("张三",9000.00,25,Status.FREE),
new Employee("李四",16000.00,22,Status.BUSY),
new Employee("王五",15000.00,22,Status.FREE),
new Employee("赵六",12000.00,23,Status.VOCATION),
new Employee("孙七",8000.00,26,Status.FREE),
new Employee("唐八",19000.00,24,Status.BUSY)
);
终止操作:归约reduce
map和reduce的连接通常称为map-reduce模式,因google用它进行网络搜索而出名
/**
* 终止操作:
*
* 归约
* reduce(T identity, BinaryOperator) / reduce(BinaryOperator)
* -- 可以将流中的元素反复结合起来,得到一个值
*/
@Test
public void test1(){
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9);
Integer reduce = list.stream().reduce(0, (x, y) -> x + y);
System.out.println(reduce);
Optional reduce1 = emps.stream()
.map(Employee::getSalary)//现将salsry映射出来
.reduce(Double::sum);//进行归约求和
System.out.println(reduce1.get());
}
终止操作:收集Collect(很强大)
将流转换成其他格式,接受一个Collector接口的实现,用于给Stream中元素做汇总的操作。
Collector接口中方法的实现决定了如何对流进行收集操作(如收集到List、Set、Map)中,Collectors实用类提供了很多静态方法,可以方便的创建常用收集器实例,具体方法见API
/**
* 收集
* collect
* -- 将流转换成其他的形式,接收一个Collector接口的实现,可以通过Collectors的实用类操作
*/
@Test
public void test2(){
//收集姓名到列表
List collect = emps.stream()
.map(Employee::getName)
.collect(Collectors.toList());
collect.forEach(System.out::println);
System.out.println("-------------------------");
//收集姓名到set
Set collect1 = emps.stream().map(Employee::getName)
.collect(Collectors.toSet());
collect1.forEach(System.out::println);
System.out.println("--------------------------");
//收集姓名到指定的数据结构
LinkedHashSet collect2 = emps.stream().map(Employee::getName)
.collect(Collectors.toCollection(LinkedHashSet::new));
collect2.forEach(System.out::println);
}
@Test
public void test3(){
//总数
Long collect = emps.stream()
.collect(Collectors.counting());
System.out.println(collect);
System.out.println("---------------------------------");
//平均
Double collect1 = emps.stream().collect(Collectors.averagingDouble(Employee::getSalary));
System.out.println(collect1);
System.out.println("-------------------------------");
//总和
Double collect2 = emps.stream().collect(Collectors.summingDouble(Employee::getSalary));
System.out.println(collect2);
System.out.println("-----------------------");
//最大值
Optional collect3 = emps.stream().collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));
System.out.println(collect3);
System.out.println("-----------------------");
}
//分组
@Test
public void test4(){
//单级分组
Map> collect = emps.stream().collect(Collectors.groupingBy(Employee::getStatus));
System.out.println(collect);
System.out.println("----------------------");
//多级分组
Map>> collect1 = emps.stream().collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {
if (e.getAge() < 20)
return "少年";
else if (e.getAge() < 30)
return "青年";
else
return "中年";
})));
System.out.println(collect1);
System.out.println("----------------------");
//分区--满足条件一个区,不满足另一个区
Map> collect2 = emps.stream().collect(Collectors.partitioningBy(e -> e.getSalary() > 8000));
System.out.println(collect2);
System.out.println("-----------------------");
//收集各种统计数据
DoubleSummaryStatistics collect3 = emps.stream().collect(Collectors.summarizingDouble(Employee::getSalary));
System.out.println(collect3+"-----------平均薪水"+collect3.getAverage());
//连接字符串
String collect4 = emps.stream().map(Employee::getName).collect(Collectors.joining(",", "-----", "-----"));
System.out.println(collect4);
}
一些练习:
@Test
public void test1(){
/**
* 需求1:
*给定一个数字,如何返回一个有每个数的平方构成的列表?
* 给定【1,2,3,4,5】,返回【1,4,9,16,25】
*/
List list = Arrays.asList(1,2,3,4,5);
List collect = list.stream().map(integer -> integer * integer).collect((Collectors.toList()));
System.out.println(collect);
/**
* 需求2:
* 用reduce和map数一数流中的元素个数
*/
Optional reduce = list.stream()
.map(e -> 1)//巧妙之处
.reduce(Integer::sum);
System.out.println(reduce);
}
Fork/Join框架:
在必要的情况下,将一个大任务进行必要的拆分Fork成若干个小任务,再将小任务的运算结果进行Join汇总。
Fork/Join框架和传统线程池的区别:
采用“工作窃取”模式(Working-stealing),即当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。
相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态,而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程等待的时间,提高了性能。
并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过parallel() 与sequential() 在并行流与顺序流之间进行切换。
/**
* 方式1:
* 自己实现的ForkJoin
*/
@Test
public void test1(){
Instant start = Instant.now();
ForkJoinPool pool = new ForkJoinPool();
ForkJoinTask task = new ForkJoinCalculate(0, 100000000L);
Long sum = pool.invoke(task);
Instant end = Instant.now();
System.out.println(Duration.between(start,end).toMillis());
}
public class ForkJoinCalculate extends RecursiveTask<Long> {
private long start;
private long end;
private static final long THRESHHOLD = 10000;
public ForkJoinCalculate(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
long length = end - start;
if(length <= THRESHHOLD){
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
}else{
long middle = (start + end) / 2;
ForkJoinCalculate left = new ForkJoinCalculate(start, middle);
left.fork();
ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);
right.fork();
return left.join()+right.join();
}
}
}
/**
* 方式2:
* 直接使用for循环
*/
@Test
public void test2(){
Instant start = Instant.now();
Long sum = 0L;
for (int i = 0; i <= 100000000L; i++) {
sum += i;
}
Instant end = Instant.now();
System.out.println(Duration.between(start,end).toMillis());
}
/**
* 方式3:
* JDK8的并行流实现。
*/
@Test
public void test3(){
Instant start = Instant.now();
long sum = LongStream.rangeClosed(0, 1000000000L)
.parallel()//并行流
//.sequential()//串行流
.reduce(0, Long::sum);
Instant end = Instant.now();
System.out.println(Duration.between(start,end).toMillis());
}
//并行流将会充分使用多核的优势,多线程并行执行,基数越大,效果越明显。其底层还是Fork/Join框架。只不过SUN公司优化的更好,比自己实现更高效
Optional
是一个容器类,代表一个值存在或不存在,原来用null 表示一个值不存在,现在Optional 可以更好的表达这个概念。并且可以避免空指针异常。
常用方法:
- Optional.of(T t)
: 创建一个Optional 实例
- Optional.empty()
: 创建一个空的Optional 实例
- Optional.ofNullable(T t)
:若t 不为null,创建Optional 实例,否则创建空实例
- isPresent()
: 判断是否包含值
- orElse(T t)
: 如果调用对象包含值,返回该值,否则返回t
- orElseGet(Supplier s)
:如果调用对象包含值,返回该值,否则返回s 获取的值
- map(Function f)
: 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
- flatMap(Function mapper)
:与map 类似,要求返回值必须是Optional
public class OptinalTest1 {
/**
* 常用方法:
Optional.of(T t) : 创建一个Optional 实例
Optional.empty() : 创建一个空的Optional 实例
Optional.ofNullable(T t):若t 不为null,创建Optional 实例,否则创建空实例
isPresent() : 判断是否包含值
orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
flatMap(Function mapper):与map 类似,要求返回值必须是Optional
*/
//flatMap
@Test
public void test6(){
// Optional op = Optional.ofNullable(new Employee("jim","shanghai",9000));
Optional op = Optional.ofNullable(null);
//flatMap,返回的必须是Optional容器,进一步避免空指针异常
Optional optional = op.flatMap(e -> Optional.of(e.getSalary()));
System.out.println(optional.orElse(10000));
}
//map
@Test
public void test5(){
// Optional op = Optional.ofNullable(new Employee("jim","shanghai",9000));
Optional op = Optional.ofNullable(null);
//有值返回map的操作,没值返回Optional.empty()
Optional salary = op.map(Employee::getSalary);
System.out.println(salary.get());//如果传入为空,此时会报错
}
//ORelse
//orElseGet
@Test
public void test4(){
Optional