java学习之旅(day.20)

注解和反射

注释comment:给人看

注解annotation:不仅可以给人看,还能给程序看,甚至能被其他程序读取

注解入门

什么是注解

注解的作用:

  • 不是程序本身,可以对程序作出解释(这一点和注释没什么区别)
  • 可以被其他程序读取(比如编译器)

注解的格式

  • 注解是以”@注释名“在代码中存在的,还可以添加一些参数值,例如:@SuppressWarmings(value="unhecked")

注解在哪里使用:

可以附加在package、class、method、field上面,相当于给他们添加了额外的辅助信息,可以通过反射机制编程实现对这些元数据的访问

内置注解

2.3

package com.zhang.annotation;

import java.util.ArrayList;
import java.util.List;

//什么是注解

@SuppressWarnings("all")//镇压警告不仅可以放在方法上面,还可以放到类上面
public class Test01 extends Object{
    @Override//重写的注解  加上注解就一定要重写父类的方法?
    public String toString() {
        return super.toString();
    }

    //Deprecated不推荐程序员使用,但是可以使用,或者存在更好的方式
    @Deprecated
    public static void test(){//加上static后就可以直接调
        System.out.println("Deprecated");

    }
    @SuppressWarnings("all")//镇压警告,这里直接写会报错,需要一个参数
    public void test02(){
        List list=new ArrayList();//new了没用到也会生成警告,可以来个镇压警告
    }

    public static void main(String[] args) {

        test();
    }
}

元注解(meta-annotation)

元注解的作用是复制注解其他注解,java定义了4个标准的元注解(@Target @Retention @Documented @Inherited )。被用来提供对其他注解类型做说明,这些类型和他们锁支持的类在java.lang.annotation包中可以找到

  • @Target :用于描述注解使用范围(即被描述的注解可以用在什么地方(类,方法,字段等))
  • @Retention :表示需要什么级别保存该注释信息,用于描述注解的生命周期(SOURCE(源码)<CLASS(类)<RUNTIME(运行时))
  • @Documented :说明该注解将被包含在javadoc中
  • @Inherited :说明子类可以继承父类中的该注解
package com.zhang.annotation;

import java.lang.annotation.*;

//测试元注解
//@MyAnnotation//放到类上就报错了
public class Test02 {
    //定义一个方法
    @MyAnnotation//可以放到方法上
    public void test(){

    }
}
//定义一个注解
//Target 表示我们的注解可以用在哪些地方
@Target(value = ElementType.METHOD)//  此处也可以传多个参数,value = {ElementType.METHOD,ElementType.TYPE},这样再放到类上就不会报错了
// ElementType.METHOD 只能在方法上使用,放在其他地方会报错 需要传参数ElementType[] value() 其中 value为参数名  ElementType[]数组
//Retention 表示我们的注释在什么地方还有效 SOURCE(源码)<CLASS(类)<RUNTIME(运行时)定义RUNTIME时前两个处也有效,范围最大嘛
@Retention(value = RetentionPolicy.RUNTIME)//默认的规范:自定义的注解的优先级都写RUNTIME(运行时有效)
//Documented 表示是否将我们的注解生成在JAVAdoc中
@Documented
//Inherited  表示子类可以继承父类的注解
@Inherited
@interface MyAnnotation {//一个类里面只能由一个public公共的方法此时用public @interface MyAnnotation{}会报错    @interface  是定义注解的关键字,光写interface就成接口了,注意区别

}

自定义注解

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

@interface用来声明一个注解,格式:public @interface 注解名{注解内容} 如果在类里面声明一个注解,要去掉public

其中每一个方法实际上是声明了一个配置参数

方法的名称就是参数的名称

返回值类型就是参数的类型(返回值只能是基本类型:Class String enum(枚举类型))

可以通过default来声明参数的默认值

如果只有一个参数成员,一啊不能参数名为value,且可以省略

注解元素必须要有值,定义注解元素时,经常使用空字符串、0作为默认值

package com.zhang.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//自定义注解
public class Test03 {

    //方法
    //注解可以显示的赋值,如果没有默认值,就必须给注解赋值
    @MyAnnotation2(name="邢",schools = "垃圾")//定义了参数,不写参数就会报错,但如果有默认值了,可以不写注解的参数,也可以显示的定义参数
    public void test(){

    }

    //方法2
    @MyAnnotation3(value = "")//此处的参数value可以省略,直接写”“
    public void test2(){

    }
}
//来个注解
//元注解
@Target({ElementType.TYPE,ElementType.METHOD})//可以作用在类和方法上
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
    //定义注解
    String name() default "";//注解的参数: 参数类型+参数名()  注意不是方法
                   //此处default”“  意思是name的默认值为空,如果有默认值了,可以不写注解的参数

    int age() default  0;
    int id() default -1;//如果默认值为-1,代表不存在
    String[] schools();
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface  MyAnnotation3{
    String value();//当只有一个参数时,建议使用value作为参数名,此时在方法上写的时候可以省略value


}

反射机制

java反射机制的概述

java的反射机制让java从静态语言变成了动态语言

  • 动态语言

是一类在运行时可以改变其结构的语言,例如i虚拟的函数、对象、甚至代码可以被引进,已有的函数可以被删除或者是其结构上的变化。即:在运行时代码可以根据某些条件改变自身结构

主要的动态语言:Object-C、C#、JavaScript、PHP、Python等

  • 静态语言

与动态语言相对应,运行时结构不可变的语言就是静态语言如:Java、C、C++。Java语言虽然不是动态语言,但可称为是准动态语言,java有一定的动态性,可以利用反射机制获得类似动态语言的特性

Java Reflection

反射(Reflection)是java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息(类名、类的接口、类的方法、类的字段、类的属性等),并能直接操作任意对象的内部属性及方法。用private修饰的就不能访问,需要通过构造方法,get set,但是利用反射可以直接读取到private修饰的关键字

2.31

java反射机制提供的功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取泛型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理等

反射的优缺点:

  • 优点:可以实现动态创建对象和编译,体现出很大的灵活性
  • 缺点:对性能有影响,使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求,这类操作总是慢于直接执行相同的操作
package com.zhang.reflection;
//反射
public class Test01 extends Object{
    public static void main(String[] args) throws ClassNotFoundException{
        //通过反射获取类的Class对象
        Class c1= Class.forName("com.zhang.reflection.User");//传入的参数是字符串  包名加类名   此处还要抛出异常  返回的是Class
        System.out.println(c1);//class com.zhang.reflection.User  得到的是user class这个对象

        //多获取几个对象
        Class c2= Class.forName("com.zhang.reflection.User");
        Class c3= Class.forName("com.zhang.reflection.User");
        Class c4= Class.forName("com.zhang.reflection.User");
        //通过hashcode看这几个类是否相等,  一个类在内存中只有一个Class对象
        //一个类被加载后,类的整个结构都会被封装在Class对象中,通过这个反射回来的class对象,可以获得这个类中的所有东西
        System.out.println(c2.hashCode());//1836019240
        System.out.println(c3.hashCode());//1836019240
        System.out.println(c4.hashCode());//1836019240    是同一个类
    }
}
//实体类:若类中只有一些属性
class User{
    private String name;
    private int id;
    private int age;

    //无参构造

    public User() {
    }

    //有参构造

    public User(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }
    //因为private修饰,要get set

    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;
    }
    //toString方便写程序的时候调试

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

理解Class类并获取Class实例

Class类

在Object类中定义了getClass方法,这是Object类中的方法,此方法可以被所有的类继承,返回值的类型是一个Class类,此类是java反射的源头。 可以通过对象反射求出类的名称

2.32

2.33

2.34

package com.zhang.reflection;

import com.zhang.Student;

//测试class类的创建方式有哪些
public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException{
        Person person = new Student1();
        System.out.println("这个人是:" + person.name);

        //方式1:通过对象获得class对象
        Class c1= person.getClass();//getClass会返回一个class对象
        System.out.println(c1.hashCode());

        //方式2:通过forname获得,此时需要包名
        Class c2= Class.forName("com.zhang.reflection.Student1");// 返回的也是一个class对象 得抛出异常
        System.out.println(c2.hashCode());

        //方式3:通过类名.class获得
        Class c3 = Student1.class;//三种方式的泛型都先去掉
        System.out.println(c3.hashCode());

        //方式4了解:基本内置类型的包装类都有一个Type属性,通过这个也可以获得
        Class c4= Integer.TYPE;
        System.out.println(c4.hashCode());

        //获得父类的类型    学生的父类类型
        Class c5 = c1.getSuperclass();//返回值也是class
        System.out.println(c5);//class com.zhang.reflection.Person通过对象获得了父类的类型
    }
}

//来个类
class Person{
   public String name;

    //无参和有参构造

    public Person() {
    }

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
//再定义几个子类
class Student1 extends Person{
    //构造方法
    public Student1(){
        this.name="学生";
    }
}

class Teacher extends Person{
    //构造方法
    public Teacher(){
        this.name="老师";
    }
}

哪些类型可以有Class对象

  • class:外部类,成员(成员内部类、静态内部类),局部内部类、匿名内部类
  • interface 接口
  • [] 数组
  • enum 枚举
  • annotation 注解@interface
  • primitive type 基本数据类型
  • void
package com.zhang.reflection;

import java.lang.annotation.ElementType;

//所有类型的class对象,哪些类型有class对象
public class Test04 {
    public static void main(String[] args) {
        //类                通过.class获得类的对象
        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;
        //void
        Class c8=void.class;
        //class本身
        Class c9=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

        //另外,一个类只有一个class对象,如两个长度不一样的数组,class对象是一样的
        //即只要元素类型与维度一样,就是同一个class
        int [] a=new int[10];
        int [] b=new int[100];

        System.out.println(a.getClass().hashCode());//1836019240
        System.out.println(b.getClass().hashCode());//1836019240

    }
}

类的加载与ClassLoader

类加载内存分析

2.36

方法区也是特殊的堆

类的加载过程:加载 链接 初始化

2.37

理解:

  • 加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象 (class对象是这个时候才生成的,是加载到内存里面才有的,所以不能主动创建,只能获取)

  • 链接:将java类的二进制代码合并到JVM的运行状态之中的过程

    • 验证:确保加载的类信息符合JVM规范,代码有没有问题,有没有安全方面的问题
    • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配(static在初始化之前,在链接阶段就有默认值了)
    • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
  • 初始化:

    • 执行构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的 (类构造器是构造类信息的,不是构造该类对象的构造器)
    • 当初始化一个类的时候,如发现其父类还没有进行初始化,则需要先触发其父类的初始化
    • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步

2.35

package com.zhang.annotation;

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

        /*
        1.加载到内存,会产生一个类对应的class对象
        2.链接,链接结束后m=0
        3.初始化    初始化的时候调用clinit方法,把代码块合并如下,自动收集类中所有类变量的赋值动作和静态代码块中的语句合并
            <clinit>(){
              System.out.println("A类静态代码块初始化");
               m=300;
               m=100;
            }
            m=100  //初始化后m=100
         */

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

    static int m=100;
    public A(){//无参构造器
        System.out.println("A类的无参构造初始化");
    }
}

分析类初始化

什么时候会发生类初始化

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

    • 当虚拟机启动,先初始化main方法所在的类
    • new一个类的对象
    • 调用类的静态成员(除final常量)和静态方法
    • 使用java.lang.reflect包的方法对类进行反射调用
    • 当初始化一个类,如果其父类没有被初始化,则先初始化它的父类
  • 类的被动引用(不会发生类的初始化)

    • 当访问一个静态域时,只有真正声明这个域的咧才会被初始化,如:当通过子类引用父类的静态变量,不会导致子类初始化
    • 通过数组定义类引用,不会触发此类的初始化
    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
package com.zhang.annotation;

import com.sun.xml.internal.ws.api.model.wsdl.WSDLOutput;

import javax.xml.bind.SchemaOutputResolver;

//测试类什么时候会初始化
public class Test05 {

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

    public static void main(String[] args) throws ClassNotFoundException{
        //1.主动引用
        Son son = new Son();

        //2.通过反射也会产生主动引用
        //Class.forName("com.zhang.annotation.Son");//这里要抛出异常

        //不会产生类的引用的方法
          //通过子类son调用父类静态变量b
        System.out.println(Son.b);//这里子类没有被加载
        //当通过子类引用父类的静态变量,不会导致子类加载初始化
          //通过数组
        Son[] array=new Son[5];//不会引起类的任何初始化
        //调用子类常量池里的常量
        System.out.println(Son.M);//引用常量不会触发此类(父类和子类)的初始化

    }
}
class Father{
    static int b=2;
    static {//代码块
        System.out.println("父类被加载");

    }

}
class Son extends Father{
    static {
        System.out.println("子类被加载");
        m=300;
    }
    static int m=100;
    static final int M=1;//默认的常量
}
/*
运行结果:
main类被加载
父类被加载
子类被加载

分析:当虚拟机启动,先初始化main方法所在的类
当初始化一个类,如果其父类没有被初始化,则先初始化它的父类
 */

类加载器的作用

2.38

类加载器的作用是用来把类class装载进内存的

2.39

package com.zhang.annotation;
//获得类加载器
public class Test06 {
    public static void main(String[] args) throws ClassNotFoundException{

        //获取系统类的加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();

        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //获取系统类加载器的父类:扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@6d6f6e28

        //获取扩展类加载器的父类加载器:根加载器
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);//null    java是获取不到这一层的,无法获取

        //测试当前类是哪个加载器加载的
            //先找class对象,再找加载器
        ClassLoader classLoader = Class.forName("com.zhang.annotation.Test06").getClassLoader();//要抛出异常
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //测试JDK内置的类是谁加载的(是根加载器加载的)
         classLoader = Class.forName("java.lang.Object").getClassLoader();//要抛出异常
        System.out.println(classLoader);//null  无法直接获取

        //如何获得系统类加载器可以加载的路径
          //打印下可以加载哪些路径
        System.out.println(System.getProperty("java.class.path"));
        /*

D:\java\JDK\jre\lib\charsets.jar;
D:\java\JDK\jre\lib\deploy.jar;
D:\java\JDK\jre\lib\ext\access-bridge-64.jar;
D:\java\JDK\jre\lib\ext\cldrdata.jar;
D:\java\JDK\jre\lib\ext\dnsns.jar;
D:\java\JDK\jre\lib\ext\jaccess.jar;
D:\java\JDK\jre\lib\ext\jfxrt.jar;
D:\java\JDK\jre\lib\ext\localedata.jar;
D:\java\JDK\jre\lib\ext\nashorn.jar;
D:\java\JDK\jre\lib\ext\sunec.jar;
D:\java\JDK\jre\lib\ext\sunjce_provider.jar;
D:\java\JDK\jre\lib\ext\sunmscapi.jar;
D:\java\JDK\jre\lib\ext\sunpkcs11.jar;
D:\java\JDK\jre\lib\ext\zipfs.jar;
D:\java\JDK\jre\lib\javaws.jar;
D:\java\JDK\jre\lib\jce.jar;
D:\java\JDK\jre\lib\jfr.jar;
D:\java\JDK\jre\lib\jfxswt.jar;
D:\java\JDK\jre\lib\jsse.jar;
D:\java\JDK\jre\lib\management-agent.jar;
D:\java\JDK\jre\lib\plugin.jar;
D:\java\JDK\jre\lib\resources.jar;
D:\java\JDK\jre\lib\rt.jar;
E:\无脑学习\javaSE\基础语法\out\production\基础语法;
E:\无脑学习\javaSE\基础语法\src\com\lib\commons-io-2.8.0.jar;
D:\IDEA\IntelliJ IDEA Community Edition 2020.3.2\lib\idea_rt.jar
         */
    }
}

创建运行时类的对象

获取运行时类的完整结构

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

Field、Method、Constructor、Superclass、interface、Annoation

package com.zhang.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//获取类的信息
public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("com.zhang.reflection.User");

        //获得类的名字
        System.out.println(c1.getName());//com.zhang.reflection.User 获得包名+类名
        //获得类的简单名字
        System.out.println(c1.getSimpleName());//User 获得类名

        //获得类得属性
        Field[] fields = c1.getFields();//获得所有属性  只能找到public的属性
        //打印
        for (Field field : fields) {
            System.out.println(field);
        }

        fields = c1.getDeclaredFields();//可以找到所有的属性
        for (Field field : fields) {
            System.out.println(field);
            /*
            private java.lang.String com.zhang.reflection.User.name
            private int com.zhang.reflection.User.id
            private int com.zhang.reflection.User.age

             */
            //获得指定的属性的值
            Field name = c1.getDeclaredField("name");//抛出异常
            System.out.println(name);//private java.lang.String com.zhang.reflection.User.name

            //获得类的方法
            Method[] methods = c1.getMethods();//获得本类及其父类的全部public方法
            for (Method method : methods) {
                System.out.println("正常的(未加DeclaredMethod)" + method);

            }
            methods = c1.getDeclaredMethods();//获得本类的所有方法,但没有父类的
            for (Method method : methods) {
                System.out.println("DeclaredMethods" + method);
            }
            //获取指定的方法
            Method getName = c1.getMethod("getName",null);//要抛出异常
            Method setName = c1.getMethod("setName",String.class);//为什么要有参数String.class  与重载有关,假如方法名是一样的,无这个参数不就区分不开了吗
            System.out.println(getName);//public java.lang.String com.zhang.reflection.User.getName()
            System.out.println(setName);//public void com.zhang.reflection.User.setName(java.lang.String)

            //获得指定的构造器
            System.out.println("===================");
            Constructor[] constructors = c1.getConstructors();//获得本类所有的public构造方法
            for (Constructor constructor : constructors) {
                System.out.println(constructor);
            }
            constructors = c1.getDeclaredConstructors();//获得本类所有的构造方法
            for (Constructor constructor : constructors) {
                System.out.println(constructor);

            }

            //获得指定的构造器
            Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
            System.out.println("指定"+declaredConstructor);



        }
    }
}

调用运行时类的指定结构

有了class对象,能做什么?

  • 创建类的对象:调用class对象的newInstance()方法
    • 类必须显示定义一个无参构造器,若无,则会报错
    • 类的构造器的访问权限需要足够

那么,如果没有显示的定义无参构造器怎么创建对象?只要再操作的时候明确的调用类中的构造器,并将参数床底进去之后,才可以实例化操作

步骤如下:

  1. 通过Class类的getDeclaredConstructor(Class...paameterTypes)取得本类的指定形参类型的构造器
  2. 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数
  3. 通过Constructor实例化对象

调用指定的方法

​ 通过反射调用类中的方法,通过Method类完成

  1. 通过class类的getMethod(String name,Class...parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型
  2. 之后使用Object invoke(Obkect obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息
  • Object对应原方法的返回值,若原方法无返回值,此时返回null
  • 若原方法为静态方法,此时形参Object obj 可为null
  • 若原方法形参列表为空,则Object[] args为null
  • 若原方法声明为private,则需要在调用此invoke()方法前,显示调用方法对象的setAccessible(true)方法,将可访问private的方法
    • Method和Field Constructor对象都有setAccessible()方法,其作用是启动和禁用访问安全检查的开关,默认为开(false)
    • 参数值为false指示反射的对象应实施java语言访问检查
    • 参数值为true,指示反射的对象在使用时应该取消java语言的访问检查
      • 提高反射的效率,如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true
      • 使得原本无法访问的私有成员也可以访问
package com.zhang.reflection;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

//通过反射动态的创建对象
public class Test08 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //通过包名构造一个对象,获得class对象
        Class c1= Class.forName("com.zhang.reflection.User");

        //构造一个对象  通过反射构造的1对象
        User user = (User)c1.newInstance();//强转为User?  抛出异常 User{name='null', id=0, age=0} 本质上是调用了类的无参构造器,但是如果user中没有无参构造器会报错
        System.out.println(user);


        //通过构造器创建对象(如果User中没有显示的定义一个有参构造就用这个方法)
            //那就先获得构造器
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
            //通过构造器去创建对象
        User user2 = (User) constructor.newInstance("邢", 001, 18);
        System.out.println(user2);

        //通过反射调用普通方法
          //创建个对象
        User user3 = (User)c1.newInstance();
          //通过反射获取一个方法
             //invoke()  激活    传递的参数   (对象,”方法的值“)
        Method setName = c1.getDeclaredMethod("setName", String.class);//会返回一个Method
        setName.invoke(user3,"菲");//给user3这个对象传递一个String”菲“   invoke  激活
        System.out.println(user3.getName());//菲

        //通过反射操作属性
           //操作属性也需要对象,创建一个对象
        User user4 = (User)c1.newInstance();
           //获取这个属性
        Field name = c1.getDeclaredField("name");  //会返回一个Field
            //操作name

        //不能直接操作私有属性,我们需要关闭程序的安全检测(通过属性或方法的setAccessible(true)关闭)
        name.setAccessible(true);//关闭权限检测 //默认是false(开启的)
        name.set(user4,"谭");//给user4这个对象赋值谭
        System.out.println(user4.getName());//只有这样的话会报错:权限不够(User类中的属性都是私有的,不是私有当然就不用setAccessible关闭权限检查了),此时要通过关闭权限检测来访问私有的属性
    }
}

性能对比分析

package com.zhang.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

//分析性能问题
public class Test09 {

    //普通方式调用方法
       //写3个方法
    public static void test01(){
        User user = new User();
        //获取开始时间
        long startTime=System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        //获取结束时间
        long endTime=System.currentTimeMillis();
        System.out.println("普通方式执行10亿次需要时间:"+(endTime-startTime)+"ms");
    }

    //反射方式调用方法
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        //获取开始时间
        long startTime=System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);//传的参数为空null
        }
        //获取结束时间
        long endTime=System.currentTimeMillis();
        System.out.println("反射方式执行10亿次需要时间:"+(endTime-startTime)+"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 startTime=System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);//传的参数为空null
        }
        //获取结束时间
        long endTime=System.currentTimeMillis();
        System.out.println("关闭权限检查反射方式执行10亿次需要时间:"+(endTime-startTime)+"ms");
    }


    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test01();
        test02();
        test03();
    }
}
/*
普通方式执行10亿次需要时间:6ms
反射方式执行10亿次需要时间:4169ms
关闭权限检查反射方式执行10亿次需要时间:1763ms
 */

通过反射操作泛型

  • java采用擦除机制来引入泛型,java的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换问题,但一旦编译完成,所有和泛型有关的类型全部擦除
  • 为了通过反射操作这些泛型,java新增了几个类:ParameterizedType(获得泛型参数类型) GenericARRAYtype TypeVariable 和WildcardType几种类型类代表不能被归一到class类中的类型但是又和原始类型齐名的类型 好绕

ParameterizedType :表示一种参数化类型Collection

GenericARRAYtype :表示一种元素类型是参数化类型或类型变量的数组类型

TypeVariable :是各种类型变量的公共父接口

WildcardType:代表一种通配符类型表达式

package com.zhang.reflection;

import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

//通过反射获取泛型
public class Test10 {
    //方法
    //泛型作为参数传参
    public void test01(Map<String,User>map, List<User> list){
        System.out.println("test01");

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

    //如何通过注解获得泛型,首先获得这个类,通过这个类创建class对象
    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test10.class.getMethod("test01",Map.class,List.class);
        //通过方法获得参数类型
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println(genericParameterType);
            if (genericParameterType instanceof ParameterizedType){//如果genericParameterType泛型的参数类型属于参数化类型
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();//强转完后获得它的真实类型
                for (Type actualTypeArgument : actualTypeArguments) {//输出以下真实的泛型参数信息
                    System.out.println(actualTypeArgument);
                }

            }
        }


        method = Test10.class.getMethod("test02",null);//test02无参数
        Type genericReturnType = method.getGenericReturnType();//获取返回值类型
        if (genericReturnType instanceof ParameterizedType){//如果genericParameterType泛型的参数类型属于参数化类型
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();//强转完后获得它的真实类型
            for (Type actualTypeArgument : actualTypeArguments) {//输出以下真实的泛型参数信息
                System.out.println(actualTypeArgument);
            }

        }
    }


}

反射操作注解

package com.zhang.reflection;

import java.lang.annotation.*;
import java.lang.reflect.Field;

//练习反射操作注解
public class Test11 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1= Class.forName("com.zhang.reflection.Student");//通过反射获得一个class对象
        //通过反射获得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);//@com.zhang.reflection.Tablezhang(value=db_student)

        }
        //获得注解中value的值
          //获取指定的注解
        Tablezhang tablezhang = (Tablezhang)c1.getAnnotation(Tablezhang.class);//强转
           //通过方法value获取值
        String value = tablezhang.value();
        System.out.println(value);//db_student

         //获得类指定的注解
        Field f= c1.getDeclaredField("name");   //获得属性name的注解
        Fieldzhang annotation = f.getAnnotation(Fieldzhang.class);    //获取指定类的注解
        //得到值
        System.out.println(annotation.columnName()); //db_name
        System.out.println(annotation.type());  //varchar
        System.out.println(annotation.length());   //3



    }

}
@Tablezhang("db_student")
class Student{
    @Fieldzhang(columnName = "db_id",type = "int",length = 10)
    private int id;
    @Fieldzhang(columnName = "db_age",type = "int",length = 10)
    private int age;
    @Fieldzhang(columnName = "db_name",type = "varchar",length = 3)
    private String name;

    public Student() {
    }

    public Student(int id, int age, String 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 String getName() {
        return name;
    }

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

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


//通过注解加一些标识
  //创建注解
//定义类名的注解
@Target(ElementType.TYPE)//作用在TYPE上
@Retention(RetentionPolicy.RUNTIME)
@interface Tablezhang{
    String value();
}

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

}
posted on 2021-02-04 18:23  懵逼的程序小白  阅读(80)  评论(0)    收藏  举报