反射、注解

反射、注解


1、反射


1.1. 反射的介绍

“程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言”,如Python,Ruby是动态语言;显然C++,Java,C#不是动态语言,但是JAVA有着一个非常突出的动态相关机制:Reflection。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。如

/**
* 入门级示例:通过对象获取 包名.类名
* @author Administrator
*/
public class Simple {
	public static void main(String[] args) {
		Simple s=new Simple();
		System.out.println(s.2.getName());
	}
}

Java反射机制,可以实现以下功能:
①在运行时判断任意一个对象所属的类;
②在运行时构造任意一个类的对象;
③在运行时判断任意一个类所具有的成员变量和方法;
④在运行时调用任意一个对象的方法;
⑤生成动态代理。
在这里插入图片描述
获取源头Class(重点)
所有类的对象其实都是Class的实例。这个Class实例可以理解为类的模子,就是包含了类的结构信息,类似于图纸。我们日常生活中,需要创造一个产品,如想山寨一个iphone手机,怎么办?
有三种方式可以实现:
⑴买个iphone手机,拆的七零八落的,开始山寨;
⑵到iphone工厂参观,拿到iphone磨具,开始山寨;
⑶跑到美国盗取iphone的图纸,开始山寨,最后一种最暴力,最爽。

同理,获取类的Class对象也有三种方式
①Class.forName(”包名.类名”)//一般尽量采用该形式
②类.class
③对象.getClass()
思考:目前我们创建对象的方式有哪写?

1.2. 获取Class对象

获取Class的例子:

public class Source {
	public static void main(String[] args) {
		//第一种方式:对象.class
		Source s=new Source();
		Class<?>c1=s.getClass();
		//第二种方式:类.class
		Class<?>c2=Source.class;
		//第三种方式(推荐方式):Class.forName()
		Class<?>c3=null;
		try {
			c3=Class.forName("com.shsxt.ref.simple.Source");
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		System.out.println(c1.getName());
		System.out.println(c2.getName());
		System.out.println(c3.getName());
	}
}

有了class对象,我们就有了一切,这就是反射的源头,接下来就是“庖丁解牛

1.3. 获取修饰符

获取修饰符,使用Modifier即可
在这里插入图片描述

Class<?>clz=Class.forName("com.shsxt.ref.simple.User");
//获得修饰符
int n=clz.getModifiers();
//使用Modifier转换为相应的字符串
System.out.println(Modifier.toString(n));

2. 创建对象


2.1. 构造器

根据Class对象,我们可以获得构造器,为实例化对象做准备。调用以下api即可
在这里插入图片描述

public class GetConstructor {
	public static void main(String[] args) {
		try {
			Class<?>clz=Class.forName("com.shsxt.ref.simple.User");
			//1、获取所有的public 权限的构造器
			Constructor<?>[]con=clz.getConstructors();
			//注意查看构造器的顺序
			for(Constructor<?> c:con){
				System.out.println(c);
			}
			//2、获取所有的构造器
			con=clz.getDeclaredConstructors();
			System.out.println("--------------");
			for(Constructor<?> c:con){
				System.out.println(c);
			}
			System.out.println("----------------");
			//3、获取指定的构造器(放入具体的类型)
			Constructor<?> c=clz.getConstructor(String.class);
			System.out.println(c);
			//非public权限
			System.out.println("----------------");
			c=clz.getDeclaredConstructor(String.class,String.class);
			System.out.println(c);
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
2.2. 实例化对象(重点)

创建对象的方式,有new 、克隆、反序列化,再加一种,根据Class对象,使用newInstance() 或者构造器实例化对象。调用以下api即可
在这里插入图片描述

/**
 * 使用反射创建对象
 */
public class Demo1 {
    public static void main(String[] args) throws Exception {
        Class<Person> aClass = (Class<Person>) Class.forName("第17天.Person");//字符串形式的类的全类名(包名+类名)
        Constructor<Person> constructor = aClass.getDeclaredConstructor(String.class,int.class);//获取第一个参数为String,第二个参数为int的构造器,私有的构造器也可以获取
        System.out.println(constructor);//打印此构造器
        //constructor.setAccessible(true);//如果构造器为私有设置其可访问
        Person person = constructor.newInstance("张三",18);//创建对象
        System.out.println(person);
    }
}

3. 父类与接口


Class<?> clz =Class.forName("com.shsxt.ref.simple.User");
//获取所有接口
Class<?>[] inters=clz.getInterfaces();
for(Class<?> in:inters){
	System.out.println(in.getName());
}
//获取父类
Class<?> cls=clz.getSuperclass();
System.out.println("继承的父类为:"+cls.getName());

4. 属性和方法


同样的我们可以通过api获取类中的属性和方法,包括父类和接口中的

4.1. 属性

获取所有属性(包括父类或接口) ,使用Field 即可操作
在这里插入图片描述

Class<?> clz = Class.forName("com.shsxt.ref.simple.User");
//获取属性
System.out.println("===============本类属性==========");
// 取得本类的全部属性
Field[] field = clz.getDeclaredFields();
for (int i = 0; i < field.length; i++) {
	// 1、权限修饰符
	int mo = field[i].getModifiers();
	String vis = Modifier.toString(mo);
	// 2、属性类型
	Class<?> type = field[i].getType();
	//3、名字
	String name = field[i].getName();
	System.out.println(vis + " " + type.getName() + " "+ name + ";");
}
System.out.println("=========公开的属性包括接口或者父类属性======");
field = clz.getFields();
for (int i = 0; i < field.length; i++) {
	System.out.println(field [i]);
}
4.2. 方法

获取所有方法(包括父类或接口),使用Method即可。
在这里插入图片描述

public static void test() throws Exception {
	Class<?> clz = Class.forName("com.shsxt.ref.simple.User ");
	//获取属性
	System.out.println("===============本类方法===============");
	// 取得全部公共方法
	Method[] methods =clz.getMethods();
	for(Method m:methods){
		//1、权限
		int mod=m.getModifiers();
		System.out.print(Modifier.toString(mod)+" ");
		//2、返回类型
		Class<?> returnType=m.getReturnType();
		System.out.print(returnType.getName()+" ");
		//3、名字
		String name =m.getName();
		System.out.print(name +"(");
		//4、参数
		Class<?>[] para=m.getParameterTypes();
		for(int i=0;i<para.length;i++){
			Class<?> p =para[i];
			System.out.print(p.getName() +" arg"+i);
			if(i!=para.length-1){
				System.out.print(",");
			}
		}
	}
}

5. 数组

操作数组需要借助Array类

//1、创建数组
Object obj =Array.newInstance(int.class, 5);
//2、获取大小
if(obj.getClass().isArray()){ //3、判断是否为数组
	System.out.println(Array.getLength(obj));
	//4、设置值
	Array.set(obj,0, 100);
	//5、获取值
	System.out.println(Array.get(obj,0));
}
6. 类加载器(了解)

在java中有三种类类加载器:
⑴Bootstrap ClassLoader 此加载器采用c++编写,一般开发中很少见。
⑵Extension ClassLoader 用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类
⑶AppClassLoader 加载classpath指定的类,是最常用的加载器。同时也是java中默认的加载器。 了解即可。

public static void main(String[] args) throws Exception {
System.out.println("类加载器
"+ClassLoader.class.getClassLoader().getClass().getName());
}

了解一下类的生命周期 :
在一个类编译完成之后,下一步就需要开始使用类,如果要使用一个类,肯定离不开JVM。在程序执行中JVM通过装载,链接,初始化这3个步骤完成。
类的装载是通过类加载器完成的,加载器将.class文件的二进制文件装入JVM的方法区,并且在堆区创建描述这个类的java.lang.Class对象。用来封装数据。 但是同一个类只会被类装载器装载一次。
链接就是把二进制数据组装为可以运行的状态。链接分为校验,准备,解析这3个阶段
1、校验一般用来确认此二进制文件是否适合当前的JVM(版本),
2、准备就是为静态成员分配内存空间。并设置默认值
3、解析指的是转换常量池中的代码作为直接引用的过程,直到所有的符号引用都可以被运行程序使用(建立完整的对应关系)完成之后,类型也就完成了初始化,初始化之后类的对象就可以正常使用了,直到一个对象不再使用
之后,将被垃圾回收。释放空间。当没有任何引用指向Class对象时就会被卸载,结束类的生命周期

7. 注解

7.1. 注解的介绍

注解是Java 1.5 引入的,目前已被广泛应用于各种Java框架,如Hibernate,Jersey,Spring。注解相当于是一种嵌入在程序中的元数据,可以使用注解解析工具或编译器对其进行解析,也可以指定注解在编译期或运行期有效。在注解诞生之前,程序的元数据存在的形式仅限于java注释或javadoc,但注解可以提供更多功能,它不仅包含元数据,还能作用于运行期,注解解析器能够使用注解决定处理流程。
Annotation(注解)就是Java提供了一种元程序中的元素关联任何信息和任何元数据 (metadata)的途径和方法。Annotation是一个接口,程序可以通过反射来获取指定程序元素的Annotation对象,然后通过Annotation对象来获取注解里面的元数据。注解API非常强大,被广泛应用于各种Java框架。
元数据:描述数据的数据
7.2. 注解的分类
根据注解参数的个数

1)、标记注解:一个没有成员定义的Annotation类型被称为标记注解。
2)、单值注解:只有一个值
3)、完整注解:拥有多个值

根据注解使用方法和用途:

1)、JDK内置系统注解
2)、元注解
3)、自定义注解

7.3. 内置注解

JavaSE中内置三个标准注解,定义在java.lang中:

7.4. @Override

限定重写父类方法,若想要重写父类的一个方法时,需要使用该注解告知编译器我们正在重写一个方法。如此一来,当父类的方法被删除或修改了,编译器会提示错误信息;或者该方法不是重写也会提示错误。

public interface Car {
	void run();
}
class QQ implements Car{
	@Override
	public void run() {}
}
class Bmw implements Car{
	@Override
	void run() {}
}
7.5. @Deprecated

标记已过时,当我们想要让编译器知道一个方法已经被弃用(deprecate)时,应该使用这个注解。Java推荐在javadoc中提供信息,告知用户为什么这个方法被弃用了,以及替代方法是什么

/**
* Deprecated -->该方法过时(有更好的解决方案)
* @author Administrator
*/
public class TestDeprecated {
	@Deprecated
	public int test(){
		System.out.println("TestDeprecated.test()");
		return 0;
	}
	public void test(int a){
		System.out.println("TestDeprecated.test(int)");
	}
}
7.6. @SuppressWarnings

抑制编译器警告,该注解仅仅告知编译器,忽略它们产生了特殊警告。如:在java泛型中使用原始类型。其保持性策略(retention policy)是SOURCE,在编译器中将被丢弃。

/**
* SuppressWarnings 压制警告
* @author Administrator
*/
public class TestSuppressWarnings {
	public static void main(String[] args) {
		@SuppressWarnings("unused")
		List<String> list =new ArrayList<String>();
	}
	@SuppressWarnings("rawtypes") //没有定义范型
	public static List test(){
		return new ArrayList();
	}
}

8. 自定义注解

8.1. 简单入门

@interface:用来声明一个注解。注解类里的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型。可以通过default来声明参数的默认值。

@interface Simple{
//这里定义了一个空的注解,它能干什么呢?我也不知道,但他能用。后面有补充
}
8.2. 元注解

元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它annotation类型作说明。Java5.0定义的元注解有四个,这些类型和它们所支持的类在ava.lang.annotation包中可以找到。

8.2.1. @Target

用于描述注解的使用范围(即:被描述的注解可以用在什么地方)。表示支持注解的程序元素的种类,一些可能的值TYPE,METHOD,CONSTRUCTOR, FIELD等等。如果Target元注解不存在,那么该注解就可以使用在任何程序元素之上。取值(ElementType)有:

1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述域
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

此时在空注解中加入@Target元注解如:

//此注解只能用在方法上
@Target(ElementType.METHOD)
@interface TestMethod {}
8.2.2. @Retention

表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)表示注解类型保留时间的长短。取值(RetentionPoicy)有:

1.SOURCE:在源文件中有效(即源文件保留)
2.CLASS:在class文件中有效(即class保留)
3.RUNTIME:在运行时有效(即运行时保留)

此时在上述注解中加入@Retention元注解如:

// 此注解可以用于注解类、接口(包括注解类型) 或enum声明
@Target(ElementType.TYPE)
//该注解运行时有效。注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理
@Retention(RetentionPolicy.RUNTIME)
@interface TestRn{
}
8.2.3. @Documented

表示使用该注解的元素应被javadoc或类似工具文档化,它应用于类型声明,类型声明的注解会影响客户端对注解元素的使用。如果一个类型声明添加了Documented注解,那么它的注解会成为被注解元素的公共API的一部分,@Documented是一个标记注解。

//可以被例如javadoc此类的工具文档化
@Documented
@interface TestDoc{
}
8.2.4. @Inherited

表示一个注解类型会被自动继承,如果用户在类声明的时候查询注解类型,同时类声明中也没有这个类型的注解,那么注解类型会自动查询该类的父类,这个过程将会不停地重复,直到该类型的注解被找到为止,或是到达类结构的顶层(Object)。

//被子类继承的注解
@Inherited
@interface TestInheri{}

9. 深入自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。

9.1. 定义注解格式:

@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数
的默认值。

public @interface 注解名{定义体}
9.2. 注解参数(即方法)

注解里面的每一个方法实际上就是声明了一个配置参数,其规则如下:

9.2.1. 修饰符

只能用public或默认(default)这两个访问权修饰 ,默认为default

9.2.2. 方法的类型

注解体重点方法的返回值,作为注解参数,只支持以下数据类型:

基本数据类型(int,float,boolean,byte,double,char,long,short);
String类型;
Class类型;
enum类型;
Annotation类型;
以上所有类型的数组

9.2.3. 命名(方法的名字)

对取名没有要求,如果只有一个参数成员,最好把参数名称设为"value",后加小括号。

9.2.4. 参数

注解中的方法不能存在参数

9.2.5. 默认值

可以包含默认值,使用default来声明默认值

/*
* 码农定义注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface Programmer{
	String value() default "马云";
}
/**
* 码农类型注解
* @author peida
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface ProgrammerType {
	/**
	* 类型枚举 程序猿 射鸡师
	*/
	public enum CoderType{MONKEYS,LION,CHOOK};
	/**
	* 颜色属性
	*/
	CoderType type() default CoderType.MONKEYS;
}
/**
* 码农制造厂
* @author Administrator
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@interface ProgrammerProductor {
	/**
	* 厂家编号
	* @return
	*/
	public int id() default -1;
	/**
	* 厂家名称
	* @return
	*/
	public String name() default "shsxt";
	/**
	* 厂家地址
	* @return
	*/
	public String address() default "上海";
}
/**
* 注解使用
*/
class Coder{
	@Programmer("老裴")
	private String coderName;
	@ProgrammerType(type=CoderType.MONKEYS)
	private String coderType;
	@ProgrammerProductor(id=1,name="程序猿乐园",address="浦东新区")
	private String coderProductor;
	public String getCoderName() {
		return coderName;
	}
	public void setCoderName(String coderName) {
		this.coderName = coderName;
	}
	public String getCoderType() {
		return coderType;
	}
	public void setCoderType(String coderType) {
		this.coderType = coderType;
	}
	public String getCoderProductor() {
		return coderProductor;
	}
	public void setCoderProductor(String coderProductor) {
		this.coderProductor = coderProductor;
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值