二十、泛型(9)

本章概要

  • 对缺乏潜在类型机制的补偿
    • 反射
    • 将一个方法应用于序列
  • Java 8 中的辅助潜在类型
    • 使用 Suppliers 类的通用方法
  • 总结:类型转换真的如此之糟吗?

对缺乏潜在类型机制的补偿

尽管 Java 不直接支持潜在类型机制,但是这并不意味着泛型代码不能在不同的类型层次结构之间应用。也就是说,我们仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。

反射

可以使用的一种方式是反射,下面的 perform() 方法就是用了潜在类型机制:

import java.awt.*;
import java.lang.reflect.*;

// Does not implement Performs:
class Mime {
    public void walkAgainstTheWind() {
    }

    public void sit() {
        System.out.println("Pretending to sit");
    }

    public void pushInvisibleWalls() {
    }

    @Override
    public String toString() {
        return "Mime";
    }
}

// Does not implement Performs:
class SmartDog {
    public void speak() {
        System.out.println("Woof!");
    }

    public void sit() {
        System.out.println("Sitting");
    }

    public void reproduce() {
    }
}

class CommunicateReflectively {
    public static void perform(Object speaker) {
        Class<?> spkr = speaker.getClass();
        try {
            try {
                Method speak = spkr.getMethod("speak");
                speak.invoke(speaker);
            } catch (NoSuchMethodException e) {
                System.out.println(speaker + " cannot speak");
            }
            try {
                Method sit = spkr.getMethod("sit");
                sit.invoke(speaker);
            } catch (NoSuchMethodException e) {
                System.out.println(speaker + " cannot sit");
            }
        } catch (SecurityException |
                 IllegalAccessException |
                 IllegalArgumentException |
                 InvocationTargetException e) {
            throw new RuntimeException(speaker.toString(), e);
        }
    }
}

public class LatentReflection {
    public static void main(String[] args) throws AWTException {
        CommunicateReflectively.perform(new SmartDog());
        CommunicateReflectively.perform(new Robot());
        CommunicateReflectively.perform(new Mime());
    }
}

在这里插入图片描述

上例中,这些类完全是彼此分离的,没有任何公共基类(除了 Object )或接口。通过反射, CommunicateReflectively.perform() 能够动态地确定所需要的方法是否可用并调用它们。它甚至能够处理 Mime 只具有一个必需的方法这一事实,并能够部分实现其目标。

将一个方法应用于序列

反射提供了一些有用的可能性,但是它将所有的类型检查都转移到了运行时,因此在许多情况下并不是我们所希望的。如果能够实现编译期类型检查,这通常会更符合要求。但是有可能实现编译期类型检查和潜在类型机制吗?

让我们看一个说明这个问题的示例。假设想要创建一个 apply() 方法,它能够将任何方法应用于某个序列中的所有对象。这种情况下使用接口不适合,因为你想要将任何方法应用于一个对象集合,而接口不可能描述任何方法。如何用 Java 来实现这个需求呢?

最初,我们可以用反射来解决这个问题,由于有了 Java 的可变参数,这种方式被证明是相当优雅的:

import java.lang.reflect.*;

public class Apply {
    public static <T, S extends Iterable<T>>
    void apply(S seq, Method f, Object... args) {
        try {
            for (T t : seq) {
                f.invoke(t, args);
            }
        } catch (IllegalAccessException |
                 IllegalArgumentException |
                 InvocationTargetException e) {
            // Failures are programmer errors
            throw new RuntimeException(e);
        }
    }
}

Apply.java 中,异常被转换为 RuntimeException ,因为没有多少办法可以从这种异常中恢复——在这种情况下,它们实际上代表着程序员的错误。

为什么我们不只使用 Java 8 方法参考(稍后显示)而不是反射方法 f ? 注意,invoke()apply() 的优点是它们可以接受任意数量的参数。 在某些情况下,灵活性可能至关重要。

为了测试 Apply ,我们首先创建一个 Shape 类:

public class Shape {
    private static long counter = 0;
    private final long id = counter++;

    @Override
    public String toString() {
        return getClass().getSimpleName() + " " + id;
    }

    public void rotate() {
        System.out.println(this + " rotate");
    }

    public void resize(int newSize) {
        System.out.println(this + " resize " + newSize);
    }
}

被一个子类 Square 继承:

// generics/Square.java

public class Square extends Shape {}

通过这些,我们可以测试 Apply

ApplyTest.java

import java.util.*;

public class ApplyTest {
    public static void main(String[] args) throws Exception {
        List<Shape> shapes =
                Suppliers.create(ArrayList::new, Shape::new, 3);
        Apply.apply(shapes, Shape.class.getMethod("rotate"));
        Apply.apply(shapes, Shape.class.getMethod("resize", int.class), 7);

        List<Square> squares =
                Suppliers.create(ArrayList::new, Square::new, 3);
        Apply.apply(squares, Shape.class.getMethod("rotate"));
        Apply.apply(squares, Shape.class.getMethod("resize", int.class), 7);

        Apply.apply(new FilledList<>(Shape::new, 3),
                Shape.class.getMethod("rotate"));
        Apply.apply(new FilledList<>(Square::new, 3),
                Shape.class.getMethod("rotate"));

        SimpleQueue<Shape> shapeQ = Suppliers.fill(
                new SimpleQueue<>(), SimpleQueue::add,
                Shape::new, 3);
        Suppliers.fill(shapeQ, SimpleQueue::add,
                Square::new, 3);
        Apply.apply(shapeQ, Shape.class.getMethod("rotate"));
    }
}

在这里插入图片描述

Suppliers.java

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

public class Suppliers {
    // Create a collection and fill it:
    public static <T, C extends Collection<T>> C
    create(Supplier<C> factory, Supplier<T> gen, int n) {
        return Stream.generate(gen)
                .limit(n)
                .collect(factory, C::add, C::addAll);
    }

    // Fill an existing collection:
    public static <T, C extends Collection<T>>
    C fill(C coll, Supplier<T> gen, int n) {
        Stream.generate(gen)
                .limit(n)
                .forEach(coll::add);
        return coll;
    }

    // Use an unbound method reference to
    // produce a more general method:
    public static <H, A> H fill(H holder,
                                BiConsumer<H, A> adder, Supplier<A> gen, int n) {
        Stream.generate(gen)
                .limit(n)
                .forEach(a -> adder.accept(holder, a));
        return holder;
    }
}

FilledList.java

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

public class FilledList<T> extends ArrayList<T> {
    FilledList(Supplier<T> gen, int size) {
        Suppliers.fill(this, gen, size);
    }

    public FilledList(T t, int size) {
        for (int i = 0; i < size; i++) {
            this.add(t);
        }
    }

    public static void main(String[] args) {
        List<String> list = new FilledList<>("Hello", 4);
        System.out.println(list);
        // Supplier version:
        List<Integer> ilist = new FilledList<>(() -> 47, 4);
        System.out.println(ilist);
    }
}

Apply 中,我们运气很好,因为碰巧在 Java 中内建了一个由 Java 集合类库使用的 Iterable 接口。正由于此, apply() 方法可以接受任何实现了 Iterable 接口的事物,包括诸如 List 这样的所有 Collection 类。但是它还可以接受其他任何事物,只要能够使这些事物是 Iterable 的——例如,在 main() 中使用下面定义的 SimpleQueue 类:

import java.util.*;

public class SimpleQueue<T> implements Iterable<T> {
    private LinkedList<T> storage = new LinkedList<>();

    public void add(T t) {
        storage.offer(t);
    }

    public T get() {
        return storage.poll();
    }

    @Override
    public Iterator<T> iterator() {
        return storage.iterator();
    }
}

正如反射解决方案看起来那样优雅,我们必须观察到反射(尽管在 Java 的最新版本中得到了显着改进)通常比非反射实现要慢,因为在运行时发生了很多事情。 但它不应阻止您尝试这种解决方案,这依然是值得考虑的一点。

几乎可以肯定,你会首先使用 Java 8 的函数式方法,并且只有在解决了特殊需求时才诉诸反射。 这里对 ApplyTest.java 进行了重写,以利用 Java 8 的流和函数工具:

import java.util.stream.*;

public class ApplyFunctional {
    public static void main(String[] args) {
        Stream.of(
                        Stream.generate(Shape::new).limit(2),
                        Stream.generate(Square::new).limit(2))
                .flatMap(c -> c) // flatten into one stream
                .peek(Shape::rotate)
                .forEach(s -> s.resize(7));

        new FilledList<>(Shape::new, 2)
                .forEach(Shape::rotate);
        new FilledList<>(Square::new, 2)
                .forEach(Shape::rotate);

        SimpleQueue<Shape> shapeQ = Suppliers.fill(
                new SimpleQueue<>(), SimpleQueue::add,
                Shape::new, 2);
        Suppliers.fill(shapeQ, SimpleQueue::add,
                Square::new, 2);
        shapeQ.forEach(Shape::rotate);
    }
}

在这里插入图片描述

由于使用 Java 8,因此不需要 Apply.apply()

我们首先生成两个 Stream : 一个是 Shape ,一个是 Square ,并将它们展平为单个流。 尽管 Java 缺少功能语言中经常出现的 flatten() ,但是我们可以使用 flatMap(c-> c) 产生相同的结果,后者使用身份映射将操作简化为“ flatten ”。

我们使用 peek() 当做对 rotate() 的调用,因为 peek() 执行一个操作(此处是出于副作用),并在未更改的情况下传递对象。

注意,使用 FilledListshapeQ 调用 forEach()Apply.apply() 代码整洁得多。 在代码简单性和可读性方面,结果比以前的方法好得多。 并且,现在也不可能从 main() 引发异常。

Java8 中的辅助潜在类型

先前声明关于 Java 缺乏对潜在类型的支持在 Java 8 之前是完全正确的。但是,Java 8 中的非绑定方法引用使我们能够产生一种潜在类型的形式,以满足创建一段可工作在不相干类型上的代码。因为 Java 最初并不是如此设计,所以结果可想而知,比其他语言中要尴尬一些。但是,至少现在成为了可能,只是缺乏令人惊艳之处。

我在其他地方从没遇过这种技术,因此我将其称为辅助潜在类型。

我们将重写 DogsAndRobots.java 来演示该技术。 为使外观看起来与原始示例尽可能相似,我仅向每个原始类名添加了 A

DogsAndRobotMethodReferences.java

import java.util.function.*;

class PerformingDogA extends Dog {
    public void speak() {
        System.out.println("Woof!");
    }

    public void sit() {
        System.out.println("Sitting");
    }

    public void reproduce() {
    }
}

class RobotA {
    public void speak() {
        System.out.println("Click!");
    }

    public void sit() {
        System.out.println("Clank!");
    }

    public void oilChange() {
    }
}

class CommunicateA {
    public static <P> void perform(P performer,
                                   Consumer<P> action1, Consumer<P> action2) {
        action1.accept(performer);
        action2.accept(performer);
    }
}

public class DogsAndRobotMethodReferences {
    public static void main(String[] args) {
        CommunicateA.perform(new PerformingDogA(),
                PerformingDogA::speak, PerformingDogA::sit);
        CommunicateA.perform(new RobotA(),
                RobotA::speak, RobotA::sit);
        CommunicateA.perform(new Mime(),
                Mime::walkAgainstTheWind,
                Mime::pushInvisibleWalls);
    }
}

在这里插入图片描述

Dog.java

public class Dog extends Pet {
    public Dog(String name) {
        super(name);
    }

    public Dog() {
        super();
    }
}

Pet.java

public class Pet extends Individual {
    public Pet(String name) {
        super(name);
    }

    public Pet() {
        super();
    }
}

Individual.java

import java.util.*;

public class Individual implements Comparable<Individual> {
    private static long counter = 0;
    private final long id = counter++;
    private String name;

    public Individual(String name) {
        this.name = name;
    }

    // 'name' is optional:
    public Individual() {
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() +
                (name == null ? "" : " " + name);
    }

    public long id() {
        return id;
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof Individual &&
                Objects.equals(id, ((Individual) o).id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }

    @Override
    public int compareTo(Individual arg) {
        // Compare by class name first:
        String first = getClass().getSimpleName();
        String argFirst = arg.getClass().getSimpleName();
        int firstCompare = first.compareTo(argFirst);
        if (firstCompare != 0) {
            return firstCompare;
        }
        if (name != null && arg.name != null) {
            int secondCompare = name.compareTo(arg.name);
            if (secondCompare != 0) {
                return secondCompare;
            }
        }
        return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
    }
}

PerformingDogARobotADogsAndRobots.java 中的相同,不同之处在于它们不继承通用接口 Performs ,因此它们没有通用性。

CommunicateA.perform() 在没有约束的 P 上生成。 只要可以使用 Consumer <P>,它在这里就可以是任何东西,这些 Consumer<P> 代表不带参数的 P 方法的未绑定方法引用。当您调用 Consumeraccept() 方法时,它将方法引用绑定到执行者对象并调用该方法。 由于 函数式编程 一章中描述的“魔术”,我们可以将任何符合签名的未绑定方法引用传递给 CommunicateA.perform()

之所以称其为“辅助”,是因为您必须显式地为 perform() 提供要使用的方法引用。 它不能只按名称调用方法。

尽管传递未绑定的方法引用似乎要花很多力气,但潜在类型的最终目标还是可以实现的。 我们创建了一个代码片段 CommunicateA.perform() ,该代码可用于任何具有符合签名的方法引用的类型。 请注意,这与我们看到的其他语言中的潜在类型有所不同,因为这些语言不仅需要签名以符合规范,还需要方法名称。 因此,该技术可以说产生了更多的通用代码。

为了证明这一点,我还从 LatentReflection.java 中引入了 Mime

使用Suppliers类的通用方法

通过辅助潜在类型,我们可以定义本章其他部分中使用的 Suppliers 类。 此类包含使用生成器填充 Collection 的工具方法。 泛化这些操作很有意义:

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

public class Suppliers {
    // Create a collection and fill it:
    public static <T, C extends Collection<T>> C
    create(Supplier<C> factory, Supplier<T> gen, int n) {
        return Stream.generate(gen)
                .limit(n)
                .collect(factory, C::add, C::addAll);
    }

    // Fill an existing collection:
    public static <T, C extends Collection<T>>
    C fill(C coll, Supplier<T> gen, int n) {
        Stream.generate(gen)
                .limit(n)
                .forEach(coll::add);
        return coll;
    }

    // Use an unbound method reference to
    // produce a more general method:
    public static <H, A> H fill(H holder,
                                BiConsumer<H, A> adder, Supplier<A> gen, int n) {
        Stream.generate(gen)
                .limit(n)
                .forEach(a -> adder.accept(holder, a));
        return holder;
    }
}

create() 为你创建一个新的 Collection 子类型,而 fill() 的第一个版本将元素放入 Collection 的现有子类型中。 请注意,还会返回传入的容器的确切类型,因此不会丢失类型信息。

前两种方法一般都受约束,只能与 Collection 子类型一起使用。fill() 的第二个版本适用于任何类型的 holder 。 它需要一个附加参数:未绑定方法引用 adder. fill() ,使用辅助潜在类型来使其与任何具有添加元素方法的 holder 类型一起使用。

因为此未绑定方法 adder 必须带有一个参数(要添加到 holder 的元素),所以 adder 必须是 BiConsumer <H,A> ,其中 H 是要绑定到的 holder 对象的类型,而 A 是要被添加的绑定元素类型。 对 accept() 的调用将使用参数 a 调用对象 holder 上的未绑定方法 holder

在一个稍作模拟的测试中对 Suppliers 工具程序进行了测试,该仿真还使用了本章前面定义的 RandomList

BankTeller.java

// A very simple bank teller simulation

import java.util.*;

class Customer {
    private static long counter = 1;
    private final long id = counter++;

    @Override
    public String toString() {
        return "Customer " + id;
    }
}

class Teller {
    private static long counter = 1;
    private final long id = counter++;

    @Override
    public String toString() {
        return "Teller " + id;
    }
}

class Bank {
    private List<BankTeller> tellers =
            new ArrayList<>();

    public void put(BankTeller bt) {
        tellers.add(bt);
    }
}

public class BankTeller {
    public static void serve(Teller t, Customer c) {
        System.out.println(t + " serves " + c);
    }

    public static void main(String[] args) {
        // Demonstrate create():
        RandomList<Teller> tellers =
                Suppliers.create(
                        RandomList::new, Teller::new, 4);
        // Demonstrate fill():
        List<Customer> customers = Suppliers.fill(
                new ArrayList<>(), Customer::new, 12);
        customers.forEach(c ->
                serve(tellers.select(), c));
        // Demonstrate assisted latent typing:
        Bank bank = Suppliers.fill(
                new Bank(), Bank::put, BankTeller::new, 3);
        // Can also use second version of fill():
        List<Customer> customers2 = Suppliers.fill(
                new ArrayList<>(),
                List::add, Customer::new, 12);
    }
}

RandomList.java

import java.util.*;
import java.util.stream.IntStream;

public class RandomList<T> extends ArrayList<T> {
    private Random rand = new Random(47);

    public T select() {
        return get(rand.nextInt(size()));
    }

    public static void main(String[] args) {
        RandomList<String> rs = new RandomList<>();
        Arrays.stream(
                        ("The quick brown fox jumped over " +
                                "the lazy brown dog").split(" "))
                .forEach(rs::add);
        IntStream.range(0, 11).forEach(i ->
                System.out.print(rs.select() + " "));
    }
}

在这里插入图片描述

可以看到 create() 生成一个新的 Collection 对象,而 fill() 添加到现有 Collection 中。第二个版本fill() 显示,它不仅与无关的新类型 Bank 一起使用,还能与 List 一起使用。因此,从技术上讲,fill() 的第一个版本在技术上不是必需的,但在使用 Collection 时提供了较短的语法。

总结:类型转换真的如此之糟吗?

自从 C++ 模版出现以来,我就一直在致力于解释它,我可能比大多数人都更早地提出了下面的论点。直到最近,我才停下来,去思考这个论点到底在多少时间内是有效的——我将要描述的问题到底有多少次可以穿越障碍得以解决。

这个论点就是:使用泛型类型机制的最吸引人的地方,就是在使用集合类的地方,这些类包括诸如各种 List 、各种 Set 、各种 Map。在 Java 5 之前,当你将一个对象放置到集合中时,这个对象就会被向上转型为 Object ,因此你会丢失类型信息。当你想要将这个对象从集合中取回,用它去执行某些操作时,必须将其向下转型回正确的类型。

我用的示例是持有 CatList (这个示例的一种使用苹果和桔子的变体在 集合 章节的开头展示过)。如果没有 Java 5 泛型版本的集合,你放到容集里和从集合中取回的都是 Object 。因此,我们很可能会将一个 Dog 放置到 CatList 中。

但是,泛型出现之前的 Java 并不会让你误用放入到集合中的对象。如果将一个 Dog 扔到 Cat 的集合中,并且试图将这个集合中的所有东西都当作 Cat 处理,那么当你从这个 Cat 集合中取回那个 Dog 引用,并试图将其转型为 Cat 时,就会得到一个 RuntimeException 。你仍旧可以发现问题,但是是在运行时而非编译期发现它的。

在本书以前的版本中,我曾经说过:

这不止令人恼火,它还可能会产生难以发现的缺陷。如果这个程序的某个部分(或数个部分)向集合中插入了对象,并且通过异常,你在程序的另一个独立的部分中发现有不良对象被放置到了集合中,那么必须发现这个不良插入到底是在何处发生的。

但是,随着对这个论点的进一步检查,我开始怀疑它了。首先,这会多么频繁地发生呢?我记得这类事情从未发生在我身上,并且当我在会议上询问其他人时,我也从来没有听说过有人碰上过。另一本书使用了一个称为 files 的 list 示例,它包含 String 对象。在这个示例中,向 files 中添加一个 File 对象看起来相当自然,因此这个对象的名字可能叫 fileNames 更好。

无论 Java 提供了多少类型检查,仍旧可能会写出晦涩的程序,而编写差劲儿的程序即便可以编译,它仍旧是编写差劲儿的程序。可能大多数人都会使用命名良好的集合,例如 cats ,因为它们可以向试图添加非 Cat 对象的程序员提供可视的警告。并且即便这类事情发生了,它真正又能潜伏多久呢?只要你开始用真实数据来运行测试,就会非常快地看到异常。

有一位作者甚至断言,这样的缺陷将“潜伏数年”。但是我不记得有任何大量的相关报告,来说明人们在查找“狗在猫列表中”这类缺陷时困难重重,或者是说明人们会非常频繁地产生这种错误。然而,你将在 多线程编程 章节中看到,在使用线程时,出现那些可能看起来极罕见的缺陷,是很寻常并容易发生的事,而且,对于到底出了什么错,这些缺陷只能给你一个很模糊的概念。因此,对于泛型是添加到 Java 中的非常显著和相当复杂的特性这一点,“狗在猫列表中”这个论据真的能够成为它的理由吗?

我相信被称为_泛型_的通用语言特性(并非必须是其在 Java 中的特定实现)的目的在于可表达性,而不仅仅是为了创建类型安全的集合。类型安全的集合是能够创建更通用代码这一能力所带来的副作用。

因此,即便“狗在猫列表中”这个论据经常被用来证明泛型是必要的,但是它仍旧是有问题的。就像我在本章开头声称的,我不相信这就是泛型这个概念真正的含义。相反,泛型正如其名称所暗示的:它是一种方法,通过它可以编写出更“泛化”的代码,这些代码对于它们能够作用的类型具有更少的限制,因此单个的代码段可以应用到更多的类型上。

正如你在本章中看到的,编写真正泛化的“持有器”类( Java 的容器就是这种类)相当简单,但是编写出能够操作其泛型类型的泛化代码就需要额外的努力了,这些努力需要类创建者和类消费者共同付出,他们必须理解这些代码的概念和实现。这些额外的努力会增加使用这种特性的难度,并可能会因此而使其在某些场合缺乏可应用性,而在这些场合中,它可能会带来附加的价值。

还要注意到,因为泛型是后来添加到 Java 中,而不是从一开始就设计到这种语言中的,所以某些容器无法达到它们应该具备的健壮性。例如,观察一下 Map ,在特定的方法 containsKey(Object key)get(Object key) 中就包含这类情况。如果这些类是使用在它们之前就存在的泛型设计的,那么这些方法将会使用参数化类型而不是 Object ,因此也就可以提供这些泛型假设会提供的编译期检查。例如,在 C++ 的 map 中,键的类型总是在编译期检查的。

有一件事很明显:在一种语言已经被广泛应用之后,在其较新的版本中引入任何种类的泛型机制,都会是一项非常非常棘手的任务,并且是一项不付出艰辛就无法完成的任务。

在 C++ 中,模版是在其最初的 ISO 版本中就引入的(即便如此,也引发了阵痛,因为在第一个标准 C++ 出现之前,有很多非模版版本在使用),因此实际上模版一直都是这种语言的一部分。

在 Java 中,泛型是在这种语言首次发布大约 10 年之后才引入的,因此向泛型迁移的问题特别多,并且对泛型的设计产生了明显的影响。其结果就是,程序员将承受这些痛苦,而这一切都是由于 Java 设计者在设计 1.0 版本时所表现出来的短视造成的。当 Java 最初被创建时,它的设计者们当然了解 C++ 的模版,他们甚至考虑将其囊括到 Java 语言中,但是出于这样或那样的原因,他们决定将模版排除在外(其迹象就是他们过于匆忙)。

因此, Java 语言和使用它的程序员都将承受这些痛苦。只有时间将会说明 Java 的泛型方式对这种语言所造成的最终影响。

某些语言,已经融入了更简洁、影响更小的方式,来实现参数化类型。我们不可能不去想象这样的语言将会成为 Java 的继任者,因为它们采用的方式,与 C++ 通过 C 来实现的方式相同:按原样使用它,然后对其进行改进。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

笔记53:torch.nn.rnn() 函数详解

参数解释&#xff1a; &#xff08;1&#xff09;input_size()&#xff1a;即输入信息 Xt 的每个序列的独热编码向量的长度&#xff0c;即 len(vocab) &#xff08;2&#xff09;hidden_size()&#xff1a;即隐变量 h 的维度&#xff08;维度是多少&#xff0c;就代表用几个数…

北京君正客户应用案例:掌静脉3D人脸猫眼视屏智能锁

凯迪仕在今年4月发布了智能锁旗舰新品K70 Pro Max掌静脉3D人脸猫眼视屏智能锁&#xff0c;随即这款新品也成了行业热议的焦点。凯迪仕每次新品都力求突破精益求精&#xff0c;不仅追求科技感、高级感与品质感&#xff0c;而且赋予科技温度&#xff0c;带来人文化的关怀。K70 Pr…

FPGA——IP核 基础操作

FPGA——IP核 基础操作 IP核例化模块时钟IP核RAM IP核 IP核例化模块 找到模版 加入代码中 时钟IP核 配置模式功能 配置输入时钟 输出配置 RAM IP核

物联网AI MicroPython学习之语法 I2C总线

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; I2C 介绍 模块功能: I2C Master设备驱动 接口说明 I2C - 构建硬件I2C对象 函数原型&#xff1a;I2C(id, scl, sda, freq)参数说明&#xff1a; 参数类型必选参数&#xff1f;说明idintYI2C外设&#xff…

关于新能源汽车的英语翻译

近年来&#xff0c;随着全球对环保和可持续发展的重视&#xff0c;新能源汽车已经成为汽车产业的重要发展方向。各国政府和企业都在加大投入&#xff0c;推动新能源汽车的技术研发和产业化发展&#xff0c;进而促进了新能源汽车翻译的需求不断提升 。那么&#xff0c;关于新能源…

免费的快速手机文件解压APP,快冲

各位小伙伴们大家好&#xff0c;今天我要介绍一款手机上必备的神奇工具&#xff01;你有没有经常遇到需要解压文件情况呢&#xff1f;还在为不知道用哪个软件而烦恼吗&#xff1f;别担心&#xff0c;我给你带来了解决方案 &#xff0c;就是这一款免费的解压精灵。 解压精灵是一…

-pthread和-lpthread

我试图在我的Ubuntu机器上设置GTest环境.但在使GTest获取库时,我收到以下错误... ../obj/gtest.a(gtest-all.o): In function testing::internal::ThreadLocal<std::vector<testing::internal::TraceInfo, std::allocator<testing::internal::TraceInfo> > >…

SQL优化

一、巧用limit分页查询&#xff08;id必须有序&#xff09; 二、like百分号向右 说明&#xff1a;全模糊查询或者左边出现%的模糊查询会导致索引失效&#xff0c;应该尽量从查询方式或表结构设计上避免&#xff0c;若无法避免且数据量庞大的情况下&#xff0c;一定要使用elasti…

openGauss学习笔记-123 openGauss 数据库管理-设置账本数据库-账本数据库概述

文章目录 openGauss学习笔记-123 openGauss 数据库管理-设置账本数据库-账本数据库概述123.1 背景信息123.2 操作步骤 openGauss学习笔记-123 openGauss 数据库管理-设置账本数据库-账本数据库概述 123.1 背景信息 账本数据库融合了区块链思想&#xff0c;将用户操作记录至两…

js的File对象,Blob和file相互转换

示例 <!DOCTYPE html> <html><head><meta charset"utf-8" /><meta name"viewport" content"widthdevice-width, initial-scale1" /><title>js的File对象&#xff0c;Blob和file相互转换</title><…

EnlightenGAN 开源代码运行问题汇总

参考链接&#xff1a; EnlightenGAN 开源代码运行EnlightenGAN的运行环境搭建和训练自己的数据 源码下载和环境配置比较简单&#xff0c;本文测试环境&#xff1a;Win10 RTX3060、cuda 11.3、python 3.8 torch 1.12.0 numpy 1.20.1 如果想修改在项目里创建test_daatset文…

【OS】操作系统课程笔记 第八章 虚拟存储管理

8.1 虚拟存储器 8.1.1 虚拟存储器的定义 1. 虚存定义 通过请求调入功能和置换功能&#xff0c;能从逻辑上对内存容量加以扩充的一种存储器系统。 2. 优势和应用 用户地址空间可以大于物理内存空间&#xff0c;使得内存可以保存数量较多的进程&#xff0c;提高了并发性&…

leetcode刷题日记:125. Valid Palindrome(验证回文串)和136. Single Number(只出现一次的数字)

125.Valid Palindrome&#xff08;验证回文串&#xff09; 验证一个串之前我们需要对字符串进行处理将空格逗号什么的去掉&#xff0c;然后进行比较&#xff0c;比较的顺序如图所示: 在比较途中如果出现比较结果为假&#xff0c;就提前结束比较&#xff0c;此时我们可以判断这…

贪心 455.分发饼干

455.分发饼干 题目&#xff1a; 小朋友胃口值数组g[i]&#xff0c;饼干尺寸数组 s[j]&#xff0c;当饼干尺寸s[j]大于等于g[i]的时候&#xff0c;对应小朋友被满足&#xff0c;小朋友每一个最多一块饼干 &#xff0c;求给定条件下最多被满足的小朋友数量。 思路&#xff1a;…

Homography详解在MVSNet中的应用

Homography&#xff08;单应性变换&#xff09;是计算机视觉中的一个重要概念&#xff0c;用于描述两个平面之间的透视关系。在图像处理和计算机视觉中&#xff0c;Homography通常表示两个平面之间的投影关系&#xff0c;这种关系可以通过一个3x3的矩阵来表示。 在数学上&…

hive数仓-数据的质量管理

版本20231116 要理解数据的质量管理&#xff0c;应具备hive数据仓库的相关知识 文章目录 1.理解什么是数据的质量管理&#xff1a;2.数据质量管理的规划数据质量标准的分类 3.数据质量管理解决方案1.ods层的数据质量校验1&#xff09;首先在hive上建立一个仓库&#xff0c;添加…

实用篇-ES-RestClient查询文档

一、快速入门 上面的查询文档都是依赖kibana&#xff0c;在浏览器页面使用DSL语句去查询es&#xff0c;如何用java去查询es里面的文档(数据)呢 我们通过match_all查询来演示基本的API&#xff0c;注意下面演示的是 match_all查询&#xff0c;也叫基础查询 首先保证你已经做好了…

DTW(Dynamic Time Warping)算法学习应用实践与效率对比分析

DTW&#xff08;Dynamic Time Warping&#xff09;算法是一种用于度量两个时间序列之间的相似性的方法。它的构建原理如下&#xff1a; 基本思想&#xff1a;DTW算法通过计算两个时间序列之间的最小距离&#xff0c;来度量它们的相似性。与欧氏距离等传统距离度量方法不同&…

【文末送书——数学经典著作】工科必备的数学思维培养

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…