文章目录
一、接口方法
JDK1.8中引入了一种新的机制:接口可以支持在声明方法的同时,提供实现。
- 主要通过两种方式可以完成这种操作:
- 默认方法
- 静态方法
1. 默认方法
- 默认方法存在的两大优势:
- 可以让接口更优雅的升级,减少使用人员操作的负担
不必随着接口方法增加,从而修改实现代码,因为默认方法在子类中可以不用实现 - 可以让实现类中省略很多不必要方法的空实现
- 可以让接口更优雅的升级,减少使用人员操作的负担
默认方法,实现类不需要重写,类似该方法继承下去,实现类构建实例对象之后可以调用
interface FirstInterface{
//传统定义,抽象方法,没有方法体。
void before();
//默认方法
default void test() {
System.out.println("Default method in FirstInterface");
}
}
class FirstClass implements FirstInterface{
//所有实现类必须实现接口中未实现的方法。
@Override
public void before() {
System.out.println("我是FirstInterface中的抽象方法,所有实现类必须实现!");
}
}
public class DefaultMethod {
public static void main(String[] args) {
FirstClass fc = new FirstClass();
fc.test();
//此处输出Default method in FirstInterface,对于默认方法,如果实现类中没有实现就是用默认的。
fc.before();
//此处输出我是FirstInterface中的抽象方法,所有实现类必须实现。
}
}
- 接口继承及默认方法冲突
- Java中一个类只能继承一个父类,但是可以实现多个接口。
- 方法调用的判断规则:
- 类中声明的方法优先级最高。
类或父类中,声明的方法要高于任何默认方法的优先级 - 如果无法依据第一条进行判断,那么子接口的优先级更高
例如,如果 B 接口继承了 A接口 ,那么 B 就比 A 更加具体,优先级更高
所以,在上述例子中,B是子接口,优先级别更高,调用test方法后输出:defaut method test in B. - 最后,如果还是无法判断,那么继承了多个接口的类,必须通过实现(重写)方法来确定方法的调用
- 类中声明的方法优先级最高。
(1)一个类C,实现两个接口A、B,其中接口B继承接口A
interface A{
default void test(){
System.out.println("Default Method test in A");
}
}
interface B extends A{
default void test(){
System.out.println("default method test in B");
}
}
class C implements A,B{
public static void main(String[] args){
C c = new C(); c.test();
}
}
//运行结果:
default method test in B
(2)一个类C,继承父类A,实现接口B
class A{
public void test(){
System.out.println("Default Method test in A");
}
}
interface B {
default void test(){
System.out.println("default method test in B");
}
}
class C extends A implements B{
public static void main(String[] args){
C c = new C(); c.test();
}
}
//运行结果:
default method test in A
(3)一个类C,实现两个接口A、B,但是A和B之间没有子父接口关系
interface A{
default void test(){
System.out.println("Default Method test in A");
}
}
interface B {
default void test(){
System.out.println("default method test in B")
}
}
//如下代码编译会报错。
/*
class C implements A,B{
public static void main(String[] args){
C c = new C(); c.test();
}
}
*/
//如果C需要同时实现A和B接口,那么必须显示覆盖
class C implements A,B{
public void test(){
//如果在C中需要显示访问A/B的默认方法,可以使用接口名.super.方法名();
A.super.test();
B.super.test();
//或者自己编写test方法的实现
}
}
2. 静态方法
接口中的静态方法可以直接通过 接口名.静态方法名的形式进行访问。
接口中的静态方法,只能使用当前接口的名字来调用
public interface StaticMethod{
public static void test(){
System.out.println("我是StaticMethod接口中的静态方法!");
}
}
class A implements StaticMethod{}
class Test{
public static void main(String args[]){
//运行成功
StaticMethod.test();
//编译报错:
//Static method may be invoked on containing interface class only
//A.test();
}
}
二、Lambda
1. 行为参数化
在实际项目中,因为用户需求的变动,需要不断的增加方法,为此复制了大量的相同的代码。
- 方法之间【相同】的地方,例如:
- 方法的修饰符
- 方法的返回类型
- 方法的参数列表
- 方法中对数据的遍历
- 方法之间【不同】的地方,例如:
- 方法的名字
- 方法中遍历数据后的核心操作
- 方法真正关心的重点:
- 方法的参数列表
- 方法中的核心操作代码
- 方法的返回类型
interface Action{
int action(int result,int i);
}
(1)传统的计算方式
//定义累加的核心操作行为
class Add implements Action{
public int action(int result,int i){
return result+i;
}
}
//定义累乘的核心操作行为
class Multiply implements Action{
public int action(int result,int i){
return result*i;
}
}
class Test{
public static void main(String[] args){
int result = 0;
result = calculate(3,5,new Add());
//[3,5]之间,累加的结果
System.out.println(result);
result = calculate(3,5,new Multiply());
//[3,5]之间,累乘的结果
System.out.println(result)
}
}
(2)匿名内部类
class Test{
public static void main(String[] args){
int result = 0;
result = calculate(3,5,new Action(){
public int action(int result,int i){
return result+next;
}
});
//[3,5]之间,累加的结果
System.out.println(result);
result = calculate(3,5,new Action(){
public int action(int result,int i){
return result*next;
}
});
//[3,5]之间,累乘的结果
System.out.println(result)
}
}
(3)函数式编程
函数式程序就是一个表达式。
允许函数式编程中没有任何返回值的
class Test{
public static void main(String[] args){
int result = 0;
/*
//忽略掉之前匿名内部类形式中,不重要的部分
Action add = (int result,int i) -> { return result + i; };
*/
//简化写法
Action add = (result,i) -> result + i;
result = calculate(3,5,add);
//去掉中间变量add
//相当于,第三个参数,我们传了一个核心的计算行为
//这个核心的计算行为,就是对Action接口中action方法的实现
//result = calculate(3,5,(result,next) -> result + next);
//[3,5]之间,累加的结果 S
ystem.out.println(result);
}
}
2. Lambda使用
接口中有且只有一个抽象方法的时候,才可以使用Lambda表达式来对其进行实现。
有且只有一个抽象方法的接口,就是函数式接口。该接口中,也允许有其他的默认方法和静态方法。
@FunctionalInterface ,用来检查被标注的接口,是不是一个函数式接口
2.1 Lambda语法
Lambda表达式的格式为: () -> {}
()
表示参数列表->
后面跟的是函数主体{}
函数主体,表达式的返回值,由这个函数主体中代码来决定
(1)抽象方法【无参】,【无返回值】
public class Test {
public static void main(String[] args) {
/**
* Action action1 = new Action(){
* public void run(){}
* };
*/
Action action1 = () -> {};
/**
* Action action2 = new Action(){
* public void run(){
* System.out.println("hello");
* }
* };
*/
//函数主体中,如果只有一句代码,那么可以省去大括号
Action action2 = () -> System.out.println("hello");
/**
* Action action3 = new Action(){
* public void run(){
* int a = 1;
* int b = 2;
* System.out.println(a+b);
* }
* };
*/
//函数主体中,如果有多句代码,那么大括号必须要写
Action action3= () -> {
int a = 1;
int b = 2;
System.out.println(a+b);
};
}
}
interface Action{
public void run();
}
(2)抽象方法【有参】,【无返回值】
- 抽象方法是一个参数的情况
public class Test {
public static void main(String[] args) {
/**
* Action action = new Action(){
* public void run(int a){}
* };
*/
Action action = (int a) -> {};
/**
* Action action1 = new Action(){
* public void run(int a){}
* };
*/
//只有一个参数时,可以不加小括号
//并且参数的类型,JVM运行时会做自动推断的,及时不写,它也知道是int
Action action1 = a -> {};
/**
* Action action2 = new Action(){
* public void run(int a){
* System.out.println(a);
* }
* };
*/
Action action2 = a -> System.out.println(a);
/**
* Action action3 = new Action(){
* public void run(int a){
* a++;
* }
* };
*/
Action action3 = a -> a++;
}
}
//抽象方法是一个参数的情况
interface Action{
public void run(int a);
}
- 抽象方法是多个参数的情况
public class Test {
public static void main(String[] args) {
/**
* Action action1 = new Action(){
* public void run(int a,int b){}
* };
*/
Action action1 = (a,b) -> {};
}
}
//抽象方法是多个参数的情况
interface Action{
public void run(int a,int b);
}
(3)抽象方法【无参】,【有返回值】
public class Test {
public static void main(String[] args) {
/**
* Action action1 = new Action(){
* public void run(){
* return 1;
* }
* };
*/
//如果就一句代码,大括号可以去掉,return关键字也会省去
Action action1 = () -> 1;
/**
* Action action2 = new Action(){
* public void run(){
* int num = 10;
* return (int)(Math.random()*num);
* }
* };
*/
Action action2 = () -> {
int num = 10;
return (int)(Math.random()*num);
};
}
}
interface Action{
public int run();
}
(4)抽象方法【有参】,【有返回值】
public class Test {
public static void main(String[] args) {
/**
* Action action1 = new Action(){
* public int run(int a,int b){
* return a+b;
* }
* };
*/
Action action1 = (a,b) -> a + b;
/**
* Action action2 = new Action(){
* public int run(int a,int b){
* int num = a+b;
* return (int)(Math.random()*num);
* }
* };
*/
Action action2 = (a,b) -> {
int num = a+b;
return (int)(Math.random()*num);
};
}
}
interface Action{
public int run(int a,int b);
}
2.2 常用接口
函数式接口都定义在 java.lang.function
包中
(1)Predicate
java.util.function.Predicate<T>
接口定义了一个名叫 test
的抽象方法,它接受泛型T
对象,并返回一个 boolean
类型的结果
and()
方法:俩个条件同时成立
public class Test {
public static void main(String[] args) {
Test t = new Test();
Integer[] arr = {12,3,43,123,34,6,56,7};
//条件1,数据大于10
Predicate<Integer> p1 = e -> e>10;
//条件2,数据小于50
Predicate<Integer> p2 = e -> e<50;
//俩个条件同时成立
Predicate<Integer> p = p1.and(p2);
arr = t.filter(arr,p);
System.out.println(Arrays.toString(arr));
}
public Integer[] filter(Integer[] arr, Predicate<Integer> p){
List<Integer> list = new ArrayList<>();
for(Integer i: arr){
//判断当前数据是否符合要求
if(p.test(i)){
list.add(i);
}
}
//把集合转为Integer类型数组
return list.toArray(new Integer[list.size()]);
}
}
//运行结果:
[12, 43, 34]
or()
方法:俩个条件成立一个即可
public class Test {
public static void main(String[] args) {
Test t = new Test();
Integer[] arr = {12,3,43,123,34,6,56,7};
//条件1,数据小于10
Predicate<Integer> p1 = e -> e<10;
//条件2,数据大于50
Predicate<Integer> p2 = e -> e>50;
//俩个条件成立一个即可
Predicate<Integer> p = p1.or(p2);
arr = t.filter(arr,p);
System.out.println(Arrays.toString(arr));
}
public Integer[] filter(Integer[] arr, Predicate<Integer> p){
List<Integer> list = new ArrayList<>();
for(Integer i: arr){
if(p.test(i)){
//判断当前数据是否符合要求
list.add(i);
}
}
//把集合转为Integer类型数组
return list.toArray(new Integer[list.size()]);
}
}
//运行结果:
[3, 123, 6, 56, 7]
negate()
方法:获取条件相反的数据
public class Test {
public static void main(String[] args) {
Test t = new Test();
Integer[] arr = {12,3,43,123,34,6,56,7};
//条件1,数据大于10
Predicate<Integer> p1 = e -> e>10;
//获取条件1相反的数据
Predicate<Integer> p = p1.negate();
arr = t.filter(arr,p);
System.out.println(Arrays.toString(arr));
}
public Integer[] filter(Integer[] arr, Predicate<Integer> p){
List<Integer> list = new ArrayList<>();
for(Integer i: arr){
//判断当前数据是否符合要求
if(p.test(i)){
list.add(i);
}
}
//把集合转为Integer类型数组
return list.toArray(new Integer[list.size()]);
}
}
//运行结果:
[3, 6, 7]
removeIf
方法:移除符合条件的数据
public class Test {
public static void main(String[] args) {
Collection<String> col = new ArrayList<String>();
col.add("abc");
col.add("hello");
col.add("world");
col.removeIf((str)->str.contains("o"));
System.out.println(col);
}
}
//运行结果:
[abc]
(2)Consumer
java.util.function.Consumer<T>
接口
accept
方法:
public class Test {
public static void main(String[] args) {
Test t = new Test();
Student stu = new Student("tom");
//操作1,给stu对象的name属性值加前缀
Consumer<Student> consumer1 = (s) -> s.name = "start_"+s.name;
//操作2,给stu对象的name属性值加后缀
Consumer<Student> consumer2 = (s) -> s.name = s.name+"_"+System.currentTimeMillis();
//操作3,给stu对象的name属性值,先加前缀,再加后缀
Consumer<Student> consumer3 = consumer1.andThen(consumer2);
//如果传入consumer1,表示只加前缀
//如果传入consumer2,表示只加后缀
//如果传入consumer3,表示先加前缀,再加后缀
t.operStu(stu,consumer3);
System.out.println(stu.name);
}
public void operStu(Student stu, Consumer<Student> consumer){
consumer.accept(stu);
}
}
class Student{
String name;
public Student(String name) {
this.name = name;
}
}
//运行结果:
briup_tom_1598282322884
forEach
方法:
public class Test {
public static void main(String[] args) {
Collection<String> col = new ArrayList<String>();
col.add("abc");
col.add("hello");
col.add("world");
//去掉中间变量,直接把Lambda表达式当前参数传入到forEach方法中
col.forEach((t)->System.out.println(t));
}
}
//运行结果:
abc
hello
world
(3)Function
java.util.function.Function<T, R>
接口
andThen
方法:
public class Test {
public static void main(String[] args) {
String str = "a-b-c-a-b-c";
//传入字符串,返回数组,操作为把字符串按照 "-" 进行分割为字符串数组
// "a-b-c-a-b-c" 转换为 {"a","b","c","a","b","c"}
Function<String,String[]> f1 = s -> s.split("-");
//传入字符串数组,返回Set<String>集合,目的是去除数组中重复的数据,存放把结果存放到Set集合中
//{"a","b","c","a","b","c"} 转换为集合 [a, b, c]
Function<String[], Set<String>> f2 = arr -> {
Set<String> set = new HashSet<>();
for(String string : arr){
set.add(string);
}
return set;
};
//刚好,f1函数的结果,作为f2函数的参数,f1和f2组合成f3函数
//f3函数表示传入字符串,最后返回Set<String>集合
//其实内部是先将字符串交给f1函数转换数组,在将数组交给f2函数转换Set集合
//通过上面列出的andThen源码也可以看出是这个效果
Function<String,Set<String>> f3 = f1.andThen(f2);
Set<String> set = f3.apply(str);
System.out.println(set);
}
}
//运行结果:
[a, b, c]
identity
静态方法(泛型):该方法可以直接返回一个Function对象,传入一个参数,直接把该参数返回,不做任何操作
public class Test {
public static void main(String[] args) {
Function<String,String> f = Function.identity();
//传入hello,返回hello
String result = f.apply("hello");
System.out.println(result);
}
}
//运行结果:
hello
computeIfAbsent
(Map接口的默认方法):用来在指定key不存在时,计算一个value值和指定key匹配,并将该key=value写入map中。
public class Test {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
map.put("1", "tom");
map.put("2", "mary");
Function<String,String> f = key->"default";
//如果key为3的时候,对应的值不存在
//那么使用f函数计算出一个value,并当前这个key-value存入到map集合中
map.computeIfAbsent("3",f);
//也可以去掉中间变量f,直接将Lambda表达式传入
//map.computeIfAbsent("3",key->"default");
map.forEach((k,v)-> System.out.println(k+"-"+v));
}
}
//运行结果:
1-tom
2-mary
3-default
(4)Supplier
java.util.function.Supplier<T>
接口
Supplier接口中的方法,不需要参数,可以根据我们指定的算法,返回一个数据
public class Test {
public static void main(String[] args) {
//生成1-100直接的随机的奇数
Supplier<Integer> supplier = ()->{
int num ;
do {
num = (int) (Math.random()*100+1);
}while ( (num & 1)==0 );
return num;
};
for (int i = 0; i < 10; i++) {
System.out.println(supplier.get());
}
}
}
//运行结果:
79
51
69
63
31
89
81
17
97
9
2.3 类型推断
(1)以Lambda表达式中没有接口的任何信息,JVM还能将其和接口匹配的上
public class Test {
public static void main(String[] args) {
Test t = new Test();
//JVM根据上下文运行环境自动推断出 ()-{} 表达式对应的是接口是Runnable
t.test1(()->{});
//JVM根据上下文运行环境自动推断出 ()-{} 表达式对应的是接口是Action
t.test2(()->{});
}
public void test1(Runnable run){ }
public void test2(Action action){ }
}
interface Action{
void run();
}
(2)自动推断出Lambda表达式中参数的类型
public class Test {
public static void main(String[] args) {
Test t = new Test();
//这俩种写法的效果是一样的,JVM根据环境自动推断出参数a和b的类型
t.test((int a,int b)->a+b);
t.test((a,b)->a+b);
}
public void test(Action action){ }
}
interface Action{
int run(int a,int b);
}
2.4 重载解析
如果类中的方法进行了重载,那么在使用Lambda表达式的时候,很可能给它的类型推断带来问题。这时候可以做类型转换,来解决这个问题:
public class Test {
public static void main(String[] args) {
//编译通过,用强转的方式指定了表达式的对应的接口类
test(1,(Predicate<Integer>)num->num>0);
}
public static void test(int a,Predicate<Integer> p){ }
public static void test(int a,Function<Integer,Boolean> f){ } }
2.5局部变量
在Lambda表达式中,使用了局部变量,那么这个局部变量就一定要使用 final
修饰符进行修饰
JDK1.8中,被匿名内部类、局部内部类、Lambda表示访问的局部变量,会默认加上
final
修饰符
public class Test {
public static void main(String[] args) {
int a = 1;
Runnable run = ()->{
//这里访问局部变量a之后,a就自动变成了final修饰的常量(JDK1.8)
//也可以手动给局部变量a加上final修饰符 //变量a的值将不可被再次赋值,变为了常量
System.out.println(a);
};
}
}
三、方法引用
(1)使用Lambda表达式,可以表示一个函数,这个函数由 “参数列表、函数主体、返回类型” 这三部分组成,同时这个函数还是一个函数式接口中,唯一的抽象方法的实现
只要一个函数是无参的、无返回类型的,它就可以作为run方法的具体实现
public class Test {
public static void main(String[] args) {
//()-{} 该表达式 代表了一个函数,该函数无参,函数主体不执行任何代码,也不返回任何数据
//()-{} 该表达式 同时也是接口Action中,唯一的抽象方法run的具体实现
//因为run方法也是无参、无返回类型
Action a = ()->{};
}
}
interface Action{
void run();
}
(2)Lambda表达式中,提供了特殊的语法,能让我们直接引用已经存在的方法,作为当前要表示的函数
public class Test {
public static void main(String[] args) {
//使用Lambda表达式,直接表示一个函数
//这个函数就作为run方法的具体实现
Action a1 = ()-> System.out.println("hello!");
//使用Lambda表达式,引用一个已经存在的方法,作为当前要表示的一个函数
//这个被引用的方法,需要和Action接口中的抽象方法run保持一致:参数列表、返回类型
//这个方法就作为run方法的具体实现
Action a2 = Student::sayHello;
a1.run(); //输出hello
a2.run(); //输出hello
}
}
interface Action{
void run();
}
class Student{
public static void sayHello(){
System.out.println("hello!");
}
}
1. 静态方法引用
使用Lambda表达式可以引用类中的【静态】方式,语法要求为:
类名::静态方法名
方法名字后面一定没有小括号
public class Test {
public static void main(String[] args) {
//只要函数的参数列表是String类型,函数的返回值是int类型,就可以作为Action接口的具体实现
Action a1 = str -> 1;
//使用 类名::静态方法名 的形式来引用当前类中的len方法
Action a2 = Test::len;
System.out.println(a1.run("hello"));//输出1
System.out.println(a2.run("hello"));//输出5
}
public static int len(String str){
return str.length();
}
}
interface Action{
int run(String str);
}
2. 实例方法引用
使用Lambda表达式可以引用类中的【非静态】方式,语法要求为:
类名::非静态方法名
使用类名来引用方法
run方法的返回类型是int
- run方法是【一个】参数
public class Test {
public static void main(String[] args) {
/*
* Action a1 = new Action() {
* @Override
* public int run(String str) {
* return 1;
* }
* };
*/
Action a1 = str -> 1;
/**
* Action a2 = new Action() {
* @Override
* public int run(String str) {
* return str.hashCode();
* }
* };
*/
Action a2 = String::hashCode;
/**
* Action a3 = new Action() {
* @Override
* public int run(String str) {
* return str.length();
* }
* };
*/
Action a3 = String::length;
}
}
interface Action{
int run(String str);
}
- run方法是【俩个】参数
public class Test {
public static void main(String[] args) {
/**
* Action a1 = new Action() {
* @Override
* public int run(String str,int i) {
* return 1;
* }
* };
*/
Action a1 = (str,i) -> 1;
/**
* Action a2 = new Action() {
* @Override
* public int run(String str,int i) {
* return str.indexOf(i);
* }
* };
*/
Action a2 = String::indexOf;
/**
* Action a3 = new Action() {
* @Override
* public int run(String str,int i) {
* return str.lastIndexOf(i);
* }
* };
*/
Action a3 = String::lastIndexOf;
System.out.println(a2.run("abc-abc",'a'));//输出 0
System.out.println(a3.run("abc-abc",'a'));//输出 4
}
}
interface Action{
int run(String str,int i);
}
- run方法是【三个】参数
public class Test {
public static void main(String[] args) {
/**
* Action a1 = new Action() {
* @Override
* public int run(String str,int i,int j) {
* return 1;
* }
* };
*/
Action a1 = (str,i,j) -> 1;
/**
* Action a2 = new Action() {
* @Override
* public int run(String str,int i,int j) {
* return str.indexOf(i,j);
* }
* };
*/
Action a2 = String::indexOf;
/**
* Action a3 = new Action() {
* @Override
* public int run(String str,int i,int j) {
* return str.lastIndexOf(i,j);
* }
* };
*/
Action a3 = String::lastIndexOf;
}
}
interface Action{
int run(String str,int i,int j);
}
- run方法是【四个】参数
public class Test {
public static void main(String[] args) {
/**
* Action a = new Action() {
* @Override
* public int run(MyHandler handler,int i, String str, List<Integer> list) {
* return handler.test(i,str,list);
* }
* };
*/
Action a = MyHandler::test;
}
}
interface Action{
int run(MyHandler handler, int i, String str, List<Integer> list);
}
class MyHandler{
public int test(int i, String str, List<Integer> list){
return 0;
}
}
3. 对象引用方法
使用对象来引用它的非静态方式,语法要求为:对象::非静态方法
任何一个对象的中的方法,只要是参数列表和返回类型和run方法保持一致, 都可以使用这个对象中的这个方法,来对Action接口进行实现
public class Test {
public static void main(String[] args) {
MyHandler handler = new MyHandler();
/**
* Action a1 = new Action() {
* @Override
* public String run(String str) {
* return str;
* }
* };
*/
Action a1 = str -> str;
//这里表示,使用handler对象的test方法,来对Action接口进行实现
//因为test方法的参数列表和返回类型,恰好是和run方法的参数列表和返回类型保持一致
Action a2 = handler::test;
System.out.println(a2.run("tom"));//输出:hello! tom
}
}
interface Action{
String run(String str);
}
class MyHandler{
public String test(String name){
return "hello! "+name;
}
}
4. 构造方法引用
引用类中的构造函数,语法要求为:类名::new
4.1 无参构造器
public class Test {
public static void main(String[] args) {
/**
* Action a1 = new Action() {
* @Override
* public Student run() {
* return new Student();
* }
* };
*/
Action a1 = ()->new Student();
//这里表示,使用Student类的无参构造函数,来对Action接口进行实现
//因为Student类的无参构造函数,恰好是和run方法的参数列表和返回类型保持一致
Action a2 = Student::new;
System.out.println(a2.run());
}
}
interface Action{
Student run();
}
class Student{ }
4.2 有参构造器
public class Test {
public static void main(String[] args) {
/**
* Action a1 = new Action() {
* @Override
* public Student run(String name) {
* return new Student(name);
* }
* };
*/
Action a1 = name->new Student(name);
//这里表示,使用Student类的有参构造函数,来对Action接口进行实现
//因为Student类的有参构造函数,恰好是和run方法的参数列表和返回类型保持一致
Action a2 = Student::new;
System.out.println(a2.run("tom"));
}
}
interface Action{
Student run(String name);
}
class Student{
private String name;
public Student(String name){
this.name = name;
}
}
4.3 数组构造
使用Lambda表达式可以引用数组类型的构造函数,语法要求为:数组类型::new
public class Test {
public static void main(String[] args) {
/**
* Action a1 = new Action() {
* @Override
* public int[] run(int len) {
* return new int[len];
* }
* };
*/
Action a1 = len -> new int[len];
//这里表示,使用int数组的构造函数,来对Action接口进行实现
//因为int数组的构造函数,恰好是和run方法的参数列表和返回类型保持一致
Action a2 = int[]::new;
System.out.println(a2.run(5));
}
}
interface Action{
int[] run(int len);
}
四、Optional
java.util.Optional<T>
类,是用来防止NullPointerException异常的辅助类型,Optional
对象中封装的值,可以是null
,也可以不是null
Optional
中常用的方法:
public class Test {
public static void main(String[] args) {
/**
* of方法 为非null的值创建一个Optional对象
* 如果传入参数为null,则抛出NullPointerException
*/
Optional<String> op1 = Optional.of("hello");
/**
* ofNullable方法
* ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况
*/
Optional<String> op2 = Optional.ofNullable(null);
/**
* isPresent方法 如果值存在返回true,否则返回false
*get方法 如果Optional有值则将其返回,否则抛出NoSuchElementException
*/
if(op1.isPresent()){
System.out.println(op1.get());
}
if(op2.isPresent()){
System.out.println(op2.get());
}
/**
* ifPresent方法 如果Optional实例有值则为其调用consumer接口中的方法,否则不做处理
* Consumer:
* public void accept(T t);
*/
op1.ifPresent(str->System.out.println(str));
op2.ifPresent(str->System.out.println(str));
//这个不执行 因为op2里面的值是 null
/**
* orElse方法 如果有值则将其返回,否则返回指定的其它值
*/
System.out.println(op1.orElse("如果op1中的值为null则返回这句话"));
System.out.println(op2.orElse("如果op2中的值为null则返回这句话"));
/**
* orElseGet方法 orElseGet与orElse方法类似,区别在于得到的默认值的方式不同
* Supplier:
* public T get();
*/
System.out.println(op1.orElseGet(()->"自己定义的返回值"));
System.out.println(op2.orElseGet(()->"自己定义的返回值"));
/**
* map方法 如果有值,则调用mapper的函数处理并得到返回值
* 返回值并且依然Optional包裹起来,其泛型和你返回值的类型一致
* public <U> Optional<U> map(Function<? super T, ? extends U> mapper)
*/
Optional<Integer> map1 = op1.map(str->1);
System.out.println(map1.orElse(0));
Optional<Double> map2 = op2.map(str->1.2);
System.out.println(map2.orElse(0D));
/**
* flatMap方法 如果有值,则调用mapper的函数返回Optional类型返回值,否则返回空 Optional
* flatMap与map方法类似,区别在于flatMap中的mapper返回值必须是Optional
* 调用结束时,flatMap不会对结果用Optional封装,需要我们自己把返回值封装为Optional
* public <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper);
*/
Optional<String> flatMap = op1.flatMap(str->Optional.of(str+"_start"));
System.out.println(flatMap.get());
/**
* filter方法 如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional
* public Optional<T> filter(Predicate<? super T> predicate);
*/
op1 = op1.filter(str->str.length()<10);
System.out.println(op1.orElse("值为null"));
op1 = op1.filter(str->str.length()>10);
System.out.println(op1.orElse("值为null"));
}
}
五、Stream
1. 概述
java.util.stream.Stream
接口,表示能应用在一组元素上,一次执行的操作序列,也就是可以对一组数据进行连续的多次操作。Stream
在使用的时候,需要指定一个数据源,比如java.util.Collection
的子类,List或者Set都可以,但是Map类型的集合不支持。- Stream是对集合功能的增强,它提供了各种非常便利、高效的聚合操作,可以大批量数据操作,同时再结合Lambda表达式,就可以极大的提高编程效率。同时它提供串行和并行两种模式进行操作。
- Stream的API提供了串行和并行两种模式进行操作数据。
- Stream操作分为中间操作或者最终操作两种:
- 中间操作,返回Stream本身,这样就可以将多个操作依次串起来
例如,map、flatMap、filter、distinct、sorted、peek、limit、skip、parallel、 sequential、unordered
- 最终操作,返回一特定类型的计算结果
例如,forEach、forEachOrdered、toArray、reduce、collect、min、max、count、 anyMatch、allMatch、noneMatch、findFirst、findAny、iterator
- 中间操作,返回Stream本身,这样就可以将多个操作依次串起来
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,4,5,6,7,8,9,10);
list = list.stream() //把集合list变为stream
.filter(e->e%2==0) //过滤,保留偶数
.sorted((e1,e2)->e2-e1) //排序,倒序
.collect(Collectors.toList()); //结果收集到一个新的List集合 中并返回
System.out.println(list);
}
}
//运行结果:
[10, 8, 6, 4, 2]
2. 其他转Stream
- 值
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("a", "b", "c");
}
}
- 数组
public class Test {
public static void main(String[] args) {
String[] arr = {"a", "b", "c"};
Stream<String> stream1 = Arrays.stream(arr);
Stream<String> stream2 = Stream.of(arr);
}
}
- 集合
只要是Collection类型的集合,都可以调用stream()方法,将集合转换为Stream对象
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
Collections.addAll(list,"a", "b", "c");
Stream<String> stream = list.stream();
}
}
- 基本类型
对于基本数值类型,有专门的三种Stream类型:
- IntStream
- LongStream
- DoubleStream
public class Test {
public static void main(String[] args) {
IntStream stream1 = IntStream.of(new int[]{1, 2, 3});
//[1,3)
IntStream stream2 = IntStream.range(1, 3);
//[1,3]
IntStream stream3 = IntStream.rangeClosed(1, 3);
}
}
3. Stream转其他
- 数组
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
/**
* <A> A[] toArray(IntFunction<A[]> generator);
* public interface IntFunction<R> {
* R apply(int value);
* }
*/
String[] strArray = stream.toArray(String[]::new); } }
- 集合
一个Stream在代码中,只能使用一次,再次使用就会报错
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
List<String> list1 = stream.collect(Collectors.toList());
//List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
//Set<String> set3 = stream.collect(Collectors.toSet());
//Set<String> set4 = stream.collect(Collectors.toCollection(HashSet::new));
}
}
- 字符串
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world");
String result = stream.collect(Collectors.joining("-"));
System.out.println(result);
}
}
//运行结果:
hello-world
4. Stream操作
4.1 最终操作
iterator
: 返回迭代器对象
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
Iterator<String> it = stream.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
forEach
:将调Stream中的每个元素,交给一个Consumer函数处理
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
stream.forEach(System.out::println);
}
}
count
:统计流中的元素数,并返回结果
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
System.out.println(stream.count());
}
}
max
:返回流中基于comparator所指定的比较规则,比较出的最大值
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
/**
* Optional<String> max = stream.max(new Comparator<String>() {
* @Override
* public int compare(String o1, String o2) {
* return o1.compareTo(o2);
* }
* });
*/
Optional<String> max = stream.max(String::compareTo);
System.out.println(max.get());
}
}
min
:返回流中基于comparator所指定的比较规则,比较出的最小值
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
/**
* Optional<String> min = stream.min(new Comparator<String>() {
* @Override
* public int compare(String o1, String o2) {
* return o1.compareTo(o2);
* }
* });
*/
Optional<String> min = stream.min(String::compareTo);
System.out.println(min.get());
}
}
toArray
: 使用调用流中的元素,生成数组返回。
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
/**
* <A> A[] toArray(IntFunction<A[]> generator);
* public interface IntFunction<R> {
* R apply(int value);
* }
*/
String[] strArray = stream.toArray(String[]::new);
}
}
collect
:将元素收集到一个可以修改的容器中,并返回该容器
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("test","hello","world","java","tom","C","javascript");
//把Stream中的元素,按照字符串长度进行分组,长度相同算是一组,并存放到同一个集合中
//map的key是字符串的长度,value是同一组的数据
Map<Integer, List<String>> map = stream.collect(Collectors.groupingBy(String::length));
map.forEach((k,v)-> System.out.println(k+" : "+v));
}
}
//运行结果:
1 : [C]
3 : [tom]
4 : [test, java]
5 : [hello, world]
10 : [javascript]
Match
:匹配操作,Stream中提供了多种匹配模式
这些操作不能同时执行,因为一个Stream只能使用一次
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("test","hello","world","java","tom","C","javascript");
//所有元素匹配成功才返回true 否则返回false
boolean allMatch = stream.allMatch((s)->s.startsWith("j"));
System.out.println(allMatch);
//任意一个匹配成功就返回true 否则返回false
boolean anyMatch = stream.anyMatch((s)->s.startsWith("j"));
System.out.println(anyMatch);
//没有一个匹配的就返回true 否则返回false
boolean noneMatch = stream.noneMatch((s)->s.startsWith("j"));
System.out.println(noneMatch);
}
}
findFirst
:返回 Stream的第一个元素
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("test","hello","world","java","tom","C","javascript");
Optional<String> first = stream.findFirst();
System.out.println(first.get());
}
}
4.2 中间操作
filter
:过滤方法,返回满足predicate指定的条件的所有元素的一个新流
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
stream.filter(e->e.contains("o")).forEach(System.out::println);
}
}
//运行结果:
hello world
map
:对调用流中的元素,应用Function所指定的操作,然后返回一个新流
(1)map生成的是个1:1映射,每个输入元素,都按照规则转换成为另外一个元素
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","strat");
List<Integer> list = stream.map(str -> str.length()) .collect(Collectors.toList());
list.forEach(System.out::println);
}
}
//运行结果:
5
5
5
(2)map 方法可以和 reduce 方法配合使用, reduce 方法是将一组数据俩俩合并,最后得出一个结果
public class Test {
public static void main(String[] args) {
//1~10之间的数字累加
IntStream stream = IntStream.rangeClosed(1, 10);
//reduce方法需要提供一个起始值(种子)
//然后依照运算规则,和Stream中的第一个数据进行操作,得出结果
//再将这个结果和Stream中的第二个数据进行操作,再得出结果,依次类推,直到得出最 终结果
int result = stream.reduce(0, (a, b) -> a + b);
System.out.println(result);
}
}
//运行结果:
55
sorted
: 排序
(1)自然排序
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
//默认自然排序
stream.sorted().forEach(System.out::println);
}
}//运行结果:
start hello world
(2)比较器排序
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("hello","world","start");
//比较器排序,注意Lambda表达式中返回的值前加了符号
stream.sorted((o1, o2) -> - o1.compareTo(o2)).forEach(System.out::println);
}
}
//运行结果:
world
hello
start
limit
:返回 Stream 的前面 n 个元素
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("test","javap","hello","world","java","tom","C","javascript");
stream.limit(5).forEach(System.out::println);
}
}
//输出结果:
test
javap
hello
world
java
skip
:跳过前 n 个元素只要后面的元素
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("test","javap","hello","world","java","tom","C","javascript");
stream.skip(5).forEach(System.out::println);
}
}//运行结果:
tom
C
javascript
concat
:拼接两个流
public class Test {
public static void main(String[] args) {
Stream<String> stream1 = Stream.of("hello","world");
Stream<String> stream2 = Stream.of("tom","mary");
Stream<String> result = Stream.concat(stream1, stream2);
result.forEach(System.out::println);
}
}
//运行结果:
hello
world
tom
mary
distinct
:去除重复数据
public class Test {
public static void main(String[] args) {
Stream<String> stream = Stream.of("test","test","hello","world","java","java","C","C");
stream.distinct().forEach(System.out::println);
}
}
//运行结果:
test
hello
world
java
C
4.3 静态方法
(1)Stream.generate
,通过Supplier接口,可以自己来控制数据的生成
public class Test {
public static void main(String[] args) {
final Random random = new Random();
//生成100个随机数,并输出
Stream.generate(()->random.nextInt(100))
.limit(100)
.forEach(System.out::println);
//生成100个随机数,并存放到集合中
List<Integer> list = Stream.generate(()->random.nextInt(100))
.limit(100)
.collect(Collectors.toList());
}
}
(2)Stream.iterate
,它跟 reduce 操作很像,需要接受一个起始值(种子),然后通过函数得出一个结果,再把结果当做参数传给函数,再得出第二个结果,依次类推,其实就是一个递归操作。
public class Test {
public static void main(String[] args) {
//生成一个等差数列,公差为3,从0开始获取前10个数字
Stream.iterate(0, n -> n + 3)
.limit(10)
.forEach(System.out::println);
}
}
//运行结果:
0
3
6
9
12
15
18
21
24
27
5. IO流和Stream
在IO流中,也有方法,可以将读取到的数据转换为Stream对象
从a.txt文件中,找出字符最长的一行,返回它的字符数
public class Test {
public static void main(String[] args) {
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("src/com/briup/demo/a.txt"));
int maxLen = br.lines() //io流转为Stream
.mapToInt(String::length) //每行字符串转换为它的字符长度
.max() //获取最大长度的数字
.getAsInt();//返回int类型的结果
System.out.println(maxLen);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
6. 并行流
串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
在数据量较大的特定场景下,并行Stream比串行Stream的效率要高一些
创建并行Stream的俩种方式:
- 调用串行Stream的
parallel()
方法,可以将其转换并行Stream
Stream<String> stream = Stream.of("hello","world","briup");
Stream<String> parallelStream = stream.parallel();
- 调用集合对象的
parallelStream
方法,之后获取并行Stream
List<String> list = new ArrayList<>();
Collections.addAll(list,"hello","world","briup");
Stream<String> parallelStream = list.parallelStream();