黑马程序员----反射

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------

反射

1.什么叫反射

当我们写一个类时,可能这个类要使用到很多以后出现的类,那么这个时候我们怎么使用这些类呢?
我们知道,之前我们在类中使用某个类,都是已知了这个类的名字,方法等等,直接用就可以了,这是因为这个类已经存在了。而那些以后出现的类我们要怎么使用呢?
也就是说我们不再是在写程序时就确定了使用的类,而是在程序运行时获取这些类的信息等来使用,这时我们就需要使用到了反射机制,使用反射机制,我们可以通过配置文件中的完成类名(包名+类名)得到类的字节码文件,通过该文件对象获取到构造函数,字段,方法等,进而使用该类。
反射:运行时动态获取某个类的信息
黑马一位前辈所说:反射就是把Java类中的各个成分映射成为Java类

获取字节码文件的三种方式:

//获取字节码文件的三种方法
class GetClassFile 
{
	public static void main(String[] args) throws Exception
	{
		Class cls1 = String.class;
		Class cls2 = "helong".getClass();
		Class cls3 = Class.forName("java.lang.String");

		if(cls1==cls2 && cls2==cls3)
			System.out.println("这说明了同一个类的字节码文件内存中只存在一份");
	}
}
运行图:



2.类的成分

既然我们要将Java类的各个成分映射成为Java类,那么我们肯定需要知道Java类都有哪些成分:

构造函数Constructor:

/*
测试反射中构造函数的应用
*/
class ConstructorDemo 
{
	public static void main(String[] args) throws Exception
	{
		//普通方式
		String str1 = new String(new StringBuffer("helong"));

		//反射方式
		String str2 = (String)Class.forName("java.lang.String")
			.getConstructor(Class.forName("java.lang.StringBuffer")).newInstance(new StringBuffer("helong"));

		//Class的newInstance方法,使用默认构造函数
		String str3 = (String)Class.forName("java.lang.String").newInstance();
		
		System.out.println("str1:"+str1);
		System.out.println("str2:"+str2);
		System.out.println("str3:"+str3);
	}
}
运行图:


属性字段Field:

/*
测试:Field成分的使用以及暴力反射问题。
*/
import java.lang.reflect.*;
class Student
{
	public String name;
	private int age;
	Student(String name,int age)
	{
		this.name=name;this.age=age;
	}
}
class FieldDemo 
{
	public static void main(String[] args) throws Exception
	{
		
		Student stu = new Student("helong",22);
		//获取并打印Student的name和age属性
		//get方法需要指定对象,因为Field只是代表一个字段,并没有与具体对象相关
		String name=(String)Class.forName("Student").getField("name").get(stu);
		System.out.println("Name:"+name);

		//int age=(int)Class.forName("Student").getField("age").get(stu);
		//第一次报错:NoSuchFieldException:当成员为private时,
		//我们不能通过getField来获取了,而是使用getDeclaredFiled(str)
		//第二次报错:can not access member “age”,当成员为私有时,需要先设置
		//使其能够被访问到,使用方法setAccessible(boolean flag)
		Field age = Class.forName("Student").getDeclaredField("age");
		age.setAccessible(true);
		int ageV = (int)age.get(stu);
		System.out.println("Age:"+ageV);

	}
}
运行图:

在这里我们发现个小知识点,那就是利用反射,我们可以获取到类的字段,而且通过getDeclaredField方法和setAccessible方法,我们可以访问到私有字段,这就是所谓的暴力反射。虽然在某些时候会派上用场,但是不得不说它也破坏了封装性,因此使用需谨慎。


/*
获取一个对象的所有String属性,并将这些属性的值中的'b'改成'a',利用反射
*/
import java.lang.reflect.*;
class Student
{
	public String name;
	public String hobby;
	public String address;
	public int age;
	Student(String name,String hobby,String address,int age)
	{this.name=name;this.hobby=hobby;this.address=address;this.age=age;}
}
class FieldTest 
{
	public static void main(String[] args) throws Exception
	{
		Student stu = new Student("booby","ball","tangshan",22);

		//1.获取所有字段
		Field[] fields = Class.forName("Student").getFields();
		//2.循环获取其中String类型,先打印,进行值的改变,再打印
		for(int i=0;i<fields.length;i++)
		{
			//这句代码作用是排除那些非String类型的字段
			//为什么用==呢?这是因为对于String来说,它的字节码文件在内存只有一份,因此地址也就一份
			//因此可以用==,而不用使用equals
			if(!(fields[i].getGenericType() == String.class))continue;
			String name = fields[i].getName();
			String value = (String)fields[i].get(stu);
			System.out.println(name+":"+value);
			value = value.replaceAll("b","a");
			fields[i].set(stu,value);
			value = (String)fields[i].get(stu);
			System.out.println(name+":"+value);
		}
	}
}
运行图:



方法Method:

/*
测试:成员方法的反射应用
*/
import java.lang.reflect.*;
class MethodDemo 
{
	public static void main(String[] args) throws Exception
	{
		//调用String.charAt方法
		String str = "helong";

		//普通调用
		System.out.println(str.charAt(3));

		//反射调用
		System.out.println(String.class.getMethod("charAt",int.class).invoke(str,3));

		//反射调用静态方法
		System.out.println(String.class.getMethod("valueOf",boolean.class).invoke(null,true));

	}
}
运行图:

/*
通过反射调用一个类的main方法(当然了其他方法也是可以的)
*/
class MethodTest 
{
	public static void main(String[] args) throws Exception
	{
		//普通调用
		//普通调用的问题在于:我们必须提前知道该类的名字及其中方法等
		//要是我们不知道呢?或者说这些信息都是通过当前main方法传递进来的呢?那就必须使用反射了。
		Demo.main(new String[]{"helong","ldy","xiaoming"});
		new Demo().show();

		//反射调用
		//通过args[0]传递类名进来
		Class.forName(args[0]).getMethod("main",String[].class).
			//invoke方法在JDK1.5后在可变参数位置如果传入一个数组,它会打开数组,获取
			//里面的各个元素,作为该处的可变参数组,但是我们知道main本身需要一个数组为
			//参数,那么问题就是如何避免新特性将数组拆包呢?
			//解决方式1:再加一层包装:new Object[]{new String[]{.....}}这样即便拆了一层,得到的也是个数组
			//解决方式2:使参数看上去不再是个数组,也就不会拆包:(Object)new String[]{.....}
			invoke(null,(Object)new String[]{"xiaoming","ldy","helong"});
		Class cls = Class.forName(args[0]);
		cls.getMethod("show").invoke(cls.newInstance());
	}
}

class Demo
{
	public static void main(String[] args)
	{
		for(int i=0;i<args.length;i++)
		{
			System.out.println(args[i]);
		}
	}
	public void show()
	{
		System.out.println("show is run");
	}
}
运行图:


小知识点:invoke方法在JDK1.5后在可变参数位置如果传入一个数组,它会打开数组,获取里面的各个元素,作为该处的可变参数组,但是我们知道main本身需要一个数组为参数,那么问题就是如何避免新特性将数组拆包呢?
解决方式1:再加一层包装:new Object[]{new String[]{.....}}即便拆了一层,得到还是个数组
解决方式2:使参数看上去不再是个数组,也就不会拆包:(Object)new String[]{.....}


3.数组的反射

我们知道,数组也是一种类型Array,也就是说数组也有其对应的字节码文件,那么我们不仅要问,哪些数组对应同一个字节码文件呢?也就是说属于同一个数组类呢?
当数组中元素类型相同,且该数组的维度相同时,这样的数组有同一个字节码文件。

我们知道,数组的父类是Object,那么数组和Object的关系式怎么样的呢?

/*
数组和Object的关系
*/
import java.util.*;
class ArrayDemo 
{
	public static void main(String[] args) 
	{
		int[] a1=new int[]{2,3};
		int[] a4=new int[4];
		int[][] a2=new int[2][3];
		String[] a3=new String[]{"helong","ldy"};

		//只有元素类型相同,且同一维度的数组对象的字节码文件才相同
		System.out.println(a1.getClass().equals(a2.getClass()));
		System.out.println(a1.getClass().equals(a3.getClass()));
		System.out.println(a1.getClass().equals(a4.getClass()));

		//Object[] obj1=a1;//false,因为obj1数组中装的元素类型是Object,而a1中的是int,不匹配。
		Object[] obj2=a2;//true.
		Object[] obj3=a3;//true.

		System.out.println(a1);
		System.out.println(a3);
		
		//由于a1和Object[]不匹配,因此a1被当做一整个元素存入列表,而不是一组数据。
		System.out.println(Arrays.asList(a1));
		System.out.println(Arrays.asList(a3));

	}
}
运行图:


观察运行图,我们可以知道,首先确实只有元素类型相同,且维度相同的数组的字节码文件是同一个class文件。其次,当我们如下调用时:Arrays.asList(a1);由于需要兼容1.4因此两个都匹配一下看看那个asList更合适,我们知道int[]和Object[]是不匹配的,因此使用可变参数的asList方法,也就是将int[]作为一个元素传给了asList,因此得到的List只有一个元素,就是int[]。而Arrays.asList(a3);就没有这个问题,由于String[]和Object[]匹配,因此a3使用的是asList(Object[])方法,也就是将a3作为数组传给asList,得到的List中的元素就是String类型。


/*
数组的反射应用:定义一个方法接收Object,如果传入一个元素,那么就打印一个元素,
	如果传入数组,那么就挨个打印数组中元素。
*/
import java.lang.reflect.*;
class ArrayTest 
{
	public static void main(String[] args) throws Exception
	{
		printObject(123);
		printObject("helong");
		printObject(new String[]{"str1","str2","str3"});
	}
	private static void printObject(Object obj)
	{
		Class cls = obj.getClass();
		if(cls.isArray())//判断该对象所属类是否为数组类
		{
			for(int i=0;i<Array.getLength(obj);i++)
			{
				System.out.print(Array.get(obj,i)+"  ");
			}
			System.out.println();
		}
		else
			System.out.println(obj);
	}
}
运行图:


4.反射在框架中应用一个小例子

/*
演示:
框架中反射的作用:某些类是以后出现的,因此在程序中不出现该类
	的名字,而是从配置文件中获取其名字,并使用反射调用它方法。
*/
import java.util.*;
import java.lang.reflect.*;
import java.io.*;
class Student
{
	private String name;
	private int age;
	Student(String name,int age)
	{this.name=name;this.age=age;}
	public int hashCode()
	{
		return name.hashCode()+age*11;
	}
	public boolean equals(Object obj)
	{
		if(!(obj instanceof Student))
			throw new RuntimeException("类型不匹配");
		Student stu=(Student)obj;
		return name.equals(stu.getName())&&age==stu.getAge();
	}
	public String getName()
	{return name;}
	public int getAge()
	{return age;}
}
class FrameTest 
{
	public static void main(String[] args) throws Exception
	{
		//从配置文件中获取使用的集合类的名字
		InputStream is = new FileInputStream("config.properties");
		Properties prop = new Properties();
		prop.load(is);
		is.close();
		String className=prop.getProperty("className");
		Class cls = Class.forName(className);
		Collection col = (Collection)cls.newInstance();
		col.add(new Student("helong",22));
		col.add(new Student("helong",19));
		col.add(new Student("ldy",22));
		col.add(new Student("helong",22));

		System.out.println(col.size());
	}
}
运行图:
我们看到,在程序代码中,并没有出现HashSet类,而是从配置文件中动态获取的,这就是反射的用处,我们不需要知道那个类是HashSet还是ArrayList,我们只要知道它是一个Collection的子类就可以了,然后我们通过反射动态的获取它的信息,大部分时候我们主要是像此处一样,利用多态,操作他们的共性方法。

5.反射心得总结

反射是Java的高级特性之一,它的使用是比较难的,但同时,它的用处也是非常的大而且无可替代,也就是说该用到反射的地方我们只能用反射来解决,而没有其他方法。
反射主要用处:用在框架中,框架和工具类的区别在于:工具类是被用户类所调用的,而框架时调用用户类的,这就使得框架必须使用反射,否则我们无法获取到用户类的信息。


-------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值