Java —— 泛型

目录

1. 什么是泛型

2. 泛型背景及其语法规则

3. 泛型类的使用

3.1 语法

3.2 示例

3.3 类型推导(Type Inference)

4. 裸类型(Raw Type)

4.1 说明

5. 泛型如何编译的

5.1 擦除机制

5.2 为什么不能实例化泛型类型数组

6. 泛型的上界

6.1 上界语法产生的背景

6.2 语法

6.3 示例

6.4 复杂示例

7. 泛型方法

泛型方法的语法

8. 通配符

8.1 通配符解决什么问题

8.2 通配符的上界

8.3 通配符下界

9. 包装类

9.1 基本数据类型和对应的包装类

9.2 装箱和拆箱

9.3 自动装箱和自动拆箱


1. 什么是泛型

一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。----- 来源《Java编程思想》对泛型的介绍。
    public static void func(int a) {}

对于func()这个代码来说, 去传参只能传int类型的数据.

而泛型就是可以传类型. 也就是说, 如果这里是泛型, 就可以传各种数据类型, 包括自定义的数据类型在内, 都可以作为参数进行传参.

泛型是在JDK1.5引入的新的语法,通俗讲,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。

泛型就是 对类型进行参数化.

2. 泛型背景及其语法规则

实现一个类,类中包含一个数组成员,使得数组中可以存放任何类型的数据,也可以根据成员方法返回数组中某个下标的值?

答: 我们可以把数组定义为Object数组, 因为所有类都默认继承于Object类.

// JVM会做一些改变 把1 2 3 4 这些数据放到这个数组当中(怎么放的涉及装箱拆箱, 在后文解析)
Object[] array = {1, 2, 3, 4, "hello", "ok"};

可以看到, 因为是array是Object数组, 所以什么类型的数据都可以放.

但是这样写会带来一个问题, 如果要写以下代码:

String[] arr2 = (Object)array;  // 报错, array中放的不一定都是String类型的数据
String[] array3 = (String[]) new Object[10];    // 报错, 不能确定Object中都是String类型的数据
实际开发中不要尝试把Object类型的数组强制转换成String类型的数组.

再回到原来的问题:

// 实现一个类
class MyArray {// 类中包含一个数组成员public Object[] obj = new Object[10];// 使得数组中可以存放任何类型的数据public void setVal(int pos, Object val) {obj[pos] = val; // 在 obj 的 pos位置 放 val}// 也可以根据成员方法返回数组中某个下标的值public Object getPos(int pos) {return obj[pos];}
}

现在我们调用一下上面这两个方法.

public class Test {public static void main(String[] args) {MyArray myArray = new MyArray();myArray.setVal(0, 10);myArray.setVal(1, "苹果");myArray.setVal(2, 10.5);}
}

可以看到, 这个类确实非常通用, 将需要的东西往里面放就可以了, 但是接下来就会有问题了.

我们尝试获取一下1下标的值, 由于它是一个字符串:

String str = myArray.getPos(1);

但是编译器直接报红了:

图中提到, 需要类型是String, 但是返回类型是Object. 我们知道, 父类类型给到子类类型是向下转型, 所以在getPos()返回的是Object, 所以我们尝试加上强转为String, 就不会报错了.

String str = (String) myArray.getPos(1);

但是这对开发人员来说很不友好, 如果此时要取2号元素, 那么就必须再看一下它的数据类型是什么, 这降低了开发的效率.


总结两个问题:以上代码实现后我们发现:

  1. 当前数组是可以存放任何类型数据
  2. 1号下标本身就是字符串,但是却编译报错。必须进行【强制类型转换】

虽然在这种情况下,当前数组任何数据都可以存放,但是,更多情况下,我们还是希望这个数组只能够持有一种数据类型。而不是同时持有这么多类型。所以,泛型的主要目的:就是指定当前的容器,要持有什么类型的对象, 让编译器去做检查。此时,就需要把类型,作为参数传递。需要什么类型,就传入什么类型。

我们在类名的后面加上<T>:

class MyArray<T> {}
<T>在这里相当于是一个占位符, 表示当前类是一个泛型类.
T只是一个"参数", 类似于形参变量, 也可以是K, 也可以是V, 只是名字而已.
其实泛型在这里不同的字母代表的是不同的意思, 只不过在这里是为了增强可阅读性.
【规范】类型形参一般使用一个大写字母表示,常用的名称有:
E 表示 Element
K 表示 Key
V 表示 Value
N 表示 Number
T 表示 Type
S, U, V 等等 - 第二、第三、第四个类型

接下来我们把所有的Object改成T:

class MyArray<T> {//public T[] obj = new T[10]; // 把所有的Object改成T, 但是此时这里会报错public T[] obj = (T[]) new Object[10]; // 暂且先写成这样, 但是依然不太好, 后续解释public void setVal(int pos, T val) {obj[pos] = val;}public T getPos(int pos) {return obj[pos];}
}

此时在main中修改代码为:

public class Test {public static void main(String[] args) {MyArray<Integer> myArray = new MyArray<Integer>();  // 改为这样的代码后, 下面代码报错了myArray.setVal(0, 10);myArray.setVal(1, "苹果");    // errmyArray.setVal(2, 10.5);    // errString str = (String) myArray.getPos(1);    // err}
}

可以发现, 在存元素的时候, 第一个没有报错, 而第二, 三个报错了, 也就是说这里指定了当前把类型作为参数传递的时候, 传的是Integer类型. 所以就能放整型. 于是:

public class Test {public static void main(String[] args) {MyArray<Integer> myArray = new MyArray<Integer>();myArray.setVal(0, 10);myArray.setVal(1, 2);myArray.setVal(2, 6);int a = myArray.getPos(1);// 同理, 要在MyArray中放字符串, 就指定String类型MyArray<String> myArray2 = new MyArray<String>();myArray2.setVal(0, "hello");myArray2.setVal(1, "hello2");String str = myArray2.getPos(0);}
}

可以看到, 当传的类型参数不一样的时候, setVal()的第二个参数就能放不同类型. 所以在我们以前传的是变量的值, 而现在则相当于传递的是类型.

那么相比前面来说, 存的时候, 它自动进行了类型的检查, 看存放的类型是否是Integer/String, 如果不是就会报错.而且, 在取数据的时候, 就不需要进行强转了. 这便是泛型所存在的两个最大意义.

泛型存在的两个最大的意义:
1. 存放元素的时候, 会进行类型的检查
2. 取出元素的时候, 会自动进行类型的转换(即 不需要再进行类型的强转).
以上两步都是 在编译的时候完成的(运行的时候, 是没有泛型的概念的).
泛型主要是编译时期的一种机制, 这种机制有一个概念 —— 擦除机制.
class 泛型类名称<类型形参列表> {// 这里可以使用类型参数
}class ClassName<T1, T2, ..., Tn> {// 一个泛型类的参数列表可以指定多个类型
}
class 泛型类名称<类型形参列表> extends 继承类/* 这里可以使用类型参数 */ {// 这里可以使用类型参数
}class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {// 可以只使用部分类型参数
}

比如:

// 拿到类型后可以定义变量
class TestDemo<K,V> {K k;    // 通过 K类型 定义kV v;    // 通过 V类型 定义v
}

注意: main中的<>中填写的必须是类类型, 不能是基本类型!

3. 泛型类的使用

3.1 语法

泛型类<类型实参> 变量名;  // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参);  // 实例化一个泛型类对象

3.2 示例

MyArray<Integer> list = new MyArray<Integer>();

注意:泛型只能接受类,所有的基本数据类型必须使用包装类!

3.3 类型推导(Type Inference)

当编译器可以根据上下文推导出类型实参时,可以省略类型实参的填写:

MyArray<Integer> list = new MyArray<>(); // 可以推导出实例化需要的类型实参为 String

4. 裸类型(Raw Type)

4.1 说明

裸类型是一个泛型类但没有带着类型实参,例如 MyArrayList 就是一个裸类型

MyArray list = new MyArray();

注意: 我们不要自己去使用裸类型,裸类型是为了兼容老版本的 API 保留的机制

小结:

  1. 泛型是将数据类型参数化,进行传递
  2. 使用<T> 表示当前类是一个泛型类。
  3. 泛型目前为止的优点:数据类型参数化,编译时自动进行类型检查和转换

5. 泛型如何编译的

5.1 擦除机制

以下代码是前文代码放到一起, 我们在IDEA中Build它, 然后查看jclasslib(需要安装 jclasslib bytecode viewer插件).
class MyArray<T> {//public T[] obj2 = new T[10]; // 把所有的Object改成T, 但是此时这里会报错public T[] obj = (T[]) new Object[10];public void setVal(int pos, T val) {obj[pos] = val;}public T getPos(int pos) {return obj[pos];}
}public class Test {public static void main(String[] args) {MyArray<Integer> myArray = new MyArray<Integer>();myArray.setVal(0, 10);myArray.setVal(1, 2);myArray.setVal(2, 6);int a = myArray.getPos(1);MyArray<String> myArray2 = new MyArray<String>();myArray2.setVal(0, "hello");myArray2.setVal(1, "hello2");String str = myArray2.getPos(0);}
}

先来看setVal(), 描述符中, V代表的是返回值, I代表int, 后面的就是T的类型, 为Object.

可以看到T是Object, 而并不是其他的类型.

getPos()也是Object.

所以可以知道, 擦除机制就是在编译的时候, 把所有的T擦除成了Object.

Java的泛型机制是在编译级别实现的。编译器生成的字节码在运行期间并不包含泛型的类型信息。

有关泛型擦除机制的文章介绍:Java泛型擦除机制之答疑解惑 - 知乎


5.2 为什么不能实例化泛型类型数组

为什么T[] obj2 = new T[10];是不对的, 编译器会报错, 而T[] obj = (T[]) new Object[10];不报错? 那么其实后面这个代码也是不对的.

假设T[] obj2 = new T[10];编译器能够通过, 那么就意味着可以提供一个方法, 返回T[].

public T[] getobj2() {return obj;
}

T[] obj = (T[]) new Object[10];是不对的, 只不过这里编译器不会报错, 但是我们可以让它报错.

public class Test {public static void main(String[] args) {MyArray<Integer> myArray = new MyArray<Integer>();Integer[] tmp = myArray.getObj2();}
}

T[]擦除成了Object, 那么它要给到Integer[], 显然是不能的, 所以会报错.


数组和泛型之间的一个重要区别是它们如何强制执行类型检查。具体来说,数组在运行时存储和检查类型信息。然而,泛型在编译时检查类型错误。

通俗讲就是:返回的Object数组里面,可能存放的是任何的数据类型,可能是String,可能是Person,运行的时候,直接转给Integer类型的数组,编译器认为是不安全的。

正确的代码(了解):

class MyArray<T> {public T[] array;public MyArray() {}/*** 通过反射创建,指定类型的数组* @param clazz* @param capacity*/public MyArray(Class<T> clazz, int capacity) {array = (T[])Array.newInstance(clazz, capacity);}public T getPos(int pos) {return this.array[pos];}public void setVal(int pos,T val) {this.array[pos] = val;}public T[] getArray() {return array;}
}public class Test {public static void main(String[] args) {MyArray<Integer> myArray1 = new MyArray<>(Integer.class,10);Integer[] integers = myArray1.getArray();}
}

6. 泛型的上界

6.1 上界语法产生的背景

写一个泛型类,类中有一个方法 求一个数组当中的最大值?

我们先来看:

// 泛型类 ALg<T>
class Alg<T> {// 方法: 找到最大值, 传进来的数组为泛型数组T[]public T findMax(T[] array) {T max = array[0];   // 假设最大值是这个数组的 0下标for (int i = 0; i < array.length; i++) {if (max < array[i]) {    // 报错!!!max = array[i]; // max 更新为 array[i]}}return max; // 最后的最大值就在max中存}
}

在IDEA中写下以上代码之后, 会看到if (max < array[i]) {报错.

T一定是一个引用类型, 它不会是基本类型.

如果是基本类型的数组:

    public static void main(String[] args) {int[] array = {1, 2, 3, 4};// 通过下标找到元素, 从而使用 大于小于 进行比较System.out.println(array[0] > array[1]);    }

但是, 对于findMax()来说, 传进来的一定是引用类型,这里不能通过大于小于进行比较.于是我们希望可以使用重写compareTo()方法的办法, 可是:

class Alg<T> {public T findMax(T[] array) {T max = array[0]; for (int i = 0; i < array.length; i++) {//if (max < array[i]) {// T此时传的是Integer, 但是会发现没有compareToif (max./* 没有compareTo? */ (array[i]) < 0) {max = array[i]; }}return max; // 最后的最大值就在max中存}
}// main代码同上, 省略...

由前文的擦除机制我们可以知道, T在编译的时候擦除成了Object, 而Object :

那么它就不具备compareTo这个功能. 所以在这里我们就需要来做一个校验, T如果要去比较, 那么就必须是实现了Comparable接口, 于是就产生了这样一个语法Alg<T extends Comparable<T>>.

修改代码:

class Alg<T extends Comparable<T>> {// 方法: 找到最大值, 传进来的数组为泛型数组T[]public T findMax(T[] array) {T max = array[0];   // 假设最大值是这个数组的 0下标for (int i = 0; i < array.length; i++) {//if (max < array[i]) {// T此时传的是Integer, 但是会发现没有compareToif (max.compareTo(array[i]) < 0) {max = array[i]; // max 更新为 array[i]}}return max; // 最后的最大值就在max中存}
}

然后max就可以.compareTo了. 此时代码就不报错了. 即, 要比较大小, max要调用compareTo, 就要【使用泛型的上界这个语法】.

也就是说class Alg<T extends Comparable<T>> {含义是, T类型实现了Comparable接口, 这个接口也指定了类型是为T.

于是:

public class Test { public static void main(String[] args) {Alg<Integer> alg = new Alg<>();Integer[] array = {1, 2, 3, 4};Integer ret = alg.findMax(array);System.out.println(ret);    // 4}
}


接下来我们再举一个反例:

class Person {}public class Test {public static void main(String[] args) {Alg<Person> alg = new Alg<>();}
}

会发现, <Person>报错了.

因为在检查的时候发现, Alg有<T extends Comparable<T>>, 而Person没有实现Comparable接口.

所以要让它不报错:

class Person implements Comparable<Person> {public int age;public Person(int age) {this.age = age;}@Overridepublic int compareTo(Person o) {return this.age - o.age;}@Overridepublic String toString() {return "Person{" +"age=" + age +'}';}
}public class Test {public static void main(String[] args) {Alg<Person> alg = new Alg<>();// 定义一个Person[]Person[] people = {new Person(10), new Person(15)};Person person = alg.findMax(people);    // 调用findMax的时候就会调用compareToSystem.out.println(person);}
}


在定义泛型类时,有时需要对传入的类型变量做一定的约束,可以通过类型边界来约束。

6.2 语法

class 泛型类名称<类型形参 extends 类型边界> {// ...
}

6.3 示例

public class MyArray<E extends Number> {// ...
}

只接受 Number 的子类型作为 E 的类型实参

MyArray<Integer> l1; // 正常,因为 Integer 是 Number 的子类型
MyArray<String> l2; // 编译错误,因为 String 不是 Number 的子类型

了解: 没有指定类型边界 E,可以视为 E extends Object

6.4 复杂示例

public class MyArray<E extends Comparable<E>> {// ...
}

E必须是实现了Comparable接口的

7. 泛型方法

对 找数组最大值 的优化:

我们发现, 需要调用findMax(), 必须要new Alg对象. 那么能否不new这个对象, 也就是说, 如果把这个方法变成static, 那么是否就可以不new这个对象?

class Alg2<T extends Comparable<T>> {public static T findMax(T[] array) {T max = array[0];for (int i = 1; i < array.length; i++) {//if(max < array[i]) {if (max.compareTo(array[i]) < 0) {max = array[i];}}return max;}
}

会看到 , 整个代码的T都报错了.

为什么加了static报错? 或者说, 没加static之前, T是如何确定的?

没加static之前, new Alg传了实参Integer, 加了static之后, 意思就是, 要通过Alg.findMax()直接调用, 显然问题在于这里没有机会传实参, 所以Alg2上加<T extends Comparable<T>>或者不加都是没用的, 那, 不加<T extends Comparable<T>>, 不就更没用了吗, 所以, 这里从语法上要这么改:

class Alg2 {public static <T extends Comparable<T>> T findMax(T[] array) {T max = array[0];for (int i = 1; i < array.length; i++) {//if(max < array[i]) {if (max.compareTo(array[i]) < 0) {max = array[i];}}return max;}
}

就不报错了. 所以当方法是静态的时候, 它不依赖于对象, 这个泛型的传参就要在static后加上<T extends Comparable<T>>, 所以此时这个方法就成为了一个泛型方法.


public class Test {public static void main(String[] args) {Integer[] array = {1, 2, 3, 4};// 给这个泛型方法传递一个 类型的实参Integer ret = Alg2.<Integer>findMax(array); // <Integer>实际上是省略的, 这里写了出来System.out.println(ret);}
}

泛型方法的语法

方法限定符 <类型形参列表> 返回值类型 方法名称(形参列表) { ... }

8. 通配符

? 用于在泛型的使用,即为通配符

8.1 通配符解决什么问题

通配符是用来解决泛型无法协变的问题的,协变指的就是如果StudentPerson 的子类,那么List<Student> 也应该是List<Person> 的子类。但是泛型是不支持这样的父子类关系的。

泛型 T 是确定的类型,一旦你传了我就定下来了,而通配符则更为灵活或者说是不确定,更多的是用于扩充参数的范围.
class Message<T> {private T message;  //消息public T getMessage() {return message;}public void setMessage(T message) {this.message = message;}
}public class Test {public static void main(String[] args) {Message<String> message = new Message();message.setMessage("haha");fun(message);}public static void fun(Message<String> temp) {System.out.println(temp.getMessage());}
}

以上程序会带来新的问题,如果现在泛型的类型设置的不是String,而是Integer.

public class Test {public static void main(String[] args) {Message<Integer> message = new Message();message.setMessage(99);fun(message); // 出现错误,只能接收String}public static void fun(Message<String> temp) {System.out.println(temp.getMessage());}
}

我们需要的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符?来处理.

范例:使用通配符

此时使用通配符" ?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改


在"?"的基础上又产生了两个子通配符:

? extends 类:设置泛型上限
? super 类:设置泛型下限

8.2 通配符的上界

语法:

<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类

如图描述了一个继承关系, 当我们写出Plate<? extends Fruit>这样的代码的时候, 能在Plate中放的就只能有Fruit或者它的子类.

class Food {}class Fruit extends Food {}class Apple extends Fruit {}class Banana extends Fruit {}class Plate<T> { // 设置泛型 shift+f6private T plate;public T getPlate() {return plate;}public void setPlate(T plate) {this.plate = plate;}
}public class Test {public static void main(String[] args) {Plate<Apple> plate1 = new Plate<>();plate1.setPlate(new Apple());fun1(plate1);Plate<Banana> plate2 = new Plate<>();plate2.setPlate(new Banana());fun1(plate2);} // 此时无法在fun函数中对temp进行添加元素,// 因为temp接收的是Fruit和他的子类,此时存储的元素应该是哪个子类无法确定。// 所以添加会报错!但是可以获取元素。public static void fun(Plate<? extends Fruit> temp) {/*不能放东西temp.setPlate(new Apple());temp.setPlate(new Banana());temp.setPlate(new Fruit());*/Fruit fruit = temp.getPlate();}
}

通配符的上界,不能进行写入数据,只能进行读取数据。

8.3 通配符下界

语法:

<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型

class Food {}class Fruit extends Food {}class Apple extends Fruit {}class Banana extends Fruit {}class Plate<T> { // 设置泛型 shift+f6private T plate;public T getPlate() {return plate;}public void setPlate(T plate) {this.plate = plate;}
}public class Test {public static void main(String[] args) {Plate<Fruit> plate1 = new Plate<>();plate1.setPlate(new Fruit());fun(plate1);Plate<Food> plate2 = new Plate<>();plate2.setPlate(new Food());fun(plate2);}public static void fun(Plate<? super Fruit> temp) {temp.setPlate(new Apple());temp.setPlate(new Banana());temp.setPlate(new Fruit());//不能存放Fruit的父类//Fruit fruit = temp.getPlate();  不能取数据 因为无法知道取出的数据类型是什么?}
}

通配符的下界,不能进行读取数据,只能写入数据。

9. 包装类

在Java中,由于基本类型不是继承自Object,为了在泛型代码中可以支持基本类型,Java给每个基本类型都对应了一个包装类型。

9.1 基本数据类型和对应的包装类

基本数据类型

包装类

byte

Byte

short

Short

int

Integer

long

Long

float

Float

double

Double

char

Character

boolean

Boolean

除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。

9.2 装箱和拆箱

int i = 10;// 装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中
Integer ii = Integer.valueOf(i);
Integer ij = new Integer(i);// 拆箱操作,将 Integer 对象中的值取出,放到一个基本数据类型中
int j = ii.intValue();

9.3 自动装箱和自动拆箱

可以看到在使用过程中,装箱和拆箱带来不少的代码量,所以为了减少开发者的负担,java 提供了自动机制。

int i = 10;Integer ii = i; // 自动装箱
Integer ij = (Integer)i; // 自动装箱int j = ii; // 自动拆箱
int k = (int)ii; // 自动拆箱

【面试题】

下列代码输出什么,为什么?

public static void main(String[] args) {Integer a = 127;Integer b = 127;Integer c = 128;Integer d = 128;System.out.println(a == b);System.out.println(c == d);
}

这是因为在Java中,对于 Integer`类型的对象,有一个内置的缓存范围为 -128 到 127。当你创建一个在这个范围内的`Integer 对象时,JVM 会尝试重用现有的对象而不是创建一个新的对象。这被称为整数缓存机制。

所以,当 a 和 b 的值都在 -128 到 127 的范围内时,它们会引用相同的对象,因此 a == b 会返回 true。而当 c 和 d 的值为 128 时,它们超出了缓存范围,因此会创建两个不同的对象,所以 c == d 返回 false。

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

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

相关文章

代码随想录算法训练营 ---第四十九天

前言&#xff1a; 今天是买卖股票的最佳时机系列&#xff0c;本系列之前在学习贪心思想时做过一些。 第一题&#xff1a; 简介&#xff1a; 本题在读题时我们要注意到几个细节 1.本题股票买卖只有一次。2.我们要在最低点买股票&#xff0c;在最高点卖股票。 我的思路&#…

【攻防世界-misc】hong

1.下载解压文件&#xff0c;是个音频文件&#xff0c;但打不开 2.复制到kali中先拆分看音频里面有隐含文件没有 用到的命令是&#xff1a;foremost 桌面/hong.mp3 点击桌面上的主文件夹 点击“output”文件夹&#xff0c; 点击文件中的jpg文件夹&#xff0c;有两张图片&#…

uniapp使用vue3和ts开发小程序获取用户城市定位

这个组件的功能&#xff1a;可以重新定位获取到用户的具体位置&#xff0c;这个是通过getLocation这个api和高德地图的api获取到的&#xff0c;getLocation这个api需要在微信公众平台后台>开发管理> 接口管理里面申请才能使用的&#xff0c;不然无法使用哦&#xff0c;这…

大数据存储技术期中考点梳理

1.CAP理论 分布式系统的CAP理论: 首先将分布式系统中的三个特性进行如下归纳: 口(一致性(C):在分布式系统中的所有数据备份&#xff0c;在同一时刻是否有同样的值。(等于所有节点访问同一份最新的数据副本) 口可用性(A):在集群中一部分节点故障后&#xff0c;集群整体是否还能…

kafka开发环境搭建

文章目录 1 安装java环境1.1 下载linux下的安装包1.2 解压缩安装包1.3 解压后的文件移到/usr/lib目录下1.4 配置java环境变量 2 kafka的安装部署2.1 下载安装kafka2.2 配置和启动zookeeper2.3 启动和停止kafka 1 安装java环境 1.1 下载linux下的安装包 &#xff08;1&#xf…

快速了解Spring AOP的概念及使用

文章目录 1. AOP概念1.1 什么是AOP&#xff1f;1.2 什么是Spring AOP&#xff1f; 2. Spring AOP的使用2.1 引入Spring AOP依赖2.2 编写AOP程序 3. Spring AOP详解3.1 Spring AOP核心概念1. 切点&#xff08;Pointcut&#xff09;2. 连接点&#xff08;Join Point&#xff09;3…

如何运用AppLink平台中的数据连接器组件

AppLink平台组件组成 AppLink平台组件分成三个板块触发事件组件、基础组件和数据连接器 数据连接器组件里面有10个组件&#xff0c;目前也在不断新增更多的数据连接器&#xff0c;那他们在AppLink平台里的原理、触发动作以及怎么使用呢&#xff1f;接下来用MySQL和TimescaleD…

在线陪诊系统: 医疗科技的崭新前沿

在医学科技的快速发展中&#xff0c;在线陪诊系统正成为医疗服务领域的创新力量。通过结合互联网和先进的远程技术&#xff0c;这一系统为患者和医生提供了更为便捷、高效的医疗体验。本文将深入探讨在线陪诊系统的技术背后的核心代码和实现原理。 技术背后的关键代码 在线陪…

用于图像分类任务的经典神经网络综述

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

探索性因子分析流程

探索性因子分析的步骤&#xff1a; 接下来&#xff0c;通过一个案例演示因子分析&#xff08;探索性因子分析&#xff09;的各个步骤应该如何进行。 案例&#xff1a;欲探究我国不同省份铁路运输能力情况&#xff0c;收集到部分相关数据如下&#xff1a; 上传数据至SPSSAU系统…

echarts 水波图

echarts 水波图 安装 npm install echarts --save npm install echarts-liquidfill --save引入 import * as echarts from echarts; import echarts-liquidfill;html <div id"chart1" ref"chart1" class"chart1"></div>css .cha…

常见面试题-Redis 切片集群以及主节点选举机制

Redis 切片集群了解吗&#xff1f; 答&#xff1a; Redis 切片集群是目前使用比较多的方案&#xff0c;Redis 切面集群支持多个主从集群进行横向扩容&#xff0c;架构如下&#xff1a; 使用切片集群有什么好处&#xff1f; 提升 Redis 读写性能&#xff0c;之前的主从模式中&…

使用C语言库函数qsort排序注意点

目录 题目背景错误C语言代码&#xff1a;正确C语言代码&#xff1a;注意点 题目背景 高校团委组织校园歌手比赛&#xff0c;进入决赛的校园歌手有10位,歌手编号从1到10进行编号。组委会随机抽取方式产生了决赛次序为&#xff1a;3,1,9,10,2,7,5,8,4,6。比赛现场有5个评委为参赛…

【Docker项目实战】使用Docker部署Plik临时文件上传系统

【Docker实战项目】使用Docker部署Plik 临时文件上传系统 一、Plik介绍1.1 Plik简介1.2 Plik特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载Plik镜像五、部署Plik临时…

PLC:200smart

PLC&#xff1a;200smart 第十章、数据类型、数据存储1、数据类型1.1、有符号数1.2、有符号数 2、传送指令 第十一章、比较指令、整数、浮点数的运算1、比较指令1、运算指令1.1、浮点数运算1.2、整数运算 第十章、数据类型、数据存储 1、数据类型 数据类型分为两大类 无符号数…

【小布_ORACLE】Part11-1--RMAN Backups笔记

Oracle的数据备份于恢复RMAN Backups 学习第11章需要掌握&#xff1a; 一.RMAN的备份类型 二.使用backup命令创建备份集 三.创建备份文件 四.备份归档日志文件 五.使用RMAN的copy命令创建镜像拷贝 文章目录 Oracle的数据备份于恢复RMAN Backups1.RMAN Backup Concepts&#x…

用了这7款html网页制作软件,你会爱上编程!

制作网页是一个复杂的过程&#xff0c;需要注意到各种细节&#xff0c;只有依靠出色的技术能力和强大的工具&#xff0c;我们才能真正达到我们的目标。幸运的是&#xff0c;有很多优秀的HTML网页设计软件可以让整个流程变得更加轻松和高效。以下就是我们经过深思熟虑和严格筛选…

Redis 的过期策略都有哪些?

思考:假如redis的key过期之后&#xff0c;会立即删除吗&#xff1f; Redis对数据设置数据的有效时间&#xff0c;数据过期以后&#xff0c;就需要将数据从内存中删除掉。可以按照不同的规则进行删除&#xff0c;这种删除规则就被称之为数据的删除策略&#xff08;数据过期策略…

i已学赋能智慧教育时代的幼儿教育

伴随“教育数字化战略行动”的深入开展,智慧教育正式成为国家战略。智慧教育延伸至家校社教育的每个阶段。当前,为适应智慧教育发展趋势,我国制定了《中国教育现代化2035》《教育部关于加强“三个课堂”应用的指导意见》《教育信息化2.0行动计划》等文件。幼儿作为智慧教育、智…

什么是高级语言、机器语言、汇编语言?什么是编译和解释?

1、高级语言 计算机程序是一种让计算机执行特定任务的方法。程序是由程序员用一种称为编程语言的特殊语言编写的。编程语言有很多种&#xff0c;例如 C、C、Java、Python 等。这些语言被称为高级语言&#xff0c;因为它们更接近人类的自然语言&#xff0c;而不是计算机能够直接…