JAVA重点类 反射 动态语言 静态语言 动态代理

1、Java反射机制概述

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

    加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(类对象:一个类只有一个Class对象) ,这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

补充:动态语言 和 静态语言

(1)动态语言

        动态语言是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。主要动态语言: Object-C.C#、JavaScript, PHP,Python, Erlang..

(2)静态语言

        与动态语言相对应的,运行时结构不可变的语言就是静态语言。如Java.C.C++。

总结:Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活

Exp:服务器已经跑起来了。前端Js发送请求想登陆,此时需要后台造登陆的对象;如果Js发送请求想注册,此时就需要后台造注册的对象,这就是动态运行造对象,此时就是用反射造对象。

1.1 Java反射机制提供的功能

        在运行时判断任意一个对象所属的类

        在运行时构造任意一个类的对象

        在运行时判断任意一个类所具有的成员变量和方法

        在运行时获取泛型信息

        在运行时调用任意一个对象的成员变量和方法

        在运行时处理注解

        生成动态代理

1.2  反射相关的主要API

        java.lang.Class:代表一个类(Class是类 class是关键字) Class类,描述类的类

        java.lang.reflect.Method:代表类的方法

        java.lang.reflect.Field:代表类的成员变量

        java.lang.reflect.Constructor:代表类的构造器

2、关于 java.lang.Class类的理解

类的加载过程:程序经过javac.exe 命令以后, 会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件(带main函数的类)进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为CLass的一个实例。

说明:

① Class就对应着一个运行时类。即运行时,这个类就出来了。所以编译时就不用去new 一个 Class

② 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过exp中的3种方法来获取此运行时类

Class对象可以是以下所有类型:

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

② interface,接口

③ [ ] 数组

④ enum:枚举

⑤ annotation:注解@interface

⑥ primitive type:基本数据类型

⑦ void

特别说明:类只有一个,所以反射获取的同一个类,都是一个类(变量会指向同一地址)。数组本身默认的是指向数组第一个数的地址,所以只要数组的元素类型和数组维度(一维数组,二维数组)一样,就是同一个地址。

exp:

public class Person{  //本文章中的所有代码都使用这个类

    private String name;

    public int age;

    public Person(String name, int age){

        this.name = name;

        this.age = age;

    }

    private Person(String name) {

        this.name = name;

    }

    public String getName(){

        return name;

    }

    public void setName(String name){

        this.name = name;

    }

    public int getAge(){

        return age;

    }

    public void show(){

        System.out.println("你好,我是08ff");

    }

    private String showNation(){

        System.out.println("我的国籍是"+nation);

         return nation;

    }

}

public class ReflectionTest{

    public void test3(){  //获取class实例的方式

        //方式一:调用运行时类的属性:.class。用Class类对象clazz1直接调用类的属性Person.class,获取Person类的结构,泛型不用也可以,但有的地方就需要强转

        Class<Person> clazz1 = Person.class;

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

         Person p1 = new Person(); //运行时,已经通过构造器造好了Person类p1对象

        Class clazz2 = p1.getClass(); //通过p1对象,反射找到Person类

        //方式三:通过Class类自己的静态方法:forName(String classPath)    classPath指类的完整名字(包含包)

        Class clazz3 = Class.forName("com.tt.java.Person"); //使用频率最高的方法,最能体现动态性,必须掌握

        //方式四:使用类的静态方法getClassLoader()获取类的加载器:ClassLoader     使用频率最低的方法,了解即可

        ClassLoader classLoader = ReflectionTest.class.getClassLoader();

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

    }

//反射之前,对于Person操作

    public void test1(){

        Person p1 = new Person("Tom",12); //1、用构造器创建Person类的对象 

        //2、通过对象,调用其内部的属性、方法

        p1.age=10;

        System.out.println(p1.toString());

        p1.show();

    //在Person类外部,不可以通过Person类的对象,调用其内部的私有结构

    //比如:name、showNation()以及私有构造器

    }

    //反射之后,对于Person操作

    public void test2(){

        Class clazz= Person.class; //反射Person类

        //通过反射,创建Person类对象

        Constuctor cons = clazz.getConstructor(String.class,int.class); //获取Person类构造器。 int.class 反射基本数据类型int,和String一样,也是Class的对象

        Object obj = cons.newInstance("Tom",12);//用反射获取的Person类构造器造对象

        Person p=(Person) obj; //将obj转换成它本身的Person类

      //通过反射,调用对象指定的属性、方法

        Field age = clazz.getDeclaredField("age"); //getDeclaredFields()方法获取当前运行时类中指定变量名的属性

        age.set(p,10);  //设置属性

        Method show = clazz.getDeclaredMethod("show");//通过反射调用show方法,

        show.invoke(p);/*show方法没有形参,所以,直接填对象p就可以了    invoke()调用方法,参数1,方法的调用者(Person类的某个对象),参数2:给方法形参赋值实参*/

       //通过反射,可以调用Person类的私有结构,比如私有构造器、方法、属性(封装性?)

        Constructor cons1 = clazz.getDeclaredConstructor(String.class); 

        cons1.setAccessible(true);  //保证当前属性可访问,否则访问private属性会报错

        Person p1 = (Person) cons1.newInstance("Jerry"); //用反射再实例化一个对象

        //调用私有属性

        Field name = clazz.getDeclaredField("name");

        name.setAccessible(true);  

        name.set(p1,"HanMeimei"); //直接将私有属性name的值修改成HanMeimei

        //调用私有方法

        Method showNation = clazz.getDeclaredMethod("showNation",String.class); 

        showNation.setAccessible(true);

        String nation = (String)showNation.invoke(p1,"中国");//相当于p1.showNation("中国"),nation来接收方法的返回值

        }       

}

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

不矛盾。封装性和反射是两回事。封装性主要是告诉你,没必要再去调private的东西,我public的已经把功能做的很好了。反射是,我有能力去调用你的private的东西。

疑问:通过直接new的方式或反射的方式都可以调用公共的结构,用哪个更好

答:静态写代码时,直接用new的方式比较好。程序动态运行时,用反射造对象

3、关于类的加载与ClassLoader的理解

3.1 类加载器的作用:

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

(2)类缓存:标准的JavasE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象。

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

exp:

public class ClassLoaderTest{

    public void test1(){

        ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();  //对于自定义类ClassLoaderTest,使用系统加载器进行加载

        ClassLoader classLoader1 = classLoader.getParent(); //调用系统加载器的getParent()方法获取扩展类加载器

        ClassLoader classLoader2 = classLoader1.getParent();//调用扩展类加载器的getParent()方法,无法获取引导类加载器,返回null

       //特别说明:引导类加载器主要负责加载java的核心类库,无法加载自定义类

    }

}

4、创建运行时类的对象(即用Class对象,创建对应类的对象

public void test4() throws IllegalAccessException, InstantiationException{ //第一个异常是空参构造器没有权限(private),第二个异常是没有空参构造器

    Class<Person> clazz = Person.class;  //获取Person类,赋值给Class对象 clazz

    //Class类的newInstance()方法,会创建对应的运行时类(这里就是Person)的对象。内部调用了运行时类的空参构造器

    Person obj = clazz.newInstance();//创建Person对象obj。注意如果不用泛型,则返回Object类型,就需要强转 即 = (Person)clazz.newInstance()

}

总结:

(1)要想newInstance()方法正常的创建运行时类的对象,要求:

① 运行时类必须提供空参的构造器

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

(2)复习:在javabean中要求提供-个public的空参构造器。原因:

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

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

5、体会反射动态性案例,随机造一个指定类的对象

public void testN(){

    int num= new Random().nextInt(3);//随机生成0、1、2

    String classPath = " ";

    switch(num){  //根据不同随机数指定要反射的类

        case 0:

            classPath = "java.util.Date"; //反射util.Date

        case 1:

            classPath = "java.sql.Date"; //反射sql.Date   注意:抽到这个会报异常,因为sql.Date没有空参构造器

        case 2:

            classPath = "com.tt.java.Person"; //反射Person

    }

    Object obj = getInstance(classPath); //根据随机数,创建运行时类的对象

}

    public Object getInstance() throws Exception(){

        Class clazz = Class.forName(classPath);

        return clazz.newInstance();

}

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

exp:

public class ReflectionTest{

    public void testField(){  //调用属性

        Class clazz = Person.class; 

        Person p =(Person)clazz.newInstance();//通过反射创建运行时的Person类对象

        Field name = clazz.getDeclaredField("name"); //getDeclaredField(String fieldName):获取运行类中指定变量名的属性

        name.setAccessible(true); //保证当前属性是可访问的,否则访问private属性会报错

        /*设置当前属性的值

        set(对象名,属性值)  对象名:指明要设置哪个对象的属性。  属性值:将此属性值设置为多少*/

        name.set(p,"Tom");

    }

    public void testMethod(){

        Class clazz = Person.class;

        //创建运行时类的对象

        Person p = (Person) clazz.newInstance();

        /*1、获取指定的某个方法

        getDeclaredMethod():参数1:指明获取的方法的名称  参数2:指明获取的方法形参列表 */

        Method show = clazz.getDeclaredMethod("show",String.class);

        show.setAccessible(true); //2、保证当前属性是可访问的,否则访问private属性会报错

        /*3、调用invoke()方法,参数1,方法的调用者(Person类的某个对象),参数2:给方法形参赋值实参

        invoke()的返回值即为对应类中调用的方法的返回值*/

        Object return Value=show.invoke(p,"CHN");

        Method showDesc = clazz.getDeclaredMethod("showDesc");//加装showDesc()是Person中的static方法

        showDesc.setAccessible(true);

        //如果运行时类没有返回值,invoke()返回null。invoke()第一个参数是Person对象,

        Object returnVal=showDesc.invoke(Person.class);

    }

    public void testConstructor() throws Exception{  //这种用法非常少,还是用反射空参构造器的时候多

        Class clazz = Person.class;

        //1.获取指定的构造器 getDeclaredConstructor():参数:指明构造器的参数列表

        Constructor constructor = clazz.getDeclaredConstructor(String.class)

        //2.保证此构造器是可访问的

        constructor.setAccessible(true);

        //3.调用此构造器创造运行时此类的对象

        Person per = (Person)constructor.niwInstance("Tom");

    }

}

6、反射的应用 动态代理  (一个通用代理类动态的去代理所有的被代理类)

6.1 代理模式的原理  

        使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

        之前为大家讲解过代理机制的操作,属于静态代理,特征是代理类和目标对象的类都是在编译期间确定下来,不利于程序的扩展。同时,每一个代理类只能为一个接口服务,这样一来程序开发中必然产生过多的代理。最好可以通过一个代理类完成全部的代理功能

6.2 动态代理的原理

(1)概述        

        动态代理是指客户通过代理类来调用其它对象的方法,并且是在程序运行时根据需要动态创建目标类的代理对象。

(2)动态代理使用场合:

               调试

               远程方法调用

(3)动态代理相比于静态代理的优点:

      抽象角色中(接口)声明的所有方法都被转移到调用处理器一个集中的方法中处理,这样,我们可以更加灵活和统一的处理众多的方法。

exp:静态代理举例

interface ClothFactory {

    void produceCloth();  

}

//代理类

class ProxyClothFactory implements ClothFactory{

    private ClothFactory factory;//用被代理类对象实例化

    public proxyClothFactory(ClothFactory factory){

        this.factory = factory;

    }

    public void produceCloth(){

        System.out.println("代理工厂做一些准备工作");

        factory.produceClooth();

        System.out.println("代理工厂做一些后续的收尾工作");

    }

}

class NikeClothFactory implements ClothFactory{

    public void produceCloth(){

        System.out.println("NIke工厂生产一批运动服");

    }    

}

main(){

    ClothFactory nike = new NikeClothFactory();//创建被代理类的对象

    ClothFactory proxyClothFactory = new ProxyClothFactory(nike);//创建代理类对象 

    proxyClothFactory.produceCloth();

}

//动态代理:/

interface Human{

    String getBelief();

    void eat(String food);

}

//被代理类

class SuperMan implements Human{

    public String  getBelief(){

        return "I believe I can fly!";

    }

    public void eat(String food){

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

    }

}

//代理类

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

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

public class ProxyFactory{  //代理类

    //这个方法的目的是,可以返回一个代理类的对象(返回值Object类)。解决问题一

    public static Object getProxyInstance(Object obj){  //形参obj为想要被代理的类的对象

        MyInvocationHandler handler = new MyInvocationHandler(); //MyInvocationHandler类已实现InvocationHandler接口

        handler.bind(obj);  //handler对象的obj就绑定了代理类

        /*Proxy类的静态方法:static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler ) 这个构造器内部已经通过反射把被代理类的方法都拿到手了,有兴趣可以看源码*/

        return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterface(),handler) //前两个形参解决问题一,第三个形参解决问题二

    }

}

class MyInvocationHandler implements InvocationHandler{ //实现InvocationHandler接口后,作为Proxy类的第三个参数

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

    public void bind(Object obj){

        this.obj = obj;     

    }

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

   //因此我们需要将被代理类要执行的方法a的功能声明在invoke()中

    public Object invoke(Object proxy, Method method, Object[ ] args) throws Throwable{ //InvocationHandler接口即重写invoke方法

        //形参proxy就是上面的被代理类的对象obj.getClass().getClassLoader()这个加载器创建的对象,method是代理类代用的方法,

        Object returnValue=method.invoke(obj,args);  //通过代理类的invoke方法,调用被代理对象要调用的方法

        return returnValue;//上述方法的返回值就作为当前类中的invoke的返回值

    }

}

main(){

    SuperMan superMan = new SuperMan();

    Human proxyInstance = (Human) ProxyFaceory.getProxyInstance(superMan); //proxyInstance即为已经获取Human类全部方法的代理类对象

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

    proxyInstance.getBelief();//调用上面的invoke()方法,上面的method就是这里的getBelief

    proxyInstance.eat("四川麻辣烫");//也是调用invoke()方法,上面的method就是eat,这时四川麻辣烫,就是被调用参数

}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值