注解
JDK5.0开始引入的技术
内置注解
定义在java.lang包中
@Override:重写
@Deprecated:表示不鼓励使用某个方法、属性(已被淘汰)
@SuppressWarnings:抑制编译时的警告信息
元注解
负责注解其他的注解
@Target:用于描述注解的使用范围
@Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(SOURCE<CLASS<RUNTIME)
@Document:说明该注解将被包含在javadoc中
@Inherited:说明子类可以继承父类中的该注解
(一个Java源文件只能有一个public类)
自定义注解
- (public)@interface 注解名{定义内容}
- 其中的每一个方法实际上是声明了一个配置参数
- 方法的名称就是参数的名称
- 返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)
- 可以通过default来声明参数的默认值
- 如果只有一个参数成员,一般参数名为value
- 注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值
//自定义注解
public class test02 {
//注解可以显示赋值,如果没有默认值,我们就必须给注解赋值
@MyAnnotation2(name = "aaa")
public void test(){
}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//注解的参数:参数类型+参数名();
String name()default "";
int age() default 0;
String[] schools() default {"西部开源","清华大学"};
}
反射
动态语言和静态语言
动态语言:在运行时可以改变其结构的语言,例如:Object-C、C#、JavaScript、PHP、Python等
静态语言:运行时结构不可变,如:Java、C、C++
java可以称为“准动态语言”
反射:将类的各个组成部分封装为其他对象
创建一个类,该类中有成员变量、构造方法、成员方法等,编译后生成class文件,该文件有多个部分,每个部分分别存储成员变量、方法…这是源代码阶段,这个阶段在磁盘上。
有一个类,类名叫class 即Class class(T),该类对象中有成员变量数组、构造方法数组等,类加载器将class文件加载到类对象中并保存到对应位置,这个阶段叫class类对象阶段,这个阶段在内存上上
最后,我们通过New一个对象,并调用该对象的成员变量、方法等,这是runtime运行时阶段。
回过头来看反射,以上图为例,把成员变量封装成field对象,把构造方法封装成构造器对象,把成员方法封装成成员方法对象。
反射的好处:
- 可以在程序运行过程中,操作这些对象
- 可以解耦,提高程序的可扩展性
获取Class对象的方式
有三种方式,分别对应着java代码经历的三个阶段
1.Class.forName(“全类名”):将字节码文件加载进内存,返回class对象——源代码阶段
(多用于配置文件,将类名定义在配置文件中。读取文件,加载类)
2.类名.class:通过类名的属性class获取(已经将字节码文件加载进内存了,也就是说这个对象已经有了,因此就不需用加载它了,只需要获取它)
(多用于参数的传递)
3.对象.getClass:这个方法在Object中定义的(以上图为例,已经有Persion对象了,通过对象的方法来获取)
(多用于对象的获取字节码的方式)
首先创建Person实体类
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public static void main(String[] args) throws ClassNotFoundException {
// 1. Class.forName("全类名")
Class<?> c1 = Class.forName("com.zhengquan.domain.Person");
System.out.println(c1);
// 2.类名.class
Class c2 = Person.class;
System.out.println(c2);
// 3.对象.getClass()
Person p =new Person();
Class c3 = p.getClass();
System.out.println(c3);
//比较三个对象
System.out.println(c1 == c2);//true
System.out.println(c1 == c3);//true
}
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个
Class对象功能概述
获取功能
- 获取成员变量们
- Field[] getFields() :
获取所有Public修饰的成员变量 - Field getField(String name)
获取指定名称的Public修饰的成员变量 - Field[] getDeclaredFields()
获取所有的成员变量 - Field getDeclaredField(String name)
获取指定名称的成员变量
- Field[] getFields() :
- 获取构造方法们
- Constructor<?>[] getConstructors()
- Constructor getConstructor(类<?>… parameterTypes)
- Constructor<?>[] getDeclaredConstructors()
- Constructor getDeclaredConstructor(类<?>… parameterTypes)
- 获取成员方法们
- Method[] getMethods()
- Method getMethod(String name, 类<?>… parameterTypes)
- Method[] getDeclaredMethods()
- Method getDeclaredMethod(String name, 类<?>… parameterTypes)
- 获取类名
- String getName()
- …
Class对象功能——获取field
首先修改一下person类
private String name;
private int age;
public String a;
protected String b;
String c;
private String d;
写Demo2,分别测试一下方法
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//1.获取Person的Class对象
Class c1 = Person.class;
// 1. 获取成员变量们
// * Field[] getFields()
// * Field getField(String name)
// * Field[] getDeclaredFields()
// * Field getDeclaredField(String name)
//1.Field[] getFields()
Field[] fields = c1.getFields();
for(Field field : fields){
System.out.println(field);
}
System.out.println("------------------------------");
Field a = c1.getField("a");
//获取成员变量a的值
Person p = new Person();
Object value = a.get(p);
System.out.println(value);
//设置a的值
a.set(p,"张三");
System.out.println(p);
System.out.println("===============================");
//获取所有成员变量,不考虑修饰符
Field[] declaredFields = c1.getDeclaredFields();
for(Field declaredField : declaredFields){
System.out.println(declaredField);
}
Field d = c1.getDeclaredField("d");
//忽略访问权限修饰符的安全检查
d.setAccessible(true);//暴力反射
Object value2 = d.get(p);
System.out.println(value2);
}
打印输出
Class对象功能——获取Construc
创建对象:
public static void main(String[] args) throws Exception {
//1.获取Person的Class对象
Class c1 = Person.class;
// 获取构造方法们
// * Constructor<?>[] getConstructors()
// * Constructor<T> getConstructor(类<?>... parameterTypes)
// * Constructor<?>[] getDeclaredConstructors()
// * Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
Constructor constructor = c1.getConstructor(String.class, int.class);
System.out.println(constructor);
//创建对象
Object person = constructor.newInstance("张三", 23);
System.out.println(person);
System.out.println("------------------------------");
Constructor constructor2 = c1.getConstructor();
System.out.println(constructor2);
//创建对象
Object person2 = constructor2.newInstance();
System.out.println(person2);
//用这种方式创建空参对象比较简单
Object o = c1.newInstance();
System.out.println(o);
}
打印输出:
Class对象功能——获取Method
public static void main(String[] args) throws Exception {
//1.获取Person的Class对象
Class c1 = Person.class;
// 获取成员方法们
// * Method[] getMethods()
// * Method getMethod(String name, 类<?>... parameterTypes)
// * Method[] getDeclaredMethods()
// * Method getDeclaredMethod(String name, 类<?>... parameterTypes)
//获取指定名称的方法
Method eat = c1.getMethod("eat");
//执行方法
Person p = new Person();
eat.invoke(p);
//获取指定名称的方法(带参数)
Method eat2 = c1.getMethod("eat",String.class);
//执行方法
eat2.invoke(p,"饭");
System.out.println("-----------------------");
//获取所有public修饰的方法(不仅有看到的,还有object方法,比如notify)
Method[] methods = c1.getMethods();
for (Method method : methods) {
System.out.println(method);
//获取方法名
String name = method.getName();
System.out.println(name);
}
//还有获取类名的方法
String className = c1.getName();
System.out.println(className);
}
案例
*需求:写一个“框架”,可以帮我们创建任意类的对象,
并且执行其中任意方法
*实现:
1.配置文件
2.反射
*步骤:
1.将需要创建的对象的全类名和需要执行的方法定义在配置文件中
2.在程序中加载读取配置文件
3.使用反射技术来加载类文件进内存
4.创建对象
5.执行方法
之前的方法:
这里创建一个对象,并执行其中方法。
弊端:如果这是一个框架,应该是半成品的,但这里创建的对象写死了
改进:
写配置文件:
className=com.zhengquan.domain.Person
methodName=eat
代码:
public static void main(String[] args) throws Exception {
//可以创建任意类的对象,可以执行任意方法
//前提:不能改变该类的任何代码
// Person p = new Person();
// p.eat();
// Student stu = new Student();
// stu.sleep();
//1.加载配置文件
//1.1创建Properties
Properties pro = new Properties();
//1.2加载配置文件,转换为一个集合
//1.2.1获取class目录下的配置文件
ClassLoader classLoader = Test.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
pro.load(is);
//2.获取配置文件中定义的数据
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//3.加载该类进内存
Class cls = Class.forName(className);
//4.创建对象
Object obj = cls.newInstance();
//5.获取方法对象
Method method = cls.getMethod(methodName);
//6.执行方法
method.invoke(obj);
}
执行:
如果想执行Student类的sleep方法,只需要更改配置文件,不用更改代码了:
className=com.zhengquan.domain.Student
methodName=sleep