Java —— 泛型

news2024/9/22 5:41:51

目录

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, "苹果");    // err
        myArray.setVal(2, 10.5);    // err

        String 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类型 定义k
    V 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, 但是会发现没有compareTo
            if (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, 但是会发现没有compareTo
            if (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;
    }

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }

    @Override
    public 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的时候就会调用compareTo
        System.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+f6
    private 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+f6
    private 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.coloradmin.cn/o/1264568.html

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

相关文章

高级JVM

一、Java内存模型 1. 我们开发人员编写的Java代码是怎么让电脑认识的 首先先了解电脑是二进制的系统&#xff0c;他只认识 01010101比如我们经常要编写 HelloWord.java 电脑是怎么认识运行的HelloWord.java是我们程序员编写的&#xff0c;我们人可以认识&#xff0c;但是电脑不…

【数值计算方法(黄明游)】常微分方程初值问题的数值积分法:欧拉方法(向前Euler)【理论到程序】

文章目录 一、数值积分法1. 一般步骤2. 数值方法 二、欧拉方法&#xff08;Euler Method&#xff09;1. 向前欧拉法&#xff08;前向欧拉法&#xff09;a. 基本理论b. 典例解析c. 算法实现 常微分方程初值问题的数值积分法是一种通过数值方法求解给定初始条件下的常微分方程&am…

Cenos7系统通过链接一键安装LAMP项目环境(linux,apache,mysql,php)

前言&#xff1a;嫌装环境麻烦&#xff0c;以下介绍自动安装环境的方法 一.环境配置 根据自己需要选择 操作系统&#xff1a;CenOS 7.x以上Web服务器&#xff1a;Apache 2.4数据库&#xff1a;MySQL 5.7开发框架&#xff1a;ThinkPHP 5.0&#xff08;PHP5.0以上&#xff09;…

模拟退火算法应用——求解TSP问题

仅作自己学习使用 一、问题 旅行商问题(TSP) 是要求从一个城市出发&#xff0c;依次访问研究区所有的城市&#xff0c;并且只访问一次不能走回头路&#xff0c;最后回到起点&#xff0c;求一个使得总的周游路径最短的城市访问顺序。 采用模拟退火算法求解TSP问题&#x…

fiddler测试弱网别再去深山老林测了,这样做就能达到弱网效果了!

弱网测试 概念&#xff1a;弱网看字面意思就是网络比较弱&#xff0c;我们通称为信号差&#xff0c;网速慢。 意义&#xff1a;模拟在地铁、隧道、电梯和车库等场景下使用APP &#xff0c;网络会出现延时、中断和超时等情况。 添加图片注释&#xff0c;不超过 140 字&#xf…

WPF中DataGrid解析

效果如图&#xff1a; 代码如下&#xff1a; <DataGrid Grid.Row"1" x:Name"dataGrid" ItemsSource"{Binding DataList}" AutoGenerateColumns"False"SelectedItem"{Binding SelectedItem,UpdateSourceTriggerPropertyChange…

【软件测试】性能测试相关指标

性能测试 了解性能测试相关指标 1.什么是做性能测试 1.1 生活中遇到的软件性能问题 软件用着用着就不能用了&#xff0c;一看热搜&#xff0c;发现该软件的服务器崩崩溃了。 1.2 性能测试定义 测试人员借助性能测试工具&#xff0c;模拟系统在不同场景下&#xff0c;对应…

QT6 Creator编译KDDockWidgets并部署到QT

为什么使用KDDockWidgets 为什么使用KDDockWidgets呢&#xff1f; 首先它是一个优秀的开源dock库&#xff0c;弥补QDockWidget的不足&#xff0c;详情见官网。 其次它支持QML&#xff0c;这是我最终选择这个dock库的主要原因&#xff0c;因为最近在考虑将前端界面用QML做&…

[Linux ] sed文本处理和免交互

一、sed 1.1 sed是什么 sed 是一种流编辑器&#xff08;stream editor&#xff09;&#xff0c;用于对文本数据进行文本转换和处理。它通常被用于在命令行中执行文本编辑任务&#xff0c;可以对输入的文本进行搜索、替换、删除等操作&#xff0c;并将结果输出。sed 是一个非交…

项目中的svg图标的封装与使用

1.安装 npm install vite-plugin-svg-icons -D2.在vite.config.ts中配置 **所有的svg图标都必须放在assets/icons // 引入svg import { createSvgIconsPlugin } from vite-plugin-svg-iconsexport default defineConfig({plugins: [vue(),createSvgIconsPlugin({iconDirs: [p…

Java第十二篇:连接安全版kafka(Kerberos认证)出现的问题解答

Could not find a ‘KafkaClient’ entry in the JAAS configuration 问题现象 问题原因 原因没有找到&#xff0c;怎么引起的倒是很清楚。原因就是找到不到指定路径下的kafka_client_jaas.conf文件&#xff0c;别看我的路径带了两个//&#xff0c;但没问题的&#xff0c;等同…

实战中使用的策略模式,使用@ConditionalOnProperty实现根据环境注册不同的bean

场景复现 举个例子&#xff0c;针对不同的设备的内存的不同加载一些资源的时候需要采取不同的策略&#xff0c;比如&#xff0c;在内存比较大的设备&#xff0c;可以一次性加载&#xff0c;繁殖需要使用懒加载&#xff0c;这个时候我们就可以采用配置文件配置中心去控制了 Cond…

.NET生成微信小程序推广二维码

前言 对于小程序大家可能都非常熟悉了&#xff0c;随着小程序的不断普及越来越多的公司都开始推广使用起来了。今天接到一个需求就是生成小程序码&#xff0c;并且与运营给的推广图片合并在一起做成一张漂亮美观的推广二维码&#xff0c;扫码这种二维码就可以进入小程序。为了…

智能学习台灯_AI摄像头学习机基于MTk8175方案

智能学习台灯是一款专为中小学生设计的学习辅助工具&#xff0c;具有多项突出的参数和功能。首先&#xff0c;它采用了基于联发科MTK平台的解决方案&#xff0c;内置了12纳米四核Cortex-A53处理器&#xff0c;提供了稳定而高效的性能。操作系统方面&#xff0c;智能学习台灯运行…

Java17(LTS Long Term Support)特性

支持JDK17的主流技术框架 spring framework 6.xspringboot 3.xkafka 3.0(不在支持jdk8)jenkins 2.357&#xff08;必须jdk11起步&#xff09;James Gosling表示赶紧弃用Java8&#xff0c;使用性能最好的JDK17Chart GPT也推荐JDK17&#xff0c;从长期到性能来说。 JDK17的特性 …

如何使用ArcGIS实现生态廊道模拟

生态廊道是指一种连接不同生态系统的走廊或通道&#xff0c;其建立有助于解决人类活动对野生动植物栖息地破碎化和隔离化的问题&#xff0c;提高生物多样性&#xff0c;减轻生态系统的压力。在城市化和农业开发不断扩张的背景下&#xff0c;生态廊道对于野生动植物的生存和繁衍…

了解 Navicat 的连接配置文件

Navicat 16 拥有了许多改进和新功能&#xff0c;以满足数据库开发人员和管理员的需求。凭借 100 多种增强功能和一个全新的界面&#xff0c;有比以往更多的方法去构建、管理和维护数据库。众多改进中&#xff0c;旨在最大限度地提高生产率的一个改进是配置多个连接配置文件的能…

unity3d 旋转cube时变形

将cube移到父路径同级&#xff0c;重置再&#xff0c;更改角度&#xff0c;或者将父路径先重置&#xff0c;再将cube移动到父节点下面

2020年6月9日 Go生态洞察:VS Code Go扩展加入Go项目

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

反射、枚举以及lambda表达式

1. 反射 1.1 定义 java的.class文件在运行时会被编译为一个Class对象&#xff0c;既然是对象&#xff0c;那么我们就可以通过一定的方式取到这个对象&#xff0c;然后对于这个对象进行一系列操作&#xff08;改变原本类的属性、方法&#xff09;。 这个操作就是反射&#xf…