二十、泛型(7)

news2024/11/24 18:40:14

本章概要

  • 动态类型安全
  • 泛型异常
  • 混型
    • C++ 中的混型
    • 与接口混合
    • 使用装饰器模式
    • 与动态代理混合

动态类型安全

因为可以向 Java 5 之前的代码传递泛型集合,所以旧式代码仍旧有可能会破坏你的集合。Java 5 的 java.util.Collections 中有一组便利工具,可以解决在这种情况下的类型检查问题,它们是:静态方法 checkedCollection()checkedList()checkedMap()checkedSet()checkedSortedMap()checkedSortedSet()。这些方法每一个都会将你希望动态检查的集合当作第一个参数接受,并将你希望强制要求的类型作为第二个参数接受。

受检查的集合在你试图插入类型不正确的对象时抛出 ClassCastException ,这与泛型之前的(原生)集合形成了对比,对于后者来说,当你将对象从集合中取出时,才会通知你出现了问题。在后一种情况中,你知道存在问题,但是不知道罪魁祸首在哪里,如果使用受检查的集合,就可以发现谁在试图插入不良对象。

让我们用受检查的集合来看看“将猫插入到狗列表中”这个问题。这里,oldStyleMethod() 表示遗留代码,因为它接受的是原生的 List ,而 @SuppressWarnings(“unchecked”) 注解对于压制所产生的警告是必需的:

CheckedList.java

import java.util.*;

public class CheckedList {
    @SuppressWarnings("unchecked")
    static void oldStyleMethod(List probablyDogs) {
        probablyDogs.add(new Cat());
    }

    public static void main(String[] args) {
        List<Dog> dogs1 = new ArrayList<>();
        oldStyleMethod(dogs1); // Quietly accepts a Cat
        List<Dog> dogs2 = Collections.checkedList(new ArrayList<>(), Dog.class);
        try {
            oldStyleMethod(dogs2); // Throws an exception
        } catch (Exception e) {
            System.out.println("Expected: " + e);
        }
        // Derived types work fine:
        List<Pet> pets = Collections.checkedList(new ArrayList<>(), Pet.class);
        pets.add(new Dog());
        pets.add(new Cat());
    }
}

在这里插入图片描述

其它相关类

Cat.java

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

    public Cat() {
        super();
    }
}

Dog.java

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

    public Dog() {
        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));
    }
}

Pet.java

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

    public Pet() {
        super();
    }
}

运行这个程序时,你会发现插入一个 Cat 对于 dogs1 来说没有任何问题,而 dogs2 立即会在这个错误类型的插入操作上抛出一个异常。还可以看到,将导出类型的对象放置到将要检查基类型的受检查容器中是没有问题的。

泛型异常

由于擦除的原因,catch 语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自 Throwable(这将进一步阻止你去定义不能捕获的泛型异常)。
但是,类型参数可能会在一个方法的 throws 子句中用到。这使得你可以编写随检查型异常类型变化的泛型代码:

import java.util.*;

interface Processor<T, E extends Exception> {
    void process(List<T> resultCollector) throws E;
}

class ProcessRunner<T, E extends Exception> extends ArrayList<Processor<T, E>> {
    List<T> processAll() throws E {
        List<T> resultCollector = new ArrayList<>();
        for (Processor<T, E> processor : this) {
            processor.process(resultCollector);
        }
        return resultCollector;
    }
}

class Failure1 extends Exception {
}

class Processor1
        implements Processor<String, Failure1> {
    static int count = 3;

    @Override
    public void process(List<String> resultCollector)
            throws Failure1 {
        if (count-- > 1) {
            resultCollector.add("Hep!");
        } else {
            resultCollector.add("Ho!");
        }
        if (count < 0) {
            throw new Failure1();
        }
    }
}

class Failure2 extends Exception {
}

class Processor2
        implements Processor<Integer, Failure2> {
    static int count = 2;

    @Override
    public void process(List<Integer> resultCollector)
            throws Failure2 {
        if (count-- == 0) {
            resultCollector.add(47);
        } else {
            resultCollector.add(11);
        }
        if (count < 0) {
            throw new Failure2();
        }
    }
}

public class ThrowGenericException {
    public static void main(String[] args) {
        ProcessRunner<String, Failure1> runner = new ProcessRunner<>();
        for (int i = 0; i < 3; i++) {
            runner.add(new Processor1());
        }
        try {
            System.out.println(runner.processAll());
        } catch (Failure1 e) {
            System.out.println(e);
        }

        ProcessRunner<Integer, Failure2> runner2 =
                new ProcessRunner<>();
        for (int i = 0; i < 3; i++) {
            runner2.add(new Processor2());
        }
        try {
            System.out.println(runner2.processAll());
        } catch (Failure2 e) {
            System.out.println(e);
        }
    }
}

在这里插入图片描述

Processor 执行 process() 方法,并且可能会抛出具有类型 E 的异常。process() 的结果存储在 List<T>resultCollector 中(这被称为_收集参数_)。ProcessRunner 有一个 processAll() 方法,它会在所持有的每个 Process 对象执行,并返回 resultCollector

如果不能参数化所抛出的异常,那么由于检查型异常的缘故,将不能编写出这种泛化的代码。

混型

术语_混型_随时间的推移好像拥有了无数的含义,但是其最基本的概念是混合多个类的能力,以产生一个可以表示混型中所有类型的类。这往往是你最后的手段,它将使组装多个类变得简单易行。

混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。如果想在混型类中修改某些东西,作为一种意外的好处,这些修改将会应用于混型所应用的所有类型之上。正由于此,混型有一点_面向切面编程_ (AOP) 的味道,而切面经常被建议用来解决混型问题。

C++ 中的混型

在 C++ 中,使用多重继承的最大理由,就是为了使用混型。但是,对于混型来说,更有趣、更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在 C++ 中,可以很容易地创建混型,因为 C++ 能够记住其模版参数的类型。

下面是一个 C++ 示例,它有两个混型类型:一个使得你可以在每个对象中混入拥有一个时间戳这样的属性,而另一个可以混入一个序列号。

// generics/Mixins.cpp

#include <string>
#include <ctime>
#include <iostream>
using namespace std;

template<class T> class TimeStamped : public T {
    long timeStamp;
public:
    TimeStamped() { timeStamp = time(0); }
    long getStamp() { return timeStamp; }
};

template<class T> class SerialNumbered : public T {
    long serialNumber;
    static long counter;
public:
    SerialNumbered() { serialNumber = counter++; }
    long getSerialNumber() { return serialNumber; }
};

// Define and initialize the static storage:
template<class T> long SerialNumbered<T>::counter = 1;

class Basic {
    string value;
public:
    void set(string val) { value = val; }
    string get() { return value; }
};

int main() {
    TimeStamped<SerialNumbered<Basic>> mixin1, mixin2;
    mixin1.set("test string 1");
    mixin2.set("test string 2");
    cout << mixin1.get() << " " << mixin1.getStamp() <<
      " " << mixin1.getSerialNumber() << endl;
    cout << mixin2.get() << " " << mixin2.getStamp() <<
      " " << mixin2.getSerialNumber() << endl;
}
/* Output:
test string 1 1452987605 1
test string 2 1452987605 2
*/

main() 中, mixin1mixin2 所产生的类型拥有所混入类型的所有方法。可以将混型看作是一种功能,它可以将现有类映射到新的子类上。注意,使用这种技术来创建一个混型是多么的轻而易举。基本上,只需要声明“这就是我想要的”,紧跟着它就发生了:

TimeStamped<SerialNumbered<Basic>> mixin1,mixin2;

遗憾的是,Java 泛型不允许这样。擦除会忘记基类类型,因此

泛型类不能直接继承自一个泛型参数

这突显了许多我在 Java 语言设计决策(以及与这些功能一起发布)中遇到的一大问题:处理一件事很有希望,但是当您实际尝试做一些有趣的事情时,您会发现自己做不到。

与接口混合

一种更常见的推荐解决方案是使用接口来产生混型效果,就像下面这样:

import java.util.*;

interface TimeStamped {
    long getStamp();
}

class TimeStampedImp implements TimeStamped {
    private final long timeStamp;

    TimeStampedImp() {
        timeStamp = new Date().getTime();
    }

    @Override
    public long getStamp() {
        return timeStamp;
    }
}

interface SerialNumbered {
    long getSerialNumber();
}

class SerialNumberedImp implements SerialNumbered {
    private static long counter = 1;
    private final long serialNumber = counter++;

    @Override
    public long getSerialNumber() {
        return serialNumber;
    }
}

interface Basic {
    void set(String val);

    String get();
}

class BasicImp implements Basic {
    private String value;

    @Override
    public void set(String val) {
        value = val;
    }

    @Override
    public String get() {
        return value;
    }
}

class Mixin extends BasicImp
        implements TimeStamped, SerialNumbered {
    private TimeStamped timeStamp = new TimeStampedImp();
    private SerialNumbered serialNumber = new SerialNumberedImp();

    @Override
    public long getStamp() {
        return timeStamp.getStamp();
    }

    @Override
    public long getSerialNumber() {
        return serialNumber.getSerialNumber();
    }
}

public class Mixins {
    public static void main(String[] args) {
        Mixin mixin1 = new Mixin(), mixin2 = new Mixin();
        mixin1.set("test string 1");
        mixin2.set("test string 2");
        System.out.println(mixin1.get() + " " +
                mixin1.getStamp() + " " + mixin1.getSerialNumber());
        System.out.println(mixin2.get() + " " +
                mixin2.getStamp() + " " + mixin2.getSerialNumber());
    }
}

在这里插入图片描述

Mixin 类基本上是在使用_委托_,因此每个混入类型都要求在 Mixin 中有一个相应的域,而你必须在 Mixin 中编写所有必需的方法,将方法调用转发给恰当的对象。这个示例使用了非常简单的类,但是当使用更复杂的混型时,代码数量会急速增加。

使用装饰器模式

当你观察混型的使用方式时,就会发现混型概念好像与_装饰器_设计模式关系很近。装饰器经常用于满足各种可能的组合,而直接子类化会产生过多的类,因此是不实际的。

装饰器模式使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。这使得对装饰器的使用是透明的——无论对象是否被装饰,你都拥有一个可以向对象发送的公共消息集。装饰类也可以添加新方法,但是正如你所见,这将是受限的。

装饰器是通过使用组合和形式化结构(可装饰物/装饰器层次结构)来实现的,而混型是基于继承的。因此可以将基于参数化类型的混型当作是一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构。

前面的示例可以被改写为使用装饰器:

import java.util.*;

class Basic {
    private String value;

    public void set(String val) {
        value = val;
    }

    public String get() {
        return value;
    }
}

class Decorator extends Basic {
    protected Basic basic;

    Decorator(Basic basic) {
        this.basic = basic;
    }

    @Override
    public void set(String val) {
        basic.set(val);
    }

    @Override
    public String get() {
        return basic.get();
    }
}

class TimeStamped extends Decorator {
    private final long timeStamp;

    TimeStamped(Basic basic) {
        super(basic);
        timeStamp = new Date().getTime();
    }

    public long getStamp() {
        return timeStamp;
    }
}

class SerialNumbered extends Decorator {
    private static long counter = 1;
    private final long serialNumber = counter++;

    SerialNumbered(Basic basic) {
        super(basic);
    }

    public long getSerialNumber() {
        return serialNumber;
    }
}

public class Decoration {
    public static void main(String[] args) {
        TimeStamped t = new TimeStamped(new Basic());
        TimeStamped t2 = new TimeStamped(new SerialNumbered(new Basic()));
        //- t2.getSerialNumber(); // Not available
        SerialNumbered s = new SerialNumbered(new Basic());
        SerialNumbered s2 = new SerialNumbered(new TimeStamped(new Basic()));
        //- s2.getStamp(); // Not available
    }
}

产生自泛型的类包含所有感兴趣的方法,但是由使用装饰器所产生的对象类型是最后被装饰的类型。也就是说,尽管可以添加多个层,但是最后一层才是实际的类型,因此只有最后一层的方法是可视的,而混型的类型是所有被混合到一起的类型。

因此对于装饰器来说,其明显的缺陷是它只能有效地工作于装饰中的一层(最后一层),而混型方法显然会更自然一些。因此,装饰器只是对由混型提出的问题的一种局限的解决方案。

与动态代理混合

可以使用动态代理来创建一种比装饰器更贴近混型模型的机制(查看 类型信息 一章中关于 Java 的动态代理如何工作的解释)。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。

由于动态代理的限制,每个被混入的类都必须是某个接口的实现:

DynamicProxyMixin.java

import java.lang.reflect.*;
import java.util.*;

import static com.example.test.Tuple.tuple;

class MixinProxy implements InvocationHandler {
    Map<String, Object> delegatesByMethod;

    @SuppressWarnings("unchecked")
    MixinProxy(Tuple2<Object, Class<?>>... pairs) {
        delegatesByMethod = new HashMap<>();
        for (Tuple2<Object, Class<?>> pair : pairs) {
            for (Method method : pair.a2.getMethods()) {
                String methodName = method.getName();
                // The first interface in the map
                // implements the method.
                if (!delegatesByMethod.containsKey(methodName)) {
                    delegatesByMethod.put(methodName, pair.a1);
                }
            }
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        Object delegate = delegatesByMethod.get(methodName);
        return method.invoke(delegate, args);
    }

    @SuppressWarnings("unchecked")
    public static Object newInstance(Tuple2... pairs) {
        Class[] interfaces = new Class[pairs.length];
        for (int i = 0; i < pairs.length; i++) {
            interfaces[i] = (Class) pairs[i].a2;
        }
        ClassLoader cl = pairs[0].a1.getClass().getClassLoader();
        return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(pairs));
    }
}

public class DynamicProxyMixin {
    public static void main(String[] args) {
        Object mixin = MixinProxy.newInstance(
                tuple(new BasicImp(), Basic.class),
                tuple(new TimeStampedImp(), TimeStamped.class),
                tuple(new SerialNumberedImp(), SerialNumbered.class));
        Basic b = (Basic) mixin;
        TimeStamped t = (TimeStamped) mixin;
        SerialNumbered s = (SerialNumbered) mixin;
        b.set("Hello");
        System.out.println(b.get());
        System.out.println(t.getStamp());
        System.out.println(s.getSerialNumber());
    }
}

在这里插入图片描述

Tuple.java

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);
    }
}

Tuple2.java

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

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

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

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

Tuple3.java

public class Tuple3<A, B, C> extends Tuple2<A, B> {
  public final C a3;
  public Tuple3(A a, B b, C c) {
    super(a, b);
    a3 = c;
  }
  @Override public String rep() {
    return super.rep() + ", " + a3;
  }
}

Tuple4.java

public class Tuple4<A, B, C, D> extends Tuple3<A, B, C> {
    public final D a4;

    public Tuple4(A a, B b, C c, D d) {
        super(a, b, c);
        a4 = d;
    }

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

Tuple5.java

public class Tuple5<A, B, C, D, E> extends Tuple4<A, B, C, D> {
    public final E a5;

    public Tuple5(A a, B b, C c, D d, E e) {
        super(a, b, c, d);
        a5 = e;
    }

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

因为只有动态类型而不是静态类型才包含所有的混入类型,因此这仍旧不如 C++ 的方式好,因为可以在具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显地更接近于真正的混型。

为了让 Java 支持混型,人们已经做了大量的工作朝着这个目标努力,包括创建了至少一种附加语言( Jam 语言),它是专门用来支持混型的。

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

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

相关文章

ubuntu中使用 vscode 连接docker开发环境

文章目录 ubuntu中使用 vscode 连接docker开发环境步骤一&#xff1a;安装 Remote Development 插件步骤二&#xff1a;连接远程环境步骤三&#xff1a;开发 问题解决参考连接 ubuntu中使用 vscode 连接docker开发环境 Remote Development 是一个 Visual Studio Code 插件&…

【python 生成器 面试必备】yield关键字,协程必知必会系列文章--自己控制程序调度,体验做上帝的感觉 1

python生成器系列文章目录 第一章 yield — Python (Part I) 文章目录 python生成器系列文章目录前言1. Generator Function 生成器函数2.并发和并行&#xff0c;抢占式和协作式2.Let’s implement Producer/Consumer pattern using subroutine: 生成器的状态 generator’s st…

【EI会议征稿】第四届环境资源与能源工程国际学术会议(ICEREE 2024)

第四届环境资源与能源工程国际学术会议&#xff08;ICEREE 2024&#xff09; 2024 4th International Conference on Environment Resources and Energy Engineering ICEREE&#xff08;2020-2023&#xff09;大会先后在珠海&#xff0c;昆明&#xff0c;长沙成功召开。会议主…

自定义Matplotlib中的颜色映射(cmap)

要自定义Matplotlib中的颜色映射&#xff08;cmap&#xff09;&#xff0c;您可以按照以下步骤进行操作&#xff1a; 导入所需的库&#xff1a; import numpy as np import matplotlib.pyplot as plt from matplotlib.colors import LinearSegmentedColormap创建自定义颜色映…

ViewPager2和TabLayout协同使用,实现多Fragment页面切换类似于QQ音乐,bilibili效果

一、ViewPager2的基本用法 使用前先添加依赖&#xff1a; implementation androidx.appcompat:appcompat:1.4.0 // AndroidX AppCompatimplementation com.google.android.material:material:1.4.0 // Material Design Components1、制作Fragment 首先制作一个Fragment的xml布…

欧拉回路和欧拉路径

目录 欧拉回路基础 欧拉回路的定义 欧拉回路的性质 判断图中是否存在欧拉回路的java代码实现 寻找欧拉回路的三个算法 Hierholzer算法 详细思路 代码实现 欧拉路径 欧拉路径的定义 欧拉路径的性质 欧拉回路基础 欧拉回路的定义 欧拉回路遍历了所有的边&#xff0c;…

spring cloud 简介

springcloud 定义 1.定义&#xff1a;springcloud为开发人员提供了在分布式系统中快速构建一些通用模式的工具&#xff08;例如配置管理、服务发现、断路器、路由、控制总线等&#xff09;2.微服务:基于单体应用&#xff0c;基于业务进行拆分&#xff0c;每个服务都是独立应用…

栈的实现---超详细

栈的实现 文章目录 栈的实现一、栈的模型二、栈的代码实现以及测试用例①栈的初始化②入栈③出栈④弹出栈顶⑤判断栈空间是否为空⑥计算栈空间长度⑦销毁栈⑧测试用例 一、栈的模型 首先栈有两个概念 1.数据结构里的栈。2.语言/操作系统中的栈(内存空间)&#xff0c;可能会在递…

xshell连接云服务器(保姆级教程)

文章目录 1. 前言2. 查看云服务器的信息3. xshell7连接云服务器 1. 前言 云服务器&#xff0c;也被称为Elastic Compute Service (ECS)&#xff0c;是一种简单高效、安全可靠、处理能力可弹性伸缩的计算服务。它源于物理服务器集群资源池&#xff0c;可以像从大海中取水一样&am…

从流程优化到经营提效,法大大电子签全面助力智慧零售升级

在新零售模式下&#xff0c;“商业综合体、百货商场、连锁商超、连锁便利店、线上电商平台”等各类商业零售企业借助数字化的手段来改造和重塑传统零售流程和逻辑&#xff0c;实现全面数字化转型&#xff0c;包括线上线下一体化、全场景覆盖、全链条联通、全渠道经营、客户服务…

大数据安全 | 【实验】RSA加密解密

文章目录 &#x1f4da;关于RSA&#x1f4da;实验目的&#x1f4da;流程梳理&#x1f407;Step1&#xff1a;求解乘法逆元&#x1f407;Step2&#xff1a;生成密钥&#x1f407;Step3&#xff1a;加密解密&#x1f407;Step4&#xff1a;最后数据导入 &#x1f4da;实验结果 &a…

github使用手册

核心代码 配置用户名/邮箱 best practice git init #在本地初始化一个仓库 git add . #将当前目录所有的文件加入&#xff08;注意这里是加入&#xff09;到缓存区 git commit -m "xxx" #将当前缓存区里的内容提交到本地仓库 git remote add <remote_rep_name&g…

开启学习新时代,电大搜题助您实现梦想!

亲爱的读者朋友们&#xff0c;当您拥有追求知识的渴望&#xff0c;秉持着对成功的执着追求时&#xff0c;浙江开放大学&#xff08;广播电视大学&#xff09;诞生了——它向您开启了一扇通向知识殿堂的大门。而今&#xff0c;我们荣幸地向您推荐一款既简便又高效的学习利器——…

Android设计模式--原型模式

一&#xff0c;定义 原型模式就是用原型实例指定创建对象的种类&#xff0c;并通过拷贝这些原型创建新的对象 也就是说用户从一个实例中复制出一个内部属性一致的对象&#xff0c;这个被复制的对象就是原型。 原型模式多用于创建复杂的或者构造耗时的实例&#xff0c;因为这…

YOLO目标检测——苹果数据集下载分享【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;监测果园中苹果的生长情况、水果品质监控、自动化分拣数据集说明&#xff1a;苹果检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富标签说明&#xff1a;使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(…

淘宝京东优惠券信息API接口系列

获取淘宝优惠券信息接口需要使用淘宝开放平台提供的API接口。以下是获取优惠券信息的步骤&#xff1a; 进入淘宝开放平台&#xff0c;注册并登录账号。在开放平台页面中&#xff0c;找到“优惠券”或“营销工具”等相关的API接口&#xff0c;根据需要进行选择。根据接口文档&a…

计算机组成原理:大而快——层次化存储

原文链接www.xiaocr.fun/index.php/2023/11/14/计算机组成原理大而快-层次化存储/ 引言 关于两种局部性 时间局部性&#xff1a;如果某个数据被访问&#xff0c;那么在不久的将来它可能再次被访问空间局部性&#xff1a;如果某个数据项被访问&#xff0c;与它相邻的数据项可…

接口自动化测试(Python+Requests+Unittest)合集详解教程

&#xff08;1&#xff09;接口自动化测试的优缺点 优点&#xff1a; 测试复用性。维护成本相对UI自动化低一些。回归方便。可以运行更多更繁琐的测试。 自动化的一个明显的好处是可以在较少的时间内运行更多的测试。为什么UI自动化维护成本更高&#xff1f; 因为前端页面变…

工作记录-------MySql主从同步

MySql主从同步简述&#xff1a; MySQL主从同步&#xff0c;可以实现将数据从一台数据库服务器同步到多台数据库服务器。MySQL数据库自带主从同步功能&#xff0c;经过配置&#xff0c;可以实现基于库、表结构的多种方案的主从同步。 Redis是一种高性能的内存数据库&#xff1…

【Hello Go】初识Go语言

初识Go语言 Go语言介绍Go语言是什么Go语言优势Go语言能用来做什么 Go语言环境安装第一个GO语言程序运行Go语言程序 Go语言介绍 Go语言是什么 go语言是是Google开发的一种静态强类型、编译型、并发型&#xff0c;并具有垃圾回收功能的编程语言. 静态类型&#xff1a;在静态类型…