一:Lambda表达式学习
一:初次接触Lambda表达式,通过实现接口来创建线程
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("通过匿名类部类实现线程:"+Thread.currentThread().getName());
}
}).start();
new Thread(()-> System.out.println("这是通过Lambda表达式创建的线程:"+Thread.currentThread().getName()))
.start();
}
代码分析:
1、上面通过两种方式来通过实现接口来创建的线程
①通过匿名内部类来创建的线程,Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法使用来指定线程任务的核心。我们为了指定run方法体,不得不需要Runnable的实现类。必须覆盖重新抽象方法run方法,所有的方法名称,方法的参数以及方法的返回值不得不重写一遍,而且都不能出错。
②第二种方式是通过Lambda表达式创建的线程,我们可以明显看出代码的量量减少,并且语法更加简单。
二:Lambda表达式的语法
Lambda表达式主要有3个部分组成
(参数类型 参数名称)->{
代码块;
}
格式说明:
1、(参数类型 参数名称):参数列表
2、{代码体;}:方法体
3、->:箭头,分割参数列表和方法体
三:Lambda表达式的练习
1、无参无返的Lambda表达式
定义一个用户接口
public interface User {
void show();
}
通过一个主方法来调用
public static void main(String[] args) {
//形式一:
getShow(()->{
System.out.println("这是用户接口的无参无返的方法");
});
//形式二:
getShow(()-> System.out.println("这是用户接口的无参无返的方法11111"));
}
public static void getShow(User user){
user.show();
}
输出结果:
这是用户接口的无参无返的方法
这是用户接口的无参无返的方法11111
2、无参有返的Lambda表达式
定义一个接口
public interface Person {
String getName();
}
通过主方法来调用
public static void main(String[] args) {
//无参无返的表达式形式一:
getShow(()->{
System.out.println("这是用户接口的无参无返的方法");
});
//无参无返的表达式形式二:
getShow(()-> System.out.println("这是用户接口的无参无返的方法11111"));
//无参有返的表达式形式一:
getShow1(()->{
return "这是无参有返的Lambda表达式";
});
//无参有返的表达式形式二:
getShow1(()->"这是无参有返的Lambda表达式111");
}
public static void getShow(User user){
user.show();
}
public static void getShow1(Person person){
String name = person.getName();
System.out.println(name);
}
输出结果:
这是用户接口的无参无返的方法
这是用户接口的无参无返的方法11111
这是无参有返的Lambda表达式
这是无参有返的Lambda表达式111
3、有参无返的Lambda表达式
本身Consumer就是一个函数式接口,里面有有一个accept方法,这个方法就是一个有参数无返的方法
public static void main(String[] args) {
/* //无参无返的表达式形式一:
getShow(()->{
System.out.println("这是用户接口的无参无返的方法");
});
//无参无返的表达式形式二:
getShow(()-> System.out.println("这是用户接口的无参无返的方法11111"));
//无参有返的表达式形式一:
getShow1(()->{
return "这是无参有返的Lambda表达式";
});
//无参有返的表达式形式二:
getShow1(()->"这是无参有返的Lambda表达式111");*/
//有参无返的表达式
getConsumer(s-> System.out.println(s));
}
/*public static void getShow(User user){
user.show();
}
public static void getShow1(Person person){
String name = person.getName();
System.out.println(name);
}*/
public static void getConsumer(Consumer consumer){
consumer.accept("这是一个有参无返的方法");
}
输出结果:
这是一个有参无返的方法
4、有参有返的Lambda表达式
定义一个实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
private Integer age;
private Integer sex;
}
通过主方法调用Collections.sort方法来实现对实体类中的年龄进行排序
public static void main(String[] args) {
ArrayList<Person> people = new ArrayList<>();
people.add(new Person("张三",23,1));
people.add(new Person("李四",21,0));
people.add(new Person("王五",22,1));
//通过Collections.sort方法,对Person对象种的年龄进行排序
Collections.sort(people,((o1, o2) -> {
return o1.getAge()-o2.getAge();
}));
people.forEach(System.out::println);
}
输出:
Person(name=李四, age=21, sex=0)
Person(name=王五, age=22, sex=1)
Person(name=张三, age=23, sex=1)
5、Lambda表达式的注意事项:
①如果参数列表中的参数只有一个,我们就可以省略参数的类型,并且可以去掉小括号不写。
②如果参数列表中没有参数,则必须要加上小括号
③如果参数列表中的参数不止一个并且参数的类型不一致则必须要加上参数的类型
④方体里面如果只有一条语句的话可以省略掉大括号
⑤如果方法体里面只有一条return语句的话return可以省略不写
四:@FunctionalInterface注解
@FunctionalInterface
public interface User {
void show();
}
如果在接口上面加了@FunctionalInterface这个注解,那么这个接口就被申明为是函数式接口,并且这个接口里面只能有一个抽象方法
五:Lambda表达式使用的前提
Lambda表达式的语法式非常简洁的,但是Lambda表达式不是随便使用的,使用时有几个条件需要注意:
1、方法的参数或者局部变量的类型必须为接口才能使用Lambda表达式
2、接口中有且只能有一个抽线方法(@FunctionalInterface)
六:Lambada表达式和匿名内部类的区别
1、所需要的类型不一样
匿名内部类的类型可以是类、抽象类、接口
Lambda表达式需要的类型必须是接口
2、抽象方法的数量不一样
匿名内部类所需要的接口中的抽象方法数量是随意的
Lambda表达式所需要的接口中只能有一个抽象方法
3、实现的原理不同
匿名内部类是在编译后形成的一个class
Lambda表达式时在程序运行的时候动态生成的class
三:JDK8后的接口中的新增的方法
一:在JDK8中针对接口做了一些增强,在JDK8之前
interface 接口名{
静态常量;
抽象方法;
}
在JDK8之后对接口增加了,默认方法和静态方法
interface 接口名{
静态常量;
抽象方法;
默认方法;
静态方法;
}
二:接口中的默认方法
interface 接口名{
修饰符 default 返回值类型 方法名{
方法体;
}
}
代码示例:
public static void main(String[] args) {
A b = new B();
b.test3();
A c = new C();
c.test3();
}
//定义一个接口A
interface A{
//普通抽线方法
void test1();
void test2();
public default void test3(){
System.out.println("这是接口中的默认方法");
}
}
//定义一个类B去实现A
static class B implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
@Override
public void test3(){
System.out.println("这是B方法里面的接口中的默认方法");
}
}
//定义一个类C去实现A
static class C implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
}
执行结果
这是B方法里面的接口中的默认方法
这是接口中的默认方法
默认方法有两种使用方法:
1、实现类直接调用接口的默认方法
2、实现类可以重写接口中的默认方法
三:接口中的静态方法
语法格式:
interface 接口名{
修饰符 static 返回值类型 方法名{
方法体;
}
}
示例代码:
package InterfceDeom;
public class Interface01 {
public static void main(String[] args) {
A b = new B();
b.test3();
A c = new C();
c.test3();
A.test4();
}
//定义一个接口A
interface A{
//普通抽象方法
void test1();
void test2();
//默认方法
public default void test3(){
System.out.println("这是接口中的默认方法");
}
//静态方法
public static void test4(){
System.out.println("这是接口中的静态方法");
}
}
//定义一个类B去实现A
static class B implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
@Override
public void test3(){
System.out.println("这是B方法里面的接口中的默认方法");
}
}
//定义一个类C去实现A
static class C implements A{
@Override
public void test1() {
}
@Override
public void test2() {
}
}
}
输出结果:
这是B方法里面的接口中的默认方法
这是接口中的默认方法
这是接口中的静态方法
静态方法的使用:
接口中的静态方法在实习类中不是能被重写的,调用的话只能通过接口名来实现:接口名.静态方法名();
四:默认方法和静态方法的区别
1、默认方法通过实例调用,静态方法是通过接口名进行调用
2、默认方法可以被继承,实现类可以直接调用接口的默认方法,也可以重写接口的默认方法
3、静态方法不能被继承,实现类不能重写接口的静态方法,只能使用接口名进行调用
五:函数式接口
一:函数式接口的介绍
在我们使用Lambda表达式的前提就是需要有函数式接口,在使用Lambda表达式时不关心接口名、抽象方法名。只关心抽象方法的参数列表和返回值。所以为了让我们能更方便的使用Lambda表达式,JDK提供了大量常用的函数式接口
主要存在于java.util.function包中
二:主要的函数接口
1、Supplier
该函数式接口是一个无参有返回值的接口,对于的Lambda表达式需要提供一个返回数据的类型
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
使用案例:
public class SupplierTest {
public static void main(String[] args) {
Supplier01(()->{
List<Integer> integers = Arrays.asList(1, 3, 2, 5, 4);
Optional<Integer> reduce = integers.stream().reduce(Integer::max);
return reduce.get();
});
}
public static void Supplier01(Supplier<Integer> supplier){
System.out.println(supplier.get());
}
}
返回值:
5
代码解释:定义一个方法里面传入Supplier函数接口,然后在主方法中进行调用,通过Lambda表达式,在里面定义一个集合然后再通过Stream流的reduce方法里面使用Integer里面的max方法获取到集合中的最大值,并将其结果返回
2、Consumer
该函数式接口是一个有参无返回值的一个接口,就像我们的消费者一样是用来消费数据的,使用的时候需要指定一个泛型来定义参数的类型
2.1、accept方法
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
使用案例:
public class ConsumerTest {
public static void main(String[] args) {
Consumer01(s-> System.out.println(s));
}
public static void Consumer01(Consumer<String> consumer){
consumer.accept("这是Consumer接口返回的参数");
}
}
输出结果:
这是Consumer接口返回的参数
2.2、默认方法:andThen
方法描述:如果一个方法的参数和返回值全是Consumer类型,那就可以实现效果,消费一个数据的时候,首先做一个操作,然后再做另外一个操作,实现组合,这个方法就是Consumer接口中的默认的方法andThen方法
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
使用案例:
public class ConsumerTest {
public static void main(String[] args) {
//Consumer01(s-> System.out.println(s));
Consumer02(s1 -> {
System.out.println("转为小写:"+s1.toLowerCase());
},s2 -> System.out.println("转为全大写:"+s2.toUpperCase()) );
}
public static void Consumer01(Consumer<String> consumer){
consumer.accept("这是Consumer接口返回的参数");
}
public static void Consumer02(Consumer<String> con1,Consumer<String> con2){
con1.andThen(con2).accept("Hello");
}
}
输出结果
转为小写:hello
转为全大写:HELLO
3、Function
该函数式接口是一个有参有返的接口,function接口是根据一个类型的数据得到另外一个类型的数据,前者是前置条件,后者是后置添加。
3.1 apply方法
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
实例代码:
public class FunctionTest {
public static void main(String[] args) {
functionTest(s-> "这是function接口返回的结果"+s);
}
public static void functionTest(Function<Integer,String> function){
System.out.println(function.apply(666));
}
}
输出结果:
这是function接口返回的结果666
3.2 默认方法:andThen方法
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
实例代码
public class FunctionTest02 {
public static void main(String[] args) {
function02(s1->{
return Integer.parseInt(s1);
},s2->{
return s2*5;
});
}
public static void function02(Function<String,Integer> fun1,Function<Integer,Integer> fun2){
System.out.println(fun1.andThen(fun2).apply("6"));
}
}
输出结果:
30
其中还有两个方法:
1、默认方法:compose方法的作用顺序和andThen方法刚好是相反的
2、静态方法identity则是,传入什么参数就返回什么参数
4、Predicate
该函数式接口是一个有参有返的接口,并且返回值时Boolean类型的
4.1 Predicate方法:
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
实例代码:
public class PredicateTest {
public static void main(String[] args) {
Predicate01(s->{
return s.contains("o");
});
}
public static void Predicate01(Predicate<String> pre){
boolean hello = pre.test("Hello");
System.out.println(hello);
}
}
返回结果:
true
4.2 默认方法:and、or、negate、isEquals方法
实例代码:
public class PredicateTest02 {
public static void main(String[] args) {
Predicate01(s->{
return s.contains("o");
},s1->{
return s1.contains("w");
});
}
public static void Predicate01(Predicate<String> pre1,Predicate<String> pre2){
//两个都满足
System.out.println(pre1.and(pre2).test("Hello"));
//两个满足其中的一个
System.out.println(pre1.or(pre2).test("Hello"));
//不满足
System.out.println(pre1.negate().test("Hello"));
}
}
输出结果:
false
true
false
五:方法引用
一、为什么要用方法引用
当我们在使用Lambda表达式的时候也会出现代码冗余的情况,比如以下代码:
public class funRef01 {
public static void main(String[] args) {
arrSum(arr->{
int sum = 0;
for (int i : arr) {
sum+= i;
}
return sum;
});
}
public static void arrSum(Function<int[],Integer> function){
int[] arr = {1,2,3,4};
Integer apply = function.apply(arr);
System.out.println(apply);
}
}
上述代码是通过Lambda表达式对数组中的元素进行求和操作
简化代码:
public class funRef01 {
public static void main(String[] args) {
/*arrSum(arr->{
int sum = 0;
for (int i : arr) {
sum+= i;
}
return sum;
});*/
arrSum(funRef01::arrSum1);
}
public static Integer arrSum1(int[] arr){
int sum = 0;
for (int i : arr) {
sum+=i;
}
return sum;
}
public static void arrSum(Function<int[],Integer> function){
int[] arr = {1,2,3,4};
Integer apply = function.apply(arr);
System.out.println(apply);
}
}
二:方法引用的格式
符号表示:::(英文格式下的双冒号)
符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用
应用场景:如果Lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用
常见的引用方法:
instanceName::methodName 对象::方法名
ClassName::staticMethodName 类名::静态方法名
ClassName::methodName 类名::普通方法
ClassName::new 类名::new调用构造器
TypeName[]::new String[]::new 调用数组的构造器
2.1 对象名::方法民
这是最常见的一种用法。如果一个类中的已经存在了一个成员方法,可以直接通过对象名引用成员方法
public static void main(String[] args) {
//正常的Lambda表达式写法
Date date = new Date();
Supplier<Long> supplier = () -> {
return date.getTime();
};
System.out.println(supplier);
//使用方法引用的写法
Supplier<Long> getTime = date::getTime;
System.out.println(getTime);
}
注意事项:
1、被引用的方法,参数要和接口中的抽象方法的参数一样
2、当接口抽象方法有返回值式,被引用的方法也必须有返回值
2.2 类名::静态方法名
public static void main(String[] args) {
Supplier<Instant> instantCallable = () -> {
return Instant.now();
};
System.out.println(instantCallable);
Supplier<Instant> user = Instant::now;
System.out.println(user);
}
2.3 类名::引用实例方法
public static void main(String[] args) {
Function<String,Boolean> app = s -> {
return s.contains("s");
};
System.out.println(app.apply("asd"));
BiFunction<String, CharSequence, Boolean> contains = String::contains;
Function<String, Integer> stringIntegerFunction = String::length;
System.out.println(contains.apply("asd", "s"));
System.out.println(stringIntegerFunction.apply("asds"));
}
2.4 类名::构造器
public static void main(String[] args) {
Supplier<Person> personCallable = () -> {
return new Person();
};
Person person1 = personCallable.get();
Supplier<Person> user = Person::new;
Person person = user.get();
}
2.5 数组::构造器
public static void main(String[] args) {
Function<Integer,String[]> arr = s->{
return new String[s];
};
String[] apply1 = arr.apply(6);
System.out.println(apply1.length);
Function<Integer,String[]> arr1 = String[]::new;
String[] apply = arr1.apply(5);
System.out.println(apply.length);
}
总结:方法引用是对于Lambda表达式符号特定情况下的一种写法方式,使得Lambda表达式更加精简,注意方法引用只能引用已经存在的方法。