文档章节

类型信息

weslie
 weslie
发布于 2015/06/29 13:41
字数 2610
阅读 16
收藏 0

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


© 著作权归作者所有

weslie
粉丝 1
博文 51
码字总数 80088
作品 0
虹口
程序员
私信 提问
.Net 中的反射(查看基本类型信息) - Part.2

.Net 中的反射(查看基本类型信息) - Part.2 反射概述 和Type类 1.反射的作用 简单来说,反射提供这样几个能力:1、查看和遍历类型(及其成员)的基本信息和程序集元数据(metadata);2、迟绑定(...

张子阳
2008/02/17
0
0
功能表单字段选择数据类型的配置详解——JEPLUS快速开发平台

功能表单字段之下拉框、单选框、多选框、树形选择数据类型的配置详解 JEPLUS平台的表单支持有多种不同的数据类型,这些不同的数据类型在展示不同类型的数据时能有很好的效果,今天这篇笔记就...

JEPLUS
2018/06/12
14
0
使用readelf/objdump查看dwaf调试信息

对源文件进行编译链接,生成.o文件。 使用 查看包含的调试信息。 这时会出现很多信息: 接着使用 查看各个调试信息节包含的内容。 *是调试节名的第一个字母,如就是查看节的内容,就是查看节...

zhang_han666
2018/05/10
0
0
eclipse信息输出区没有logcat的解决方法

有时候会莫名其妙的就找不到输出信息区的LogCat日志了,很郁闷。![LogCat输出区示意图][1] 解决方法:Eclipse菜单栏中【Windows--->Show Views】弹层中选择LogCat,如果找不到则点击最下方的...

bukw
2013/12/14
5K
0
Oracle之查看当前用户的所有表和表的列-yellowcong

我之前写过一个工具,动态生成oracle的实体类的功能,但是这个工具只能用来给Searsar这个项目用,所以呢,所以意义不是特别的大,但是也可以扩展为别的实体类生成的。和包含了表的字段和列信...

yelllowcong
2018/01/22
0
0

没有更多内容

加载失败,请刷新页面

加载更多

redis 内存信息解析

used_memory:由 Redis 分配器分配的内存总量,包含了redis进程内部的开销和数据占用的内存,以字节(byte)为单位 used_memory_rss:向操作系统申请的内存大小。与 top 、 ps等命令的输出一...

Canaan_
28分钟前
4
0
windows 下 python3 安装 pip setuptools

本文链接:https://blog.csdn.net/huzuxing/article/details/80807744 最近在家使用python的时候,总是报setuptools 模块未找到,于是在网上搜索了相关解决办法,但是都没有解决问题。 于是去...

开源中国首席CYO
35分钟前
4
0
数据库添加索引

mysql索引添加 navicat 步骤 - 选择表 -> 设计表 ->索引

以谁为师
45分钟前
6
0
java7与java9中的try-finally关闭资源

1.java7中的try 在java7之前,对于一些需要使用finally关闭资源的操作,会显得很臃肿. try{//}catch(Exception e){//}finally{if(xxxx != null){xxxx.close();}} 在jav...

Blueeeeeee
46分钟前
4
0
字节序转换详解

在跨平台和网络编程中我们经常会提到网络字节序和主机字节序,如果没有正确对两者进行转换,从而导致两方产生了不同的解释,就会出现意想不到的bug。 目录 0x01 概念 0x02 分类 0x03 两种字节...

无心的梦呓
56分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部