Java基础 :反射、注解、代理、线程池、依赖的学习和理解

2023-11-01

高新技术的重要性

这里的高新技术指的是Java基础中的知识,比如:反射、注解、代理、线程池、依赖注入等等。

市面上的开源框架大多都是使用了这些Java基础的知识去实现的,掌握了这些Java基础的知识,能帮助我们更好的理解一些好的开源框架的实现原理。

反射

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

说白了就是在Android中给我们提供了一个android.jar可以调用android的api,但是还有一部分api的方法是没有暴露出来的,那么如果我们想要调用这些方法,就需要通过反射来调用。

 

用处:

l  在运行时判断任意一个对象所属的类;

Ø  obj instanceof Object => obj.getClass().equals(Object.class)

l  在运行时构造任意一个类的对象;

l  在运行时判断任意一个类所具有的成员变量和方法;

l  在运行时调用任意一个对象的方法;

l  生成动态代理。

注意:

一般如果不是必要情况下,尽量不要使用反射,反射会影响app的性能。

获取Class对象

万事万物皆对象,每个类中都具有成员变量,构造方法,成员方法,所以可以使用Class类来表示每个类,每个类是Class类的实例对象。

Class的实例对象是各个类在内存中的那份字节码文件。

基本数据类型,String,void,数组,引用类型都存在类类型Class。

Class常见方法

l  newInstance(); 创建实例对象

l  getName(); 获取类的名字,带包名的

l  getSimpleName(); 获取不带包名的类名

l  getMethod(方法名,方法参数的类类型); 获取指定公有的方法

l  getDeclaredMethod(方法名,方法参数的类类型); 获取所有的指定的方法

l  getMethods(); 获取所有公有地方法

l  getDelcaredMethods(); 获取类上面所有的方法

l  getFields(); 获取所有公有的成员变量

l  getField("成员变量的名称"); 通过成员变量的名字获取公有的成员变量

l  getDeclaredField("成员变量的名称"); 通过成员变量的名字获取成员变量

l  getDeclaredFields(); 获取所有的成员变量

l  getConstructor("","","");

l  getConstructors();

l  getDeclaredConstructor(parameterTypes);

获取Class文件的三种方式

获取Class文件的三种方式:

1.    类名.class;

2.    对象名.getClass();

3.    Class.forName(“类的包名+类的名字”);

 

/**
 *
获取Class的三种方式:
 *       类名.class
 *       对象.getClass()
 *       Class.forName("类名")
 */
public class GetClass {
   
public static void main(String[] args)throws Exception {
       
//方式1:Class.forName("类名")
        Class clazz1= Class.forName("com.fuyuan.example.bean.People");
       
System.out.println("Class.forName()方式 :"+ clazz1);

       
//第二种:对象.getClass()方法
        People people=newPeople();
       
Class clazz2 = people.getClass();
       
System.out.println("对象.getClass()方式 :"+ clazz2 + ", "  + (clazz1 == clazz2));

       
//第三种:类名.class方法
        Class clazz3 = People.class;
       
System.out.println("类名.class方式 :"+ clazz3 + ", "  + (clazz1 == clazz3));
   
}
}

 

运行结果:

动态加载类和静态加载类

静态加载:在编译时期加载的类,叫静态加载。

动态加载:在运行时期加载的类,叫动态加载。

 

简单来说:

在代码中写死的,直接new出来的类,就属于是静态加载;

运行期间通过配置信息来动态获取相关类的实例,就属于动态加载。

 

动态加载的优点:提高程序的可扩展性

动态加载范例

现有Excel、Word、PPT 3个类,他们都实现了Office接口,重写了Office接口的startWork方法。

现在通过动态加载的方式,使用相应的类去执行相关的操作。

 

接口Office,包含一个startWork方法:Office.java

public interface Office {
    public void startWork();
  }

 

Office的实现类Excel:

public class Excel implements Office{
    @Override
    public void startWork() {
        System.out.println("Excel start work......");
    }
}

 

Office的实现类Word:

public class Word implements Office{
    @Override
    public void startWork() {
        System.out.println("Word start work......");
    }
}

 

写一个测试类,演示动态加载的使用场景:DynamicLoad.java

/**
 * 动态加载
 */
  public class DynamicLoad {
  
    public static void main(String[] args) {
        String worker = "com.fuyuan.example.bean." + "Excel";
        startWord(worker);
    }
  
    public static void startWord(String worker) {
        try {
            // 1. 获取字节码文件
            Class clazz = Class.forName(worker);
            // 2. 获取类的对象
            Office office = (Office) clazz.newInstance();
            // 3. 调用相关方法
            office.startWork();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  }

打印类中方法的信息

Class中用于获取方法的API为:

l  getMethod(方法名,方法参数的类类型); 获取指定公有的方法

l  getDeclaredMethod(方法名,方法参数的类类型); 获取所有的指定的方法

l  getMethods(); 获取所有公有地方法

l  getDelcaredMethods(); 获取类上面所有的方法

 

Method的常见API:

l  method.getReturnType(); 获取返回值的类类型

l  method.getName(); 获取方法的名字

l  method.getParameterTypes(); 获取所有参数的类类型

 

需求:给定一个对象,打印出这个对象身上所有的方法的返回值、方法名和参数

/**
    *
打印方法的信息,包括:  返回值  方法名  参数
    */
   public static void printMethodMessage(Object object){
      String temp=
"";

     
//1.获取字节码文件
     
Class c=object.getClass();
     
//2.获取类上面的方法
     
/**
       *
获取一个方法
       *
参数1:方法的名字
       * 参数2:方法参数的类类型
       */
//    c.getMethod(name, parameterTypes);

      //
获取所有的公有地方法
     
Method[] methods = c.getMethods();

     
//获取类上面的所有方法
//    c.getDeclaredMethod(name, parameterTypes)
//    c.getDeclaredMethods();

     
for (Method method : methods) {
        
//返回值的类类型
        
Class<?> returnType = method.getReturnType();
        
temp+=returnType.getName()+"  ";

        
//获取方法的名字
        
String name=method.getName();
         
temp+=name+"(";

        
//获取参数
        
Class<?>[] parameterTypes = method.getParameterTypes();
         for
(Class<?> class1 : parameterTypes) {
            String parameterName=class1.getName()
;
           
temp+=parameterName+",";
        
}
         temp+=
")";

           
// 打印方法信息
        
System.out.println(temp);
        
temp="";
     
}
   }

 

调用此方法打印String身上的所有方法信息,运行结果:

打印类中成员变量的信息

Class中用于获取成员变量的API为:

l  getFields(); 获取所有公有的成员变量

l  getField("成员变量的名称"); 通过成员变量的名字获取公有的成员变量

l  getDeclaredFields (); 获取所有的成员变量(包括private的)

l  getDeclaredFields("成员变量的名称"); 通过成员变量的名字获取成员变量

 

Field的常见API:

l  getName(); 获取成员变量的名称

l  getType(); 返回成员变量的类类型

 

需求:给定一个对象,打印出这个对象身上所有的成员变量名称和类类型

/**
    *
打印类中成员变量的信息
    */
  
public static void printFieldMessage(Object obj){
     
//1.获取字节码文件
     
Class c1=obj.getClass();

     
//2.拿到字节码文件中所有的变量
      //
获取到所有的公有的成员变量
      Field[] fields = c1.getFields();

       
// 获取到指定公有的成员变量
//    c1.getField("
成员变量的名称");

        // 获取到指定的成员变量
//    c1.getDeclaredField("成员变量的名称");

        //可以获取到所有的成员变量
//    c1.getDeclaredFields();

      for (Field field : fields) {
        
//3.获取成员变量的名称
        
String name=field.getName();
        
//4.获取成员变量的类型
        
Class typeClass=field.getType();
        
System.out.println(typeClass+"  "+name+";");
     
}
   }

 

调用此方法打印int身上所有的公有的成员变量的信息,运行结果:

打印类中的构造方法的信息

可变参数

l  可变参数的出现解决了一个方法接受的参数个数不固定的问题;

l  可变参数只能出现在参数列表的最后;

l  ...位于变量类型和变量名之间,前后有无空格都可以;

l  调用可变参数的方法时,编译器为该可变参数隐含创建了一个数组;

l  在方法体中,可以以数组的形式访问可变参数;

 

可变参数使用范例:

/**
 *
可变参数
 */
public class ChangeArgs {
   
public static void main(String[] args) {
        System.
out.println(add(2,5));
       
System.out.println(add(2,5,9,7));
   
}

   
/**
     *
可变参数的使用(可变参数实质就是数组的形式)
     */
   
public static int add(int... args){
       
int sum=0;
        for
(inti=0;i<args.length;i++) {
            sum+=args[i]
;
       
}
       
return sum;
   
}
}

打印构造方法的信息

Class中用于获取构造方法的API为:

l  getConstructor("","",""); 获取指定公有的构造方法,参数是可变参数

l  getConstructors();  获取所有的公有构造方法

l  getDeclaredConstructor(parameterTypes); 获取指定的构造方法

l  getDeclaredConstructors();      获取所有的构造方法

 

Constructor的常见API:

l  getName(); 获取构造方法的名字

l  getParameterTypes(); 获取所有参数的类类型

 

需求:给定一个对象,打印出这个对象身上所有的构造方法的名称和参数类型

/**
    * 打印构造方法信息
    */
   public static void printConstructorMessage(Object obj){
      String temp="";
  
      //1.获取字节码文件
      Class c1=obj.getClass();
  
      //2.获取构造方法
  
      //参数是可变参数,可以理解为数组,代表的是构造方法的参数的类类型
      //获取指定的构造方法
//    c1.getConstructor("","","");
  
      //获取所有公有的构造方法
//    c1.getConstructors();
  
      //获取指定的构造方法
//    c1.getDeclaredConstructor(parameterTypes);
  
      //获取所有构造方法
      Constructor[] constructors = c1.getDeclaredConstructors();
  
      for (Constructor constructor : constructors) {
         //获取构造方法的名称
         String name=constructor.getName();
         temp+=name+"(";
            
         //获取构造的参数的类类型
         Class[] parameterTypes = constructor.getParameterTypes();
         for (Class class1 : parameterTypes) {
            //获取构造方法的参数的类类型的名字
            String paramName=class1.getName();
            temp+=paramName+",";
         }
         temp+=")";
         System.out.println(temp);
         temp="";
      }
   }

 

调用此方法打印String身上所有的公有的成员变量的信息,运行结果:

方法和成员变量的反射

测试用Number类:Number.java

public class Number {
  
   private static String num="number start...";
  
   public void add(int a,int b){
      System.out.println(a+b);
   }
  
   public static void printNum(){
      System.out.println(num);
   }
}

通过反射调用某个类的方法

通过反射调用某个类的方法的步骤:

  1. 获取Class字节码
  2. 获取构造方法
  3. 通过构造方法创建对象
  4. 获取方法(可以是私有的)
  5. 方法设置Accessible为true(暴力反射)
  6. 调用invoke方法

 

/**
 *  通过反射调用方法
 */
  public static void reflectionMethod() throws Exception {
    // 1. 获取字节码文件
    Class clazz = Number.class;
  
    // 2. 获取类的对象
    Number number = (Number) clazz.newInstance();
  
    // 3. 获取方法
    Method method = clazz.getDeclaredMethod("add", int.class, int.class);
  
    // 4. 设置方法访问权限(暴力反射)
    method.setAccessible(true);
  
    // 5. 执行方法
    method.invoke(number, 2, 6);
  }

通过反射给某个类的成员变量赋值

通过反射给某个类的成员变量赋值的步骤:

  1. 获取Class字节码
  2. 获取构造方法
  3. 通过构造方法创建对象
  4. 获取成员变量(可以是私有的)
  5. 方法设置Accessible为true(暴力反射)
  6. 调用Field.set(对象,参数值) 方法赋值

 

/**
 *  通过反射为成员变量赋值
 */
  public static void reflectionField() throws Exception {
    // 1. 获取字节码文件
    Class clazz = Number.class;
  
    // 2. 获取类的对象
    Number number = (Number) clazz.newInstance();
  
    // 调用方法打印赋值前的成员变量的值
    System.out.print("修改前:");
    number.printNum();
  
    // 3. 获取成员变量
    Field field = clazz.getDeclaredField("num");
  
    // 4. 设置成员变量的访问权限(暴力反射)
    field.setAccessible(true);
  
    // 5. 为成员变量赋值
    field.set(number, "我是被修改后的Number值");
  
    // 调用方法打印赋值后的成员变量的值,看是否修改成功
    System.out.print("修改后:");
    number.printNum();
  }

 

方法执行结果:

注解

Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。

Annotation(注解)是JDK5.0及以后版本引入的。它可以用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。从某些方面看,Annotation就像修饰符一样被使用,并应用于包、类型、构造方法、方法、成员变量、参数、本地变量的声明中。

Annotation的行为十分类似public、final这样的修饰符。每个Annotation具有一个名字和成员(成员的个数>=0)。每个Annotation的成员具有被称为name=value对的名字和值(就像javabean一样),name=value装载了Annotation的信息。也就是说注解中可以不存在成员。

 

使用注解的基本规则:

Annotation不能影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。

  

Annotation类型:

Annotation类型定义了Annotation的名字、类型、成员默认值。一个Annotation类型可以说是一个特殊的java接口,它的成员变量是受限制的,而声明Annotation类型时需要使用新语法。当我们通过java反射api访问Annotation时,返回值将是一个实现了该 annotation类型接口的对象,通过访问这个对象我们能方便的访问到其Annotation成员。

 

简而言之:

l  一个注解就是一个类,使用注解,就相当于创建了一个类的实例对象。

l  注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记。

l  Java编译器、开发工具或者其他程序可以用反射来了解你的类及各种元素上有无何种标记,通过不同的标记,就去干相应的事。

l  标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

 

注解是一个特殊的类,他的格式同接口,只是在接口前加了”@“

 

注解的分类

根据注解参数的个数,我们可以将注解分为三类:

1.标记注解:一个没有成员定义的Annotation类型被称为标记注解,@Override;

2.单值注解

3.完整注解  

 

根据注解使用方法和用途,我们可以将Annotation分为三类:

       1.系统注解

       2.元注解

       3.自定义注解

系统注解

@Override  

@Override 用来表示实现或者重写接口或父类中的方法。

@Override 是一个标记注解类型,它被用作标注方法。它说明了被标注的方法重载了父类的方法,起到了断言的作用。如果我们使用了这种Annotation在一个没有覆盖父类方法的方法时,java编译器将以一个编译错误来警示。这个annotaton常常在我们试图覆盖父类方法而确又写错了方法名时发挥威力。使用方法极其简单:在使用此annotation时只要在被修饰的方法前面加上@Override即可。

@Deprecated

@Deprecated用来标记已过时的类型或者类型成员。

@Deprecated也是一个标记注解。当一个类型或者类型成员使用@Deprecated修饰的话,编译器将不鼓励使用这个被标注的程序元素。而且这种修饰具有一定的“延续性”:如果我们在代码中通过继承或者覆盖的方式使用了这个过时的类型或者成员,虽然继承或者覆盖后的类型或者成员并不是被声明为@Deprecated,但编译器仍然要报警。

@SuppressWarnings

@SuppressWarnings是用来警告用户的,它用于通知java编译器禁止特定的编译警告。

@SuppressWarnings被用于有选择的关闭编译器对类、方法、成员变量、变量初始化的警告。

元注解

元注解就是注解的注解,它的作用就是负责注解其他注解。

Java5.0定义了4个标准的元注解类型,它们被用来提供对其它注解类型作说明。

 

@Target

作用:

用于描述注解的使用范围,即被描述的注解可以用在什么地方

 

取值(ElementType):

1.CONSTRUCTOR:用于描述构造器

2.FIELD:用于描述域

3.LOCAL_VARIABLE:用于描述局部变量

4.METHOD:用于描述方法

5.PACKAGE:用于描述包

6.PARAMETER:用于描述参数

7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

 

使用范例:

/**
 * Created by Fuyuan on 2016/6/15.
 *
 * @Target
用于表示注解应用的范围
 * ElementType
中包含方法、接口、包、注解、类、构造方法等等
 *
 * 这里指定自定义注解TestAnnotation只能被声明在方法上和成员变量上
 */
@Target({ElementType.METHOD, ElementType.FIELD})
public @interfaceTestTargetAnnotation {

}

@Retention

作用:

@Retention 用于说明注解的保留期,表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

 

取值(RetentionPoicy):

l  SOURCE:在源文件中有效(即源文件保留)

l  CLASS:在class文件中有效(即class保留,在加载到JVM虚拟机时丢弃

l  RUNTIME:在运行时有效(即运行时保留,此时可以通过反射获得定义在某个类上的所有注解

 

默认值在Class阶段

@Override在SOURCE阶段(给编译器看的)

@SuppressWarning在SOURCE阶段(给编译器看)

@Deprecated在RUNTIME阶段

 

使用范例:

/**
 * Created by Fuyuan on 2016/6/15.
 *
 * 
注解的生命周期分为三个阶段:
 *        java
源文件、class文件、内存中的字节码
 * 对应的@Retention元注解:
 *        RetentionPolicy.SOURCE、RetentionPolicy.CLASS、RetentionPolicy.RUNTIME
 *
 * 默认值在Class阶段
 * @Override在SOURCE阶段(给编译器看的)
 
* @SuppressWarning在SOURCE阶段(给编译器看)
 
* @Deprecated在RUNTIME阶段(调进内存后扫描二进制码来查看方法,所以是RUNTIME
 *
 *
这里指定自定义注解的声明生命为运行时保留
 */
@Retention(RetentionPolicy.RUNTIME)
public @interfaceTestRetentionAnnotation {

}

@Documented

@Documented 注解用于生成文档的时候,带有@Documented的注解会被显示在文档中。

@Inherited

@Inherited 表示父类的注解可以被子类继承, 前提是Retention必须是RUNTIME的。

 

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。

如果一个使用了@Inherited修饰的注解类型被用于一个class,则这个注解将被用于该class的子类。

 

注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

 

当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

 

简而言之:

       @Inherited表示父类的注解可以被子类继承,但是这个可被继承的前提是注解的生命周期是运行时注解,且@Inherited代表的是子类可以继承父类类级别的注解,父类的方法如果被子类重写,子类不继承父类方法上的注解。

 

范例:

定义一个注解,指明直接是@Inherited的:TestInheritedAnnotation.java

/**
 * Created by Fuyuan on 2016/6/15.
 *
 * @Inherited
表示父类的注解可以被子类继承,
 */
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interfaceTestInheritedAnnotation {
    String
value();
}

 

创建一个父类,在父类的方法、抽象方法、类上都使用了自定义的@Inherited的注解:Parent.java

@TestInheritedAnnotation("I'm parent")
public abstract class Parent {

   
@TestInheritedAnnotation("I'm parent method1")
   
public void method1() {
        System.
out.println(" Parent method1......");
   
}

   
@TestInheritedAnnotation("I'm parent method2")
   
public void method2() {
        System.
out.println("Parent method2......");
   
}

   
@TestInheritedAnnotation("I'm parent absMethod")
   
public abstract void absMethod();
}

 

定义一个子类,继承Parent,实现其抽象方法absMethod,重写其普通方法method1:Child.java

public classChild extendsParent{
   
@Override
   
public void method1() {
       
super.method1();
   
}

   
@Override
   
public void absMethod() {
        System.
out.println("子类实现抽象父类的抽象方法absMethod");
   
}
}

 

写一个测试方法,判断Child上面的各个方法和类上是否有我们的自定义注解@ TestInheritedAnnotation:

/**
 * 
测试@Inherited注解
 */
private static void testInheritedAnnotation()throws NoSuchMethodException {
   
// 1. 获取子类的class
   
Class clazz=Child.class;

   
//2. 获取子类重写的父类抽象方法
  
 Method method = clazz.getMethod("absMethod");
   
// 3. 判断子类实现的父类的抽象方法是否继承了注解
   
if(method.isAnnotationPresent(TestInheritedAnnotation.class)){
       
TestInheritedAnnotation inheritedAnnotation = method.getAnnotation(TestInheritedAnnotation.class);
       
System.out.println("子类实现的抽象方法继承到父类抽象方法中的Annotation,其信息如下:");
       
System.out.println(inheritedAnnotation.value());
   
}else{
        System.
out.println("子类实现的抽象方法没有继承到父类抽象方法中的Annotation");
   
}

   
// 4. 判断子类重写父类的方法
   
Method methodOverride = clazz.getMethod("method1");
    if(methodOverride.isAnnotationPresent(TestInheritedAnnotation.class)){
       
TestInheritedAnnotation inheritedAnnotation = methodOverride.getAnnotation(TestInheritedAnnotation.class);
       
System.out.println("子类method1方法继承到父类method1方法中的Annotation,其信息如下:");
       
System.out.println(inheritedAnnotation.value());
   
}else{
        System.
out.println("子类method1方法没有继承到父类method1方法中的Annotation");
   
}

   
// 5. 判断子类不重写父类的方法
  
 Method methodParent = clazz.getMethod("method2");
    if(methodParent.isAnnotationPresent(TestInheritedAnnotation.class)){
       
TestInheritedAnnotation inheritedAnnotation = methodParent.getAnnotation(TestInheritedAnnotation.class);
       
System.out.println("子类method2方法继承到父类method2方法中的Annotation,其信息如下:");
       
System.out.println(inheritedAnnotation.value());
   
}else{
        System.
out.println("子类method2方法没有继承到父类method2方法中的Annotation");
   
}

   
// 6. 判断子类继承自父类的类上的注解
 
  if(Child.class.isAnnotationPresent(TestInheritedAnnotation.class)){
       
TestInheritedAnnotation annotation = (TestInheritedAnnotation) clazz.getAnnotation(TestInheritedAnnotation.class);
       
System.out.println("子类继承到父类类上Annotation,其信息如下:");
       
System.out.println(annotation.value());
   
}else{
        System.
out.println("子类没有继承到父类类上Annotation");
   
}
}

 

打印日志:

 

去掉@ TestInheritedAnnotation中的@Inherited标记,打印日志:

 

结论:

       对于方法上的注解,加不加@Inherited没有影响;但是对于类上的注解,加了@Inherited的,父类的注解会被子类继承。

自定义注解

注解是一个特殊的类,他的格式同接口,只是在接口前加了”@“

定义注解格式:

  public @interface 注解名 {定义体}

 

注解参数的可支持数据类型:

l  所有基本数据类型(int,float,boolean,byte,double,char,long,short)

l  String类型

l  Class类型

l  enum类型

l  Annotation类型

l  以上所有类型的数组

 

注意:

l  只能用public或默认(default)这两个访问权修饰。

例如:String value();这里把方法设为defaul默认类型;

l  参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组。

例如:String value();这里的参数成员就为String;  

l  如果只有一个参数成员, 方法名需要定义成value,后加小括号。

 

简而言之:

l  当只有一个变量的时候,方法名最好定义成value。

原因:

       假如方法名定义成value,那么用的时候可以直接@注解(“value值”);

假如方法名定义的其他的,比如name(),那么用的时候必须写成key-value形式:@注解(name = “value值”)

l  当多个的时候可以随便定义。

l  注解可以有默认值。

 

自定义注解范例:@CustomAnnotation

/**
 * 自定义注解
 *
 * 假如方法名定义成value,那么用的时候可以直接@注解(“value值”);
 * 假如方法名定义的其他的,比如name(),那么用的时候必须写成key -value形式:@注解(name = “value值”)
 */
  public @interface CustomAnnotation {
    /** 假如注解中只有一个属性,建议名字定义成value */
  //    String value();
  
    /** 为注解的属性定义一个默认值 */
    String name() default "zhangsan";
  
    int age();
  }

 

使用范例:

@CustomAnnotation(age = 25)
  private String custom = "hello";

 

注解的处理器

l  <T extends Annotation> T getAnnotation(Class<T>annotationClass): 返回改程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null。

l  Annotation[] getAnnotations():返回该程序元素上存在的所有注解。

l  boolean is AnnotationPresent(Class<?extends Annotation>annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.

l  Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组)。该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

 

使用注解定义网络框架

我们在发送网络请求的时候,需要知道请求是GET请求还是POST请求,请求的URL是什么。这里我们通过一个简单的自定义网络框架来看一下注解是如何被应用的。

 

定义注解,用于标识网络访问的请求方式:RequestMethod.java

/**
 * 网络请求方法: GET or POST
 */
  @Target(ElementType.METHOD) // 限制使用位置:方法体
  @Retention(RetentionPolicy.RUNTIME) // 生命周期:运行时
  @Documented // 显示在文档中
  public @interface RequestMethod {
  
    /** 定义枚举类,限制网络请求的方法仅POST和GET这2种 */
    enum Method{GET, POST}
  
    /** 默认请求方式为GET请求 */
    Method value() default Method.GET;
  }

 

定义注解,用于标识网络访问的URL地址:RequestURL.java

/**
 * 网络请求要访问的URL地址
 */
  @Target(ElementType.METHOD) // 限制使用位置:方法体
  @Retention(RetentionPolicy.RUNTIME) // 生命周期:运行时
  @Documented // 显示在文档中
  public @interface RequestURL {
  
    /** 默认的访问网络的URL地址为"" */
    String value() default "";
  }

 

写一个简单的网络框架,用于解析注解,并发送网络请求,并返回请求的结果:HttpUtil.java

/**
 * 网络请求的工具类
 */
  public class HttpUtil {
    /**
     * 解析并执行网络请求
     * @param object 发起网络请求的类
     */
    public static String parseRequest(Object object) throws IOException {
        // 网络请求返回的结果
        String result = "";
  
        // 1. 获取Class
        Class clazz = object.getClass();
  
        // 2. 获取全部的公有方法
        Method[] methods = clazz.getMethods();
  
        // 3. 遍历所有的方法,寻找哪个方法身上有@RequestURL和@RequestMethod
        for (Method method : methods) {
            // 4. 先判断是否有@RequestURL,获取要访问的URL地址;
            // 若获取不到URL地址,不需要再往后解析了
            if (method.isAnnotationPresent(RequestURL.class)) {
                // 获取URL地址
                RequestURL annotationUrl = method.getAnnotation(RequestURL.class);
                String url = annotationUrl.value();
                // 如果URL地址为空,那么也不需要再往后解析了
                if (!TextUtils.isEmpty(url)) {
                   // 5. 判断是否有@RequestMethod,获取请求方法
                    if (method.isAnnotationPresent(RequestMethod.class)) {
                        // 获取访问的方法
                        RequestMethod annotationMethod = method.getAnnotation(RequestMethod.class);
                        RequestMethod.Method requestMethod = annotationMethod.value();
                        if (requestMethod.equals(RequestMethod.Method.GET)) {
                            // 发送GET请求
                            result = get(url);
                        } else {
                            // 发送POST请求
                            result = post(url);
                        }
                    }
                }
            }
        }
        return result;
    }
  
    /**
     *  发送GET请求
     * @param url 要请求的URL地址
     */
    public static String get(String url) throws IOException {
        // 借助okHttp发送网络请求
        Request request = new Request.Builder().url(url).build();
        Call call = new OkHttpClient().newCall(request);
        // 发送同步请求
        Response response = call.execute();
        return  response.body().string();
    }
  
    /**
     *  发送POST请求
     * @param url 要请求的URL地址
     */
    public static String post(String url) throws IOException {
        RequestBody requestBody = new FormEncodingBuilder().add("key", "value").build();
        // 借助okHttp发送网络请求
        Request request = new Request.Builder().post(requestBody).url(url).build();
        Call call = new OkHttpClient().newCall(request);
        // 发送同步请求
        Response response = call.execute();
        return  response.body().string();
    }
}

 

写一个测试Activity测试我们的框架是否好用:MainActivity.java

public class MainActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        loadNet();
    }
  
      @RequestMethod(RequestMethod.Method.GET)
    @RequestURL("http://192.168.191.1:8080/testjson.json")
    public void loadNet() {
        // 开启线程访问网络
        new Thread(){
            @Override
            public void run() {
                try {
                    String request = HttpUtil.parseRequest(MainActivity.this);
                    Log.e("MainActivity", "========网络请求返回结果========:" + request);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
  }

 

执行结果:

 

Xutils injectView实现原理

使用场景:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
      @ViewInject(id = R.id.button1, clickable = true)
    private Button button1;
    @ViewInject(id = R.id.button2)
    private Button button2;
      ……
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        AnnotateUtils.injectView(this);
    }
……
}

 

public static void injectViews(Object object, View sourceView){
    Field[] fields = object.getClass().getDeclaredFields();
    for (Field field : fields){
        ViewInject viewInject = field.getAnnotation(ViewInject.class);
        if(viewInject != null){
            int viewId = viewInject.id();
            boolean clickable = viewInject.clickable();
            if(viewId != -1){
                try {
                    field.setAccessible(true);
                    field.set(object, sourceView.findViewById(viewId));
                    if(clickable == true){
                        sourceView.findViewById(viewId).setOnClickListener((View.OnClickListener) (object));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

 

动态代理

代理根据运行和编译时期,分为静态代理和动态代理。

 

如果编译时存在的话是静态代理,所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。 

 

如果运行时存在的则是动态代理,动态代理之所以称为动态,是因为代理类是在运行时由Proxy类产生的,这就大大减少了需要我们手工设计代理类的数量。

动态代理的应用场景

AOP即Aspect orientedprogram,面向切面的编程

 

系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面

例如:

     StudentService类用于处理学生信息

     CourseService类用于处理课程信息

     MiscService类用于处理教室信息

这三个类都有安全、事务、日志的功能,这三个功能贯穿到好多个模块中,所以,它们就是交叉业务。

 

交叉业务的编程问题即为面向方面的编程,AOP的目标就是要使交叉业务模块化。

    

所谓模块化,就是将这些交叉业务只写一份,应用到所有需要的地方,而不是每个需要的地方都写一份。这就需要使用代理技术,代理技术是实现AOP功能的核心和关键。

 

要为系统中的各种接口的类增加代理功能,如果全部采用静态代理方式,写成百上千的代理类,不符合实际。

JVM可以在运行期动态的生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。

JVM生成的动态类必须实现了一个或者多个接口,以便让JVM知道他都实现了什么方法,需要为这些方法来生成代理。所以,JVM生成的动态类只能用于具有相同接口的目标类的代理。

 

 

如何实现动态代理

步骤:

1.      定义接口

2.      定义委托类,委托类需要实现接口

3.      实现InvocationHandler接口,定义委托类和代理类的桥梁

4.      生成代理类Proxy.newProxyInstance();

5.      调用代理类的方法

 

定义接口StudentInterface.java

public interface StudentInterface {
    public void study();
    public void play();
    public void sleep();
  }

 

定义委托类Student,委托类需要实现接口StudentInterface:

/**
 * 委托类
 */
  public class Student implements StudentInterface {
  
    @Override
    public void study() {
        System.out.println("==== Student ==== study ====");
    }
  
    @Override
    public void play() {
        System.out.println("==== Student ==== play ====");
    }
  
    @Override
    public void sleep() {
        System.out.println("==== Student ==== sleep ====");
    }
}

 

实现InvocationHandler接口,定义委托类和代理类的桥梁:StudentProxyHandler.java

/**
 * 委托类Student和代理类的桥梁
 */
  public class StudentProxyHandler implements InvocationHandler {
  
    /** 委托类对象 */
    private Object targetObj;
  
    public StudentProxyHandler(Object targetObj) {
        this.targetObj = targetObj;
    }
  
    /**
     * @param proxy  代理类的对象
     * @param method 要执行的方法
     * @param args 要执行的方法的参数
     * @return 方法执行后的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 代理类可以过滤方法,必须我们控制play方法不被执行
        if (method.getName().equals("play")) {
            System.out.println("====StudentProxy 拦截了 play方法 ==========");
            return null;
        }
  
        // 代理类可以增强方法,必须在执行方法前打印Log信息
        System.out.println("====打印了一条log,代理类有方法被执行了 ==========");
  
        // 执行委托类的对应方法
        Object result = method.invoke(targetObj, args);
        return result;
    }
}

 

通过Proxy.newProxyInstance();生成代理类对象:

// 创建委托类对象
  Student student = new Student();
  // 生成代理类对象
  StudentInterface proxyInstance = (StudentInterface) Proxy.newProxyInstance(StudentInterface.class.getClassLoader(),
        new Class[]{StudentInterface.class}, new StudentProxyHandler(student));

 

调用代理类的方法:

// 调用代理类的方法
  proxyInstance.study();
  proxyInstance.sleep();
  proxyInstance.play();
 

 

执行结果:

线程池

线程池的优点

l  避免线程的创建和销毁带来的性能开销

l  避免大量的线程间因互相抢占系统资源导致的阻塞现象

l  能够对线程进行简单的管理并提供定时执行、间隔执行等功能

 

注意:

线程池在使用的时候最好搞成静态的,因为线程池比较消耗资源。

线程池的概念

Java里面线程池的顶级接口是Executor,不过真正的线程池接口是 ExecutorService, ExecutorService 的默认实现是 ThreadPoolExecutor;普通类 Executors 里面调用的就是 ThreadPoolExecutor。

 

Executors提供四种线程池:

l  newCachedThreadPool

newCachedThreadPool 得到的是一个可根据需要创建新线程的线程池

特点:

Ø  如果有缓存的线程可用,优先用缓存的;如果没现有线程可用,则创建一个新的线程加入到池中。

Ø  终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。

 

l  newSingleThreadExecutor

newSingleThreadExecutor 创建是一个单线程池,也就是该线程池只有一个线程在工作,所有的任务是串行执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行

 

l  newFixedThreadPool

newFixedThreadPool创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

 

l  newScheduledThreadPool

newScheduledThreadPool 创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求

 

线程池相关构造参数含义

/**
 * corePoolSize:
 *      线程池的核心线程数,一般情况下不管有没有任务都会一直在线程池中一直存活,
 *      只有在 ThreadPoolExecutor 中的方法 allowCoreThreadTimeOut(boolean value) 设置为 true 时,
 *      闲置的核心线程会存在超时机制,如果在指定时间没有新任务来时,核心线程也会被终止,
 *      而这个时间间隔由 keepAliveTime 属性指定。
 *
 * maximumPoolSize:
 *      线程池所能容纳的最大线程数,当活动的线程数达到这个值后,后续的新任务将会被阻塞。
 *
 * keepAliveTime:
 *      控制线程闲置时的超时时长,超过则终止该线程。
 *
 * unit:
 *      用于指定 keepAliveTime 参数的时间单位。
 *
 * workQueue:
 *      线程池的任务队列,通过线程池的 execute(Runnable command) 方法会将任务 Runnable 存储在队列中。
 *
 * threadFactory:
 *      线程工厂,它是一个接口,用来为线程池创建新线程的
 */
ThreadPoolExecutor executor = new ThreadPoolExecutor(3,//核心线程数
        5, //最大线程数
        5, //线程空闲时间存活时间
        TimeUnit.SECONDS, //存活时间的单位
        new LinkedBlockingQueue<Runnable>(),   //任务队列
        Executors.defaultThreadFactory());//线程产生的工厂

ThreadPoolExecutor 提供了两个方法,用于线程池的关闭:线程池关闭的API

l  shutdown()

不会立即的终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。

l  shutdownNow()

立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。

线程池的工作原理

当任务来了之后,如果核心线程数没有满,那么就使用核心线程数,如果核心线程数满了,那么就将任务放入任务队列中,如果任务队列也满了,就开始使用最大线程数,如果最大线程数也使用满了,会抛出异常,拒绝任务。

依赖注入

什么是依赖

如果在 Class A 中,有Class B 的实例,则称 Class A 对 ClassB 有一个依赖。

 

存在依赖的例子:

/**
 * Human对Father有一个依赖
 */
  public class Human {
    // Human中使用了Father的实例,产生了对Father依赖
    private Father father;
    /**
     *  主动初始化依赖,耦合严重
     */
    public Human() {
        father = new Father();
    }
}

 

存在的问题:

l  如果现在要改变 father 生成方式,如需要用new Father(String name)初始化 father,需要修改 Human 代码;

l  如果想测试不同 Father 对象对 Human 的影响会变得很困难,因为 father 的初始化被写死在了 Human 的构造函数中;

l  如果new Father()过程非常缓慢,单测时我们希望用已经初始化好的 father 对象也很困难。

 

问题产生原因:

       两个类不够独立,耦合严重。

 

解决方案:由外界来提供依赖的实例

/**
 * Human对Father有一个依赖
 */
  public class Human {
  
    private Father father;
  
    /**
     * 由外界来注入来传入依赖,解耦
     */
    public Human(Father father) {
        this.father = father;
    }
}

 

类似这种非自己主动初始化依赖,而通过外部来传入依赖的方式,我们就称为依赖注入。

什么是依赖注入

依赖注入的目的是为了使类与类之间解耦合,提高系统的可扩展性和可维护性。

Java中一般都是通过注解 + 反射的方式来实现依赖注入的。

 

依赖注入示例:

       通过依赖注入,为类的成员变量赋值。

 

定义注解类,用于生命成员变量的值:StringAnnotation.java

/**
 * 自定义注解,用于为成员变量赋值
 */
  @Target(ElementType.FIELD) // 作用范围:成员变量
  @Retention(RetentionPolicy.RUNTIME) // 生命周期:运行时
  @Documented // 在文档中显示
  public @interface StringAnnotation {
    String value();
  }

 

定义解析注解并为成员变量赋值的注入工具类:ParseAnnocation.java

/**
 *  注入工具类
 */
  public class ParseAnnocation {
   public static void parseAnnocation(Object object) throws Exception{
      //1.获取字节码文件
      Class c=object.getClass();
      //2.获取成员变量
      Field[] fields = c.getFields();
      //3.遍历成员变量
      for (Field field : fields) {
            // 4. 找到带有StringAnnotation注解的成员变量
         if(field.isAnnotationPresent(StringAnnotation.class)){
                StringAnnotation annotation = field.getAnnotation(StringAnnotation.class);
            //5. 获取注解中的值
            String value=annotation.value();
                // 6. 为成员变量赋值
            field.set(object, value);
         }
      }
   }
}

 

使用注解 + 注入工具类,测试注入结果:

/**
 * 测试依赖注入
 */
  public class Dependency {
  
    @StringAnnotation("张三")
    public String name = "猜我是谁";
  
    public static void main(String[] args) throws Exception {
        Dependency dependency = new Dependency();
        // 解析注解,为成员变量赋值
        ParseAnnocation.parseAnnocation(dependency);
        // 打印注入后的值
        System.out.println("======Dependency name =======" + dependency.name);
    }
}

 

运行结果:

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java基础 :反射、注解、代理、线程池、依赖的学习和理解 的相关文章

  • Spring部署期间依赖注入问题

    我正在启动一个 Primefaces Spring Hibernate 项目 并且仍在学习如何正确处理这些组件 但就在此时 我面临着一个与 spring 依赖注入相关的问题 这让我很害怕 我已经在网上寻找答案两天了 但找不到我的代码有什么问
  • 在 PLSQL Oracle 中抛出特定错误消息...在休眠中捕获?

    是否可以在 PL SQL oracle 存储过程中抛出特定的错误消息 并在调用它时在 Hibernate 中捕获它 您可以从 PL SQL 代码中抛出用户定义的错误消息 20000 到 20999 之间的错误代码保留用于用户指定的错误消息
  • 如何替换引号之间出现的任何单词

    我需要能够替换所有出现的单词 and 仅当它出现在单引号之间时 例如 将字符串中的 and 替换为 XXX This and that with you and me and others and not her and him 结果是 T
  • 在 IntelliJ 插件中创建后台任务

    我正在开发一个 IntelliJ idea 插件 并希望在后台任务中运行代码 在后台任务对话框和 UI 之外的另一个线程中可见 我发现了以下内容助手类 https github com inmite android selector cha
  • 线程“main”中出现异常 java.lang.NoClassDefFoundError:无法初始化类 com.sun.jersey.core.header.MediaTypes

    我正在尝试运行一个球衣客户端并面临这个问题 WS级 import javax ws rs GET import javax ws rs Path import javax ws rs Produces import javax ws rs
  • Spring MVC 配置启用

    我正在从头开始建立一个项目 目前我正在配置Spring MVC 4 1 5使用java配置 整个应用程序正在 tomcat gradle 插件上运行 有人可以解释一下为什么我需要对班级进行以下调用DefaultServletHandlerC
  • Java switch case 抛出 nullPointer 异常

    我有一个枚举声明如下 public enum Status REQ URL1 NOT URL2 GET URL3 String getURL Status String getURL this getURL getURL 我班上的一个领域
  • 从 Java 监听系统鼠标点击

    我的主要目的是计算特定应用程序上的鼠标点击次数 想象一下 我在 PC 上打开了 Microsoft Word 和 Web 浏览器 我的 Java 代码应该告诉我单击 Word 和 Web 浏览器的次数 我需要应用程序名称和点击次数 我怎样才
  • 将分区扩展到另一级

    根据下图来自春季批量文档 http docs spring io spring batch reference html scalability html partitioning 主步骤被划分为六个从步骤 它们是主步骤的相同副本 我的问题
  • 如何从属性中获取枚举值

    我有一个带有值的枚举VALID and INVALID 它们有一个与之关联的布尔属性 我想根据我提供的布尔值获取枚举值 如果是true我应该得到VALID 如果是false我应该得到INVALID 我想根据成员变量的值 在如下所示的 get
  • 无法向 openfire 服务器发送消息

    我无法使用 SMACK API 向 openfire 服务器上的 XMPP 客户端发送消息 我不确定我哪里出错了 我在 gtalk 上测试了相同的代码 它工作正常 public class SenderTest public static
  • 0x0A 和 0x0D 之间的区别

    我正在研究蓝牙 我试图编写代码以在连接时继续监听输入流 我遇到了以下代码片段 int data mmInStream read if data 0x0A else if data 0x0D buffer new byte arr byte
  • 从文件执行db语句

    我在我的应用程序中使用嵌入式 Apache derby 我有一个名为的 SQL 脚本创建的数据库 sql创建数据库中的所有表并用初始数据填充它 例如 SET SCHEMA APP CREATE TABLE study study id bi
  • 如何在 Spring Boot 中跳过将某些 @Entity 类创建为 h2(内存中)数据库中的表?

    我正在尝试构建一个使用 2 个数据源的 Spring Boot 应用程序 我现在的主要数据库是内存数据库 仅用于测试目的 其中的表是在我创建的 sql 文件的帮助下填充的 另一个数据库 oracledb 具有已填充的表 我想实现什么目标 我
  • 在 Eclipse 中编写链接特定行的注释

    我正在 Java 中使用 Eclipse 并且处理很长的类 我需要这样的功能 在方法的顶部注释中 例如 有一个由该方法执行的操作列表 对于列出的每个操作 我想将注释的一部分 超链接 到相关代码的特定行 然后使用 Ctrl Click 到该行
  • 有人使用 Hibernate 使用 Elasticache 作为二级缓存吗?

    我发现一些线程说这是可行的 但没有找到具体的说明或配置信息 我也想从 Beanstalk 执行此操作 应用程序应该部署到 beanstalk 并使用将 hibernate 指向 elasticache 实例的配置 是的 我们能够使用二级缓存
  • 如何在 Java 中以编程方式获取接口的所有实现的列表?

    我可以通过反思或类似的方式来做到这一点吗 我已经搜索了一段时间 似乎有不同的方法 这里总结一下 反思 https github com ronmamo reflections如果您不介意添加依赖项 该库非常受欢迎 它看起来像这样 Refle
  • Java 中的引用变量里面有什么?

    我们知道对象引用变量保存表示访问对象的方式的位 它不保存对象本身 但保存诸如指针或地址之类的东西 我正在阅读 Head First Java 第 2 版 一书 书中写道 第 3 章第 54 页 在 Java 中我们并不真正知道什么是 在引用
  • 使用java读取行并映射过滤数据[关闭]

    这个问题不太可能对任何未来的访客有帮助 它只与一个较小的地理区域 一个特定的时间点或一个非常狭窄的情况相关 通常不适用于全世界的互联网受众 为了帮助使这个问题更广泛地适用 访问帮助中心 help reopen questions publi
  • 在 WildFly 10 中添加 jar 作为部署

    有没有办法 我们可以将 jar 部署为库 部署WildFly 10就像我们可以做到的那样weblogic服务器 或者我们可以将 jar 放在服务器的任何文件夹中并将这些依赖项定义为provided 我得到了什么部署方式jars on Wil

随机推荐

  • HTTP 协议详解

    目录 前言 1 HTTP 介绍 2 URL介绍 1 了解 URL 和 URI 2 URL 格式 3 URL encode 3 HTTP 协议格式 1 请求报文格式 2 响应报文格式 3 协议格式总结 4 HTTP 请求 Request 1
  • 红宝书--第一章总结分享

    红宝书 第一章总结分享 作为一名前端开发者 我想很有必要认真阅读业界大佬的著作 这不仅能拓展认知 更能发现曾经的遗漏点和误区 为了激励自己能坚持阅读完 特在此分享自己的品读总结 菜鸟也会变成老鸟 为了我的全栈梦 前端是少不了的 1 Java
  • 如何在Windows 11上安装pycocotools(实操记录)

    参考 https blog csdn net m0 45971439 article details 118332681 https blog csdn net en Wency article details 124767742 目录 一
  • 轻松拿结果-第一部分-第二章 管理者要做“定海神针”

    第二章 管理者要做 定海神针 管理者的三张面孔 做一个严厉的爸爸 在整个团队的管理过程中坚持执行制度 提高团队的人效 做一个温暖的妈妈 让所有员工感受到团队带来的安全感 让大家有所依靠 做一个优秀的教练 有方法 成系统 精细化 过程化的对员
  • 分拆TableSplit 让多个mapper同时读取

    分拆TableSplit 让多个mapper同时读取 默认情况下 一个region是一个tableSplit 对应一个mapper进行读取 但单mapper读取速度较慢 因此想着把默认一个table split分拆成多个split 这样ha
  • AttributeError:‘CartPoleEnv‘ object has no attribute ‘seed‘解决方案

    前言 在尝试运行gym的classic control模块中的Cart Pole的相关代码时 想用随机种子重置一下环境 结果不停的报AttributeError CartPoleEnv object has no attribute see
  • PyQt5-QTablewidght设置表头外框线

    1 设置表头外框线 自带的效果 修改 经过一番探索后 添加一行代码 self logo encode table horizontalHeader setStyleSheet color rgb 0 83 128 border 1px so
  • WIN10系统下VS2019编译CloudCompare2.12.4

    目录 一 源码下载 二 源码编译 1 CCCoreLib 2 Cmake编译 3 设置相关选项 三 报错处理 四 使用插件 本文由CSDN点云侠原创 原文链接 爬虫网站自重 一 源码下载 1 CloudCompare源码 https git
  • oracle rdbms 占内存_Oracle-块损坏故障:ORA-01578

    ORA 01578 错误 ORACLE data block corrupted file s block s 块损坏故障现象 ORA 01578 一般情况下 ORA 01578 错误是由硬件问题引起的 如果ORA 01578 错误始终返回
  • 金山文档服务器暂不可用,常见问题

    GPU相对于CPU有哪些优势 GPU比CPU拥有更多的逻辑运算单元 ALU 支持并行计算 可以多线程大规模并行计算 GPU加速型云服务器是否可以支持配置升级和降级 GPU加速型云服务器支持对直通型GPU云服务器实例进行升级配置 支持套餐有G
  • mysql切换数据库命令_MySQL数据库的基础使用命令大全

    show databases 显示所有已经存在的数据库 create database test 创建名字为test的数据库 drop database test 删除名字为test的数据库 use test 使用名字为test的数据库 s
  • some()和every()的区别

    for in for of forEach some 方法用于检测数组中的是否存在元素满足指定条件 存在返回true 否则返回false 即 只要有一个元素满足条件即为true some 不会对空数组进行检测 some 不会改变原始数组 如
  • 数据分析之数据准备(1-3)

    承接数据分析之数据探索 https blog csdn net qq 45626019 article details 108074152 import pandas as pd import numpy as np housing pd
  • 【Hive】insert into 与 insert overwrite的区别

    目录 一 无分区表情况 1 创建表 2 insert into插入数据 3 继续insert overwrite插入数据 二 分区表情况 1 创建表 2 insert into插入数据 3 继续insert overwrite插入数据 三
  • Ubuntu 20.04.3 server 安装

    Ubuntu server安装 选择安装openssh 安装的时候设置镜像源 https mirrors aliyun com ubuntu 选择磁盘的时候不要选择虚拟磁盘 等待 然后重启 用ssh登录ip地址 更新源 sudo apt u
  • 【ERROR】ERROR1: PROJ: proj_create_from_database: Cannot find proj.db

    目录 场景复现 解决方案 场景复现 在使用GDAL库读取tiff格式的文件 发现居然找不到proj db文件 ERROR 1 PROJ proj create from database Cannot find proj db ERROR
  • 打卡湘大OJ的第三天

    1070 if3 Description 输入一个三位的整数 如果它有且仅有两位数码相同 那么就输出Yes 否则输出No Sample Input 112 Sample Output Yes 题解 include
  • MATLAB中对于矩阵的平均数、中位数、方差、标准差、相关系数以及协方差等进行计算

    在数据分析和数学统计的时候 常常需要对矩阵的平均数 中位数 方差 标准差 相关系数以及协方差进行计算 这些数据可以反映一组数的整体大小 离散程度 相关性等一系列性质 这些数据是进行数据处理时的重要指标 目录 1 平均数 2 中位数 3 标准
  • 11.22日工作内容----更新时间预留脚本

    11 22日工作任务 更新脚本 文章目录 一 pandas是什么 二 使用步骤 1 引入库 2 读入数据 总结 欢迎使用Markdown编辑器 新的改变 功能快捷键 合理的创建标题 有助于目录的生成 如何改变文本的样式 插入链接与图片 如何
  • Java基础 :反射、注解、代理、线程池、依赖的学习和理解

    高新技术的重要性 这里的高新技术指的是Java基础中的知识 比如 反射 注解 代理 线程池 依赖注入等等 市面上的开源框架大多都是使用了这些Java基础的知识去实现的 掌握了这些Java基础的知识 能帮助我们更好的理解一些好的开源框架的实现