Java高级编程—泛型

news2024/12/25 15:07:18

文章目录

  • 1.为什么要有泛型 (Generic)
    • 1.1 泛型的概念
    • 1.2 使用泛型后的好处
  • 2.在集合中使用泛型
  • 3.自定义泛型结构
    • 3.1 自定义泛型类、泛型接口
    • 3.2 自定义泛型方法
  • 4.泛型在继承上的体现
  • 5.通配符的使用
    • 5.1 基本使用
    • 5.2 有限制的通配符的使用

1.为什么要有泛型 (Generic)

Java中的泛型就类似一个标签(Label)。比如在中药店,每个抽屉外面贴着标签,用于标记里面放的药材是什么。又比如在超市购物架上有很多瓶子,每个瓶子装的是什么,也有标签作标识。
image.png
泛型的设计背景:
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在 JDK1.5 之前只能把元素类型设计为Object,JDK1.5 之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList, 这个就是类型参数,即泛型。

1.1 泛型的概念

所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念, 允许我们在创建集合时再指定集合元素的类型,正如:List,这表明 该List只能保存字符串类型的对象。
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

1.2 使用泛型后的好处

直接使用Object也可以存储数据,那么为什么要用泛型呢?

  1. 解决元素存储的安全性问题,好比商品、药品标签,不会弄错。
  2. 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。
    在集合中没有泛型时:
    image.png
    在集合中有泛型时:
    image.png

Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException异常。同时,代码更加简洁、健壮。

2.在集合中使用泛型

在这一部分我们来验证一下1.2节中提到的在集合中使用泛型所带来的好处。
首先来看一下在集合中没有使用泛型时的情况:

//在集合中使用泛型之前的情况:
@Test
public void test1(){
    ArrayList list = new ArrayList();
    //需求:存放学生成绩
    list.add(78);
    list.add(76);
    list.add(89);
    list.add(88);
    //问题一:类型不安全
    list.add("kaixuan");

    for(Object score : list){
        //问题二:强转时,可能出现ClassCastException
        int stuScore = (Integer) score;

        System.out.println(stuScore);
    }
}

在上述代码块中,我们声明了一个ArrayList对象list,(ArrayList的本质:对象引用的“变长”数组),然后往里面添加了4个 int 类型的学生成绩和一个String类型的值"kaixuan"。但是这个时候并不会报编译错误,因为现在list里面可以添加的是Object类型的,因此就会出现前面提到的类型不安全的问题。接着我们去遍历一下 list 集合中的元素,由于我们需要获取到的是 int 类型的值,而 list 里面还存放有别的类型的数据,因此需要强制类型转换。那么在这一步骤就有可能出现“类型转换异常”。
测试结果如下:
image.png可以看到,提示 String类型不能强转成 Integer 类型。
接下来我们来看一下在集合中使用了泛型的情况,以ArrayList为例。

@Test
public void test2(){
    //泛型是一个类型,它不能是基本数据类型,所以需要使用基本数据类型的包装类
    //下面的这个是标准的写法
    //ArrayList<Integer> list = new ArrayList<Integer>();
    
    //jdk7新特性:类型推断。 所以后面的泛型类型可以省略不写
    ArrayList<Integer> list = new ArrayList<>();
    list.add(78);
    list.add(99);
    list.add(100);
    //编译时,就会进行类型检查,保证数据的安全。
   //list.add("dabing");        //现在要添加的是一个String类型,不是Integer类型的,所以编译不通过

    //遍历方式一:
    for(Integer score : list){

        //避免了强转操作
        int stuScore = score;
        System.out.println(stuScore);
    }

    System.out.println("----------------------------------");

    //方式二:
    Iterator<Integer> iterator = list.iterator();
    while (iterator.hasNext()){
        int stuScore = iterator.next();
        System.out.println(stuScore);
    }
}

上述代码块中,我们首先声明了一个存放 Integer 类型数据的 ArrayList类的对象 list。而之所以可以这样声明是因为Java中的集合接口或集合类在 jdk5.0 时都修改为带泛型的结构。如下图所示:
image.png
所以我们在实例化集合类时,可以指明具体的泛型类型。指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。比如:add(E e) —> 实例化以后:add(Integer e)。
接着我们在 list 中添加了三个 int 类型的数据和一个String类型的数据"dabing",那么这个时候编译就会给出警告了,因为我们指定了只能添加Integer类型的数据,现在要添加的是一个String类型的。如下图所示:
image.png
所以去除掉不符合要求的数据,这一过程其实就是IDE帮我们做了一个类型检查。最后实现对 list 中的元素的遍历即可,由于使用了泛型,所以在遍历时就避免了强转的操作。我们以两种方式完成遍历:增强 for 循环和 迭代器。
测试结果如下:
image.png
注意点:1.泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换。
2.如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。

3.自定义泛型结构

3.1 自定义泛型类、泛型接口

由于类和接口在声明上的相似性,这里把自定义泛型类和自定义泛型接口放在一起讨论。
自定义泛型类的声明:

interface List<T>

自定义泛型接口的声明:

class GenTest<K,V>

其中,T,K,V 不代表值,而是表示类型。这里使用任意字母都可以。 常用 T 表示,是Type的缩写。
泛型类、泛型接口的实例化:
一定要在类名后面指定类型参数的值(类型)。如:

List<String> strList = new ArrayList<String>();
Iterator<Customer> iterator = customers.iterator();

下面来看一个具体的例子:

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

/*
* 自定义泛型类
* */
public class Order<T> {    //这个T是什么类型现在还不知道,实例化的时候才能知道,所以这里当作是一个参数
    String orderName;
    int orderId;

    //类的内部结构就可以使用类的泛型
    T orderT;

    public Order(){

    }
    public Order(String orderName, int orderId, T orderT){
        this.orderName = orderName;
        this.orderId = orderId;
        this.orderT = orderT;
    }

    public T getOrderT() {
        return orderT;
    }

    public void setOrderT(T orderT) {
        this.orderT = orderT;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + '\'' +
                ", orderId=" + orderId +
                ", orderT=" + orderT +
                '}';
    }

}

在上述代码块中,我们声明了一个名为 Order 的泛型类。(由于在类名后加了,所以Order 就是一个泛型类,那么在该类的内部就可以使用这个泛型了。)在 Order 类中声明了两个成员变量orderName 和 orderId,同时声明了一个 T 类型的成员变量 orderT,这个 T 是什么类型现在还不知道,只有等到实例化 Order类 的时候才能知道。接着创建了该类的无参和带参的两个构造器,注意到,泛型类的构造器是不能加<>符号的,创建格式如下:

public GenericClass(){   
}

最后为orderT属性添加了get 、set方法,重写了toString方法。
下面我们针对声明的泛型类 Order 做出一些测试,首先做一下实例化操作,创建两个Order类的对象。

@Test
public void test1(){

    //如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型(不建议这样写)
    //要求:如果定义了类是带泛型的,建议在实例化时要指明类的泛型。
    Order order = new Order();
    order.setOrderT(123);
    order.setOrderT("ABC");
    System.out.println(order.getOrderT());

    //建议:实例化时指明类的泛型    (且实例化的时候后面要补上<>)
    Order<String> order1 = new Order<>("orderAA", 1001, "order:AA");

    order1.setOrderT("AA:hello");
    System.out.println(order1.getOrderT());

}

如果在实例化没有指明类的泛型,则认为此泛型类型为Object类型, 也就是类型会被擦除,泛型对应的类型均按照Object处理,但不等价于Object。如上述代码块中创建的order 对象,由于实例化时没有指定泛型的结构,所以在调用setOrderT()时可以传入任意类型的参数。但不建议不指定泛型结构的这种写法。所以一般我们要求:如果定义了的类是带泛型的,建议在实例化时要指明该类的泛型,如上述代码块中创建对象order1 一样,只要我们指定了具体的类型,那么在后续的使用中编译器就会自动帮我们做类型的检查。如下图所示,在指定了 T 为String 后,在需要用到 T 的地方就自动给我们限定了是String 类型的了。
image.png
测试结果如下:
image.png下面我们来测试一下继承了泛型类的子类的实例化。
首先声明两个Order类的子类:SubOrder 和 SubOrder1
SubOrder类的设计如下:

public class SubOrder extends Order<Integer> {
    //因为这个子类已经指明了泛型的结构,所以SubOrder这个子类就不再是泛型类了

}

我们在 SubOrder 类中指定了泛型的结构,所以SubOrder这个子类就不再是泛型类了。(即SubOrder类名后就不要跟<>了,而且在实例化SubOrder类时也不再需要指明泛型的类型,只需把它当作普通类去实例化即可。)
下面给出SubOrder的实例化测试:

@Test
public void test2(){
    SubOrder sub1 = new SubOrder();
    //由于子类在继承带泛型的类时指明了泛型类型,则实例化子类对象时,不再需要指明泛型。(也可以理解为:此时的子类已经不是泛型类了)
    sub1.setOrderT(13);
    System.out.println(sub1.getOrderT().getClass().getName());
}

可见,在实例化时不再需要指明泛型。由于我们指定了SubOrder类中orderT的类型为Intgeger,所以最后输出验证一下该成员变量的类型。
测试结果如下:
image.png
SubOrder1类的设计如下:

public class SubOrder1<T> extends Order<T>{  //SubOrder1<T>:仍然是泛型类
}

在SubOrder1类中,没有指明泛型的结构,所以此时SubOrder1类还是一个泛型类。那么实例化的时候就需要像上述Order类那样实现了。下面给出测试程序:

@Test
public void test2(){
    SubOrder1<String> sub2 = new SubOrder1<>();
    sub2.setOrderT("order2....");
    System.out.println(sub2.getOrderT().getClass().getName());
}

测试结果如下:
image.png
此外,关于泛型类和泛型接口还有一些别的规定。具体如下所示:

  1. 泛型不同的引用不能相互赋值。
    下面来做一下验证:
@Test
public void test3(){
    //泛型不同的引用不能相互赋值
    ArrayList<String> list1 = null;
    ArrayList<Integer> list2 = null;
    list1 = list2;       //此处会报类型不兼容的错误
}

错误信息如下:
image.png
这是因为尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。
2. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
这是因为接口和抽象类本就是不能创建对象的。
3. jdk1.7时,对泛型做了简化的操作:

//1.7之前
ArrayList<Fruit> flist = new ArrayList<<Fruit>>(); 
//1.7之后
ArrayList<Fruit> flist = new ArrayList<>(); 
  1. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
    我们在Order类中添加如下的静态方法show():
//静态方法中不能使用类的泛型  
public static void show(){
    System.out.println(orderT);  
}

错误信息如下:
image.png
这是因为静态结构早于对象的创建,而泛型的指定在实例化的时候。所以在show方法创建的时候,属性orderT还没有指定是什么类型的,而且根本就没有出生,这里可以借助java中静态方法中不能调用非静态变量来辅助理解。
5. 异常类不能是泛型的。
image.png

3.2 自定义泛型方法

方法也可以被泛型化,不管此时定义在其中的类是不是泛型类,也就是说,泛型方法所属的类是不是泛型类都没有关系。 在泛型方法中可以定义泛型参数,泛型参数与类的泛型参数没有任何关系。此时,参数的类型就是传入数据的类型。
回顾一下在 Order 类中所定义的几个方法:

public T getOrderT() {
    return orderT;
}

public void setOrderT(T orderT) {
    this.orderT = orderT;
}


@Override
public String toString() {
    return "Order{" +
            "orderName='" + orderName + '\'' +
            ", orderId=" + orderId +
            ", orderT=" + orderT +
            '}';
}

上述的三个方法都使用到了Order类的泛型,但它们都不是泛型方法。
泛型方法的格式如下:

[访问权限]  <泛型>  返回类型  方法名([泛型标识 参数名称])  抛出的异常

举例如下,在Order类中添加一个泛型方法,取名为copyFromArrayToList:

public <E> List<E> copyFromArrayToList(E[] arr){  
    ArrayList<E> list = new ArrayList<>();
    for(E e : arr) {
        list.add(e);
    }
    return list;
}

可以看到,泛型方法头部的声明结构与普通方法的区别在于两点:一是形参列表和返回值的类型处多了泛型结构;二是在权限修饰符和返回值类型之间添加了泛型标识,这是因为如果加 public 后面的,编译器会把形参位置“E[] arr”误认为是一个对象数组,即数组元素为类E的对象,但其实并没有名为 E 的类。
接下来我们来调用一下这个泛型方法:

@Test
public void test4(){
    Order<String> order = new Order<>();
    Integer[] arr = {1, 3, 4, 9};
    //泛型方法在调用时,指明泛型参数的类型。这个类型和Order类的泛型没有任何的关系
    List<Integer> list = order.copyFromArrayToList(arr);

    System.out.println(list);
}

在实例化 Order 类的时候,指明它的泛型结构为 String,而在为泛型方法copyFromArrayToList() 指定形参的泛型时指定为 Integer,所以再一次验证了泛型参数的类型与类的泛型没有任何的关系。
测试结果如下:
image.png
此外,泛型方法可以声明为静态的。因为泛型参数是在调用方法时确定的,并非是在实例化类时确定。设计如下:

public static <E> List<E> copyFromArrayToList(E[] arr){  
    ArrayList<E> list = new ArrayList<>();
    for(E e : arr) {
        list.add(e);
    }
    return list;
}

4.泛型在继承上的体现

如果 B 是 A 的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,则G并不是G的子类型!关系如下图所示:
image.png
比如:String 是 Object 的子类,但是List 并不是List 的子类。
下面给出如下的测试:

@Test
public void test1(){
    List<Object> list1 = null;
    List<String> list2 = null;
    //此时的lsit1和list2的类型不再具有父子类关系。
    //编译不通过
   // list1 = list2;
}

此时语句 list1 = list2 会编译不通过,警告信息如下:
image.png
这就是因为此时的 list1和list2 的类型不再具有父子类关系。
下面以反证法的角度解释一下为什么不能这样赋值。假设 list1 = list2 可以实现,那么现在往 list1中添加一个整形数据,那么这样list2中就会混入非String的数据,所以出错。
此外,如果类A是类B的父类, G是具有泛型声明的类或接口,则 A 是 B 的父类。
image.png
给出如下的测试:

@Test
public void test2(){
    AbstractList<String> list1 = null;
    List<String> list2 = null;
    ArrayList<String> list3 = null;

    list1 = list3;
    list2 = list3;

    List<String> list4 = new ArrayList<>();  //这个的本质其实就是把list3赋给了list2
}

测试结果如下:
image.png
可以看到,上述的操作是没有问题的。

5.通配符的使用

5.1 基本使用

通配符的符号:?
若类A是类B的父类,则G和G是没有关系的,二者共同的父类是:G<?> 比如 List<?>是List、List等各种泛型List的父类。
下面给出测试程序:

@Test
public void test3(){
    List<Object> list1 = null;
    List<String> list2 = null;

    List<?> list = null;

    list = list1;   
    list = list2;
}

在上述代码段中,List<?>是list1和list2的父类,所以把list1和list2分别赋值给list是没有问题的,编译通过。 接下来我们写一个参数为List<?>的函数,这样的话,list1和list2都应该能当作实参传进去。

//list1和list2都可以传进这个方法里
public void print(List<?> list){
    Iterator<?> iterator = list.iterator();
    while(iterator.hasNext()){
        Object obj = iterator.next();
        System.out.println(obj);
    }
}

由于在形参中使用了通配符,所以在迭代器中也要用到,最后获取的元素类型用Object接收。给出调用print方法的测试如下:

@Test
public void test3(){
    List<Object> list1 = null;
    List<String> list2 = null;

    List<?> list = null;

    list = list1;   //相当于List<?>是list1和list2的父类
    list = list2;

    //编译通过
    print(list1);
    print(list2);
}

接下来尝试一个添加和读取的操作:

@Test
public void test3(){
    List<Object> list1 = null;
    List<String> list2 = null;

    List<?> list = null;

    List<String> list3 = new ArrayList<>();
    list3.add("AA");
    list3.add("BB");
    list3.add("CC");
    
    list = list3;
    //添加:对于List<?>就不能向其内部添加数据。
    //除了添加null之外。  因为什么类型的对象对可以赋值为null
    list.add(null);

    //读取: 允许读取数据,读取的数据类型为Object。
    Object o = list.get(0);
    System.out.println(o);

}

在上述代码块中,list3的类型为List,并向其中添加了三个字符串元素,然后将list3赋值给list,这样的话list和list3就指向同一片空间。那么这个时候就不能向List<?>内部添加数据,因为添加的数据类型可能是非String的。 唯一的例外的是null,它是所有类型的成员;而对于读取元素则无限制,因为list包含的元素总是一个Object。
测试结果如下:
在这里插入图片描述

5.2 有限制的通配符的使用

通配符指定上限:
上限 extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=。
通配符指定下限:
下限super:使用时指定的类型不能小于操作的类,即>= 。
举例如下:
image.png
下面给处测试验证:
首先我们声明一个Person类,并声明一个继承了Person类的Student类,把他们用作泛型的类型。给出如下的测试程序:

@Test
public void test4(){
    List<? extends Person> list1 = null;      //表示list1中的对象为 <=Person类型的
    List<? super Person> list2 = null;        //表示list2中的对象为 >=Person类型的

    List<Student> list3 = new ArrayList<>();
    List<Person> list4 = new ArrayList<>();
    List<Object> list5 = new ArrayList<>();

    list1 = list3;
    list1 = list4;
    //list5已经大于了Person了,所以会报错
  //  list1 = list5;

 //   list2 = list3;    //list3为小于Person的类型了,所以会报错
    list2 = list4;
    list2 = list5;
}

显然,三者之间的关系为:Object是Person的父类,Person是Student的父类。所以根据<? extends Person>的规则,List 类型是不能传给List<? extends Person>的。同理,list3也不能赋给list2。
错误信息如下:
image.png
image.png
读取数据测试:

@Test
public void test4(){
    List<? extends Person> list1 = null;      //表示list1中的对象为 <=Person类型的
    List<? super Person> list2 = null;        //表示list2中的对象为 >=Person类型的

    List<Student> list3 = new ArrayList<>();
    List<Person> list4 = new ArrayList<>();
    List<Object> list5 = new ArrayList<>();

    //读取数据
    list1 = list3;
    Person p = list1.get(0);
    //Student s = list1.get(0);    //编译不通过,需要用Person类型接收,找到的是Student

    list2 = list4;
    Object obj = list2.get(0);
     //Person ob = list2.get(0);   //编译不通过,list2为大于等于Person的,所以只能用Object对象来接收
}

在接收数据时,为了保证不出现问题,需要用最大的类型去接收。因为在多态中,子类可以赋给父类,但父类不能赋为子类。
写入数据测试:

@Test
public void test4(){
List<? extends Person> list1 = null;      //表示list1中的对象为 <=Person类型的
List<? super Person> list2 = null;        //表示list2中的对象为 >=Person类型的

List<Student> list3 = new ArrayList<>();
List<Person> list4 = new ArrayList<>();
List<Object> list5 = new ArrayList<>();

//写入数据:
//编译不通过
//list1.add(new Student());    //虽然Student类型是小于Person的,但是需要添加的类型可能比Student还小(因为父类是不能赋给子类的)

//编译通过
list2.add(new Person());
list2.add(new Student());    //此处不报错,是多态的体现

}

向list1中添加Student类的对象时会出错,这是因为虽然Student类型是小于Person的,但是需要添加的类型可能比Student还小,那么像这样传参就会出现父类传给子类的现象。向list2中添加Person对象和Student对象都是可以的,这是因为在list2的规定下Person类型确实是最小的,而且Person都可以添加,子类一定也是可以的。这其实都是多态的应用。

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

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

相关文章

为什么我接不到大单?

以前的领导创业多年&#xff0c;今天找我聊了一下想让我跟他一起做点事情&#xff0c;聊了一下我的现状&#xff0c;突然让我明白为何我一直都接不到大单了 说起来也不是完全没有好的机会&#xff0c;貌似有点像“公交车定律”&#xff0c;当我很忙碌的时候订单一个接一个&…

IP在网络通信中的重要作用

IP&#xff0c;全称Internet Protocol&#xff0c;即网际互连协议&#xff0c;是TCP/IP体系中的网络层协议。IP作为整个TCP/IP协议族的核心&#xff0c;是构成互联网的基础。它的作用重大且深远&#xff0c;下面将详细阐述IP的定义及其在网络通信中的重要作用。 首先&#xff0…

LVGL移植到ARM开发板(GEC6818开发板)

LVGL移植到ARM开发板&#xff08;GEC6818开发板&#xff09; 一、LVGL概述 LVGL&#xff08;Light and Versatile Graphics Library&#xff09;是一个开源的图形用户界面库&#xff0c;旨在提供轻量级、可移植、灵活和易于使用的图形用户界面解决方案。 它适用于嵌入式系统…

陈景东:集中与分布式拾音与声信号处理 | 演讲嘉宾公布

一、声音与音乐技术专题论坛 声音与音乐技术专题论坛将于3月28日同期举办&#xff01; 声音的应用领域广泛而深远&#xff0c;从场所识别到乐器音响质量评估&#xff0c;从机械故障检测到心肺疾病诊断&#xff0c;声音都发挥着重要作用。在互联网、大数据、人工智能的时代浪潮中…

爬虫逆向实战(35)-MyToken数据(MD5加盐)

一、数据接口分析 主页地址&#xff1a;MyToken 1、抓包 通过抓包可以发现数据接口是/ticker/currencyranklist 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现有一个code参数 请求头是否加密&#xff1f; 无 响应是否加密&#xf…

【OpenCV实战】基于OpenCV中DNN(深度神经网络)使用OpenPose模型实现手势识别详解

一、手部关键点检测 如图所示,为我们的手部关键点所在位置。第一步,我们需要检测手部21个关键点。我们使用深度神经网络DNN模块来完成这件事。通过使用DNN模块可以检测出手部21个关键点作为结果输出,具体请看源码。 二,openpose手势识别模型 OpenPose的原理基于卷积神经网…

Arcgis新建位置分配求解最佳商店位置

背景 借用Arcgis帮助文档中的说明:在本练习中,您将为连锁零售店选择可以获得最大业务量的商店位置。主要目标是要将商店定位在人口集中地区附近,因为这种区域对商店的需求量较大。设立这一目标的前提是假设人们往往更多光顾附近的商店,而对于距离较远的商店则较少光顾。您…

构建用户身份基础设施,推动新能源汽车高质量发展

随着市场进入智能电动汽车时代&#xff0c;车企们发现&#xff0c;在激烈竞争的市场中不断增长&#xff0c;并不是一件容易的事。《麻省理工科技评论》&#xff0c;前段时间写了一篇报道&#xff1a;中国是如何称霸电动汽车世界的&#xff1f;“过去两年&#xff0c;中国电动汽…

学点Java打小工——Day2Day3一点作业

1 猜数字&#xff08;10次机会&#xff09; 随机生成[1,1000]的一个数&#xff0c;输入你猜的数程序会给出反馈&#xff0c;直到猜对或次数用尽(10次)。 //猜数字 10次机会Testpublic void guessNumber() {Random random new Random();// [0, 1000) 1// [1, 1000]int num ra…

【JavaScript】JavaScript 简介 ④ ( 解释型语言 和 编译型语言 | 计算机程序本质 | 编译器 和 解释器 )

文章目录 一、 解释型语言 和 编译型语言1、计算机程序本质2、编译器 和 解释器3、编译器 分析4、解释器 分析 一、 解释型语言 和 编译型语言 1、计算机程序本质 计算机 的 程序 是在 CPU 上执行的 , CPU 上执行的只有匹配该 CPU 的机器码指令 , 不同类型的 CPU 执行的 机器码…

【django framework】ModelSerializer+GenericAPIView,如何获取HTTP请求头中的信息(远程IP、UA等)

【django framework】ModelSerializerGenericAPIView&#xff0c;如何获取HTTP请求头中的信息(远程IP、UA等) 某些时候&#xff0c;我们不得不获取调用当前接口的客户端IP、UA等信息&#xff0c;如果是第一次用Django Restframework&#xff0c;可能会有点懵逼&#xff0c;那么…

【分布式websocket】群聊中的各种难点以及解决推拉结合【第16期】

前言 群聊中未读消息如何设计&#xff0c;以及是推消息还是拉去消息如何选择是需要讨论的。推送消息是推送全量消息还是推送信号消息让客户端再去拉取。其中方案如何选型会比较纠结。 首先基本的推拉结合思路是在线用户推送消息。用户离线的话上线去拉取消息。这是简单的推拉结…

Nginx、LVS、HAProxy工作原理和负载均衡架构

当前大多数的互联网系统都使用了服务器集群技术&#xff0c;集群是将相同服务部署在多台服务器上构成一个集群整体对外提供服务&#xff0c;这些集群可以是 Web 应用服务器集群&#xff0c;也可以是数据库服务器集群&#xff0c;还可以是分布式缓存服务器集群等等。 在实际应用…

Camtasia Studio 编辑好的视频如何生成MP4

Camtasia Studio 是一款功能强大的屏幕录像和视频编辑软件&#xff0c;由 TechSmith 公司开发。它可以帮助用户轻松地录制屏幕和音频、添加注释和特效、剪辑和编辑视频&#xff0c;并导出高质量的视频文件。作为一款非常流行的视频制作工具&#xff0c;Camtasia Studio 在教育、…

论文阅读-federated unlearning via class-discriminative pruning

论文阅读-federated unlearning via class-discriminative pruning FUCP 通过类别区分性剪枝进行联邦遗忘 综述中描述&#xff1a;属于面向全局模型中的局部参数调整 利用卷积层的结构特定进行联邦忘却学习&#xff0c;wang等人提出了针对图像分类任务的联邦忘却学习算法FUCP&…

Oracle with as用法

一、简介 with…as关键字&#xff0c;是以‘with’关键字开头的sql语句&#xff0c;在实际工作中&#xff0c;我们经常会遇到同一个查询sql会同时查询多个相同的结果集&#xff0c;即sql一模一样&#xff0c;这时候我们可以将这些相同的sql抽取出来&#xff0c;使用with…as定…

ETH共识升级之路

简介 根据我们之前的介绍&#xff0c;了解到ETH网络的共识方式&#xff0c;已经从 PoW 切换到了 PoS&#xff0c;今天我们就回顾下升级之路&#xff0c;以及升级带来的影响 最早的共识机制 PoW 以太坊创建之初采用了类似比特币的工作量证明机制&#xff0c;即矿工通过计算哈希函…

【排序算法】-- 深入理解桶排序算法

概述 在计算机科学中&#xff0c;排序算法是一种对数据进行有序排列的重要技术。桶排序&#xff08;Bucket Sort&#xff09;是一种常见的排序算法&#xff0c;它通过将数据分到有限数量的桶中&#xff0c;并对每个桶中的数据分别排序&#xff0c;最后按照顺序将所有桶中的数据…

PBKDF2算法:保障密码安全的利器

title: PBKDF2算法&#xff1a;保障密码安全的利器 date: 2024/3/14 16:40:05 updated: 2024/3/14 16:40:05 tags: PBKDF2算法密码安全性迭代盐值密钥 PBKDF2算法起源&#xff1a; PBKDF2&#xff08;Password-Based Key Derivation Function 2&#xff09;算法是一种基于密码…

远程办公、企业内网服务器的Code-Server上如何配置使用CodeGeeX插件

很多小伙伴都会在工作中使用code-server&#xff0c;比如说远程办公&#xff0c;当你需要在家访问你的工作环境&#xff0c;亦或者是你们公司的Docker是放入服务器中。code-server 无疑是最好的选择&#xff0c;它可以让你通过互联网安全地连接到远程服务器上的开发环境并且使用…