黑马程序员---张老师高新技术之反射,内省及注解

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

反射:

反射的基石--->Class

Class类代表Java类,它的实例对象对应各个类在内存中的字节码,例如,Person类的字节码,ArrayList类的字节码,等等。

一个类被类加载器加载到内存中,占用一片存储空间,这个空间里面的内容就是类的字节码,不同的类的字节码是不同的,所以它们在内存中的内容是不同的,这一个个的空间可分别用一个个的对象来表示,这些对象显然具有相同的类型,这个类型就是Class类型

得到字节码对应的实例对象的方法( Class类型)

A:类名.class,例如,System.class

B:对象.getClass(),例如,new Date().getClass()

C:Class.forName("类名"),例如,Class.forName("java.util.Date");

九个预定义Class实例对象:包含八种基本数据类型和void,这些类对象由 Java 虚拟机创建,与其表示的基本类型同名,即boolean、byte、char、short、int、long、float 和double。 

可以用isPrimitive()方法判断是不是基本数据类型

数组的Class实例对象:类型[ ].class,可以用isArray()方法判断一个Class实例是否为数组。 

总之,只要是在源程序中出现的类型,都有各自的Class实例对象,例如,int[]void

注意:字节码的之间的比较用==比较;

反射:反射就是把Java类中的各种成分映射成相应的java.

一个类中的每个成员都可以用相应的反射API类的一个实例对象来表示,通过调用Class类的方法可以得到这些实例对象后,得到这些实例对象后有什么用呢?怎么用呢?这正是学习和应用反射的要点。

(1)Constructor:

Constructor类代表某个类中的一个构造方法.

得到某个类所有的构造方法:

例子:Constructor [] constructors= Class.forName("java.lang.String").getConstructors();

得到某一个构造方法:该构造方法需是public

例子:

Constructor constructor = Class.forName(java.lang.String).getConstructor(StringBuffer.class);

//获得方法时要用到类型,也就是获得那个参数类型的构造方法.

注意:getDeclaredConstructors();//可以获得所有的的构造方法,包括私有的 

创建实例对象:

通常方式:

String str = new String(new StringBuffer("abc"));

反射方式:

String str = (String)constructor.newInstance(new StringBuffer("abc"));

注意:newInstance的参数和获得constructor时的类型一致,

//调用获得的方法时要用到上面相同类型的实例对象

Class.newInstance()方法:直接获得空参数的构造方法并创建实例;

例子:String obj = (String)Class.forName("java.lang.String").newInstance();

该方法内部先得到默认的构造方法,然后用该构造方法创建实例对象。

该方法内部用到了缓存机制来保存默认构造方法的实例对象。

综合示例:

//获取String.class参数为String的构造方法
Constructor const = String.class.getConstructor(String.class);
//通过newInstance(new String)的方法获得该构造方法的所构造的实例,因为返回值是Object类型,所以要强转
String str = (String)const.newInstance("abcd");
System.out.println(str);

(2)Field

Field类代表某个类中的一个成员变量

得到的Field对象是对应到类上面的成员变量的定义,而不是具体的变量

getField(String name):获取某个类中的一个成员变量

  例:Field f = Person.class.getField("name");

get(Object o): 返回指定对象上此 Field 表示的字段的值。

:f.get(new Person);

Set(Object obj,Object value):将指定对象变量上此Field对象表示的字段设置为指定的新值。

Class.getDeclaredField(): 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段。(public声明的)

Class.getField(): 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定公共成员字段。(public声明的)

Class.setAccessible(boolean flag):暴力反射,忽略访问检查包括私有:将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。

综合示例:

通过反射改变某一对象中所有String类型参数,将其中的a变成b

public static void changeStringValue(Object obj)throws Exception{
	//获取该对象所有成员变量的类型,存入Field型数组中
	Field[] fields = obj.getClass().getDeclaredFields();
	//遍历Field型数组
	for(Field field : fields){
		//设置成员变量的Accessible值为true,也就是说不管是不是私有,我都要
		field.setAccessible(true);
		//判断成员变量类型是不是String类型
		if(field.getType() == String.class){
			//用get方法取出String中的值,替换后,用set方法修改.
			String oldValue = (String)field.get(obj);
			String newValue = oldValue.replace('b', 'a');
			field.set(obj, newValue);
		}
	}
}

(3)Method

Method类代表某个类中的一个成员方法

得到类中的某一个方法:

例子:

Method charAt = Class.forName("java.lang.String").getMethod("charAt",int.class);

调用方法:

通常方式:System.out.println(str.charAt(1));

反射方式: System.out.println(charAt.invoke(str,1)); 

格式

反射的方法.invoke(哪个对象调用(如果是静态方法则为null),该方法的传入参数(如果没有可以为0或者null))

如果传递给Method对象的invoke()方法的第一个参数为null,说明该Method对象对应的是一个静态方法!

jdk1.4jdk1.5invoke方法的区别:

Jdk1.5public Object invoke(Object obj,Object... args)

Jdk1.4public Object invoke(Object obj,Object[] args),即按jdk1.4的语法,需要将一个数组作为参数传递给invoke方法时,数组中的每个元素分别对应被调用方法中的一个参数,所以,调用charAt方法的代码也可以用Jdk1.4改写为 charAt.invoke(str, new Object[]{1})形式。

注意:getMethod()中参数用Class类型,也就是Class.class

综合示例:用反射方式执行某个类中的main方法 

class TestArguments{
	public static void main(String [] args){
		for(String arg:args){
		System.out.println(arg);
		}
	}
}
// 注意,要在该程序的运行环境中配置 Program Arguments TestArguments; 

String startingClassName =args[0];

Method mainMethod =Class.forName(startingClassName).getMethod("main",String[].class);

//JDK1.5向下兼容1.4版本,而1.4版本中,会把传递进的数组当一个个参数的组成的数组,而进行拆分。这句调用的是1.4的方法。

mainMethod.invoke(null,new Object[]{new String[]{"abc","edf"}});

//这句是调用了1.5的方法。

mainMethod.invoke(null,(Object)new String[]{"abc","edf"});

(4)数组的反射

A:具有相同维数和元素类型的数组属于同一个类型,即具有相同的Class实例对象。

B:代表数组的Class实例对象的getSuperClass()方法返回的父类为Object类对应的Class

C:基本类型的一维数组可以被当作Object类型使用,不能当作Object[]类型使用;非基本类型的一维数组,既可以当做Object类型使用,又可以当做Object[]类型使用。

D:Array工具类用于完成对数组的反射操作。

注意:能否得到一个数组的元素类型?不可能得到,例如:对应new Object[]{"adc",105,'ch'}这个数组,因为里边的数据类型不一致, 所以只能得到具体某个元素的类型。 

综合示例:打印一个对象,如果是数组则打印数组中每个元素的值 

public static void printObject(Object obj) {
	Class classz = obj.getClass();
	if(classz.isArray()){
		int len = Array.getLength(obj);
		for(int x = 0;x<len;x++){
			System.out.println(Array.get(obj, x));
		}
	}
	else
	System.out.println(obj);
}

(5)反射的作用<实现框架功能>

框架与工具类有区别:工具类被用户的类调用,而框架则是调用用户提供的类。

框架要解决的核心问题:因为在写框架时,你无法知道要调用的类名,所以无法直接new某个类的实例对象,而要用反射的方式来做了。 

综合示例:采用配置文件加反射的方式创建ArrayListHashSet的实例对象.

Config.properties中有键值对className = java.util.ArrayList

InputStream is = ReflectTest2.class.getResourceAsStream("config.properties");
Properties p = new Properties();
p.load(is);
is.close();
String className = p.getProperty("className");
Collection cool = (Collection) Class.forName(className).newInstance();
ReflectPoint rp1 = new ReflectPoint(3,3);
ReflectPoint rp2 = new ReflectPoint(5,5);
ReflectPoint rp3 = new ReflectPoint(3,3);
cool.add(rp1);
cool.add(rp2);
cool.add(rp3);
cool.add(rp1);
//rp1.y = 10;//此处不能修改,修改后容易造成内存泄露
//cool.remove(rp1);
System.out.println(cool.size());//配置文件中className为ArrayList时值为4,HashSet时值为2;

特别注意:HashSet,存入元素时,先调用hashcode方法,将一个元素放入集合中,如果相等在判断equals方法。hashcode可以通过计算hashcode值来判断将元素放入哪个区中,hashcode默认是通过内存地址计算的。 

当添加完元素后,就不要修改元素中那些参与计算哈希值的字段了,否则,对象修改后的哈希值与最初存储进hashset集合中时的哈希值就不同了.

添加完元素后改变元素的属性,如果该对象的hashcode计算用到了该属性,那么remove的时候是找不到该对象的,容易造成内存泄露.

总结:

反射在配置文件中的应用.

1.建立配置文件,添加键值对

2.创建读取流关联配置文件

3.创建Properties对象,调用Propertiesload()方法加载文件

4.关闭读取流

5.通过Properties对象getProperty()获取相应键对应的值,className

6.Class.forName(className).newInstance()创建对象空参数实例.

加载配置文件的方法

1.用绝对路径或相对路径;先获取程序安装目录,然后拼上配置文件的相对路径,获取绝对路径(不是硬编码获取绝对路径)。这样就可以既读取,有能写入(即保存更改后的配置信息)。它是读写资源文件的唯一方法。 

2.用类加载器,通过类加载器,来加载资源文件,但只能加载,不能修改配置文件。框架多是这样加的。(在MyEclipse中,会把除源文件之外的所有资源文件都在bin执行文件目录下拷贝一份。)这种方式有两个方法可以调用,最终都是利用类加载器来完成的。 

A:用类加载器:getClassLoader().getResourceAsStream(String filename)

InputStream is = file.class.getClassLoader().getResourceAsStream(String filename);

B:用类文件的,getResourceAsStream(String filename)方法

file.class.getResourceAsStream(String filename);

JavaBean  内省与beanutils工具包

内省(IntroSpector)主要用于对JavaBean进行操作。

1.JavaBean是一种特殊的Java类,主要用于传递数据信息,这种java类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。

用  途:如果要在两个模块之间传递多个信息,可以将这些信息封装到一个JavaBean中,这种JavaBean的实例对象通常称之为值对象(Value Object,简称VO)。这些信息在类中用私有字段来存储,如果读取或设置这些字段的值,则需要通过一些相应的方法来访问。

特  点:JavaBean的属性是根据其中的settergetter方法来确定的,而不是根据其中的成员变量。去掉setget前缀,剩余部分就是属性名。如果剩余部分的第二个字母是小写的,则把剩余部分的首字母改成小的。JavaBean必须有一个不带参数的构造方法。

好  处:一个符合JavaBean特点的类可以当作普通类一样进行使用,但如果把它当做JavaBean,那么就可以调用JDK提供的对专门对JavaBean进行操作的API,以实现对一些对普通类来说比较复杂的功能。

2.内省:JDK提供的对JavaBean进行操作的API,就被称为内省。(参看avaAPI文档中的java.beans包和java.beans.beancontext

3.JavaBean的简单内省操作

ReflectPoint pt = new ReflectPoint(3,5);
String propertyName = "x";
PropertyDescriptor pd = new PropertyDescriptor(propertyName,pt.getClass());
Method methodSet = pd.getWriteMethod();
methodSet.invoke(pt,value);
PropertyDescriptor pd1 = new PropertyDescriptor(propertyName,pt1.getClass());
Method methodGet = pd.getReadMethod();
Object retVal = methodGet.invoke(pt1);

上边即是用java.beans包中的PropertyDescriptor类把ReflectPoint当做JavaBean进行set和get操作。
4.使用BeanInfo类和IntroSpector类查看把ReflectPoint当做一个JavaBean看时,显示的信息,并获取"x"属性的值.

5.beanutils工具包

Apache提供的开源的操作JavaBean的工具包,它要和Apachelogging工具包导入到Project中,然后Build Path才可以使用。
里边的BeanUtilPropertyUtils类使用示例如下:

ReflectPoint pt = new ReflectPoint(5,5);
BeanUtils.setProperty(pt, "y", "100");
BeanUtils.setProperty(pt, "birthday.time","1111" );
System.out.println(BeanUtils.getProperty(pt, "y").getClass().getName());//java.lang.String
System.out.println(BeanUtils.getProperty(pt, "birthday.time"));
PropertyUtils.setProperty(pt, "x", 9);
System.out.println(PropertyUtils.getProperty(pt, "x"));//java.lang.Integer

BeanUtils以字符串(String)的形式对JavaBean 的属性进行操作,getProperty方法得到的属性值是以字符串形式返回的(网络开发时,获取的用户数据类型都是String)。

PropertyUtils以属性本身的类型最JavaBean的属性进行操作,getProperty()方法得到的属性值是以属性本来的类型返回的。

注解:

1.注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则等于没有某种标记,

以后,javac编译器,开发工具和其他程序可以用反射来了解你的类及各种元素上有无何种标记,看你有什么标记,就去干相应的事。

标记可以加在包,类,字段,方法,方法的参数以及局部变量上。

2.java.lang包,可看到JDK中提供的最基本的annotation

基本的注解:

@SuppressWarnings:<>指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。

@Deprecated:已过时的,用 @Deprecated 注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择。

@Override:覆写,重写,覆盖,指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告。

注解就相当于一个你的源程序中要调用的一个类,要在源程序中应用某个注解,得先准备好了这个注解类。就像你要调用某个类,得先有开发好这个类。

枚举和注解都是特殊的类,不能用new 创建它们的实例对象,创建枚举的实例对象就是在其中增加元素。

在程序中如何创建出一个注解的实例对象啊?直接用@放上一个标记即可

MetaAnnotation注解的定义:

public @interface MetaAnnotation {
   String value();
}

元注解:注解类的注解

Annotation
     |--Documented 指示某一类型的注释将通过 javadoc 和类似的默认工具进行文档化。 
     |--Inherited 指示注释类型被自动继承。 
     |--Retention 指示注释类型的注释要保留多久。 (描述注释存在的周期)
     |--Target 指示注释类型所适用的程序元素的种类 (描述注释存在的位置) 

@Retention:指示注释类型的注释要保留多久。如果注释类型声明中不存在 Retention 注释,则保留策略默认为 RetentionPolicy.CLASS。 只有元注释类型直接用于注释时,Target 元注释才有效。如果元注释类型用作另一种注释类型的成员,则无效。 

RetentionPolicy:注释保留策略。此枚举类型的常量描述保留注释的不同策略。它们与 Retention 元注释类型一起使用,以指定保留多长的注释。

CLASS:<class文件>编译器将把注释记录在类文件中,但在运行时 VM 不需要保留注释。 

RUNTIME:<内存中的字节码>编译器将把注释记录在类文件中,在运行时 VM 将保留注释,因此可以反射性地读取。 

SOURCE:<java源文件>编译器要丢弃的注释。

@Target:指示注释类型所适用的程序元素的种类。如果注释类型声明中不存在 Target 元注释,则声明的类型可以用在任一程序元素上。如果存在这样的元注释,则编译器强制实施指定的使用限制。

ElementType:程序元素类型。此枚举类型的常量提供了 Java 程序中声明的元素的简单分类。

这些常量与 Target 元注释类型一起使用,以指定在什么情况下使用注释类型是合法的。

ANNOTATION_TYPE:注释类型声明 

CONSTRUCTOR:构造方法声明 

FIELD:字段声明(包括枚举常量) 

LOCAL_VARIABLE:局部变量声明 

METHOD:方法声明 

PACKAGE:包声明 

PARAMETER:参数声明 

TYPE:类、接口(包括注释类型)或枚举声明 

注解的属性

一个注解相当于一个胸牌,如果你胸前贴了胸牌,就是传智播客的学生,否则,就不是。如果还想区分出是传智播客哪个班的学生,

这时候可以为胸牌在增加一个属性来进行区分。

加了属性的标记效果为:@MyAnnotation(color="red");

定义基本类型的属性和应用属性:

增加属性的格式: 
 类型  变量名() default 变量实例 ; 

类型为8个基本数据类型,String类,Class类, 枚举,注解类型(注解类型也可以作为基本属性加个注解)以及数组

在注解类中增加String color();

@MyAnnotation(color="red")

为属性指定缺省值:

String color() default "yellow";

value属性:

String value() default "zxx"; 

如果注解中有一个名称为value的属性,且你只想设置value属性(即其他属性都采用默认值或者你只有一个value属性),那么可以省略value=部分,

例如:@MyAnnotation("lhm")

数组类型的属性

int [] arrayAttr() default {1,2,3};

@MyAnnotation(arrayAttr={2,3,4})

如果数组属性中只有一个元素,这时候属性值部分可以省略大括

枚举类型的属性

EnumTest.TrafficLamp lamp() ;

@MyAnnotation(lamp=EnumTest.TrafficLamp.GREEN)

注解类型的属性:

MetaAnnotation annotationAttr() default @MetaAnnotation("xxxx");

@MyAnnotation(annotationAttr=@MetaAnnotation(“yyy”) )

可以认为上面这个@MyAnnotationMyAnnotaion类的一个实例对象,同样的道理,可以认为上面这个@MetaAnnotationMetaAnnotation类的一个实例对象,调用代码如下:

MetaAnnotation ma =  myAnnotation.annotationAttr();

System.out.println(ma.value());

注解的详细语法可以通过看java语言规范了解,即看javalanguage specification

综合示例:创建一个有多属性的注解

@Retention(RetentionPolicy.RUNTIME)//设置保留策略为到运行时
@Target(ElementType.TYPE)//元注解,定义注解使用的类型为所有类型
public @interface ItcastAnnotation {
	String color() default "yellow";//基本String类型属性color
	String value();//value属性
	int[] arr() default{1,2,3};//数组类型属性
	EnumTest.TrafficLamp lamp() default EnumTest.TrafficLamp.RED;//枚举类型属性
	MetaAnnotation annotationAttr() default @MetaAnnotation("aaaaa");//注解类型属性
}


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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值