【Java学习笔记】API:反射

Java反射机制

  • 反射是java的动态机制,可以允许我们在程序[运行期间]再确定实例化,调用某个方法,操作某个属性。
  • 反射机制大大的提高了代码的灵活度,但是会有更高的系统开销和较慢的运行效率。
  • 因此反射机制不能被过度的使用

获取一个类的类对象的常见方式

类对象  Class的实例

JVM在加载一个类的class文件时,就会同时创建一个Class的实例,使用该实例记录加载的
类的一切信息(类名,有哪些属性,哪些方法,哪些构造器等)。并且每个被JVM加载的类都有
且只有一个Class的实例与之对应。
反射的第一步就是获取要操作的类的类对象,以便程序在运行期间得知要操作的类的一切信息
然后对其进行响应的操作。

获取类对象

自定义一个Person类:

package reflect;

/**
 * 使用当前类测试反射机制
 */
public class Person {
    private String name =  "燕子李三";
    private int age = 18;
    public Person() {                                              //无参构造器
    }

    public Person(String name, int age) {                          //有参构造器
        this.name = name;
        this.age = age;
    }
    public void sayHi(){                                           //sayHi()方法
        System.out.println("大家好!我叫"+name+",今年"+age+"岁了");
    }
    public void watchTV(){
        System.out.println(name+"在看电视");
    }
    public void sing(){                                         //sing()方法
        System.out.println(name+"在唱歌");
    }
    public void sayNo(String name){                        //sayNo(String name)方法
        System.out.println(name+":sayNo");
    }
    public void doSomeThing(String something){
        System.out.println(name+"正在"+something);
    }
    public void doSomeThing(String something,int count){
        for(int i = 1;i<count;i++){
            System.out.println(name+"正在"+something+i+"次");
        }
    }
    private void dosome(){                                         //私有方法
        System.out.println("我是Person的私有方法dosome()!!!!!!");
    }

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

方式一:类名.class

例如:
Class cls = String.class;
Class cls = int.class;

注意:基本类型获取类对象只有这一种方式。

Class cls = String.class;//获取String的类对象
Class cls1 = ArrayList.class;//获取ArrayList的类对象
Class cls = Person.class;//获取自定义Person的类对象

方式二:Class.forName(String className)

例如:
Class cls = Class.forName("java.lang.String");
这里传入的类名必须是类的完全限定名,即:包名.类名

//通过控制台输入类名获取类对象
Scanner scanner = new Scanner(System.in);
System.out.println("请输入类名:");
String className = scanner.nextLine();
/*
   类名要全称:包名(一级包.二级包.等等).类名
   java.util.ArrayList
   java.util.HashMap
   java.io.ObjectInputStream
   java.lang.String
   reflect.Person//自定义Person类的包名全称
*/
Class cls = Class.forName(className);

方式三:通过类加载器形式

无参构造器实例化对象

有参构造器实例化对象并初始化

类对象的相关方法

获取类名

Class cls = String.class;//获取String的类对象

//获取当前类对象所表示的类的完全限定名
String name = cls.getName();
System.out.println(name);//java.lang.String

//仅获取类名(不包含包名)
name = cls.getSimpleName();
System.out.println(name);//String

获取包名

Package getPackage()
获取当前类对象所表示的类的包,返回的Package实例表示该包信息
Class cls = String.class;//获取String的类对象
Package pack = cls.getPackage();
String packName = pack.getName();
System.out.println("包名:"+packName);//包名:java.lang

获取方法

* java.lang.reflect.Method类,方法对象 该类的每一个实例用于表示某个类中定义的一个方法,通过它可以获取其表示的方法中的 相关信息(方法名,参数个数,参数类型,返回值类型等等,并且还可以调用这个方法)
* Method[] getMethods():获取多个
获取当前类对象所表示的类中定义的所有公开方法,包含从超类继承下来的方法

* Method getMethod(String name, Class<?>... parameterTypes):获取一个
根据参数列表获取当前类对象所表示的类中定义的一个公开方法,包含从超类继承下来的方法
它们都是获取Class所表示的类的所有公开方法,包含从超类继承的
* Method[] getDeclaredMethods():获取多个 
* Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取一个
这两个方法获取的都是Class所表示的类中当前类自身定义的方法。包含私有方法

注:包名、类名、方法名等不能胡写,胡写会抛出一个异常,需要try..catch或者throws抛出异常来判别有没有找到该类,没有找到则抛出一个异常

实际项目中不能在main方法上写throws,否则出现异常会直接杀掉主线程

若只是作为测试Demo,无所谓

补充:

构造方法不是咱们所说的方法,只是在中文翻译中带有方法名

在英文中构造方法是构造器(Constructor),方法英文是Method,不是同一个东西

Class cls = Person.class;//获取Person的类对象
//main方法的当前类与Person在同包中
//若不在同一个包中,需导入:import reflect.Person;
Method[] methods = cls.getMethods();//私有方法获取不到
for(Method method : methods){
    System.out.println(method.getName()+"()");
}
/*
toString()
watchTV()
sayHi()
sayNo()
sing()
doSomeThing()
doSomeThing()
wait()//从这里开始:后面都是继承自Object的方法
wait()
wait()
equals()
hashCode()
getClass()
notify()
notifyAll()
*/

使用反射机制实例化对象 

 使用无参构造器进行实例化 

只能通过要实例化类的无参构造器进行实例化

只要你有无参构造器就可以实例化,若没有构造器,系统会默认提供一个无参构造器

而若重载了构造方法,系统就不再默认提供无参构造方法,想要通过无参构造器实例化对象就要自己写一个无参构造方法

补充:在定义类时,若重载了构造方法,一般都会加上无参构造方法,为了之后有些功能使用

/*
使用无参构造器进行实例化
*/

//之前固定的实例化对象写法
Object p = new Person();
System.out.println(p);//Person{name='燕子李三', age=18}
//p.toString()

//1获取要实例化的类的类对象

          Class cls = Class.forName("reflect.Person");
         //方式一:创建一个reflect.Person的类对象
         //一开始就确定了:forName()中是什么类型对象,就实例化该对应类的对象

        //方式二:通过控制台来确定类名,再实例化该类对象
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要实例化的类名:");
        String className = scanner.nextLine();
        Class cls = Class.forName(className);//根据控制台来确定

//2类对象直接提供了可以通过公开的无参构造器实例化的功能
        Object obj = cls.newInstance();
        System.out.println(obj);
//流不能测试,因为流基本都没有无参构造器
//JDKk后来不建议用newInstance方法,因为不是所有的类都是公开无参的
//在实际的项目中:改代码就得重新编译(成字节码)

/*输出:
reflect.Person{name='燕子李三', age=18}
*/

 使用有参构造器进行实例化

/*
使用有参构造器进行实例化
*/

//之前固定的实例化对象写法
Person p = new Person("王五",55);
System.out.println(p);//Person{name='王五', age=55}

Class cls = Class.forName("reflect.Person");//获取类对象
        
//cls.getConstructor();//不传任何参数时获取的仍然是无参构造器
Constructor c = cls.getConstructor(String.class,int.class);、
//若有包装类型,传参时可以自动拆装箱
//获取Person的构造器Person(String,int)
        
Object obj = c.newInstance("王五",55);//实例化时要传入构造器要求的实际参数
//此步相当于之前写法的后半句:new Person("王五",55);
System.out.println(obj);//Person{name='王五', age=55}

 使用反射机制调用方法

 调用无参方法

//之前固定写法
        Person p = new Person();
        p.sayHello();

//动态调用:调用的是无参方法
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入类名:");
        String className = scanner.nextLine();
        System.out.println("请输入方法名:");
        String methodName = scanner.nextLine();

//1.实例化
        //Class cls = Class.forName("reflect.Person");//此写法写死了类名
        Class cls = Class.forName(className);//获取控制台输入的类对象
        //Object obj = new Person();
        Object obj = cls.newInstance();//通过无参构造器实例化类对象

//2.调用方法
   //2.1通过类对象获取要调用的方法
        //Method method = cls.getMethod("sayHello");//方法写死:获取的是无参的sayHello方法
        Method method = cls.getMethod(methodName);//获取要调用控制台输入方法名的无参方法
  //2.2通过获取的方法对象(method)来调用该方法
        method.invoke(obj);
       //obj.sayHello()  因为obj指向的是一个Person对象,因此反射机制可以调用到它的sayHello()

/*该步骤传入的是一个obj的类型,而obj类是没有要输入的(Person)方法的,
所以应该这样理解:这个不等同于之前的用对象.方法名来调用方法
Person类的实例化对象只是用了多态的方式来接收,而创建的method方法对象它实质就是Person类的方法
所以在该步中,method会自动判别Person对象,最后通过Person来调用,而不是让Object来调用*/

调用有参方法

//调用有参方法

Class cls = Class.forName("reflect.Person");//获取类对象
Object obj = cls.newInstance();//实例化对象
Method method = cls.getMethod("doSomeThing", String.class);
//从第二个参数开始,传的是String类型的参数

//通过获取的方法对象(method)来调用该方法
method.invoke(obj,"开启王者开黑模式");
method.invoke(obj,"进入疯狂飙车模式");
Method method1 = cls.getMethod("doSomeThing", String.class, int.class);
method1.invoke(obj,"进行百米冲刺",10);
method1.invoke(obj,"医院人工抢救",100);
System.out.println("没有了,没有奇迹了!");

/*
燕子李三正在开启王者开黑模式
燕子李三正在进入疯狂飙车模式
燕子李三正在进行百米冲刺1次
燕子李三正在进行百米冲刺2次
...
燕子李三正在进行百米冲刺9次
燕子李三正在医院人工抢救1次
燕子李三正在医院人工抢救2次
...
燕子李三正在医院人工抢救99次
没有了,没有奇迹了!
*/

实例一:

/**
 * 自动调用reflect.Person类中所有无参且方法名中含有say的方法
 * 提示:
 * Method类上定义了一个方法:int parameterCount(),该方法可以返回当前Method对象表示的
 * 方法的参数个数。
 */
package reflect;

import java.lang.reflect.Method;

//自动调用reflect.Person类中的所有无参方法
public class Test{
    public static void main(String[] args) throws Exception {
        Class cls = Class.forName("reflect.Person");
        Object obj = cls.newInstance();
        Method[] methods = cls.getMethods();
        for(Method method : methods){
            if(method.getName().contains("say") && method.getParameterCount()==0){
                System.out.println("自动调用方法:"+method.getName());
                method.invoke(obj);//让每个符合条件的方法对象调用方法
            }
        }
        //wait():是Object类中的方法
    }

调用私有(private)方法

//调用私有方法

//使用之前的写法:无法调用其他类私有方法
//Person p = new Person();
//p.dosome();//编译错误:dosome()为Person的私有方法,只能自己调用


Class cls = Class.forName("reflect.Person");
Object obj = cls.newInstance();
        
//Method[] methods = cls.getDeclaredMethods();//获取Person类的所有方法,包括私有方法
//for(Method method : methods){
//     System.out.println(method.getName());//可以正常输出
//     method.invoke(obj);
//   }
//但是在遍历过程中,碰到私有方法无法调用,
//抛出异常:ava.lang.IllegalArgumentException: wrong number of arguments
//私有方法访问权限为private,private代表只能再本类中调用

Method method = cls.getDeclaredMethod("dosome");//获取一个Person类的dosome()无参方法
method.setAccessible(true);//强行打开dosome方法的访问权限
method.invoke(obj);//我是Person的私有方法dosome()!!!!!!

//正常情况下,不应该这么做,破坏了原有类的封装性
/*
方法名使用private访问权限:一般有这几种情况:
1.为了重用代码:提成了一个方法
2.重复利用的功能:作为功能性方法,完成某个功能
3.根据罗列的步骤:将每个步骤定义成方法
*/

定义方法:变长参数

* JDK5之后推出了一个特性:变长参数
* 该特性用于适应那些传入参数的个数不固定的使用场景,使得使用一个方法就可以解决该问题,而无   
  需穷尽所有参数个数组合的重载。
* 一个方法里只能有一个变长参数,且必须是最后一个参数
public class ArgDemo {
    public static void main(String[] args) {
        dosome(1);//变长参数可以一个参数不传,数组长度为0
        dosome(2,"one","two");
        dosome(3,"one","two","three");
    }
    //有变长参数的方法
    public static void dosome(int a,String... arg){
        //变长参数本质就是数组,...同[],都是表示一个数组
        System.out.println(arg.length);
        System.out.println(Arrays.toString(arg));
    }
}
/*
0
[]
2
[one, two]
3
[one, two, three]
*/

获取类路径的两种写法

/*
    两个开发中常用的相对路径
 */

package reflect;
import java.io.File;

public class ReflectDemo7 {
    public static void main(String[] args) throws Exception {
       //此main位置抛异常只作为demo
   //1.这里的当前目录表示的是当前ReflectDemo7这个类所在最外层包的上一级目录
    File dir = new File(
    ReflectDemo7.class.getClassLoader().getResource(".").toURI());
    System.out.println(dir.getName());//获取相对路径
    //CGB2202_SE
    System.out.println(dir.getAbsoluteFile());//获取绝对路径
   //E:\CGB2202_SE\out\production\CGB2202_SE

  System.out.println(dir);//直接输出dir也是绝对路径

   //2这里的当前目录就是当前类所在的目录
   File dir1 = new File(
   ReflectDemo7.class.getResource(".").toURI());
   System.out.println(dir1.getName());//获取相对路径
   //reflect
   System.out.println(dir1.getAbsoluteFile());//获取绝对路径
  //E:\CGB2202_SE\out\production\CGB2202_SE\reflect
  }
}

 补充:

虚拟机执行时不看源代码(路径),真实对应的是编译后的字节码目录,执行的是字节码文件,

所以该方法调用后返回的路径代表的是out目录中的字节码文件路径

实例二:

/**
 * 自动调用与当前类ReflectDemo7所在同一个包中,自动调用本类自己定义的无参的公开方法
 */
package reflect;

import java.io.File;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectDemo7 {
public static void main(String[] args) throws Exception {
     File dir = new File(ReflectDemo7.class.getResource(".").toURI());
       //这里的当前目录就是当前类所在的目录
        String packageName = ReflectDemo7.class.getPackage().getName();
      //获取当前类的包名:reflect
        File[] subs = dir.listFiles(f->f.getName().endsWith(".class"));
      //获取ReflectDemo7.class文件所在的目录中所有.class文件
      //为了严谨,筛选出后缀名为.class的字节码文件
        for(File sub : subs){
            String fileName = sub.getName();//获取每一个子项中字节码文件的文件名(含后缀)
            System.out.println(fileName);
            //获取每一个子项的名字:
            /*
                由于java命名要求,文件名必须与类名一致,所以我们可以通过文件名得知该字节码
                文件中保存的类的类名
             */
            //进行拼接
            String className = fileName.substring(0,fileName.indexOf("."));
            //由于类名与文件名(不含编译后的后缀名)一致,所以将编译后的后缀名去掉得到类名
            System.out.println("类名:"+className);

            //加载该类的类对象
            Class cls = Class.forName(packageName+"."+className);
            System.out.println("加载的类为:"+cls.getName());
            Object obj = cls.newInstance();
            //获取方法
            Method[] methods = cls.getDeclaredMethods();
           //getDeclaredMethods是获取本类中自己定义的方法(不包含继承的方法)

      //补充:若用getMethods,它会继承超类的方法
      //而继承自Object的wait()在调用时会报错,所以在用getMethods时要将继承自Object的方法排除

            for(Method method : methods){
            //排除获取方法中的私有方法及无参方法
        if(method.getParameterCount()==0 && method.getModifiers()== Modifier.PUBLIC){
                   System.out.println("自动调用"+className+"的方法:"+method.getName());
                   method.invoke(obj);
                }
            }
        }
    }
}

补充:main方法中的参数

 * main方法上的参数String[] args的作用是:
 * 在命令行上使用java命令指定当前类时,可以传递参数进来,此时会被main上的String[] args
   接收
 * 例如:
 * java ReflectDemo7 arg1 arg2 arg3
 * main方法执行后,args数组就有三个元素,对应的就是"arg1","arg2","arg3"

 注解

 * 注解
 * 注解在开发中常被我们利用到反射机制中,辅助反射机制做更多灵活的操作
 * 注解在如今JAVA流行的框架中被大量的应用,简化了以前繁琐的配置工作。
 *
 * 注解可以在:
 * 类上,属性上,方法上,构造器上,以及参数上使用
 * 可以通过java内置的注解@Target来说明当前注解可以被应用的位置,对应的值被定义在  
 * ElementType上

 * 例如:
 * @Target(ElementType.TYPE)  注解只能被用于类上
 * @Target({ElementType.TYPE,ElementType.METHOD}) 注解只能被用于类上或方法上
 * 当可以用于多个位置时,需要定义成数组的方式包含所有ElementType的值,即"{}"包含
 *
 * @Retention注解,用于标注当前注解的保留级别,有三个选项
 * RetentionPolicy.SOURCE 注解仅保留在源代码中
 * RetentionPolicy.CLASS 注解保留在字节码中,但是反射机制不能调用
 * RetentionPolicy.RUNTIME 注解保留在字节码文件中,并且可以被反射机制所使用
 * 当不指定@Retention时,默认的保留级别为CLASS,因此我们通常都需要明确指出保留级别为
 * RUNTIME

 使用注解定义类

package reflect.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//定义一个类的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoRunClass {

}

使用注解定义方法

定义参数的格式为:
格式:类型 参数名() [default 默认值]
注:default可选,用于为当前参数定义默认值。如果不指定,则使用注解时必须为此参数赋值。

使用注解传参时格式:
@注解名(参数名1=参数值1[,参数名2=参数值2,....])

如果注解@AutoRunMethod只有一个参数,且参数名为num时,那么使用时格式如下:
@AutoRunMethod(num=1)

=============重点=============
如果注解中只有一个参数,参数名建议选取value,这样的好处是,使用时可以不指定参数名,如:
@AutoRunMethod(1)

如果指定了默认值,则可以不指定参数,例如:
@AutoRunMethod()   此时注解中参数的使用default的默认值
package reflect.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoRunMethod {
//为注解定义一个int型的参数
//int num() default 1;//一个参数时,参数名不建议选取value以外的名字。
    int value() default 1;


}

boolean isAnnotationPresent(参数传入的是一个指定泛型的类对象):

用于判断其表示的内容是否被某个注解标注了,除了类对象Class之外,像方法对象Method,属性对象Field等都有该方法

public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
{
    return AnnotatedElement.super.isAnnotationPresent(annotationClass);
}
//通过方法对象获取该注解,传入的是一个指定泛型,返回的就是该类型
//AutoRunMethod arm = method.getAnnotation(AutoRunMethod.class);

在反射机制中运用注解@: 

自定义两个类:Person类和Student类

package reflect;

import reflect.annotations.AutoRunClass;
import reflect.annotations.AutoRunMethod;

/**
 * 使用当前类测试反射机制
 */
//Person类:被注解
@AutoRunClass
public class Person {

    private String name = "张三";
    private int age = 18;

    public Person(){}


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

    @AutoRunMethod(3)                                  //sayHello():被注解
    public void sayHello(){
        System.out.println(name+":hello!");
    }
    @AutoRunMethod(5)                                 //watchTV():被注解
    public void watchTV(){
        System.out.println(name+":看电视");
    }
    @AutoRunMethod                                   //sayHi():被注解
    public void sayHi(){
        System.out.println(name+":Hi!");
    }

    public void sing(){
        System.out.println(name+":唱歌");
    }

    public void doSomeThing(String something){
        System.out.println(name+"正在做"+something);
    }
    public void doSomeThing(String something,int count){
        for(int i=0;i<count;i++) {
            System.out.println(name + "正在做" + something + i + "次");
        }
    }
    private void dosome(){
        System.out.println("我是Person的私有方法dosome()!!!!!!");
    }



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

import reflect.annotations.AutoRunClass;
import reflect.annotations.AutoRunMethod;

//Student类:被注解
@AutoRunClass
public class Student {
    @AutoRunMethod(7)                                                //study():被注解
    public void study(){                            
        System.out.println("Student:good good study!day day up!");
    }


    public void playGame(){
        System.out.println("Student:玩游戏!");
    }
}

 实例

/**
 * 自动调用与Test4在同一个包中那些被@AutoRunClass标注的类中所有被@AutoRunMethod标注的方法
 * n次,n对应的是注解@AutoRunMethod传入的参数值
 */
package reflect; 

public class Test4 {
    public static void main(String[] args) throws Exception {
        File dir = new File(Test4.class.getResource(".").toURI());
        //通过当前类Test4的类对象获取所在的包名
        String packageName = Test4.class.getPackage().getName();
        //获取Test4.class文件所在的目录中所有.class文件
        File[] subs = dir.listFiles(f->f.getName().endsWith(".class"));
        for(File sub : subs) {
            //获取字节码文件的文件名
            String fileName = sub.getName();
            String className = fileName.substring(0, fileName.indexOf("."));
            //加载该类的类对象
            Class cls = Class.forName(packageName + "." + className);
            if(cls.isAnnotationPresent(AutoRunClass.class)){
                Object o = cls.newInstance();
                //获取该类定义的所有方法
                Method[] methods = cls.getDeclaredMethods();
                for(Method method : methods){
                    if(method.isAnnotationPresent(AutoRunMethod.class)){
                       AutoRunMethod arm = method.getAnnotation(AutoRunMethod.class);
                       int value = arm.value();

                       System.out.println("自动调用"+className+"被@AutoRunMethod注释的
                       方法:"+method.getName()+"():"+value+"次");
                        for(int i = 0;i<value;i++){
                            method.invoke(obj);
                    }
                }

            }
        }
    }
}

/*
自动调用Person类的方法:watchTV()5次
张三:看电视
张三:看电视
张三:看电视
张三:看电视
张三:看电视
自动调用Person类的方法:sayHello()3次
张三:hello!
张三:hello!
张三:hello!
自动调用Person类的方法:sayHi()1次
张三:Hi!
自动调用Student类的方法:study()7次
Student:good good study!day day up!
Student:good good study!day day up!
Student:good good study!day day up!
Student:good good study!day day up!
Student:good good study!day day up!
Student:good good study!day day up!
Student:good good study!day day up!
*/

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值