您的位置:首页 > 编程语言 > Java开发

Java之反射、枚举、内省、注解

2014-09-02 00:49 465 查看

一:反射技术:

1.概述:其实就是动态加载一个指定的类,并获取该类中的所有的内容。而且将字节码文件封装成对象, 并将字节码文件中的内容都封装成对象,这样便于操作这些成员。简单说:反射技术可以对一个 类进行解剖,解剖后的属性、方法都为对象

2.反射的好处:大大的增强了程序的扩展性。

3.反射的基本步骤:

3.1.获得Class对象,就是获取到指定的名称的字节码文件对象。

3.2.实例化对象,获得类的属性、方法或构造函数。

3.3.访问属性、调用方法、调用构造函数创建对象。

4.获取这个Class对象,有三种方式:

4.1.通过每个对象都具备的方法getClass来获取。弊端:必须要创建该类对象,才可以调用getClass方法。

4.2.每一个数据类型(基本数据类型和引用数据类型)都有一个静态的属性class。弊端:必须要先明确该类。

4.3.使用的Class类中的方法,静态的forName方法。

指定什么类名,就获取什么类字节码文件对象,这种方式的扩展性最强,只要将类名的字符串传入即可。

4.3.1. 根据给定的类名来获得 用于类加载

String classname ="cn.itcast.reflect.Person";// 来自配置文件

Class clazz = Class.forName(classname);// 此对象代表Person.class

4.3.2.如果拿到了对象,不知道是什么类型 用于获得对象的类型

Object obj = new Person();
Class clazz1 = obj.getClass();// 获得对象具体的类型

4.3.3.如果是明确地获得某个类的Class对象 主要用于传参

Class clazz2 = Person.class;

5.反射的用法:

5.1.需要获得java类的各个组成部分,首先需要获得类的Class对象,获得Class对象的三种方式:

Class.forName(classname) 用于做类加载
obj.getClass() 用于获得对象的类型
类名.class 用于获得指定的类型,传参用

5.2.反射类的成员方法:

Classclazz = Person.class;
Methodmethod = clazz.getMethod(methodName, new Class[]{paramClazz1, paramClazz2});
method.invoke();

5.3.反射类的构造函数:

Constructorcon = clazz.getConstructor(new Class[]{paramClazz1, paramClazz2,...})
con.newInstance(params...)

5.4.反射类的属性:

Fieldfield = clazz.getField(fieldName);
field.setAccessible(true);
field.setObject(value);

6.获取了字节码文件对象后,最终都需要创建指定类的对象:

创建对象的两种方式(其实就是对象在进行实例化时的初始化方式):

6.1.调用空参数的构造函数:使用了Class类中的newInstance()方法。

6.2.调用带参数的构造函数:先要获取指定参数列表的构造函数对象,然后通过该构造函数的对象的 newInstance(实际参数)进行对象的初始化。

代码示例如下:
1.定义被反射的类:
package com.JavaSE.Reflect;
public class Point {
public int x;
private int y;
public String str1 = "ball";
public String str2 = "basketball";
public String str3 = "itcast";
public static String str4 = "ball";
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
}


2.反射实例:

package com.JavaSE.Reflect;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Reflect {
public static void main(String[] args) throws Exception {
int i = 0;
Object obj = i;//单独的基本数据类型赋给Object会被自动装箱
int[] in = new int[2];
//Object[] object = in;//会报错,因为数组in中存放的是int基本类型数据,而基本数据类型是没有父类的
//因此不能转换为Object
System.out.println(obj.getClass());//由于基本数据的自动装箱,obj为Integer类型
Class clazz = int.class;
System.out.println(clazz.getSuperclass());//基本数据类型的父类为null

// 反射构造方法
Constructor[] constructors = String.class.getConstructors();
Constructor<String> constructor = String.class
.getConstructor(StringBuffer.class);// 通过类的字节码的方法获取一个构造方法的对象
String str = (String) constructor.newInstance(new StringBuffer("abc"));// 通过该对象实例化该类
System.out.println(str.charAt(2));

// 反射属性字段
Point p1 = new Point(3, 5);
Field fld1 = p1.getClass().getField("x");// 将x属性属性对象关联起来
System.out.println(fld1.get(p1));// 要获取属性值,必须通过传递被映射的对象

Field fld2 = p1.getClass().getDeclaredField("y");// 映射所有申明过的属性(public,<span style="font-family: Arial; ">private,</span><span style="font-family: Arial; ">protected</span><span style="font-family: Arial; ">

</span>		fld2.setAccessible(true);// 通过暴力反射获取私有属性
System.out.println(fld2.get(p1));

changeStringValue(p1);// 通过反射修改成员变量的值
System.out.println(p1.str1 + ":" + p1.str2 + ":" + p1.str3 + ":"+ p1.str4);

// 成员方法的反射
String str1 = "abc";
Method method = str1.getClass().getMethod("charAt", int.class);// 将方法与方法对象关联
System.out.println(method.invoke(str1, 1));// 根据该方法的对象调用该方法(参数是方法的对象和方法参数),若第一个参                                                            //数为Null则调用的是静态方法
System.out.println(method.invoke(str1, new Object[] { 2 }));// 将参数自动转化

// 对main方法进行反射和调用
String className = args[0];// 假设main方法所在的类名为className
Method methodMain = Class.forName(className).getMethod("main",
String[].class);// 将方法和方法对象相关联
methodMain.invoke(null, (Object) new String[] { "111", "222", "333" });// 由于在Java1.4以后main方法中会将new String                                                                                       //中的数组对象拆开(编译器特殊处理),			                                                                      // 因此就有了三个对象,造成参数不匹配,解决办                                                                                       //法就是在前面加一个(Object)把他看成是一个                                                                                      //对象

// 对数组的反射
String[] str2 = new String[] { "444", "555", "666" };
printObject(str2);
printObject(new int[] { 1, 2, 3 });

}
// 对数组的反射
private static void printObject(Object obj) {
Class clazz = obj.getClass();
if (clazz.isArray()) {
for (int i = 0; i < Array.getLength(obj); i++) {
System.out.println(Array.get(obj, i));
}
} else {
System.out.println(obj);
}
}

// 通过反射修改成员变量的值
private static void changeStringValue(Object p2) throws Exception {
Field[] fields = p2.getClass().getDeclaredFields();// 获取所有属性
for (Field field : fields) {
if (field.getType() == String.class) {// 当属性类型为String时进行修改
String oldValue = (String) field.get(p2);
String newValue = oldValue.replace('b', 'a');// 替换字符
field.set(p2, newValue);// 将替换的字符重新放
}
}
}
}

class TestMain {
public static void main(String[] args) {
for (String arg : args) {
System.out.println(arg);
}

}
}


二:枚举:

1.概述:枚举就是一个特殊的java类,可以定义属性、方法、构造函数、实现接口、继承类,对象的某个属性的值不能是
任意的,必须为固定的一组取值其中的某一个,因此出现了枚举。

2.直接来代码最合适:

代码如下:

1.定义枚举
package com.JavaSE.Enum;
//枚举
public enum WeekDayEnum {
Sun,Mon,Tue,Wen,Thr,Fri,Sat;
}


2.枚举的原理
package com.JavaSE.Enum;
/*
* 枚举
* 1.就是一个类,在该类中每一个成员变量都是一个final static变量
* 2.有一个私有构造函数,只能通过自己的内部的函数来实例化对象,因此只能实例化已经定义好的对象
* 3.匿名内部类其实就是一个子类,该子类实现父类的方法
*/

//利用类来解释枚举内部原理
public abstract class WeekDayClass {
private WeekDayClass() {
}
public final static WeekDayClass Sun = new WeekDayClass(){//匿名内部类
@Override
public WeekDayClass nextDay() {//重写nextDay方法
return Mon;
}
};
public final static WeekDayClass Mon = new WeekDayClass(){
@Override
public WeekDayClass nextDay() {
// TODO Auto-generated method stub
return Tue;
}
};
public final static WeekDayClass Tue = new WeekDayClass(){

@Override
public WeekDayClass nextDay() {
// TODO Auto-generated method stub
return Sun;
}
};

public abstract WeekDayClass nextDay();
//	public WeekDay nextDay(){
//		if(this == Sun){
//			return Mon;
//		}else if(this == Mon){
//			return Tue;
//		}else{
//			return Sun;
//		}
//	}
public String toString(){
if(this == Sun){
return "Sun";
}else if(this == Mon){
return "Mon";
}else{
return "Tue";
}
}
}


3.枚举的加强

package com.JavaSE.Enum;

/*
* 1.在枚举中可以有构造函数(代参数或则不代参数),变量根据是否带参数来实例化
* 2.单只有一个变量时则为单例模式
*/
public enum EnumEnhance {
GREEN(10){
@Override
public EnumEnhance next() {
// TODO Auto-generated method stub
return RED;
}
},
RED{
@Override
public EnumEnhance next() {
// TODO Auto-generated method stub
return YELLOW;
}
},
YELLOW{
@Override
public EnumEnhance next() {
// TODO Auto-generated method stub
return GREEN;
}
};
public abstract EnumEnhance next();

private EnumEnhance() {//枚举中的方法必须在变量之后
System.out.println("first");
}
private EnumEnhance(int days){//带有参数的都早函数
System.out.println("second");
}
public static EnumEnhance GREEN(int i) {//静态枚举构造函数,访问时为EnumEnhance.GREEN(10)
// TODO Auto-generated method stub
System.out.println("three");
return null;
}
}


4.枚举的应用

package com.JavaSE.Enum;

public class Test {

/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
//利用模拟枚举的类实现枚举的功能
//		WeekDayClass weekday1 = WeekDayClass.Sun;
//		WeekDayClass weekday2 = WeekDayClass.Mon;
//		WeekDayClass weekday3 = WeekDayClass.Tue;
//		System.out.println(weekday1.toString());
//		System.out.println(weekday2.toString());
//		System.out.println(weekday3.toString());
//
//		System.out.println(weekday1.nextDay());
//		System.out.println(weekday2.nextDay());
//		System.out.println(weekday3.nextDay());

//		//枚举实例
//		WeekDayEnum weekdayEnum = WeekDayEnum.Mon;
//		System.out.println(weekdayEnum);
//		System.out.println(weekdayEnum.name());
//		System.out.println(weekdayEnum.ordinal());
//		System.out.println(weekdayEnum.valueOf("Tue"));
//		System.out.println(weekdayEnum.values().length);

//枚举加强
EnumEnhance RED = EnumEnhance.RED;
System.out.println(RED.next());

}

}


三:内省:即对JavaBean的操作

1.说明:对象JavaBean的简单内省操作(简单方便:PropertyDescriptor)和复 杂操作(稍微复杂点:IntroSpector)

2.步奏:

2. 1.创建PropertyDescriptor对象

2.2.获取JavaBean的方法对象

2.3.通过方法对象获取属性值

2.4.修改属性值

代码如下:
1.JavaBean:
package com.JavaSE.IntroSpector;

public class Point {
private int x;
private int y;
public Point(){

}
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}

}


2.对JavaBean的操作
package com.JavaSE.IntroSpector;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
//对象JavaBean的简单内省操作(简单方便:PropertyDescriptor)和复杂操作(稍微复杂点:IntroSpector)
//通过内省来操纵JavaBean,通过PropertyDescriptor类才操作,调用该类时也是用的反射
public class IntroSpectorTest {
/**
* 1.创建PropertyDescriptor对象
* 2.获取JavaBean的方法对象
* 3.通过方法对象获取属性值
* 4.修改属性值
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Point p1 = new Point(3,5);//JavaBean  Point
//获取JavaBean的属性
Object value = getProperties(p1);
System.out.println(value);

//设置JavaBean的属性(简单方式:PropertyDescriptor)
Object y = 7;//所修改属性的值
setPropertiesEasy(p1, y);
System.out.println(p1.getY());

//设置JavaBean的属性(复杂方式:IntroSpector)
setPropertiesComplex(p1,y);
System.out.println(p1.getY());
}
private static Object getProperties(Point p1) throws Exception{
//1.创建PropertyDescriptor对象,通过属性名和类的字节码初始化一个PropertyDescriptor对象
PropertyDescriptor pd = new PropertyDescriptor("x", p1.getClass());
//2.获取JavaBean的方法对象,通过属性名获取JavaBean的方法对象(读)
Method methodX = pd.getReadMethod();
//3.通过方法对象获取属性值
Object retVal = methodX.invoke(p1);
return retVal;
}
private static void setPropertiesEasy(Object p1, Object y) throws Exception{//简单方式
PropertyDescriptor pd = new PropertyDescriptor("y", p1.getClass());//通过属性名和类的字节码初始化一个PropertyDescriptor对象
Method methodSetY = pd.getWriteMethod();//通过属性名获取JavaBean的方法对象(写)
methodSetY.invoke(p1, y);//修改属性值
}
private static void setPropertiesComplex(Object p1, Object y) throws Exception{//复杂内省操作JavaBean
//1.通过Introspector.getBeanInfo()方法获取bean的对象(BeanInfo),传递JavaBean的字节码参数
BeanInfo beanInfo = Introspector.getBeanInfo(p1.getClass());
//2.获取到BeanInfo之后获得所有的JavaBean的Properties
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
//3.通过迭代、反射来修改JavaBean的属性
for(PropertyDescriptor pd : pds){
if(pd.getName().equals("y")){//匹配属性值
Method methodSetY = pd.getWriteMethod();
methodSetY.invoke(p1, 10);
}
}
}
}


四:注解

1.概述:注解相当于一种标记,没加则等于没有某种标记,以后javac编译器开发工具和其它程序可以用反 射来了解你的类

以及各种元素上有无何种标及,看你有什么标记就去干相应的事,标记可以加在包,类,字段,方法,方法的参数

以及局部变量上。

2.常用三注解:

interface Deprecated

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

interface Override

表示一个方法声明打算重写超类中的另一个方法声明。

interface SuppressWarnings

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

3.Retention:指示注释类型的注释要保留多久,如果注释类型声明中不存在Retention注释,则保留策率默认为 RetentionPolicy.Class

4.注解生命周期:

RetentionPolicy.SOURCE: java源文件,即javac编译之后就不存在啦。

RetentionPolicy.CLASS: class文件,即编译后还存在字节码中,但不在内存中。

RetentionPolicy.RUNTIME:内存中的字节码,即一直保存在内存中。

5.为注解添加高级属性:

数组类型的属性:

注解中:int []arrarAttr() default{1,2,3};

类中给注解赋值:@注解名(arrayAttr={2,3,4})

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

枚举类型的属性:

注解类型的属性:

示例代码如下:
1.定义一个注解
package com.JavaSE.Annotation;

import java.lang.annotation.ElementType;

@Retention(RetentionPolicy.RUNTIME)//运行时的注解,表明一直与源程序被保存到内存中
@Target({ElementType.METHOD,ElementType.TYPE})//目标,即注解所放的位置为方法和类型前面
public @interface Annotation {//注解中可以放8个类型得的数据
//属性
String str() default "defaultString";
String value() ;
int data() default 1;
int[] array() default{1,2,3};
MetaAnnotation annotation() default @MetaAnnotation("注解属性");
Lamp Enum() default Lamp.GREEN;

}


2.测试注解
package com.JavaSE.Annotation;
@Annotation(value="123")
public class AnnotationTest {
@Annotation(value="123")
public static void main(String[] args) {
if(AnnotationTest.class.isAnnotationPresent(Annotation.class)){//判断当前类是否加了注解
Annotation annotation = AnnotationTest.class.getAnnotation(Annotation.class);//通过反射得到注解
//打印一些注解的信息
System.out.println(annotation.data());
System.out.println(annotation.value());
System.out.println(annotation.str());
System.out.println(annotation.annotation());
System.out.println(annotation.annotationType());
System.out.println(annotation.array().length);
System.out.println(annotation.Enum());
}

}

}
3.定义一个枚举
package com.JavaSE.Annotation;

public enum Lamp {
RED,GREEN,YELLOW;

}
4.定义一个注解
package com.JavaSE.Annotation;

public @interface MetaAnnotation {
String value();
}


总结:
通过反射可以理解Java虚拟机是怎样操作字节码的,由于字节码在Java虚拟机的唯一性,进而更方便的可以拿到自己想要的对象,在struts框架和Hibernate框架中可以体现出反射的作用。
枚举可以让一个类的实例固定化,从而对比现实生活中一些固定的事物,如交通灯。
内省可以自由操控实体Bean的属性,可以再Hibernate中得以体现。
注解也是在Hibernate中体现的,由于注解原理的实现很像一个类,因此可以简化Hibernate的操作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐