java进阶四-深入理解泛型和注解

泛型和注解是框架技术必备的技能

  • 5 泛型
    • 5.1泛型理解
      • 5.1.1 泛型概念
      • 5.1.2 泛型的特点
      • 5.1.3 如何理解Java中的泛型是伪泛型?
      • 5.1.4 泛型的价值
    • 5.2 泛型语法
      • 5.2.1 泛型类
      • 5.2.2 泛型接口
      • 3.2.3 泛型方法
      • 3.2.4泛型的上下边界
      • 3.2.5创建泛型数组
    • 5.3泛型应用场景
      • 5.3.1数据库操作组件封装
      • 5.3.2 数据库分页组件封装
      • 5.3.3 第三方中间件

5 泛型

5.1泛型理解

5.1.1 泛型概念

泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。

Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。本文综合多篇文章后,总结了Java 泛型的相关知识,希望可以提升你对Java中泛型的认知效率。

5.1.2 泛型的特点

泛型只在编译阶段有效。看下面的代码:

List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();if(classStringArrayList.equals(classIntegerArrayList)){Log.info("泛型测试,类型相同");
}

通过上面的例子可以证明,在编译之后程序会采取去泛型化的措施。也就是说Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

5.1.3 如何理解Java中的泛型是伪泛型?

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。理解类型擦除对于用好泛型是很有帮助的,尤其是一些看起来“疑难杂症”的问题,弄明白了类型擦除也就迎刃而解了。

擦除原则

  • 消除类型参数声明,即删除<>及其包围的部分。
  • 根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
  • 为了保证类型安全,必要时插入强制类型转换代码。
  • 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性

如何擦除类型?

(1) 无限制的转化为Object
当类定义中的类型参数没有任何限制时,在类型擦除中直接被替换为Object,即形如和<?>的类型参数都被替换为Object。
在这里插入图片描述

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

(3).擦除方法定义中的类型参数

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

如何证明被擦除了呢?

看测试代码

    @Testpublic void t1() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {ArrayList<Integer> list = new ArrayList<Integer>();list.add(1);  //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer// list.add("字符串");//编译器语法检查会报错,因为引用了泛型,无法编译通过//通过反射注入值,编译能通过list.getClass().getMethod("add", Object.class).invoke(list, "字符串");for (Object o:list){System.out.println(o);}}

在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型

5.1.4 泛型的价值

既然编译后会擦除泛型,那为什么又要使用泛型呢,不是没事找事吗,有以下原因:

(1)程序的健壮和安全性

以集合为例子,在没有泛型之前,从集合中读取到的每一个对象都必须进行类型转换,如果不小心插入了错误的类型对象,在运行时的转换处理就会出错。
有了泛型后,出现不符合预期的代码就会编译不通过。相当于告诉编译器每个集合接收的对象类型是什么,编译器在编译期就会做类型检查,告知是否插入了错误类型的对象,使得程序更加安全,增强了程序的健壮性。

(2)避免了不必要的装箱、拆箱操作,提高程序的性能

以集合为例子,在没有泛型之前,从集合中读取到的每一个对象都必须进行类型强制转换,大量的开箱拆箱工作将会降低代码性能。

(3)避免重复代码,提升程序优雅性

当我们为处理不同的对象,必须增加不同方法或者类时,用泛型可以避免这些,必须当你需要创建一个通用的数据结构,例如列表、栈、队列、字典等,这些结构可以处理各种类型的数据时,可以使用泛型类,用泛型代替Object

5.2 泛型语法

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

5.2.1 泛型类

泛型类:把泛型定义在类上
语法:把类体里面要用的泛型类型,在类后声明,可以1个或者多个,泛型的名字无限制

public class 类名 <泛型类型1,…> { }

注意事项:

  • 泛型类型必须是引用类型(非基本数据类型)
  • 定义泛型类,在类名后添加一对尖括号,并在尖括号中填写类型参数,参数可以有多个,多个参数使用逗号分隔:
  • 参数名称可以任意
    当然,这个后面的参数类型也是有规范的,通常类型参数我们都使用大写的单个字母表示:

实例1:单个泛型

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

使用泛型类

 @Testpublic void t2()  {Pair<String> pair = new Pair<>();pair.setValue("www");String r = pair.getValue();Pair<Integer> pair1=new Pair<>();Integer i=pair1.getValue();}

实例2:多个泛型

public class MoreGenerics <k,v>{private k id;private v name;public k getId() {return id;}public void setId(k id) {this.id = id;}public v getName() {return name;}public void setName(v name) {this.name = name;}
}

使用

 @Testpublic void t4(){MoreGenerics<String,String> mg=new MoreGenerics<>();mg.setId("1");mg.setName("jzk");MoreGenerics<Integer,String> mg2=new MoreGenerics<>();Integer id=mg2.getId();}

5.2.2 泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中

public interface 接口名 <泛型类型1,…> { }

例如

public interface GenericeServeice<T> {public T getKey();
}

实现接口的类,有三种方式

  • 指定具体类型:就是在实现接口时,明确指定泛型参数的具体类型;
  • 保留泛型参数:在实现接口时,不明确指定泛型参数的具体类型,而是保留泛型参数。
  • 保留泛型参数:并增加新的泛型类型

注意语法

class A implements GenericeServeice<String>{}//指定具体类型
class B<T> implements GenericeServeice<T>{}//保留泛型
class  C<K,T> implements GenericeServeice<T>{}//保留并新增泛型

接口

ublic interface GenericeServeice<T> {public T getKey();
}

实现类

public class GenericeServeiceImp {//1.就是在实现接口时,明确指定泛型参数的具体类型;注意  A  implements B <具体类型>{}class A implements GenericeServeice<String>{@Overridepublic String getKey() {   // T getKey() T 用具体类型 String代替return "大太阳";}}//2.在实现接口时,不明确指定泛型参数的具体类型,而是保留泛型参数class B<T> implements GenericeServeice<T>{private T id;@Overridepublic T getKey() {return id;}public void  setId(T id){this.id=id;}}//3.继承了接口的泛型参数,并新增泛型class  C<K,T> implements GenericeServeice<T>{private T id;private K name;@Overridepublic T getKey() {return id;}public void setName(K name){this.name=name;}public K getName(){return name;}public void setKey(T id){this.id=id;}}@Testpublic void t1(){A a=new A();String key=a.getKey();B<Integer> b=new B<>();b.setId(3);Integer id=b.getKey();C<String,Integer> c=new C<>();c.setName("奎哥");c.setKey(3);C<Number,Integer> c1=new C<>();c1.setName(34);c1.setKey(3);System.out.println("A.getKey()="+key);System.out.println("B.getKey()="+id);}}

3.2.3 泛型方法

修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }

注意事项:

  • 泛型方法必须标注在方法修饰符和返回值之间<泛型变量…>,和对应类是否时泛型无关,彼此独立

泛型方法声明:

public class GeneralM {/***方法无返回值,入参是一个泛型参数*/public <T> void  m(T t){System.out.println(t.getClass().getName());}/***方法无入参,返回值是一个泛型变量*/public <T> T m1(){T t=null;return t;}/***入参和返回值都是一个泛型变量*/public <T> T m2(T t){return t;}/***声明多个泛型*/public <K,V> void m3(K k,V v){System.out.println(k.getClass().getName());System.out.println(v.getClass().getName());}/***Class<T>这个表示泛型T的具体类型是Class*/public <T> T getObj(Class<T> c) throws InstantiationException, IllegalAccessException {T t= c.newInstance();return t;}/***写一个函数把数组转化为List*/public <T> List<T> toList(T[] arrs){List<T> list=new ArrayList<>();for (T arr : arrs) {list.add(arr);}return list;}}

泛型方法使用

 @Testpublic void t5() throws InstantiationException, IllegalAccessException {GeneralM gm=new GeneralM();//对应  public <T> void  m(T t){}System.out.println("对应  public <T> void  m(T t){}=============");gm.m("d");gm.m(new Pair());gm.m(1);//对应  public <T> T m1(){}System.out.println("对应 public <T> T m1(){}=============");String ds= gm.m1();Integer i=gm.m1();//对应   public <T> T m2(T t){}System.out.println("对应 public <T> T m2(T t){}=============");Integer i1=gm.m2(3);String s=gm.m2("d");//对应 public <K,V> void m3(K k,V v){}System.out.println("对应 public <K,V> void m3(K k,V v){}=============");gm.m3(12, "大哥");//对应 public <T> T getObj(Class<T> c)System.out.println("对应 public <T> T getObj(Class<T> c)=============");VO vo= gm.getObj(VO.class);vo.setId(1);vo.setName("dd");System.out.println(vo);//对应  public <T> List<T> toList(T[] arrs)System.out.println("对应  public <T> List<T> toList(T[] arrs)");Integer[] arr1={1,2,3};List<Integer> list=gm.toList(arr1);String[] arr2={"1","2","3"};List<String> list2=gm.toList(arr2);//List<Integer> list3=gm.toList(arr2);//编译报错}

注意:泛型方法使用,并没有泛型类或者接口用<>确定类型,和正常的方法一样,因为方法不会实例化

案例详解

在这里插入图片描述
在这里插入图片描述

案例说明:

  • 定义泛型方法时,必须在返回值前边加一个,来声明这是一个泛型方法,持有一个泛型T,然后才可以用泛型T作为方法的返回值。

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

  • 泛型方法要求的参数是Class类型,而Class.forName()方法的返回值也是Class,因此可以用Class.forName()作为参数。其中,forName()方法中的参数是何种类型,返回的Class就是何种类型。在本例中,forName()方法中传入的是User类的完整路径,因此返回的是Class类型的对象,因此调用泛型方法时,变量c的类型就是Class,因此泛型方法中的泛型T就被指明为User,因此变量obj的类型为User。

当然,泛型方法不是仅仅可以有一个参数Class,可以根据需要添加其他参数。

我们再看解析json的中间件Gson的源码,加深理解:
Gson
在这里插入图片描述

泛型类和泛型方法往往都在一起,容易让初学者混淆,查看实例

public class GenericTest {//这个类是个泛型类,在上面已经介绍过public class Generic<T>{     private T key;public Generic(T key) {this.key = key;}//我想说的其实是这个,虽然在方法中使用了泛型,但是这并不是一个泛型方法。//这只是类中一个普通的成员方法,只不过他的返回值是在声明泛型类已经声明过的泛型。//所以在这个方法中才可以继续使用 T 这个泛型。public T getKey(){return key;}/*** 这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"* 因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。public E setKey(E key){this.key = keu}*/}/** * 这才是一个真正的泛型方法。* 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T* 这个T可以出现在这个泛型方法的任意位置.* 泛型的数量也可以为任意多个 *    如:public <T,K> K showKeyName(Generic<T> container){*        ...*        }*/public <T> T showKeyName(Generic<T> container){System.out.println("container key :" + container.getKey());//当然这个例子举的不太合适,只是为了说明泛型方法的特性。T test = container.getKey();return test;}//这也不是一个泛型方法,这就是一个普通的方法,只是使用了Generic<Number>这个泛型类做形参而已。public void showKeyValue1(Generic<Number> obj){Log.d("泛型测试","key value is " + obj.getKey());}//这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?//同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类public void showKeyValue2(Generic<?> obj){Log.d("泛型测试","key value is " + obj.getKey());}/*** 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "* 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。* 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。public <T> T showKeyName(Generic<E> container){...}  *//*** 这个方法也是有问题的,编译器会为我们提示错误信息:"UnKnown class 'T' "* 对于编译器来说T这个类型并未项目中声明过,因此编译也不知道该如何编译这个类。* 所以这也不是一个正确的泛型方法声明。public void showkey(T genericObj){}*/public static void main(String[] args) {}
}

泛型方法有什么优势

泛型类必须要在实例化对象时指明具体的泛型的替代类型,不同的化必须实例化一个新对象如:

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

但泛型方法就更为简单,不需要再实例化对象,也不需要专门用<>声明数据类型,更为灵活,如上面例子提到的:

       //对应  public <T> List<T> toList(T[] arrs)System.out.println("对应  public <T> List<T> toList(T[] arrs)");Integer[] arr1={1,2,3};List<Integer> list=gm.toList(arr1);String[] arr2={"1","2","3"};List<String> list2=gm.toList(arr2);//List<Integer> list3=gm.toList(arr2);//编译报错

3.2.4泛型的上下边界

  • 上限

在使用泛型的时候,我们可以为传入的泛型类型实参进行上下边界的限制,如:类型实参只准传入某种类型的父类或某种类型的子类。

语法 :泛型变量 extends 具体类型

测试代码:这里泛型参数T只能是Number及其子类

public class Info <T extends  Number>{private  T id;public Info(T id){this.id=id;}public static void main(String[] args) {Info<Integer> info1=new Info<>(12); //okInfo<Float> info2=new Info<>(12.45f); //ok// Info<String> info3=new Info<>("123"); //编译出错}
}
  • 下限
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 + ", ") ;}
}

小结

<?> 无限制通配符
<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类// 使用原则《Effictive Java》
// 为了获得最大限度的灵活性,要在表示 生产者或者消费者 的输入参数上使用通配符,使用的规则就是:生产者有上限、消费者有下限
1. 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;
2. 如果它表示一个 T 的消费者,就使用 < ? super T>3. 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。

通配符 ?

通配符?表示任意的,一般和泛型的上下界限制一起搭配使用

3.2.5创建泛型数组

看代码

		List<String>[] list11 = new ArrayList<String>[10]; //编译错误,非法创建 List<String>[] list12 = new ArrayList<?>[10]; //编译错误,需要强转类型 List<String>[] list13 = (List<String>[]) new ArrayList<?>[10]; //OK,但是会有警告 List<?>[] list14 = new ArrayList<String>[10]; //编译错误,非法创建 List<?>[] list15 = new ArrayList<?>[10]; //OK List<String>[] list6 = new ArrayList[10]; //OK,但是会有警告

上面都不是创建泛型数组的最佳方式

我们在使用到泛型数组的场景下应该尽量使用列表集合替换,此外也可以通过使用 java.lang.reflect.Array.newInstance(Class componentType, int length) 方法来创建一个具有指定类型和维度的数组,如下

public class ArrayWithTypeToken<T> {private T[] array;public ArrayWithTypeToken(Class<T> type, int size) {array = (T[]) Array.newInstance(type, size);}public void put(int index, T item) {array[index] = item;}public T get(int index) {return array[index];}public T[] create() {return array;}
}
//...ArrayWithTypeToken<Integer> arrayToken = new ArrayWithTypeToken<Integer>(Integer.class, 100);
Integer[] array = arrayToken.create();

所以使用反射来初始化泛型数组算是优雅实现,因为泛型类型 T在运行时才能被确定下来,我们能创建泛型数组也必然是在 Java 运行时想办法,而运行时能起作用的技术最好的就是反射了。

5.3泛型应用场景

泛型最到的特征是在定义类时并不指定类里的具体参数,这样就可以把一些共性抽象出来,在泛型之前,我们只能把具体参数对象抽象成Object类,在使用时再强制转化成具体对象,但其健壮性和安全性存在一定问题,所以泛型在做通用基础组件里得到广泛的应用,在实际业务场景中,我们在结合类的反射,这样就能形成通用的封装类,我们查看很多三方组件,都有大量的泛型应用。

5.3.1数据库操作组件封装

BaseDao定义了基本的数据库增删查改, 之后可以继承该泛型类,实现各自的增删查改,或者使用超类的增删查改,同时每个继承类还能增加自己的操作:

思路:
利用类得反射原理,把传入对象的字段属性和值都读出来,动态生成SQL语句,为简单我们假设类名和属性名称和数据库表与字段一一对应

示意代码

public class BaseDAO <T>{//数据库连接信息private String dbURL="";public void save(T t){Class cls=t.getClass();//利用反射原理获得T的属性和值,动态生成SQL语句}public T getByKey(Integer id,Class<T> c) {try{T o=c.newInstance();//把sql查询值利用反射机制注入objSystem.out.println("执行getByKey()");return o;}catch (Exception e){throw  new RuntimeException("映射出错");}}
}

继承基类

public class StudentDAO extends BaseDAO<Student>{
}
public class OrderDAO extends BaseDAO<Order>{
}

使用

 @Testpublic void t9()  {StudentDAO sdao=new StudentDAO();sdao.save(new Student());Student st=sdao.getByKey(1, Student.class);OrderDAO odao=new OrderDAO ();odao.save(new Order());Student st=sdao.getByKey(1, Order.class);}

5.3.2 数据库分页组件封装

在使用java对数据库操作时候,很常见的一个功能分页操作,java接收的常常是一个count和相应的记录列表,然后,一般的定bean的方法如下:

vo类 如:order

@Data
public class Order {private Integer id;private String orderCoder;private Integer goodId;private Integer buyNum;
}

分页类:
一般包含分页的公共数据和当前记录数据

@Data
public class PageSpitOrder {//分页通用数据private Integer pageNum;//页数private Integer curPage;//当前页private Integer pageSize;//每页条数private List<Order> list;//当前当前页数据
}

如果我们再有商品分页,最简单的方法再定义一个PageSpitGood的封装类,这样肯定就不通用了,泛型以前我们会一定义一个List类来存储当前页数据,在使用类里在强转到具体对象。
用泛型就优雅得多:
代码示意图

public class PageSpitComm<T> {//分页通用数据private Integer pageNum;//页数private Integer curPage;//当前页private Integer pageSize;//每页条数private List<T> list;//当前当前页数据,使用T类型public List<T> getList(){return list;}
}

使用代码

 @Testpublic void t8(){PageSpitComm<Order> pg1=new PageSpitComm<>();List<Order> list1=pg1.getList();PageSpitComm<VO> pg2=new PageSpitComm<>();List<VO> list2=pg2.getList();}

5.3.3 第三方中间件

Gson.fromJson,原理解析json字符串,反映射注入类中
在这里插入图片描述

mybatis中间件
在这里插入图片描述
BaseMapper源码
在这里插入图片描述

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

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

相关文章

(七)独立按键

文章目录 独立按键原理图三行代码法简单概述代码书写键码推算如何使用短按键长按键 状态机法简单概述代码书写键码推算如何使用短按键长按键 现象 独立按键原理图 三行代码法 简单概述 代码书写 u8 Trg 0x00;//短按键 u8 Cont 0x00;//长按键 void BtnThree(void) {u8 reada…

CMake入门教程【核心篇】安装(install)

&#x1f608;「CSDN主页」&#xff1a;传送门 &#x1f608;「Bilibil首页」&#xff1a;传送门 &#x1f608;「本文的内容」&#xff1a;CMake入门教程 &#x1f608;「动动你的小手」&#xff1a;点赞&#x1f44d;收藏⭐️评论&#x1f4dd; 文章目录 1. 概述2. 使用方法2…

如何从零开始搭建公司自动化测试框架?

一、为什么要搭建自动化测试框架 测试如果按照是否手工划分&#xff0c;可以分为“手工测试”和“自动化测试”。 “手工测试”也就是用人力来进行功能测试。相比自动化测试而言执行效率慢&#xff0c;可以进行探索性测试和发散性测试。 “自动化测试”主要是通过所开发的软…

Python控制程控电源(USB)

文章目录 前言一、环境搭建1.软件安装2.硬件安装二、设置程控电源连接方式三、Python代码四、验证结果五、pyd文件前言 随着智能电动汽车行业的持续发展,汽车电子或嵌入式设备在软硬件的测试中,都会使用程控电源供电,特别是自动化测试、压力测试场景必定使用到程控电源控制…

nifi详细介绍--一款开箱即用、功能强大可靠,可用于处理和分发数据的大数据组件

目录 目录 一、引言 二、NiFi 的历史背景介绍 三、NiFi 是什么&#xff1f; 核心特性 应用领域 四、NIFI 入门 五 、NiFi 工作流程 六、实际应用场景 七、优势总结 一、引言 NiFi&#xff08;Apache NiFi&#xff09;&#xff0c;全名为“Niagara Files”&#xff0…

StratifiedGroupKFold解释和代码实现

StratifiedGroupKFold解释和代码实现 文章目录 一、StratifiedGroupKFold解释和代码实现是什么&#xff1f;二、 实验数据设置2.1 实验数据生成代码2.2 代码结果 三、实验代码3.1 实验代码3.2 实验结果3.3 结果解释 四、样本类别类别不平衡 一、StratifiedGroupKFold解释和代码…

Redis:原理速成+项目实战——初识Redis、Redis的安装及启动、Redis客户端

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;首期文章 &#x1f4da;订阅专栏&#xff1a;Redis速成 希望文章对你们有所帮助 在此之前&#xff0c;我做过的项目里面也用到了…

利用深度学习图像识别技术实现教室人数识别

引言 在现代教育环境中&#xff0c;高效管理和监控教室成为了一个重要议题。随着人工智能技术的迅猛发展&#xff0c;特别是深度学习和图像识别领域的突破&#xff0c;我们现在可以通过智能系统来自动识别教室内的人数&#xff0c;从而实现更加智能化的教室管理。 深度学习与图…

LeetCode 84. 柱状图中最大的矩形

84. 柱状图中最大的矩形 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 示例 1: 输入&#xff1a;heights [2,1,5,6,2,3] 输出&#xff1a;10 解释…

Spring-IOC综述

文章迁移自语雀。 怎么查看spring的文档 ioc综述 说到spring的ioc,其实就是控制反转,为啥需要控制反转呢,其实是为了功能的增强,如果不用spring, 我们直接使用工厂方法,静态工厂方法, 都是是可以获取到对象的,但是如果需求变了,我们在类的生成时,添加了很多信息,使用工厂就不…

【鸿蒙杂谈①】——鸿蒙基础介绍及应用领域

1.前言 小伙伴们大家好&#xff0c;最近被复习整的痛苦无比&#xff0c;所以今天咱们了解 一点轻松的东西&#xff0c;至于高并发就先放放吧。好了&#xff0c;废话不多说&#xff0c;咱们进入正题。 相信小伙伴们都已经看到了最近鸿蒙的势头了&#xff0c;那鸿蒙究竟是怎么发…

《Linux C编程实战》笔记:实现自己的myshell

ok&#xff0c;考完试成功复活 这次是自己的shell命令程序的示例 流程图&#xff1a; 关键函数 1.void print_prompt() 函数说明&#xff1a;这个函数打印myshell提示符&#xff0c;即“myshell$$”. 2.void get_input(char *buf) 函数说明&#xff1a;获得一条指令&#…

Vue3-32-路由-重定向路由

什么是重定向 路由的重定向 &#xff1a;将匹配到的路由 【替换】 为另一个路由。 redirect : 重定向的关键字。 重定向的特点 1、重定向是路由的直接替换,路由的地址是直接改变的&#xff1b; 2、在没有子路由配置的情况下&#xff0c;重定向的路由可以省略 component 属性的配…

Langchain访问OpenAI ChatGPT API Account deactivated的另类方法,访问跳板机API

笔者曾经写过 ChatGPT OpenAI API请求限制 尝试解决 Account deactivated. Please contact us through our help center at help.openai.com if you need assistance. 结果如何&#xff1f; 没有啥用。目前发现一条曲线救国的方案。 1. 在官方 openai 库中使用 此处为最新Op…

全国计算机等级考试| 二级Python | 真题及解析(10)

一、选择题 1.要实现将实数型变量a的值保留三位小数,以下python可以实现的是( ) A.a%0.001 B.a//0.001 C.round(a,3) D.round(3,a) 2.在Python中要交换变量a和b中的值,应使用的语句组是( )。 A…

思科校园网搭建及配置综合小型实验

思科校园网搭建及配置综合小型实验 实验拓扑配置步骤配置聚合链路配置VTP&#xff0c;vlan域模板第一步 配置二层VLAN第二步 配置生成树第三步 配置相关IP地址第四步 配置DHCP及DHCP中继第五步 配置三层的网关冗余协议 双机热备及OSPF第六步 配置静态路由,NAT地址转换及其他配置…

麒麟云增加计算节点

一、安装基座系统并配置好各项设置 追加的计算节点服务器&#xff0c;安装好系统&#xff0c;把主机名、网络网线&#xff08;网线要和其他网线插的位置一样&#xff09;、hosts这些配置好&#xff0c;在所有节点的/etc/hosts里面添加信息 在控制节点添加/kylincloud/multinod…

人工智能趋势报告解读:ai野蛮式生长的背后是机遇还是危机?

近期&#xff0c;Enterprise WordPress发布了生成式人工智能在营销中的应用程度的报告&#xff0c;这是一个人工智能迅猛发展的时代&#xff0c;目前人工智能已经广泛运用到内容创作等领域&#xff0c;可以预见的是人工智能及其扩展应用还将延伸到我们工作与生活中的方方面面。…

springboot自动配置原理

第一步启动类注解 第二步可以看到启动类注解组合了自动配置这个注解&#xff08;enableAutoConfiguration&#xff09; 第三步进入这个注解 可以看到里面导入了一个impotSelector这个自动配置的字节码 第四步点进去 可以看到实现了deferredImportSelector这个接口 并且在这个类…

Rockchip平台Android应用预安装功能(基于Android13)

Rockchip平台Android应用预安装功能(基于Android13) 1. 预安装应用类型 Android上的应用预安装功能&#xff0c;主要是指配置产品时&#xff0c;根据厂商要求&#xff0c;将事先准备好的第三方应用预置进Android系统。预安装分为以下几种类型&#xff1a; 安装不可卸载应用安…