注解与反射

1. 什么是注解

  • Annotation是从JDK5.0开始引入的新技术
  • Annotation的作用
    • 不是程序本身,可以对程序作出解释(这一点和注释没区别)
    • 可以被其他程序(比如:编译器等)读取
  • Annotation的格式
    • 注解是以 @注释名 在代码中存在的,还可以添加一些参数值,例如:@注释名(value=“值”)
  • Annotation在哪里使用?
    • 可以附加在package,class,method,field等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程来实现对这些元数据的访问

2. 元注解

元注解的作用:负责注解其他注解,Java定义了4个标准的meta-annotion类型,他们被用来提供对其他annotation类型作说明,这些类型和它们所支持的类在java.lang.annotation包中可以找到

  • @Target:用于描述注解的使用范围(被描述的注解可以用在什么地方)
  • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
    • (SOURCE < CLASS < RUNTIME)
    • 一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解;
      如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;
      如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,使用SOURCE 注解。
  • @Document:说明该注解将被包含在javadoc中
  • @Inherited:说明子类可以继承父类中的该注解

3. 自定义注解

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

  • @interface用来声明一个注解,格式:public @interface 注解名 {定义内容}
  • 其中的每一个方法,实际上是声明了一个配置参数
  • 方法名称就是参数名称
  • 返回值类型就是参数类型
  • 可以通过default来声明参数的默认值
  • 如果只有一个参数成员,一般参数名为value
  • 注解元素必须要有值,定义注解元素时,经常使用空字符串,0 作为默认值
import java.lang.annotation.*;

public class MyAnnotation {
    //作用在方法上,如果没有设置默认值,就必须赋值
    @MyAnnotation2(name = "awsl")
    public void test(){

    }
}

//作用在类上(ElementType),或者方法上(ElementType.METHOD)
@Target({ElementType.TYPE,ElementType.METHOD})
//注解的生命周期为RUNTIME
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@interface MyAnnotation2{
    //这个方法一样的就是注解中的参数,default是设置默认值
    String name() default "";
}


4.反射机制(Reflection)

  • Reflection是java被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法

    • Class c = Class.forName("java.lang.String")
  • 加载完类之后, 在堆内存的方法区中就产生了一个Class对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构

  • 将一个类的内部结构,一个一个都封装成一个对象(所有方法是一个对象,所有变量是一个对象,所有的构造器是一个对象)

  • 可以解耦,提高程序的可扩展性

  • 正常方式:

    1. 引入需要的“包类”名称
    2. 通过new实例化
    3. 取得实例对象
  • 反射方式:

  1. 实例化对象
  2. . getClass()方法
  3. 得到完整的“包类”名称

5.Class类

(这篇有对Class类有详细解释)https://blog.csdn.net/mcryeasy/article/details/52344729

每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。Class对象对应着java.lang.Class类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。相同类型和同一维度的实例对象的Class对象都是同一个

5.1 获取Class类的实例的方式

  1. 若已知具体的类,通过类的class属性获取,该方式最为安全可靠,程序性能最高
    Class clazz = Person.class;
  2. 已知某个类的实例,调用该实例的getClass()方法获取Class对象
    Class clazz = person.geiClass();
  3. 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取(将字节码文件加载到内存),可能会抛出ClassNotFoundException
    Class clazz = class.forName("demo.Student")
  4. 内置基本数据类型可以直接用 类名.Type
  5. 利用ClassLoader

5.2 Class类的方法

方法名说明
forName()(1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。(2)为了产生Class引用,forName()立即就进行了初始化。
Object-getClass()获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
getName()取全限定的类名(包括包名),即类的完整名字。
getSimpleName()获取类名(不包括包名)
getCanonicalName()获取全限定的类名(包括包名)
isInterface()判断Class对象是否是表示一个接口
getInterfaces()返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
getSupercalss()返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
newInstance()返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。
getFields()获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。
getDeclaredFields获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。
getConstructors()

5.2.1 Field:获取成员变量

getField(String name)获取指定名称的公共的成员变量,返回一个Field对象

getFields()获取这个class对象所有的公共的成员变量,返回一个Field对象

getDeclaredField(String name)获取指定成员变量,包括私有的,并返回一个Field对象

Field.get(Object obj)获取实例对象的成员变量的值,需要传入一个实例对象参数

import java.lang.reflect.Field;

public class TestGetClass {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
        Person person = new Person();
        // 三种获取class对象的方式
        Class c1 = Person.class;
        System.out.println(c1.hashCode());
        Class c2 = Class.forName("Demo2.Person");
        System.out.println(c2.hashCode());
        Class c3 = person.getClass();
        System.out.println(c3.hashCode());

        //通过.getFields()获取所有 public 成员变量,并返回一个Field对象数组
        //注意,这时候的.getClass() 获取的不是person对象,是一个class对象
        Field[] field1 = person.getClass().getFields();
        for (Field fields : field1) {
            //通过.get()获取这个成员变量的值
            System.out.println(fields.get(person));
        }
        
        //======================================================================
        
        //通过.getDeclaredField()获取指定成员变量,包括 private ,并返回一个Field对象
        Field field2 = person.getClass().getDeclaredField("b");
        //如果获取了私有变量的值,虽然会拿到值,但是会报修饰符安全检查的异常
        // .setAccessible(true)这里只需要设置忽略异常就可以
        field2.setAccessible(true);
        System.out.println(field2.get(person));
    }
}

class Person {
    public int a;
    private int b;
    public void test1() {
    }
}

5.2.2 Construcor:获取构造器

getConstructor(Class<?>... parameterTypes)获取无参构造或者有参构造(填上对应的有参参数列表类型),返回一个Constructor对象
Constructor.newInstance(Object ... initargs)通过构造器获取实例对象,可传入有参构造的值,返回一个object对象

public class TestGetClass {
    public static void main(String[] args) throws Exception {
        Person person = new Person();

        //通过.getConstructor()获取无参构造,或者有参构造,有参构造需要给对应的参数列表,返回一个Constructor对象
        Constructor<? extends Person> constructor=person.getClass().getConstructor();
        //通过构造器对象获得类对象(注意,这里不是class对象)返回一个object对象
        Object object = constructor.newInstance();
        System.out.println(object);
			
		//====================================================================
		
		//通过.getConstructor()获取无参构造,或者有参构造,有参构造需要给对应的参数列表,返回一个Constructor对象
        Constructor<? extends Person> constructor=person.getClass().getConstructor(int.class,int.class);
        //通过构造器对象获得类对象(注意,这里不是class对象)返回一个object对象,可以传入有参构造的值
        Object object = constructor.newInstance(10,20);
        System.out.println(object);
    }
}

class Person {
    public int a;
    private int b;

    public Person() {
    }

    public Person(int a, int b) {
        this.a = a;
        this.b = b;
    }
    public void test1() {
    }

    public int getA() {
        return a;
    }

    public void setA(int a) {
        this.a = a;
    }

    public int getB() {
        return b;
    }

    public void setB(int b) {
        this.b = b;
    }

    @Override
    public String toString() {
        return "Person{" +
                "a=" + a +
                ", b=" + b +
                '}';
    }
}

5.2.3 Method:获取方法

getMethods()获取class对象中的所有方法,返回值为Method对象数组
getMethod( String name, Class<?>... parameterTypes )获取class对象中指定的方法,第一个是方法名,第二个参数为有参方法的参数列表类型。返回值为Method对象
Method.invoke(Object obj, Object... args)调用方法,第一个参数为对象实例,第二个参数为方法参数值

public class TestGetClass {
    public static void main(String[] args) throws Exception {
        Person person = new Person();


        //获取这个person类的class对象的所有方法,返回一个Method对象
        Method[] method=Person.class.getMethods();
        for (Method method1 : method) {
            System.out.println(method1.getName());
        }

        //获取指定的class对象的方法,参数为方法名还有方法的参数列表的类型,返回一个Method对象
        Method method1 =Person.class.getMethod("test1",String.class);
        System.out.println(method1);
        //通过方法对象,调用方法,参数是实例对象和方法的参数
        method1.invoke(person,"张三");
    }
}

class Person {
    public int a;
    private int b;

    public Person() {
    }

    public Person(int a, int b) {
        this.a = a;
        this.b = b;
    }
    public void test1(String name) {
        System.out.println("名字是"+name);
    }
}

5.2.4 通过配置文件获取class和method

注意,配置文件要放在resources下,不然可能会找不到资源报空指针

public class TestGetClass {
    public static void main(String[] args) throws Exception {
  		//创建Properties对象
        Properties properties = new Properties();
        //获取class对象的类加载器
        ClassLoader classLoader = Person.class.getClassLoader();
        //从类加载器中找到配置文件,返回一个Inputstream
        InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
        //加载配置文件
        properties.load(resourceAsStream);
        //获取配置文件中的信息
        String className =properties.getProperty("className");
        String methodName=properties.getProperty("methodName");
        Class cls =Class.forName(className);
        //创建实例对象
        Object obj = cls.newInstance();
        //注意自己的方法有没有参数
        Method met = cls.getMethod(methodName,String.class);
        //调用方法
        met.invoke(obj,"张三");
    }
    }
}

pro.properties

className=Demo2.Person
methodName=test1

6. Java内存

    • 存放new的对象和数组
    • 可以被所有的线程共享,不会存放别的对象引用
    • 存放基本变量类型(会包含这个基本类型的具体数值)
    • 引用对象的变量(会存放这个引用在堆里的具体地址)
  • 方法区
    • 可以被所有的线程共享
    • 包含了所有class的static变量,方法、常量池、代码

7. 类的加载过程

引用自:https://blog.csdn.net/m0_38075425/article/details/81627349

加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。

类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础,JVM提供的这些类加载器通常被称为系统类加载器。除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。

通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源。

  • 从本地文件系统加载class文件,这是前面绝大部分示例程序的类加载方式。
  • 从JAR包加载class文件,这种方式也是很常见的,前面介绍JDBC编程时用到的数据库驱动类就放在JAR文件中,JVM可以从JAR文件中直接加载该class文件。
  • 通过网络加载class文件。
  • 把一个Java源文件动态编译,并执行加载。
    类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值