黑马程序员--反射、内省、类加载器

--------------android培训java培训、学习型技术博客、期待与您交流! --------------

一、反射(Reflect)

1、概述

   反射就是分析类的具体能力,就是将Java类的各种成分映射成相应的java类。

   在学习反射时,一定要记住,一切的操作都将使用Object完成,类、数组的引用都可以使用Object进行接收。

2、Class类

      Class类是所有Java类的类,是反射的基石。

      在正常情况下,需要先有一个类的完整路径引入后,才能按照固定的格式产生实例化对象。但是在java中也允许通过一个实例化对象,来找到一个类的完整信息,这就是Class的功能。其实按照面向对象来说,类也是对象,所以Java中提供了一个Class类来封装这些对象。如有一个Person类,我们可以通过new运算符新建一个对象,Person p = new Person();我们也可以通过p.getClass()方法来获取该对象的类。

     一个Java类经过编译后由.java文件变成了.class文件,也叫字节码文件,在我们需要创建对象的时候,就是通过类加载器加载这些字节码,然后经过一系列操作,产生对象。

     获取字节码的三种方式:

      第一种:运用对象直接获取字节码,对象.getClass()

                  如:Person p = new Person();

                          Class clazz = p.getClass();

      第二种:直接用类名获取,类名.class

                 如:Class clazz = Person.class;

      第三种:用类名作为字符串获取,Class.forName(类名)

                如:Class clazz = Class.forName("java.lang.String");

       第三种是获取字节码的最常用方式,仅传入字符串就行,便于扩展

      Class.forName(类名)的作用:当内存中没有该类时,就用类加载器将类加载进内存并缓存,然后返回该类字节码;若内存中有,则直接返回该类字节码。

      万物皆对象,java中的基本类型数据也是对象,而物以类聚,则基本数据类型也有其对应的类,java中有九种预定义的Class 对象,表示八个基本类型和 void。这些类对象由 Java 虚拟机创建,与其表示的基本类型同名,即booleanbytecharshortintlongfloatdouble。 当然数组也有。

   一个类的构成元素有很多,所以Class类中的方法有很多。类中有父类,有接口,有构造函数,有成员函数,有成员变量。Class类中常用的操作有: 

    static Class forName(String className)

        返回与给定字符串名的类或接口的相关联的Class对象。

    Constructor getConstructor(Class<?>... parameterTypes)

        返回Constructor对象,它反映此Class对象所表示的类的指定公共构造方法。

    Constructor getConstructors()

          返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共构造方法。

    Field getField(String name)

        返回一个Field对象,它表示此Class对象所代表的类或接口的指定公共成员字段。

    Field[] getFields()

        返回包含某些Field对象的数组,表示所代表类中的成员字段。

    Method getMethod(String name, Class<?>... parameterTypes)

        返回一个Method对象,它表示的是此Class对象所代表的类的指定公共成员方法。

    Method[] getMehtods()

        返回一个包含某些Method对象的数组,是所代表的的类中的公共成员方法。

    String getName()

        以String形式返回此Class对象所表示的实体名称。

    String getSuperclass()

        返回此Class所表示的类的超类的名称

    boolean isArray()

        判定此Class对象是否表示一个数组

    boolean isPrimitive()

        判断指定的Class对象是否是一个基本类型。

    T newInstance()

        创建此Class对象所表示的类的一个新实例。

   注意:通过newInstance()方法来新建对象时,一定要保证该类中有无参构造方法,如果不存在,是无法实例化的。因此,写类时最好编写无参构造方法。

     Class应用示例

package cn.itheima.blog8;

public class Person {

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

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public String toString(){
		return name+ ":" + age + gender;
	}
}

 

package cn.itheima.blog8;

public class ReflectPersonClass {

	/**
	 * 获取字节码及常用操作演示
	 */
	public static void main(String[] args) {

		Person p = new Person("mike", 22, "male");
		//类名.getClass()获取字节码
		Class clazz1 = p.getClass();
		//类名.class获取字节码
		Class clazz2 = Person.class;
		Class clazz3 = null;
		try {
			//Class.forName()获取字节码,可能会类不存在异常
			 clazz3 = Class.forName("cn.itheima.blog8.Person");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
		//字节码只有一份,全部相等
		System.out.println(clazz1 == clazz2);
		System.out.println(clazz2 == clazz3);
		
		//判断是否为数组
		System.out.println(clazz1.isArray());
		//判断是否为八种基本数据类型	
		System.out.println(clazz1.isPrimitive());
		//获取父类
		System.out.println(clazz1.getSuperclass());
		//获取类的名称
		System.out.println(clazz1.getName());
	}

}

 

3、Constructor(构造函数)

     反射是将java类中各种成分映射为java类,因此需要java类去接收。Java类中的构造函数的类为Constructor。

     作为构造函数,有自己的方法签名,修饰符,还可以通过构造函数来初始化对象等等操作。

     运用构造函数初始化对象的步骤:

          (1)取得字节码,用字节码的getConstructors()方法获取所有构造方法,或者根据参数类型来取得指定构造方法。

          (2)通过构造函数的newInstance()初始化实例,若有参数需要传入参数,得到的对象为Object类型,需要进行强转。

     Constrctor用法示例:

package cn.itheima.blog8;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

public class ReflectPersonCon {

	/**
	 * 运用反射获取构造函数并进行实例化
	 */
	public static void main(String[] args) throws Exception {
		//获取Person的字节码
		Class clazz = Class.forName("cn.itheima.blog8.Person");
		
		//获取Person所有的构造函数,并打印
		Constructor[] constructors = clazz.getConstructors();
		for(Constructor con : constructors){
			System.out.println(con);
		}
		
		//获取参数为(String, int, String)的构造函数
		Constructor constructor = clazz.getConstructor(String.class, int.class, String.class);
		
		//通过构造函数初始化对象,构造方法得到的是Object,需要强转
		Person p = (Person) constructor.newInstance("mike", 22, "male");
		System.out.println(p);
		
		//获取构造函数的访问修饰符
		/*
		 * 访问修饰符是以数字形式作为返回值,所以需要用Modifier的静态方法toString转成public
		 */
		System.out.println(constructor + "的访问修饰符为:" + Modifier.toString(constructor.getModifiers()));
		
	}

}

 

4、Field(成员变量)

     Field类用来表示的是java类中的成员变量部分。变量有公有,也有私有,若得到私有成员,是无法访问的,需要暴力破解。代码演示如下

package cn.itheima.blog8;

import java.lang.reflect.Field;

public class ReflectPersonField {

	/**
	 * 通过反射获取对象的属性
	 * @throws SecurityException 
	 * @throws Exception 
	 */
	public static void main(String[] args) throws Exception {
		//new Person对象
		Person p = new Person("mike", 22, "male");
		//获取字节码
		Class clazz = p.getClass();
		//获取公共属性
		Field[] fields = clazz.getFields();
		for(Field field : fields){
			System.out.println(field);
		}
		
		System.out.println("-----------------------");
		
		//获取所有属性
		Field[] fields2 = clazz.getDeclaredFields();
		for(Field field : fields2){
			System.out.println(field);
		}
		
		//获取指定私有属性name
		Field field = clazz.getDeclaredField("name");
		//由于属性是私有的,所以需要强行获取,也称暴力破解
		field.setAccessible(true);
		System.out.println(field.get(p));
		
		//获取指定公有属性
		Field field2 = clazz.getField("gender");
		System.out.println(field2.get(p));
	}

}


 

5、Method(成员方法)

    Method类用来表示的是java类中的成员变量部分。方法存在重载形式,因此当要确定某一个方法的时候,仅仅通过函数名是无法确定的,还需要参数来确定。代码演示如下

package cn.itheima.blog8;

import java.lang.reflect.Method;

public class ReflectPersonMethod {

	/**
	 * 反射得到方法,并调用方法演示
	 */
	public static void main(String[] args) throws Exception {
		//new Person对象
		Person p = new Person("mike", 22, "male");
		//获取字节码
		Class clazz = p.getClass();
		//获取所有public修饰的方法
		Method[] methods = clazz.getMethods();
		for(Method method : methods){
			System.out.println(method);
		}
		
		//获取所有方法
		Method[] methods2 = clazz.getDeclaredMethods();
		
		//获取方法名为getName,无参数的方法
		/*
		 * 由于方法存在重载,所以唯一确定一个函数除了需要函数名,还需要参数
		 */
		Method method = clazz.getMethod("getName", null);
		System.out.println(method);
		//调用方法,
		System.out.println(method.invoke(p, null));
	}

}


 

6、数组的反射

   反射包中使用Array类表示一个数组,通过此类可以取得数组长度,取得数组内容的操作。Class类中有getComponentType()方法,取得数组的数据类型的字节码文件。Array中有newInstance(Class<?> componentType, int length)方法,根据已有的数组类型开辟新的数组对象。现修改数组大小示例代码如下:

package cn.itheima.blog8;

import java.lang.reflect.Array;

public class ReflectArray {

	/**
	 * 通过反射修改数组的大小
	 */
	public static void main(String[] args) {
		int[] temp = {1,2,3};
		System.out.println("temp数组的长度为:" + temp.length);
		int[] newTemp = (int[])changArrLen(temp, 5);
		System.out.println("变动后的数组长度为:" + newTemp.length);
	}

	public static Object changArrLen(Object temp, int len) {
		Class clazz = temp.getClass().getComponentType();
		Object newObj = Array.newInstance(clazz, len);
		int length = Array.getLength(temp);
		System.arraycopy(temp, 0, newObj, 0, length);
		return newObj;
	}
}


 

7、反射的作用--实现框架的功能

     反射式一种功能强大而且复杂的机制,使用它的主要对象是工具构造者,而不是应用程序员(摘自java核心技术卷1)。

     工具类:java.util包中有许多工具类,如ArrayList,需要时直接new,然后用,非常方便。这是因为工具类是开发者事先写好的。

     框架:框架也是开发者写好的,但是我们用的不是框架的代码,而是框架用我们的代码,框架写出来的时候,框架作者是不知道框架谁会用他的框架,这就是矛盾所在。因此框架开发者会提供一些借口规范,然后通过反射来用我们所写的内容。

   

二、内省(IntroSpector)

     内省是Java 语言对JavaBean类属性、事件的一种缺省处理方法。

1、JavaBean

    JavaBean是一种特殊的类,主要用于数据的传递,也称为值对象,这种Java类中的方法主要用于访问私有的字段,且方法符合某种特殊的命名规则。若一个类当做JavaBean来使用时,JavaBean的属性可以根据方法名推断出来的,它根本看不到java类内部的成员变量。

    JavaBean中方法的命名规范:

          若有方法getName()/setName()则表明该JavaBean中有name属性,且该属性不是boolean型;方法名将get/set去掉后,若仅有第一个字母为大写,则属性名为将大写字母改成小写后得到的名称

         若有方法isFlag/setFlag()则表明该JavaBean中有f属性,且属性为boolean型;若有is开头的方法,表明为布尔型属性,去掉is后将第一个字母大小改小写。

         若有方法getCPU/setCPU则表明该JavaBean中有属性,为CPU;方法名将get/set去掉后,若仅第一个与第二个字母均为大写,则仅需去掉get/set

         JavaBean的用处很广泛,如JEE开发中有三层结构,将Jsp+servlet+JavaBean想结合,用JavaBean封装数据;Java提供了一套对JavaBean操作的Api,叫做内省,通过这套api可以比用普通方法操作JavaBean更方便。

2、JavaBean的两种内省方式

      (1)通过PropertyDescriptor类,获取名称对应的PropertyDescriptor对象,再用getReadMethod()或者getWriteMethod()方法获取get,set方法,最后用Method中的invoke()进行操作。

     (2)通过 Introspector.getBeanInfo(Class<?> beanClass)获取BeanInfo对象,然后获得所有的PropertyDescriptor,迭代该数组,依次判断名称获取指定方法,最后进行invoke()操作。

具体代码演示如下

package cn.itheima.blog8;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class IntroSpectorDemo {

	/**
	 * 设有Person类,与前面的相同
	 * 通过内省操作getName()方法,获取name属性的值,并打印
	 * 
	 */
	public static void main(String[] args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException {

		Person p = new Person("mike", 20, "male");
		String propertyName = "name";
		getNameProperty_2(propertyName, p);
	}
	
	//第一种方式:获取name属性的值
	public static <T> void getNameProperty_1(String propertyName, T t) throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
		//通过属性名与类名获得PropertyDescriptor对象
		PropertyDescriptor prop = new PropertyDescriptor(propertyName, t.getClass());
		//获取get方法
		Method getPropName = prop.getReadMethod();
		//调用get方法
		System.out.println(getPropName.invoke(t));
	}
	
	//第二种方式:获取name属性的值
	public static <T> void getNameProperty_2(String propertyName, T t) throws IntrospectionException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
		//通过类获取BeanInfo对象
		BeanInfo bean = Introspector.getBeanInfo(t.getClass());
		//获取所有的PropertyDescriptor对象
		PropertyDescriptor[] pds = bean.getPropertyDescriptors();
		//遍历数组,寻找与name属性对应的方法
		for(PropertyDescriptor pd : pds){
			if(propertyName.equals(pd.getName())){
				//获取属性的get方法
				Method getPropName = pd.getReadMethod();
				//调用get方法
				System.out.println(getPropName.invoke(t));
			}
		}
		
	}
}


 

3、BeanUtils工具包

       由于JavaBean的用途广泛,为了方便操作,所以Apach就提供了一套工具类,BeanUtils工具包,beanutils工具包的操作中运用了另一个jar--commons-logging,所以要用该工具类,需要在工程下拷入两个jar包。

       Eclipse中添加jar包,在工程写简历一个lib文件夹,将beanutils与commons-logging包复制到该jar包中,然后鼠标右键点击lib目录,选择build path,最后选择add build path。该种添加jar包的方式比较常用,因为其中的jar包跟着工程移动,不以开发环境为转移。

      BeanUtils包的功能比较强大,可以对八种数据类型进行自动类型转换,设置值时,可设置为字符串,取出来时仍是字符串,但是存入javabean中却是以基本数据类型存储,当有非基本数据类型存入是,也可以进行转换,但是需要先注册转换器Converter;同时,BeanUtils还支持属性级联操作,如将Date中有setTime方法,我们在设置Date型值是就可以birthday.time。 BeanUtils还提供JavaBean与Map进行转换等等一系列方法。运用示例如下

package cn.itheima.blog8;

import java.lang.reflect.InvocationTargetException;

import org.apache.commons.beanutils.BeanUtils;

public class BeanutilsDemo {

	/**
	 * BeanUtils运用演示
	 * 
	 */
	public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
		
		Person p = new Person("mike", 22, "male");
		String propertyName = "age";
		//运用BeanUtils
		System.out.println(BeanUtils.getProperty(p, propertyName));
		System.out.println(BeanUtils.getProperty(p, propertyName).getClass());
	}

}


 

三、类加载器

1、概述

类加载器就是加载类的工具,就是将.class文件加载到内存,并转成Class类的一个实例。

系统默认有三个类加载器:BootStrap,ExtClassLoader,AppClassLoader。类加载器也是一个类,然后最源头的类加载器(BootStrap)却不是,它潜在虚拟机内核中,由C++语言编写,因此打印他的名称为null。类加载器存在父子树状结构,不同的类加载器有不同加载范围,如下图所示

2、委托机制

为了更好地集中管理字节码,让内存中的字节码只有一份,类的加载就用到了委托机制。当一个类需要被一个类加载器加载时,先找其父类,让其父类加载,然后其父类又先不加载,先给父类的父类加载,依次类推,若上层的类加载不到,再由子类加载,直到加载结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值