初识Java 11-1 函数式编程

news2024/11/17 23:57:08

目录

旧方式与新方式

lambda表达式

方法引用

Runnable

未绑定方法引用

构造器方法引用

函数式接口

带有更多参数的函数式接口

解决缺乏基本类型函数式接口的问题


本笔记参考自: 《On Java 中文版》


        函数式编程语言的一个特点就是其处理代码片段的简易性,就像处理数据一样简单。Java 8加入的lambda表达式方法引用为函数式风格编程做出了一定的支持。

        在计算机的早期时代,为了让程序能够适应有限的内存,程序员往往需要在程序执行时修改内存中的代码,让程序做出不同的行为,依此节省空间。这就是自修改代码技术。因为彼时的程序大都足够小,因此维护起来并不会太麻烦。

        但随着内存的增大,自修改代码被认为是一个糟糕的想法,它极大地增加了程序的维护成本。尽管如此,这种使用代码以某种方式操纵其他代码的想法依旧十分吸引人:通过组合已经经过良好测试的代码,我们可以生产出更有效率、更加安全的代码。

        函数式编程的意义就在于此:通过整合现有代码来产生新的功能,而不是从零开始编写所有内容,由此可以得到更加可靠、实现起来更快的代码。

    面向对象编程抽象数据,而函数式编程抽象行为。

        纯函数式语言在安全方面规定了额外的约束条件,所有的数据必须是不可变的:设置一次,永不改变。此时函数绝对不会修改现有值,而是只生成新值。(纯函数式语言在面对一些问题时能够提出一个好的解决,但这不代表纯函数式语言就是最好的解决方式)

        Python等非函数式编程语言已经将函数式编程的概念纳入其中,并且受益匪浅。Java也加入了类似的特性。

旧方式与新方式

        通过将代码传递给方法,我们可以控制方法,使其产生出不同的行为。

        旧的方式是创建一个对象(在下例中是Strategy),让其的某个方法包含所需行为,在将这个对象传递给我们想要控制的方法:

package functional;

import java.util.Locale;

interface Strategy {
    String approch(String msg);
}

class Soft implements Strategy {
    @Override
    public String approch(String msg) {
        return msg.toLowerCase() + "?";
    }
}

class Unrelated {
    static String twice(String msg) {
        return msg + " " + msg;
    }
}

public class Strategize {
    Strategy strategy;
    String msg;

    Strategize(String msg) {
        strategy = new Soft(); // 将Soft()作为一个默认的决策
        this.msg = msg;
    }

    void communicate() {
        System.out.println(strategy.approch(msg));
    }

    void changeStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public static void main(String[] args) {
        Strategy[] strategies = {
                new Strategy() { // 创建一个匿名内部类来改变行为,虽然依旧会有重复的代码
                    @Override
                    public String approch(String msg) {
                        return msg.toUpperCase() + "!";
                    }
                },
                msg -> msg.substring(0, 5), // 这就是Java 8开始提供的lambda表达式
                Unrelated::twice // 这也是Java 8中出现的方法引用
        };

        Strategize s = new Strategize("Hello there");
        s.communicate();
        for (Strategy newStrategy : strategies) {
            s.changeStrategy(newStrategy); // 遍历数组strategies中的每一个决策,并将其放入s中进行决策更换
            s.communicate(); // 更换决策后,每一次输出都会产生不同的结果:我们传递了行为,而被仅仅是数据
        }
    }
}

        程序执行的结果是:

        Strategy提供的接口包含了唯一的approach()方法。通过创建不同的Strategy对象,就可以创建不同的行为。

        在上述程序中,包含了默认的决策Soft()和一个匿名内部类。除此之外,还出现两个了Java 8添加的新内容:

  1. lambda表达式:
    msg -> msg.substring(0, 5)

    这种表达式的特点是使用箭头->分隔参数和函数体。

  2. 方法引用:
    Unrelated::twice

    特点是::。其中,::左边是类名或对象名,右边是方法名,但没有参数列表。

        在Java 8之前,使用普通的类或者匿名内部类来传递功能,但这种语法的并不方便。lambda表达式和方法引用改变了这种情况,使得传递功能变得更加便捷。

lambda表达式

||| lambda表达式是使用尽可能少的语法编写的函数定义

        换言之,lambda表达式产生的是函数,而不是方法。当然,Java中的一切都是类,之所以lambda表达式会让人产生这种“错觉”,是因为幕后进行了各种各样的操作。作为程序员,我们可以将lambda表达式视为函数。

        lambda表达式的语法宽松,且易于编写。例如:

package functional;

interface Description {
    String brief();
}

interface Body {
    String detailed(String head);
}

interface Multi {
    String twoArg(String head, Double d);
}

public class LambdaExpressions {
    static Body bod = h -> h + "No Parens!"; // 本条语句并不需要使用括号(仅限只有一个参数时)
    static Body bod2 = (h) -> h + "More details"; // 使用了括号。处于一致性的考虑,在只有一个参数时也使用括号
    static Description desc = () -> "Short info"; // 若没有参数,必须使用括号来指示空的参数列表
    static Multi mult = (h, n) -> h + n; // 有多个参数,此时必须将它们放在使用括号包裹的参数列表中

    static Description moreLines = () -> {
        System.out.println("moreLines()");
        return "from moreLines()";
    };

    public static void main(String[] args) {
        System.out.println(bod.detailed("Oh!"));
        System.out.println(bod2.detailed("Hi!"));
        System.out.println(desc.brief());
        System.out.println(mult.twoArg("Pi: ", 3.14159));
        System.out.println(moreLines.brief());
    }
}

        程序执行的结果是:

        在上述的3个接口中,每个接口都有一个方法(这是后续会提到的函数式接口)。任何lambda表达式的基本语法如下:

    注释中提到过,若没有参数,就必须使用括号来指示空的参数列表。

        对一行的lambda表达式而言,方法体中表达式的结果会自动成为lambda表达式的返回值,所以这里使用return关键字是不和法的。另外,若lambda表达式需要多行代码,如上文中的moreLines,就需要将表达式的代码放入到花括号中。此时又会需要使用return从lambda表达式中生成一个值了。

    可以看到,lambda表达式可以通过接口更方便地生成行为不同的对象。

递归

        递归,即函数调用了自身。Java也允许编写递归的lambda表达式,但需要注意一点:这个lambda表达式必须被赋值给一个静态变量或一个实例变量。通过两个示例说明这些情况:

        两个示例会使用一个相同的接口:

interface IntCall {
    int call(int arg);
}

【示例:静态变量】实现一个阶乘函数,递归计算小于等于n的正整数的乘积:

public class RecursiveFactorial {
    static IntCall fact;

    public static void main(String[] args) {
        fact = n -> n == 0 ? 1 : n * fact.call(n - 1);
        for (int i = 0; i <= 10; i++)
            System.out.println(fact.call(i));
    }
}

        程序执行的结果是:

        在这个例子中,fact就是一个静态变量。递归函数会不断调用其自身,因此必须有某种停止条件(在上述例子中是n == 0),否则就会陷入无限递归,直到栈空间被耗尽。

        下方这种初始化fact的方式是不被允许的:

static IntCall fact = n -> n == 0 ? 1 : n * fact.call(n - 1);

这种处理对Java编译器而言还是太过复杂了,会导致编译错误。

---

【示例:示例变量】实现斐波那契数列:

public class RecursiveFibonacci {
    IntCall fib;

    RecursiveFibonacci() {
        fib = n -> n == 0 ? 0 :
                n == 1 ? 1 :
                        fib.call(n - 1) + fib.call(n - 2);
    }

    int fibonacci(int n) {
        return fib.call(n);
    }

    public static void main(String[] args) {
        RecursiveFibonacci rf = new RecursiveFibonacci();
        for (int i = 0; i <= 10; i++)
            System.out.println(rf.fibonacci(i));
    }
}

        程序执行的结果是:

方法引用

        Java 8提供的方法引用,其指向的是方法。方法引用的格式如下:

interface Callable {
    void call(String s); // 与hello()和show()的签名了保持一致
}

class Describe {
    void show(String msg) {
        System.out.println(msg);
    }
}

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

    static class Description { // 定义一个内部类
        String about;

        Description(String desc) {
            about = desc;
        }

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

    static class Helper {
        static void assist(String msg) { // assist()是静态内部类中的一个静态方法
            System.out.println(msg);
        }
    }

    public static void main(String[] args) {
        Describe d = new Describe();
        Callable c = d::show; // 将Describe对象的show方法赋给了Callable
        c.call("call()"); // 通过call(),调用了show()

        c = MethodReferences::hello; // 等号右边是一个静态方法引用
        c.call("Bob");

        c = new Description("valuable")::help; // 对某个活跃对象上的方法的方法引用(“绑定方法引用”)
        c.call("information");

        c = Helper::assist; // 获得静态内部类中的静态方法的方法引用
        c.call("Help!");
    }
}

        程序执行的结果是:

        在上述程序中,Callable.call()Describe.show()MethodReferences.hello(),这三者的签名保持了一致。这解释了为什么语句 Callable c = d::show; 及其之后的语句能够顺利编译。

Runnable

        Runnable是一个java.lang包提供的接口。这个包遵循特殊的单方法接口格式:它的run()方法没有参数,也没有返回值。

因此,可以将lambda表达式或方法引用用作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("定义一个run()方法");
            }
        }).start();

        new Thread(() -> System.out.println("这是一个lambda表达式")).start();

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

        程序执行的结果是:

        Thread类在官方文档中的描述如下:

        Thread会接受一个Runnable作为其构造器参数,它的start()方法会调用run()

    只有匿名内部类需要提供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; // 无效的方法引用
        TransformX sp = X::f;
        X x = new X();

        // 下列两条语句的效果是相同的
        System.out.println(sp.transform(x));
        System.out.println(x.f());
    }
}

        程序执行的结果是:

        在上述例子之前,示例中对方法的引用,方法与其关联接口的签名是相同的。但这里出现了特例:

MakeString ms = X::f;

编译器不允许上述语句的编译,若强制执行,会引发报错(此为IDEA的报错信息)

这个报错指出X::f是一个未绑定方法引用,因为这里涉及到了一个隐藏的参数:this。若把这条有问题的语句换成下列语句,则没有问题:

X x = new X();
MakeString ms = x::f; // 无效的方法引用

上下两种语句的区别就在于,下方的语句提供了一个可供附着的X的对象x,这使得调动f()变为可能。X::f本身是无法“绑定到”一个对象上的。

        显而易见,除了自己生成一个对象外,我们还有另一个方式能解决这个问题。关键在于,我们还需要一个额外的参数,如TransformX中所示:

String transform(X x);

这种做法告诉我们:函数式方法(接口中的单一方法)的签名与方法引用的签名不必完全匹配。

        最后在看看这条语句:

System.out.println(sp.transform(x));

在前述知识的基础上,可以推断这条语句执行的过程:println()接受了一个未绑定引用,x作为参数在这个引用中调用了transform(),最终调用了x.f()

        若一个方法具有多个参数,则只需要让第一个参数使用这种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, 2.14);
        threeargs.call3(athis, 11, 3.14, "Three");
        fourargs.call4(athis, 11, 3.14, "Four", 'Z');
    }
}

构造器方法引用

        同样的,也可以对构造器的引用进行捕获,此后通过这个引用来调用构造器:

class Dog {
    String name;
    int age = -1;

    Dog() {
        name = "流浪狗";
    }

    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) {
        // 所有这3个构造器都只有一个名字 ::new
        MakeNoArgs mna = Dog::new;
        Make1Arg m1a = Dog::new;
        Make2Args m2a = Dog::new;

        Dog dn = mna.make();
        Dog d1 = m1a.make("卡卡");
        Dog d2 = m2a.make("拉尔夫", 4);
    }
}

        注意语句Dog::new。3条相同的语句告诉我们,这些构造器都有(且只有)一个名字 —— ::new。并且每一个引用都被赋予了不同的接口,编译器可以从接口来推断所需使用的构造器。

        在这里,调用函数式接口方法(make())意味着调用构造器。

函数式接口

        方法引用和lambda表达式都需要先赋值,然后才能进行使用。而这些赋值都需要类型信息,让编译器确保类型的正确性。尤其是lambda表达式。例如:

x -> x.toString()

        toString()方法会返回String,但上述语句并没有表示x的类型。这时候就需要进行类型推断了。因此,编译器必须要能够通过某种方式推断出x的类型。

        还有其他例子:

(x, y) -> x + y // 需要考虑String类型存在与否
System.out::println

为了解决这种类型推断的问题,Java 8引入了包含一组接口的java.util.function,这些接口是lambda表达式和方法引用的目标类型。其中的每个接口都只包含了一个抽象方法(非抽象方法可以有多个),被称为函数式方法

        使用了这种”函数式方法“模式的接口,可以通过@FunctionalInterface注解来强制执行:

@FunctionalInterface
interface Functional { // 使用了注解
    String goodbye(String arg);
}

interface FunctionNoAnn { // 没有使用注解
    String goodbye(String arg);
}

//@FunctionalInterface
//interface NoFunctional{ // 内置了两个方法,不符合函数式方法定义
//    String goodbye(String arg);
//    String hello(String arg);
//}

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;
        FunctionNoAnn fna = fa::goodbye;
//        Functional fac = fa; // 类型不兼容

        Functional f1 = arg -> "Goodbye, " + arg;
        FunctionNoAnn fnal = arg -> "Goodbye, " + arg;
    }
}

        @FunctionalInterface注解是可选的。当只有一个方法时,Java把main()中的FunctionalFunctionalNoAnn都视为了函数式接口。

        现在看向两条赋值语句:

Functional f = fa::goodbye;
FunctionNoAnn fna = fa::goodbye;

这两条赋值语句均把一个方法(这个方法甚至不是接口方法的实现)赋值给了一个接口引用。这是Java 8增加的功能:若把一个方法引用或lambda表达式赋值给某个函数式接口(且类型匹配),匿名Java会调整这次赋值,使其能够匹配目标接口。

    在底层的实现中,Java编译器会创建一个实现了目标接口的类的示例,并将我们进行赋值的方法引用或lambda表达式包裹在其中。

        使用了@FunctionalInterface注解的接口也叫做单一抽象方法

命名规则

        java.util.function旨在创建一套足够完备的接口。一般来说,可以通过接口的名字了解接口的作用。以下是基本的命名规则(也可以去官方文档进行查看):

  • 只处理对象(而不是基本类型):名字较为直接,如FunctionConsumerPredicate等。
  • 接受一个基本类型的参数:使用名字的第一部分表示,如LongConsumerDoubleFunctionInPredicate(例外:基本的Supplier类型)
  • 返回的是基本类型的结果:To表示,例如ToLongFunction<T>IntToLongFunction
  • 返回类型和参数类型相同:被命名为OperatorUnaryOperator表示一个参数,BinaryOperator表示两个参数。
  • 接受一个参数并返回boolean被命名为Predicate
  • 接受两个不同类型的参数:名字中会有一个Bi(比如BiPredicate)。

    因为基本类型的存在,Java在设计这些接口时不得不考虑众多的类型,这无疑增加了Java的复杂性。

        例如:

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 ToLongFunction<LBaz> f4 = lb -> lb.l;
    static DoubleToIntFunction f5 = d -> (int) d;

    public static void main(String[] args) {
        Bar b = f1.apply(new Foo());
        IBaz ib = f2.apply(11);

        LBaz lb = f3.apply(12);
        long l = f4.applyAsLong(lb);

        int i = f5.applyAsInt(14);
    }
}

        在一些情况下,需要使用类型转换,否则编译器会报出截断错误。上述程序中的每个方法都会调用其关联的lambda表达式。

        方法引用还有一些特别的用法:

import java.util.function.BiConsumer;

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("somwOtherName()");
    }

    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); //行不通
        bic.accept(new In1(), new In2());
    }
}

        程序执行的结果是:

        以下是BitConSumer的文档说明:

这个接口有一个accept()方法可以被用作方法引用。并且,即使名字并不相同,如someOtherName(),只要参数类型和返回类型能够与BiConsumeraccept()相同,也没有问题。

    使用函数式接口时,名字不重要,重要的是参数类型和返回类型。

        Java会负责将我们起的名字映射到函数式方法上。若要调用我们的方法,就需要调用这个函数式方法的名字。

带有更多参数的函数式接口

        java.util.function中的接口是有限的,同时也是直观易懂的。因此,当我们需要的接口并没有在java.util.function中被提供时,我们也可以轻松地编写自己的接口:

@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) -> (i + l.intValue() + d.intValue());
        System.out.println(tf.apply(12, 12l, 12d));
    }
}

        程序执行成功,输出36


解决缺乏基本类型函数式接口的问题

        可以通过使用BiConsumer这种面向对象的接口,开创建java.util.function中没有提供的,涉及int等基本类型的函数式接口:

import java.util.function.BiConsumer;

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("%f, %d%n", d, i);
    static BiConsumer<Integer, Long> bicil = (i, l) -> System.out.format("%d, %d%n", i, l);

    public static void main(String[] args) {
        bicid.accept(11, 45.14);
        bicdi.accept(11.45, 14);
        bicil.accept(1, 14L);
    }
}

        程序执行的结果是:

        上述程序使用了System.out.format(),这个方法支持%n这种跨平台的字符。

        这个例子中发生了自动装箱和自动拆箱,通过这种方式,我们可以获得处理基本类型的接口。同样的,可以在其他函数式接口中使用包装类:

import java.util.function.Function;
import java.util.function.IntToDoubleFunction;

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

        需要注意的是使用强制类型转换的时机,否则会出现报错。

        可以发现,只需要通过包装类就可以获得一个用来处理基本类型的函数式接口。因此,若存在函数式接口的基本类型变种,其唯一的原因就是防止自动装箱/拆箱过程带来的性能损耗。

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

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

相关文章

从零开始之了解电机及其控制(10)空间矢量理论

与一维数字转子位置不同&#xff0c;电流和电压都是二维的。可以在矩形笛卡尔平面中考虑这些尺寸。 用旋转角度和幅度来描述向量 虽然电流命令的幅度和施加的电压是进入控制器的误差项的函数&#xff0c;它们施加的角度是 d-q 轴方向的函数&#xff0c;因此也是转子位置的函数。…

目标追踪学习经验总结

标题目标追踪算法学习经验总结   最近对目标追踪算法进行了学习&#xff0c;以下是我的学习经验&#xff0c;如有不对之处&#xff0c;欢迎大家指正。 1、简介 1.1 定义 目标跟踪是通过分析视频图片序列&#xff0c;对检测出的各个候选目标区域实施匹配&#xff0c;定位出这…

架构设计师

一、软件架构 .特定领域软件架构(Domain Specific Software Architecture,DSSA) &#xff08;1&#xff09;垂直域。定义了一个特定的系统族,导出在该领域中可作为系统的可行解决方案的一个通用软件架构。 &#xff08;2&#xff09;水平域。定义了在多个系统和多个系统族中功…

pt28django教程

缓存 缓存是一类可以更快的读取数据的介质统称&#xff0c;读取硬盘、较复杂的计算、渲染都会产生较大的耗时。数据变化频率不会很高的场景适合使用缓存。使用缓存场景&#xff1a;博客列表页、电商商品详情页、缓存导航及页脚。 Django中设置缓存 Django中提供多种缓存方式…

解决 MyBatis-Plus 中增加修改时,对应时间的更新问题

问题&#xff1a;在添加修改时&#xff0c;对应的 create_time 与 insert_time 不会随着添加修改而自动的更新时间 第一步&#xff1a;首先在对应的属性上&#xff0c;加上以下注解 如果只添加以下注解&#xff0c;在增加或者修改时&#xff0c;可能对应的 LocalDateTime 会出…

大学生登记国家证书软件著作权提升就业资质

大学生登记国家证书软件著作权提升就业资质 随着信息技术的快速发展&#xff0c;软件行业成为了许多大学生就业的热门选择之一。然而&#xff0c;在竞争激烈的就业市场中&#xff0c;除了掌握专业知识和技能外&#xff0c;如何提升自己的就业资质也显得尤为重要。其中&#xff…

【数据结构】冒泡排序,快速排序的学习知识总结

目录 1、冒泡排序 1.1 算法思想 1.2 代码实现 方式一&#xff1a;顺序表 方式二&#xff1a;链表 2、快速排序 2.1 算法思想 2.2 代码实现 2.3 例题分析 1、冒泡排序 1.1 算法思想 冒泡排序是一种简单的排序算法&#xff0c;它的基本思想是从数组的第一个元素开始…

2009 款沃尔沃 S80L 车换挡顿挫

故障现象 一辆2009款沃尔沃S80L车&#xff0c;搭载2.5T发动机和6速自动变速器&#xff0c;累计行驶里程约为30万km。车主反映&#xff0c;换入D挡或R挡时车辆会顿挫。 故障诊断 接车后试车&#xff0c;起动着机后仪表无故障灯点亮&#xff0c;踩住制动踏板&#xff0c;换入D挡或…

LetCode算法题---第2天

注:大佬解答来自LetCode官方题解 80.删除有序数组的重复项Ⅱ 1.题目 2.个人解答 var removeDuplicates function (nums) {let res [];for (let index 0; index < nums.length; index) {let num 0;if (res.includes(nums[index])) {for (let i 0; i < res.length; …

软考高级之系统架构师之软件需求工程

概述 一个完整的软件生存周期是以需求为出发点。软件需求是指用户对系统在功能、行为、性能、设计约束等方面的期望。 需求开发&#xff1a; 需求获取需求分析需求定义&#xff08;需求规格说明书&#xff09;需求验证 需求管理: 变更控制版本控制需求跟踪需求状态跟踪 需…

MATLAB 多信号显示方案

效果示例 重要参考资料 MATLAB官方资料 matlab plot3 函数说明 利用for循环和plot函数画多条曲线时如何添加图例&#xff1f; x1:100; krandperm(20)‘; A{‘aa’,‘bb’,‘ccc’,‘ddd’}; y(k*x)’ for i1:20 plot(x,y(:,i)) STRsprintf(‘曲线%d’,i); A(i)cellstr(STR); …

腾讯云 Cloud Studio 实战训练营结营活动获奖公示

点击链接了解详情 “腾讯云 Cloud Studio 实战训练营” 是由腾讯云联合 CSDN 推出的系列开发者技术实践活动&#xff0c;通过技术分享直播、动手实验项目、优秀代码评选、有奖征文活动等&#xff0c;让广大开发者沉浸式体验腾讯云开发者工具 Cloud Studio 的同时&#xff0c;实…

Docker——容器生命周期管理(下篇)

Docker 一、run1、options说明2、-p的三种写法3、实例14、实例25、实例36、实例47、实例58、实例69、实例78、实例89、退出容器 二、start/stop/restart1、语法格式2、stop/restart 命令的 options 三、kill1、重点2、说明3、实例 四、rm1、说明2、实例 五、create实例 六、exe…

【Qt】QTabWidget如何添加控件到Tab页水平位置

在开发中&#xff0c;QTabWidget控件经常出现在项目或软件中&#xff0c;有时为了美观兼顾操作便利&#xff0c;需要把按钮或其他控件添加到QTabWidget控件的Tab页水平位置。 实现思路&#xff1a; 查看帮助文档&#xff0c;发现该类有个方法void setCornerWidget()可以实现所…

Java笔记四(方法与递归)

方法 Java的方法类似于C语言的函数&#xff0c;是一段用来完成特定功能的代码片段&#xff0c;一般情况下&#xff0c;定义一个方法包含以下语法&#xff1a; 方法包含一个方法头和一个方法体&#xff0c;下面是一个方法的所有部分&#xff1a; ◆修饰符:修饰符,这是可选的&…

FPGA设计时序约束二、输入延时与输出延时

目录 一、背景 二、set_input_delay 2.1 set_input_delay含义 2.2 set_input_delay参数说明 2.3 使用样例 三、set_output_delay 3.1 set_output_delay含义 3.2 set_output_delay参数说明 3.3 使用样例 四、样例工程 4.1 工程代码 4.2 时序报告 五、参考资料 一、…

风光储一体化能源中心 | 图扑数字孪生

自“双碳”目标提出以来&#xff0c;我国能源产业不断朝着清洁低碳化、绿色化的方向发展。其中&#xff0c;风能、太阳能等可再生能源在促进全球能源可持续发展、共建清洁美丽世界中被寄予厚望。风能、太阳能具有波动性、间歇性、随机性等特点&#xff0c;主要通过转化为电能再…

如何在几分钟内创建一个对话机器人?

随着互联网的发展&#xff0c;人们迫切希望以快速高效的方式获取信息和解决问题&#xff0c;传统的人工客服渐渐地已经无法满足人们的需求。然而&#xff0c;对话机器人&#xff08;chatbot&#xff09;的出现可以很好地解决这个痛点。 对话机器人是一种人工智能工具&#xff…

[代码随想录]基本数据结构篇

文章目录 1.数组篇1.1 704-二分查找1.2 27-移除数组1.3 977-有序数组的平方1.4* 209--长度最小的子数组(滑动窗口)1.5* 59-螺旋矩阵II 2. 链表篇2.1 203-移除链表元素2.2 707-设计链表2.3 206-反转链表2.4* 24-两两交换链表中的节点(跳针)2.5* 19-删除链表的倒数第N个节点(快慢…

[Realtek sdk-3.4.14b]RTL8197FH-VG 2.4G to WAN吞吐量低于60%的问题分析及解决方案

问题描述 RTL8197FH-VG 2.4G wifi to WAN吞吐量低于65%的标准,正常2T2R的wifi 300Mbps x 65% = 195Mbps,但是实际只能跑到160Mbps,这个时候CPU的idl已经为0,sirq占用率达到98%左右 网络拓扑 一台PC通过2.4G WiFi连接到RTL8197FH-VG,另外一台PC直接通过WAN口连接到RTL8197…