第十五章:Java反射机制

2021311

11:30

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

 

Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。

 

通过反射,可以调用Person类的私有结构的。比如:私有的构造器、方法、属性

疑问1:通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用哪个?

//建议:用直接new的方式。

那么什么时候会使用反射的方式?

//这个关系到反射的特征:动态性,需要动态调用的时候用反射的方式

疑问2:反射机制与面向对象中的封装性是不是矛盾的?如何看待两个技术?

//不矛盾。

//封装性是:建议去调什么,给出的一些提示,解决的是开发中调用哪个的问题。比如可以直接用公共的,就不要去调私有的。公共的可以解决问题,也可能写了很多处理的逻辑,更适合用。私有的是内部使用的,暴露出的公共的结构中调用了私有结构,方便直接使用,没必要单独再去调私有的。

//放到单例模式上是:一个对象就可以解决问题,再造一个对象也是一样用,跟原来的对象一样的用法。既然给你造好对象了,直接用就可以了。

//反射解决的是能不能调的问题

 

关于java.lang.Class类的理解

1.类的加载过程:

    程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。

    接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件

    加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此

    运行时类,就作为Class的一个实例。

2.换句话说,Class的实例就对应着一个运行时类

Ø Class 对象只能由系统建立对象

Ø Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象

3.加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。(获取到的是同一个,== true,见下面代码

 

获取Class类的实例的四种方法

1前提:若已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高

实例:Class clazz = String.class;

2前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象

实例:Class clazz = “www.atguigu.com”.getClass();

3前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

实例:Class clazz = Class.forName(“java.lang.String”);

4)其他方式(不做要求)

ClassLoader cl = this.getClass().getClassLoader();

Class clazz4 = cl.loadClass(“类的全类名”);

//方式一:调用运行时类的属性:.class

Class clazz1 = Person.class;

 

//方式二:通过运行时类的对象,调用getClass()

Person person1 = new Person("Tom",12);

Class clazz2 = person1.getClass();

 

//方式三:调用Class的静态方法:forName(String classPath)

//通过全类名,使用频率最高

Class clazz3 = Class.forName("com.atguigu.java.Person");

 

//方式四:使用类的加载器:ClassLoader  (了解)

ClassLoader classLoader = ReflectionTest.class.getClassLoader();

Class clazz4 = classLoader.loadClass("com.atguigu.java.Person");

 

System.out.println(clazz1 == clazz2); //true

System.out.println(clazz1 == clazz3); //true

System.out.println(clazz1 == clazz4); //true

 

哪些类型可以有Class对象?

1class

外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

2interface:接口

3[]:数组

4enum:枚举

5annotation:注解@interface

6primitive type:基本数据类型

7void

Class c1 = Object.class;

System.out.println(c1); //class java.lang.Object

Class c2 = Comparable.class;

System.out.println(c2); //interface java.lang.Comparable

Class c3 = String[].class;

System.out.println(c3); //class [Ljava.lang.String;

Class c4 = int[][].class;

System.out.println(c4); //class [[I

Class c5 = ElementType.class;

System.out.println(c5); //class java.lang.annotation.ElementType

Class c6 = Override.class;

System.out.println(c6); //interface java.lang.Override

Class c7 = int.class;

System.out.println(c7); //int

Class c8 = void.class;

System.out.println(c8); //void

Class c9 = Class.class;

System.out.println(c9); //class java.lang.Class

 

int[] a = new int[10];

int[] b = new int[100];

Class c10 = a.getClass();

Class c11 = b.getClass();

System.out.println(c10); //class [I

System.out.println(c11); //class [I

// 只要数组的元素类型与维度一样,就是同一个Class

System.out.println(c10 == c11); //true

 

获取实例时候加上泛型,以后不用再强转了

Class<Person> clazz0 = Person.class;

Constructor<Person> constructor = clazz0.getConstructor(String.class, int.class);

Person tomCat = constructor.newInstance("TomCat", 15);

 

类加载器的作用class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

JVM 规范定义了如下类型的类的加载器:

Bootstap Classloader 
Extension Classloader 
System Classloader

 

引导类加载器:用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取

扩展类加载器:负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库

系统类加载器:负责java –classpath –D java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器

我们写的类是系统类加载器加载的

获取一个系统类加载器ClassLoader.getSystemClassLoader()

ClassLoader classloader = ClassLoader.getSystemClassLoader();

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

ClassLoader classLoader1 = ClassLoaderTest.class.getClassLoader();

System.out.println(classLoader1); //sun.misc.Launcher$AppClassLoader@18b4aac2 我们写的类是系统类加载器加载的

获取系统类加载器的父类加载器,即扩展类加载器

ClassLoader classLoader2 = classLoader1.getParent();

System.out.println(classLoader2); //sun.misc.Launcher$ExtClassLoader@28a418fc

获取扩展类加载器的父类加载器,即引导类加载器

获取不到,所以为null

String作为Java平台核心库的类,也是由引导类加载器加载的

ClassLoader classLoader3 = classLoader2.getParent();

System.out.println(classLoader3); //null

ClassLoader classLoader4 = String.class.getClassLoader();

System.out.println(classLoader4); //null

 

关于类加载器的一个主要方法:getResourceAsStream(String str):获取类路径下的指定文件的输入流

Properties:用来读取配置文件的两种方式

Properties pros = new Properties();

 

//读取配置文件的方式一:new一个FileInputStream

//此时的文件默认在当前的module下。

//要读取src目录下的文件,需要加上src\\的路径

//FileInputStream fis = new FileInputStream("jdbc.properties");

//FileInputStream fis = new FileInputStream("src\\jdbc1.properties");

//pros.load(fis);

 

//读取配置文件的方式二:

//使用ClassLoadergetResourceAsStream()生成InputStream

//配置文件默认识别为:当前modulesrc

ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();

InputStream is = classLoader.getResourceAsStream("jdbc1.properties");

pros.load(is);

 

String user = pros.getProperty("user");

String password = pros.getProperty("password");

System.out.println("user = " + user + ",password = " + password);

 

pros.load(InputStream is) 加载InputStream里的数据

pros.getProperty(String key) 获取对应的属性值

 

通过反射创建对应的运行时类的对象

Class对象的 newInstance() : 调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器

Class<Person1> clazz = Person.class;

Person p1 = clazz.newInstance();

默认返回Object类型的对象,用多态就不用强转类型了

 

要想此方法正常的创建运行时类的对象,要求:

1.运行时类必须提供空参的构造器(默认有的话也行)

2.空参的构造器的访问权限得够。通常,设置为public。

 

在javabean中要求提供一个public的空参构造器。原因:

        1.便于通过反射,创建运行时类的对象

        2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

 

获取当前运行时类的属性结构

getFields():获取当前运行时类及其父类中声明为public访问权限的属性

getFileds只能获取public,跟在不在一个包无关(也就是即使在一个包也取不了默认属性的)

getDeclaredFields():获取当前运行时类中声明的所有属性。(不分权限。但不包含父类中声明的属性)

属性的 权限修饰符  数据类型 变量名 各项都可以获取到

权限修饰符:getModifiers();//返回int型,可以用Modifier.toString(intmod)转换

数据类型:getType();//返回Class实例,可用getName()方法获取全类名

变量名:getName();

 

获取运行时类的方法结构

getMethods():获取当前运行时类及其所有父类中声明为public权限的方法

getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)

注解(有@符号):getAnnotations() //@com.atguigu.java1.MyAnnotation(value=hello)

权限修饰符:getModifiers()  //static会包含在这里

Modifier.toString(modifiers)

返回值类型:getReturnType()

方法名:getName()

形参列表:getParameterTypes()  //返回Class[]

抛出的异常:getExceptionTypes() //返回Class[],可以getName()

 

获取运行时类的其他结构

构造器:getConstructors() //获取当前运行时类中声明为public的构造器(没有父类的!)

getDeclaredConstructors() //获取当前运行时类中声明的所有的构造器

父类:getSuperclass() //getSuperclass() c小写

带泛型的父类getGenericSuperclass() //com.atguigu.java1.Creature<java.lang.String> 返回类型是ParameterizedTypeImpl,实现了ParameterizedType接口。没有泛型的话,此方法直接调用getSuperclass()返回父类了,(ParameterizedType)会发生强转错误

获取运行时类的带泛型的父类的泛型

getGenericSuperclass() 获取带泛型的父类后,

(ParameterizedType) 强转成ParameterizedType

getActualTypeArguments() 调用方法返回Class类型的 Type[]

Class clazz = Person.class;

 

Type genericSuperclass = clazz.getGenericSuperclass(); //返回类型是ParameterizedTypeImpl,实现了ParameterizedType接口。没有泛型的话,此方法直接调用getSuperclass()返回父类了,下面会强转错误

ParameterizedType paramType = (ParameterizedType) genericSuperclass;

//获取泛型类型

Type[] actualTypeArguments = paramType.getActualTypeArguments();

for(Type t : actualTypeArguments){

    //返回的是Class类型的,可强转成Class,调用getName()

//Type接口没有getName()方法。java.lang.String

    System.out.println(((Class)t).getName());

}

 

Type接口没有getName()方法,可强转成Class,调用getName()

强转后调用方法,应该把强转和对象用括号括起来,然后再调用方法。!!

((Class)t).getName()

 

实现的接口:getInterfaces() //不包含父类实现的

//interface java.lang.Comparable

父类实现的接口:getSuperclass().getInterfaces()

所在的包:getPackage() //package com.atguigu.java1

类声明的注解:getAnnotations() //@com.atguigu.java1.MyAnnotation(value=hi)

 

调用运行时类中指定的结构:属性、方法、构造器

注意:getField()getMethod()getConstructor()方法要求运行时类中构造声明为public,所以通常不采用此系列方法

通用步骤:

1.获取指定的结构(属性、方法、构造器)

2.保证此结构是可访问的(不管是不是public的,都可以设置一下)

3.使用该结构

一、操作运行时类中的指定的属性

getDeclaredField(String fieldName): ①获取运行时类中指定变量名的属性

//NoSuchFieldException 找不到会报这个异常

//IllegalAccessException 获取到了,但是属于非法访问。set()get()都不可以

setAccessible(true):②保证当前属性是可访问的

set(p1,1001) 参数1:指明设置哪个对象的属性参数 2:将此属性值设置为多少

get(p1) 参数:获取哪个对象的当前属性值

二、操作运行时类中的指定的方法

getDeclaredMethod("show",String.class) 参数1:指明获取的方法的名称 参数2:指明获取的方法的形参列表

setAccessible(true):保证当前方法是可访问的

Object chn = show.invoke(p,"CHN"); 调用方法的invoke():参数1:方法的调用者 参数2:给方法形参赋值的实参 其中invoke()的返回值即为对应类中调用的方法的返回值。

如果调用的运行时类中的方法没有返回值,则此invoke()返回null,而不是void对象

注意:invoke()实参列表不匹配的话,会报IllegalArgumentException

因为用不同的getDeclaredMethod()实参生成的是不同的Method对象,所以对应的只能匹配生成时的实参对应的重载方法

假如a对象用的是String,int两个参数,b对象用的是String一个参数。a对象调用invoke()只传入一个String会报错。

ab是两个不同的对象,互不影响,并不会根据a的参数去自动匹配执行b方法。

//IllegalArgumentException: wrong number of arguments 数量不对

//IllegalArgumentException: argument type mismatch 类型不对

调用静态方法:静态方法调用时候第一个参数可以是任意对象,可以写个null

showNameAge.invoke(null,"古木苏",17);

三、调用运行时类中的指定的构造器

getDeclaredConstructor(String.class) 参数:指明构造器的参数列表。也可以获取空参构造器,但是没必要,空参可以直接用Class对象newInstance()

setAccessible(true):保证当前构造器是可访问的

newInstance("Jerry") 调用此构造器创建运行时类的对象

 

关于setAccessible方法的使用

l MethodFieldConstructor对象都有setAccessible()方法。

l setAccessible启动和禁用访问安全检查的开关

l 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查

Ø 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true

Ø 使得原本无法访问的私有成员也可以访问

l 参数值为false则指示反射的对象应该实施Java语言访问检查。

 

动态代理&AOP面向切面编程

interface Human{

    String getBelief();

    void eat(String food);

}

 

class HumanUtil{

    public void method1(){

        System.out.println("=====通用方法一=====");

    }

    public void method2(){

        System.out.println("=====通用方法二=====");

    }

}

 

class SuperMan implements Human{

 

    @Override

    public String getBelief() {

        System.out.println("I believe I can fly,and I can save the world!");

        return "I believe I can fly,and I can save the world!";

    }

 

    @Override

    public void eat(String food) {

        System.out.println("SuperMan喜欢吃: " + food);

    }

}

 

/*

要想实现动态代理,需要解决的问题?

问题一:如何根据加载到内存中的被代理类,动态的创建一个代理类及其对象。

问题二:当通过代理类的对象调用方法a时,如何动态的去调用被代理类中的同名方法a

 */

 

class ProxyFactory{

    //调用此方法,返回一个代理类的对象。解决问题一

    public static Object getNewProxyInstance(Object obj){ //obj:被代理类的对象

        MyInvocationHandler handler = new MyInvocationHandler();

        handler.bind(obj);

 

        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),handler);

    }

}

 

class MyInvocationHandler implements InvocationHandler{

 

    private Object obj; //需要使用被代理类的对象进行赋值

 

    public void bind(Object obj){

        this.obj = obj;

    }

 

    //当我们通过代理类的对象,调用方法a时,就会自动的调用如下的方法:invoke()

    //将被代理类要执行的方法a的功能就声明在invoke()

    @Override

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //AOP面向切面编程

        //方法一和方法二之间动态插入其他方法

        //InvocationHandler中实现

        HumanUtil humanUtil = new HumanUtil();

        humanUtil.method1();

 

        //method:即为代理类对象调用的方法,此方法也就作为了被代理类对象要调用的方法

        //obj:被代理类的对象

        Object returnValue = method.invoke(obj, args);

 

        humanUtil.method2();

 

        //上述方法的返回值就作为当前类中的invoke()的返回值。

        return returnValue;

    }

}

 

public class ProxyTest {

    public static void main(String[] args) {

        SuperMan superMan = new SuperMan();

        //proxyInstance:代理类的对象

        Human proxyInstance = (Human) ProxyFactory.getNewProxyInstance(superMan);

 

        //当通过代理类对象调用方法时,会自动的调用被代理类中同名的方法

        String belief = proxyInstance.getBelief();

        System.out.println(belief);

        proxyInstance.eat("四川麻辣烫");

    }

}

 

202131217:13:35 完结。结尾拉到上面。

 

结尾

 

使用 Microsoft OneNote 2016 创建。 版权所有:古木苏