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 class StudentDAO extends BaseDAO<Student>{
}
public class OrderDAO extends BaseDAO<Order>{
}

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.rhkb.cn/news/229820.html

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

相关文章

C++八股学习心得.3

1.C 数组 C 支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素&#xff0c;最高的地址对应最后一个…

dvwa问题篇 -- dvwa出现数据库无法访问的时候,Could not connect to the MySQL service. -- 小黑解决教程

各位小伙伴初次玩dvwa会出现各种问题&#xff0c;本来想把一些问题直接总结写一篇dvwa文章来着&#xff0c;但因为都是关键字搜索&#xff0c;所以将一些问题都拆分出来&#xff0c;以便大家方便查类似问题。&#xff08;大家有遇到不一样的问题欢迎投稿&#xff01;&#xff0…

文件夹变0字节文件数据恢复方法

对于许多电脑用户来说&#xff0c;在使用电脑的过程中&#xff0c;经常会遇到一个令人头疼的问题&#xff1a;执行文件夹变0字节文件操作&#xff0c;导致数据丢失。这是一个相当普遍的现象&#xff0c;但遗憾的是&#xff0c;目前相对于其他类型的数据丢失&#xff0c;如删除或…

python c语言 代码动态检查,python c语言语法分析

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;python c语言 代码动态检查&#xff0c;python c语言语法分析&#xff0c;今天让我们一起来看看吧&#xff01; Source code download: 本文相关源码 初学编程&#xff0c;应该学习哪一门编程语言&#xff0c;有不少人感…

OS 7--DNS配置+Apache发布网站

环境准备 centOS 7 1.配置DNS 1.1 域名为lianxi.com 1.2 为WWW服务器、FTP服务器、NEWS服务器做域名解析 1)安装DNS yum -y install bind bind-utils (如果安装不上&#xff0c;就把磁盘在重洗挂载一下&#xff09; 2&#xff09;修改DNS配置文件 vim /etc/resolv.conf…

gookit/color - Go语言命令行色彩使用库教程

gookit/color - Go语言命令行色彩使用库教程 1.安装2.基础颜色(16-color)3.256色彩/RGB风格 1.安装 go get github.com/gookit/color2.基础颜色(16-color) 提供通用的API方法&#xff1a;Print Printf Println Sprint Sprintf 1、例如&#xff1a; color.Yellow.Println(&q…

计算机网络【EPOLL 源码详解】

IO多路复用 在以前&#xff0c;传统的网络编程是多线程模型&#xff0c;一个线程单独处理一个请求。 然而&#xff0c;线程是很昂贵的资源&#xff1a; 线程的创建和销毁成本很高&#xff0c;linux的线程实际上是特殊的进程&#xff1b;因此通常会使用线程池来减少线程创建和…

邮件群发称呼怎么写?写群发邮件开头技巧?

如何写外贸邮件群发称呼&#xff1f;外贸群发邮件开头怎么称呼&#xff1f; 邮件群发已成为企业、个人和组织之间沟通的重要手段。而一个恰当的称呼&#xff0c;不仅能够展现出礼貌和尊重&#xff0c;还能够拉近彼此的距离。那么&#xff0c;如何写好邮件群发的称呼呢&#xf…

Swagger 教程:从零开始学习Swagger

Swagger 是一个开源的 API 设计和文档工具&#xff0c;可以帮助全栈工程师更快、更简单地设计、构建、文档化和测试 RESTful API。本篇文章将为全栈工程师介绍 Swagger 的基础知识和使用方法&#xff0c;以及如何使用 Swagger 设计、文档化和测试 RESTful API。 一、Swagger 简…

关键字:new关键字

在 Java 中&#xff0c;new关键字用于创建对象实例。它是对象创建的语法糖&#xff0c;用于分配内存空间并调用构造函数来初始化对象。 以下是new关键字的基本语法&#xff1a; 在上述语法中&#xff0c;ObjectType是要创建对象的类名&#xff0c;objectName是对象的引用变量…

Allins 官网上线,标志铭文赛道正式进入 AMM 交易时代

“Allins 正在通过全新的 AMM 方案为BRC20及多链铭文资产拓展 DeFi 场景&#xff0c;官网的全新上线意味着铭文资产的交易正式进入 AMM 时代。”

HW06 GAN来生成卡通人物头像- simple

simple -GAN 理论 生成对抗网络(GAN)是最近十年来比较火爆的技术之一,被誉为21世纪最有趣的创想。GAN作为生成网络,自然是可以生成诸多形式的数据,这些数据甚至是现实世界中不曾存在的。例如,最近非常火爆的换脸技术,或者是称为DeepFake,就是GAN的杰作(当然不可能是…

Bert-vits2最终版Bert-vits2-2.3云端训练和推理(Colab免费GPU算力平台)

对于深度学习初学者来说&#xff0c;JupyterNoteBook的脚本运行形式显然更加友好&#xff0c;依托Python语言的跨平台特性&#xff0c;JupyterNoteBook既可以在本地线下环境运行&#xff0c;也可以在线上服务器上运行。GoogleColab作为免费GPU算力平台的执牛耳者&#xff0c;更…

ExecutorCompletionService详解

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 微信公众号&#xff1a;Java随想录 文章目录 摘要ExecutorCompletionService适用场景ExecutorCompletionService使用ExecutorCompletionService原理解析注意事项总结 摘要 ExecutorCompletionService 是Jav…

Java原生启动Tomcat

文章目录 引入依赖启动Tomcat代码示例将嵌入式 Tomcat 服务器用于已有的 WAR 文件为现有的 Java Web 应用程序嵌入 Tomcat 服务器 相关APITomcat APIContonxt API 启动错误springboot底层Tomcat的实现学习博客 引入依赖 maven: <dependency><groupId>org.apache.…

如何在无公网IP环境使用Windows远程桌面Ubuntu

文章目录 一、 同个局域网内远程桌面Ubuntu二、使用Windows远程桌面连接三、公网环境系统远程桌面Ubuntu1. 注册cpolar账号并安装2. 创建隧道&#xff0c;映射3389端口3. Windows远程桌面Ubuntu 四、 配置固定公网地址远程Ubuntu1. 保留固定TCP地址2. 配置固定的TCP地址3. 使用…

48道Linux面试题

本博客将汇总 Linux 面试中常见的题目&#xff0c;并提供详细的解答。 文章目录 1、绝对路径用什么[符号表](https://so.csdn.net/so/search?q符号表&spm1001.2101.3001.7020)示&#xff1f;当前目录、上层目录用什么表示&#xff1f;主目录用什么表示? 切换目录用什么命…

DolphinScheduler实际应用

前言 最近公司新启动了一个项目&#xff0c;然后领导想用一下新技术&#xff0c;并且为公司提供多个大数据调度解决方案&#xff0c;我呢就根据领导要求调研了下当前的开源调度工具&#xff0c;最终决定采用DolphinScheduler&#xff0c; 因此研究了一下DolphinScheduler &…

php安装扩展event 提示 No package ‘openssl‘ found 解决方法

在使用pecl编译安装最新版event模块的时候提示 No package openssl found , 可是本机是安装了openssl的, 编译时找不到, 大概率就是环境配置的问题了, 增加 OPENSSL_CFLAGS OPENSSL_LIBS环境变量即可解决. 异常提示信息: checking for openssl > 1.0.2... no configure: …

阿里云服务器端口PPTP 1723放行教程

阿里云服务器安装PPTP VPN需要先开通1723端口&#xff0c;阿里云服务器端口是在安全组中操作的&#xff0c;阿里云服务器网aliyunfuwuqi.com来详细说明阿里云服务器安全组开放PPTP VPN专用1723端口教程&#xff1a; 阿里云服务器放行1723端口教程 PPTP是点对点隧道协议&#…