方法引用和lambda表达式的奥妙
方法引用替代Lambda表达式
什么情况可以使用方法引用替代lambda表达式?
下面代码中两处使用了lambda表达式,一个是filter内,一个是forEach内。其中,forEach内的lambda表达式可以被方法引用替代,但是filter内的lambda表达式不能被方法引用替代。
package com.yimeng;import java.util.Arrays;
import java.util.List;public class Test {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);numbers.stream().filter(e -> e % 2 == 0).forEach(e -> System.out.println(e));}
}
使用方法引用取代的做法:
package com.yimeng;import java.util.Arrays;
import java.util.List;public class Test {public static void main(String[] args) {List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);numbers.stream().filter(e -> e % 2 == 0).forEach(System.out::println);}
}
为什么filter内的lambda表达式不能使用方法引用取代,但是forEach内的lambda表达式可以被方法引用取代呢?
这个需要看lambda表达式里面的内容了。
在上面案例中,filter内的lambda表达式是写一个对流中元素进行处理的逻辑。而forEach内的lambda表达式只是做了一个传递参数的动作,他相当于是借用了已经存在的方法去执行具体的逻辑,至于逻辑是什么样的,得看System.out对象的println方法了。
所以看出区别了吧!一个是自己在lambda表达式的方法体里面去处理逻辑,一个是传递参数,使用已经有的方法去处理逻辑。就是说,你想要做的事情已经有了一个解决方案了,你直接用那个解决方案来处理就行了(方法就是解决方案)。
总结:如果是直接传递参数到其他方法中,使用其他方法去处理的,那么可以使用方法引用来替代lambda表达式。(这里还要注意一点,就是能替代为方法引用的lambda表达式,一定是单纯的调用其他方法来处理哈,一定是只有一句语句,就是仅仅做参数传递的lambda表达式才能简化为方法引用。)
我们知道lambda表达式是满足某些条件(满足存在上下文并且上下文推断出一个接口,并且接口是一个函数式接口)的匿名内部类的一种简写。
而方法引用其实就是对满足某些条件的lambda表达式再次进行简写的一种写法。注意:匿名内部类、lambda表达式、方法引用都是一个对象,是一个实例哦。
满足某些条件的lambda表达式,那么这里说的满足某些条件是指什么?
什么条件下,我们可以使用方法引用替代lambda表达式?
1. 上下文能推断出函数式接口
首先,能使用方法引用的地方一定能使用lambda表达式,因为方法引用是在特定条件下lambda表达式的简写嘛!
某个位置要能写lambda表达式,能写lambda表达式,所以这个能写方法引用的地方通过上下文环境肯定能推断出函数式接口。(lambda表达式中的知识点)
比如,我们要写内容的地方是在某个方法里面,方法的形参是函数式接口,那么这个地方就能写lambda表达式。比如赋值操作,赋值号的左边是一个函数式接口,那么你赋值号的右边就可以写lambda表达式。
所以要能写方法引用第一个条件就是,要有能推断出函数式接口的上下文。
2. 需要完成的函数式接口的抽象方法可以通过直接借用的存在方法来达到目标
然后,因为不是所有的lambda表达式都可以用方法引用来替代的,只有在函数式接口抽象方法实现可以是单纯调用其他已经存在的方法就可以完成当前需要的功能的情况下,才可以使用方法引用来替代lambda表达式。所以要某个地方要能使用方法引用,那么必须是这个地方的上下文推断出来的抽象方法的实现,可以是单纯地调用某个存在的方法就可以完成我们需求的。(注意,相当于是纯调用才行,并且只能是一句语句)
好,我们想象一下,如果现在要你补充完一段代码逻辑,你发现这段代码的上下文是一个函数式接口,并且你也找到了能借用的方法。那么你要怎么借用呢?
怎么使用方法引用去借用已经存在的方法
1. 如果要借用的方法是一个静态方法
“类名::静态方法”
使用==“类名::静态方法”==就行。(注意,不能用实例::静态方法
)
对函数式接口的要求:抽象方法的形参能传给被借用方法,抽象方法的返回值能接纳借用方法的返回值,或者抽象方法的返回值是void。
例子:
package com.yimeng.mydemo;public class Demo1 {public static void main(String[] args) {new Demo1().f();}private void f() {/* 问题:MyInterface1 myInterface1 = ??;这里的??** 看题目知道当前存在可以推断出函数式接口的上下文。并且知道要实现的方法长这样:long sum(int a,Integer b);* 知道这个接口的抽象方法想要实现的功能是把a和b相加,然后返回相加后的结果。* 看到Demo1中有一个静态方法demo1Sum(Integer a,Integer b)和我们想实现的功能是一样的,我们直接调用那个方法就能实现我们要的功能,所以我们可以用方法引用来完成。* 怎么用呢?要方法引用一个静态方法,那么就要用“类名::静态方法名”**/MyInterface1 myInterface1 = Demo1::demo1Sum;// 注意,方法引用和lambda表达式、匿名内部类一样,整体就是一个函数式接口的实现类实例,所以可以直接调用方法。方法的功能就看实现的方法体。这里是接口引用,是借用,相当于是实现的方法体中就一句语句,语句的效果是:直接调用Demo1类中的静态方法demo1Sum给返回(这里有返回值,所以会返回)System.out.println(myInterface1.sum(1,2));// MyInterface1 myInterface2 = new Demo1()::demo1Sum;// 不行}public static int demo1Sum(Integer a,Integer b){return a+b;}
}
interface MyInterface1{// long sum(int a,String b);// 这个不行,因为类型没有匹配(不需要完全匹配,能兼容就行)long sum(int a,Integer b);// 借用方法和抽象方法能匹配就行
}
结果:
3
注意:没有要求函数式接口的抽象方法和要借用的方法完全一模一样(没有要求形参类型一样,返回值要求是,如果抽象方法如果有返回,那么借用的方法的返回类型就要能被抽象方法返回值所接收就行,如果抽象方法没有返回值,那么随便借用方法返回什么)。要满足什么条件呢?看下面方法引用推出lambda表达式、匿名内部类的笔记就能理解了。
注意:函数式接口的抽象方法和要借用的方法参数列表要匹配才行。(没有说完全匹配,必须要参数列表的顺序一样、参数列表的类型可以兼容。返回值是,如果抽象方法如果有返回,那么借用的方法的返回类型就要能被抽象方法返回值所接收就行,如果抽象方法没有返回值,那么随便借用方法返回什么)
注意:写完方法引用后,要看看这个方法引用的类名,是不是和函数式接口中抽象方法的第一个形参一样,并且这个类中也能找到一个成员方法,成员方法的形参和抽象方法除第一个形参外的形参列表匹配,如果能找到,那么会出现语义不明确。
2. 如果要借用的方法是一个成员方法
“实例::成员方法”
使用==“实例::成员方法”==就行。注意:this和super也是实例,只有成员方法可以用this和super,使用成员方法就必须要创建对象,那么这个成员方法执行的时候,这个this和super就是指你调用这个成员方法的对象。
对函数式接口的要求:抽象方法的形参能传给被借用方法,抽象方法的返回值能接纳借用方法的返回值,或者抽象方法的返回值是void。
例子:
package com.yimeng.mydemo;public class Demo2 extends Demo2Parent{public static void main(String[] args) {Demo2 demo2 = new Demo2();demo2.f();}public void f(){/* 问题:MyInterface2 myInterface2 = ??;这里的??可以怎么填,要求它的抽象方法可以实现把形参打印出来,并且带上前缀:“prefix类名-:”。* * 看题目知道当前存在可以推断出函数式接口的上下文。并且知道要实现的方法长这样:void printInt(Integer i);* 知道这个接口的抽象方法想要实现的功能是:把形参打印出来,并且带上前缀:“prefix类名-:”。* 看到Demo2、Demo2的父类、Demo2的祖先类中都有成员方法和我们想实现的功能是一样的。我们直接调用对应方法就能实现我们要的功能,所以我们可以用方法引用来完成。* 怎么用呢?要借用一个成员方法,那么就要用“实例::成员方法名”,如果要借用的方法和使用方法引用的方法在一个类里面,并且都在成员方法里面(静态方法里面不能用this),那么可以用“this::成员方法名”代替“实例::成员方法名”。如果要借用的方法在他的父类或者祖先类中,那么可以用“super::成员方法名”代替“实例::成员方法名”。**/Demo2 demo2 = new Demo2();// 方法引用MyInterface2 myInterface2 = demo2::say;myInterface2.printInt(100);// 方法引用,使用thisMyInterface2 myInterface21 = this::say;myInterface21.printInt(100);// 方法引用,使用super借用父类的方法MyInterface2 myInterface22 = super::sayParent;myInterface22.printInt(100);// 方法引用,使用this也借用父类的方法。因为子类会继承父类的方法MyInterface2 myInterface222 = this::sayParent;myInterface222.printInt(100);// 方法引用,使用super借用祖先类的方法MyInterface2 myInterface23 = super::sayGrandpa;myInterface23.printInt(100);}public void say(Object o){System.out.println("prefix-Demo2:"+o);}
}
interface MyInterface2{void printInt(Integer i);
// int printInt(Integer i);// 不行,因为返回值不匹配
}
class Demo2Parent extends Demo2Grandpa{public void sayParent(Object o){System.out.println("prefix-Demo2Parent:"+o);}
}
class Demo2Grandpa{public void sayGrandpa(Object o){System.out.println("prefix-Demo2Grandpa:"+o);}
}
结果:
prefix-Demo2:100
prefix-Demo2:100
prefix-Demo2Parent:100
prefix-Demo2Parent:100
prefix-Demo2Grandpa:100
注意:要求借用方法和函数式借口的抽象方法形参类型相互匹配才行。返回值要求是,如果抽象方法如果有返回,那么借用的方法的返回类型就要能被抽象方法返回值所接收就行,如果抽象方法没有返回值,那么随便借用方法返回什么。
3. 如果要借用的方法是函数式接口抽象方法第一个参数的成员方法
“类名::成员方法”
使用==“类名::成员方法”==就行。
对函数式接口的要求:抽象方法的第一个形参类型和被被借用方法所在类一样,抽象方法除第一个形参外的其他参数能传给被借用方法的形参,抽象方法的返回值能接纳借用方法的返回值,或者抽象方法的返回值是void。(只要你抽象方法的第一个形参类型中能找到被借用方法,这里找的方法,不仅包括第一个形参类型本类中的方法,也包括第一个形参类型中从他父类中继承到的方法哈)
注意:要借用方法的参数要和函数式接口的抽象方法除第一个参数外的参数匹配(如果抽象方法除了第一个参数外没有其他参数,那么借用方法的参数就是无参的)。
例子:
package com.yimeng.mydemo;public class Demo3 {public static void main(String[] args) {new Demo3().f();}private void f() {/* 问题1:MyInterface3 myInterface3 = ??;这里的??可以怎么填,要求它的抽象方法可以实现打印“MyClass3的test方法被借用”并返回“say”字符串* 问题2:MyInterface33 myInterface33 = ??;这里的??可以怎么填,要求它的抽象方法可以实现打印“MyClass33的test3方法被借用+传进去的字符串+传进去的整数”并返回“hello”字符串** 看题目1知道当前环境可以推断出函数式接口的上下文是MyInterface3。并且知道要实现的方法长这样:String say(MyClass3 myClass3);* 看题目2知道当前环境可以推断出函数式接口的上下文是MyInterface33。并且知道要实现的方法长这样:String say(MyClass33 myClass33, String s, int i);* 知道这个MyInterface3接口的抽象方法想要实现的功能是:打印“MyClass3的test方法被借用”并返回“say”字符串。* 看到MyInterface3接口的抽象方法say第一个参数myClass3中有成员方法和我们MyInterface3抽象方法想实现的功能是一样的,并且要借用方法的形参和抽象方法除第一个形参外的参数是匹配的,返回值也是匹配的。所以我们可以用方法引用来完成。* 知道这个MyInterface33接口的抽象方法想要实现的功能是:打印“MyClass33的test3方法被借用+传进去的字符串+传进去的整数”并返回“hello”字符串* 看到MyInterface33接口的抽象方法say第一个参数myClass33中有成员方法和我们MyInterface33抽象方法想实现的功能是一样的,并且要借用方法的形参和抽象方法除第一个形参外的参数是匹配的,返回值也是匹配的。所以我们可以用方法引用来完成。* 怎么用呢?要借用抽象方法第一个参数的成员方法,那么就要用“抽象方法第一个参数类名::成员方法名”。**/// 抽象方法除第一个参数外,无参MyInterface3 myInterface3 = MyClass3::test;String say = myInterface3.say(new MyClass3());System.out.println(say);// 抽象方法除第一个参数外,还有参数MyInterface33 myInterface33 = MyClass33::test3;String hello = myInterface33.say(new MyClass33(), "hello", 1);System.out.println(hello);}
}interface MyInterface3 {String say(MyClass3 myClass3);
}class MyClass3 {// 下面这个注释打开就会和String test()冲突。因为MyInterface3 myInterface3 = MyClass3::test;可以通过“类名::静态方法名”找到String test(MyClass3 myClass3),也可以通过“类名::成员方法名”找到String test(),这样就语义不明确了。
// public static String test(MyClass3 myClass3) {
// System.out.println("MyClass3的test方法被借用");
// return "say";
// }public String test() {System.out.println("MyClass3的test方法被借用");return "say";}
}interface MyInterface33 {String say(MyClass33 myClass33, String s, int i);
}class MyClass33 {public String test3(String s, int i) {System.out.println("MyClass33的test3方法被借用" + s + i);return "hello";}
}
结果:
MyClass3的test方法被借用
say
MyClass33的test3方法被借用hello1
hello
注意:写完方法引用后,要看看这个方法引用是不是,也能找到方法引用的类中,有没有一个静态方法,方法的形参和抽象方法形参匹配,如果能找到,那么会出现语义不明确。
4. 特别的,如果要借用的方法是一个构造方法
“类名::new”
使用==“类名::构造方法”==就行。
对函数式接口的要求:抽象方法的形参能传给被借用构造方法,抽象方法的返回值能接纳借用这个构造方法创建的对象,或者抽象方法的返回值是void。
注意:函数式接口的形参和要借用的构造方法的形参匹配。返回值要求是,如果抽象方法如果有返回,那么借用的方法的返回类型就要能被抽象方法返回值所接收就行,如果抽象方法没有返回值,那么随便借用方法返回什么。
例子:
package com.yimeng.mydemo;public class Demo4 {public static void main(String[] args) {/* 问题:MyInferface4 myInferface4 = ??;这里的??可以怎么填,要求它的抽象方法效果是创建Student对象并且把Student对象返回,并且要创建对象的时候把name也给赋值上。** 看题目知道当前存在可以推断出函数式接口的上下文。并且知道要实现的方法长这样:Student createInstance(String name);* 知道这个接口的抽象方法想要实现的功能是:是创建Student对象并且把Student对象返回,并且要创建对象的时候把name也给赋值上。* 看到Student的Student(String name)构造方法,和我们想实现的功能是一样的。我们直接调用这个方法就能实现我们要的功能,所以我们可以用方法引用来完成。* 怎么用呢?要借用一个构造方法,那么就要用“类名::new”。**/MyInferface4 myInferface4= Student::new;myInferface4.createInstance("张三");}
}
interface MyInferface4{Student createInstance(String name);// 可以,相当于是调用了构造方法,并且把创建的对象给返回。
// void createInstance(String name);// 可以,相当于是单纯的调用构造方法进行执行,没有返回值。
// Object createInstance(String name);// 可以,Student是Object的子类。
// String createInstance(String name);// 不可以,其他的返回值就行不行了。
}
class Student {private String name;private int age;public Student(String name) {System.out.println("构造方法执行");this.name = name;}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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
结果:
构造方法执行
5. 特别的,如果要借用数组的创建
“类型[]::new”【引用类型和基本类型都行】
注意:创建数组的这种函数式接口中的抽象方法形参只能是Integer或者int,其他都不行。long和Object都不行。
对函数式接口的要求:抽象方法的形参为int或者Integer。返回值是lambda表达式想创建的数组类型。
package com.yimeng.mydemo;import java.util.Arrays;public class Demo5 {/* 问题:MyInferface5 myInferface5 = ??;这里的??可以怎么填,要求它的抽象方法效果是创建一个数组对象,创建数组元素的个数取决于是抽象方法参数的值。** 看题目知道当前存在可以推断出函数式接口的上下文。并且知道要实现的方法长这样:String[] f(Integer i);* 知道这个接口的抽象方法想要实现的功能是:创建一个数组对象,创建数组的个数取决于是抽象方法参数的值。* 这种情况就直接用“类型[]::new”来实现即可。**/public static void main(String[] args) {// 方法引用// 引用类型MyInferface5 myInferface5 = String[]::new;String[] arr = myInferface5.f(2);System.out.println(Arrays.toString(arr));// 基本类型也行MyInferface51 myInferface51 = int[]::new;int[] arr2 = myInferface51.f(3);System.out.println(Arrays.toString(arr2));}
}interface MyInferface5 {String[] f(Integer i);
// String[] f(Object i);// 不行
}interface MyInferface51 {int[] f(int i);
// int[] f(long i);// 不行
}
结果:
[null, null]
[0, 0, 0]
注意:看到执行的结果是创建数组,并且把数组中的值设值为初始化的值。即,这种的调用创建数组的长度取决于传的实参,创建出来的数组里面的元素都是初始值。
怎么通过方法引用推断出lambda表达式和匿名内部类
其实很简单,需要下面两步:
- 通过上下文找到函数式接口和里面的抽象方法
- 找到方法引用借用的方法
1. 类名::静态方法
例子:
package com.yimeng.mydemo2;public class Demo1 {public static void main(String[] args) {new Demo1().f();}private void f() {// 方法引用MyInterface1 myInterface1 = Demo1::Demo1Sum;System.out.println(myInterface1.sum(1,2));// lambda表达式
// MyInterface1 myInterface2 = (a,b)->Demo1Sum(a,b);// 这个也行的。Demo1Sum(a,b)这个相当于是下面Demo1.Demo1Sum(a,b)的省略写法,因为这里使用Demo1Sum方法的地方和Demo1Sum方法在一个类里面嘛。所以可以省略。
// MyInterface1 myInterface2 = (a,b)->Demo1.Demo1Sum(a,b);// 这个是lambda表达式的省略写法MyInterface1 myInterface2 = (int a,Integer b)->{return Demo1.Demo1Sum(a,b);};// 这个是lambda表达式的完整写法。System.out.println(myInterface2.sum(1,2));// 匿名内部类MyInterface1 myInterface3 = new MyInterface1() {@Overridepublic long sum(int a, Integer b) {return Demo1.Demo1Sum(a,b);}};System.out.println(myInterface3.sum(1,2));// 方法引用MyInterface11 myInterface11 = Demo1::Demo1Sum;
// System.out.println(myInterface11.sum(1,2));// 不行,因为抽象方法 void sum(Integer a,Integer b);没有返回值。myInterface11.sum(1,2);// lambda表达式MyInterface11 myInterface12 = (Integer a,Integer b)->{Demo1.Demo1Sum(a,b);};myInterface12.sum(1,2);// 匿名内部类MyInterface11 myInterface13 = new MyInterface11() {@Overridepublic void sum(Integer a, Integer b) {Demo1.Demo1Sum(a,b);}};myInterface13.sum(1,2);}public static int Demo1Sum(Integer a,Integer b){return a+b;}// 如果下面这个注释打开,那么方法引用的就应该是下面这个方法了,而不是int Demo1Sum(Integer a,Integer b)了。因为有多个匹配的重载方法,那么就会调用最匹配的重载方法(重载方法的知识点)。
// public static int Demo1Sum(int a,Integer b){
// return a+b;
// }
}
interface MyInterface1{long sum(int a,Integer b);
}interface MyInterface11{void sum(Integer a,Integer b);
}
执行结果:
3
3
3
找这个Demo2::Demo2Sum借用的方法:
上面的例子展示了方法引用——>lambda表达式——>匿名内部类
的过程,看到把方法引用还原为完整的匿名内部类了,我们可以从匿名内部类看到方法引用具体的省略的细节。
这里看到细节就可以解释为什么函数式接口的抽象方法的形参是(int a,Integer b),返回值是long,可以使用int Demo2Sum(Integer a,Integer b)这个方法作为方法引用的方法了。虽然他们类型没有完全匹配,但是却没有问题。因为上面myInterface1的方法引用相当于是上面myInterface3匿名内部类的写法。long sum(int a, Integer b)中int的a可以传给int Demo2Sum(Integer a,Integer b)中Integer的a,因为自动装箱。int Demo2Sum(Integer a,Integer b)中int的返回值,可以作为long sum(int a,Integer b)的返回值,因为java类型自动转换,自动向上扩展。所以没有问题。
所以,结论是:抽象方法的如果传给被借用方法的形参是可以的,那么就行,如果被借用方法的返回值可以传给抽象方法的返回值就行。没有要求抽象方法和借用方法的形参、返回值完全一样。
我们也看到,抽象方法返回值是void,那么借用方法的返回值可以是任何东西。因为抽象方法是void的时候,使用方法引用,那么方法引用的效果相当于调用了借用的方法,但是不return借用方法的返回值。所以,你借用方法返回值是任何东西都没有关系。这就是对上面说的“返回值是,如果抽象方法如果有返回,那么借用的方法的返回类型就要能被抽象方法返回值所接收就行,如果抽象方法没有返回值,那么随便借用方法返回什么”的解释。
2. 实例::成员方法
package com.yimeng.mydemo2;public class Demo2 extends Demo2Parent {public static void main(String[] args) {Demo2 demo2 = new Demo2();demo2.f();}public void f(){Demo2 demo2 = new Demo2();// 方法引用MyInterface2 myInterface2 = demo2::say;myInterface2.printInt(100);// 方法引用,使用this(和上面的demo2::say效果一样,但是上面这种做法需要多创建一个Demo2对象,下面这种不需要再创建一个Demo2对象了。相当于是直接用main中创建的Demo2对象,this嘛,就是指当前对象,即指调用这个f()方法的对象。)MyInterface2 myInterface21 = this::say;myInterface21.printInt(100);// 转换为lambda表达式和匿名内部类。MyInterface2 myInterface211 = (Integer i)->{this.say(i);// lambda表达式和方法引用中使用的this就直接指向外部类};myInterface211.printInt(100);MyInterface2 myInterface2111 = new MyInterface2() {@Overridepublic void printInt(Integer i) {Demo2.this.say(i);// 匿名内部类中的this是指向匿名内部类的,所以要用Demo2.this才能指向外部类}};myInterface2111.printInt(100);// 方法引用,使用super借用父类的方法MyInterface2 myInterface22 = super::sayParent;myInterface22.printInt(100);MyInterface2 myInterface221 = (Integer i)->{super.sayParent(i);};myInterface221.printInt(100);MyInterface2 myInterface2211 = new MyInterface2() {@Overridepublic void printInt(Integer i) {Demo2.super.sayParent(i);// 和上面的this一样,指向内部类。}};myInterface2211.printInt(100);// 方法引用,使用super借用祖先类的方法MyInterface2 myInterface23 = super::sayGrandpa;myInterface23.printInt(100);MyInterface2 myInterface231 = (Integer i)->{super.sayGrandpa(i);};myInterface231.printInt(100);MyInterface2 myInterface2311 =new MyInterface2() {@Overridepublic void printInt(Integer i) {Demo2.super.sayGrandpa(i);// 和上面的this一样,指向内部类。}};myInterface2311.printInt(100);}public void say(Object o){System.out.println("prefix-Demo2:"+o);}
}
interface MyInterface2{void printInt(Integer i);
// int printInt(Integer i);// 不行,因为返回值不匹配
}
class Demo2Parent extends Demo2Grandpa {public void sayParent(Object o){System.out.println("prefix-Demo2Parent:"+o);}
}
class Demo2Grandpa{public void sayGrandpa(Object o){System.out.println("prefix-Demo2Grandpa:"+o);}
}
结果:
prefix-Demo2:100
prefix-Demo2:100
prefix-Demo2:100
prefix-Demo2:100
prefix-Demo2Parent:100
prefix-Demo2Parent:100
prefix-Demo2Parent:100
prefix-Demo2Grandpa:100
prefix-Demo2Grandpa:100
prefix-Demo2Grandpa:100
找这个demo2::say;借用的方法:先看demo2::say,看到是“实例::XX”,所以就确定是找一个方法名为say的成员方法了。然后看上下文确定函数式接口和抽象方法,我们找到这个抽象方法的形参,用这个形参去对比demo2中say的成员方法,找到一个say成员方法的形参能和void printInt(Integer i);匹配的,就是那个方法了,如果有多个匹配的,那么就找最匹配的。所以找到了void say(Object o)。
注意:匿名内部类中的this是指向匿名内部类的(指向最内层的类),所以要用“外部类.this”才能指向外部类。但是对于lambda表达式和方法引用中使用的this就直接指向外部类了(准确的说是指向lambda表达式和方法引用所在的最内层类)。原因可能是:编译器先去处理this,把this替换为对应的对象引用,然后再去把lambda表达式和方法引用转为匿名内部类的。super也是同理,在匿名内部类内super是指最内层类的,需要用“外部类.super”指向外部类,但是lambda表达式和方法引用中的super是指向外部类的(准确的说是指向lambda表达式和方法引用所在的最内层类)。
3. 类名::成员方法
package com.yimeng.mydemo2;public class Demo3 {public static void main(String[] args) {new Demo3().f();}private void f() {// 抽象方法除第一个参数外,无参MyInterface3 myInterface3 = MyClass3::test;String say = myInterface3.say(new MyClass3());System.out.println(say);// 改为lambda表达式MyInterface3 myInterface31 = (myClass3) -> myClass3.test();String say1 = myInterface31.say(new MyClass3());System.out.println(say1);// 改为匿名内部类MyInterface3 myInterface32 = new MyInterface3() {@Overridepublic String say(MyClass3 myClass3) {return myClass3.test();}};String say2 = myInterface32.say(new MyClass3());System.out.println(say2);// 抽象方法除第一个参数外,还有参数MyInterface33 myInterface33 = MyClass33::test3;String hello = myInterface33.say(new MyClass33(), "hello", 1);System.out.println(hello);// 改为lambda表达式MyInterface33 myInterface333 = (myClass33, s, i) -> myClass33.test3(s, i);String hello1 = myInterface333.say(new MyClass33(), "hello", 1);System.out.println(hello1);// 改为匿名内部类MyInterface33 myInterface334 = new MyInterface33() {@Overridepublic String say(MyClass33 myClass33, String s, int i) {return myClass33.test3(s, i);}};String hello2 = myInterface334.say(new MyClass33(), "hello", 1);System.out.println(hello2);}
}interface MyInterface3 {String say(MyClass3 myClass3);
}class MyClass3 {public String test() {System.out.println("MyClass3的test方法被借用");return "say";}
}interface MyInterface33 {String say(MyClass33 myClass33, String s, int i);
}class MyClass33 {public String test3(String s, int i) {System.out.println("MyClass33的test3方法被借用" + s + i);return "hello";}
}
结果:
MyClass3的test方法被借用
say
MyClass3的test方法被借用
say
MyClass3的test方法被借用
say
MyClass33的test3方法被借用hello1
hello
MyClass33的test3方法被借用hello1
hello
MyClass33的test3方法被借用hello1
hello
找MyClass3::test借用的方法:先看MyClass3的test方法,找到成员方法或者静态方法都行。然后我们看上下文推断出函数式接口,并找到抽象方法。看抽象方法的参数,如果是第一个参数和方法引用借用的类不一样,那么,我们应该找静态方法,并且找的方法和抽象方法的形参匹配才行。如果抽象方法的第一个参数类型和方法引用借用的类是一样的,那么有两种可能,可能找静态方法,也可能找成员方法,找静态方法要求找静态方法的形参与抽象方法的形参匹配的静态方法,找成员方法,就找方法形参和抽象方法除第一个参数外的参数匹配的成员方法。如果找静态方法找到了某个可能借用的方法,找成员方法也找到了某个可能借用的方法,那么这种情况下,idea会提示报错的,因为编译器通过方法引用去匹配借用的方法的时候遇到了语义不明确的问题了。
注意:“类名::成员方法”可能和“类名::静态方法”形成语义不明确
比如:
package com.yimeng.mydemo;public class MyDemo {public static void main(String[] args) {new MyDemo().f();}private void f() {
// MyDemoInterface myDemoInterface = MyDemoClass::test;// 注释打开会语义不明确// 可能会语义不明确的情况下,使用lambda表达式来做或者匿名内部类来做就行了。减少省略就行了。// 使用lambda表达式// 使用成员方法MyDemoInterface myDemoInterface1 = (myDemoClass, s, i) -> myDemoClass.test(s, i);// 使用静态方法MyDemoInterface myDemoInterface2 = (myDemoClass, s, i) -> MyDemoClass.test(myDemoClass, s, i);// 使用匿名内部类// 使用成员方法MyDemoInterface myDemoInterface11 = new MyDemoInterface() {@Overridepublic String say(MyDemoClass myDemoClass, String s, int i) {return MyDemoClass.test(myDemoClass, s, i);}};// 使用静态方法MyDemoInterface myDemoInterface22 = new MyDemoInterface() {@Overridepublic String say(MyDemoClass myDemoClass, String s, int i) {return myDemoClass.test(s, i);}};}
}
interface MyDemoInterface {String say(MyDemoClass myDemoClass, String s, int i);
}
class MyDemoClass {public String test(String s, int i) {System.out.println("MyDemoClass的test(String s, int i)方法被借用" + s + i);return "hello";}public static String test(MyDemoClass myDemoClass,String s, int i) {System.out.println("MyDemoClass的test(MyDemoClass myDemoClass,String s, int i)方法被借用" + s + i);return "hello";}
}
如果把上面的注释打开会语义不明确。
原因:我们通过MyDemoClass::test知道是找MyDemoClass类的test方法,我们找到了两个方法String test(String s, int i)和String test(MyDemoClass myDemoClass,String s, int i)。我们看函数式接口的抽象方法的形参来选择这些重载方法。选择的时候,会看抽象方法的第一个参数是不是方法引用的要借用的方法的类。如果不是,那么只会找静态方法,并且找的静态方法参数要和抽象方法匹配。如果是(即,抽象方法第一个参数类型和要借用的方法所在的类一样),那么可能找参数匹配的静态方法,也可能找成员方法,找成员方法的时候要求成员方法的形参要和抽象方法除第一个参数外的其他参数组成参数列表匹配。上面的例子里面,属于是抽象方法的第一个参数是我们要借用方法的类,所以可能找成员方法,也可能找静态方法。结果发现,找参数列表匹配成员方法,找到了String test(String s, int i),找参数列表和抽象方法除第一个形参外的其他参数组成的参数列表匹配的静态方法,找到了String test(MyDemoClass myDemoClass,String s, int i),这样就导致了编译器不知道借用哪一个方法了,所以就语义不明确了。
这种可能会产生模糊的情况,最好还是用lambda表达式或者匿名内部类来写,这样省略的东西就少了,不用编译器自己推断了,可以避免任何混淆或可能的错误。
4. 类名::new
这个方法引用还原起来很简单,就是找函数式表达式的抽象方法,知道形参。然后找引用的类的构造方法,找到形参和抽象方法形参匹配的构造方法,方法引用就是调用这个构造方法。为什么是找形参和抽象方法形参匹配的构造方法,你看方法引用还原为匿名内部类的代码就知道了。只要你抽象方法的参数可以全部放到构造方法中进行调用就算匹配了。
package com.yimeng.mydemo2;public class Demo4 {public static void main(String[] args) {// 方法引用MyInferface4 myInferface4= Student::new;myInferface4.createInstance("张三");// lambda表达式MyInferface4 myInferface41= (String name)->new Student(name);myInferface41.createInstance("李四");// 匿名内部类MyInferface4 myInferface42= new MyInferface4() {@Overridepublic Student createInstance(String name) {return new Student(name);}};myInferface42.createInstance("王五");}
}
interface MyInferface4{Student createInstance(String name);
}
class Student {private String name;private int age;public Student(String name) {System.out.println("构造方法执行");this.name = name;}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;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}
结果:
构造方法执行
构造方法执行
构造方法执行
5. 类型[]::new
这个方法引用还原起来也是很简单的,这个就拿来纯记忆就行了,就认为“类型[]::new”是等价与抽象方法的实现是“【return】new 类型[抽象方法形参对应的变量];”就行了,至于有没有return,就单纯看抽象方法是不是void就行。
package com.yimeng.mydemo2;import java.util.Arrays;public class Demo5 {public static void main(String[] args) {// 方法引用// 引用类型MyInferface5 myInferface5 = String[]::new;String[] arr = myInferface5.f(2);System.out.println(Arrays.toString(arr));// lambda表达式MyInferface5 myInferface52 = (i) -> new String[i];System.out.println(Arrays.toString(myInferface52.f(3)));// 匿名内部类MyInferface5 myInferface53 = new MyInferface5() {@Overridepublic String[] f(Integer i) {return new String[i];}};System.out.println(Arrays.toString(myInferface53.f(3)));}
}interface MyInferface5 {String[] f(Integer i);
}
结果:
[null, null]
[null, null, null]
[null, null, null]
回顾一下创建数组的知识点
创建数组的几种方式:
摘录自:【Java-数组】Java数组的创建的3种方法6种写法_java创建数组的正确语句-CSDN博客
一维数组
动态创建(4种写法)
第1种:
int a[] = new int[5];//创建长度为5的一维数组等待赋值,初始值为0
第2种:
int[] a1 = new int[5];//创建长度为5的一维数组等待赋值,初始值为0
第3种:
int b[] = new int[] {1,2,3};//声明并创建内存空间且赋值
第4种:
int[] b1 = new int[] {1,2,3};//声明并创建内存空间且赋值
静态创建(2种写法)
第1种:
int c[] = {1,2,3};//声明并创建内存空间,直接赋值
第2种:
int[] c1 = {1,2,3};//声明并创建内存空间,直接赋值
二维数组
动态创建(4种写法)
第1种:
int[][] arr1 = new int[3][3];//声明并创建内存空间,等待赋值,初始值为0
第2种:
int arr2[][] = new int[3][3];//声明并创建内存空间,等待赋值,初始值为0
第3种:
int[][] arr1 = new int[][] {{1,2,3},{4,5,6},{7,8,9}};//声明并创建内存空间且赋值
第4种:
int arr2[][] = new int[][] {{1,2,3},{4,5,6},{7,8,9}};//声明并创建内
静态创建(2种写法)
第1种:
int[][] arr1 = {{1,2,3},{4,5,6},{7,8,9}};//声明并创建内存空间,直接赋值
第2种:
int arr2[][] = {{1,2,3},{4,5,6},{7,8,9}};//声明并创建内存空间,直接赋值
总结
怎么用方法引用
- 抽象方法的实现方法效果是直接借用某个类的静态方法,需要满足的条件:静态方法的形参与函数式接口的抽象方法形参匹配,那么可以直接用“类名::静态方法”。注意语义不明确问题,容易和“抽象方法第一个参数的类名::成员方法”冲突。(需要完全一样吗?答:不需要完全一样。是不是只要抽象方法的返回值能接受借用方法的返回值,并且抽象方法的形参能传给借用方法的形参就行了?答,是的)
- 抽象方法的实现方法效果是直接借用某个实例的成员方法,需要满足的条件是:成员方法的形参和返回值与函数式接口的抽象方法匹配,那么可以直接用“实例::成员方法”(这里还包括“this::方法名”、“super::方法名”)
- 抽象方法的实现方法效果是调用抽象方法第一个形参的成员方法,需要满足的条件是:存在函数式接口的上下文,要调用的方法形参和抽象方法除第一个形参外的其他形参组成的参数列表匹配,那么可以使用“抽象方法第一个参数的类名::成员方法”。注意语义不明确问题,容易和“类名::静态方法”冲突。
- 抽象方法的实现方法想要的效果时调用某个类的构造方法,需要满足的条件是:存在函数式接口的上下文,抽象方法需要的形参和某个构造方法的形参匹配,那么可以用“类名::new”。
- 抽象方法的实现方法想要的效果是创建数组,需要满足的条件是:存在函数式接口的上下文,抽象方法形参是int或者Integer,那么可以用使用”类型[]::new“。创建出来的数组对象的长度是调用抽象方法时传的实参确定的,创建出来的数组中元素的值是初始化的值。
怎么看方法引用
先看上下文,然后推断出抽象方法,再去看方法引用所找的方法。
- 如果是“实例::XXX”就找这个实例变量的类中找成员方法。如果是“this::XXX”就找本类中的成员方法,当然哈,父类祖先类继承下来的成员方法也可以找到。如果是“super::XXX”就找父类和父类继承(祖先类)的成员方法。静态方法不会找。
- 如果是”类名::new“,那么就找这个类名的构造方法,构造方法的形参要和抽象方法的形参兼容。
- 如果是”类型[]::new“,那么调用函数式接口的抽象方法就是返回一个你指定类型的数组,调用的实参决定你创建数组元素的长度,创建后数组中元素的值都是初始化的值。
- 如果是”类名::XX“,就要看看抽象方法的形参第一个参数是不是方法引用的类了。
- 如果不是这个类型,那么就去找这个类名中XX方法名对应的所有静态方法,然后找到形参和抽象方法兼容的方法就是要借用的方法了。(父类和祖先类中的方法要不要找?答:会找)
- 如果抽象第一个参数和方法引用的类一样,那么要找这个类名中对应的所有静态方法(父类和祖先类的静态方法也会找),如果静态方法的形参和抽象方法的形参兼容,那么就找到了,作为备选方法A。但是还是要继续,因为这种情况还可能是借用成员方法。我们继续找成员方法,要找的形参和抽象方法除第一个形参外的其他形参组成的参数列表都兼容的成员方法,作为备选方法B。如果A和B都找到了,那么编译不会过,因为语义不明确。
例子(看到可以继承父类的静态方法的):
package com.yimeng.mydemo;public class Demox1 extends Demox{public static void main(String[] args) {new Demox1().f();}private void f() {int i = Demox1.xSum(2, 4);System.out.println(i);MyInterface1 myInterface1 = Demox1::xSum;System.out.println(myInterface1.sum(1,2));MyInterface1 myInterface2 = Demox1::ySum;System.out.println(myInterface2.sum(4,5));}
}
interface MyInterfacex1{long sum(int a,Integer b);
}
class Demox extends Demoy{public static int xSum(Integer a,Integer b){return a+b;}
}class Demoy{public static int ySum(Integer a,Integer b){return a+b;}
}
结果:
6
3
9
补充:函数式接口
函数式接口:
- 如果一个接口有且仅有一个抽象方法(非抽象方法无论有多少个都行),那么该接口就是一个函数式接口。
- 如果我们在某个接口上声明了 @FunctionalInterface 注解,那么编译器就会按照函数式接口的定义来要求该接口。效果是什么呢?比如我们在加上@FunctionalInterface注解的接口中写了两个抽象方法,程序编译就会报错的,相当于给我们程序员的一个提醒,一般你加了这个注解,就想要借这个接口来使用lambda表达式或者方法引用,要使用方法引用或者lambda表达式,就要求接口一定只有一个抽象方法,所以我们确定某个地方一定借这个接口来使用lambda表达式或者方法引用的话,我们在这个接口上面加上这个注解,那么我们就必须在接口中写一个抽象方法了,这样就不会有问题了,相当于给自己加上一个检查机制。没有加这个注解的接口有且仅有一个抽象方法的话,那么这个接口也是函数式接口。只是加上这个注解编译器就会自动进行检测,相当于你告诉编译器某个接口要符合函数式接口的定义,如果不符合就提前告知你,是把错误前置的一种做法。
- 函数式接口一般都是为lambda表达式的使用而存在的。当然哈,你不想用lambda表达式也可以写函数式接口哈。
@FunctionalInterface的例子:
package com.yimeng;// 函数式接口只能有一个抽象方法
@FunctionalInterface
public interface FuncTest {void accept(Object o);
}
如果在函数式接口中写两个以上的方法,编译会报错:
我们来自己写一个实际的例子了解下函数式接口的使用:
package com.yimeng;// 函数式接口只能有一个抽象方法
// 1.写一个对传入参数进行操作的函数式接口
@FunctionalInterface
public interface FuncTest {Integer operation(Integer x);
}
class Main{public static void main(String[] args) {// 3.借用函数式接口写lambda表达式System.out.println(operate(1, (x) -> x + x)); // 输出 2}// 2.写一个方法,将函数式接口作为参数private static Integer operate(Integer a, FuncTest funcTest) {return funcTest.operation(a);}
}
Java8内置的函数式接口
常见的4种函数式接口
我们想使用lambda表达式或者方法引用就需要一个函数式接口,所以上面的很多例子中,我们都自己创建了函数式接口,然后在利用这个函数式接口构建出上下文环境,再使用lambda表达式。但是其实,java内其实已经给我们定义了一些比较常见比较通用的函数式接口了,你可以直接用的,避免了你重新定义了。
四种常用的函数式接口:
-
Consumer :消费型接口
方法:void accept(T t);
Consumer<String> consumer = x -> System.out.println(x);// 内部类处理是打印实参 consumer.accept("Hello"); // 打印Hello
-
Supplier :供给型接口
方法:T get();
Supplier<MyTest> supplier = () -> new MyTest();// 内部类处理是创建MyTest对象并返回 System.out.println(supplier.get()); // 打印com.yimeng.MyTest@7cca494b
-
Function<T, R> :函数型接口
方法:R apply(T t);
Function<String, Integer> function = x -> x.length();// 内部处理是拿到字符串的长度 System.out.println(function.apply("Hello")); // 打印5
-
Predicate :断言型接口
方法:boolean test(T t);
Predicate<String> predicate = x -> x.isEmpty();// 内部处理是判断是否为空 System.out.println(predicate.test("Hello")); // 打印false
例子:
package com.yimeng;import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;public class MyTest {public static void main(String[] args) {// 1. 消费型接口,方法:void accept(T t);// 适合:实际调用的时候传一个参数,然后交给内部处理,结果不返回的情况。// lambda表达式Consumer<String> consumer = x -> System.out.println(x);// 内部类处理是打印实参consumer.accept("Hello"); // 打印Hello// 方法引用Consumer<String> consumer2 = System.out::println;consumer2.accept("Hello"); // 打印Hello// 2. 供给型接口,方法:T get();// 适合:实际调用的时候不需要传参数,然后内部处理后,直接返回处理结果的情况。// lambda表达式Supplier<MyTest> supplier = () -> new MyTest();// 内部类处理是创建MyTest对象并返回System.out.println(supplier.get()); // 打印com.yimeng.MyTest@7cca494b// 方法引用Supplier<MyTest> supplier2 = MyTest::new;System.out.println(supplier2.get()); // 打印com.yimeng.MyTest@7699a589// 3. 函数型接口,方法:R apply(T t);// 适合:实际调用的时候传一个参数,然后交给内部处理,直接返回处理结果的情况。// lambda表达式Function<String, Integer> function = x -> x.length();// 内部处理是拿到字符串的长度System.out.println(function.apply("Hello")); // 打印5// 方法引用Function<String, Integer> function2 = String::length;System.out.println(function2.apply("Hello")); // 打印5// 4. 断言型接口,方法:boolean test(T t);// 适合:实际调用的时候传一个参数,然后交给内部处理,结果返回boolean类型的情况。// lambda表达式Predicate<String> predicate = x -> x.isEmpty();// 内部处理是判断是否为空System.out.println(predicate.test("Hello")); // 打印false// 方法引用Predicate<String> predicate2 = String::isEmpty;System.out.println(predicate2.test("Hello")); // 打印false}
}
执行结果:
Hello
Hello
com.yimeng.MyTest@7cca494b
com.yimeng.MyTest@7699a589
5
5
false
false
如果上面四个不够用,那么可以使用下面Java.util.function其他一些函数式接口。
其他函数式接口
Java.util.function 它包含了很多类,用来支持 Java的 函数式编程,该包中的函数式接口有:
怎么用起来lambda表达式和方法引用
从我们开发者来思考怎么把lambda表达式用起来。
比如这样一个案例:我们开发的时候经常会操作数据库,遇到批量更新或者批量插入的情况,我们如果在for循环里面一个个插入或者更新,这样的效率很低下,原因是IO次数太多了。所以我们想做到把要批量执行的sql一次给到数据库,然后批量执行,这样就减少了IO。做法也很简单,就是利用JDBC的flushStatements方法来完成,我们把插入或者更新语句放到一个缓存里面去,到一定数量的时候,批量拿到的数据库去执行就行了。但是这里的问题是,如果我们每一个想批量插入或者更新的语句都这样写,操作很麻烦,所以,我们想写一个通用的方法。但是通用的方法,不同mapper要执行的方法是不同的,执行的逻辑是不同的,那么怎么来做到通用性呢?其实这里就可以用函数式接口和lambda表达式来解决。
我们定义了一个方法batchUpdateOrInsert,第一个参数是要操作的数据List,使用泛型可以更加具有通用性。第二个参数是,一个函数式接口,里面定义一个方法,方法的作用是插入或者更新单个数据。具体怎么操作,看这个接口的实现就行了,反正我们只要指定这个抽象方法的效果是插入或者更新单个数据就行,这个方法需要一个参数,参数是要更新或者插入的单个数据对象,抽象方法的返回值是插入和更新影响的行数。
具体的实现,我们可以用lambda表达式或者方法引用来写的。
package com.yimeng;import java.util.Arrays;
import java.util.List;public class MybatisBatchUtils {/*** 每次处理1000条。*/private static final int BATCH_SIZE = 1000;/*** 批量处理修改或者插入** @param data 需要批量被查找和插入的数据* @param processDatabase 自定义处理逻辑* @return int 影响的总行数*/public <T> int batchUpdateOrInsert(List<T> data,ProcessDatabase<T> processDatabase) {int i = 1;System.out.println("开启事务,并拿到SqlSession");try {// 拿到数据的总数int size = data.size();// 分批处理for (T element : data) {// 想要借用一个参数的方法。processDatabase.insertOrUpdate(element);if ((i % BATCH_SIZE == 0) || i == size) {// 可以减少数据库的IO操作System.out.println("批量刷新缓存,一次执行到数据库");}i++;}System.out.println("提交事务");} catch (Exception e) {System.out.println("回滚事务");} finally {System.out.println("关闭SqlSession");}return i - 1;}
}// 执行
class AccessingTheDB{public static void main(String[] args) {MybatisBatchUtils mybatisBatchUtils=new MybatisBatchUtils();UserMapper userMapper=new UserMapper();List<String> userList= Arrays.asList("user1","user2","user3");mybatisBatchUtils.batchUpdateOrInsert(userList,(user)->userMapper.insertUser(user));mybatisBatchUtils.batchUpdateOrInsert(userList, userMapper::updateUser);}
}
// 具体的操作
class UserMapper{public int insertUser(String user){System.out.println("insert user: " + user);return 1;}public int updateUser(String user){System.out.println("update user: " + user);return 1;}
}
// 操作数据库
interface ProcessDatabase<T>{// 插入或者更新数据库,参数是要插入或者更新的数据对象,返回值是影响的行数int insertOrUpdate(T data);
}
执行结果:
开启事务,并拿到SqlSession
insert user: user1
insert user: user2
insert user: user3
批量刷新缓存,一次执行到数据库
提交事务
关闭SqlSession
开启事务,并拿到SqlSession
update user: user1
update user: user2
update user: user3
批量刷新缓存,一次执行到数据库
提交事务
关闭SqlSession
上面这里的函数式接口可以使用java内置的函数式接口来替代。
我们找一找有没有存在只有一个形参的,返回值是int的抽象方法的函数式接口:
找到了,我们现在进行改造:
package com.yimeng;import java.util.Arrays;
import java.util.List;
import java.util.function.ToIntFunction;public class MybatisBatchUtils {/*** 每次处理1000条。*/private static final int BATCH_SIZE = 1000;/*** 批量处理修改或者插入** @param data 需要批量被查找和插入的数据* @param processDatabase 自定义处理逻辑* @return int 影响的总行数*/
// public <T> int batchUpdateOrInsert(List<T> data,ProcessDatabase<T> processDatabase) {public <T> int batchUpdateOrInsert(List<T> data, ToIntFunction<T> processDatabase) {int i = 1;System.out.println("开启事务,并拿到SqlSession");try {// 拿到数据的总数int size = data.size();// 分批处理for (T element : data) {// 想要借用一个参数的方法。
// processDatabase.insertOrUpdate(element);processDatabase.applyAsInt(element);if ((i % BATCH_SIZE == 0) || i == size) {// 可以减少数据库的IO操作System.out.println("批量刷新缓存,一次执行到数据库");}i++;}System.out.println("提交事务");} catch (Exception e) {System.out.println("回滚事务");} finally {System.out.println("关闭SqlSession");}return i - 1;}
}// 执行
class AccessingTheDB{public static void main(String[] args) {MybatisBatchUtils mybatisBatchUtils=new MybatisBatchUtils();UserMapper userMapper=new UserMapper();List<String> userList= Arrays.asList("user1","user2","user3");mybatisBatchUtils.batchUpdateOrInsert(userList,(user)->userMapper.insertUser(user));mybatisBatchUtils.batchUpdateOrInsert(userList, userMapper::updateUser);}
}
// 具体的操作
class UserMapper{public int insertUser(String user){System.out.println("insert user: " + user);return 1;}public int updateUser(String user){System.out.println("update user: " + user);return 1;}
}
操作数据库
//interface ProcessDatabase<T>{
// // 插入或者更新数据库,参数是要插入或者更新的数据对象,返回值是影响的行数
// int insertOrUpdate(T data);
//}
执行结果:
开启事务,并拿到SqlSession
insert user: user1
insert user: user2
insert user: user3
批量刷新缓存,一次执行到数据库
提交事务
关闭SqlSession
开启事务,并拿到SqlSession
update user: user1
update user: user2
update user: user3
批量刷新缓存,一次执行到数据库
提交事务
关闭SqlSession
看到效果一样!
抽象方法的设计还是比较多样的,看你想怎么用吧。
比如你也可以这样:
package com.yimeng;import java.util.Arrays;
import java.util.List;public class MybatisBatchUtils {/*** 每次处理1000条。*/private static final int BATCH_SIZE = 1000;/*** 批量处理修改或者插入** @param data 需要批量被查找和插入的数据* @param mapper 执行的mapper* @param processDatabase 自定义处理逻辑* @return int 影响的总行数*/public <T,U> int batchUpdateOrInsert(List<T> data,U mapper,ProcessDatabase<U,T> processDatabase) {int i = 1;System.out.println("开启事务,并拿到SqlSession");try {// 拿到数据的总数int size = data.size();// 分批处理for (T element : data) {// 想要借用一个参数的方法。processDatabase.handle(mapper,element);if ((i % BATCH_SIZE == 0) || i == size) {// 可以减少数据库的IO操作System.out.println("批量刷新缓存,一次执行到数据库");}i++;}System.out.println("提交事务");} catch (Exception e) {System.out.println("回滚事务");} finally {System.out.println("关闭SqlSession");}return i - 1;}
}// 执行
class AccessingTheDB{public static void main(String[] args) {MybatisBatchUtils mybatisBatchUtils=new MybatisBatchUtils();UserMapper userMapper=new UserMapper();List<String> userList= Arrays.asList("user1","user2","user3");mybatisBatchUtils.batchUpdateOrInsert(userList,userMapper,(u,user)->u.insertUser(user));mybatisBatchUtils.batchUpdateOrInsert(userList,userMapper,(u,user)->u.updateUser(user));}
}
class UserMapper{public int insertUser(String user){System.out.println("insert user: " + user);return 1;}public int updateUser(String user){System.out.println("update user: " + user);return 1;}
}
interface ProcessDatabase<T,U>{// 调用mapper的方法,date作为参数,返回值是影响行数int handle(T date,U mapper);
}
执行结果:
开启事务,并拿到SqlSession
insert user: user1
insert user: user2
insert user: user3
批量刷新缓存,一次执行到数据库
提交事务
关闭SqlSession
开启事务,并拿到SqlSession
update user: user1
update user: user2
update user: user3
批量刷新缓存,一次执行到数据库
提交事务
关闭SqlSession
使用内置的函数式接口:
package com.yimeng;import java.util.Arrays;
import java.util.List;
import java.util.function.ToIntBiFunction;public class MybatisBatchUtils {/*** 每次处理1000条。*/private static final int BATCH_SIZE = 1000;/*** 批量处理修改或者插入** @param data 需要批量被查找和插入的数据* @param mapper 执行的mapper* @param processDatabase 自定义处理逻辑* @return int 影响的总行数*/
// public <T,U> int batchUpdateOrInsert(List<T> data,U mapper,ProcessDatabase<U,T> processDatabase) {public <T,U> int batchUpdateOrInsert(List<T> data, U mapper, ToIntBiFunction<U,T> processDatabase) {int i = 1;System.out.println("开启事务,并拿到SqlSession");try {// 拿到数据的总数int size = data.size();// 分批处理for (T element : data) {// 想要借用一个参数的方法。
// processDatabase.handle(mapper,element);processDatabase.applyAsInt(mapper,element);if ((i % BATCH_SIZE == 0) || i == size) {// 可以减少数据库的IO操作System.out.println("批量刷新缓存,一次执行到数据库");}i++;}System.out.println("提交事务");} catch (Exception e) {System.out.println("回滚事务");} finally {System.out.println("关闭SqlSession");}return i - 1;}
}// 执行
class AccessingTheDB{public static void main(String[] args) {MybatisBatchUtils mybatisBatchUtils=new MybatisBatchUtils();UserMapper userMapper=new UserMapper();List<String> userList= Arrays.asList("user1","user2","user3");mybatisBatchUtils.batchUpdateOrInsert(userList,userMapper,(u,user)->u.insertUser(user));mybatisBatchUtils.batchUpdateOrInsert(userList,userMapper,(u,user)->u.updateUser(user));}
}
class UserMapper{public int insertUser(String user){System.out.println("insert user: " + user);return 1;}public int updateUser(String user){System.out.println("update user: " + user);return 1;}
}
//interface ProcessDatabase<T,U>{
// // 调用mapper的方法,date作为参数,返回值是影响行数
// int handle(T date,U mapper);
//}
执行结果:
开启事务,并拿到SqlSession
insert user: user1
insert user: user2
insert user: user3
批量刷新缓存,一次执行到数据库
提交事务
关闭SqlSession
开启事务,并拿到SqlSession
update user: user1
update user: user2
update user: user3
批量刷新缓存,一次执行到数据库
提交事务
关闭SqlSession
看到使用函数式接口的好处其实主要是,在定义通用方法的时候会用。效果就是,我们知道要做的操作是什么功能,操作要接收的参数和返回值是什么,暂时不用管具体是怎么实现的,我们写通用方法具体逻辑的时候就当这个抽象方法已经按我们想的方式来实现好了,我们直接按照我们想的这个抽象方法的功能来用这个抽象方法把通用方法的逻辑写好就行了。然后就是使用阶段了,使用时我们实际去实现这个抽象方法,就按我们具体的逻辑去写就行,想怎么填充就怎么填充,有很大的灵活性,但是注意,你实现这个抽象方法的时候,要按照我们当时设计这个抽象方法的功能的模样去实现,比如需要的参数是什么,返回值是什么,方法的功能是什么都是已经想象好的,你的实现不能脱离当时设计通用方法时对这个抽象方法的设想。
下面再来做一个例子:
平时业务中,我们经常有创建对象,并给对象赋值的操作。一般,我们都类似User user=new User();user.setName(“张三”);user.setAge(12);这样操作。但是这样比较麻烦,我们就想能不能写一个工具类,使用链式编程,并且可以使用lambda表达式来创建对象。
代码:
package com.yimeng;import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;public class Builder<T> {private final Supplier<T> instantiator;private List<Consumer<T>> modifiers = new ArrayList<>();public Builder(Supplier<T> instantiator) {this.instantiator = instantiator;}public static <T> Builder<T> of(Supplier<T> instantiator) {return new Builder<>(instantiator);}public <P1> Builder<T> with(Consumer1<T, P1> consumer, P1 p1) {Consumer<T> c = instance -> consumer.accept(instance, p1);modifiers.add(c);return this;}public <P1, P2> Builder<T> with(Consumer2<T, P1, P2> consumer, P1 p1, P2 p2) {Consumer<T> c = instance -> consumer.accept(instance, p1, p2);modifiers.add(c);return this;}public <P1, P2, P3> Builder<T> with(Consumer3<T, P1, P2, P3> consumer, P1 p1, P2 p2, P3 p3) {Consumer<T> c = instance -> consumer.accept(instance, p1, p2, p3);modifiers.add(c);return this;}public T build() {T value = instantiator.get();modifiers.forEach(modifier -> modifier.accept(value));modifiers.clear();return value;}/*** 1 参数 Consumer*/@FunctionalInterfacepublic interface Consumer1<T, P1> {void accept(T t, P1 p1);}/*** 2 参数 Consumer*/@FunctionalInterfacepublic interface Consumer2<T, P1, P2> {void accept(T t, P1 p1, P2 p2);}/*** 3 参数 Consumer*/@FunctionalInterfacepublic interface Consumer3<T, P1, P2, P3> {void accept(T t, P1 p1, P2 p2, P3 p3);}
}
class User{private String name;private int age;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;}@Overridepublic String toString() {return "User{" +"name='" + name + '\'' +", age=" + age +'}';}
}
class MyMain{public static void main(String[] args) {// 使用老的做法User zhangsan=new User();zhangsan.setName("张三");zhangsan.setAge(12);System.out.println(zhangsan);// User{name='张三', age=12}// lambda表达式的做法User lisi = Builder.of(() -> new User()).with((u, name) -> u.setName(name), "李四").with((u, age) -> u.setAge(age), 12).build();System.out.println(lisi);// User{name='李四', age=12}// 方法引用User wangwu = Builder.of(User::new).with(User::setName, "王五").with(User::setAge, 12).build();System.out.println(wangwu);// User{name='王五', age=12}User zhaoliu = new Builder<User>(User::new).with(User::setName, "赵六").with(User::setAge, 12).build();System.out.println(zhaoliu);// User{name='赵六', age=12}}
}
结果:
User{name='张三', age=12}
User{name='李四', age=12}
User{name='王五', age=12}
User{name='赵六', age=12}
分析下面这个语句执行为什么能返回一个User,并且返回时还设置了对应的属性值:
User lisi = Builder.of(() -> new User()).with((u, name) -> u.setName(name), "李四").with((u, age) -> u.setAge(age), 12).build();
System.out.println(lisi);// User{name='李四', age=12}
- Builder.of(() -> new User())是调用了of方法,of方法又调用了new Builder<>(instantiator),创建Builder对象。
- 创建对象的时候,把这个() -> new User()实例给instantiator成员变量了。通过分析这个lambda表达式知道,我们之后要是调用instantiator的抽象方法get()【Supplier的抽象方法是T get();】,就会创建就是相当于执行了new User();
- .with((u, name) -> u.setName(name), “李四”)。因为Builder.of(() -> new User())返回的是Builder对象,所以可以调用with方法。调用with方法,看到调用的第一个参数是lambda表达式,所以知道,(u, name) -> u.setName(name)是一个Consumer1类型的对象,并且调用这个对象的accept方法【void accept(T t, P1 p1);是Consumer1的抽象方法】就是相当于是执行“accept第一个参数的setName(accept第二个参数)”。with方法第二个参数是字符串“李四”,所以p1是“李四”。所以consumer.accept(instance, p1)的效果是,相当于调用“instance.setName(“李四”)”。所以Consumer c = instance -> consumer.accept(instance, p1);就是说,之后调用c的accept方法【void accept(T t);是Consumer的抽象方法】,就是调用“accept传的参数的setName(“李四”)”。with中最后把c放到了List<Consumer> modifiers中。
- .with((u, age) -> u.setAge(age), 12)也是和上面的3点类似,不分析了。相当于是之后调用c的accept方法【void accept(T t);是Consumer的抽象方法】,就是调用“accept传的参数的setAge(12)”。区别是,3点中,创建的c对象是modifiers的第一个元素,而4点中创建的c对象【3创建的c对象和4创建的c对象不是一个对象哈,只是局部变量叫c而已】是modifiers的第二个元素。
- 最后执行builder方法,先执行T value = instantiator.get();返回的是一个T,根据1点,我们知道这里调用instantiator.get();相当于是执行了new User(),所以返回的是User对象。然后执行modifiers.forEach(modifier -> modifier.accept(value));即,把modifiers中的对象按顺序来执行他们的accept方法,并且把User对象作为参数传进去,因为执行的时候是看实际的对象的,所以执行第一个元素的accept方法,效果是给value.setName(“李四”)【看3点】,调用第二个元素的accept方法,效果是给value.setAge(12)【看4点】。这样后,那么这个value值就被设置了对应的属性的值了。然后对modifiers进行clear。最后返回这个value。这样我们拿到的User就是设置好属性后的结果了。
相关文章:
方法引用和lambda表达式的奥妙
方法引用替代Lambda表达式 什么情况可以使用方法引用替代lambda表达式? 下面代码中两处使用了lambda表达式,一个是filter内,一个是forEach内。其中,forEach内的lambda表达式可以被方法引用替代,但是filter内的lambda…...
AI 智能名片 S2B2C 商城小程序在社群团购运营中的作用与价值
摘要:本文深入探讨了 AI 智能名片 S2B2C 商城小程序在社群团购运营中的重要作用。随着社群团购的兴起,如何有效运营成为关键问题。AI 智能名片 S2B2C 商城小程序凭借其独特功能,能够在促进消费者互动、提升产品传播效果、影响购买决策以及实现…...
设计模式の建造者适配器桥接模式
文章目录 前言一、建造者模式二、适配器模式2.1、对象适配器2.2、接口适配器 三、桥接模式 前言 本篇是关于设计模式中建造者模式、适配器模式(3种)、以及桥接模式的笔记。 一、建造者模式 建造者模式是属于创建型设计模式,通过一步步构建一个…...
.net framework手动升级到.net core注意点
因为项目原因,还使用着比较原始的 .NETFramework框架,但因为某种原因,暂时不让升级到.NET 6。为了能够解锁更多 VisualStudio2022的功能,尝试手动修改 csproj文件。 这个过程中,也会遇到不少坑,再次做个记…...
排队论、负载均衡和任务调度关系
目录 排队论、负载均衡和任务调度关系 一、排队论 二、负载均衡 三、任务调度 四、总结 排队论、负载均衡和任务调度关系 排队论为负载均衡和任务调度提供了数学理论和方法支持 排队论、负载均衡和任务调度是三个相关但不同的概念。以下是对这三个概念的详细解释和它们之…...
【C++图论】1042. 不邻接植花|1712
本文涉及知识点 C图论 LeetCode1042. 不邻接植花 有 n 个花园,按从 1 到 n 标记。另有数组 paths ,其中 paths[i] [xi, yi] 描述了花园 xi 到花园 yi 的双向路径。在每个花园中,你打算种下四种花之一。 另外,所有花园 最多 有…...
AI开源南京分享会回顾录
AI 开源南京分享会,已于2024年11月30日下午在国浩律师(南京)事务所5楼会议厅成功举办。此次活动由 KCC南京、PowerData、RISC-Verse 联合主办,国浩律师(南京)事务所协办。 活动以“开源视角的 AI 对话”为主…...
Java版-图论-最短路-Floyd算法
实现描述 网络延迟时间示例 根据上面提示,可以计算出,最大有100个点,最大耗时为100*wi,即最大的耗时为10000,任何耗时计算出来超过这个值可以理解为不可达了;从而得出实现代码里面的: int maxTime 10005…...
ChatGPT大模型 创作高质量文案的使用教程和案例
引言 随着人工智能技术的飞速发展,大语言模型如 ChatGPT 在创作文案、生成内容方面展现出了强大的能力。无论是个人用户还是企业用户,都可以利用 ChatGPT 提高工作效率、激发创意、甚至解决实际问题。本文将详细介绍 ChatGPT 如何帮助创作各类高质量文案,并通过具体案例展示…...
SQL注入及解决
SQL注入是一种常见的网络攻击方式,攻击者通过在输入字段中插入恶意的SQL代码,诱使应用程序执行攻击者构造的SQL语句,从而达到非法获取数据、篡改数据或执行恶意操作的目的。 以下是SQL注入的主要原理总结: 1. 核心原理 SQL注入…...
uni-app多环境配置动态修改
前言 这篇文章主要介绍uniapp在Hbuilderx 中,通过工程化,区分不同环境、动态修改小程序appid以及自定义条件编译,解决代码发布和运行时手动切换问题。 背景 当我们使用uniapp开发同一个项目发布不同的环境二级路径不同时,这时候…...
EasyPlayer.js播放器如何在iOS上实现低延时直播?
随着流媒体技术的迅速发展,H5流媒体播放器已成为现代网络视频播放的重要工具。其中,EasyPlayer.js播放器作为一款功能强大的H5播放器,凭借其全面的协议支持、多种解码方式以及跨平台兼容性,赢得了广泛的关注和应用。 那么要在iOS上…...
mHand Pro动捕数据手套在人形机器人领域的具体运用
mHandPro是一款高精度的动作捕捉数据手套,可应用于动作捕捉与VR交互等领域,配套”mHand Studio“引擎,可实时捕捉真人手部位姿及运动轨迹数据,将数据导出还可以用于人形机器人的训练加速高精度机器人操作技能的培训进程。 高精度动…...
【css常用动画总结01】
一、效果如下: 屏幕录制2024-11-27 17.28.30 二、css常用动画代码: .flex-box{position: relative; } .animation-all {display: flex;p{margin:0;font-size: 12px;}.animate-test1 {width: 102.4px;height: 102.4px;background: url(../assets/images/…...
从入门到精通:系统化棋牌游戏开发全流程教程
棋牌游戏开发需要丰富的技术知识和全面的规划,从开发环境搭建到实际功能实现,步骤清晰且逻辑严谨。以下是完整教程,涵盖了每个关键环节,并提供相关软件的具体下载地址,助力开发者高效完成棋牌游戏项目。 一、开发环境准…...
MyBatis 框架学习与实践
引言 MyBatis 是一个流行的 Java 持久层框架,它提供了简单的方法来处理数据库中的数据。本文将结合笔记和图片内容,详细讲解 MyBatis 的使用,包括配置、注解、优化技巧以及如何处理特殊字符和参数。 1. MyBatis 基础 1.1 引入依赖 首先&a…...
数据可视化的Python实现
一、GDELT介绍 GDELT ( www.gdeltproject.org ) 每时每刻监控着每个国家的几乎每个角落的 100 多种语言的新闻媒体 -- 印刷的、广播的和web 形式的,识别人员、位置、组织、数量、主题、数据源、情绪、报价、图片和每秒都在推动全球社会的事件,GDELT 为全…...
微信小程序实现联动删除输入验证码框
以下是json代码 {"component": true,"usingComponents": {} }以下是wxml代码 <van-popup show"{{ show }}" bind:close"onClose" custom-class"extract"><image src"../../images/extract/icon1.png"…...
C语言程序设计P6-1【应用指针进行程序设计 | 第一节】——知识要点:指针的概念、定义和运算、指针变量作函数的参数
知识要点:指针的概念、定义和运算、指针变量作函数的参数 视频: 目录 一、任务分析 二、必备知识与理论 三、任务实施 一、任务分析 输入两个整数,按大小顺序输出,要求用函数处理,而且用指针类型的数据作函数参数…...
C++编程: 基于cpp-httplib和nlohmann/json实现简单的HTTP Server
文章目录 0. 引言1. 完整实例代码2. 关键实现3. 运行与测试 0. 引言 本文基于 cpp-httplib 和 nlohmann/json 实现简单的 HTTPS Server 实例代码,这两个库均是head-only的。 1. 完整实例代码 如下实例程序修改自example/server.cc #include <httplib.h>#i…...
多模态大模型(二)——用Transformer Encoder和Decoder的方法(BLIP、CoCa、BEiTv3)
文章目录 BLIP: Bootstrapping Language-Image Pre-training for Unified Vision-Language Understanding and Generation 理解、生成我都要,一个很有效的、根据图片生成caption的工具1. BLIP的研究动机2. BLIP的模型结构3. CapFilt Model4. BLIP的训练过程 CoCa: C…...
SpringBoot快速入门
SpringBoot 文章目录 SpringBoot1. Spring Boot 概念2. Spring 使用痛点3. Spring Boot功能4. 快速搭建5. 起步依赖原理6. SpringBoot 配置6.1 配置文件6.2 YAML介绍6.3 YAML语法6.4 YAML数据6.5 YAML参数引用 7.配置数据读取7.1 Value("${}")7.2 Environment7.3 Con…...
Qt编写区位码gb2312、机内码、国标码————附带详细介绍和编码实现
文章目录 0 背景1 了解编码1.1 ASCII码1.2 机内码、国标码、区位码1.2.1 区位码1.2.2 国标码(GB 2312-80)1.2.3 汉字机内码(GB 2312) 1.3 GBK和GB2312的区别2 编码实现2.1 QString数据转QByteArray类型2.1.1 使用QTextCodec2.1.2 …...
IDEA 未启用lombok插件的Bug
项目中maven已引用了lombok依赖,之前运行没有问题的,但有时启动会提示: java: You arent using a compiler supported by lombok, so lombok will not work and has been disabled. Your processor is: com.sun.proxy.$Proxy8 Lombok support…...
R语言学习笔记-1
1. 基础操作和函数 清空环境:rm(list ls()) 用于清空当前的R环境。 打印输出:print("Hello, world") 用于输出文本到控制台。 查看已安装包和加载包: search():查看当前加载的包。install.packages("package_na…...
NFT的公链及开放联盟链信息整理
BSN链 名称内容类型开放联盟链网址https://www.bsnbase.com/节点信息登陆后可免费获取区块链浏览器可查看交易详情使用案例光明艺品、数藏中国、千寻数藏、集集文创、乾坤数藏 至信链 名称内容类型开放联盟链网址https://zxchain.qq.com/节点信息需要登录并充值后获取区块链…...
android notification
前言 在做应用时,时常需要在通知栏显示一条通知,那么具体流程是怎样的呢,怀着这样的探究目的,来进行一步步源码分析。 源码梳理 package com.android.server; ... public final class SystemServer implements Dumpable {...pr…...
C# 多态性
文章目录 前言一、多态性的定义二、C# 中的多态性实现方式1. 方法重写(Overriding)2. 方法重载(Overloading)3. 接口实现(Interface implementation) 三、多态性的优点1. 提高代码的可维护性2. 增强代码的可…...
类与对象以及ES6的继承
认识class定义类 类的声明用的比较多 类与构造函数的异同 类的构造函数 类的实例方法 类的访问器方法 在类里面写拦截方法 类的静态方法 通过类名直接访问 es6类的继承-extends super关键字 子类可以重写父类方法包括父类的静态方法也可以继承父类的静态方法 babel可以将新的代…...
每日一站技術架構解析之-cc手機桌布網
# 網站技術架構解析: ## 一、整體架構概述https://tw.ccwallpaper.com是一個提供手機壁紙、桌布免費下載的網站,其技術架構設計旨在實現高效的圖片資源管理與用戶訪問體驗優化。 ### (一)前端展示 1. **HTML/CSS/JavaScript基礎構…...
【橘子容器】如何构建一个docker镜像
你肯定打过docker镜像是吧,作为一个开发这很正常,那么你用的什么打包方式呢,这里我们来梳理几种常用的docker镜像构建方式。 ps:这里不是太讲原理,更多的是一种科普和操作。因为讲原理的东西网上已经够多了。 一、Dock…...
【漏洞复现】CVE-2024-34102 Magento Open Source XXE漏洞
目录 漏洞介绍 影响版本 环境搭建 查看版本 漏洞复现 手动复现 漏洞 poc Magento Open Source 是一个免费开源的电子商务平台,适合中小企业或开发团队通过自定义代码和插件创建在线商店。它由社区开发和支持,功能强大但需要更多的技术投入。Adobe…...
数据结构 ——二叉树转广义表
数据结构 ——二叉树转广义表 1、树转广义表 如下一棵树,转换为广义表 root(c(a()(b()()))(e(d()())(f()(j(h()())())))) (根(左子树)(右子树)) 代码实现 #include<stdio.h> #include<stdlib.h>//保存…...
Redis篇-6--原理篇5--单线程模型
1、概述 Redis 采用单线程模型来处理客户端请求,这意味着在任意时刻只有一个命令被执行。这种设计简化了 Redis 的实现,并确保了高并发环境下的数据一致性。尽管 Redis 是单线程的,但它通过高效的内存管理和网络 I/O 操作,仍然能…...
LSTM详解
1. LSTM设计 LSTM(长短期记忆网络)详解 长短期记忆网络(LSTM, Long Short-Term Memory) 是一种特殊的循环神经网络(RNN),特别适合处理和预测序列数据中的长时间依赖关系。LSTM 通过引入“门机制”(如输入门、遗忘门、输出门)来解决标准 RNN 在长时间序列任务中梯度消…...
Docker 安装 Seata2.0.0 (快速配置)
说明:已安装Docker、MySql等,案例使用Mysql数据库模式、Nacos配置信息 1、准备工作 1.1 拉取镜像 [rootTseng ~]# docker pull seataio/seata-server:2.0.0 2.0.0: Pulling from seataio/seata-server 001c52e26ad5: Already exists d9d4b9b6e964: P…...
文件断点续传(视频播放,大文件下载)
客户端每次请求取大文件部分数据。 浏览器播放mp4视频时,会首先传Range消息头,检测到206状态码,和Content-Range,Accept-Ranges 会自动请求余下数据。后端需要在文件任意偏移量取数据。 参考: springboot项目实现断…...
神经网络基础-初识神经网络
人工神经网络( Artificial Neural Network, 简写为ANN)也简称为神经网络(NN),是一种模仿生物神经网络结构和功能的计算模型。人脑可以看做是一个生物神经网络,由众多的神经元连接而成。各个神经…...
爬虫获取的数据能否用于商业分析?
根据搜索结果,爬虫获取的数据能否用于商业分析,主要取决于以下几个因素: 数据的合法性与合规性: 爬虫技术本身并不违法,关键在于使用的方式和目的。爬虫技术的使用必须遵守相关法律法规,如《反不正当竞争法…...
【Java】3、并发编程 JUC(模块三:设计模式)
目录 Immutability模式Copy-on-Write模式线程本地存储模式Guarded Suspension模式(保护性暂停)Balking模式Thread-Per-Message模式Worker Thread模式两阶段终止模式生产者-消费者模式 Immutability模式 Copy-on-Write模式 线程本地存储模式 Guarded S…...
ASP.NET|日常开发中连接Sqlite数据库详解
ASP.NET|日常开发中连接Sqlite数据库详解 前言一、安装和引用相关库1.1 安装 SQLite 驱动1.2 引用命名空间 二、配置连接字符串2.1 连接字符串的基本格式 三、建立数据库连接3.1 创建连接对象并打开连接 四、执行数据库操作4.1 创建表(以简单的用户表为例…...
渗透测试学习笔记(四)web漏洞
一.web相关漏洞 漏洞分类漏洞类型Web 源码类漏洞SQL 注入,文件上传,XSS,代码执行,变量覆盖,逻辑漏洞,反序列化Web 中间件漏洞未授权访问,变量覆盖数据库漏洞弱口令,权限提升系统层漏…...
Facebook如何避免因IP变动而封号?实用指南
随着Facebook在个人社交与商业推广中的广泛应用,越来越多的用户面临因“IP变动”而被封号的问题。尤其是跨境电商、广告运营者和多账号管理用户,这种情况可能严重影响正常使用和业务发展。那么,如何避免因IP变动导致的封号问题?本…...
【Vulkan入门】10-CreatePipeline
目录 先叨叨Git信息关键代码TestPipeline::Initialize() 编译运行 先叨叨 到上篇为止已经创建了FrameBuffer和RenderPass。建立Pipeline的先决条件已经具备。本篇就来创建Pipeline。 Git信息 repository: https://gitee.com/J8_series/easy-car-uitag: 10-CreatePipelineurl…...
视频安防监控平台:Liveweb视频监控管理云平台方案
LiveWeb是深圳市好游科技有限公司开发的一套综合视频汇聚管理平台,可提供多协议(RTSP/RTMP/GB28181/海康Ehome/大华,海康SDK等)的视频设备接入,支持GB/T28181上下级联,RTSP\RTMP转GB/T28181,云台…...
企业级日志分析系统ELK之ELK概述
ELK 概述 ELK 介绍 什么是 ELK 早期IT架构中的系统和应用的日志分散在不同的主机和文件,如果应用出现问题,开发和运维人员想排 查原因,就要先找到相应的主机上的日志文件再进行查找和分析,所以非常不方便,而且还涉及…...
scala隐式转换
概念: 在Scala编程语言中,隐式转换是一种强大的功能,它允许程序在需要时自动转换数据类型或增强对象功能。这种转换通常是通过定义一个标记为implicit的函数来实现的,这个函数能够将一种类型转换为另一种类型。隐式转换的使用可以…...
基于无线传感器网络的无线土壤湿度采集系统(附详细使用教程+完整代码+原理图+完整课设报告)
🎊项目专栏:【Zigbee课程设计系列文章】(附详细使用教程完整代码原理图完整课设报告) 前言 👑由于无线传感器网络(也即是Zigbee)作为🌐物联网工程的一门必修专业课,具有…...
367_C++_计算mouse移动过程中,视频框的右侧、底部边距,以及根据实时的右侧、底部边距计算—视频框的左上角位置
代码分析 1. restorePos 方法 restorePos 的作用是恢复 NavigationFrame 的位置,将其移动到父窗口或者指定矩形内的特定位置。 void NavigationFrame::restorePos() {// 获取目标矩形:优先使用 `m_pRect`,否则默认使用视频区域或父窗口区域RSRect videoRect(m_pVide...
Ubuntu下将Julia嵌入Jupyter内核
一.安装 Julia 如果 Julia 尚未安装: 打开终端,下载最新的 Julia 安装包: wget https://julialang-s3.julialang.org/bin/linux/x64/1.9/julia-1.9.3-linux-x86_64.tar.gz 解压并移动到 /opt: tar -xvzf julia-1.9.3-linux-x86_…...