Java基础-泛型

news2024/9/27 15:22:54

泛型

    • 基本概念
    • 为什么我们需要泛型
    • 泛型类型
      • 泛型类
        • 简单泛型类
        • 多元泛型类
      • 泛型接口
      • 泛型方法
        • 为什么要使用泛型方法呢?
        • 使用方法
      • 泛型的上下限
        • 上限
        • 下限
        • 加点难度的例子
          • 例子一
          • 例子二
          • 例子三
      • 深入理解泛型
        • 什么是泛型擦除后保留的原始类型
        • 泛型类型擦除原则
        • 如何进行擦除的?
        • 怎么证明存在类型擦除呢
        • 如何理解泛型的编译期检查
        • 泛型中参数化类型为什么不考虑继承关系
        • 如何理解泛型的多态和桥接方法

基本概念

Java泛型是JDK5引入的一个新特性,它提供编译时类型安全检测机制.
该机制可以在编译时检测到非法的类型.
泛型的本质是参数化类型(所操作的数据类型被指定为一个参数)
换句话说:由原来具体的类型,转换为类似方法中的变量参数(形参

为什么我们需要泛型

我们举个例子来看一下:

List arrayList = new ArrayList();
     arrayList.add("string");
     arrayList.add(1);

     for(int i = 0; i< arrayList.size();i++){
          String item = (String)arrayList.get(i);
          System.out.println("泛型测试"+"item = " + item);
     }

这段代码在编译时没问题,但一运行就会报错,报错日志如下:

java.lang.Integer cannot be cast to java.lang.String

在JDK1.5之前,没有范型的情况下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型的转换,这需要开发者对实际参数类型可以预知才能进行.
对于强转类型的转换作物,编译器在编译期可能不会提示错误,在运行时才出现异常,所以这是一个安全隐患.

  1. 提高代码的利用率
    范型使得数据的类别可以像参数一样由外部传递进来.它提供了一种扩展能力.更符合面向抽象开发的软件编程宗旨
  2. 把一些运行时错误提前到了编译时,提高安全性.
    只有相匹配的数据才可以正常赋值,否则编译起不通过,它属于类型安全检测机制,提高了软件的安全性,防止出现低级失误

我们再来一个例子来体验一下泛型的好处:

private static int add(int a, int b) {
    System.out.println(a + "+" + b + "=" + (a + b));
    return a + b;
}

private static float add(float a, float b) {
    System.out.println(a + "+" + b + "=" + (a + b));
    return a + b;
}

private static double add(double a, double b) {
    System.out.println(a + "+" + b + "=" + (a + b));
    return a + b;
}

如果没有泛型的话,要实现这种不同类型的加法,每种类型都需要重载一个add方法;
通过泛型,我们可以复用为一个方法:

private static <T extends Number> double add(T a, T b) {
    System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue()));
    return a.doubleValue() + b.doubleValue();
}

泛型类型

泛型有三种存在方式:

  1. 泛型类
  2. 泛型接口
  3. 泛型方法

泛型类

泛型用于类的定义中,通过泛型可以完成对一组类向外提供相同的接口

我们先从一个简单泛型类看起

简单泛型类
class Point<T>{         // 此处可以随便写标识符号,T是type的简称  
    private T var ;     // var的类型由T指定,即:由外部指定  
    public T getVar(){  // 返回值的类型由外部决定  
        return var ;  
    }  
    public void setVar(T var){  // 设置的类型也由外部决定  
        this.var = var ;  
    }  
}  
public class GenericsDemo06{  
    public static void main(String args[]){  
        Point<String> p = new Point<String>() ;     // 里面的var类型为String类型  
        p.setVar("it") ;                            // 设置字符串  
        System.out.println(p.getVar().length()) ;   // 取得字符串的长度  
    }  
}
多元泛型类
class Notepad<K,V>{       // 此处指定了两个泛型类型  
    private K key ;     // 此变量的类型由外部决定  
    private V value ;   // 此变量的类型由外部决定  
    public K getKey(){  
        return this.key ;  
    }  
    public V getValue(){  
        return this.value ;  
    }  
    public void setKey(K key){  
        this.key = key ;  
    }  
    public void setValue(V value){  
        this.value = value ;  
    }  
} 
public class GenericsDemo09{  
    public static void main(String args[]){  
        Notepad<String,Integer> t = null ;        // 定义两个泛型类型的对象  
        t = new Notepad<String,Integer>() ;       // 里面的key为String,value为Integer  
        t.setKey("汤姆") ;        // 设置第一个内容  
        t.setValue(20) ;            // 设置第二个内容  
        System.out.print("姓名;" + t.getKey()) ;      // 取得信息  
        System.out.print(",年龄;" + t.getValue()) ;       // 取得信息  
  
    }  
}

泛型接口

与泛型类的定义及使用基本相同,允许在接口定义中使用类型参数.
实现该接口的类可以为这些类型参数提供具体的类型.
增加了代码的灵活性和重用性
常被用在各种类的生产器中.
举个例子来说:

interface Info<T>{        // 在接口上定义泛型  
    public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型  
}  
class InfoImpl<T> implements Info<T>{   // 定义泛型接口的子类  
    private T var ;             // 定义属性  
    public InfoImpl(T var){     // 通过构造方法设置属性内容  
        this.setVar(var) ;    
    }  
    public void setVar(T var){  
        this.var = var ;  
    }  
    public T getVar(){  
        return this.var ;  
    }  
} 
public class GenericsDemo24{  
    public static void main(String arsg[]){  
        Info<String> i = null;        // 声明接口对象  
        i = new InfoImpl<String>("汤姆") ;  // 通过子类实例化对象  
        System.out.println("内容:" + i.getVar()) ;  
    }  
}  

泛型方法

为什么要使用泛型方法呢?

因为泛型类需要在实例化的时候就指明类型,一旦实例化完成,这个对象的泛型类型就固定下来了。如果你想使用另一种类型的泛型对象,你需要重新实例化这个类,并指定新的类型。不太灵活;
而泛型方法允许在调用方法的时候才指定泛型的具体类型。这意味着可以在不重新实例化类的情况下,多次调用同一个泛型方法,并每次指定不同的类型。
举个例子来说:

  • 泛型类
public class Box<T> {  
    private T t;  
      
    public void set(T t) {  
        this.t = t;  
    }  
      
    public T get() {  
        return t;  
    }  
}  
  
// 使用时需要指定具体类型,并实例化  
Box<Integer> integerBox = new Box<>();  
integerBox.set(10);  
Integer value1 = integerBox.get(); // 获取时类型已经确定为Integer  
  
Box<String> stringBox = new Box<>(); // 需要重新实例化来存储字符串类型  
stringBox.set("Hello");  
String value2 = stringBox.get(); // 获取时类型已经确定为String
  • 泛型方法
public class Utility {  
    // 泛型方法,可以在调用时指定类型  
    public static <T> T echo(T input) {  
        return input;  
    }  
}  
  
// 使用时直接调用方法,并指定类型(类型推断)  
Integer intValue = Utility.echo(10); // 这里T被推断为Integer类型  
String strValue = Utility.<String>echo("Hello"); // 这里显式指定了T为String类型,但实际上可以省略,因为编译器可以推断出来  
使用方法

在调用方法的时候指明泛型的具体类型.泛型方法的定义比泛型类和接口要复杂.
泛型类是指实例化类的时候指明泛型的具体类型;
泛型方法是在调用方法的时候指明泛型的具体类型.
只有声明了 的方法才是泛型方法,在泛型类中使用了泛型的成员方法并不是泛型方法.
举个例子来说明基本语法:

/**
 * 泛型方法介绍
 * @param url
 * @param result 
 * @param <T> 只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法
 *           与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
 * @return T 返回值为T类型
 */
public static <T> T doGetRequst(String url,Result<T> result){
}

或者拿一张网图来解释:
在这里插入图片描述
调用泛型方法语法格式
在这里插入图片描述
Class<T>的作用是指明泛型的具体类型,而Class<T>类型的变量c,可以用来创建泛型类的对象.

为什么要用变量c来创建对象呢?
因为既然是泛型方法,就代表我们不知道具体的类型是什么,也不知道构造方法是什么,因此没有办法去new一个对象
但是我们可以利用变量c的newInstance方法区创建对象,也就是利用反射创建对象.

看一个简单的实例帮助理解:

public class GenericMethodExample {  
  
    public static <T> void printArray(T[] array) {  
        for (T element : array) {  
            System.out.print(element + " ");  
        }  
        System.out.println();  
    }  
  
    public static void main(String[] args) {  
        Integer[] intArray = {1, 2, 3, 4, 5};  
        String[] strArray = {"Hello", "World"};  
  
        // 调用泛型方法并传入整型数组  
        printArray(intArray);  
  
        // 调用泛型方法并传入字符串数组  
        printArray(strArray);  
    }  
}

泛型的上下限

泛型上下限是用来约束泛型类型参数的范围/

上限

泛型的上限使用extends来定义,表示参数化的类型必须是制定类型或其子类.
eg: <T extends Number>表示T可以是Number类或其任何子类(如Integer,Double等)
所以它也称为上界通配符,它定义了泛型类型的上界,在泛型方法或泛型类中使用上限时,可以确保类型安全,并且只允许在泛型容器中添加符合约束条件的对象.
上限例子

class Info<T extends Number>{    // 此处泛型只能是数字类型
    private T var ;        // 定义泛型变量
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // 直接打印
        return this.var.toString() ;
    }
}
public class demo1{
    public static void main(String args[]){
        Info<Integer> i1 = new Info<Integer>() ;        // 声明Integer的泛型对象
    }
}
下限

泛型的下限使用super关键字来定义,表示参数化的类型必须是制定类型或其超类(父类).
泛型系统并不支持下界通配符(? super T) 作为类型参数来声明泛型类或接口.
但是在使用泛型方法的时候,可以使用下界通配符(? super T)来放宽对类型的约束,允许方法接受更广泛的类型参数.
实际上在泛型方法的参数中使用下界通配符的情况相对较少见,因为Java的类型推断通常足够智能,可以推断出正确的类型参数而无需显式指定下界。

我们在使用通配符(如 ? , ? extends T , ? super T)来表示未知类型或类型范围,但是要遵循一定的规则和最佳实践,确保代码的类型安全和可读性.
例如,在PECS原则(Product Extends, Consumer Super)中建议,当泛型容器用于提供(produce) 元素时,应使用上限通配符;
当用于消费(consume)元素时,应使用无界通配符或下限通配符(尽管Java不支持直接作为类型参数约束的下限通配符)
这样可以确保类型兼容性并避免不必要的类型转换错误。
下限例子

class Info<T>{
    private T var ;        // 定义泛型变量
    public void setVar(T var){
        this.var = var ;
    }
    public T getVar(){
        return this.var ;
    }
    public String toString(){    // 直接打印
        return this.var.toString() ;
    }
}
public class GenericsDemo21{
    public static void main(String args[]){
        Info<String> i1 = new Info<String>() ;        // 声明String的泛型对象
        Info<Object> i2 = new Info<Object>() ;        // 声明Object的泛型对象
        i1.setVar("hello") ;
        i2.setVar(new Object()) ;
        fun(i1) ;
        fun(i2) ;
    }
    public static void fun(Info<? super String> temp){    // 只能接收String或Object类型的泛型,String类的父类只有Object类
        System.out.print(temp + ", ") ;
    }
}
加点难度的例子
例子一
class A{}
class B extends A {}

// 如下两个方法不会报错
public static void funA(A a) {
    // ...          
}
public static void funB(B b) {
    funA(b);
    // ...             
}

// 如下funD方法会报错
public static void funC(List<A> listA) {
    // ...          
}
public static void funD(List<B> listB) {
    funC(listB);          
}

为什么funD方法会报错呢?
如果你不了解泛型的上下限,这个问题很难作答.
即使B是A的子类型,List也并不是List的子类型.
所以我们不能直接将List传递给需要List的方法,这就是funD方法报错的原因.

那么如果我们想实现上面的情况,希望funD可以调用funC

public static void funC(List<? extends A> listA) {  
    // ...            
}

因为List是List<? extends A>的子类型(因为B是A的子类型).

例子二
private  <E extends Comparable<? super E>> E max(List<? extends E> e1) {
    if (e1 == null){
        return null;
    }
    //迭代器返回的元素属于 E 的某个子类型
    Iterator<? extends E> iterator = e1.iterator();
    E result = iterator.next();
    while (iterator.hasNext()){
        E next = iterator.next();
        if (next.compareTo(result) > 0){
            result = next;
        }
    }
    return result;
}

上述代码中类型参数是E的范围是<E extends Comparable<? super E>>
这个我们来分布解释一下:

  • <E extends Comparable<? super E>>
    这是一个泛型方法,它定义了一个类型参数E.
    这个类型参数E有一个约束,即它必须扩展(或实现)Comparable接口.而Comparable的类型参数是? super E,意味着它可以接受E或E的任意超类作为类型参数.
    这是为了确保compareTo方法可以接受当前对象或者其父类型的对象作为参数
  • List<? extends E> e1
    接受一个列表作为参数,这个列表包含的元素是E或E的任意子类型,表示要操作的数据是E的子类的列表,指定上限,这样容器才够大.
例子三
public class Client {
    //工资低于2500元的上班族并且站立的乘客车票打8折
    public static <T extends Staff & Passenger> void discount(T t){
        if(t.getSalary()<2500 && t.isStanding()){
            System.out.println("恭喜你!您的车票打八折!");
        }
    }
    public static void main(String[] args) {
        discount(new Me());
    }
}

代码中使用的泛型<T extends Staff & Passenger> 是一个有限定的类型参数.
这里的泛型声明表示T必须是一个类型,该类型同事是Staff接口和Passenger接口的子类型(或者是直接实现了这两个接口的类)

深入理解泛型

泛型这个特性是从JDK1.5才开始加入的,所以为了兼容之前的版本,Java的范型实现是采取的“伪泛型”
即虽然Java语法上支持泛型,但是编辑阶段会进行所谓的“类型擦除(Type Erasure)”,将所有的泛型表示(尖括号中的内容)都替换成具体的类型(其对应的原生态类型),所以看起来就像是没有泛型一样.

什么是泛型擦除后保留的原始类型

原始类型: 擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换
举个例子
Pair的范型情况:

class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
} 

Pair的原始类型为

class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}

因为Pair中,T是一个无限定的类型变量,所以用Object替换,其结果就是一个普通的类,如同泛型加入Java之前的已经实现的样子.
在程序中可以包含不同类型的Pair,如Pair或Pair,但是擦除类型后他们的就成为原始的Pair类型了,原始类型都是Object。

那如果类型变量有限定,那么原始类型就用第一个边界的类型变量类替换
如果Pair这样声明的话:
public class Pair<T extends Comparable> {}
那么原始类型就是Comparable

我们要分清原始类型喝泛型变量的类型

泛型类型擦除原则
  • 消除类型参数声明,即删除<>及其包围的部分
  • 根据类型参数的上下界推断并替换所有的类型参数为原生态类型:
  1. 类型参数是无限制通配符或没有上下界限定则替换为Object
  2. 存在上下界限则根据子类替换原则取类型参数的最左边限定类型(即父类)
  • 为了保证类型安全,必要时插入强制类型转换代码
  • 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”
如何进行擦除的?
  • 擦除类定义中的类型参数 - 无限制类型擦除
    当类定义中的类型参数没有任何限制的时候,在类型擦除中直接被替换为Object,即形如 和 <?>的类型参数都被替换为Object.
    在这里插入图片描述

  • 擦除类定义中的类型参数 - 有限制类型擦除
    当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界和下界,比如<T extends Number><? extends Number>的类型参数被替换为Number<? super Number> 被替换为Object.
    在这里插入图片描述

  • 擦除方法定义中的类型参数
    擦除方法定义中的类型参数原则和擦除类定义中的类型参数是一样的,这里仅以擦除方法定义中的有限制类型参数为例.
    在这里插入图片描述

怎么证明存在类型擦除呢

如果存在泛型类型擦除,那么我们可以从两个方面来证明

  • 如果存在类型擦除,则原始类型肯定相等
public class Test {

    public static void main(String[] args) {

        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");

        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);

        System.out.println(list1.getClass() == list2.getClass()); // true
    }
}

定义了两个不同泛型类型的ArrayList数组,我们通过比对类的信息,最后发现结果都为true,说明泛型类型String喝Integer都被擦除掉了,只剩下原始类型.

  • 通过反射添加其他类型元素
public class Test {

    public static void main(String[] args) throws Exception {

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

        list.add(1);  //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer

        list.getClass().getMethod("add", Object.class).invoke(list, "asd");

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

}

我们定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add方法,是只能存储整数数据
但我们利用反射调用add,却可以存储字符串,说明了Interger泛型实例中编译之后被擦除掉了,只保留了原始类型.

如何理解泛型的编译期检查

既然说类型变量在编译的时候擦除掉,那为什么像ArrayList创建的对象中添加整数会报错呢?
不是说泛型变量String在编译的时候会变为Object类型嘛?
既然类型擦除了,那为什么不能存别动类型,到底是如何保证我们只能使用泛型变量限定类型的呢?

答: Java编译器是通过先检查代码中泛型的类型,然后再进行类型擦除,再进行编译

举个例子:

public static  void main(String[] args) {  
    ArrayList<String> list = new ArrayList<String>();  
    list.add("123");  
    list.add(123);//编译错误  
}

上面代码中,用add添加一个整型会直接报错,说明这就是在编译之前进行检查(因为如果是编译之后检查,类型擦除后,原始类型为Object,时应该允许任意引用类型添加的).
那么这个类型检查时针对谁的呢?
我们再举个例子来看,先看一下参数话类型和原始类型的兼容.
以ArrayList为例子,以前的写法

ArrayList list = new ArrayList();  

现在的写法:

ArrayList<String> list = new ArrayList<String>(); 

我们兼容一下以前的代码,则会出现下面的情况:

ArrayList<String> list1 = new ArrayList(); //第一种 情况
ArrayList list2 = new ArrayList<String>(); //第二种 情况

第一种情况可以实现与完全使用泛型参数一样的效果,第二种情况则没有这个效果.
在这里插入图片描述

这是为什么呢?
因为类型检查中编译之前就完成了,new ArrayList()只是在内存中开辟了一个存储空间,可以存储任何类型对象.
所以真正涉及类型检查的是它的引用,因为我们是使用它的引用list1来调用它的方法,比如说所以list1引用能完成泛型类型的检查。而引用list2没有使用泛型,所以不行。

所以我们可以明白,类型检查就是针对引用的,谁是一个引用,这个引用调用泛型方法,就会对着干引用调用的方法进行类型检测,而无关它真正引用的对象.

泛型中参数化类型为什么不考虑继承关系

如果考虑继承的话,会有两种情况出现

ArrayList<String> list1 = new ArrayList<Object>(); //编译错误  
ArrayList<Object> list2 = new ArrayList<String>(); //编译错误
  • 第一种情况,我们扩展成下面的形式
ArrayList<Object> list1 = new ArrayList<Object>();  
list1.add(new Object());  
list1.add(new Object());  
ArrayList<String> list2 = list1; //编译错误

第四行代码出现编译错误
我们假设它编译没错,那么list2引用去使用get方法取之的时候,返回的都是String类型的对象(上面提到了,类型检测是根据引用来决定的),可是它里面实际上已经被我们存放了Object对象,那么就会抛出ClassCastException.
所以为了避免这种极易出现的错误,Java不允许进行这样的引用传递(这也是泛型出现的原因,就是为了解决类型转换的问题,我们不能违背它的初衷)

  • 第二种情况,我们拓展成下面的形式
ArrayList<String> list1 = new ArrayList<String>();  
list1.add(new String());  
list1.add(new String());
ArrayList<Object> list2 = list1; //编译错误

这样的情况比第一种情况好的多,最起码,在我们用list2取值的时候不会出现ClassCastException,因为是从String转换为Object。
可是,这样做是没有意义呢,泛型出现的原因,就是为了解决类型转换的问题。如果我们按上面这样做,我们使用了泛型,到头来,还是要自己强转,违背了泛型设计的初衷。
再说,如果用list2往里面add()新的对象,那么到时候取得时候,我怎么知道我取出来的到底是String类型的,还是Object类型的呢?
泛型中的引用传递的问题要额外注意,这就是泛型中引用传递的基本问题.

如何理解泛型的多态和桥接方法

明天更完

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

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

相关文章

腾讯云轻量服务器地域选择教程以及不同地域的区别

腾讯云服务器地域怎么选择&#xff1f;不同地域之间有什么区别&#xff1f;腾讯云哪个地域好&#xff1f;地域选择遵循就近原则&#xff0c;访客距离地域越近网络延迟越低&#xff0c;速度越快。腾讯云百科txybk.com告诉大家关于地域的选择还有很多因素&#xff0c;地域节点选择…

WPF —— TabControl、StackPanel 控件详解

1 TabControl简介 表示包含多个项的控件&#xff0c;这些项共享屏幕上的同一空间。 TabControl有助于最大程度地减少屏幕空间使用量&#xff0c;同时允许应用程序公开大量数据。 TabControl包含共享同一屏幕空间的多个 TabItem 对象。一次只能看到 TabControl 中的一个 Ta…

Oracle登录错误ERROR: ORA-01031: insufficient privileges解决办法

这个问题困扰了我三个星期&#xff0c;我在网上找的解决办法&#xff1a; 1.控制面板->管理工具->计算机管理->系统工具->本地用户和组->ORA_DBA组。 但我电脑上根本找不到。 2.在oracle安装目录下找到oradba.exe运行。 最开始我都不到这个oradba.exe文件在哪…

数字生活的未来:探索Web3的全新世界

随着科技的飞速发展&#xff0c;我们正迈向一个数字化的未来。而在这个数字化的时代&#xff0c;Web3技术的崛起正引领着我们进入一个全新的世界。本文将深入探讨Web3技术的特点以及它给我们带来的全新体验。 1. 去中心化的特点 Web3的去中心化是其最显著的特点之一&#xff0…

设置jmeter默认语言为中文

问题描述 通过面板上面的选项修改语言&#xff08;如下图&#xff09;&#xff0c;每次运行程序都需要重新再设置一遍&#xff0c;我需要每次打开都是中文界面 解决方案 进入jmeter的文件目录 bin——> jmeter.properties 打开这个文件 搜索Preferred GUI language在下方添…

linux环境基础开发工具1(vim 、 yum)

目录 前言 Linux编辑器-vim使用 Linux 软件包管理器 yum 前言 集成开发环境&#xff08;IDE&#xff0c;Integrated Development Environment &#xff09;是用于提供程序开发环境的应用程序&#xff0c;一般包括代码编辑器、编译器、调试器和图形用户界面等工具。集成了代码…

如何利用百度SEO优化技巧将排到首页

拥有一个成功的网站对于企业和个人来说是至关重要的&#xff0c;在当今数字化的时代。在互联网上获得高流量和优质的访问者可能并不是一件容易的事情&#xff0c;然而。一个成功的SEO战略可以帮助你实现这一目标。需要一些特定的技巧和策略、但要在百度搜索引擎中获得较高排名。…

爬虫3_爬取翻页URL不变的网站

之前实现了对大学排数据爬取&#xff1a;爬虫2_2019年549所中国大学排名. 近期复现代码&#xff0c;发现原网站升级&#xff0c;在翻页时&#xff0c;发现URL不改变&#xff0c;修改代码&#xff0c;使用网页自动化工具selenium实现对该类网站数据获取。 #-*- coding: UTF-8 -…

hadoop伪分布式环境搭建详解

&#xff08;操作系统是centos7&#xff09; 1.更改主机名&#xff0c;设置与ip 的映射关系 hostname //查看主机名 vim /etc/hostname //将里面的主机名更改为master vim /etc/hosts //将127.0.0.1后面的主机名更改为master&#xff0c;在后面加入一行IP地址与主机名之间的…

数字排列 - 华为OD统一考试(C卷)

OD统一考试&#xff08;C卷&#xff09; 分值&#xff1a; 200分 题解&#xff1a; Java / Python / C 题目描述 小明负责公司年会&#xff0c;想出一个趣味游戏: 屏幕给出 1−9 中任意 4 个不重复的数字,大家以最快时间给出这几个数字可拼成的数字从小到大排列位于第 n 位置…

稀碎从零算法笔记Day17-LeetCode:有效的括号

题型&#xff1a;栈 链接&#xff1a;20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述&#xff08;红字为笔者添加&#xff09; 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 …

CentOS无法解析部分网站(域名)

我正在安装helm软件&#xff0c;参考官方文档&#xff0c;要求下载 get-helm-3 这个文件。 但是我执行该条命令后&#xff0c;报错 连接被拒绝&#xff1a; curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 # curl: (7) Fai…

Spring Boot 集成 WebSocket 实例 | 前端持续打印远程日志文件更新内容(模拟 tail 命令)

这个是我在 CSDN 的第一百篇原则博文&#xff0c;留念&#x1f60e; #1 需求说明 先说下项目结构&#xff0c;后端基于 Spring Boot 3&#xff0c;前端为 node.js 开发的控制台程序。现在希望能够在前端模拟 tail 命令&#xff0c;持续输出后端的日志文件。 #2 技术方案 #2.…

手动创建线程池各个参数的意义?

今天我们学习线程池各个参数的含义&#xff0c;并重点掌握线程池中线程是在什么时机被创建和销毁的。 线程池的参数 首先&#xff0c;我们来看下线程池中各个参数的含义&#xff0c;如表所示线程池主要有 6 个参数&#xff0c;其中第 3 个参数由 keepAliveTime 时间单位组成。…

人工智能课题、模型源码

人工智能研究生毕业&#xff5e;深度学习、计算机视觉、时间序列预测&#xff08;LSTM、GRU、informer系列&#xff09;、python、人工智能项目代做和指导&#xff0c;各种opencv图像处理、图像分类模型&#xff08;vgg、resnet、mobilenet、efficientnet等&#xff09;、人脸检…

.NET高级面试指南专题十七【 策略模式模式介绍,允许在运行时选择算法的行为】

介绍&#xff1a; 策略模式是一种行为设计模式&#xff0c;它允许在运行时选择算法的行为。它定义了一系列算法&#xff0c;将每个算法封装到一个对象中&#xff0c;并使它们可以互相替换。这使得算法可独立于使用它的客户端变化。 原理&#xff1a; 策略接口&#xff08;Strat…

使用BBDown下载bilibili视频的方法

一款命令行式哔哩哔哩下载器. Bilibili Downloader. 下载地址 https://github.com/nilaoda/BBDown 功能 番剧下载(Web|TV|App) 课程下载(Web) 普通内容下载(Web|TV|App) 合集/列表/收藏夹/个人空间解析 多分P自动下载 选择指定分P进行下载 选择指定清晰度进行下载 下载外挂字幕…

基于springboot+vue实现电子商务平台管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现电子商务平台管理系统演示 研究的目的和意义 据我国IT行业发布的报告表明&#xff0c;近年来&#xff0c;我国互联网发展呈快速增长趋势&#xff0c;网民的数量已达8700万&#xff0c;逼近世界第一&#xff0c;并且随着宽带的实施及降价&#xff0c;每天约有…

实现微服务:匹配系统

HTTP与WebSocket协议 1. HTTP协议是无状态的&#xff0c;每次请求都是独立的&#xff0c;服务器不会保存客户端的状态信息。而WebSocket协议是有状态的&#xff0c;一旦建立连接后&#xff0c;服务器和客户端可以进行双向通信&#xff0c;并且可以保持连接状态&#xff0c;服务…

基于Java的海南旅游景点推荐系统(Vue.js+SpringBoot)

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四、核心代码4.1 随机景点推荐4.2 景点评价4.3 协同推荐算法4.4 网站登录4.5 查询景点美食 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的海南旅游推荐系统&#xff…