java--反射的基本概念

反射的基本概念
  如果正常的情况下,如果使用一个类,则必须按照如下的步骤操作:
    • 使用import 导入类所在的包;(类:java.lang.Class)
    • 明确的使用类名称或借口名称定义对象;
    • 通过关键字new进行类对象实例化;(构造方法:java.lang.reflect.Constructor);
    • 产生对象可以使用“对象.属性”进行类中属性的调用(属性:java.lang.reflect.Field);
    • 通过“对象.方法()”调用类中方法(方法:java.lang.reflect.Method);
  反射过程不需要有明确类型的对象,所有的对象使用Object表示
    1. 可以直接使用Object与反射机制的混合调用类中的方法。
Object类中的所有方法以及每一个方法使用上的注意事项
  • 对象克隆:protected Object clone​() throws CloneNotSupportedException 创建并返回此对象的副本。 “复制”的精确含义可能取决于对象的类。
    •   为什么克隆方法返回的是Object? 答:因为克隆方法可能针对所有类对象使用,为了统一参数用Object
    •   克隆对象所在的类一定要实现java.lang.Cloneable接口而子类只需要继续调用Object 的克隆方法就可以成功实现克隆操作;
  • 对象输出:public String toString​() 返回对象的字符串表示形式。
    •   直接输出对象时会默认调用toString()方法
      •   原因:由于平时我们会直接System.out.println();来直接输出对象,那么我们打开源码看一下为什么会默认调用toString()方法
                在printStream类中找到print输出方法 如下 
            public void print(Object obj) {write(String.valueOf(obj));}
                 可以看到他在输出的时候调用了String的valueOf方法,下面打开valueOf方法源码
             public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString(); }
            从中我们可以看到如果传入的对象不为null的话就会自动的调用toString()方法然后返回
  • 对象比较:public boolean equals​(Object obj) 指示一些其他对象是否等于此。
    •   有哪些时候会隐式调用此方法??
      • 当我们保存Set集合时,会依靠hashCode()和equals()判断对象是否重复;
  • 取得对象的hash码:public boolean equals​(Object obj) 指示一些其他对象是否等于此。
    •   可以理解为每一个对象的唯一编码,比较时会先判断编码是否相同,然后再调用equals方法判断是否相同
  • 取得Class类对象:public final Class<?> getClass​() 返回此Object的运行时类。
    •   通过一个已经实例化好的对象进行对象的反射操作;
  • 线程等待:public final void wait​() throws InterruptedException 导致当前线程等到另一个线程调用该对象的notify()方法或notifyAll()方法。 换句话说,这个方法的行为就好像简单地执行调用wait(0)
    •   执行到此代码时线程要等待执行,直到执行notify()或者notifyAll()方法来唤醒线程;
  • 一个线程唤醒:public final void notify​() 唤醒正在等待对象监视器的单个线程。
  • 全部线程唤醒:public final void notifyAll​() 唤醒正在等待对象监视器的所有线程。
  • 垃圾回收前释放:protected void finalize​() throws Throwable 已过时。 定稿机制本质上是有问题的。 定稿可能导致性能问题,死锁和挂起。
    •   当使用gc回收无用的垃圾空间时默认调用;
        
 
Class类
    class类是整个反射的操作源头,而类的定义如下:
    public final class Class<T> extends Object implements
          Serializable, GenericDeclaration, Type, AnnotatedElement
 
    如果想要使用Class类进行操作,那么必须首先产生Class类的实例化对象,而有三种方法可以去的Claas类的实例化对象
    • Object类提供了一个返回Class类对象的方法:public final Class<?> getClass​();
    • 利用“类.class”取得,日后建的最多的就是在Hibernate上;
    • 利用Class类的static方法取得,public static Class<?> forName​(String className) throws ClassNotFoundException  返回与给定字符串名称的类或接口相关联的Class对象。
      如果是程序设计人员,使用最多的方法一定是forName()方法,但是如果是使用者会使用“类.class”。工厂设计模式最好利用反射机制来解决耦合问题。
利用反射实例化对象
    Class类如果使用了forName()方法之后,就可以使用Class类定义的newInstance()方法默认去调用类之中的无参构造器进行操作
        public T newInstance​() throws InstantiationException, IllegalAccessException,此泛型使用不到
    代码演示: 在这里是不能接受的了这个返回值的,
         

 

    这里就是解决上面错误的代码实现: 从中我们就可以看到我们不一定非要使用new实例化对象,只要我们有一个类的完整名称也可以实例化对象
public class R {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("test.Student");
//jdk 1.9 开始直接使用的newInstance()方法已经过时,可以使用下面的方式来调用newInstance()方法 
//相当于关键字new实例化对象。等价于 Object newInstance = new Student();Object newInstance = cls.getDeclaredConstructor().newInstance(); } } 
class Student{ public Student() { System.out.println("构造方法Student"); 
} }
    执行结果:构造方法Student
 
    但是如果使用反射实例化对象,必须要求类中存在有无参构造方法,因为newInstance()方法只能找到无参。如果没有无参构造函数如下:
      Exception in thread "main" java.lang.NoSuchMethodException:  
     如果想找到无参构造怎么办?操作构造方法
操作构造方法:
   为了解决NoSuchMethodException错误,这个时候这能取得类之中的构造方法,传递所需要的参数后才能使用。
   在Class类里面定义了可以取得一个类中的构造方法的操作:
    •   public Constructor<T> getConstructor​(Class<?>... parameterTypes)                                    (重点使用)
throws NoSuchMethodException, SecurityException
       返回一个Constructor对象,及时取得类中制定参数的构造,该对象反映由此Class对象表示的类的指定公共构造函数。
    • public Class<?>[] getDeclaredClasses​() throws SecurityException
            返回一个Class对象的数组,就是全部构造,反映了所有被声明为由这个Class对象表示的类的成员的类和接口。
      代码演示:取得String中的全部构造方法。
    
import java.lang.reflect.Constructor;
 public class GetStringConstructor {
 public static void main(String[] args) throws Exception {
 Class<?> forName = Class.forName("java.lang.String");
 Constructor<?>[] constructors = forName.getConstructors();//得到所有构造
 for (int i = 0; i < constructors.length; i++) { System.out.println(constructors[i]); } } }

 执行结果:

public java.lang.String(byte[])
public java.lang.String(byte[],int,int)
public java.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int,int,java.nio.charset.Charset)
public java.lang.String(java.lang.StringBuilder)
public java.lang.String(java.lang.StringBuffer)
public java.lang.String(char[],int,int)
public java.lang.String(char[])
public java.lang.String(java.lang.String)
public java.lang.String()
public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int)
public java.lang.String(byte[],int,int,int)
public java.lang.String(int[],int,int
View Code

 

     所以如果现在想要进行制定构造方法的调用,就必须将关注点放在Contructor类中。
    public T newInstance​(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException  使用由此Constructor对象表示的构造函数,使用指定的初始化参数创建和初始化构造函数的声明类的新实例
 代码实现:实例化刚才的Student对象
  
import java.lang.reflect.Constructor;
class Student{
    public  Student(String str) {
        System.out.println("构造方法Student:    "+str);
    }
} 
public class GetStringConstructor {
 public static void main(String[] args) throws Exception 
{ Class<?> forName = Class.forName("test.Student");
 Constructor<?> constructors = forName.getConstructor(String.class);
//从这可以看到要与想调用的构造方法的参数类型一直才可以 
constructors.newInstance("纯菜鸟-java-反射"); } }

 

执行结果:构造方法Student:    纯菜鸟-java-反射
正是因为是通过构造方法实例化对象规格不统一。所以在进行简单java类的时候就明确的规定了必须有无参的构造方法
 
调用类中的方法
取得一个类的实例化对象之后,下面主要的任务就是要调用类之中可以使用的操作方法,对于一个类中的方法有两类
取得父类继承而来的方法:
取得全部方法:public 方法[] getMethods​() throws SecurityException 返回一个包含方法对象的数组
取得指定方法:public 方法 getMethod​(String name,Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException 返回一个方法对象
取得本类定义的方法:
取得全部方法:public 方法[] getDeclaredMethods​() throws SecurityException返回一个包含方法对象的数组,
取得指定方法:public 方法 getDeclaredMethod​(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException 返回一个方法对象,
代码示例:取得Student类中的方法
import java.lang.reflect.Method;
class Student {
 private String Student(String name,Integer age) {
 return name + " " + age;
 }
}
public class R {
 public static void main(String[] args) throws Exception {
 Class<?> cls = Class.forName("test.Student"); //获取对象
 {
 Method[] methods = cls.getMethods();
 for (int i = 0; i < methods.length; i++) {
 System.out.println(methods[i]);
 }
 }
 {
 System.out.println("=======================================");
 Method[] methods = cls.getDeclaredMethods();
 for (int i = 0; i < methods.length; i++) {
 System.out.println(methods[i]);
 }
 }
 }

 

执行结果:
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() 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 java.lang.String java.lang.Object.toString()
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()
=======================================
private java.lang.String test.Student.Student(java.lang.String,java.lang.Integer)
View Code

 

下面就是利用Method类来获取指定对象的方法的方法修饰符
      • public int getModifiers​() 返回此类或接口的Java语言修饰符,以整数编码。
由于此方法返回的是一个整数类型的值,我们并不好判断是什么类型的修饰符,例如public 代表1 static代表200,此时方法如果是public static 修饰,那么就返回201,所以这时候我们有一个Modifer类的toString()方法来进行根据返回的数值返回字符类型的修饰符,区别与Object 的toString()方法,
程序中找的不是Public,Static关键字,而是关键字所代表的数字
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
class Student {
 private String Student(String name,Integer age) {
 return name + " " + age;
 }
}
public class R {
 public static void main(String[] args) throws Exception {
 Class<?> cls = Class.forName("test.Student"); //获取对象
 {
 Method[] methods = cls.getDeclaredMethods();
 for (int i = 0; i < methods.length; i++) {
 System.out.println(methods[i].getModifiers());
 System.out.println(Modifier.toString(methods[i].getModifiers()));
 }
 }
 }
}

 

 

执行结果:
2
private
 
下面直接说一下Method中的获取方法的参数类型,返回值,方法名之类的一系列的方法的使用
代码范例:获取Student中的方法的详细信息
package test;
 
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
class Student {
 private static String rr(String name,Integer age) {
 return name + " " + age;
 }
 public final void tt() throws Exception{
 
 }
}
public class R {
 public static void main(String[] args) throws Exception {
 Class<?> cls = Class.forName("test.Student"); //获取对象
 {
 //只有在正真调用本类方法的时候才需要创建实例对象
 Method[] methods = cls.getDeclaredMethods();//获取对象的本类方法
 for (int i = 0; i < methods.length; i++) {
 //由于methods[i].getModifiers()返回的是修饰符的数值,可以用Modifier.toString()方法将其转换成字符类型的修饰符
 System.out.print(Modifier.toString(methods[i].getModifiers()));
 //得到方法的返回值类型,这时候返回的是全类名类似于java.lang.String,getSimpleName()可以的到简单类名
 System.out.print(" "+methods[i].getReturnType().getSimpleName());
 //返回方法名
 System.out.print(" "+methods[i].getName());
 //返回抛出的错误类型数组
 Class<?>[] exceptionTypes = methods[i].getExceptionTypes();
 if(exceptionTypes.length>0) {
 System.out.print(" throws ");
 for (int j = 0; j < exceptionTypes.length; j++) {
 //取得错误的简单类名
 System.out.print(exceptionTypes[j].getSimpleName());
if(exceptionTypes.length-1>j) {
System.out.print(",");
}
 }
 }
 System.out.print("(");
 //返回此时方法的全部参数类型
 Parameter[] parameters = methods[i].getParameters();
 for (int j = 0; j < parameters.length; j++) {
 //每一个参数类型得到类型后再次得到简单类名,方便看
 System.out.print(parameters[j].getType().getSimpleName()+" arg"+j);
 if(j+1<parameters.length) {
 System.out.print(",");
 }
 }
 System.out.print(")");
 System.out.println("{}");
 }
 }
 }
}

 执行结果:

private static String rr(String arg0,Integer arg1){}
public final void tt throws Exception(){}
所谓的随笔提示功能就是依据上述代码实现的,在Method中有一个最重要的方法Invoke();
调用:public Object invoke​(Object obj,Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
使用指定的参数调用由此方法对象表示的基础方法。
代码范例:反射调用Student中的方法
package test;
import java.lang.reflect.Method;
class Student {
 private String name;
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
}
public class R {
 public static void main(String[] args) throws Exception {
 Class<?> cls = Class.forName("test.Student"); //获取对象
 Object object = cls.getDeclaredConstructor().newInstance();//获取实例对象
 Method method = cls.getMethod("setName", String.class);//得到setName对象
 Method method2 = cls.getMethod("getName");
 method.invoke(object, "纯菜鸟");//setName方法赋值
 Object invoke2 = method2.invoke(object);//调用getName
 System.out.println(invoke2);
 }
}

 

执行结果:纯菜鸟
在开发之中,getter 和 setter必须严格编写
 
调用类中的一个属性(尽量不用)
关于类中的属性也可以直接利用反射进行操作,而支持的方法有两类
取得所有继承而来的属性
取得全部属性:public Field[] getFields​() throws SecurityException
返回一个包含Field对象的数组
取得指定属性:public Field getField​(String name) throws NoSuchFieldException, SecurityException 返回一个Field对象
取得本类定义的属性
取得全部属性:public Field[] getDeclaredFields​() throws SecurityException
返回一个Field对象的数组
取得指定属性:public Field getDeclaredField​(String name) throws NoSuchFieldException, SecurityException返回一个Field对象
代码示例:取得一个类中的全部属性、
package test;
 
import java.lang.reflect.Field;
 
interface People{
 public static final String age = "男";
}
class Person{
 private String name;
}
class Student extends Person implements People{
 private String school;
}
public class R {
 public static void main(String[] args) throws Exception {
 Class<?> cls = Class.forName("test.Student"); //获取对象
 {
 //获得本类的属性
 System.out.println("================本类属性==================");
 Field[] declaredFields = cls.getDeclaredFields();
 for (int i = 0; i < declaredFields.length; i++) {
 System.out.println(declaredFields[i]);
 }
 }
 {
 //获得父类的属性
 System.out.println("================父类属性==================");
 //在这是已经得到父类的对象,所以再调用本类的属性方法时是在调父类中的属性
 Field[] declaredFields = cls.getSuperclass().getDeclaredFields();
 for (int i = 0; i < declaredFields.length; i++) {
 System.out.println(declaredFields[i]);
 }
 }
 {
 //获得父类的属性
 System.out.println("================接口属性==================");
 Field[] declaredFields = cls.getFields();
 for (int i = 0; i < declaredFields.length; i++) {
 System.out.println(declaredFields[i]);
 }
 }
 }
}

 

 

执行结果:
================本类属性==================
private java.lang.String test.Student.school
================父类属性==================
private java.lang.String test.Person.name
================接口属性==================
public static final java.lang.String test.People.age
 
在Field类里面还定义有属性调用的方法:
设置属性内容:public void set​(Object obj,Object value) throws、
IllegalArgumentException,IllegalAccessException
将指定的对象参数上由此Field对象表示的字段设置为指定的新值
取得属性内容:public Object get​(Object obj)throws
IllegalArgumentException,IllegalAccessException
返回指定对象上由此Field表示的字段的值
代码范例:设置Student的值并且输出次值:
import java.lang.reflect.Field;
class Student {
 private String school;
}
public class R {
 public static void main(String[] args) throws Exception {
 Class<?> cls = Class.forName("test.Student"); //获取对象
 Object student = cls.getDeclaredConstructor().newInstance();
 Field school = cls.getDeclaredField("school");
 school.set(student, "河北软件");
 Object object = school.get(student);
 System.out.println(object);
 }
}

 

 

执行结果
Exception in thread "main" java.lang.IllegalAccessException: class test.R cannot access a member of class test.Student with modifiers "private"
at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Unknown Source)
at java.base/java.lang.reflect.AccessibleObject.checkAccess(Unknown Source)
at java.base/java.lang.reflect.Field.checkAccess(Unknown Source)
at java.base/java.lang.reflect.Field.set(Unknown Source)
at test.R.main(R.java:13)
看错误原因是因为这个参数的private 的属性封装引起的错误,
我们来看AccessibleObject这个类中的子类信息如下,Field是他的直接子类,我们再点开Executable就能看 到,他的子类是Constructor和Method(可能是因为汉化原因导致的单词汉化了),所以我们就知道了这三个类Method,Constructor,Field的父类中是AccessibleObject,正好这个父类中有一个取消封装操作的方法:
 
 

 

这就是父类中那个方法:public void setAccessible​(boolean flag)
将此反射对象的accessible标志设置为指示的布尔值。 值为true表示反射对象应该在使用 Java语言访问控制时抑制检查,也就是当设置为true时,属性就会取消封装了
代码更正:在上个代码示例中Field school = cls.getDeclaredField("school");后加入取消封装操作:school.setAccessible(true);代码就可以正常运行了。
执行结果:河北软件
在开发中,只需要灵活使用Constructor,Method,Field以及源Class就可以使用反射进行一系列的代码实现了
 
 
posted @ 2017-11-10 21:59  Bap  阅读(2098)  评论(0编辑  收藏  举报