发行版本 |
发行时间 |
备注 |
Java 1.0 |
1996.01.23 |
Sun公司发布了Java的第一个开发工具包 |
Java 5.0 |
2004.09.30 |
①版本号从1.4直接更新至5.0;②平台更名为JavaSE、JavaEE、JavaME |
Java 8.0 |
2014.03.18 |
此版本是继Java 5.0以来变化最大的版本。是长期支持版本(LTS) |
Java 9.0 |
2017.09.22 |
此版本开始,每半年更新一次 |
Java 10.0 |
2018.03.21 |
|
Java 11.0 |
2018.09.25 |
JDK安装包取消独立JRE安装包,是长期支持版本(LTS) |
Java 12.0 |
2019.03.19 |
|
... |
... |
|
Java17.0 |
2021.09 |
发布Java 17.0,版本号也称为21.9,是长期支持版本(LTS) |
... |
... |
|
Java19.0 |
2022.09 |
发布Java19.0,版本号也称为22.9。 |
从Java 9 这个版本开始,Java 的计划发布周期是 6个月。
这意味着Java的更新从传统的以特性驱动的发布周期,转变为以时间驱动的发布模式,并且承诺不会跳票。通过这样的方式,开发团队可以把一些关键特性尽早合并到 JDK 之中,以快速得到开发者反馈,在一定程度上避免出现像 Java 9 两次被迫延迟发布的窘况。
针对企业客户的需求,Oracle 将以三年为周期发布长期支持版本(long term support)。
Oracle 的官方观点认为:与 Java 7->8->9 相比,Java 9->10->11的升级和 8->8u20->8u40 更相似。
新模式下的 Java 版本发布都会包含许多变更,包括语言变更和 JVM 变更,这两者都会对 IDE、字节码库和框架产生重大影响。此外,不仅会新增其他 API,还会有 API被删除(这在 Java 8 之前没有发生过)。
目前看这种发布策略是非常成功的,解开了 Java/JVM 演进的许多枷锁,至关重要的是,OpenJDK 的权力中心,正在转移到开发社区和开发者手中。在新的模式中,既可以利用 LTS 满足企业长期可靠支持的需求,也可以满足各种开发者对于新特性迭代的诉求。因为用 2-3 年的最小间隔粒度来试验一个特性,基本是不现实的。
名词解释:Oracle JDK和Open JDK
这两个JDK最大不同就是许可证不一样。但是对于个人用户来讲,没区别。
Oracle JDK |
Open JDK |
|
来源 |
Oracle团队维护 |
Oracle和Open Java社区 |
授权协议 |
Java 17及更高版本 Oracle Java SE 许可证Java16及更低版本甲骨文免费条款和条件 (NFTC) 许可协议 |
GPL v2许可证 |
关系 |
由Open JDK构建,增加了少许内容 |
|
是否收费 |
2021年9月起Java17及更高版本所有用户免费。 16及更低版本,个人用户、开发用户免费 |
2017年9月起,所有版本免费 |
对语法的支持 |
一致 |
一致 |
名词解释:JEP
JEP(JDK Enhancement Proposals):jdk 改进提案,每当需要有新的设想时候,JEP可以提出非正式的规范(specification),被正式认可的JEP正式写进JDK的发展路线图并分配版本号。
名词解释:LTS
LTS(Long-term Support)即长期支持。Oracle官网提供了对Oracle JDK个别版本的长期支持,即使发发行了新版本,比如目前最新的JDK19,在结束日期前,LTS版本都会被长期支持。(出了bug,会被修复,非LTS则不会再有补丁发布)所以,一定要选一个LTS版本,不然出了漏洞没人修复了。
版本 |
开始日期 |
结束日期 |
延期结束日期 |
7(LTS) |
2011年7月 |
2019年7月 |
2022年7月 |
8(LTS) |
2014年3月 |
2022年3月 |
2030年12月 |
11(LTS) |
2018年9月 |
2023年9月 |
2026年9月 |
17(LTS) |
2021年9月 |
2026年9月 |
2029年9月 |
21(LTS) |
2023年9月 |
2028年9月 |
2031年9月 |
如果要选择Oracle JDK,目前可选的LTS版本为8、11、17三个。
Java 8 (又称为 JDK 8或JDK1.8) 是 Java 语言开发的一个主要版本。 Java 8 是oracle公司于2014年3月发布,可以看成是自Java 5 以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。
速度更快
代码更少(增加了新的语法:Lambda表达式)
强大的 Stream API
便于并行
最大化减少空指针异常:Optional
Nashorn引擎,允许在JVM上运行JS应用
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable
接口来定义任务内容,并使用java.lang.Thread
类来启动该线程。代码如下:
package com.suyv.fp;
public class UseFunctionalProgramming {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程任务执行!");
}
}).start(); // 启动线程
}
}
本着“一切皆对象”的思想,这种做法是无可厚非的:首先创建一个Runnable接口的匿名内部类对象来指定任务内容,再将其交给一个线程来启动。
代码分析:
对于Runnable的匿名内部类用法,可以分析出几点内容:
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
package com.suyv.lambda;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2024-01-01 11:51
* @Description: 从匿名类到 Lambda 的转换举例1
*/
public class Demo01 {
public static void main(String[] args) {
// 匿名内部类
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
};
// Lambda表达式
Runnable r2 = () -> System.out.println("Hello World");
}
}
package com.suyv.lambda;
import java.util.Comparator;
import java.util.TreeSet;
/**
* @Author: 憨憨浩浩
* @CreateTime: 2024-01-01 11:54
* @Description: 从匿名类到 Lambda 的转换举例2
*/
public class Demo02 {
public static void main(String[] args) {
// 使用匿名内部类作为参数传递
TreeSet tree = new TreeSet<>(new Comparator() {
@Override
public int compare(String o1, String o2) {
return Integer.compare(o1.length(),o2.length());
}
});
// 使用Lombda表达式作为参数传递
TreeSet tree1 = new TreeSet<>(
(o1,o2) -> Integer.compare(o1.length(),o2.length())
);
}
}
Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
语法格式一:无参,无返回值
@Test
public void test1(){
//未使用Lambda表达式
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("我爱北京天安门");
}
};
r1.run();
System.out.println("***********************");
//使用Lambda表达式
Runnable r2 = () -> {
System.out.println("我爱北京故宫");
};
r2.run();
}
语法格式二:Lambda 需要一个参数,但是没有返回值。
@Test
public void test2(){
//未使用Lambda表达式
Consumer con = new Consumer() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("谎言和誓言的区别是什么?");
System.out.println("*******************");
//使用Lambda表达式
Consumer con1 = (String s) -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");
}
语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”
@Test
public void test3(){
//语法格式三使用前
Consumer con1 = (String s) -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");
System.out.println("*******************");
//语法格式三使用后
Consumer con2 = (s) -> {
System.out.println(s);
};
con2.accept("一个是听得人当真了,一个是说的人当真了");
}
语法格式四:Lambda 若只需要一个参数时,参数的小括号可以省略
@Test
public void test4(){
//语法格式四使用前
Consumer con1 = (s) -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");
System.out.println("*******************");
//语法格式四使用后
Consumer con2 = s -> {
System.out.println(s);
};
con2.accept("一个是听得人当真了,一个是说的人当真了");
}
语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值
@Test
public void test5(){
//语法格式五使用前
Comparator com1 = new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
}
};
System.out.println(com1.compare(12,21));
System.out.println("*****************************");
//语法格式五使用后
Comparator com2 = (o1,o2) -> {
System.out.println(o1);
System.out.println(o2);
return o1.compareTo(o2);
};
System.out.println(com2.compare(12,6));
}
语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略
@Test
public void test6(){
//语法格式六使用前
Comparator com1 = (o1,o2) -> {
return o1.compareTo(o2);
};
System.out.println(com1.compare(12,6));
System.out.println("*****************************");
//语法格式六使用后
Comparator com2 = (o1,o2) -> o1.compareTo(o2);
System.out.println(com2.compare(12,21));
}
@Test
public void test7(){
//语法格式六使用前
Consumer con1 = s -> {
System.out.println(s);
};
con1.accept("一个是听得人当真了,一个是说的人当真了");
System.out.println("*****************************");
//语法格式六使用后
Consumer con2 = s -> System.out.println(s);
con2.accept("一个是听得人当真了,一个是说的人当真了");
}
在语法格式三 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。
举例:
@Test
public void test() {
//类型推断1
ArrayList list = new ArrayList<>();
//类型推断2
int[] arr = {1, 2, 3};
}
java.util.function
包下定义了Java 8 的丰富的函数式接口Java从诞生日起就是一直倡导“一切皆对象”,在Java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,Java不得不做出调整以便支持更加广泛的技术要求,即Java不但可以支持OOP还可以支持OOF(面向函数编程)
面向对象的思想:
函数式编程思想:
在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。
简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
举例1:
举例2:
作为参数传递 Lambda 表达式:
作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。
之前学过的接口,有些就是函数式接口,比如:
java.lang.Runnable
java.lang.Iterable
java.lang.Comparable
java.util.Comparator
函数式接口 |
称谓 |
参数类型 |
用途 |
Consumer |
消费型接口 |
T |
对类型为T的对象应用操作,包含方法: void accept(T t) |
Supplier |
供给型接口 |
无 |
返回类型为T的对象,包含方法:T get() |
Function |
函数型接口 |
T |
对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t) |
Predicate |
判断型接口 |
T |
确定类型为T的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t) |
类型1:消费型接口
消费型接口的抽象方法特点:有形参,但是返回值类型是void
接口名 |
抽象方法 |
描述 |
BiConsumer |
void accept(T t, U u) |
接收两个对象用于完成功能 |
DoubleConsumer |
void accept(double value) |
接收一个double值 |
IntConsumer |
void accept(int value) |
接收一个int值 |
LongConsumer |
void accept(long value) |
接收一个long值 |
ObjDoubleConsumer |
void accept(T t, double value) |
接收一个对象和一个double值 |
ObjIntConsumer |
void accept(T t, int value) |
接收一个对象和一个int值 |
ObjLongConsumer |
void accept(T t, long value) |
接收一个对象和一个long值 |
类型2:供给型接口
这类接口的抽象方法特点:无参,但是有返回值
接口名 |
抽象方法 |
描述 |
BooleanSupplier |
boolean getAsBoolean() |
返回一个boolean值 |
DoubleSupplier |
double getAsDouble() |
返回一个double值 |
IntSupplier |
int getAsInt() |
返回一个int值 |
LongSupplier |
long getAsLong() |
返回一个long值 |
类型3:函数型接口
这类接口的抽象方法特点:既有参数又有返回值
接口名 |
抽象方法 |
描述 |
UnaryOperator |
T apply(T t) |
接收一个T类型对象,返回一个T类型对象结果 |
DoubleFunction |
R apply(double value) |
接收一个double值,返回一个R类型对象 |
IntFunction |
R apply(int value) |
接收一个int值,返回一个R类型对象 |
LongFunction |
R apply(long value) |
接收一个long值,返回一个R类型对象 |
ToDoubleFunction |
double applyAsDouble(T value) |
接收一个T类型对象,返回一个double |
ToIntFunction |
int applyAsInt(T value) |
接收一个T类型对象,返回一个int |
ToLongFunction |
long applyAsLong(T value) |
接收一个T类型对象,返回一个long |
DoubleToIntFunction |
int applyAsInt(double value) |
接收一个double值,返回一个int结果 |
DoubleToLongFunction |
long applyAsLong(double value) |
接收一个double值,返回一个long结果 |
IntToDoubleFunction |
double applyAsDouble(int value) |
接收一个int值,返回一个double结果 |
IntToLongFunction |
long applyAsLong(int value) |
接收一个int值,返回一个long结果 |
LongToDoubleFunction |
double applyAsDouble(long value) |
接收一个long值,返回一个double结果 |
LongToIntFunction |
int applyAsInt(long value) |
接收一个long值,返回一个int结果 |
DoubleUnaryOperator |
double applyAsDouble(double operand) |
接收一个double值,返回一个double |
IntUnaryOperator |
int applyAsInt(int operand) |
接收一个int值,返回一个int结果 |
LongUnaryOperator |
long applyAsLong(long operand) |
接收一个long值,返回一个long结果 |
BiFunction |
R apply(T t, U u) |
接收一个T类型和一个U类型对象,返回一个R类型对象结果 |
BinaryOperator |
T apply(T t, T u) |
接收两个T类型对象,返回一个T类型对象结果 |
ToDoubleBiFunction |
double applyAsDouble(T t, U u) |
接收一个T类型和一个U类型对象,返回一个double |
ToIntBiFunction |
int applyAsInt(T t, U u) |
接收一个T类型和一个U类型对象,返回一个int |
ToLongBiFunction |
long applyAsLong(T t, U u) |
接收一个T类型和一个U类型对象,返回一个long |
DoubleBinaryOperator |
double applyAsDouble(double left, double right) |
接收两个double值,返回一个double结果 |
IntBinaryOperator |
int applyAsInt(int left, int right) |
接收两个int值,返回一个int结果 |
LongBinaryOperator |
long applyAsLong(long left, long right) |
接收两个long值,返回一个long结果 |
类型4:判断型接口
这类接口的抽象方法特点:有参,但是返回值类型是boolean结果。
接口名 |
抽象方法 |
描述 |
BiPredicate |
boolean test(T t, U u) |
接收两个对象 |
DoublePredicate |
boolean test(double value) |
接收一个double值 |
IntPredicate |
boolean test(int value) |
接收一个int值 |
LongPredicate |
boolean test(long value) |
接收一个long值 |
举例1:
package com.suyv.four;
import java.util.Arrays;
import java.util.List;
public class TestConsumer {
public static void main(String[] args) {
List list = Arrays.asList("java","c","python","c++","VB","C#");
//遍历Collection集合,并将传递给action参数的操作代码应用在每一个元素上。
list.forEach(s -> System.out.println(s));
}
}
举例2:
package com.suyv.four;
import java.util.function.Supplier;
public class TestSupplier {
public static void main(String[] args) {
Supplier supplier = () -> "Java";
System.out.println(supplier.get());
}
}
举例3:
package com.suyv.four;
import java.util.ArrayList;
public class TestPredicate {
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
list.add("hello");
list.add("java");
list.add("atguigu");
list.add("ok");
list.add("yes");
System.out.println("删除之前:");
list.forEach(t-> System.out.println(t));
//用于删除集合中满足filter指定的条件判断的。
//删除包含o字母的元素
list.removeIf(s -> s.contains("o"));
System.out.println("删除包含o字母的元素之后:");
list.forEach(t-> System.out.println(t));
}
}
举例4:
package com.suyv.four;
import java.util.function.Function;
public class TestFunction {
public static void main(String[] args) {
//使用Lambda表达式实现Function接口,可以实现将一个字符串首字母转为大写的功能。
Function fun = s -> s.substring(0,1).toUpperCase() + s.substring(1);
System.out.println(fun.apply("hello"));
}
}
Lambda表达式是可以简化函数式接口的变量或形参赋值的语法。而方法引用和构造器引用是为了简化Lambda表达式的。
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
方法引用可以看做是Lambda表达式深层次的表达。换句话说,方法引用就是Lambda表达式,也就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
格式:使用方法引用操作符 “::” 将类(或对象) 与 方法名分隔开来。
如下三种主要使用情况:
要求1:Lambda体只有一句语句,并且是通过调用一个对象的/类现有的方法来完成的
例如: System.out对象,调用println()方法来完成Lambda体
Math类,调用random()静态方法来完成Lambda体
要求2:
针对情况1:函数式接口中的抽象方法a在被重写时使用了某一个对象的方法b。如果方法a的形参列表、返回值类型与方法b的形参列表、返回值类型都相同,则我们可以使用方法b实现对方法a的重写、替换。
针对情况2:函数式接口中的抽象方法a在被重写时使用了某一个类的静态方法b。如果方法a的形参列表、返回值类型与方法b的形参列表、返回值类型都相同,则我们可以使用方法b实现对方法a的重写、替换。
针对情况3:函数式接口中的抽象方法a在被重写时使用了某一个对象的方法b。如果方法a的返回值类型与方法b的返回值类型相同,同时方法a的形参列表中有n个参数,方法b的形参列表有n-1个参数,且方法a的第1个参数作为方法b的调用者,且方法a的后n-1参数与方法b的n-1参数匹配(类型相同或满足多态场景也可以)
例如:t->System.out.println(t)
() -> Math.random() 都是无参
public class MethodRefTest {
// 情况一:对象 :: 实例方法
//Consumer中的void accept(T t)
//PrintStream中的void println(T t)
@Test
public void test1() {
Consumer con1 = str -> System.out.println(str);
con1.accept("北京");
System.out.println("*******************");
Consumer con2 = System.out::println;
con2.accept("beijing");
}
//Supplier中的T get()
//Employee中的String getName()
@Test
public void test2() {
Employee emp = new Employee(1001,"Tom",23,5600);
Supplier sup1 = () -> emp.getName();
System.out.println(sup1.get());
System.out.println("*******************");
Supplier sup2 = emp::getName;
System.out.println(sup2.get());
}
// 情况二:类 :: 静态方法
//Comparator中的int compare(T t1,T t2)
//Integer中的int compare(T t1,T t2)
@Test
public void test3() {
Comparator com1 = (t1,t2) -> Integer.compare(t1,t2);
System.out.println(com1.compare(12,21));
System.out.println("*******************");
Comparator com2 = Integer::compare;
System.out.println(com2.compare(12,3));
}
//Function中的R apply(T t)
//Math中的Long round(Double d)
@Test
public void test4() {
Function func = new Function() {
@Override
public Long apply(Double d) {
return Math.round(d);
}
};
System.out.println("*******************");
Function func1 = d -> Math.round(d);
System.out.println(func1.apply(12.3));
System.out.println("*******************");
Function func2 = Math::round;
System.out.println(func2.apply(12.6));
}
// 情况三:类 :: 实例方法 (有难度)
// Comparator中的int comapre(T t1,T t2)
// String中的int t1.compareTo(t2)
@Test
public void test5() {
Comparator com1 = (s1,s2) -> s1.compareTo(s2);
System.out.println(com1.compare("abc","abd"));
System.out.println("*******************");
Comparator com2 = String :: compareTo;
System.out.println(com2.compare("abd","abm"));
}
//BiPredicate中的boolean test(T t1, T t2);
//String中的boolean t1.equals(t2)
@Test
public void test6() {
BiPredicate pre1 = (s1,s2) -> s1.equals(s2);
System.out.println(pre1.test("abc","abc"));
System.out.println("*******************");
BiPredicate pre2 = String :: equals;
System.out.println(pre2.test("abc","abd"));
}
// Function中的R apply(T t)
// Employee中的String getName();
@Test
public void test7() {
Employee employee = new Employee(1001, "Jerry", 23, 6000);
Function func1 = e -> e.getName();
System.out.println(func1.apply(employee));
System.out.println("*******************");
Function func2 = Employee::getName;
System.out.println(func2.apply(employee));
}
}
当Lambda表达式是创建一个对象,并且满足Lambda表达式形参,正好是给创建这个对象的构造器的实参列表,就可以使用构造器引用。
格式:类名::new
举例:
public class ConstructorRefTest {
//构造器引用
//Supplier中的T get()
//Employee的空参构造器:Employee()
@Test
public void test1(){
Supplier sup = new Supplier() {
@Override
public Employee get() {
return new Employee();
}
};
System.out.println("*******************");
Supplier sup1 = () -> new Employee();
System.out.println(sup1.get());
System.out.println("*******************");
Supplier sup2 = Employee :: new;
System.out.println(sup2.get());
}
//Function中的R apply(T t)
@Test
public void test2(){
Function func1 = id -> new Employee(id);
Employee employee = func1.apply(1001);
System.out.println(employee);
System.out.println("*******************");
Function func2 = Employee :: new;
Employee employee1 = func2.apply(1002);
System.out.println(employee1);
}
//BiFunction中的R apply(T t,U u)
@Test
public void test3(){
BiFunction func1 = (id,name) -> new Employee(id,name);
System.out.println(func1.apply(1001,"Tom"));
System.out.println("*******************");
BiFunction func2 = Employee :: new;
System.out.println(func2.apply(1002,"Tom"));
}
}
package com.suyv.java2;
public class Employee {
private int id;
private String name;
private int age;
private double salary;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Employee() {
System.out.println("Employee().....");
}
public Employee(int id) {
this.id = id;
System.out.println("Employee(int id).....");
}
public Employee(int id, String name) {
this.id = id;
this.name = name;
}
public Employee(int id, String name, int age, double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", age=" + age + ", salary=" + salary + '}';
}
}
当Lambda表达式是创建一个数组对象,并且满足Lambda表达式形参,正好是给创建这个数组对象的长度,就可以数组构造引用。
格式:数组类型名::new
举例:
//数组引用
//Function中的R apply(T t)
@Test
public void test4(){
Function func1 = length -> new String[length];
String[] arr1 = func1.apply(5);
System.out.println(Arrays.toString(arr1));
System.out.println("*******************");
Function func2 = String[] :: new;
String[] arr2 = func2.apply(10);
System.out.println(Arrays.toString(arr2));
}
Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API。
Stream API ( java.util.stream
) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
实际开发中,项目中多数数据源都来自于MySQL、Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要Java层面去处理。
Stream 是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,讲的是数据,而 Stream 是有关计算的,讲的是计算。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,通过 CPU 实现计算。
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。即一旦执行终止操作,就执行中间操作链,并产生结果。
④ Stream一旦执行了终止操作,就不能再调用其它中间操作或终止操作了。
1- 创建 Stream
一个数据源(如:集合、数组),获取一个流
2- 中间操作
每次处理都会返回一个持有结果的新Stream,即中间操作的方法返回值仍然是Stream类型的对象。因此中间操作可以是个操作链,可对数据源的数据进行n次处理,但是在终结操作前,并不会真正执行。
3- 终止操作(终端操作)
终止操作的方法返回值类型就不再是Stream了,因此一旦执行终止操作,就结束整个Stream操作了。一旦执行终止操作,就执行中间操作链,最终产生结果并结束Stream。
方式一:通过集合
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
@Test
public void test01(){
List list = Arrays.asList(1,2,3,4,5);
//JDK1.8中,Collection系列集合增加了方法
Stream stream = list.stream();
}
方式二:通过数组
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
@Test
public void test02(){
String[] arr = {"hello","world"};
Stream stream = Arrays.stream(arr);
}
@Test
public void test03(){
int[] arr = {1,2,3,4,5};
IntStream stream = Arrays.stream(arr);
}
方式三:通过Stream的of()
可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。
@Test
public void test04(){
Stream stream = Stream.of(1,2,3,4,5);
stream.forEach(System.out::println);
}
方式四:创建无限流(了解)
可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
// 方式四:创建无限流
@Test
public void test05() {
// 迭代
// public static Stream iterate(final T seed, final
// UnaryOperator f)
Stream stream = Stream.iterate(0, x -> x + 2);
stream.limit(10).forEach(System.out::println);
// 生成
// public static Stream generate(Supplier s)
Stream stream1 = Stream.generate(Math::random);
stream1.limit(10).forEach(System.out::println);
}
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
1-筛选与切片
方 法 |
描 述 |
filter(Predicatep) |
接收 Lambda , 从流中排除某些元素 |
distinct() |
筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
limit(long maxSize) |
截断流,使其元素不超过给定数量 |
skip(long n) |
跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 |
2-映 射
方法 |
描述 |
map(Function f) |
接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) |
接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
mapToInt(ToIntFunction f) |
接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
mapToLong(ToLongFunction f) |
接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
flatMap(Function f) |
接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
3-排序
方法 |
描述 |
sorted() |
产生一个新流,其中按自然顺序排序 |
sorted(Comparatorcom) |
产生一个新流,其中按比较器顺序排序 |
代码举例:
package com.suyv.stream;
import org.junit.Test;
import java.util.Arrays;
import java.util.stream.Stream;
public class StreamMiddleOperate {
@Test
public void test01(){
//1、创建Stream
Stream stream = Stream.of(1,2,3,4,5,6);
//2、加工处理
//过滤:filter(Predicate p)
//把里面的偶数拿出来
/*
* filter(Predicate p)
* Predicate是函数式接口,抽象方法:boolean test(T t)
*/
stream = stream.filter(t -> t%2==0);
//3、终结操作:例如:遍历
stream.forEach(System.out::println);
}
@Test
public void test02(){
Stream.of(1,2,3,4,5,6)
.filter(t -> t%2==0)
.forEach(System.out::println);
}
@Test
public void test03(){
Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
.distinct()
.forEach(System.out::println);
}
@Test
public void test04(){
Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
.limit(3)
.forEach(System.out::println);
}
@Test
public void test05(){
Stream.of(1,2,2,3,3,4,4,5,2,3,4,5,6,7)
.distinct() //(1,2,3,4,5,6,7)
.filter(t -> t%2!=0) //(1,3,5,7)
.limit(3)
.forEach(System.out::println);
}
@Test
public void test06(){
Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
.skip(5)
.forEach(System.out::println);
}
@Test
public void test07(){
Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
.skip(5)
.distinct()
.filter(t -> t%3==0)
.forEach(System.out::println);
}
@Test
public void test08(){
long count = Stream.of(1,2,3,4,5,6,2,2,3,3,4,4,5)
.distinct()
.peek(System.out::println) //Consumer接口的抽象方法 void accept(T t)
.count();
System.out.println("count="+count);
}
@Test
public void test09(){
//希望能够找出前三个最大值,前三名最大的,不重复
Stream.of(11,2,39,4,54,6,2,22,3,3,4,54,54)
.distinct()
.sorted((t1,t2) -> -Integer.compare(t1, t2))//Comparator接口 int compare(T t1, T t2)
.limit(3)
.forEach(System.out::println);
}
@Test
public void test10(){
Stream.of(1,2,3,4,5)
.map(t -> t+=1)//Function接口抽象方法 R apply(T t)
.forEach(System.out::println);
}
@Test
public void test11(){
String[] arr = {"hello","world","java"};
Arrays.stream(arr)
.map(t->t.toUpperCase())
.forEach(System.out::println);
}
@Test
public void test12(){
String[] arr = {"hello","world","java"};
Arrays.stream(arr)
.flatMap(t -> Stream.of(t.split("|")))//Function接口抽象方法 R apply(T t) 现在的R是一个Stream
.forEach(System.out::println);
}
}
1-匹配与查找
方法 |
描述 |
allMatch(Predicate p) |
检查是否匹配所有元素 |
anyMatch(Predicate p) |
检查是否至少匹配一个元素 |
noneMatch(Predicatep) |
检查是否没有匹配所有元素 |
findFirst() |
返回第一个元素 |
findAny() |
返回当前流中的任意元素 |
count() |
返回流中元素总数 |
max(Comparator c) |
返回流中最大值 |
min(Comparator c) |
返回流中最小值 |
forEach(Consumer c) |
内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了) |
2-归约
方法 |
描述 |
reduce(T identity, BinaryOperator b) |
可以将流中元素反复结合起来,得到一个值。返回 T |
reduce(BinaryOperator b) |
可以将流中元素反复结合起来,得到一个值。返回 Optional |
备注:map 和 reduce 的连接通常称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。
3-收集
方 法 |
描 述 |
collect(Collector c) |
将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector 接口中方法的实现决定了如何对流执行收集的操作(如收集到 List、Set、Map)。
另外, Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
方法 |
返回类型 |
作用 |
toList |
Collector |
把流中元素收集到List |
List
方法 |
返回类型 |
作用 |
toSet |
Collector |
把流中元素收集到Set |
Set
方法 |
返回类型 |
作用 |
toCollection |
Collector |
把流中元素收集到创建的集合 |
Collection
方法 |
返回类型 |
作用 |
counting |
Collector |
计算流中元素的个数 |
long count = list.stream().collect(Collectors.counting());
方法 |
返回类型 |
作用 |
summingInt |
Collector |
对流中元素的整数属性求和 |
int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
方法 |
返回类型 |
作用 |
averagingInt |
Collector |
计算流中元素Integer属性的平均值 |
double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));
方法 |
返回类型 |
作用 |
summarizingInt |
Collector |
收集流中Integer属性的统计值。如:平均值 |
int SummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
方法 |
返回类型 |
作用 |
joining |
Collector |
连接流中每个字符串 |
String str= list.stream().map(Employee::getName).collect(Collectors.joining());
方法 |
返回类型 |
作用 |
maxBy |
Collector |
根据比较器选择最大值 |
Optional
方法 |
返回类型 |
作用 |
minBy |
Collector |
根据比较器选择最小值 |
Optional
方法 |
返回类型 |
作用 |
reducing |
Collector |
从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值 |
int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
方法 |
返回类型 |
作用 |
collectingAndThen |
Collector |
包裹另一个收集器,对其结果转换函数 |
int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
方法 |
返回类型 |
作用 |
groupingBy |
Collector |
根据某属性值对流分组,属性为K,结果为V |
Map
方法 |
返回类型 |
作用 |
partitioningBy |
Collector |
根据true或false进行分区 |
Map
举例:
package com.suyv.stream;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
public class StreamEndding {
@Test
public void test01(){
Stream.of(1,2,3,4,5)
.forEach(System.out::println);
}
@Test
public void test02(){
long count = Stream.of(1,2,3,4,5)
.count();
System.out.println("count = " + count);
}
@Test
public void test03(){
boolean result = Stream.of(1,3,5,7,9)
.allMatch(t -> t%2!=0);
System.out.println(result);
}
@Test
public void test04(){
boolean result = Stream.of(1,3,5,7,9)
.anyMatch(t -> t%2==0);
System.out.println(result);
}
@Test
public void test05(){
Optional opt = Stream.of(1,3,5,7,9).findFirst();
System.out.println(opt);
}
@Test
public void test06(){
Optional opt = Stream.of(1,2,3,4,5,7,9)
.filter(t -> t%3==0)
.findFirst();
System.out.println(opt);
}
@Test
public void test07(){
Optional opt = Stream.of(1,2,4,5,7,8)
.filter(t -> t%3==0)
.findFirst();
System.out.println(opt);
}
@Test
public void test08(){
Optional max = Stream.of(1,2,4,5,7,8)
.max((t1,t2) -> Integer.compare(t1, t2));
System.out.println(max);
}
@Test
public void test09(){
Integer reduce = Stream.of(1,2,4,5,7,8)
.reduce(0, (t1,t2) -> t1+t2);//BinaryOperator接口 T apply(T t1, T t2)
System.out.println(reduce);
}
@Test
public void test10(){
Optional max = Stream.of(1,2,4,5,7,8)
.reduce((t1,t2) -> t1>t2?t1:t2);//BinaryOperator接口 T apply(T t1, T t2)
System.out.println(max);
}
@Test
public void test11(){
List list = Stream.of(1,2,4,5,7,8)
.filter(t -> t%2==0)
.collect(Collectors.toList());
System.out.println(list);
}
}
新增1:Stream实例化方法
ofNullable()的使用:
Java 8 中 Stream 不能完全为null,否则会报空指针异常。而 Java 9 中的 ofNullable 方法允许我们创建一个单元素 Stream,可以包含一个非空元素,也可以创建一个空 Stream。
//报NullPointerException
//Stream
iterator()重载的使用:
//原来的控制终止方式:
Stream.iterate(1,i -> i + 1).limit(10).forEach(System.out::println);
//现在的终止方式:
Stream.iterate(1,i -> i < 100,i -> i + 1).forEach(System.out::println);