关于对【java中的Lambda表达式】的理解与简述

news2024/11/26 8:26:18

【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)
https://blog.csdn.net/m0_69908381/article/details/130522535
出自【进步*于辰的博客】

坦白说,在我学会如何使用Lambda表达式后,不禁感叹:“开发出Lambda表达式的人真是天才”。
“将函数如参数般传递” 作为Lambda表达式的核心思想,新颖、实用、又便捷,可谓是为高质量的代码的编写作出了巨大的贡献。
当然,也如很多前辈所言。对于没学过或暂未弄懂Lambda表达式的博友们而言,阅读带有Lambda表达式的代码,难度会增加很多,更遑论修改和维护。
而我本人的看法。Lambda表达式作为一种“新颖”的编码思想(当然,Lambda表达式已经出生很多年了,不能算是“新概念”),它的理解难度在于。。嗯。。。怎么说呢,其实Lambda表达式的难度不大,关键是能不能转过那个“弯”(有点抽象,不知如何表述)。
我个人觉得,要想学会Lambda表达式,2个知识是基础:1、匿名内部类;2、泛型。学会“匿名内部类”对理解Lambda表达式的帮助在于理解“变式”,而“泛型”则是用于Lambda表达式的“扩展”。
喝水不忘挖井人”。这是我系统学习Lambda表达式时参考的博文【Lambda表达式超详细总结】(转发)。在下文中,我会借用这篇博文中的一些资源,如:图片、描述。(因为我觉得那位前辈总结的很好,我再尽力做也最多如此了)。
参考笔记一,P33.8、P43.5。

文章目录

  • 1、关于内部类
    • 1.1 匿名内部类
  • 2、关于Lambda优化匿名内部类
  • 3、Lambda优化规范
    • 3.1 问题
    • 3.2 规范
      • 3.2.1 参数列表
      • 3.2.2 方法体
      • 3.2.3 补充说明
  • 4、Java内置函数式接口
    • 4.1 四大核心函数式接口
    • 4.2 其他函数式接口
    • 4.3 其他函数式接口补充
    • 4.4 示例补充说明
  • 5、Lambda表达式的三种引用
    • 5.1 方法引用
      • 5.1.1 先说结论
      • 5.1.2 格式
      • 5.1.3 说明示例
    • 5.2 构造器引用
      • 5.2.1 概述
      • 5.2.2 示例
    • 5.3 数组引用
  • 6、Lambda表达式的作用域
    • 6.1 引用局部常量
      • 6.1.1 引用局部基本类型常量
      • 6.1.2 引用局部引用类型常量
    • 6.2 引用成员变量、类变量
    • 6.3 引用成员常量、类常量
    • 6.4 Lambda表达式限制访问局部变量的原因
  • 7、最后

1、关于内部类

java中内部类分为成员内部类静态内部类局部内部类匿名内部类
示例:

class TestInnerClass {
    /**
     * 成员内部类
     */
    class MemberInner {
    }
    /**
     * 静态内部类
     */
    static class StaticInner {
    }

    public static void main(String[] args) {
        /**
         * 局部内部类
         */
        class LocalInner {
        }
        /**
         * 匿名内部类
         */
        Runnable run = new Runnable() {
            @Override
            public void run() {
            }
        };
        new Thread(run).start();// 启动线程
    }
}

与Lambda表达式相关的只有匿名内部类
之所以将其他内部类也列举出来呢,主要是我曾分不清这4种内部类(因为极少用)。

1.1 匿名内部类

示例1:

Runnable runa = new Runnable() {
    @Override
    public void run() {
    	int letter = (int) (Math.random() * 26) + 97;
    	sout "随机生成的小写字母是:" + (char) letter;
    }
};
new Thread(runa).start();

等同于:
示例2:

class MyRunnable implements Runnable {
    @Override
    public void run() {
    	int letter = (int) (Math.random() * 26) + 97;
    	sout "随机生成的小写字母是:" + (char) letter;
    }
}
class TestThread {
    public static void main(String[] args) {
        Runnable runa = new MyRunnable();
        new Thread(runa).start();
    }
}

这是很简单、单一的创建线程的方法之一,大家耳熟能详。
示例2中,先手动创建一个类MyRunnable 实现Runnable 接口,再将MyRunnable类实例化并上转为Runnable类型变量runa,最后将runa作为Thread构造方法实参创建线程。很显然,这种方法冗余、代码质量不高(很Low)。
示例1是对示例2的优化。先执行new Runnable()(Runnable是接口,自然不是实例化),因其实现了run()抽象方法,因此,new Runnable()创建了一个Runnable子类的实例,与示例2中的new MyRunnable()相同。
自然的,示例1相较于示例2优化许多,这就是匿名内部类的作用。

2、关于Lambda优化匿名内部类

优化上面中的示例1。

Runnable runa = () -> {
    int letter = (int) (Math.random() * 26) + 97;
    sout "随机生成的小写字母是:" + (char) letter;
};
new Thread(runa).start();

有什么变化?是不是省去了重写run()抽象方法的方法定义部分。
继续优化。

new Thread(() -> {
    int letter = (int) (Math.random() * 26) + 97;
    sout "随机生成的小写字母是:" + (char) letter;
}).start();

这是将“Runnable接口的匿名内部类的定义部分”作为Thread构造方法实参来创建线程。
就如序言中所言 “将函数如参数般传递”。这里的“函数”对应“Runnable接口的匿名内部类的定义部分”。
大家先初步了解,继续看。。。

3、Lambda优化规范

从:

Runnable runa = new Runnable() {
    @Override
    public void run() {
    	int letter = (int) (Math.random() * 26) + 97;
    	sout "随机生成的小写字母是:" + (char) letter;
    }
};
new Thread(runa).start();

优化到:

Runnable runa = () -> {
    int letter = (int) (Math.random() * 26) + 97;
    sout "随机生成的小写字母是:" + (char) letter;
};
new Thread(runa).start();

难道可以随便写?当然不是。那规范是什么?

3.1 问题

大家从我简单举例说明Lambda优化匿名内部类的例子中,肯定看出了一个问题。
上个例子。

Runnable runa = () -> {
    int letter = (int) (Math.random() * 26) + 97;
    sout "随机生成的小写字母是:" + (char) letter;
};
new Thread(runa).start();

Runnable接口只有一个抽象方法run(),可在此例中,有任何有关run方法的定义吗?没有。因此,Lambda只能优化只有一个抽象方法的接口的匿名内部类的定义部分(这类接口称之为“函数式接口”,后续说明)。

举个例:定义java.lang.Number抽象类的匿名内部类。
这是Number抽象类的源码。
在这里插入图片描述

可见,有4个抽象方法。因此,创建Number抽象类的匿名内部类,必须重写这4个抽象方法。即:

new Number() {
    @Override
    public int intValue() {
        return 0;
    }
    @Override
    public long longValue() {
        return 0;
    }
    ......
};

如这般,就无法使用Lambda表达式进行优化。

3.2 规范

既已知道Lambda优化的限制,直接步入正题。

Lambda表达式的基础语法:() -> {}
说明:->的左边是圆括号,对应匿名内部类重写的唯一抽象方法的参数列表;右边是花括号,对应相应方法体
(注:从此处之后,我的阐述中将不会再加上“匿名内部类”或“唯一抽象方法”等字样,上文这般言是遵循循序渐进,为了方便大家理解)

3.2.1 参数列表

  1. 若无参,则必须是(),即:() -> {}
  2. 若只有1个参数xx,则可以是(xx)xx,即:(xx) -> {}xx -> {}
  3. 若有2个参数a、b,则必须是(a, b),即:(a, b) -> {}
  4. 举一反三。

3.2.2 方法体

方法体这边由于不用关注方法定义部分,因此没有什么需要注意的,唯一需要注意的就是一些省略规范返回值类型。当然,此处仅对省略规范进行阐述。
1、 若只有一条语句,可省略分号花括号
示例:

new Thread(() -> sout "只有一条语句").start();
// 等同于:
new Thread(() -> {
	sout "只有一条语句";
}).start();

Runnable接口的run()无参。
2、 若方法有返回值,且只有一条语句时,可省略分号花括号return
示例:

@FunctionalInterface
interface SelfInterface {
    double getTrigonometric(double angle);
}

class TestSelf {
    public static void main(String[] args) {
        SelfInterface service = xx -> Math.sin(xx);
        // 等同于:
        SelfInterface service = xx -> {
            return Math.sin(xx);
        };
        
        double radian = service.getRadian(10);
        sout radian;// 打印:-0.5440211108893698
    }
}

SelfInterface 是一个自定义函数式接口。其中,@FunctionalInterface注解用于标识“函数式接口”,与@Overide注解属于“同类型”注解。

3.2.3 补充说明

以上例为例:
在这里插入图片描述
两个问题:

  1. JVM是如何知道图中红框部分是getTrigonometric()的方法体的?
  2. JVM是如何知道参数xx的类型是double?(因为Math.sin()的形参类型是double

下图是Math.sin()的API截图:
在这里插入图片描述
解释: (以下阐述转载至博文【Lambda表达式超详细总结】)

因为JVM可以通过上下文推断出为何接口实现抽象方法,即“接口推断”,以及推断出*所实现的相应抽象方法的参数列表(包括形参类型),即“类型推断”。
简言之,Lambda表达式依赖于上下文环境。

4、Java内置函数式接口

提前说明:

  1. 所有Java内置函数式接口都运用了泛型

关于泛型,详述请查阅博文【关于对【java泛型】的理解与简述(读后简结)】。也建议大家先大致浏览这篇博文,再看下面的示例,这样会更容易理解。

  1. 在下述每个示例的后面,我会适当的对代码进行一些阐述(例如:方法出处)。不过,不是解释代码。因为以下示例我是按照“由易到难、循序渐进”的原则进行举例的,相信大家能够在逐步的阅读中慢慢理解。因此,我觉得做代码解释是赘述。

4.1 四大核心函数式接口

在这里插入图片描述
(此图出自博文【Lambda表达式超详细总结】)
使用示例:
1、消费型接口 Consumer<T>

Consumer<String> service1 = str -> sout str;
service1.accept("消费型接口Consumer");// 打印:消费型接口Consumer

方法:void accept(T t)
2、供给型接口 Supplier<T>

Supplier<Integer> service2 = () -> (int)(Math.random() * 100);// 获取0~100的随机整数
sout service2.get();// 打印:66

方法:T get()
3、函数型接口 Function<T,R>

Function<String, Integer> service3 = str -> str.length();
sout service3.apply("I love China!!");// 打印:14

方法:R apply(T t)
4、断定型接口 Predicate<T>

Integer i1 = 10;
Predicate<Integer> service4 = xx -> i1.equals(xx);
sout service4.test(10);// 打印:true
sout service4.test(20);// 打印:false

方法:boolean test(T t)

4.2 其他函数式接口

在这里插入图片描述
(此图出自博文【Lambda表达式超详细总结】)
使用示例:
1、函数型接口 BiFunction<T, U, R>

BiFunction<Character[], Character, Integer> service5 = (charArr, c) -> Arrays.binarySearch(charArr, c);
sout service5.apply(new Character[]{65, 66, 67}, 'B');// 打印:1

方法:R apply(T t, U u)
这个示例的关键是Arrays.binarySearch(),其出处:博文【Java-API简读_java.util.Arrays(基于JDK1.8)(浅析源码)】的2.2项。
2、函数型接口 UnaryOperator<T>

UnaryOperator<String> service6 = str -> str.trim().split("!")[1].trim().toUpperCase();
sout service6.apply("   Hello KiTi! I am 小白 of csdn   ");// 打印:I AM 小白 OF CSDN

方法:T apply(T t)
3、函数型接口 BinaryOperator<T>

BinaryOperator<List<Character>> service7 = (list1, list2) -> {
    Collections.copy(list2, list1);
    return list2;
};
sout service7.apply(Arrays.asList('C', 'h'), Arrays.asList('#', '#', 'i', 'n', 'a'));// 打印:[C, h, i, n, a]

方法:T apply(T t1, T t2)
示例中使用了2个方法:

  1. Collections.copy(),出处:博文【Java-API简读_java.util.Collections(基于JDK1.8)(浅析源码)】的2.10项;
  2. Arrays.asList(),出处:博文【Java-API简读_java.util.Arrays(基于JDK1.8)(浅析源码)】的2.1项。

4、消费型接口 BiConsumer<T,R>

BiConsumer<char[], Character>service8 = (charArr, c) -> {
    Arrays.fill(charArr, c);
    sout Arrays.toString(charArr);// 打印:[#, #, #, #, #]
};
service8.accept(new char[]{'进', '步', '*', '于', '辰'}, '#');

方法:void accept(T t, U u)
这个示例的关键是Arrays.fill(),其出处:博文【Java-API简读_java.util.Arrays(基于JDK1.8)(浅析源码)】的2.8项。
5、未知型接口 To[Int/Long?Double]Function<T>

// ToIntFunction<T>
ToIntFunction<List> service9= list -> list.size();
int size = service9.applyAsInt(Arrays.asList(true, 3, 2, 1, "yes", 'f', 'i', 'r', 'e'));// 结果:9

// ToLongFunction<T>
ToLongFunction<Integer> service10 = n-> {
    long startTime = System.nanoTime();
    while ((n--) > 0) {}
    long endTime = System.nanoTime();
    return endTime - startTime;
};
long time = service10.applyAsLong(100000);// 结果:6486400ms

// ToDoubleFunction<T>
ToDoubleFunction<Double> service11 = radian -> Math.toDegrees(radian);
double angle = service11.applyAsDouble(Math.PI);// 结果:180.0

方法:int/long/double applyAs[Int/Long/Double]
6、函数型接口 [Int/Long/Double]Function<R>

// IntFunction<R>
IntFunction<String> service12 = length -> {
    StringBuffer builder = new StringBuffer(10);
    while ((length--) > 0) {
        int x = (int) (Math.random() * 10);// 获取0~9的随机数
        builder.append(x);
    }
    return builder.toString();
};
String code = service12.apply(6);// 结果:695821

// LongFunction<R>
LongFunction<Date> service13 = timeStamp -> new Date(timeStamp);
Date current = service13.apply(System.currentTimeMillis());// 结果:Tue May 09 22:17:26 CST 2023

// DoubleFunction<R>
DoubleFunction<Long> service14 = originN -> Math.round(originN);
long round = service14.apply(10.5);// 结果:11

方法:R apply(int/long/double i)

4.3 其他函数式接口补充

函数式接口参数类型返回值类型说明
断定型接口 `BiPredicate<T, U>T, Uboolean确定类型分别为T/U的对象是否满足某约束,并返回 boolean 值。包含方法:boolean test(T t, U u)

使用示例:
1、断定型接口 `BiPredicate<T,U>:

BiPredicate<String, String > service1 = (t, u) -> t.equals(u);
sout service1.test("abc", "abc");// 打印:true
sout service1.test("abc", "123");// 打印:false

方法:boolean test(T t, U u)

4.4 示例补充说明

大家在看上文中我使用Java内置函数式接口写的示例时,肯定想吐槽:“你写的那些方法体,很多都是多此一举。”
从实用性来说的确是,例如:sout strstr.lengthxx > 0? true: false。直接调用相应方法不好么?还用Lambda表达式转个弯实现。
那我为何还这样写?
因为我觉得使用Lambda表达式编写函数式接口的关键在于“灵活、扩展、通用”这六个字。因此,我作那些示例的初衷是“任意举例、简单易懂”,目的不在于实现何种功能,而是阐述Lambda表达式的使用。

5、Lambda表达式的三种引用

提前说明:
以下关于引用的示例,我会尽量用上文中Java内置函数式接口及其所举示例进行“演变”举例,以降低大家阅读代码的成本。

5.1 方法引用

5.1.1 先说结论

方法引用中所使用的“缺省参数列表”必须与抽象方法的参数列表相同,返回值类型也必须相同。

何为“缺省参数列表”?这是我自定义的概念,我会在示例中举例说明。

5.1.2 格式

  • 格式1:对象 :: 成员方法名
  • 格式2:类 :: 类方法名
  • 格式3:类 :: 成员方法名

5.1.3 说明示例

1、String类的成员方法boolean equals(String str)
以此例为基础进行举例。

BiPredicate<String, String > service1 = (t, u) -> t.equals(u);

演变

// 示例1:类 :: 成员方法名
BiPredicate<String, String> service = String::equals;
sout service.test("csdn", "bilibili");// 打印:false
sout service.test("csdn", "csdn");// 打印:true

// 示例2:对象 :: 成员方法名
String str = "csdn";
Predicate<String> service = str::equals;
sout service.test("bilibili");// 打印:false
sout service.test("csdn");// 打印:true

示例说明:

  1. equals()的返回值类型为 boolean 类型,则此抽象方法的返回值类型也必须是 boolean 类型;
  2. equals()是实例方法,一共需要2个变量,1、String类型对象;2、String类型实参;
  3. 在示例1中,方法引用是String::equals;使用了第3种格式。由于未给定任何变量(即:缺省2个变量),则这2个变量必须由抽象方法的形参提供。因此,此时抽象方法的参数列表必须也是2个参数,且类型要与方法引用的类型相同。方法引用为String::equals,则这2个变量的类型只能是String。故在BiPredicate<T, U>中,泛型<T>、<U>类型实参为String;
  4. 在示例2中,方法引用是str::equals;使用了第1种格式。由于给定了String对象str(即:缺省1个变量)。同理,抽象方法的形参必须只有一个,且类型为String。故在Predicate<T>中,泛型<T>类型实参为String。

2、Arrays类的静态方法void fill()
以此例为基础进行举例。

BiConsumer<char[], Character>service8 = (charArr, c) -> {
    Arrays.fill(charArr, c);
    sout Arrays.toString(charArr);// 打印:[#, #, #, #, #]
};
service8.accept(new char[]{'进', '步', '*', '于', '辰'}, '#');

演变

// 类 :: 静态方法名
BiConsumer<char[], Character> service8 = Arrays::fill;
char[] charArr = new char[]{'进', '步', '*', '于', '辰'};
service8.accept(charArr, '#');
sout Arrays.toString(charArr);// 打印:[#, #, #, #, #]

示例说明:

  1. fill()无返回值,而抽象方法accept()也无返回值,故匹配;
  2. fill()出处:博文【Java-API简读_java.util.Arrays(基于JDK1.8)(浅析源码)】的2.8项,参数列表有2个参数,第1个参数类型为基本数据类型数组;第2个参数类型为基本数据类型。这2个参数都由抽象方法(即:accept())提供。故在BiConsumer<T, U>中,泛型<T>类型实参char[],而泛型<R>类型实参Character

5.2 构造器引用

5.2.1 概述

格式:类 :: new。
说明:
顾名思义,“构造器引用”的作用就是实例化,即 返回实例
例如:为实体类Users创建构造器引用,则构造器引用固定Users::new,即返回一个Users实例
约束:
抽象方法的参数列表决定了匹配哪个构造方法,即 构造器引用等同于构造方法

5.2.2 示例

1、实体类。

class Users {
    private Integer id;
    private String[] hobby;

    public Users() {
    }

    public Users(Integer id) {
        this.id = id;
    }

    public Users(String[] hobby) {
        this.hobby = hobby;
    }

    public Users(Integer id, String[] hobby) {
        this.id = id;
        this.hobby = hobby;
    }
    
    @Override
    public String toString() {
        return "Users{" +
                "id=" + id +
                ", hobby=" + Arrays.toString(hobby) +
                '}';
    }
}

2、测试。

Supplier<Users> service1 =Users::new;
Users user1 = service1.get();
sout user1;// 打印:Users{id=null, hobby=null}

Function<Integer, Users> service2 = Users::new;
Users user2 = service2.apply(1001);
sout user2;// 打印:Users{id=1001, hobby=null}

Function<String[], Users> service3 = Users::new;
Users user3 = service3.apply(new String[]{"编程", "Game"});
sout user3;// 打印:Users{id=null, hobby=[编程, Game]}

BiFunction<Integer, String[], Users> service4 = Users::new;
Users user4 = service4.apply(1002, new String[]{"java", "cf"});
sout user4;// 打印:Users{id=1002, hobby=[java, cf]}

5.3 数组引用

格式: 类型[] :: new。
说明:
与构造器引用同理。不过,数组引用返回的是 数组。(目前我还不知如何使用数组引用创建非空数组)
示例:

Function<Integer, Integer[]> service1 = Integer[]::new;
Integer[] arr = service1.apply(5);
sout Arrays.toString(arr);// 打印:[null, null, null, null]

6、Lambda表达式的作用域

如下阐述转载至博文【Lambda表达式超详细总结】。

Lambda表达式可以看作是匿名内部类实例化的对象,Lambda表达式对变量的访问限制和匿名内部类一样。因此Lambda表达式可以访问局部变量、局部引用,静态变量,实例变量。

6.1 引用局部常量

规定在Lambda表达式中只能引用由final修饰的局部变量(即:局部常量),包括局部基本类型常量局部引用类型常量

6.1.1 引用局部基本类型常量

double d1 = 10.2;-------------------------------------------A
// final double d1 = 10.2;----------------------------------------B
UnaryOperator<Double> service = d -> Math.floor(d + d1);----C
// d1 = 5.1;------------------------------------------------D
sout service.apply(5.9);// 打印:16.0

d1定义为变量(A),可当引用于Lambda表达式中(C)时,会隐式转为常量,但当为d1赋值(D)时,这种“隐式转换”功能会失效,d1仍为变量,则C会编译报错。若将d1显式定义为常量(B),则C可编译通过,但由于常量不可修改,D将会编译报错。

6.1.2 引用局部引用类型常量

示例1:

String subStr = "csdn";
Predicate<String> service = str -> str.contains(subStr);
sout service.test("csdn, bilibili, 博客园");// 打印:true
// subStr = "bili";

此示例与上文中【引用局部基本类型常量】的示例同理。
示例2:

List list = new ArrayList();
list.add(2023);
list.add("年");
list.add(5.12);
Supplier<Integer> service = () -> list.size();
sout service.get();// 打印:3
list.add(true);
sout service.get();// 打印:4

执行list.add(true)是对list进行了修改,按照上面的结论,这个示例是编译报错的。可实际上编译通过。为什么?难道上面的结论有纰漏??
在后面加上这么一条代码试试:

list = new ArrayList();

这样就编译报错了。大家看出来了吧。。。
结论:
由Lambda表达式引用的局部常量不可修改,指的是不可修改引用指向

6.2 引用成员变量、类变量

public class TestReference {
	String originStr1 = "csdn,bilibili,博客园";
	static String originStr2 = "csdn,bilibili,博客园";
	
	public static void main(String[] args) {
	    Supplier<TestReference> service1 = TestReference::new;
	    TestReference t1 = service1.get();
	
	    Supplier<String[]> service2 = () -> t1.originStr1.split(",");
	    String[] arr1 = service2.get();-----------A
	    sout Arrays.toString(arr1);// 打印:[csdn, bilibili, 博客园]
	    t1.originStr1 = "";-----------------------B
	
	    Supplier<String[]> service3 = () -> originStr2.split(",");
	    String[] arr2 = service3.get();-----------C
		sout Arrays.toString(arr2);// 打印:[csdn, bilibili, 博客园]
	    originStr2 = "";--------------------------D
	}
}

B、D处分别修改成员变量originStr1与类变量originStr2,编译通过。可见,Lambda表达式不限制对成员变量和类变量的引用
至于Lambda有没有如上文中【局部常量】般将变量隐式转为“常量”,暂未可知。不过,我觉得没有隐式转换,因为B、D处编译通过。

6.3 引用成员常量、类常量

以上述【引用成员变量、类变量】的示例为基础,在成员变量originStr1和类变量originStr2的定义前加上final,即:

final String originStr1 = "csdn,bilibili,博客园";
final static String originStr2 = "csdn,bilibili,博客园";

则A、C处都编译通过,说明,Lambda表达式不限制对成员常量和类常量的引用;而B、D处都编译报错。这是常量本身的性质,与Lambda表达式无关。

6.4 Lambda表达式限制访问局部变量的原因

关于原因,那位前辈已经总结得很全面,我就不班门弄斧了,详述请查阅博文【Lambda表达式超详细总结】(转发)的8.3项。

7、最后

本文中的示例是为了阐述Lambda表达式、方便大家理解而简单举出的,不一定有实用性。示例很多,不过,我所举的示例都是“以简为宗旨”,重心不在于使用Lambda编写多么强大的功能,而在于尽量扩展对Lambda的使用,让大家能够更透彻地理解Lambda的格式、规范、限制等。
旁白:

这是我迄今为止写过的内容最多的一篇文章,近1.5万字(包括代码),我都有点佩服我自己(真的)。
当然,这个文章内容量与大神们动则几万、十几万的大作相比,不值一提(我不是谦虚,类如几万、十几万的大作,那完全是上了另一个层面的文章了,用“论文”形容更贴切)。不过,我还是挺有成就感的。
最后,望我的写作对大家有帮助!!

本文完结。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/538825.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

机器学习笔记:高斯混合模型 GMM

1 高斯混合模型 总体分布是由K个高斯分布的组成的混合分布 1.1 一些记号 xj第j个观测数据K模型中高斯模型的数量αk 观测数据属于第k个子模型的概率 第k个子模型的高斯分布密度函数 &#xff08;也就是一个高斯分布的密度函数 第j个观测数据属于第k个子模型的概率 1.2 高斯…

Windows环境下安装Redis

下载地址&#xff1a; Releases microsoftarchive/redis GitHub Redis 支持 32 位和 64 位。这个需要根据你系统平台的实际情况选择&#xff0c;这里我们下载 Redis-x64-xxx.zip压缩包到 D 盘redis文件夹下。 网盘下载&#xff1a; 链接&#xff1a;https://pan.baidu.co…

web应用安全漏洞

注入类 数据库注入 SQL注入 结构化查询语言 (Structured Query Language)简称SQL&#xff0c;结构化查询语言是一种数 据库查询和程序设计语言&#xff0c;用于存取数据以及查询、更新和管理关系数据库系统 关系型数据库 &#xff0c;是指采用了关系模型来组织数据的数据库&…

STM32(一)准备开发环境CLion+CubeMX

本篇内容 一、CLion和STM32CubeMX基础安装二、安装OpenOCD三、安装交叉编译工具链四、配置CLion并点亮第一个LED灯五、烧录程序六、错误排查 本篇安装配置STM32的开发环境&#xff0c;使用的是稚晖君同款CLionSTM32CubeMX的开发环境 一、CLion和STM32CubeMX基础安装 软件安装只…

Java-API简析_java.lang.Integer类(基于JDK1.8)(浅析源码)

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/130730986 出自【进步*于辰的博客】 其实我的【Java-API】专栏内的博文对大家来说意义是不大的。…

Python绘制带误差棒的柱状图渐变色填充含数据标注(进阶)

往期python绘图合集: python绘制简单的折线图 python读取excel中数据并绘制多子图多组图在一张画布上 python绘制带误差棒的柱状图 python绘制多子图并单独显示 python读取excel数据并绘制多y轴图像 python绘制柱状图并美化|不同颜色填充柱子 文章目录 准备数据一、绘制图表二、…

Android RecyclerView实现吸顶动态效果,附详细效果图

文章目录 一、ItemDecoration二、实现RecyclerView吸顶效果1、实现一个简单的RecyclerView2、通过ItemDecoration画分割线3、画出每个分组的组名4、实现吸顶效果 完整demo 链接:https://download.csdn.net/download/JasonXu94/87786702 一、ItemDecoration [外链图片转存失败…

SpringDataRedis

SpringDataRedis SpringDataRedis简介RedisTemplate对Redis操作类型SpringDataRedis快速入门1、引入spring-boot-starter-data-redis依赖2、在application.properties配置Redis信息3、注入RedisTemplate并测试 SpringDataRedis的序列化方式现象分析SpringDataRedis的序列化方式…

基于RK3588的以太网PHY的问题分析

环境:RK3588为荣品电子核心板,PHY为裕太微电子YT8521; 首先上电后识别不到以太网: 1.先怀疑驱动的问题,因为本方案中直接使用的是荣品电子官方的驱动; 对比原理图及驱动。涉及到一个配置问题。 基于时钟来源的不通,驱动程序可分为四种模式,PHY的时钟和TXCLK的时钟;…

北华大学第九届程序设计竞赛 题解

5.14和队友VP一场&#xff0c;第二次VP&#xff0c;状态明显比第一次好很多&#xff0c;总共A了7题&#xff0c;基本是能做出来的都做出来了&#xff0c;最后还剩下接近2小时的时间。。。。。 A "北华"有几何 思路&#xff1a;数图片中“北华”的数量&#xff0c;直…

双平台GraalVM编译二进制程序

本文示例均采用Java11&#xff0c;GraalVM目前无法支持跨平台编译&#xff0c;比如&#xff0c;我通过Linux直接编译Windows可执行的exe&#xff0c;是不行的。 因此&#xff0c;需要掌握两种平台的GraalVM的安装、使用。 一、背景 1.1 为何GraalVM快&#xff1f; 常规Java…

永恒之塔私服 2.0包楼纳斯达克 网游的诺曼底登陆-

二战末期的诺曼底登陆&#xff0c;至今让人历历在目。盟军自此在西欧展开大规模进攻&#xff0c;加速了纳粹德国的崩溃。从某种意义上说&#xff0c;诺曼底登陆是整个二战一次生死攸关的转折点。《永恒之塔2.0&#xff1a;进军龙界》登上纳斯达克&#xff0c;也是一场激荡人心的…

基于自动编码器VAE的声音生成之音频预处理模块preprocess pipeline的实现和代码讲解

文章目录 概述Preprocessline模块实现以及代码讲解Loader模块Padder模块LogSpectrogramExtractor模块MinMaxNormaliser模块Saver模块PreprocessPipeLine模块知识补充property修饰词 总结 概述 这部分是将原来基于mnist手写数据集生成模型&#xff0c;一个用到基于FSDD音频数据…

蓝桥:前端开发笔面必刷题——Day2 数组(二)

文章目录 &#x1f4cb;前言&#x1f3af;删除有序数组中的重复项&#x1f4da;题目内容✅解答 &#x1f3af;移动零&#x1f4da;题目内容✅解答 &#x1f3af;长度最小的子数组&#x1f4da;题目内容✅解答 &#x1f3af;反转字符串数组&#x1f4da;题目内容✅解答 &#x1…

麒麟操作系统软件更新灾难连篇之二:QQ罢工

在解决了中文输入法消失的问题后&#xff0c;还没缓过气来&#xff0c;又发现QQ罢工了&#xff1a;双击电脑桌面上的QQ图标&#xff0c;没有显示QQ登录界面。 重启电脑再试&#xff0c;还是不显示QQ登录界面。 前不久腾讯正式宣布&#xff0c;QQ Linux 版 3.0 已在 QQ 官网上…

最好用的文本与文件查询软件AnyTXT Searcher与Listary简介

1. 工具简介 1.1 Listary简介 Listary是一个革命性的Windows搜索工具&#xff0c;借助 Listary软件&#xff0c;你可以快速搜索电脑文件、定位文件、执行智能命令、记录访问历史、快速切换目录、收藏常用项目等。 Listary为Windows传统低效的文件打开/保存对话框提供了便捷、…

Apache Tomcat AJP协议文件读取与包含

永远也不要忘记能够笑的坚强&#xff0c;就算受伤&#xff0c;我也从不彷徨。 0x01.漏洞情况分析 Tomcat是Apache软件基金会Jakarta 项目中的一个核心项目&#xff0c;作为目前比较流行的Web应用服务器&#xff0c;深受Java爱好者的喜爱&#xff0c;并得到了部分软件开发商的…

makefile 学习(2):C语言的编译及库文件的生成与链接

文章目录 1. 介绍2. C语言编译2.1 预处理2.2 生成汇编语言2.3 编译目标文件2.4 编译为可执行文件 3. .a静态库的编译与链接案例 4 .so 动态库的编译与链接 1. 介绍 编译C语言的相关后缀 .a 文件是一个静态库文件.c文件是c语言的源文件.h c语言的头文件.i 是预处理文件.o 目标文…

两个用字符串表示的大数字的和

文章目录 题目详情Java实现分析Java 怎么获取到字符串中的对应位置的数字值Java完整代码测试验证 python实现python 怎么获取到字符串中的对应位置的数字值python完整代码 总结 这是遇到的一道快手数仓岗位的面试题目&#xff0c;题目大意如下&#xff1a; 题目详情 现在有两个…

【树莓派4B安装18.04桌面+远程SSH】

【树莓派4B安装18.04桌面远程SSH】 1. 前言2 .树莓派安装ubuntu18.04 系统2.1 下载ubuntu Server 18.04 的镜像包2.2 镜像烧录2.3 高级设置2.4 配置WiFi2.5 ssh文件配置2.6 Pi 4B启动文件 3. 安装finalshell3.1 windows版下载3.2 windows版安装3.3 SSH连接 4. 安装ubuntu桌面4.…