当前位置: 首页 > news >正文

读书笔记-《ON JAVA 中文版》-摘要23[第二十章 泛型-2]

文章目录

  • 第二十章 泛型
    • 5. 泛型擦除
      • 5.1 泛型擦除
      • 5.2 迁移兼容性
      • 5.3 擦除的问题
      • 5.4 边界处的动作
    • 6. 补偿擦除
    • 7. 边界
    • 8. 通配符
      • 8.1 通配符
      • 8.2 逆变
    • 9. 问题
    • 10. 动态类型安全
    • 11. 泛型异常

第二十章 泛型

普通的类和方法只能使用特定的类型:基本数据类型或类类型。如果编写的代码需要应用于多种类型,这种严苛的限制对代码的束缚就会很大。

多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。

拘泥于单一的继承体系太过局限,如果方法以接口而不是类作为参数,限制就宽松多了,只要实现了接口就可以。

即便是接口也还是有诸多限制。一旦指定了接口,它就要求你的代码必须使用特定的接口。而我们希望编写更通用的代码,能够适用“非特定的类型”,而不是一个具体的接口或类。

这就是泛型的概念,是 Java 5 的重大变化之一。在很多情况下,它可以使代码更直接更优雅。

5. 泛型擦除

5.1 泛型擦除

package generics;import java.util.ArrayList;public class ErasedTypeEquivalence {public static void main(String[] args) {Class c1 = new ArrayList<String>().getClass();Class c2 = new ArrayList<Integer>().getClass();System.out.println(c1 == c2);}
}

输出:

true

ArrayList 和 ArrayList 应该是不同的类型。不同的类型会有不同的行为。例如,如果尝试向 ArrayList 中放入一个 Integer ,所得到的行为(失败)和向ArrayList 中放入一个 Integer 所得到的行为(成功)完全不同。然而上面的程序认为它们是相同的类型。

下面的例子是对该谜题的补充:

package generics;import java.util.*;class Frob {
}class Fnorkle {
}class Quark<Q> {
}class Particle<POSITION, MOMENTUM> {
}public class LostInformation {public static void main(String[] args) {List<Frob> list = new ArrayList<>();Map<Frob, Fnorkle> map = new HashMap<>();Quark<Fnorkle> quark = new Quark<>();Particle<Long, Double> p = new Particle<>();// Class.getTypeParameters() “返回一个 TypeVariable 对象数组,// 表示泛型声明中声明的类型参数...”System.out.println(Arrays.toString(list.getClass().getTypeParameters()));System.out.println(Arrays.toString(map.getClass().getTypeParameters()));System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));System.out.println(Arrays.toString(p.getClass().getTypeParameters()));}
}

输出:

[E]
[K, V]
[Q]
[POSITION, MOMENTUM]

残酷的现实是:

在泛型代码内部,无法获取任何有关泛型参数类型的信息

因此,你可以知道如类型参数标识符和泛型边界这些信息,但无法得知实际的类型参数从而用来创建特定的实例。

Java 泛型是使用擦除实现的。这意味着当你在使用泛型时,任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此, List 和 List 在运行时实际上是相同的类型。它们都被擦除成原生类型 List 。

package generics;public class HasF {public void f() {System.out.println("HasF.f()");}
}
package generics;
class Manipulator<T> {private T obj;public Manipulator(T obj) {this.obj = obj;}public void manipulate() {obj.f(); // 报错,没有 f()}
}
public class Manipulation {public static void main(String[] args) {HasF hf = new HasF();Manipulator<HasF> manipulator = new Manipulator<>(hf);manipulator.manipulate();}
}

因为擦除,Java 编译器无法将 manipulate() 方法中调用 obj 的 f() 方法这一需求映射到HasF 的 f() 方法。为了调用 f() ,我们必须协助泛型类,给定泛型类一个边界,以此告诉编译器只能接受遵循这个边界的类型。这里重用了 extends 关键字。由于有了边界,下面的代码就能通过编译:

package generics;public class Manipulator2<T extends HasF> {private T obj;Manipulator2(T x) {obj = x;}public void manipulate() {obj.f();}
}

边界 声明 T 必须是 HasF 类型或其子类。

你可能认为泛型在 Manipulator2.java 中没有贡献任何事。你可以很轻松地自己去执行擦除,生成没有泛型的类:

package generics;public class Manipulator3 {private HasF obj;Manipulator3(HasF x) {obj = x;}public void manipulate() {obj.f();}
}

泛型只有在类型参数比某个具体类型(以及其子类)更加“泛化”——代码能跨多个类工作时才有用

如果某个类有一个返回 T 的方法,那么泛型就有所帮助,因为它们之后将返回确切的类型:

package generics;public class ReturnGenericType<T extends HasF> {private T obj;public ReturnGenericType(T x) {this.obj = x;}public T get(){return obj;}
}

5.2 迁移兼容性

擦除不是一个语言特性,它是 Java 实现泛型的一种妥协,因为泛型不是 Java 语言出现时就有的。泛型在 Java 中仍然是有用的,只是不如它们本来设想的那么有用,而原因就是擦除。

擦除的核心动机是你可以在泛化的客户端上使用非泛型的类库,反之亦然。这经常被称为“迁移兼容性”。在理想情况下,所有事物将在指定的某天被泛化。在现实中,即使程序员只编写泛型代码,他们也必须处理 Java 5 之前编写的非泛型类库。这些类库的作者可能从没想过要泛化他们的代码,或许他们可能刚刚开始接触泛型。

因此 Java 泛型不仅必须支持向后兼容性——现有的代码和类文件仍然合法,继续保持之前的含义——而且还必须支持迁移兼容性,使得类库能按照它们自己的步调变为泛型,当某个类库变为泛型时,不会破坏依赖于它的代码和应用。在确定了这个目标后,Java 设计者们和从事此问题相关工作的各个团队决策认为擦除是唯一可行的解决方案。擦除使得这种向泛型的迁移成为可能,允许非泛型的代码和泛型代码共存。

—PS:擦除虽然有弊端,但它是解决泛型向后兼容性的唯一可行方案

5.3 擦除的问题

因此,擦除主要的正当理由是从非泛化代码到泛化代码的转变过程,以及在不破坏现有类库的情况下将泛型融入到语言中。

泛型不能用于显式地引用运行时类型的操作中,例如转型、instanceof 操作和 new 表达式。因为所有关于参数的类型信息都丢失了,当你在编写泛型代码时,必须时刻提醒自己,你只是看起来拥有有关参数的类型信息而已。

public class Foo<T> {T var;
}

看上去当你创建一个 Foo 实例时:

Foo<Cat> f = new Foo<>();

class Foo 中的代码应该知道现在工作于 Cat 之上。泛型语法也在强烈暗示整个类中所有 T 出现的地方都被替换,就像在 C++ 中一样。但是事实并非如此,当你在编写这个类的代码时,必须提醒自己:“不,这只是一个 Object“。

另外,擦除和迁移兼容性意味着,使用泛型并不是强制的:

package generics;class GenericBase<T> {private T element;public void set(T arg) {element = arg;}public T get() {return element;}
}class Derived1<T> extends GenericBase<T> {
}class Derived2 extends GenericBase {
}// Derived3 产生的错误意味着编译器期望得到一个原生基类
//class Derived3 extends GenericBase<?> {}public class ErasureAndInteritance {public static void main(String[] args) {Derived2 d2 = new Derived2();Object obj = d2.get();d2.set(obj);}
}

5.4 边界处的动作

边界:即对象进入和离开方法的地点。这些正是编译器在编译期执行类型检查并插入转型代码的地点。这就告诉我们泛型的所有动作都发生在边界处——对入参的编译器检查和对返回值的转型。这有助于澄清对擦除的困惑,记住:“边界就是动作发生的地方”。

6. 补偿擦除

因为擦除,我们将失去执行泛型代码中某些操作的能力。无法在运行时知道确切类型:

package generics;public class Erased<T> {private final int SIZE = 100;public void f(Object arg) {if (arg instanceof T) {} // 报错T var = new T(); // 报错T[] array = new T[SIZE]; // 报错T[] array2 = (T[]) new Object[SIZE]; // 书上说这个地方应该有警告,但是没有,不知道为啥}
}

—PS:上文有:泛型不能用于显式地引用运行时类型的操作中,例如转型、instanceof 操作和 new 表达式。

有时,我们可以对这些问题进行编程,但是有时必须通过引入类型标签来补偿擦除。这意味着为所需的类型显式传递一个 Class 对象,以在类型表达式中使用它。

例如,由于擦除了类型信息,因此在上一个程序中尝试使用 instanceof 将会失败。类型标签可以使用动态 isInstance() :

package generics;class Building {
}class House extends Building {
}public class ClassTypeCapture<T> {Class<T> kind;public ClassTypeCapture(Class<T> kind) {this.kind = kind;}public boolean f(Object arg) {return kind.isInstance(arg);}public static void main(String[] args) {ClassTypeCapture<Building> ctt1 =new ClassTypeCapture<>(Building.class);System.out.println(ctt1.f(new Building()));System.out.println(ctt1.f(new House()));ClassTypeCapture<House> ctt2 =new ClassTypeCapture<>(House.class);System.out.println(ctt2.f(new Building()));System.out.println(ctt2.f(new House()));}
}

输出:

true
true
false
true

7. 边界

边界允许我们对泛型使用的参数类型施加约束。尽管这可以强制执行有关应用了泛型类型的规则,但潜在的更重要的效果是我们可以在绑定的类型中调用方法。

由于擦除会删除类型信息,因此唯一可用于无限制泛型参数的方法是那些 Object 可用的方法。但是,如果将该参数限制为某类型的子集,则可以调用该子集中的方法。为了应用约束,Java 泛型使用了extends 关键字

重要的是要理解,当用于限定泛型类型时, extends 的含义与通常的意义截然不同。此示例展示边界的基础应用:

package generics;import java.awt.*;interface HasColor {java.awt.Color getColor();
}class WithColor<T extends HasColor> {T item;WithColor(T item) {this.item = item;}T getItem() {return item;}java.awt.Color color() {return item.getColor();}
}class Coord {public int x, y, z;
}// 类需要放接口前面
// class WithColorCoord<T extends HasColor & Coord> {}
class WithColorCoord<T extends Coord & HasColor> {T item;WithColorCoord(T item) {this.item = item;}T getItem() {return item;}java.awt.Color color() {return item.getColor();}int getX() {return item.x;}int getY() {return item.y;}int getZ() {return item.z;}
}interface Weight {int weight();
}// 泛型只能 extends 一个类,但是可以 extends 多个接口
class Solid<T extends Coord & HasColor & Weight> {T item;Solid(T item) {this.item = item;}T getItem() {return item;}java.awt.Color color() {return item.getColor();}int getX() {return item.x;}int getY() {return item.y;}int getZ() {return item.z;}int weight() {return item.weight();}
}class Bounded extends Coord implements HasColor, Weight {@Overridepublic Color getColor() {return null;}@Overridepublic int weight() {return 0;}
}public class BasicBounds {public static void main(String[] args) {Solid<Bounded> solid = new Solid<>(new Bounded());solid.color();solid.getY();solid.weight();}
}

你可能会观察到 BasicBounds.java 中似乎包含一些冗余,它们可以通过继承来消除。在这里,每个继承级别还添加了边界约束:

package generics;class HoldItem<T> {T item;HoldItem(T item) {this.item = item;}T getItem() {return item;}
}class WithColor2<T extends HasColor> extends HoldItem<T> {WithColor2(T item) {super(item);}java.awt.Color color() {return item.getColor();}
}class WithColorCoord2<T extends Coord & HasColor> extends WithColor2<T> {WithColorCoord2(T item) {super(item);}int getX() {return item.x;}int getY() {return item.y;}int getZ() {return item.z;}
}class Solid2<T extends Coord & HasColor & Weight> extends WithColorCoord2<T> {Solid2(T item) {super(item);}int weight() {return item.weight();}
}public class InheritBounds {public static void main(String[] args) {Solid2<Bounded> solid2 = new Solid2<>(new Bounded());solid2.color();solid2.getY();solid2.color();}
}

HoldItem 拥有一个对象,因此此行为将继承到 WithColor2 中,这也需要其参数符合 HasColor

WithColorCoord2Solid2 进一步扩展了层次结构,并在每个级别添加了边界。现在,这些方法已被继承,并且在每个类中不再重复。

—PS:边界由 T extends 限定,每个类或接口中有独有的方法,在最后的继承类中都能引用

8. 通配符

8.1 通配符

起始示例要展示数组的一种特殊行为:将派生类的数组赋值给基类的引用:

package generics;class Fruit {
}class Apple extends Fruit {
}class Jonathan extends Apple {
}class Orange extends Fruit {
}public class CovariantArrays {public static void main(String[] args) {Fruit[] fruit = new Apple[10];fruit[0] = new Apple();fruit[1] = new Jonathan();try {// 编译不报错fruit[0] = new Fruit(); // 运行报错-ArrayStoreException} catch (Exception e) {System.out.println(e);}try {// 编译不报错fruit[0] = new Orange(); // 运行报错-ArrayStoreException} catch (Exception e) {System.out.println(e);}}
}

输出:

java.lang.ArrayStoreException: generics.Fruit
java.lang.ArrayStoreException: generics.Orange

看起来就像数组对它们持有的对象是有意识的,因此在编译期检查和运行时检查之间,你不能滥用它们。

数组的这种赋值并不是那么可怕,因为在运行时你可以发现插入了错误的类型。但是泛型的主要目标之一是将这种错误检测移到编译期。所以当我们试图使用泛型集合代替数组时,会发生什么呢?

package generics;import java.util.ArrayList;
import java.util.List;public class NonCovariantGenerics {public static void main(String[] args) {List<Fruit> fruit = new ArrayList<Apple>();}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uNaoGevq-1693296802004)(img/201.png)]

阅读这段代码时会认为“不能将一个 Apple 集合赋值给一个 Fruit 集合”。记住,泛型不仅仅是关于集合,它真正要表达的是“不能把一个涉及 Apple 的泛型赋值给一个涉及 Fruit 的泛型”。

与数组不同,泛型没有内建的协变类型。这是因为数组是完全在语言中定义的,因此可以具有编译期和运行时的内建检查,但是在使用泛型时,编译器和运行时系统不知道你想用类型做什么,以及应该采用什么规则。

但是,有时你想在两个类型间建立某种向上转型关系。通配符可以产生这种关系。

package generics;import java.util.ArrayList;
import java.util.List;public class GenericsAndCovariance {public static void main(String[] args) {List<? extends Fruit> flist = new ArrayList<>();
//        flist.add(new Apple());
//        flist.add(new Fruit());
//        flist.add(new Object());flist.add(null);Fruit f = flist.get(0);}
}

展示一个简单的 Holder 类:

package generics;import java.util.Objects;public class Holder<T> {private T value;public Holder() {}public Holder(T value) {this.value = value;}public T get() {return value;}public void set(T value) {this.value = value;}@Overridepublic boolean equals(Object o) {return o instanceof Holder && Objects.equals(value, ((Holder) o).value);}@Overridepublic int hashCode() {return Objects.hashCode(value);}public static void main(String[] args) {Holder<Apple> apple = new Holder<>(new Apple());Apple d = apple.get();apple.set(d);
//        Holder<Fruit> fruit = apple; // 不能向上转型Holder<? extends Fruit> fruit = apple;Fruit p = fruit.get();d = (Apple) fruit.get();try {Orange c = (Orange) fruit.get(); // No warning} catch (Exception e) {System.out.println(e);}
//        fruit.set(new Apple());
//        fruit.set(new Fruit());System.out.println(fruit.equals(d));}
}

输出:

java.lang.ClassCastException: generics.Apple cannot be cast to generics.Orange
false

以上得到的信息:

1、创建了一个 Holder ,就不能将其向上转型为 Holder ,但是可以向上转型为 Holder<? extends Fruit> 。

2、如果调用 get() ,只能返回一个 Fruit——这就是在给定“任何;额扩展自 Fruit 的对象”这一边界后,它所能知道的一切了。如果你知道更多的信息,就可以将其转型到某种具体的 Fruit 而不会导致任何警告,但是存在得到ClassCastException 的风险。

3、set() 方法不能工作在 AppleFruit 上,因为 set() 的参数也是"?

extends Fruit",意味着它可以是任何事物,编译器无法验证“任何事物”的类型安全性。

8.2 逆变

还可以走另外一条路,即使用超类型通配符。这里,可以声明通配符是由某个特定类的任何基类来界定的,方法是指定 <?super MyClass> ,或者甚至使用类型参数: <?super T> 。

package generics;import java.util.List;public class SuperTypeWildcards {static void writeTo(List<? super Apple> apples) {apples.add(new Apple());apples.add(new Jonathan());
//        apples.add(new Fruit()); // 报错}
}

参数 applesApple 的某种基类型的 List,这样你就知道向其中添加 AppleApple 的子类型是安全的。但是因为 Apple 是下界,所以你知道向这样的 List 中添加 Fruit 是不安全的,因为这将使这个List 敞开口子,从而可以向其中添加非 Apple 类型的对象,而这是违反静态类型安全的。 下面的示例复习了一下逆变和通配符的的使用:

package generics;import java.util.Arrays;
import java.util.List;public class GenericReading {static List<Apple> apples = Arrays.asList(new Apple());static List<Fruit> fruits = Arrays.asList(new Fruit());static <T> T readExact(List<T> list) {return list.get(0);}static void f1() {Apple a = readExact(apples);Fruit f = readExact(fruits);f = readExact(apples); // 向上转型}// 内部类static class Reader<T> {T readExact(List<T> list) {return list.get(0);}}static void f2() {Reader<Fruit> fruitReader = new Reader<>();Fruit f = fruitReader.readExact(fruits);
//        Fruit a = fruitReader.readExact(apples);}static class CovariantReader<T> {T readCovariant(List<? extends T> list) {return list.get(0);}}static void f3() {CovariantReader<Fruit> fruitReader = new CovariantReader<>();Fruit f = fruitReader.readCovariant(fruits);Fruit a = fruitReader.readCovariant(apples);}public static void main(String[] args) {f1();f2();f3();}
}

看到这里有点费劲,还积攒了几个问题,没关系,带着疑惑找答案才能有收获,推荐大佬文章:Java 中的泛型(两万字超全详解)

看完大佬文章,下面的别看了,我是随便摘了几个。

9. 问题

任何基本类型都不能作为类型参数

Java 泛型的限制之一是不能将基本类型用作类型参数。因此,不能创建ArrayList 之类的东西。

实现参数化接口

一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。下面是产生这种冲突的情况:

package generics;interface Payable<T> {}class Employee implements Payable<Employee> {}// Hourly 不能编译,因为擦除会将 Payable<Employe> 和 Payable<Hourly> 简化为相同的类
// Payable,这样,上面的代码就意味着在重复两次地实现相同的接口。
class Hourly extends Employee implements Payable<Hourly> {}public class MultipleInterfaceVariants {
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJ8zhGL7-1693296802005)(img/202.png)]

转型和警告

使用带有泛型类型参数的转型或 instanceof 不会有任何效果。

重载

package generics;public class UseList {void f(List<T> v) {}void f(List<W> v) {}
}

因为擦除,所以重载方法产生了相同的类型签名。

基类劫持接口

package generics;public class ComparablePet implements Comparable<ComparablePet>{@Overridepublic int compareTo(ComparablePet o) {return 0;}
}class Cat extends ComparablePet implements Comparable<Cat> {}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hSzZy8zr-1693296802006)(img/203.png)]

—PS:就是上面提到的 擦除 引起的

自限定的类型

class SelfBounded<T extends SelfBounded<T>> { // ...

这就像两面镜子彼此照向对方所引起的目眩效果一样,是一种无限反射。SelfBounded 类接受泛型参数T,而 T 由一个边界类限定,这个边界就是拥有 T 作为其参数的 SelfBounded

当你首次看到它时,很难去解析它,它强调的是当 extends 关键字用于边界与用来创建子类明显是不同的。

10. 动态类型安全

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

11. 泛型异常

由于擦除的原因,catch 语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自 Throwable(这将进一步阻止你去定义不能捕获的泛型异常)。 但是,类型参数可能会在一个方法的 throws 子句中用到。
在这里插入图片描述
(图网,侵删)

相关文章:

读书笔记-《ON JAVA 中文版》-摘要23[第二十章 泛型-2]

文章目录 第二十章 泛型5. 泛型擦除5.1 泛型擦除5.2 迁移兼容性5.3 擦除的问题5.4 边界处的动作 6. 补偿擦除7. 边界8. 通配符8.1 通配符8.2 逆变 9. 问题10. 动态类型安全11. 泛型异常 第二十章 泛型 普通的类和方法只能使用特定的类型&#xff1a;基本数据类型或类类型。如果…...

【爬虫】5.6 Selenium等待HTML元素

目录 任务目标 创建Ajax网站 创建服务器程序 Selenium XX 等待 1. Selenium强制等待 2. Selenium隐性等待 3. Selenium循环等待 4. Selenium显示等待 等待方法 任务目标 在浏览器加载网页的过程中&#xff0c;网页的有些元素时常会有延迟的现象&#xff0c;在HTML元素…...

0102阿里云配置3台ECS服务器-大数据学习

文章目录 1 前言1 配置VPC和子网2 创建安全组3 创建云服务器ECS3.1 规划配置3.2 配置 4 xshell连接服务器5 配置基础环境5.1 主机名映射5.2 ssh免密登录5.3 jdk 6 问题集6.1 Permission denied (publickey,gssapi-keyex,gssapi-with-mic).6.2 用tar解压文件出现错误Not found i…...

android 输入法demo

背景&#xff1a; 一个简单的android输入法demo&#xff0c;支持输入png、gif&#xff0c;jpeg、webp等格式。 此示例演示如何编写一个应用程序&#xff0c;该应用程序接受使用 Commit Content API 从键盘发送的丰富内容&#xff08;例如图像&#xff09;。 用户通常希望通过表…...

【经验分享】Markdown中如何显示空格和回车

Markdown中如何显示空格和回车 空格 利用html中的空格实体引用&#xff1a; eg&#xff1a; 这是一些 额外的空格。回车&#xff1a; 方法一&#xff1a;在你想要回车的地方连续按两次回车键 方法二&#xff1a;使用<br>标签 eg&#xff1a; 我想显示<br>…...

深入篇【C++】set和map(multiset/multimap)特性总结与使用

深入篇【C】set和map(multiset/multimap&#xff09;特性总结与使用 一.set/multiset总结二.map/multiset总结三.set/map应用 一.set/multiset总结 set是按照一定次序存储元素的容器在set中&#xff0c;元素的value也标识它(value就是key&#xff0c;类型为T)&#xff0c;并且每…...

OpenAI推出ChatGPT企业版,提供更高安全和隐私保障

&#x1f989; AI新闻 &#x1f680; OpenAI推出ChatGPT企业版&#xff0c;提供更高安全和隐私保障 摘要&#xff1a;OpenAI发布了面向企业用户的ChatGPT企业版&#xff0c;用户可以无限制地访问强大的GPT-4模型&#xff0c;进行更深入的数据分析&#xff0c;并且拥有完全控制…...

Linux虚拟机磁盘扩容

Linux虚拟机磁盘扩容 问题起源 在使用linux系统开发时遇到文件无法创建的问题&#xff0c;根据提示发现是磁盘空间不足。 使用df -h查看具体磁盘使用情况。 针对这个问题&#xff0c;有两种解决方案&#xff1a; 使用du -sh ./*可以查看当前工作目录下各文件的占用空间大小…...

【Go 基础篇】Go语言结构体实例的创建详解

在Go语言中&#xff0c;结构体是一种强大的数据类型&#xff0c;允许我们定义自己的复杂数据结构。通过结构体&#xff0c;我们可以将不同类型的数据字段组合成一个单一的实例&#xff0c;从而更好地组织和管理数据。然而&#xff0c;在创建结构体实例时&#xff0c;有一些注意…...

服务器上使用screen的学习记录

服务器上使用screen 训练模型的时候&#xff0c;花费时间是很长的&#xff0c;不可能一直挂在桌面上。所以就想到用screen了。 记录一下简单的操作指令。 创建screen screen -S roof # 新建一个名字为name的窗口&#xff0c;并进入到该窗口中进入后打开环境&#xff0c;运…...

基于Django+node.js+MySQL+杰卡德相似系数智能新闻推荐系统——机器学习算法应用(含Python全部工程源码)+数据集

目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境node.js前端环境MySQL数据库 模块实现1. 数据预处理2. 热度值计算3. 相似度计算1&#xff09;新闻分词处理2&#xff09;计算相似度 4. 新闻统计5. API接口开发6. 前端界面实现1&#xff09;运行逻辑2&#xff0…...

2001-2022年全国各区县最低工资数据

2001-2022年全国各区县最低工资数据 1、时间&#xff1a;2001-2022年 2、来源:人社部 3、指标&#xff1a;年份、行政区划代码、地区、所属省份、所属城市、经度、纬度、最低工资-每月、最低工资-小时 4、样本量&#xff1a;5.5万条 5、指标解释&#xff1a;最低工资标准是…...

D357周赛复盘:模拟双端队列反转⭐⭐+贪心

文章目录 2810.故障键盘1.直接用reverse解决2.双端队列 2811.判断能否拆分数组&#xff08;比较巧妙的贪心&#xff09;思路完整版 2812.找出最安全路径2810.故障键盘1.直接用reverse解决2.双端队列 2811.判断能否拆分数组&#xff08;比较巧妙的贪心&#xff09;思路完整版 28…...

大数据项目实战(安装Hive)

一&#xff0c;搭建大数据集群环境 1.3 安装Hive 1.3.1 Hive的安装 1.安装MySQL服务 1&#xff09;检查是否安装MySQL&#xff0c;如安装将其卸载。卸载命令 rpm -qa | grep mysql 2&#xff09;搜索MySQL文件夹&#xff0c;如存在则删除 find / -name mysql rm -rf /etc/s…...

跨屏无界 | ZlongGames 携手 Google Play Games 打造无缝游戏体验

一款经典游戏&#xff0c;会在时间的沉淀中被每一代玩家所怀念&#xff0c;经久不衰。对于紫龙游戏来讲&#xff0c;他们就是这样一群怀揣着创作出经典游戏的初心而聚集在一起的团队&#xff0c;致力于研发出被广大玩家喜爱的作品。 从 2015 年团队成立&#xff0c;到 2019 年走…...

mysql数据文件

提示&#xff1a;mysql相关系列的教程和笔记不断持续更新和完善 文章目录 db.opt 文件FRM 文件MYD 文件MYI 文件IBD 文件和 IBDATA 文件 :ibdata1 ibdata n文件 查看数据文件的位置 获取硬盘中数据存储的位置&#xff1a; SHOW VARIABLES LIKE datadir;db.opt 文件 该文件记录…...

Vue2里监听localstorage里值的变化

有的时候,我们需要根据本地缓存在localstorage里值的变化做出相应的操作,这就需要我们监听localstorage: 首先,我们在src下的libs文件夹下新建一个stroage.js用于重写setItem事件,当使用setItem的时候,触发,window.dispatchEvent派发事件 const Stroage = {// 重写set…...

QSqlDatabase(2)实例,QTableView显示数据库表数据

目录 前言 1、实现的功能 2、具体的代码实现 前言 想了解QSqlDatabase基本知识的&#xff0c;以及增删改查的用法&#xff0c;可以浏览上一篇文章&#xff1a; QSqlDatabase&#xff08;1&#xff09;基本接口&#xff0c;以及(增删改除)的简单实例_Ivy_belief的博客-CSDN…...

vue3 监听props 的变化

再三说明 仅仅个人学习用&#xff0c;不误导别人 我觉得props 会创建对应的属性&#xff0c;去接受这些值&#xff0c;比如传递一个ref的基本值 age props.age age.value 传递一个ref的引用值 person props.person person.value 传递一个reactive的引用值 person props.person…...

Docker容器

1、什么是docker,为什么要使用docker 有了docker,可以获取各种软件的镜像&#xff0c;将软件的镜像下载到linux中&#xff0c;基于这个镜像就能够去启动这个容器&#xff0c;这个容器就是这个镜像的完整运行环境&#xff0c;比如mysql、redis、nginx&#xff0c;还能秒级启动他…...

spring 请求等问题

1.post请求 /*** desc: (gateway主要接收前端请求 &#xff0c; 然后对请求的数据进行验证 &#xff0c; 验证之后请求反向代理到服务器 。*当请求 method 为 GET 时 &#xff0c; 可以顺利通过gateway 。 当请求 method 为 POST 时 &#xff0c; gateway则会报如下错误 。*jav…...

汽车制造行业,配电柜如何实施监控?

工业领域的生产过程依赖于高效、稳定的电力供应&#xff0c;而配电柜作为电力分配和控制的关键组件&#xff0c;其监控显得尤为重要。 配电柜监控通过实时监测、数据收集和远程控制&#xff0c;为工业企业提供了一种有效管理电能的手段&#xff0c;从而确保生产的连续性、安全性…...

stable diffusion实践操作-VAE

本文专门开一节写图生图相关的内容&#xff0c;在看之前&#xff0c;可以同步关注&#xff1a; stable diffusion实践操作 大部分底模有VAE&#xff0c;但是部分底模没有VAE&#xff0c;需要专门下载VAE才能使用。 最常用的VAE&#xff1a;vae-ft-mse-840000-ema-pruned 用来饱…...

《Flink学习笔记》——第一章 概念及背景

​ 什么是批处理和流处理&#xff0c;然后由传统数据处理架构为背景引出什么是有状态的流处理&#xff0c;为什么需要流处理&#xff0c;而什么又是有状态的流处理。进而再讲解流处理的发展和演变。而Flink作为新一代的流处理器&#xff0c;它有什么优势&#xff1f;它的相关背…...

顺序表链表OJ题(2)->【数据结构】

W...Y的主页 &#x1f60a; 代码仓库分享 &#x1f495; 前言&#xff1a; 单链表的结构常常不完美&#xff0c;没有双向链表那么”优秀“&#xff0c;所以繁衍出很多OJ练习题。今天我们继续来look look数据结构习题。 下面就是OJ时间&#xff01;&#xff01;&#xff01; …...

css3有哪些新特性?(包含哪些模块)

css3有哪些新特性?包含哪些模块&#xff1f;以下是整理的21个css3新特性&#xff1a; 1.新增选择器 p:nth-child(n){color: rgba(255, 0, 0, 0.75)} 2.新增伪元素 ::before 和 ::after 3.弹性盒模型 display: flex; 4.多列布局 column-count: 5; 5.媒体查询 media (max-width:…...

【Grasshopper基础15】“右键菜单似乎不太对劲”

距离上一篇文章已经过去了挺久的&#xff0c;很长时间没有写GH基础部分的内容了&#xff0c;原因其一是本职工作太忙了&#xff0c;进度也有些落后&#xff0c;白天工作累成马&#xff0c;回家只想躺着&#xff1b;其二则是感觉GH基础系列基本上也介绍得差不多了&#xff0c;电…...

华为Mate60低调发布,你所不知道的高调真相?

华为Mate60 pro 这两天的劲爆新闻想必各位早已知晓&#xff0c;那就是华为Mate60真的来了&#xff01;&#xff01;&#xff01;并且此款手机搭载了最新国产麒麟9000s芯片&#xff0c;该芯片重新定义了手机性能的巅峰。不仅在Geekbench测试中表现出色&#xff0c;还在实际应用…...

C++(18):命名空间

多个库将名字放置在全局命名空间中将引发命名空间污染。 命名空间可以用来防止名字冲突&#xff0c;它分割了全局命名空间&#xff0c;其中每个命名空间是一个作用域。通过在某个命名空间中定义库的名字&#xff0c;库的作者&#xff08;以及用户&#xff09;可以避免全局名字…...

K8S最新版本集群部署(v1.28) + 容器引擎Docker部署(上)

温故知新 &#x1f4da;第一章 前言&#x1f4d7;背景&#x1f4d7;目的&#x1f4d7;总体方向 &#x1f4da;第二章 基本环境信息&#x1f4d7;机器信息&#x1f4d7;软件信息&#x1f4d7;部署用户kubernetes &#x1f4da;第三章 Kubernetes各组件部署&#x1f4d7;安装kube…...