初识Java 18-1 泛型

目录

简单泛型

元组库

通过泛型实现栈类

泛型接口

泛型方法

可变参数和泛型方法

通用Supplier

简化元组的使用

使用Set创建实用工具


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


        继承的层次结构有时会带来过多的限制,例如:编写的方法或类往往要依赖于具体的类型。尽管接口突破了单一继承层次结构,但我们可能会想要更加“泛用”的代码,这也是面向对象编程的目的之一。

        为了让代码不再依赖于特定的接口与类,Java 5引入了泛型的概念。

||| 在术语中,“泛型” 是指“适用或者可以兼容大批的”。

        泛型可以生成参数化类型,以此来支持适用于多种类型的组件。当我们创建了某个参数化类型的实例时,类型转换会自动发生,并且在编译期间确保类型的正确性

    然而,Java在正式引入泛型之前,已经有了近十年的历史,这意味着在此期间无数工作者创建并使用的库并不会涉及泛型。为了兼容这些旧的库与程序,Java的设计者不得不在Java的泛型上“走些远路”。因此,Java的泛型可能不会如同其他一些语言来得好用。

简单泛型

        泛型设计的目的之一,就是用于创建集合类。集合比起数组要更加灵活,并且会具备不同的特性(实际上,集合也是复用性最高的库之一)

        假设有一个类,它持有一个简单的对象,具有简单的操作:

【例子:简单的类】

class Automobile {
}

public class Holder1 {
    private Automobile a;

    public Holder1(Automobile a) {
        this.a = a;
    }

    Automobile get() {
        return a;
    }
}

        对于这个类而言,具体的对象限制了对它的复用。如果这个类表示着某个功能模块,我们可能就需要为每一个模块写一份相似的代码。

        在Java 5之前,如果要解决这个问题,我们可以使用Object对象:

【例子:使用Object对象创建通用的类】

public class ObjectHolder {
    private Object a;

    private ObjectHolder(Object a) {
        this.a = a;
    }

    public void set(Object a) {
        this.a = a;
    }

    public Object get() {
        return a;
    }

    public static void main(String[] args) {
        ObjectHolder h2 =
                new ObjectHolder(new Automobile());
        Automobile a = (Automobile) h2.get();

        h2.set("传入不是Automobile的类型(字符串)");
        String s = (String) h2.get();

        h2.set(1); // 发生自动装箱
        Integer x = (Integer) h2.get();
    }
}

        通过这种方式,ObjectHolder类实现了对不同类型对象的持有。但这依旧不够“具体”,尽管通过一个集合持有许多不同的对象在一些时候会有用,但更多时候,我们会将具体类型的对象放入特定的集合中

         泛型的一个目的就是指定集合能够持有的对象类型,并且通过编译器强制执行这一规范:

【例子:使用泛型创建简单的类】

public class GenericHolder<T> {
    private T a;
    public GenericHolder(){
    }

    public void set(T a){
        this.a = a;
    }

    public T get(){
        return a;
    }

    public static void main(String[] args) {
        GenericHolder<Automobile> h3 =
                new GenericHolder<>(); // 钻石语法
        h3.set(new Automobile()); // 在编译时,会检测类型
        Automobile a = h3.get();

        // h3.set("类型不对,不允许输入");
        // h3.set(1);
    }
}

        这里第一次正式提到了泛型语法:

public class GenericHolder<T> {

此处的【T】被称为类型参数,在此处作为一个类型占位符使用。在使用时,【T】会被替换为具体的类型。

        从main()中可以看到,泛型需要在尖括号语法中定义其要存储的类型。通过这种方式,我们可以强制 h3 只存储指定类或其的子类。

    这里也体现了Java中泛型的核心理念:只需告诉泛型所需的类型,剩下的细节由编译器来处理。

        一个好的理解方式是,将泛型视同其他的类型,只是泛型恰好有类型参数而已。

元组库

        有时,我们会希望能够从方法中返回多个对象。一般,我们需要通过一个特殊的类(集合)来实现这一功能。但这种实现往往会受到具体类型的限制。因此,在这里使用泛型是一个不错的选择(同时,我们也可以享受到泛型带来的编译时检查)

        通过泛型打包多个对象,这一概念的实现就是元组(或称数据传输对象、信使)。这种对象有一个限制,它只能读取,不能写入

        元组一般不会设置长度限制,且允许每一个对象是不同的类型。但为了接收方便,我们仍会指定元素的类型:

【例子:一个持有两个对象的元组】

public class Tuple2<A, B> {
    public final A a1;
    public final B b1;

    public Tuple2(A a, B b) {
        a1 = a;
        b1 = b;
    }

    public String rep() {
        return a1 + "," + b1;
    }

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

        类型参数的不同使得元组可以隐式地按序存储数据。

        首先分析一下两个final对象:

public final A a1;
public final B b1;

尽管这两个对象是public的,但final关键字保证了它们不会在初始化后被再次更改。若强行赋值,会看到如下报错:

同时,这种写法也允许外部读取这两个对象。比起使用get()方法而言,这种方法在提供了与private等价的安全性的同时,也更加简洁。

    并不建议对a1和b1进行重新赋值。若需要,那么更好的方法是创建一个新的元组。

        我们也可以在现有元组的基础上创建一个更长的元组。通过继承,可以轻易做到:

【例子:更长的元组】

public class Tuple3<A, B, C> extends Tuple2<A, B> {
    public final C c3;

    public Tuple3(A a, B b, C c) {
        super(a, b);
        c3 = c;
    }

    @Override
    public String rep() {
        return super.rep() + "," + c3;
    }
}

        乃至于更长的元组,这里不展示更多了:

        接下来就可以尝试使用元组了:

【例子:使用元组】

        先定义一些类,用以放入元组中:

public class Amphibian {}
public class Vehicle {}

         然后就是使用元组了:

import onjava.Tuple2;
import onjava.Tuple3;

public class TupleTest {
    static Tuple2<String, Integer> f() {
        // 会发生自动装箱(int -> Integer)
        return new Tuple2<>("Hi", 123);
    }

    static Tuple3<Amphibian, Vehicle, Integer> g() {
        return new Tuple3<>(new Amphibian(), new Vehicle(), 321);
    }

    public static void main(String[] args) {
        // 当接受时,需要设置好对应的元组元素
        Tuple2<String, Integer> t1 = f();
        System.out.println(f());

        System.out.println(g());
    }
}

        程序执行的结果是:

        通过泛型,可以轻松地使方法返回一组对象。


通过泛型实现栈类

        Java中的栈(Stack)主要由两部分组成:泛型类Stack<T>LinkedList<T>。栈的结构较为简单,而通过泛型,我们也可以独立实现自己的栈类:

【例子:实现一个栈类】

public class LinkedStack<T> {
    // 使用内部类实现结点(结点同样是一个泛型):
    private static class Node<U> {
        U item;
        Node<U> next;

        Node() {
            item = null;
            next = null;
        }

        Node(U item, Node<U> next) {
            this.item = item;
            this.next = next;
        }

        boolean end() {
            return item == null &&
                    next == null;
        }
    }

    // 设置空的结点头(也被称为末端哨兵)
    private Node<T> top = new Node<>();

    public void push(T item) {
        // 在创建新结点的同时完成结点之间的链接
        // top指向栈顶元素
        top = new Node<>(item, top);
    }

    public T pop() {
        T result = top.item;
        if (!top.end())
            top = top.next; // top向下移动
        return result;
    }

    public static void main(String[] args) {
        LinkedStack<String> lss = new LinkedStack<>();
        for (String s : "第一 第二 第三".split(" "))
            lss.push(s);
        String s;
        while ((s = lss.pop()) != null)
            System.out.println(s);
    }
}

        程序执行的结果是:

        top哨兵)的存在,使得我们可以在栈上进行移动,并完成各种操作。

泛型接口

        接口的参数同样可以是泛型。java.util.function.Supplier就是一个典型的例子:

实际上,这一接口同时还是Java定义的生成器,其中的生成方法是T get(),能够根据实现生成一个新的对象。

    生成器设计模式来自于工厂方法设计模式,它们都用于创建对象。不同的地方在于,生成器不需要传入参数(即不需要额外信息)来生成对象。

        这是一个创建Supplier<T>的例子:

【例子:实现Supplier<T>

        先设计一个简单的继承结构,首先确定基类Coffee:

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

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

        之后我们需要的只是打印对象,因此子类只需要其名称即可。

        然后是Supplier<T>的实现:

import generics.coffee.*;

import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.Random;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class CoffeeSupplier
        implements Supplier<Coffee>, Iterable<Coffee> {
    private Class<?>[] types = {Latte.class, Mocha.class,
            Cappuccino.class, Americano.class, Breve.class};
    private static Random random = new Random(47);

    public CoffeeSupplier() {
    }

    private int size = 0;

    public CoffeeSupplier(int sz) {
        size = sz;
    }

    @Override
    public Coffee get() {
        try {
            return (Coffee) types[random.nextInt(types.length)]
                    .getConstructor().newInstance();
        } catch (InstantiationException |
                 NoSuchMethodException |
                 InvocationTargetException |
                 IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    class CoffeeIterator implements Iterator<Coffee> {
        int count = size;

        @Override
        public boolean hasNext() {
            return count > 0;
        }

        @Override
        public Coffee next() {
            count--;
            return CoffeeSupplier.this.get();
        }

        @Override
        public void remove() { //该方法未实现,因此返回异常
            throw new UnsupportedOperationException();
        }
    }

    @Override
    public Iterator<Coffee> iterator() {
        return new CoffeeIterator();
    }

    public static void main(String[] args) {
        Stream.generate(new CoffeeSupplier())
                .limit(5)
                .forEach(System.out::println);

        //由于实现了Iterable接口
        // 因此可以将CoffeeSupplier用于for-in语句
        for (Coffee c : new CoffeeSupplier(5))
            System.out.println(c);
    }
}

        程序执行的结果是:

 ---

        再看一个例子,使用Supplier生成斐波那契数列:

【例子:生成斐波那契数列】

import java.util.function.Supplier;
import java.util.stream.Stream;

public class Fibonacci implements Supplier<Integer> {
    private int count = 0;

    @Override
    public Integer get() {
        return fib(count++);
    }

    private int fib(int n) {
        if (n < 2) return 1;
        return fib(n - 2) + fib(n - 1);
    }

    public static void main(String[] args) {
        Stream.generate(new Fibonacci())
                .limit(18)
                .map(n -> n + " ")
                .forEach(System.out::print);
    }
}

        程序执行的结果是:

        这里需要注意的是类型参数:

这里使用的类型参数是Integer,而在类内部我们只使用了int类型。之所以需要使用包装类来规定类型参数,是因为泛型不允许将基本类型作为类型参数(涉及到类型擦除)

【扩展】

        进一步地,若我们想要实现一个可以迭代(Iterable)的斐波那契数列,有两个方法:

  • 一:改装原有的类,并添加Iterable接口。这一方法有一个缺点:我们并不总是能够获得原始代码(或者代码的控制权)。

因此这里选择使用第二个方法:

  • 二:使用适配器生成所需的接口。

【例子:使用继承生成适配器】

import java.util.Iterator;

public class IterableFibonacci extends Fibonacci
        implements Iterable<Integer> {
    private int n;

    public IterableFibonacci(int count) {
        n = count;
    }

    @Override
    public Iterator<Integer> iterator() {
        return new Iterator<Integer>() {
            @Override
            public boolean hasNext() {
                // 需要设置这样的一个n(边界),用于判断是否返回false
                return n > 0;
            }

            @Override
            public Integer next() {
                n--;
                return IterableFibonacci.this.get();
            }

            @Override
            public void remove() {//未实现,返回异常
                throw new UnsupportedOperationException();
            }
        };
    }

    public static void main(String[] args) {
        for (int i : new IterableFibonacci(18))
            System.out.print(i + " ");
    }
}

        程序执行的结果是:

泛型方法

        除了对整个类进行泛型化外,还可以对单个的方法使用泛型化,这就成了泛型方法

        泛型方法的行为会随着类型参数的改变而变化,并且不受类的影响。一般情况下,泛型方法会更加方便,因为相比于整个类,单一方法的泛型化往往更加清晰

    因此,可以“尽量”使用泛型方法

        除此之外,若某个方法是静态的,那么它将无法访问类的泛型类型参数。此时,若方法需要使用到泛型,那么该方法就必须被设置为泛型方法。

        下面的例子展示了定义泛型方法的方式:

【例子:定义泛型方法】

public class GenericMethods {
    // 泛型参数列表(即<T>)需要放在返回值之前
    public <T> void f(T x) {
        System.out.println(x.getClass().getName());
    }

    public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1);
        gm.f(1.0);
        gm.f(1.0F);
        gm.f('c');
        gm.f(gm);
    }
}

        程序执行的结果是:

        就如同例子中的f()方法一样:

必须在返回值前设置<T>表示的类型参数列表。除此之外,泛型方法和泛型类的使用还有一个区别:泛型类在实例化时必须指定类型参数,而泛型方法不用,编译器会处理好一切。这就是类型参数推断


可变参数和泛型方法

        泛型方法也兼容可变参数列表:

【例子:包含可变参数列表的泛型方法】

import java.util.ArrayList;
import java.util.List;

public class GenericVarargs {
    @SafeVarargs
    public static <T> List<T> makeList(T... args) {
        List<T> result = new ArrayList<>();
        for (T item : args)
            result.add(item);
        return result;
    }

    public static void main(String[] args) {
        List<String> ls = makeList("A");
        System.out.println(ls);

        ls = makeList("A", "B", "C");
        System.out.println(ls);

        ls = makeList("ABCDEFGHIJKLMNOPQRSTUVWXYZ"
                .split(""));
        System.out.println(ls);
    }
}

        程序执行的结果是:

        此处的@SafeVarargs注解表示,我们向系统承诺不会对变量参数列表进行任何修改(实际上我们也没有做任何修改)。若没有这个注解,编译器就会产生警告。

(警告会在编译时产生,但依旧可以通过编译并产生.class文件)


通用Supplier

        可以提供泛型创建更通用的Supplier。下面的例子可以为任何一个具有无参构造器的类生成一个Supplier

import java.lang.reflect.InvocationTargetException;
import java.util.function.Supplier;

public class BasicSupplier<T> implements Supplier<T> {
    private Class<T> type;

    public BasicSupplier(Class<T> type) {
        this.type = type;
    }

    @Override
    public T get() {
        try {
            // newInstance()只对public的类有效
            return type.getConstructor().newInstance();
        } catch (InstantiationException |
                 NoSuchMethodException |
                 InvocationTargetException |
                 IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    // 根据类型标记(token)返回一个默认的Supplier
    public static <T> Supplier<T> create(Class<T> type) {
        return new BasicSupplier<>(type);
    }
}

        若一个类符合以下条件,则可以通过上述代码创建其对象的基本实现:

  • 该类是public的。
  • 该类具有无参构造器。

        静态方法create()具有独立的类型参数。通过这个方法,可以方便地创建一个BasicSupplier对象。下面是BasicSupplier类的使用例:

【例子:BasicSupplier的使用例】

        为了展示BasicSupplier的功能,先创建一个简单的类:

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

    public long id() {
        return id;
    }

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

        现在可以通过BasicSupplierCountedObject创建Supplier

import java.util.stream.Stream;

public class BasicSupplierDemo {
    public static void main(String[] args) {
        Stream.generate(
                        BasicSupplier.create(CountedObject.class))
                .limit(5)
                .forEach(System.out::println);
    }
}

        程序执行的结果是:

        这么做有两个好处:

  1. 减少了我们生成Supplier对象所需的代码编写量。
  2. 创建BasicSupplier时,泛型强制要求传入Class对象,也为create()方法提供了类型判断。

简化元组的使用

        通过静态导入(static)和类型参数推断,就可以整合之前创建的元组:

【例子:更通用的元组】

public class Tuple {
    public static <A, B> Tuple2<A, B> tuple(A a, B b) {
        return new Tuple2<>(a, b);
    }

    public static <A, B, C> Tuple3<A, B, C> tuple(A a, B b, C c) {
        return new Tuple3<>(a, b, c);
    }

    public static <A, B, C, D> Tuple4<A, B, C, D>
    tuple(A a, B b, C c, D d) {
        return new Tuple4<>(a, b, c, d);
    }

    public static <A, B, C, D, E> Tuple5<A, B, C, D, E>
    tuple(A a, B b, C c, D d, E e) {
        return new Tuple5<>(a, b, c, d, e);
    }
}

        可以用于之前类似的方式测试这个新类:

【例子:测试新的元组】

import onjava.Tuple2;
import onjava.Tuple3;
import onjava.Tuple4;
import onjava.Tuple5;
// 静态导入Tuple:
import static onjava.Tuple.*;

public class TupleTest2 {
    static Tuple2<String, Integer> f() {
        return tuple("Hello", 47);
    }

    static Tuple2 f2() {
        return tuple("Hello", 47);
    }

    static Tuple3<Amphibian, String, Integer> g() {
        return tuple(new Amphibian(), "Hello", 47);
    }

    static Tuple4<Vehicle, Amphibian, String, Integer> h() {
        return tuple(
                new Vehicle(), new Amphibian(), "Hello", 47);
    }

    static Tuple5<Vehicle, Amphibian,
            String, Integer, Double> k() {
        return tuple(
                new Vehicle(), new Amphibian(),
                "Hello", 47, 11.1);
    }

    public static void main(String[] args) {
        Tuple2<String, Integer> ttsi = f();
        System.out.println(ttsi);
        System.out.println(f2());
        System.out.println(g());
        System.out.println(h());
        System.out.println(k());
    }
}

        程序执行的结果是:

        这里需要注意的是f2(),它返回了一个未参数化的Tuple2对象。尽管如此,但由于我们并未试图获取f2()的结果(并将其放入参数化的Tuple2中),因此编译器没有发出警告。


使用Set创建实用工具

        可以利用Set创建一系列表示数学关系的方法:

import java.util.HashSet;
import java.util.Set;

public class Sets {
    // 合并a、b(取并集)
    public static <T> Set<T> union(Set<T> a, Set<T> b) {
        Set<T> result = new HashSet<>(a);
        result.addAll(b);
        return result;
    }

    // 取a、b中都存在的元素(取交集)
    public static <T>
    Set<T> intersection(Set<T> a, Set<T> b) {
        Set<T> result = new HashSet<>(a);
        result.retainAll(b);
        return result;
    }

    // 从超集中减去子集:
    public static <T> Set<T>
    difference(Set<T> superset, Set<T> subset) {
        Set<T> result = new HashSet<>(superset);
        result.removeAll(subset);
        return result;
    }

    // 获取所有不在交集中的元素
    public static <T> Set<T>
    complement(Set<T> a, Set<T> b) {
        return difference(union(a, b), intersection(a, b));
    }
}

        通过将数据复制到一个新的HashSet中,我们可以保证进行的修改不会影响原本的数据。

        接下来就可以使用这些Set工具了。为此,我们还需要先创建一些用于存储的枚举:

【例子:创建枚举】

package generics.watercolors;

public enum Watercolors {
    RED, GREEN, BLUE, YELLOW, ORANGE,
    PURPLE, CYAN, MAGENTA, WHITE, BLACK
}

        接下来就可以使用这个枚举类型展示Set工具的用法了:

【例子:使用创建的Set工具】

import generics.watercolors.Watercolors;

import static generics.watercolors.Watercolors.*;

import java.util.EnumSet;

import java.util.Set;

import static onjava.Sets.*;

public class WatercolorSets {
    public static void main(String[] args) {
        Set<Watercolors> set1 =
                EnumSet.range(RED, MAGENTA);
        Set<Watercolors> set2 =
                EnumSet.range(BLUE, BLACK);

        System.out.println("set1: " + set1);
        System.out.println("set2: " + set2);

        System.out.println("union(set1, set2): " +
                union(set1, set2));

        Set<Watercolors> subset = intersection(set1, set2);
        System.out.println("intersection(set1, set2): " +
                subset);

        System.out.println("difference(set1, subset): " +
                difference(set1, subset));
        System.out.println("difference(set2, subset): " +
                difference(set2, subset));
        System.out.println("complement(set1, set2): " +
                complement(set1, set2));
    }
}

        程序执行的结果是:

        还可以用这些工具比较不同Collection之间的区别:

【例子:不同Collection之间的区别】

import onjava.Sets;

import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;

public class CollectionMethodDifferences {
    static Set<String> methodSet(Class<?> type) {
        return Arrays.stream(type.getMethods())
                .map(Method::getName)
                .collect(Collectors.toCollection(TreeSet::new));
    }

    static void interfaces(Class<?> type) {
        System.out.print("【" + type.getSimpleName() +
                "】继承了的接口:");
        System.out.println(Arrays.stream(type.getInterfaces())
                .map(Class::getSimpleName)
                .collect(Collectors.toList()));
    }

    static Set<String> object =
            methodSet(Object.class);

    static {
        object.add("加载Object的方法");
    }

    static void
    difference(Class<?> superset, Class<?> subset) {
        System.out.print("【" + superset.getSimpleName() +
                "】继承自【" + subset.getSimpleName() +
                "】,并添加了这些方法:");
        Set<String> comp = Sets.difference(methodSet(superset),
                methodSet(subset));
        comp.removeAll(object); // 忽略所有Object类中的方法
        System.out.format(comp + "%n");
        interfaces(superset);
    }

    // 方便打印
    static void
    printDifference(Class<?> superset, Class<?> subset) {
        System.out.println();
        difference(superset, subset);
    }

    public static void main(String[] args) {
        System.out.println("【Collection】中的方法有:" +
                methodSet(Collection.class));
        interfaces(Collection.class);

        printDifference(Set.class, Collection.class);
        printDifference(HashSet.class, Set.class);
        printDifference(LinkedHashSet.class, HashSet.class);
    }
}

        程序执行的结果是:

(也可以用于查看Map之间的区别。因为集合类较多,这里不一一展示。)

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

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

相关文章

前端学习笔记--React

1. 什么是React? React 是一个用于构建用户界面的JavaScript库核心专注于视图,目的实现组件化开发我们可以很直观的将一个复杂的页面分割成若干个独立组件,每个组件包含自己的逻辑和样式 再将这些独立组件组合完成一个复杂的页面。 这样既减少了逻辑复杂度&#xff0c;又实现…

12-1- GAN -简单网络-线性网络

功能 随机噪声→生成器→MINIST图像。 训练方法 1 判别器的训练,首先固定生成器参数不变,其次判别器应当将真实图像判别为1,生成图像判别为0 loss=loss(real_out, 1)+loss(fake_out, 0) 2 生成器的训练,首先固定判别器参数不变,其次判别器应当将生成图像判别为1 loss…

你应该知道关于Python的这几个技巧!

随着大数据时代的到来&#xff0c;我们每天都在接触爬虫相关的事情&#xff0c;这其中就不得不提及Python这门编程语言。我已经使用Python编程有多年了&#xff0c;即使今天我仍然惊奇于这种语言所能让代码表现出的整洁和对DRY编程原则的适用。这些年来的经历让我学到了很多的小…

MySQL覆盖索引的含义

覆盖索引&#xff1a;SQL只需要通过索引就可以返回查询所需要的数据&#xff0c;而不必通过二级索引查到主键之后再去查询数据&#xff0c;因为查询主键索引的 B 树的成本会比查询二级索引的 B 的成本大。 也就是说我select的列就是我的索引列&#xff08;或者主键&#xff0c;…

XD6500S— LoRa SIP模块

XD6500S是一系列LoRa SIP模块&#xff0c;集成了射频前端和LoRa射频收发器SX1262系列&#xff0c;支持LoRa和FSK调制。收发器SX1262系列&#xff0c;支持LoRa和FSK调制。LoRa技术是一种扩频协议&#xff0c;针对LPWAN 应用的低数据速率、超远距离和超低功耗通信进行了优化。通信…

KMP算法详讲(问题导向,通俗易懂)

KMP算法是一种高效的字符串匹配算法&#xff0c;相比于BF算法的时间复杂度为O(n*m)&#xff0c;它的时间复杂度降低到了O(nm)。这种算法的高效性在于它利用了主串的指针不回溯&#xff0c;而只移动模式串的指针位置。然而&#xff0c;对于初学者来说&#xff0c;KMP算法并不容易…

全面掌握:性能测试计划的制胜法宝

一&#xff0e;简介 简介部分就不用过多描述了&#xff0c;无非项目的背景&#xff0c;进行此次性能测试的原因&#xff0c;以及性能测试覆盖的范围等等&#xff0c;几乎所有项目文档都在开端对项目进行简单的阐述。 二&#xff0e;性能测试需求 寻找的被测试对象和压力点 …

windows 部署 weblogic 12.1.3

1、安装 1&#xff09;下载 地址&#xff1a;WebLogic Server 12c (12.2.1), WebLogic Server 11g (10.3.6) and Previous Releases 2&#xff09;安装 weblogic server java -Xmx1024m -jar fmw_12.1.3.0.0_wls.jar 出现图形界面按需配置&#xff0c;注意配置的安装路径不能…

11月编程榜最新出炉,第一名很离谱

这段时间&#xff0c;随着人工智能的崛起&#xff0c;Python的地位水涨船高。有不少朋友感觉到危机重重。 其中&#xff0c;最明显的&#xff0c;是市场环境的变化&#xff1a; 外部招聘&#xff1a;Python岗位日均需求量高达15000&#xff01;不仅是程序员&#xff0c;内容编…

【分享课】11月16日晚19:30PostgreSQL分享课:PG缓存管理器主题

PostsreSQL分享课分享主题: PG缓存管理器主题 直播分享平台&#xff1a;云贝教育视频号 时间&#xff1a;11月16日 周四晚 19: 30 分享内容: 缓冲区管理器结构 缓冲区管理器的工作原理 环形缓冲区 脏页的刷新

uniapp使用Canvas实现电子签名

来源&#xff1a; 公司的一个需求&#xff0c;需要给新注册的会员和客商需要增加签署协议功能&#xff1b; 之前的思路&#xff1a; 1、使用vue-signature-pad来实现电子签名&#xff0c;但是安卓手机不兼容&#xff1b; 2、uniapp插件市场来实现&#xff0c;但是对HBuilderX…

为什么小型企业应该拥抱数字化转型?

在当今飞速发展的商业环境中&#xff0c;数字化转型已经成为各种规模组织的必然选择。特别是小型企业&#xff0c;通过数字化转型&#xff0c;可以在保持竞争力、提高运营效率并开启新的增长机会方面获益匪浅。本文探讨了数字化转型的概念&#xff0c;强调了它对小型企业的重要…

测试小白必看:自动化测试入门基础知识

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

小型企业网络搭建方案

在这个日益数字化和连接的世界里&#xff0c;一个稳固的小型企业网络是实现高效运作的关键支柱。不论您是在经营一家初创公司还是小型企业&#xff0c;一个可靠的企业网络都是保证顺畅沟通、数据分享以及访问在线资源的重要因素。本篇文章将会引导您完成构建一个小型企业网络的…

C++入门第七篇--STL模板--vector模拟实现

前言&#xff1a; 有了前面的string库的介绍&#xff0c;在这里我就不再介绍vector库了&#xff0c;而是直接模拟实现了。 vector库的概念和作用&#xff1a; vector库是针对于数组的数据类型的容器&#xff0c;它有点类似我们曾经实现过的顺序表&#xff0c;你完全可以按照…

Google codelab WebGPU入门教程源码<6> - 使用计算着色器实现计算元胞自动机之生命游戏模拟过程(源码)

对应的教程文章: https://codelabs.developers.google.com/your-first-webgpu-app?hlzh-cn#7 对应的源码执行效果: 对应的教程源码: 此处源码和教程本身提供的部分代码可能存在一点差异。点击画面&#xff0c;切换效果。 class Color4 {r: number;g: number;b: number;a…

挑战字节软件测试岗,原来这么轻松...

当前就业环境&#xff0c;裁员、失业消息满天飞&#xff0c;好像有一份工作就不错了&#xff0c;更别说高薪了。其实这只是一方面&#xff0c;而另一方面&#xff0c;各大企业依然求贤若渴&#xff0c;高技术人才依然紧缺&#xff0c;只要你技术过硬&#xff0c;拿个年薪50w不是…

锁之间的故事

目录 常用锁策略 1.乐观锁 VS 悲观锁 2.轻量级锁 VS 重量级锁 3.自旋锁 VS 挂起等待锁 4.互斥锁 VS 读写锁 5.公平锁 VS 非公平锁 6.可重入锁 VS 可重入锁 CAS ABA问题 Synchronized原理 1. 锁升级/锁膨胀 2.锁消除 3.锁粗化 常用锁策略 1.乐观锁 VS 悲观锁 站在…

二叉树相关

一、概念 二、题目 2.1 把数组转换成二叉树 2.2.1 使用队列方式 public static Node getTreeFromArr2(int[] arr) {if (arr null || arr.length 0) {return null;}LinkedList<Node> quque new LinkedList<>();Node root new Node(arr[0]);quque.add(root);in…

有大量虾皮买家号想防关联该怎么做?

Shopee平台规定一个买家只能拥有一个买家号&#xff0c;如果一台电脑或者一个手机同时登录好几个买家号&#xff0c;那么很有可能就会关联封号的。那么有大量虾皮买家号想防关联该怎么做&#xff1f; 如果想要运用大量的shopee买家号来操作&#xff0c;那么需要使用有防指纹技术…