跟狂神学注解和反射笔记

笔记是根据狂神的B站视频,以及自己的理解做出来的。如果有说错的地方,望大佬能够指出,相互学习!

注解

  • 和注释一样,注解不是程序本身,而是对程序作出解释,而注解与注释不同的点在于,注解可以被其他程序比如编译器读取

//内置注解

@Override//重写注解
@Deprecated//不推荐使用注解,可以使用但是又风险或者有更好的方式
@SuppressWarnings//“镇压”警告注解

元注解

元注解 重点需要了解 @Target @Retention

  • 元注解的作用就是注解其他注解,Java定义了4个标准的meta-annotation类型,他们被用来提供对其他annotation类型做说明
  • 4个元注解分别为:
    • @Target:用于描述注解的使用范围
    • @Retention:用于表示需要在什么级别保存注解信息,用于描述注解的声明周期,(SOURCE<CLASS<RUNTIME)
    • @Document:说明该注解将被包含在javadoc(文档)中
    • @Inherited:说明子类可以继承父类中的该注解

测试元注解

//测试元注解
@MyAnnotation
public class Test02 {
  
    public void test(){

    }

}

//定义注解
//Target  表示我们的注解可以用在哪些地方  METHOD方法  TYPE 类
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented //表示把注解生成在Javadoc中
@Inherited //表示可以被继承
@interface MyAnnotation{

}

自定义元注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口

image-20201214001642498

public class Test03 {
    //注解可以显示赋值,如果没有默认值,就必须给注解赋值
    @MyAnnotation2(schools = {"贵州大学","清华大学"})
    public void test(){
    }
    //如果注解只有一个参数可以省略参数名,或value
    @MyAnnotation3("")
    public void test2(){

    }
}
//注解的写法
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
    //注解的参数:类型+参数名() [default 默认值];
    String name() default "";
    int age() default 0;
    int id() default -1;  //如果默认值为-1,代表不存在
    String[] schools();
}

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
    //如果注解只有一个值  可以直接使用value
    String[] value();  //不成名的一个规范
}

反射机制


动态语言和静态语言

动态语言

  • 在运行时可以改变其结构:例如新的函数、对象甚至代码可以被引进,已有的函数可以被删除或者是其他结构上的变化。通俗来说就是运行时代码可以根据一些条件来改变自身的结构。
  • 主要动态语言:Object-C、C#、JavaScript、PHP、Python等。

这里演示下 javaScript :弱语言(是比较随意的)

function f(){
    var x="var a=3;var b=5;alert(a+b)";
    eval(x);  //执行
}

静态语言

  • 与动态语言相对应的,运行时不能改变其结构,如Java、C、C++
  • Java不是动态语言,但是java可以称为是“准动态语言”。即java有一定的动态性,可以利用反射机制获得类似动态语言的特性。Java的动态性使得编程时更加灵活。

Java Reflection

  • Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API获取任何类的内部信息(比如类名,类的接口,类的方法,字段,属性…),并且能够直接操作任意对象的内部属性及方法。

    Class c = Class.forName("java.lang.String")
    
  • 加载完类之后,在堆内存中的方法区中间产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射

    image-20201214004432882

java 放射机制及应用

image-20201214011853001

java反射优点和缺点

优点:

  • 可以实现动态创建对象和编译,体现出很大的灵活性

缺点:

  • 对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM 我们希望做什么并且它满足我们的需求。这类操作总是慢于 直接执行相同的操作。

反射相关的主要API

image-20201214012551359

代码演示:

这里提到的hash code:是一种编码方式,在Java中,每个对象都会有一个hashCode,Java可以通过这个hashCode来识别一个对象。

public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通过反射获取类的class对象
        Class c1 = Class.forName("com.javacto.reflection.User");//需要全限定名
        System.out.println(c1); //class com.javacto.reflection.User

        Class c2 = Class.forName("com.javacto.reflection.User");
        Class c3 = Class.forName("com.javacto.reflection.User");

        //打印hashcode可以看出一个类在内存中只有一个Class对象,不可能有多个
        //一个类被被载后,类的整个结构都会被封装在Class对象中
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
       
    }


}

//实体类  :pojo  entity
class User{
    private String name;
    private int id;
    private int age;

    /**
     * 无参构造方法
     */
    public User() {
    }

    /**
     * 有参构造方法
     * @param name
     * @param id
     * @param age
     */
    public User(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }
}

运行结果:

image-20201214015933525

Class类

image-20201214012701857

从这张图可以看出:可以通过对象反射出类的名称 这就是反射

image-20201214221042011

  • Class类的常用方法
方法名说明
static Class forName(String name)返回指定类名name对应的Class对象
Object newInstance()调用缺省构造函数,返回Class对象的一个实例
String getName()返回此Class对象所表示的实体(类、接口、数组类或者void)的名称
Class getSuperClass返回当前Class对象的父类Class对象
Class[] getinterfaces()获取当前Class对象的接口
ClassLoader getClassLoader()返回该类的加载器
Constructor[] getConstructors()返回一个包含某些Constructor对象的数组
Method getMothed(String name,Class… T)返回一个Method对象,此对象形参类型为param Type
Fied[] getDeclaredFields()返回Field对象的一个数组

  • 获得Class类的实例

    a)如果已有具体的类,通过类的class属性获取,最为安全可靠且性能最高的方法。
    C l a s s 变 量 = X X X . c l a s s ; Class 变量=XXX.class; Class=XXX.class;

    b)已知某个类的实例,调用此实例的getClass()方法获取Class对象。
    C l a s s 变 量 = x x x . g e t C l a s s ( ) ; Class 变量=xxx.getClass(); Class=xxx.getClass();

    c)已知一个类的全名且在类路径下,可以通过Class类的静态方法forName()获取,需要处理异常ClassNotFoundException
    C l a s s 变 量 = C l a s s . f o r N a m e ( " 已 知 类 的 全 限 定 名 " ) ; Class 变量=Class.forName("已知类的全限定名"); Class=Class.forName("");

    d)内置基本数据类型可以直接使用类名.Type

    e)还可以用ClassLoader(之后讲解)

测试class 类的创建方式有哪些

public class Test03 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person=new Student();
        System.out.println("这个人是:"+person.name);

        //方式1:通过对象获得
        Class c1=person.getClass();
        System.out.println(c1.hashCode());  //460141958

        //方式2:forName获得
        Class c2=Class.forName("com.javacto.reflection.Student");
        System.out.println(c2.hashCode());  //460141958

        //方式3: 通过类名.class 获得
        Class c3 = Student.class;
        System.out.println(c3.hashCode());  //460141958
//---------------------------------------------------------------------------------------

        //方式4: 基本内置类型的包装类都有一个 TYPE属性  类名.TYPE
        Class c4 = Integer.TYPE;
        System.out.println(c4);  //int  默认的基本类型 (d.获得内置基本数据类型)

        //获得父类类型
        Class c5 = c1.getSuperclass();
        System.out.println(c5);  //class com.javacto.reflection.Person
    }
}

//定义一个父类
class Person{
    public String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

//子类1: 学生
class Student extends Person{
    public Student(){
        this.name="学生";
    }
}

//子类2:老师
class Teacher extends Person{
    public Teacher(){
        this.name="老师";
    }
}

运行结果:可以看到 前三种通过不同的方式获得Class对象一样(这个class对象是Student,因为对象实例化的是Student这个类)

image-20201214230745746


  • 哪些类型可以有Class对象?
    • class:外部类、成员(成员内部类、静态内部类),局部内部类,匿名内部类。
    • interface:接口
    • []:数组
    • enum:枚举
    • annotation:注解 (也是一个类型)
    • primitive type:基本数据类型
    • void

所有类的Class对象

public class Test04 {
    public static void main(String[] args) {
        Class c1= Object.class;//类
        Class c2= Comparable.class;//接口
        Class c3= String[].class;//一维数组
        Class c4= int[][].class;//二维数组
        Class c5=Override.class;//注解
        Class c6= ElementType.class;//枚举
        Class c7= Integer.class;//基本数据类型
        Class c8= void.class;//void (代表空类型)
        Class c9= Class.class;//Class

        System.out.println(c1);  //class java.lang.Object
        System.out.println(c2);  //interface java.lang.Comparable
        System.out.println(c3);  //class [Ljava.lang.String;
        System.out.println(c4);  //class [[I
        System.out.println(c5);  //interface java.lang.Override
        System.out.println(c6);  //class java.lang.annotation.ElementType
        System.out.println(c7);  //class java.lang.Integer
        System.out.println(c8);  //void
        System.out.println(c9);  //class java.lang.Class


        System.out.println("=======================");
        //只要元素类型与维度一样,就是同一个class
        int[] a=new int[10];
        int[] b=new int[100];
        int[][] c=new int[10][10];
        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());
        System.out.println(c.getClass().hashCode());
    }
}

查看运行结果:

image-20201214234925277

  • 注意:只要元素类型与维度一样,就是同一个class,反之 同样是int类型的数组,维度不同Class对象所打印出的hashcode不同,即:数组维度不同对应不同的Class对象。

类加载内存分析

java内存分析

image-20201215001210878

了解:类的加载过程

image-20201215001307449

类的加载与ClassLoader的理解

image-20201215001451972

测试demo以及画图分析

public class Test05 {
    public static void main(String[] args) {
        A a =new A();
        System.out.println(A.m);

        /**
         * 1.加载到内存,会产生一个对应Class对象
         * 2.链接, 链接结束后 m=0
         * 3.初始化
                <clint>(){
                        System.out.println("A类静态代码块初始化");
                        m=300;
                        m=100;
                 }
         */
    }
}

class A{

    //无参构造方法
    public A(){
        System.out.println("A类的无参构造初始化");

    }
    //静态代码块
    static {
        System.out.println("A类静态代码块初始化");
        m=300;
    }

    /**
     * m=300
     * m=100  (覆盖了上面的值)
     */
    //静态变量
    static int m=100;
}

image-20201215023915425

基于图的分析步骤:

1.类写好后,编译成class加载到内存中(方法区可以说是特殊的堆,可以看到里面存储class相关的信息包含方法的信息)

2.在类加载的时候形成了class对象

3.准备执行main方法,这里的m是静态变量,链接段初始值为为0,这些内存都将在方法区中进行分配(栈内存空间:存放方法中的局部变量)

4.之后开始执行代码 堆内存(我的理解是只要new出来的就存在堆内存),

new A(); 产生新的对象 A类对象 (A类自己创建出来的对象)先去找到自己Class类,通过A类的具体方法进行赋值。

5.初始化,这一步是由jvm去执行的,赋值后通过 方法 初始化 详情看代码 最终结果就为m=100.

查看代码的运行结果:

image-20201215030911361

额外补充:

静态代码块:用staitc声明,jvm加载类时执行,仅执行一次
构造代码块:类中直接用{}定义,每一次创建对象时执行。
执行顺序优先级:静态块,main(),构造块,构造方法。

有关 静态代码块,构造代码块,构造函数执行执行顺序查看该链接(写得很详细)

查看

什么时候会发生初始化

类的主动应用(一定会发生类的初始化)

image-20201216212714239

测试代码: 类什么时候会初始化

public class Test06 {

    static {
        System.out.println("Main类被加载");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        //1.主动引用
        // Son son=new Son();

        //反射也会产生主动引用
        Class.forName("com.javacto.reflection.Son");
    }
}
class Father{
    static int b=2;

    static {
        System.out.println("父类被加载");
    }
}

class Son extends Father{
    static {
        System.out.println("子类被加载");

    }
    static  int m=100;
    static final int M =1;  //常量
}

运行效果:

image-20201216212954000

可以看出:当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。且初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

什么时候不会发生初始化

类的被动引用,不会发生初始化

image-20201216224014964

基于同样的代码测试如下:

public class Test06 {


    static {
        System.out.println("Main类被加载");
    }
    public static void main(String[] args) throws ClassNotFoundException {


        //System.out.println("==========不会发生类的初始化===============");
        //不会产生类的引用的方法
        //1.通过子类引用父类的静态变量,不会导致子类初始化  因为static在链接阶段的时候已经存在了
        //System.out.println(Father.b);  

        //2.只是一个数组 命了一个名和开辟了空间而已
        //Son [] array= new Son[5];
		//System.out.println(array.getClass());
		
        //3.常量不会引起父类与子类的初始化,因为所有的常量以及静态变量,都是在链接阶段都已经赋了一个值了,初始化的时候已经存在了
        //System.out.println(Son.M);  


    }
}
class Father{
    static int b=2;

    static {
        System.out.println("父类被加载");
    }
}

class Son extends Father{
    static {
        System.out.println("子类被加载");

    }
    static  int m=100;
    static final int M =1;  //常量
}

1.对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,**只会触发父类的初始化而不会触发子类的初始化。**在本例中,虚拟机会先发现其父类Father还未被初始化,因此虚拟机将先初始化父类Father,而Son始终不会被初始化。

image-20201216224516034

2.通过数组定义来引用类,不会触发此类的初始化

image-20201216230814644

上述案例运行之后并没有任何输出,说明虚拟机并没有初始化类Son。但是,这段代码触发了另外一个名为 [Lcom.javacto.reflection.Son的类的初始化。从类名称我们可以看出,这个类代表了元素类型为Son的一维数组,它是由虚拟机自动生成的,直接继承于Object的子类,创建动作由字节码指令newarray触发。

3.常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化

常量不会引起父类与子类的初始化,因为所有的常量以及静态变量,都是在链接阶段都已经赋了一个值了,初始化的时候已经存在了

image-20201216230211304

类加载器

类加载器的作用

注:类是有缓存的 是提高效率的 在加载的时间当中 如果有不需要的会垃圾回收器 (gc)回收这些class对象 。

image-20201217000342891

image-20201217000505709

java平台核心库:rt.jar 包

扩展类加载器 :ExtClassLoader:

系统类加载器: System Classloder ,也有的地方叫 AppClassLoader

java的运行环境jre -> lib 目录 运行所有的jar包都在这里面

image-20201217011931885

测试获取加载器:

public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取系统的类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系统类加载器:"+systemClassLoader);

        //获取系统类加载的父类加载器--> 扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println("扩展类加载器:"+parent);

        //获取扩展类加载器的父类加载器--> 根加载器 (c/c++ 写的 读取不到 返回 null)
        ClassLoader parent1 = parent.getParent();
        System.out.println("根加载器:"+parent1);

        //测试当前类是哪个加载器加载的
        ClassLoader classLoader = Class.forName("com.javacto.reflection.Test07").getClassLoader();
        System.out.println("当前类加载器:"+classLoader);

        //测试jdk 内置的类是谁加载的
        classLoader=Class.forName("java.lang.Object").getClassLoader();
        System.out.println("JDK内置类的加载器:"+classLoader);  //根加载器加载的

    }
}

运行结果:

image-20201217014622799

可以看到 jdk内置类的加载器 就是java平台核心库,跟加载器加载的 因为用c++编写的的,无法直接获取 所有显示null。

扩充: 获得系统类加载器可以加加载的路径

如何获得系统类加载器可以加加载的路径
System.out.println(System.getProperty("java.class.path"));
//打印输出的结果 ,可以看到 java运行环境路径,扩展包类jar包 rt.jar包  包括我们自己写的工程,ide的jar包。当然如果我们的类不在这些地方就读取不到了,加载是有机制的
/*
        D:\java\tools\jdk1.8.0_201\jre\lib\charsets.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\deploy.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\access-bridge-64.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\cldrdata.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\dnsns.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\jaccess.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\jfxrt.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\localedata.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\nashorn.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\sunec.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\sunjce_provider.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\sunmscapi.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\sunpkcs11.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\ext\zipfs.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\javaws.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\jce.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\jfr.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\jfxswt.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\jsse.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\management-agent.jar
        D:\java\tools\jdk1.8.0_201\jre\lib\plugin.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\resources.jar;
        D:\java\tools\jdk1.8.0_201\jre\lib\rt.jar;
        D:\java\project\ideaPro\Test\ProjectAll\out\production\注解和放射;
        D:\java\常用工具\ideaIU-2019.3.2.win\lib\idea_rt.jar

         */

了解什么是双亲委派机制:

当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。

双亲委派机制的作用

1、防止重复加载同一个.class。通过委托去向上面问一问,加载过了,就不用再加载一遍。保证数据安全。
2、保证核心.class不能被篡改。通过委托方式,不会去篡改核心.clas,即使篡改也不会去加载,即使加载也不会是同一个.class对象了。不同的加载器加载同一个.class也不是同一个Class对象。这样保证了Class执行安全。

创建运行时类的对象

获取运行时类的完整结构

通过反射获取运行时类的完整结构

  • Field(字段)、Method(方法)、Constructor(构造器)、Superclass(父类)、Interface(接口)、Annotation(注解)
    • 实现的全被接口
    • 所继承的父类
    • 全部的构造器
    • 全部的方法
    • 全部的Field
    • 注解
    • 。。。

操作很简单,总结只需要注意:

默认 只能找到 public 字段, 属性,方法 。。。。。

关键字:Declared 能找到全部的 字段, 属性,方法 。。。。。

//获得类的信息
public class Test08 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("com.javacto.reflection.User");

        System.out.println("=========获得类的名字=============");
        //类的名字
        System.out.println(c1.getName());//包名+类名
        System.out.println(c1.getSimpleName());//类名

        /*
        打印输出结果
        com.javacto.reflection.User
        User
         */

        System.out.println("==============获得类的属性===================");
        //获得类的属性
        Field[] field1=c1.getFields();//只能找到public属性
        for (Field f :field1) {
            System.out.println(f);  //因为没有定义public属性所以为无
        }

        Field[] field2=c1.getDeclaredFields();//找到全部的属性
        for (Field f :field2) {
            System.out.println(f);
        }

        /*
        输出结果
        private java.lang.String com.javacto.reflection.User.name
        private int com.javacto.reflection.User.id
        private int com.javacto.reflection.User.age
         */

        //获得指定的属性
        Field name=c1.getDeclaredField("name");
        System.out.println("指定:"+name);

        /*
        输出结果
        指定:private java.lang.String com.javacto.reflection.User.name
         */

        //获得类的方法
        System.out.println("==============获得类的方法===================");
        Method[] methods=c1.getMethods();//获得本类及其父类的全部public方法
        for (Method method:methods) {
            System.out.println("正常的:"+method);
        }
        methods=c1.getDeclaredMethods();//获得本类的所有方法,包括私有的  不包括父类
        for (Method method:methods) {
            System.out.println("DeclaredMethods:"+method);
        }

        /*
        打印输出结果
        正常的:public java.lang.String com.javacto.reflection.User.toString()
        正常的:public java.lang.String com.javacto.reflection.User.getName()
        正常的:public int com.javacto.reflection.User.getId()
        正常的:public void com.javacto.reflection.User.setName(java.lang.String)
        正常的:public int com.javacto.reflection.User.getAge()
        正常的:public void com.javacto.reflection.User.setId(int)
        正常的:public void com.javacto.reflection.User.setAge(int)
        正常的:public final void java.lang.Object.wait() throws java.lang.InterruptedException
        正常的:public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
        正常的:public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
        正常的:public boolean java.lang.Object.equals(java.lang.Object)
        正常的:public native int java.lang.Object.hashCode()
        正常的:public final native java.lang.Class java.lang.Object.getClass()
        正常的:public final native void java.lang.Object.notify()
        正常的:public final native void java.lang.Object.notifyAll()
        DeclaredMethods:public java.lang.String com.javacto.reflection.User.toString()
        DeclaredMethods:public java.lang.String com.javacto.reflection.User.getName()
        DeclaredMethods:public int com.javacto.reflection.User.getId()
        DeclaredMethods:public void com.javacto.reflection.User.setName(java.lang.String)
        DeclaredMethods:private void com.javacto.reflection.User.test()
        DeclaredMethods:public int com.javacto.reflection.User.getAge()
        DeclaredMethods:public void com.javacto.reflection.User.setId(int)
        DeclaredMethods:public void com.javacto.reflection.User.setAge(int)
         */

        //获得指定方法只需要在()中添加参数(方法名,方法参数)
        //添加参数  实则是因为考虑到了 重载
        System.out.println("============获得指定方法=============");
        Method getName = c1.getMethod("getName", null);
        Method setName = c1.getMethod("setName", String.class);

        System.out.println(getName);
        System.out.println(setName);

        /*
        打印输出结果
        public java.lang.String com.javacto.reflection.User.getName()
        public void com.javacto.reflection.User.setName(java.lang.String)
         */


        System.out.println("=============获得指定的构造器===================");
        //获得指定构造器
        Constructor[] constructors=c1.getConstructors();//获得public方法

        for (Constructor constructor : constructors) {
            System.out.println("public:"+constructor);
        }
        constructors=c1.getDeclaredConstructors();//获得本类所有方法
        for (Constructor c :constructors) {
            System.out.println("全部:"+c);
        }
        //获得指定构造器  (String name, int id, int age)
        Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        System.out.println("指定构造器:"+declaredConstructor);
        
        /*
        打印输出结果:
        public:public com.javacto.reflection.User(java.lang.String,int,int)
        public:public com.javacto.reflection.User()
        全部:public com.javacto.reflection.User(java.lang.String,int,int)
        全部:public com.javacto.reflection.User()
        指定构造器:public com.javacto.reflection.User(java.lang.String,int,int)
         */
    }
}


有了Class对象,能做什么

  • 创建类的对象,调用Class对象的newInstance()方法

    1. 必须要有一个无参数的构造器

    2. 类的构造器的访问权限要够

  • 通过获取Class对象的构造器创建

    1. 通过Class对象的getDeclaredConstructor(所需要的参数 Type.class) 方法获取本类指定参数类型的构造器

    2. 向构造器传入一个对象数组进去,里面包含此构造器所需要的各个参数

    3. 通过Constructor实例化对象

通过反射动态创建对象测试

public class Test09 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //获得Class对象
        Class c1 = Class.forName("com.javacto.reflection.User");

        //构造一个对象
       /* User user = (User)c1.newInstance(); //本质上调用了类的无参构造器
        System.out.println(user);  //User{name='null', id=0, age=0}*/

        //通过构造器创建对象
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);

        User user2 = (User) constructor.newInstance("小明", 1, 18);
        System.out.println(user2); //User{name='小明', id=1, age=18}


        //通过反射调用普通方法
        User user3= (User) c1.newInstance();
        //通过反射获取一个方法
        Method setName = c1.getMethod("setName", String.class);

        //invoke: 激活的意思  (对象, "方法需要的参数")
        setName.invoke(user3,"小明3");
        System.out.println(user3.getName());


        System.out.println("========通过反射操作属性===========");
        //通过反射操作属性
        User user4= (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        
        //不能直接操作私有属性, 我们需要关闭程序的安全检查,属性或者方法的 setAccessible(true)
        name.setAccessible(true);
        name.set(user4,"小明4"); //修改属性值
        System.out.println(user4.getName());//setAccessible 默认为false 如果没有关闭将会报没有访问private的权限
        //can not access a member of class com.javacto.reflection.User with modifiers "private"

    }
}

执行结果:

image-20201218010601616

调用指定的方法 invoke

invoke 激活的意思

(对象,“方法所需要的参数”)

image-20201218010718267

setAccessible

启动和禁用安全检查的开关,默认为false (启动)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QJM02udB-1608384690640)(C:\Users\Cyj\AppData\Roaming\Typora\typora-user-images\image-20201218010740137.png)]

分析性能问题

public class Test10 {

    //普通方式调用
    public static void test01(){
        User user=new User();
        //开始时间
        long starTime=System.currentTimeMillis();

        //模拟10亿次不断获取名字所需要的时间
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }

        //结束时间
        long endTime=System.currentTimeMillis();

        System.out.println("普通方式执行10亿次:"+(endTime-starTime)+"ms");
    }

    //反射方式调用
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();

        //获取指定方法
        Method getName = c1.getDeclaredMethod("getName", null);
        //开始时间
        long starTime = System.currentTimeMillis();

        //模拟10亿次不断获取名字所需要的时间
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);
        }

        //结束时间
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式执行10亿次:"+(endTime-starTime)+"ms");
    }

        //反射方式调用  关闭检测
        public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            User user = new User();
            Class c1 = user.getClass();

            //获取指定方法
            Method getName = c1.getDeclaredMethod("getName", null);
            getName.setAccessible(true);
            //开始时间
            long starTime = System.currentTimeMillis();

            //模拟10亿次不断获取名字所需要的时间
            for (int i = 0; i < 1000000000; i++) {
                getName.invoke(user, null);
            }

            //结束时间
            long endTime = System.currentTimeMillis();
            System.out.println("关闭检测执行10亿次:"+(endTime-starTime)+"ms");
        }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test01();
        test02();
        test03();
        //如果反射调用次数多的话,可以关闭这个检测 提高程序的一个效率
    }
}

执行结果:

普通方式执行最快,

反射会比正常的效率慢,

关闭检测执行改善了反射调用效率问题

image-20201218011841308

  • 注意:其中setAccessible(true)方法调用后会关闭对应属性、方法的安全检查,但会改善反射调用的效率问题

如果反射调用频率高的话,可以关闭这个检查 以便提高程序的一个效率

获取泛型信息

  • 反射操作泛型

  • Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除。

    思考该怎么获得? (之前有说过,类加载的时候就产生了Class对象,故class对象里面应该是有保留的)

  • 为了通过反射操作这些类型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。

类型说明
ParameterizedType表示一种参数化类型,比如Collection< String >
GenericArrayType表示一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable是各种类型变量的公共父接口
WildcardType代表一种通配符类型表达式

通过反射获取泛型 扩展了解

public class Test11 {

    /**
     * 通过泛型传参
     * @param map
     * @param list
     */
    public void test01(Map<String,User> map, List <User> list){
        System.out.println("test01");

    }



    /**
     * 通过泛型返回值
     * @return
     */
    public Map<String,User> test02(){
        System.out.println("test02");
        return null;
    }


    public static void main(String[] args) throws NoSuchMethodException {
        //获得参数类型
        Method method = Test11.class.getMethod("test01", Map.class, List.class);
        //获取参数类型  即Map和 List
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("1:"+genericParameterType);

            //判断genericParameterType参数类型 是否属于 ParameterizedType 参数化类型
            if (genericParameterType instanceof ParameterizedType){
                //如果属于参数化类型,获得他的真实类型 getActualTypeArguments
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();

                //再次输出真实的泛型信息
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("2:"+actualTypeArgument);
                }
            }
        }
        
        
        //获得返回值类型
        method = Test11.class.getMethod("test02",null);
        Type genericReturnType = method.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType){
            //如果genericReturnType返回值类型属于参数化类型,获得他的真实类型 getActualTypeArguments
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            
            //再次输出真实的泛型信息
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println("3:"+actualTypeArgument);
            }
        }
    }
}

查看结果: 得到泛型

image-20201218023547327


获取注解信息

反射操作注解

image-20201218031249308

练习反射操作注解

public class Test12 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("com.javacto.reflection.Student2");

        //通过反射获取注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //获得注解的value的值   获取指定注解值
        MyTable myTable =(MyTable) c1.getAnnotation(MyTable.class);
        String value = myTable.value();
        System.out.println(value);


        //获得类指定的注解
        System.out.println("=====获得类指定的注解======");
        Field f= c1.getDeclaredField("name");
        MyField annotation = f.getAnnotation(MyField.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }

}

@MyTable("db_students")
class Student2{
    @MyField(columnName = "db_id",type = "int",length = 10)
    private int id;
    @MyField(columnName = "db_age",type = "int",length = 10)
    private int age;
    @MyField(columnName = "db_name",type = "varchar",length = 50)
    private System name;

    public Student2() {
    }

    public Student2(int id, int age, System name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public System getName() {
        return name;
    }

    public void setName(System name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student2{" +
                "id=" + id +
                ", age=" + age +
                ", name=" + name +
                '}';
    }
}

//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable{
    String value();
}

//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
    String columnName(); //列名
    String type(); //类型
    int length(); //长度
}

执行结果:

image-20201218031614874

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值