十三、函数式编程(2)

news2024/10/7 20:27:12

本章概要

  • 方法引用
    • Runnable 接口
    • 未绑定的方法引用
    • 构造函数引用
  • 函数式接口
    • 多参数函数式接口
    • 缺少基本类型的函数

方法引用

Java 8 方法引用没有历史包袱。方法引用组成:类名或对象名,后面跟 :: ,然后跟方法名称。

interface Callable { // [1]
    void call(String s);
}

class Describe {
    void show(String msg) { // [2]
        System.out.println(msg);
    }
}

public class MethodReferences {
    static void hello(String name) { // [3]
        System.out.println("Hello, " + name);
    }

    static class Description {
        String about;

        Description(String desc) {
            about = desc;
        }

        void help(String msg) { // [4]
            System.out.println(about + " " + msg);
        }
    }

    static class Helper {
        static void assist(String msg) { // [5]
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        Describe d = new Describe();
        Callable c = d::show; // [6]
        c.call("call()"); // [7]

        c = MethodReferences::hello; // [8]
        c.call("Bob");

        c = new Description("valuable")::help; // [9]
        c.call("information");

        c = Helper::assist; // [10]
        c.call("Help!");
    }
}

输出结果:

在这里插入图片描述

[1] 我们从单一方法接口开始(同样,你很快就会了解到这一点的重要性)。

[2] show() 的签名(参数类型和返回类型)符合 Callablecall() 的签名。

[3] hello() 也符合 call() 的签名。

[4] help() 也符合,它是静态内部类中的非静态方法。

[5] assist() 是静态内部类中的静态方法。

[6] 我们将 Describe 对象的方法引用赋值给 Callable ,它没有 show() 方法,而是 call() 方法。 但是,Java 似乎接受用这个看似奇怪的赋值,因为方法引用符合 Callablecall() 方法的签名。

[7] 我们现在可以通过调用 call() 来调用 show(),因为 Java 将 call() 映射到 show()

[8] 这是一个静态方法引用。

[9] 这是 [6] 的另一个版本:对已实例化对象的方法的引用,有时称为_绑定方法引用_。

[10] 最后,获取静态内部类中静态方法的引用与 [8] 中通过外部类引用相似。

上例只是简短的介绍,我们很快就能看到方法引用的所有不同形式。

Runnable接口

Runnable 接口自 1.0 版以来一直在 Java 中,因此不需要导入。它也符合特殊的单方法接口格式:它的方法 run() 不带参数,也没有返回值。因此,我们可以使用 Lambda 表达式和方法引用作为 Runnable

// 方法引用与 Runnable 接口的结合使用

class Go {
    static void go() {
        System.out.println("Go::go()");
    }
}

public class RunnableMethodReference {
    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Anonymous");
            }
        }).start();

        new Thread(
                () -> System.out.println("lambda")
        ).start();

        new Thread(Go::go).start();
    }
}

输出结果:

在这里插入图片描述

Thread 对象将 Runnable 作为其构造函数参数,并具有会调用 run() 的方法 start()。 注意这里只有匿名内部类才要求显式声明 run() 方法。

未绑定的方法引用

未绑定的方法引用是指没有关联对象的普通(非静态)方法。 使用未绑定的引用时,我们必须先提供对象:

// 没有方法引用的对象

class X {
    String f() {
        return "X::f()";
    }
}

interface MakeString {
    String make();
}

interface TransformX {
    String transform(X x);
}

public class UnboundMethodReference {
    public static void main(String[] args) {
        // MakeString ms = X::f; // [1]
        TransformX sp = X::f;
        X x = new X();
        System.out.println(sp.transform(x)); // [2]
        System.out.println(x.f()); // 同等效果
    }
}

输出结果:

在这里插入图片描述

到目前为止,我们已经见过了方法引用和对应接口的签名(参数类型和返回类型)一致的几个赋值例子。 在 [1] 中,我们尝试同样的做法,把 Xf() 方法引用赋值给 MakeString。结果即使 make()f() 具有相同的签名,编译也会报“invalid method reference”(无效方法引用)错误。 问题在于,这里其实还需要另一个隐藏参数参与:我们的老朋友 this。 你不能在没有 X 对象的前提下调用 f()。 因此,X :: f 表示未绑定的方法引用,因为它尚未“绑定”到对象。

要解决这个问题,我们需要一个 X 对象,因此我们的接口实际上需要一个额外的参数,正如在 TransformX 中看到的那样。 如果将 X :: f 赋值给 TransformX,在 Java 中是允许的。我们必须做第二个心理调整——使用未绑定的引用时,函数式方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 原因是:你需要一个对象来调用方法。

[2] 的结果有点像脑筋急转弯。我拿到未绑定的方法引用,并且调用它的transform()方法,将一个X类的对象传递给它,最后使得 x.f() 以某种方式被调用。Java知道它必须拿第一个参数,该参数实际就是this 对象,然后对此调用方法。

如果你的方法有更多个参数,就以第一个参数接受this的模式来处理。

// 未绑定的方法与多参数的结合运用

class This {
    void two(int i, double d) {
    }

    void three(int i, double d, String s) {
    }

    void four(int i, double d, String s, char c) {
    }
}

interface TwoArgs {
    void call2(This athis, int i, double d);
}

interface ThreeArgs {
    void call3(This athis, int i, double d, String s);
}

interface FourArgs {
    void call4(
            This athis, int i, double d, String s, char c);
}

public class MultiUnbound {
    public static void main(String[] args) {
        TwoArgs twoargs = This::two;
        ThreeArgs threeargs = This::three;
        FourArgs fourargs = This::four;
        This athis = new This();
        twoargs.call2(athis, 11, 3.14);
        threeargs.call3(athis, 11, 3.14, "Three");
        fourargs.call4(athis, 11, 3.14, "Four", 'Z');
    }
}

需要指出的是,我将类命名为 This,并将函数式方法的第一个参数命名为 athis,但你在生产级代码中应该使用其他名字,以防止混淆。

构造函数引用

你还可以捕获构造函数的引用,然后通过引用调用该构造函数。

class Dog {
    String name;
    int age = -1; // For "unknown"

    Dog() {
        name = "stray";
    }

    Dog(String nm) {
        name = nm;
    }

    Dog(String nm, int yrs) {
        name = nm;
        age = yrs;
    }
}

interface MakeNoArgs {
    Dog make();
}

interface Make1Arg {
    Dog make(String nm);
}

interface Make2Args {
    Dog make(String nm, int age);
}

public class CtorReference {
    public static void main(String[] args) {
        MakeNoArgs mna = Dog::new; // [1]
        Make1Arg m1a = Dog::new;   // [2]
        Make2Args m2a = Dog::new;  // [3]

        Dog dn = mna.make();
        Dog d1 = m1a.make("Comet");
        Dog d2 = m2a.make("Ralph", 4);
    }
}

Dog 有三个构造函数,函数式接口内的 make() 方法反映了构造函数参数列表( make() 方法名称可以不同)。

注意我们如何对 [1][2][3] 中的每一个使用 Dog :: new。 这三个构造函数只有一个相同名称::: new,但在每种情况下赋值给不同的接口,编译器可以从中知道具体使用哪个构造函数。

编译器知道调用函数式方法(本例中为 make())就相当于调用构造函数。

函数式接口

方法引用和 Lambda 表达式都必须被赋值,同时赋值需要类型信息才能使编译器保证类型的正确性。尤其是Lambda 表达式,它引入了新的要求。 代码示例:

x -> x.toString()

我们清楚这里返回类型必须是 String,但 x 是什么类型呢?

Lambda 表达式包含 类型推导 (编译器会自动推导出类型信息,避免了程序员显式地声明)。编译器必须能够以某种方式推导出 x 的类型。

下面是第二个代码示例:

(x, y) -> x + y

现在 xy 可以是任何支持 + 运算符连接的数据类型,可以是两个不同的数值类型或者是 一个 String 加任意一种可自动转换为 String 的数据类型(这包括了大多数类型)。 但是,当 Lambda 表达式被赋值时,编译器必须确定 xy 的确切类型以生成正确的代码。

该问题也适用于方法引用。 假设你要传递 System.out :: println 到你正在编写的方法 ,你怎么知道传递给方法的参数的类型?

为了解决这个问题,Java 8 引入了 java.util.function 包。它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型。 每个接口只包含一个抽象方法,称为 函数式方法

在编写接口时,可以使用 @FunctionalInterface 注解强制执行此“函数式方法”模式:

@FunctionalInterface
interface Functional {
    String goodbye(String arg);
}

interface FunctionalNoAnn {
    String goodbye(String arg);
}

/*
@FunctionalInterface
interface NotFunctional {
  String goodbye(String arg);
  String hello(String arg);
}
产生错误信息:
NotFunctional is not a functional interface
multiple non-overriding abstract methods
found in interface NotFunctional
*/

public class FunctionalAnnotation {
    public String goodbye(String arg) {
        return "Goodbye, " + arg;
    }

    public static void main(String[] args) {
        FunctionalAnnotation fa =
                new FunctionalAnnotation();
        Functional f = fa::goodbye;
        FunctionalNoAnn fna = fa::goodbye;
        // Functional fac = fa; // Incompatible
        Functional fl = a -> "Goodbye, " + a;
        FunctionalNoAnn fnal = a -> "Goodbye, " + a;
    }
}

@FunctionalInterface 注解是可选的; Java 会在 main() 中把 FunctionalFunctionalNoAnn 都当作函数式接口来看待。 在 NotFunctional 的定义中可看出@FunctionalInterface 的作用:当接口中抽象方法多于一个时产生编译期错误。

仔细观察在定义 ffna 时发生了什么。 FunctionalFunctionalNoAnn 声明了是接口,然而被赋值的只是方法 goodbye()。首先,这只是一个方法而不是类;其次,它甚至都不是实现了该接口的类中的方法。这是添加到Java 8中的一点小魔法:如果将方法引用或 Lambda 表达式赋值给函数式接口(类型需要匹配),Java 会适配你的赋值到目标接口。 编译器会在后台把方法引用或 Lambda 表达式包装进实现目标接口的类的实例中。

虽然 FunctionalAnnotation 确实符合 Functional 模型,但是 Java不允许我们像fac定义的那样,将 FunctionalAnnotation 直接赋值给 Functional,因为 FunctionalAnnotation 并没有显式地去实现 Functional 接口。唯一的惊喜是,Java 8 允许我们将函数赋值给接口,这样的语法更加简单漂亮。

java.util.function 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。主要因为基本类型的存在,导致预定义的接口数量有少许增加。 如果你了解命名模式,顾名思义就能知道特定接口的作用。

以下是基本命名准则:

  1. 如果只处理对象而非基本类型,名称则为 FunctionConsumerPredicate 等。参数类型通过泛型添加。
  2. 如果接收的参数是基本类型,则由名称的第一部分表示,如 LongConsumerDoubleFunctionIntPredicate 等,但返回基本类型的 Supplier 接口例外。
  3. 如果返回值为基本类型,则用 To 表示,如 ToLongFunction <T>IntToLongFunction
  4. 如果返回值类型与参数类型相同,则是一个 Operator :单个参数使用 UnaryOperator,两个参数使用 BinaryOperator
  5. 如果接收参数并返回一个布尔值,则是一个 谓词 (Predicate)。
  6. 如果接收的两个参数类型不同,则名称中有一个 Bi

下表描述了 java.util.function 中的目标类型(包括例外情况):

特征函数式方法名示例
无参数;
无返回值Runnable (java.lang)
run()Runnable
无参数;
返回类型任意Supplierget()
getAs类型()Supplier**<T>**
**
BooleanSupplier
IntSupplier
LongSupplier
DoubleSupplier**
无参数;
返回类型任意Callable (java.util.concurrent)
call()Callable**<V>**
1 参数;
无返回值Consumeraccept()**Consumer<T>**
**
IntConsumer
LongConsumer
DoubleConsumer**
2 参数 ConsumerBiConsumeraccept()**BiConsumer<T,U>**
2 参数 Consumer
第一个参数是 引用;
第二个参数是 基本类型Obj类型Consumeraccept()**ObjIntConsumer<T>**
**
****ObjLongConsumer<T>**
**
****ObjDoubleConsumer<T>**
1 参数;
返回类型不同Functionapply()
To类型类型To类型applyAs类型()Function**<T,R>**
**
IntFunction****<R>**
**
****LongFunction<R>**
**
DoubleFunction****<R>**
**
ToIntFunction****<T>**
**
****ToLongFunction<T>**
**
****ToDoubleFunction<T>**
**
IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction**
1 参数;
返回类型相同UnaryOperatorapply()**UnaryOperator<T>**
**
IntUnaryOperator
LongUnaryOperator
DoubleUnaryOperator**
2 参数,类型相同;
返回类型相同BinaryOperatorapply()**BinaryOperator<T>**
**
IntBinaryOperator
LongBinaryOperator
DoubleBinaryOperator**
2 参数,类型相同;
返回整型Comparator
(java.util)
compare()**Comparator<T>**
2 参数;
返回布尔型Predicatetest()**Predicate<T>**
**
****BiPredicate<T,U>**
**
IntPredicate
LongPredicate
DoublePredicate**
参数基本类型;
返回基本类型类型To类型FunctionapplyAs类型()**IntToLongFunction
IntToDoubleFunction
LongToIntFunction
LongToDoubleFunction
DoubleToIntFunction
DoubleToLongFunction**
2 参数;
类型不同Bi操作 (不同方法名)**BiFunction<T,U,R>**
**
****BiConsumer<T,U>**
**
****BiPredicate<T,U>**
**
****ToIntBiFunction<T,U>**
**
****ToLongBiFunction<T,U>**
**
****ToDoubleBiFunction<T>**

此表仅提供些常规方案。通过上表,你应该或多或少能自行推导出你所需要的函数式接口。

可以看出,在创建 java.util.function 时,设计者们做出了一些选择。

例如,为什么没有 IntComparatorLongComparatorDoubleComparator 呢?有 BooleanSupplier 却没有其他表示 Boolean 的接口;有通用的 BiConsumer 却没有用于 intlongdoubleBiConsumers 变体(我理解他们为什么放弃这些接口)。这到底是疏忽还是有人认为其他组合使用得很少呢(他们是如何得出这个结论的)?

你还可以看到基本类型给 Java 添加了多少复杂性。该语言的第一版中就包含了基本类型,原因是考虑效率问题(该问题很快就缓解了)。现在,在语言的生命周期里,我们一直忍受语言设计的糟糕选择所带来的影响。

下面枚举了基于 Lambda 表达式的所有不同 Function 变体的示例:

import java.util.function.*;

class Foo {
}

class Bar {
    Foo f;

    Bar(Foo f) {
        this.f = f;
    }
}

class IBaz {
    int i;

    IBaz(int i) {
        this.i = i;
    }
}

class LBaz {
    long l;

    LBaz(long l) {
        this.l = l;
    }
}

class DBaz {
    double d;

    DBaz(double d) {
        this.d = d;
    }
}

public class FunctionVariants {
    static Function<Foo, Bar> f1 = f -> new Bar(f);
    static IntFunction<IBaz> f2 = i -> new IBaz(i);
    static LongFunction<LBaz> f3 = l -> new LBaz(l);
    static DoubleFunction<DBaz> f4 = d -> new DBaz(d);
    static ToIntFunction<IBaz> f5 = ib -> ib.i;
    static ToLongFunction<LBaz> f6 = lb -> lb.l;
    static ToDoubleFunction<DBaz> f7 = db -> db.d;
    static IntToLongFunction f8 = i -> i;
    static IntToDoubleFunction f9 = i -> i;
    static LongToIntFunction f10 = l -> (int) l;
    static LongToDoubleFunction f11 = l -> l;
    static DoubleToIntFunction f12 = d -> (int) d;
    static DoubleToLongFunction f13 = d -> (long) d;

    public static void main(String[] args) {
        Bar b = f1.apply(new Foo());
        IBaz ib = f2.apply(11);
        LBaz lb = f3.apply(11);
        DBaz db = f4.apply(11);
        int i = f5.applyAsInt(ib);
        long l = f6.applyAsLong(lb);
        double d = f7.applyAsDouble(db);
        l = f8.applyAsLong(12);
        d = f9.applyAsDouble(12);
        i = f10.applyAsInt(12);
        d = f11.applyAsDouble(12);
        i = f12.applyAsInt(13.0);
        l = f13.applyAsLong(13.0);
    }
}

这些 Lambda 表达式尝试生成适合函数签名的最简代码。 在某些情况下有必要进行强制类型转换,否则编译器会报截断错误。

main()中的每个测试都显示了 Function 接口中不同类型的 apply() 方法。 每个都产生一个与其关联的 Lambda 表达式的调用。

方法引用有自己的小魔法:

import java.util.function.*;

class In1 {
}

class In2 {
}

public class MethodConversion {
    static void accept(In1 i1, In2 i2) {
        System.out.println("accept()");
    }

    static void someOtherName(In1 i1, In2 i2) {
        System.out.println("someOtherName()");
    }

    public static void main(String[] args) {
        BiConsumer<In1, In2> bic;

        bic = MethodConversion::accept;
        bic.accept(new In1(), new In2());

        bic = MethodConversion::someOtherName;
        // bic.someOtherName(new In1(), new In2()); // Nope
        bic.accept(new In1(), new In2());
    }
}

输出结果:

在这里插入图片描述

查看 BiConsumer 的文档,你会看到它的函数式方法为 accept() 。 的确,如果我们将方法命名为 accept(),它就可以作为方法引用。 但是我们也可用不同的名称,比如 someOtherName()。只要参数类型、返回类型与 BiConsumeraccept() 相同即可。

因此,在使用函数接口时,名称无关紧要——只要参数类型和返回类型相同。 Java 会将你的方法映射到接口方法。 要调用方法,可以调用接口的函数式方法名(在本例中为 accept()),而不是你的方法名。

现在我们来看看,将方法引用应用于基于类的函数式接口(即那些不包含基本类型的函数式接口)。下面的例子中,我创建了适合函数式方法签名的最简单的方法:

import java.util.*;
import java.util.function.*;

class AA {
}

class BB {
}

class CC {
}

public class ClassFunctionals {
    static AA f1() {
        return new AA();
    }

    static int f2(AA aa1, AA aa2) {
        return 1;
    }

    static void f3(AA aa) {
    }

    static void f4(AA aa, BB bb) {
    }

    static CC f5(AA aa) {
        return new CC();
    }

    static CC f6(AA aa, BB bb) {
        return new CC();
    }

    static boolean f7(AA aa) {
        return true;
    }

    static boolean f8(AA aa, BB bb) {
        return true;
    }

    static AA f9(AA aa) {
        return new AA();
    }

    static AA f10(AA aa1, AA aa2) {
        return new AA();
    }

    public static void main(String[] args) {
        Supplier<AA> s = ClassFunctionals::f1;
        s.get();
        Comparator<AA> c = ClassFunctionals::f2;
        c.compare(new AA(), new AA());
        Consumer<AA> cons = ClassFunctionals::f3;
        cons.accept(new AA());
        BiConsumer<AA, BB> bicons = ClassFunctionals::f4;
        bicons.accept(new AA(), new BB());
        Function<AA, CC> f = ClassFunctionals::f5;
        CC cc = f.apply(new AA());
        BiFunction<AA, BB, CC> bif = ClassFunctionals::f6;
        cc = bif.apply(new AA(), new BB());
        Predicate<AA> p = ClassFunctionals::f7;
        boolean result = p.test(new AA());
        BiPredicate<AA, BB> bip = ClassFunctionals::f8;
        result = bip.test(new AA(), new BB());
        UnaryOperator<AA> uo = ClassFunctionals::f9;
        AA aa = uo.apply(new AA());
        BinaryOperator<AA> bo = ClassFunctionals::f10;
        aa = bo.apply(new AA(), new AA());
    }
}

注意,每个方法名称都是随意的(如 f1()f2()等)。正如你刚才看到的,一旦将方法引用赋值给函数接口,我们就可以调用与该接口关联的函数方法。 在此示例中为 get()compare()accept()apply()test()

多参数函数式接口

java.util.functional 中的接口是有限的。比如有 BiFunction,但也仅此而已。 如果需要三参数函数的接口怎么办? 其实这些接口非常简单,很容易查看 Java 库源代码并自行创建。代码示例:

// functional/TriFunction.java

@FunctionalInterface
public interface TriFunction<T, U, V, R> {
    R apply(T t, U u, V v);
}

简单测试,验证它是否有效:

public class TriFunctionTest {
    static int f(int i, long l, double d) {
        return 99;
    }

    public static void main(String[] args) {
        TriFunction<Integer, Long, Double, Integer> tf =
                TriFunctionTest::f;
        tf = (i, l, d) -> 12;
    }
}

这里我们同时测试了方法引用和 Lambda 表达式。

缺少基本类型的函数

让我们重温一下 BiConsumer,看看我们将如何创建各种缺失的预定义组合,涉及 intlongdouble (基本类型):

import java.util.function.*;

public class BiConsumerPermutations {
    static BiConsumer<Integer, Double> bicid = (i, d) ->
            System.out.format("%d, %f%n", i, d);
    static BiConsumer<Double, Integer> bicdi = (d, i) ->
            System.out.format("%d, %f%n", i, d);
    static BiConsumer<Integer, Long> bicil = (i, l) ->
            System.out.format("%d, %d%n", i, l);

    public static void main(String[] args) {
        bicid.accept(47, 11.34);
        bicdi.accept(22.45, 92);
        bicil.accept(1, 11L);
    }
}

输出结果:

在这里插入图片描述

这里使用 System.out.format() 来显示。它类似于 System.out.println() 但提供了更多的显示选项。 这里,%f 表示我将 n 作为浮点值给出,%d 表示 n 是一个整数值。 这其中可以包含空格,输入 %n 会换行 — 当然使用传统的 \n 也能换行,但 %n 是自动跨平台的,这是使用 format() 的另一个原因。

上例只是简单使用了合适的包装类型,而装箱和拆箱负责它与基本类型之间的来回转换。 又比如,我们可以将包装类型和Function一起使用,而不去用各种针对基本类型的预定义接口。代码示例:

import java.util.function.*;

public class FunctionWithWrapped {
    public static void main(String[] args) {
        Function<Integer, Double> fid = i -> (double) i;
        IntToDoubleFunction fid2 = i -> i;
    }
}

如果没有强制转换,则会收到错误消息:“Integer cannot be converted to Double”(Integer 无法转换为 Double),而使用 IntToDoubleFunction 就没有此类问题。 IntToDoubleFunction 接口的源代码是这样的:

@FunctionalInterface
public interface IntToDoubleFunction {
    double applyAsDouble(int value);
}

因为我们可以简单地写 Function <Integer,Double> 并产生正常的结果,所以用基本类型(IntToDoubleFunction)的唯一理由是可以避免传递参数和返回结果过程中的自动拆装箱,进而提升性能。

似乎是考虑到使用频率,某些函数类型并没有预定义。

当然,如果因为缺少针对基本类型的函数式接口造成了性能问题,你可以轻松编写自己的接口( 参考 Java 源代码)——尽管这里出现性能瓶颈的可能性不大。

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

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

相关文章

系统架构设计师-计算机网络

目录 一、计算机网络技术概述 1、网络概述 2、网络有关指标 3、网络分类 4、5G技术 二、组网技术 1、交换技术 2、基本交换原理 三、TCP/IP协议簇 1、DHCP 2、DNS 四、网络规划与设计 一、计算机网络技术概述 1、网络概述 计算机网络的功能&#xff1a; &#xff08;1&…

项目经理升级却面临挑战?如何解决任务分解和成员职责不明难题

有一个朋友刚升任项目经理。但他这两天却一副愁眉不展的样子&#xff0c;因为他之前是做技术的&#xff0c;缺乏管理经验&#xff0c;在制定计划时没有合理的分解任务&#xff0c;并且没有明确项目成员的职责&#xff0c;导致项目在推进过程中项目进度不清晰。 项目管理涉及到…

Mendeley在linux中无法打开APPimage

原因:FUSE 库为用户空间程序提供了一个接口&#xff0c;可以将虚拟文件系统导出到 Linux 内核。由于缺少这个关键库&#xff0c;AppImage 无法按预期工作。 1 安装fuse,打开终端,输入命令 sudo apt install libfuse2 输入用户密码结,果如下 2 确保APPimage作为程序运行 右击…

在阿里云 linux 服务器上查看当前服务器的Nginx配置信息

我们可以通过命令 sudo nginx -t查看到nginx.conf的路径 可以通过 sudo nginx -T查看 nginx 详细配置信息&#xff0c;包括加载的配置文件和配置块的内容 其中也会包括配置文件的内容

【k8s】Kubernetes版本v1.17.3 kubesphere 3.1.1 默认用户登录失败

1.发帖&#xff1a; Kubernetes版本v1.17.3 kubesphere 3.11 默认用户登录失败 - KubeSphere 开发者社区 2. 问题日志&#xff1a; 2.1问题排查方法 &#xff1a; 用户无法登录 http://192.168.56.100:30880/ 2.2查看用户状态 kubectl get users [rootk8s-node1 ~]# k…

Java 多线程系列Ⅶ(线程安全集合类)

线程安全集合类 前言一、多线程使用线性表二、多线程使用栈和队列三、多线程下使用哈希表 前言 在数据结构中&#xff0c;我们学习过 Java 的内置集合&#xff0c;但是我们知道&#xff0c;我们学过的大多数集合类都是线程不安全的&#xff0c;少数如 Vector&#xff0c;Stack…

Fastjson_1.2.24_unserialize_rce漏洞复现

fastjson_1.2.24_unserialize_rce 说明内容漏洞编号CNVD-2017-02833漏洞名称FastJson < 1.2.24 远程代码执行漏洞评级高危影响范围1.2.24漏洞描述通过parseObject/parse将传入的字符串反序列化为Java对象时由于没有进行合理检查修复方案升级组件&#xff0c;打补丁&#xf…

PWmat计算再发Science:用于甲烷热解高效制氢的三元镍钼铋液态合金催化剂

文章信息 原标题: Ternary NiMo-Bi liquid alloy catalyst for efficient hydrogen production from methane pyrolysis 中文标题&#xff1a;用于甲烷热解高效制氢的三元镍钼铋液态合金催化剂 作者&#xff1a;Luning Chen, Zhigang Song, Shuchen Zhang, Chung-Kai Chang…

opencv 基础(持续更新中)

1 前言 https://www.couragesteak.com/ 2 安装 3 基础属性demo 打开一张图片&#xff1a; import cv2img cv2.imread(./girl.jpg)print(img.shape) # (1536, 1024, 3) 数组形状 print(type(img)) # numpy 数组 print(img) # 三维数组&#xff08;彩色图片&am…

基于SSM的校园快递代取系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

两个好用的数据标注软件labelme和CVAT

我们使用yolov3、yolov4、yolov5、yolov8等训练自己的权重时&#xff0c;需要有大量标注好的数据集&#xff0c;这里有两个好用的数据标注软件labelme和CVAT 一、labelme labelme&#xff1a;https://github.com/wkentaro/labelme 这个软件用的比较多&#xff0c;但是会经常更…

10:00面试,10:06就出来了,问题问的实在有点变态

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到8月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降30%,…

poi-tl word模版生成、动态表格、坑点合集

一、配置 1、导入依赖 <dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.10.0</version></dependency>apache poi版本要对应 <dependency><groupId>org.apache.poi</…

Docker安装RabbitMQ集群_亲测成功

先安装Docker Centos7离线安装Docker 华为云arm架构安装Docker RabbitMQ集群模式介绍 RabbitMQ集群搭建和测试总结_亲测 RabbitMQ 有三种模式&#xff1a;单机模式&#xff0c;普通集群模式&#xff0c;镜像集群模式。单机模式即单独运行一个 rabbitmq 实例&#xff0c;而…

【LangChain系列 6】Prompt模版——自定义prompt模版

原文地址&#xff1a;【LangChain系列 6】Prompt模版——自定义prompt模版 本文速读&#xff1a; 自定义prompt模版 LangChain提供了很多默认的prompt模版&#xff0c;同时LangChain提供了两种基础prompt模版&#xff1a; 字符串prompt模版 对话prompt模版 基于这两种模版&…

1-5 AUTOSAR数据交换文件ARXML

目录 一、Arxml文件 二、各类ARXML文件 一、Arxml文件 arxml文件是AUTOSAR&#xff08;Automotive Open System Architecture&#xff09;标准定义的XML文件&#xff0c;用于描述汽车电子系统中的软件组件、通信接口和参数配置等信息。 arxml文件的主要作用是在AUTOSAR架构下…

秋招,面试被问麻了....

前几天组了一个软件测试面试的群&#xff0c;没想到效果直接拉满&#xff0c;看来大家对面试这块的需求还是挺迫切的。昨天我就看到群友们发的一些面经&#xff0c;感觉非常有参考价值&#xff0c;于是我就问他还有没有。 结果他给我整理了一份非常硬核的面筋&#xff0c;打开…

使用STATCOM对电力系统进行潮流分析(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

遥感数据与作物模型同化技术应用

基于过程的作物生长模拟模型DSSAT是现代农业系统研究的有力工具&#xff0c;可以定量描述作物生长发育和产量形成过程及其与气候因子、土壤环境、品种类型和技术措施之间的关系&#xff0c;为不同条件下作物生长发育及产量预测、栽培管理、环境评价以及未来气候变化评估等提供了…

使用k8s helm离线部署spark-operator(私有仓库)

制作镜像 docker pull ghcr.io/googlecloudplatform/spark-operator:v1beta2-1.3.8-3.1.1 docker images docker save ImageID > ./spark.tar将制作的镜像上传到目的机器中&#xff0c;加载镜像 docker load < ./spark.tar打标签其中xxxx.xxx/xx/为私有仓库的地址 doc…