黑马程序员——从反射开始

------- android培训java培训、期待与您交流! ----------

类可以说是面向对象编程的象征,而反射描述了一个类的所有部分:Class对象、Constructor构造器、Field字段、Method方法、Modifier修饰符,甚至可以获取函数的参数和返回值类型。

反射:能够分析类能力的程序称为反射(reflective),

反射可以:

1.在运行时才分析类的能力。

2.在运行时才查看对象

3.实现通用的数组的操作方法

4.利用Method对象,类似C++的函数指针

 一、反射分析类的能力

写一个类出来

class SuperTest{}
interface TestInterface{}
class Test extends SuperTest implements TestInterface
{
	private String memberVal;
	public Test(String memberVal)
	{
		this.memberVal=memberVal;
	}
	Test()
	{
		this.memberVal="nothing";
	}
	public String getVal()
	{
		return memberVal;
	}	
	public void setVal(String memberVal)
	{
		this.memberVal=memberVal;
	}
}
首先是获取这个类的Class对象:

//第一种方法:		
Class cTest=Class.forName("Test");
//第二种方法:
Test t=new Test("x");
Class cTest2=t.getClass();
//第三种方法:
Class cTest3=Test.class;


注意:每个类只有一个class文件,所以比较Class对象时,可以直接==比较


获得Class对象后就可以对这个类进行分析了。

首先是分析类

class Test extends SuperTest implements TestInterface
这里有类名,父类,接口,实际上还有修饰符。这就对应了四个方法:

getModifiers();getName();getSuperclass();getInterfaces();

		System.out.println(Modifier.toString(cTest.getModifiers()));//getModifiers()返回此类或接口以整数编码的 Java 语言修饰符。再使用Modifier.toString(int)将编码转换成修饰符的字符表示
		System.out.println(cTest.getName());//getName()获取Class的名称
		System.out.println(cTest.getSuperclass().getName());//getSuperclass()获取Class的父类的Class对象
		Class[] cArr=cTest.getInterfaces();//获取Class所实现的全部接口的Class对象
		for(Class c:cArr)
		{
			System.out.println(c.getName());
		}
其次是对字段的分析:
private String memberVal;
这里有修饰符,类型,变量名,以及字段本身

获取类的字段Field对象使用的是

Field[] fs=cTest.getDeclaredFields();

返回的是在类中声明的所有字段,包括非公有的字段。如果只希望获得公有的字段,可以使用getFields(),如果希望获得某个特定的字段,则需要getField(String s);//s是变量名

对每个字段对象f,可以使用getModifiers();getType();getName();获得相应的内容:

		Field[] fs=cTest.getDeclaredFields();
		for(Field f:fs)
		{
			System.out.println(Modifier.toString(f.getModifiers()));
			System.out.println(f.getType());//获得字段的类型
			String varName=f.getName();//获得字段的名字
			System.out.println(varName);
			//f.setAccessible(true);//可以将私有字段设置为可获取到的。暴力反射,慎用
			//System.out.println(f.get(t));
			//f.setAccessible(false);
		}

=========

150527 add:

Modifier.isStatic(field.getModifiers())//判断域是否是静态域,如果是静态域,在列出对象的域的时候可以跳过



接着是对方法的分析:

public String getVal()
public void setVal(String memberVal)
这里有这里有修饰符,返回值类型,方法名,参数类型以及方法本身

获取类的方法Method对象的方法是:

Method[] ms=cTest.getDeclaredMethods();

与字段相同要使用特定的某个方法必须给出相应的参数列表的参数类型Class对象列表作为参数。

对每个方法对象m,可以使用getModifiers();getReturnType();getName();getParameterTypes()获得相应的内容:

		Method[] ms=cTest.getDeclaredMethods();
		for(Method m:ms)
		{
			System.out.println(Modifier.toString(m.getModifiers()));			
			System.out.println(m.getReturnType());//获取返回值类型
			System.out.println(m.getName());//获取方法名
			Class[] cs=m.getParameterTypes();//获取参数类型列表
			for(Class c:cs)
			{
				System.out.println(c.getName());
			}
		}

最后是对构造器的分析:

public Test(String memberVal)
Test()
这里有这里有修饰符,构造器名,参数类型以及构造器本身

获取类的构造器Constructor对象的方法是:

Constructor[] cs=cTest.getDeclaredConstructors();//注意,构造器虽然特殊,但是也是不加Declared只能获取到公有的构造器。

对每个构造器对象c,可以使用getModifiers();getName();getParameterTypes()获得相应的内容(但是getName()没啥意思- -):

		for(Constructor c:cs)
		{
			System.out.println(Modifier.toString(c.getModifiers()));
			System.out.println(c.getName());
			Class[] css=c.getParameterTypes();
			for(Class cc:css)
			{
				System.out.println(cc.getName());
			}
		}

对于已知类的成员、方法和构造器可以使用如下方法调用:

		//对于已知类的成员、方法和构造器可以使用如下方法调用
		Field f=cTest.getDeclaredField("memberVal");
		f.setAccessible(true);//因为memberVal是私有成员,需要暴力反射
		String s= (String) f.get(t);//get返回的是Object,需要强制转换类型。
		System.out.println(s);
		f.set(t,"z");//将t的memberVal设置为"z"
		Method m=cTest.getMethod("setVal",String.class);//方法名,类型的Class对象
		m.invoke(t,"y");//对象,新的参数		
		System.out.println(t.getVal());
		//获取默认构造函数创建的实例对象:
		cTest.newInstance();
		//获取特地构造函数创建的实例对象,必须先获取到对应参数的构造器对象,然后再调用其newInstance()方法名,类型的Class对象
		Constructor ctor=cTest.getConstructor(String.class);
		ctor.newInstance("haha");


补充:

1.获取字段的值使用的是get(obj)的方法,但是返回值是Object也就是说不能返回基本数据类型,如果需要返回基本数据类型,则需要使用对应的getXXX方法,比如getDouble(obj);或者使用get(obj),但是返回的是对应基本类型的包装类,比如Double。

2.setAccessible()是AccessibleObject的方法,Field/Method/Constructor都是其子类,继承了这个方法。这个方法还可以对一个数组全部进行设置。比如:

AccessibleObject.setAccessible(cTest.getDeclaredFields());//直接将Test类中的所有字段暴力反射。

3.静态方法不需要对象,所以在使用getMethod()时,第一个参数可为null。


二、数组的反射


数组如果有相同的元素类型和维度,则是相同的类型,具有相同的Class对象
1.具有相同维度和元素类型的数组属于同一个类,即是具有相同Class实例对象
2.代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class ,也就是说String[]是Object的子类
3.基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用。非基本类型的一维数组则既可以做Object也可以做Object[]
4.Arrays.asList()方法处理int[]和String[]的差异:基本数据类型的数组使用asList(),asList()将整个数组视为一个对象,当作只有一个元素;类类型的数组则可以正常转换。
5.Array工具类用于完成对数组的反射操作;(java.lang.reflect.*)//Array是反射工具,Arrays是集合工具。


这里需要提到3个Class类的方法:

1.isPrimitive();//判断Class对象是不是基本数据类型和void

2.isArray();//判断Class对象是不是数组

3.getComponentType();//这个方法是后来看文档发现的,可以直接判断数组元素的类型。

/*数组的反射

数组如果有相同的元素类型和维度,则是相同的类型
1.具有相同维度和元素类型的数组属于同一个类,即是具有相同Class实例对象
2.代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class
3.基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用。非基本类型的一维数组则既可以做Object也可以做Object[]
4.Arrays.asList()方法处理int[]和String[]的差异
5.Array工具类用于完成对数组的反射操作;(java.lang.reflect.*)
*/

import java.lang.reflect.*;
import java.util.*;
class ArrayClassDemo
{
	public static void main(String[] args)
	{
		//int[] a1=new int[4]{1,2,3,4};//如果在后面大括号内初始化数组,则不能写个数
		int[] a1=new int[]{1,2,3,4};
		int[] a2=new int[5];
		int[][] a3=new int[][]{{2,3,5},{3,6,9}};
		String[] a4=new String[]{"a","b","c","d","e","f"};
		
		newPrint(a1.getClass()==a2.getClass());
		//newPrint(a1.getClass()==a3.getClass());//不可比较
		//newPrint(a1.getClass()==a4.getClass());//
		
		//获取数组类型的名字
		newPrint(a1.getClass().getName());//[I  [表示数组  I表示int
		newPrint(a3.getClass().getName());//[[I [[表示二维数组 
		newPrint(a4.getClass().getName());//[Ljava.lang.String; L/classname
		//获取数组父类的名字,都是Object
		newPrint(a1.getClass().getSuperclass().getName());//java.lang.Object
		newPrint(a2.getClass().getSuperclass().getName());//java.lang.Object
		newPrint(a3.getClass().getSuperclass().getName());//java.lang.Object
		
		
		
		//向下转换,数组与Object的关系
		Object aObj1=a1;//基本数据类型int数组是类
		Object aObj2=a4;//String数组是类
		//Object[] aObj3=a1;//这个不能通过编译,因为Object[]表示a1中的元素是对象类,但是a1是中元素是基本数据类型int,所以不能通过编译
		Object[] aObj4=a3;//这个可以,因为二维数组元素可以看作是一维数组,一维数组是对象类
		Object[] aObj5=a4;//String是类
		
		newPrint(a1);//[I@1db9742
		
		newPrint(a4);//[Ljava.lang.String;@106d69c
		
		newPrint(Arrays.asList(a1));//[[I@1db9742]    public static <T> List<T> asList(T... a),基本数据类型不被认为是对象,而是把数组当作一个对象
		newPrint(Arrays.asList(a4));//[a, b, c, d, e, f]
		
		
		
		elemPrint(a3);
		
	}
	public static void newPrint(Object obj)
	{	
		System.out.println(obj);
	}	
	public static void elemPrint(Object obj)
	{	
		elemPrint0(obj);
		System.out.println();
	}	
	public static void elemPrint0(Object obj)
	{	
			/*
			static int	getLength(Object array) 
			以 int 形式返回指定数组对象的长度。
			
			static Object	get(Object array, int index) 
          返回指定数组对象中索引组件的值。
			
			
			*/	
		
		if(obj.getClass().isArray())//检查参数是否是数组
		{
			Object o=Array.get(obj,0);//Array.get(arr,index),第一个param是数组对象,第二个是元素的下标,返回的是Object类。
			newPrint(o.getClass().getName());
			
			int len=Array.getLength(obj);//<span style="font-family: Arial, Helvetica, sans-serif;">Array.getLength(obj)可以获取数组长度</span>

			System.out.print("[");
			for(int i=0;i<len-1;++i)
			{				
				elemPrint0(Array.get(obj,i));
				System.out.print(",");
			}
			elemPrint0(Array.get(obj,len-1));
			System.out.print("]");
		}
		else
		{
			System.out.print(obj);
		}
	}


三,关于可以变参数的传递

main的参数为一个字符串数组,1.5为了兼容1.4,当传入一个字符串数组,会把数组拆包,每一个元素都当作参数,这样就造成了参数个数不符合的问题
为了解决这一问题,在字符串数组前加(Object)告诉编译器这是一个对象,不要拆包;或者将字符串数组再次打包一次。但是后者有点效率低。


/**
需求:写一个程序,根据用户提供的类名,执行该类的main方法

步骤:
0.创建一个给程序使用的类
1.获取用户输入的类名,可以将类名作为参数传入程序的参数列表
2.判断参数列表是否合法
3.获取类名的字节码
4.获取main方法
5.反射方法调用main方法
*/
import java.lang.reflect.*;
class ForRun//打印传给main的参数
{
	public static void main(String[] args)	
	{
		for(String s:args)
		{
			System.out.println(s);
		}	
	}
}

class RunMain
{
	public static void main(String[] args)throws Exception
	{
		if(args.length<1)
			throw new RuntimeException("wrong arguments!");
		String className=args[0];
		String[] newArgs=new String[args.length-1];
		for(int i=1;i<args.length;++i)
		{
			newArgs[i-1]=args[i];			
		}
		
		Class cls=Class.forName(className);//获取传入的要被调用的类的对象 ClassNotFoundException
		
		Method m=cls.getMethod("main",String[].class);//获取其main方法的对象。NoSuchMethodException
		//m.invoke(null,new Object[]{newArgs});//将字符串数组再次打包,但是效率稍低。IllegalAccessException
		m.invoke(null,(Object)newArgs);
		//main的参数为一个字符串数组,1.5为了兼容1.4,当传入一个字符串数组,会把数组拆包,每一个元素都当作参数,这样就造成了参数个数不符合的问题
		//为了解决这一问题,在字符串数组前加(Object)告诉编译器这是一个对象,不要拆包
		//或者将字符串数组再次打包一次。但是后者有点效率低
	}	
}

class UserRun//传递须调用的类的main以及传给main的参数
{	
	public static void main(String[] args)throws Exception
	{		
		RunMain.main(new String[]{"ForRun","a","b","c"});	
	}	
}


四、内存泄漏

/*
ArrayList 可以添加同一元素
HashSet 不可以添加重复元素
但是HashSet默认的比较方法是hascode,如果是两个属性相同的对象仍然算成2个元素,
需要覆盖:如果两个对象属性相等,hashcode也应该相同
从1万个元素中判断是否有与将存入的元素相同的,如果是用equals会效率非常低下,所以为了提高效率使用hashcode,值相同则表示是同一对象。
HashCode

当一个对象被存储进来HashSet后就不能再修改这个对象中的那些参与计算的哈希值字段了,否则对象修改过后的哈希值与最初存入hashset时的哈希值就不同了。
面试题:java中是否有内存泄漏,为什么?内存泄漏就是这个对象我不需要用,但是没有被释放掉。举例来说,hashset中存入某个对象后,又对此对象参与hashcode计算的字段进行修改,这样就导致了此对象hashcode的改变,之后如果对此对象单独进行删除,则无法找到此对象,而会误以为已经删除,实际上仍然存在,导致内存的泄漏。
*/


import java.lang.reflect.*;
import java.util.*;
import java.io.*;
class ReflectPoint
{
	private int x;
	public int y;
	public String str1="ball";
	public String str2="basketball";
	public String str3="itcast";
	ReflectPoint(int x,int y)
	{
		this.x=x;
		this.y=y;
	}
	public String toString()
	{
		return str1+":"+str2+":"+str3;
	}
	
	public int hashCode() //如果x,y相同则hash值相同
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}
	public boolean equals(Object obj) //如果x,y相同则相同
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		ReflectPoint other = (ReflectPoint) obj;
		if (x != other.x)
			return false;
		if (y != other.y)
			return false;
		return true;
	}
}

class ReflectDemo
{
	public static void main(String[] args) throws Exception
	{
		
		InputStream in= new FileInputStream("config.properties");//配置文件内写的是className=ArrayList或者HashSet
		
		Properties props=new Properties();
		props.load(in);
		String className=props.getProperty("className");//现在是HashSet
		
		in.close();
		
		Collection c=(Collection) Class.forName(className).newInstance();	
		
		//Collection c=new ArrayList<ReflectPoint>();
		//Collection c=new HashSet<ReflectPoint>();
		ReflectPoint pt1=new ReflectPoint(4,6);
		ReflectPoint pt2=new ReflectPoint(5,8);
		ReflectPoint pt3=new ReflectPoint(4,6);
		
		c.add(pt1);
		c.add(pt2);
		c.add(pt3);
		c.add(pt1);
		//newPrint(c);
		newPrint(c.size());
		
		pt1.y=7;//如果是Arraylist则可以删除,因为它不是用hashcode来判断是否相同。(注意,这里指的是覆盖过equals方法,通过判断属性字段的值判断是否相等)
		c.remove(pt1);//无法完成,因为改变y的值导致hashcode变化了,jvm去不同的hashcode区域寻找pt1,找不到,这就造成内存泄漏//面试题
		
	}
	public static void newPrint(Object obj)
	{	
		System.out.println(obj);
	}	
	
	
}










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值