反射
利用反射机制获取到每个类对应的Class对象,一共有三种方法获取
-
类名.class
-
Class.forName()
使用Class类静态方法forName(),通过包名.类名,注意返回值是Class<?> -
getClass()
方法
Class对象和多态
使用反射机制判断类型
public static void main(String[] args) {
String str = "";
System.out.println(str.getClass() == String.class); //直接判断是否为这个类型
}
判断是否为子类或者是接口/抽象类的实现,使用asSubClass()
方法:
public static void main(String[] args) {
Integer i = 10;
i.getClass().asSubclass(Number.class); //当Integer不是Number的子类时,会产生异常
}
使用getSuperclass()
方法获取当前父类的Class对象
public static void main(String[] args) {
System.out.println(Integer.class.getSuperclass());
}
使用getGenericSuperclass()
获取父类的原始类型的Type
使用类似的方法去获取父类的接口以及接口类型
public static void main(String[] args) {
for (Class<?> anInterface : String.class.getInterfaces()) {
System.out.println(anInterface.getName());
}
System.out.println("-----------------------------------");
for (Type genericInterface : String.class.getGenericInterfaces()) {
System.out.println(genericInterface.getTypeName());
}
}
创建类对象
获取到当前类的Class对象后,可以使用newInstance()
方法创建该类的对象
public class test01 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Class<Student> clazz = Student.class;
Student student = clazz.newInstance();
student.test();
}
}
class Student{
public void test(){
System.out.println("hello world");
}
}
但是newInstance()
只适用于无参构造,否则会出现InstantiationException异常
当默认无参构造的权限不是public
时,会出现IllegalAccessException异常,表示我们无权去调用默认构造方法。
在JDK9之后,不再推荐使用newInstance()
方法了,而是使用getConstructor()
方法来获取类的构造方法,使用的格式为
Class对象.getConstructor(构造方法形参1的class对象,构造方法形参2的class对象,...).newInstance(实参1,实参2,...)
示例:
public class test01 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<Student> clazz = Student.class;
Student student = clazz.getConstructor(String.class,int.class).newInstance("123",123);
}
}
class Student{
Student(){
}
public Student(String str,int x){
System.out.println("构造方法");
}
public void test(){
System.out.println("hello world");
}
}
需要注意的是,该方法的前提是该有参构造函数的权限是public
,否则就需要使用getDeclaredConstructor()
方法,并使用setAccessible()
方法修改访问权限,然后再进行构造
public class test01 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<Student> clazz = Student.class;
Constructor<Student> declaredConstructor = clazz.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
Student student = declaredConstructor.newInstance("123",123);
student.test();
}
}
class Student{
Student(){
}
Student(String str,int x){
System.out.println("构造方法");
}
public void test(){
System.out.println("hello world");
}
}
调用类方法
使用getMethod()
方法可以获取到类中声明为public
的方法,得到一个Method
对象,使用该对象中的invoke()
方法(返回值为获取到的方法的返回值)调用已获取到方法,格式为
Method method = class对象.getMethod("对象中的public成员方法名",方法中形参的class对象,...);
method.invoke(该类的实例化对象,实参1,实参2,...);
示例:
public class test01 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class<Student> studentClass = Student.class;
Student student = studentClass.newInstance();
Method test = studentClass.getMethod("test", String.class);
test.invoke(student,"fafafafd");
}
}
class Student{
Student(){
}
Student(String str,int x){
System.out.println("构造方法");
}
public void test(String a){
System.out.println(a + "hello world");
}
}
同构造方法一样,出现非public
方法时,使用getDeclaredMethod()
方法,然后使用Method
对象中的setAccessible(true)
无视权限修饰符调取方法
如果参数的类型是可变参数,实际就是该参数类型的数组,只需要传入该数组的Class对象即可
修改类的属性
使用getField()
方法可以获取一个类定义的指定字段,得到一个Field
对象后使用该对象的set()
方法即可设定属性值
ublic class test01 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<Student> studentClass = Student.class;
Student student = studentClass.newInstance();
Field age = studentClass.getField("age");
age.set(student,100);
System.out.println(student.age);
}
}
class Student{
public int age;
Student(){
}
Student(String str,int x){
System.out.println("构造方法");
}
public void test(String a){
System.out.println(a + "hello world");
}
}
如果该属性是private属性,也可以按照之前的操作使用getDeclaredField()
获取Field
对象,实现越权访问并设置属性值
public class test01 {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Class<Student> studentClass = Student.class;
Student student = studentClass.newInstance();
Field age = studentClass.getDeclaredField("age");
age.setAccessible(true);
age.set(student,100);
System.out.println(student.age);
}
}
class Student{
int age;
Student(){
}
Student(String str,int x){
System.out.println("构造方法");
}
public void test(String a){
System.out.println(a + "hello world");
}
}
注解
注解可以被标注在任意地方,包括方法上、类名上、参数上、成员属性上、注解定义上等,就像注释一样,它相当于我们对某样东西的一个标记。而与注释不同的是,注解可以通过反射在运行时获取,注解也可以选择是否保留到运行时
预设注解
JDK预设了以下注解,作用于代码:
- @Override- 检查(仅仅是检查,不保留到运行时)该方法是否是重写方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。
- @Deprecated- 标记过时方法。如果使用该方法,会报编译警告。
- @SuppressWarnings- 指示编译器去忽略注解中声明的警告(仅仅编译器阶段,不保留到运行时)
- @FunctionalInterface- Java 8 开始支持,标识一个匿名函数或函数式接口。
- @SafeVarargs- Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
元注解
元注解是作用于注解上的注解,用于我们编写自定义的注解:
- @Retention- 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
- @Documented- 标记这些注解是否包含在用户文档中。
- @Target- 标记这个注解应该是哪种 Java 成员。
- @Inherited- 标记这个注解是继承于哪个注解类(默认 注解并没有继承于任何子类)
- @Repeatable- Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
@Retention表示此注解的保留策略,使用时可以传入参数RetentionPolicy.*
,‘‘ * ’’有三种选择:CLASS
、SOURCE
、RUNTIME
@Target限定注解的使用范围,使用时传入参数ElementType.*
,例如该注解只能标记方法,则使用@Target(ElementType.METHOD)
注解的使用
注解中还可以定义一些属性,也叫成员变量。注解只能有成员变量,不能有成员方法,注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value();
}
默认只有一个属性,且该属性名称为value
时,赋值时不需要手动指定注解的属性名称
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value();
}
public class Main {
@Test("hello world") //括号内传入属性值
public static void main(String[] args) {
}
}
如果不是value
则需要指定属性名称后赋值
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String test();
}
public class Main {
@Test(test = "hello world")
public static void main(String[] args) {
}
}
使用default
关键字可以为属性指定默认值
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value() defualt "hello world";
}
当属性存在默认值,使用注解不用传入属性,如果是数组,数组只有一个元素可以和普通属性一样传入一个值即可,如果是多个值则需要罗列出来用逗号隔开并套上花括号
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String[] test();
String[] test1();
}
@Test(test={"1231","123131"},test1 = {"123131","12313131"})
public class test01 {
public static void main(String[] args){
}
}
反射获取注解
@Test(test={"1231","123131"},test1 = {"123131","12313131"})
public class test01 {
public static void main(String[] args) {
for (Annotation annotation : test01.class.getAnnotations()) {
System.out.println(annotation.annotationType()); //注解类型
System.out.println(annotation instanceof Test); //判断是否为Test
Test test = (Test) annotation;
System.out.println(Arrays.toString(test.test())); //获取注解中的属性值
System.out.println(Arrays.toString(test.test1()));
}
}
}