类型信息

    Java是如何让我们在运行时识别对象和类的信息的。主要有两种方式:一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型;另一种是“反射”机制,它允许我们在运行时发现和使用类的信息。

    RTTI和反射之间真正的区别只在于,对RTTI来说,编译器在编译时打开和检查.class文件。而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。


一、RTTI

1、RTTI的含义:Run Time Type Information,在运行时,识别一个对象的类型。在Java中,所有的类型转换都是在运行时进行正确性检查(p314)

     要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的。RTTI在Java中有三种形式:Class对象、类字面变量(即类名.class)、instanceof。

2、Class

    Class它包含了与类有关的信息。事实上,Class对象就是用来创建类的所有的“常规”对象(参考p313 Shape的例子理解,Class相当于Shape)。Java使用Class对象来执行其RTTI,即使你正在执行的是类似转型这样的操作。

    类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编译一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中)。只要是在源程序中出现的类型,都会有各自的Class实例对象,

        例如:int.class,int[].class,void.class。

    为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统。类加载器子系统实际上可以包含一条类加载器链,但是只有一个原生类加载器,它是JVM实现的一部分。

    如何得到某个class文件对应的class对象呢?有三种方式可以得到以String为例

      注意:不管这个类生成了多少个对象,它们所对应的Class对象只有唯一的一个在内存中

public static void main(String[] args) throws Exception {
		//第一种:类名.class
		Class c1 = String.class;
		
		//第二种:对象.getClass()
		Class c2 = new String().getClass();
		
		//第三种:Class.forName()
		Class c3 = Class.forName("java.lang.String"); //全路径
		
		System.out.println(c1==c2);//true
		System.out.println(c1==c3);//true
		System.out.println(c3==c2);//true
}

      Class.forName返回的是Class对象的引用,调用forName方法后,如果该类没有被加载就加载它

      getClass返回的是该对象的实际类型的Class引用,这个方法属于根类Object的一部分

     (1)泛化的Class引用

          Class泛化即给Class加上泛型,如下:

public static void main(String[] args) {
	Class<?> intclass = int.class;
	Class<? extends Number> intclass2 = int.class;//创建一个Class引用并限定为某种类型 
}
     (2)新的转型语法,即cast()方法

Class<House> houseType = House.class;
House h = houseType.cast(b);

3、类字面变量(类名.class)

    通过“类名.class”来生成Class对象不仅更简单,也更安全,因为在编译时就会受到检查(因此不需要置于try语句块中),并且根除了对forName()方法的调用,所以更高效。(p318)

    注意,使用“.class”来创建对Class对象的引用时,不好自动地初始化该Class对象

   类字面变量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用,指向对应的基本数据类型的Class对象,如下所示:

      

   但是需注意,int.class不等价于Integer.class。

public static void main(String[] args) {
	System.out.println(int.class == Integer.class);//false
	System.out.println(int.class == Integer.TYPE);//true
}

4、Instanceof 和 isInstance

Instanceof有比较严格的限制:只能将其与命名类型进行比较,而不能与Class对象作比较。如果程序中编写了许多的instanceof表达式,就说明你的设计可能存在瑕疵。

Instanceof和isInstance保存了类型的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”,而如果欧勇==比较实际的Class对象,就没有考虑继承——它或者是这个确切的类型,或者不是。


二、反射

       Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了Field(类的成员变量,属性)、Method(类的方法)以及Constructor(类的构造方法),每个类都实现了Member接口。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。(p335)

        反射就是把JAVA类中的各种成分映射成一个个的JAVA对象。例如,一个类有:成员变量,方法,构造方法,包等信息,利用反射技术可以对一个类进行解剖,把各个组成部分映射成一个个对象。通俗的理解反射就像建大厦时的设计图纸,Class就是用来描述类的。

(1)、通过反射获取类的属性(Field)

public class Person {
	public String name;
	
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

public static void main(String[] args) throws Exception {
	// 取得Person类对应的字节码对象Class
	Class c = Class.forName("com.cyqg.test.Person");
	// 取得该类的构造方法,创建一个实例
	Constructor con = c.getConstructor();
	User p = (User) con.newInstance();
	// 获取name属性
	Field f = c.getField("name");
//	f.getName();//获取属性的名字
//	Field[] fields = c.getFields();//获取所有字段
	f.set(p, "杰克");// p.setName("杰克") [对象,对象属性]设置属性的值
	System.out.println("用户名:" + p.getName());
}
注意:私有的属性也是通过getDeclared...来实现,同时要设置可访问性


(2)、通过反射获取类里面的方法

public class Person {
	//无参
	public void show(){
		System.out.println("public void show()");
	}
	//有参
	public void show(String[] likes,double salary){
		System.out.println("public void show(String[] likes,double salary)");
	}
	//有返回值
	public int count(int i,int j){
		System.out.println("public int count(int i,int j)");
		return i+j;
	}
}

public static void main(String[] args) throws Exception {
	Class c = Class.forName("com.cyqg.test.Person");
	Constructor con = c.getConstructor();
	Person p = (Person) con.newInstance();
	Method m1 = c.getMethod("show");//无参[这里有两个参数:方法名,参数类型(可变参数)]
	m1.invoke(p);//执行方法 [两个参数:谁执行,可变参数]
		
	Method m2 = c.getMethod("show", String[].class,double.class);//有参[这里有两个参数:方法名,参数类型(可变参数)]
	m2.invoke(p, new String[]{"A","B","C"},5000);
		
	Method m3 = c.getMethod("count",int.class,int.class);//带返回值
	Integer returnValue = (Integer) m3.invoke(p,3,8);//获取返回值
	System.out.println(returnValue);
}
注意:私有的属性也是通过getDeclared...来实现,同时要设置可访问性


(3)、Constructor. 

       Constructor类的实例对象代表类的一个构造方法。通过反射怎样获取一个类的构造方法呢?

public class Person {
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	// 无参构造方法
	public Person() {
		System.out.println("无参构造方法");
	}
	// 有参构造方法
	public Person(String name, int age) {
		System.out.println("有参构造方法");
	}
	public void show() {
		System.out.println("public void show()");
	}
}

public static void main(String[] args) throws Exception {
	//取得Person类对应的字节码对象Class
	Class clazz = Class.forName("com.cyqg.test.Person");
		
	//取得该类的唯一构造方法
	Constructor c1  = clazz.getConstructor();//clazz.getConstructor(null) 获取无参构造方法
    	Constructor c2 = clazz.getConstructor(String.class,int.class); //获取有参的构造方法
 		
	//创建实例
	Person p1 = (Person) c1.newInstance();//c1.newInstance(null)
	Person p2 = (Person) c2.newInstance("berry",10); //创建实例时要传参
   		
	//执行方法
	p1.show();
	p2.show();
}


访问私有的构造方法

public class Person {
	private String name;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	// 无参私有构造方法	protected也是一样
	private Person() {
		System.out.println("private Person()");
	}
	public void show() {
		System.out.println("public void show()");
	}
}

public static void main(String[] args) throws Exception {
	//取得Person类对应的字节码对象Class
	Class clazz = Class.forName("com.cyqg.test.Person");
	//取得该类的唯一构造方法
	Constructor c = clazz.getDeclaredConstructor(null);
	//设置非public成员的访问性,默认false即不可访问性
	c.setAccessible(true);
	//创建实例
	Person p = (Person) c.newInstance(null);
	//执行方法
	p.show();
}
注意:通过getConstructor()只能取得该类public的类型

          通过getDeclaredConstructor()可以取得该类非public的类型,同时要设置非public类型的可访问性,默认为false,不可访问(c.setAccessible(true))



(4)、用反射执行某个类中的main方法

public static void main(String[] args) throws Exception {
	Class c = Class.forName("com.cyqg.test.Person");
	Constructor con = c.getConstructor();
	Method m1 = c.getMethod("main",String[].class);
	//这种方式去执行会报错:提示参数不对?
	m1.invoke(null,new String[]{"A","B","C","D","E","F"});//由于main方法是静态的,所以这里的第一个参数(执行对象)可以不写
		
	//在反射main方法时,编译器会将数组自动拆分,取第一个值,所以上面的写法会报错,有以下两种解决方式:
	m1.invoke(null,(Object)new String[]{"A","B","C","D"});//将数组当作对象,此时编译器不进行拆分
	m1.invoke(null,new Object[]{new String[]{"A1","B1","C1","D1"}});//在数组中嵌入另一个数组
}

例子:

public static Object copy(Object obj) throws Exception{
	//获得对象类型
	Class classType = obj.getClass();
	System.out.println(classType);//String的输出结果 class java.lang.String
	System.out.println(classType.getName());//String的输出结果 java.lang.String
	
	//通过默认构造方法创建一个新的对象
	Object objectCopy = classType.getConstructor(new Class[]{})//后面new Class这个参数表示的是这个构造方式是不带参数的
						.newInstance(new Object[]{});//不带参数的实例
	
	//获得对象的所有属性
	Field[] fields = classType.getDeclaredFields();
	for(int i=0;i<fields.length;i++){
		Field field = fields[i];
		//获取属性的名字
		String fieldName = field.getName();
		
		//获取这个属性的GET方法
		String firstLetter = fieldName.substring(0,1).toUpperCase();
		String getMethodName = "get" + firstLetter + fieldName.substring(1);//从1开始截取
		Method getMethod = classType.getMethod(getMethodName, new Class[]{});
		Object value = getMethod.invoke(obj, new Object[]{});//去调用get方法,获取属性的值
		
		
		//获取这个属性的SET方法
		String setMethodName = "set" + firstLetter + fieldName.substring(1);
		Method setMethod = classType.getMethod(setMethodName, new Class[]{});
		setMethod.invoke(objectCopy, new Object[]{value});//调用set方法,将value设值进去
	}
	return objectCopy;
}


三、其他

1、你可能会认为,可以通过只发布编译后的代码来阻止这种情况,但是这并不解决问题。因为只需运行javap,一个随JDK发布的反编译器即可突破这一限制。下面是一个使用它的命令行:

     javap -private C

     -private 标识表示所有的成员都应该显示,甚至包括私有成员。(p348)

    

2、面向对象编程中基本的目的是:让代码只操作对基类的引用。这样,如果要添加一个新类来扩展程序,就不会影响到原来的代码。(p313)

3、不要太早地关注程序的效率问题,这是个诱人的陷阱,最好首先让程序运作起来,然后再考虑它的速度。


转载于:https://my.oschina.net/weslie/blog/471984

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值