文章目录
反射
0. 学习资料
(87条消息) 高薪程序员&面试题精讲系列24之你熟悉反射吗?_一一哥-CSDN博客
1. 反射基础
介绍
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
通过Java语言中的反射机制可以操作字节码文件。可以读写字节码文件,操作代码片段(class文件)
反射相关的包:
java.lang.reflect.*;
反射相关的类:
- java. lang.class:代表整个字节码。代表一个类型,代表整个类。
- java.lang.refleat.Field:代表字节码中的属性字节码。代表类中的成员变量**(静态变量和静态变量)**
- java.lang.reflect.constructor:代表字节码中的构造方法字节码。代表类中的构造方法
- java.lang.reflect.Method:代表字节码中的方法字节码。代表类中的方法。
//1.Class
public class User {
//2.Filed
private String name;
//3.Constructor
public User(){
}
//4.Method
public void setName(String name){
}
}
获取类
类相关
- static 类<?> forName(String className) 返回与给定的字符串名称的类或接口相关的类对象。
- ClassLoader getClassLoader() 返回类的类装载器。
- String getName() 返回单位名称(类,接口,数组类,原始类型,或无效)的 类对象表示,作为一个 String。
- String getSimpleName() 返回在源代码中给定的底层类的简单名称。
类<? super T> getSuperclass() 返回表示的实体(类、接口类的 类,原始类型或void)的 类代表。 - int getModifiers() 返回该类或接口的java语言的修饰,在整数编码。
- T newInstance() 创建这个 类对象表示的类的一个新实例。
- String toString() 将对象转换为字符串。
- Type[] getGenericInterfaces() 返回表示接口,通过该对象表示的类或接口直接实现的 Types。
- Type getGenericSuperclass() 返回表示实体的直接父类的 Type(类、接口、简单类型或void)的 类代表。 类<?>[] getInterfaces() 确定由该对象表示的类或接口实现的接口。
- String getTypeName() 返回此类型的名称的信息字符串。
与注解相关
- boolean isAnnotation() 如果这 类对象表示注释类型返回true。
- boolean isAnnotationPresent(类<? extends Annotation> annotationClass) 如果在这个元素上存在指定类型的注释,则返回真,否则为假。
构造器相关
- Constructor getConstructor(类<?>… parameterTypes) 返回一个 Constructor对象反映指定的公共构造函数的 类对象表示的类。
- Constructor<?>[] getConstructors() 返回一个数组包含 Constructor物体反射所有的 类对象表示的类的公共构造函数。
- Constructor getDeclaredConstructor(类<?>… parameterTypes) 返回一个Constructor对象反映指定的构造函数的类或接口的 类对象表示。
- Constructor<?>[] getDeclaredConstructors() 返回 Constructor物体反射所有的构造函数通过 类对象表示的类中声明一个数组。
属性相关
- Field getDeclaredField(String name) 返回一个对象,反映了 Field指定声明字段的类或接口的 类对象表示。
- Field[] getDeclaredFields() 返回 Field物体反射所有字段的类或接口的 类对象表示声明数组。
- Field getField(String name) 返回一个 Field对象反映的类或接口的 类对象表示的指定公共成员。
- Field[] getFields() 返回一个数组包含 Field物体反射的类或接口的 类对象代表所有可访问的公共领域。
方法相关
- Method getDeclaredMethod(String name, 类<?>… parameterTypes) 返回一个 方法对象反映指定声明方法的类或接口的 类对象表示。
- Method [] getDeclaredMethods() 返回一个数组包含 方法物体反射所有声明的方法的类或接口的 类对象,代表包括公众、保护,默认(包)的访问,和私有方法,但不包括继承的方法。
- Method getMethod(String name, 类<?>… parameterTypes) 返回一个 方法对象反映的类或接口的 类对象表示的指定公共成员方法。
- Method [] getMethods() 返回一个数组包含 方法物体反射所有的类或接口的 类对象表示的公共方法,包括那些由类或接口的超类和超接口继承的声明。
获取类的三种方式
Java提供了三种通过反射获取类的范式。
-
Class.forName(“类的包路径.类名”)
-
public class ReflectTest01 { public static void main(String[] args) { try { Class c1 = Class.forName("java.lang.String");//c1表示String.class文件,表示String类型 Class c2 = Class.forName("java.util.Date");//表示Date类型 Class c3 = Class.forName("java.lang.Integer");//表示Integer类型 Class c4 = Class.forName("java.lang.System");//表示System类型 } catch (ClassNotFoundException e) { e.printStackTrace(); } } }
-
注意:我们可以使用Class.forName会导致类加载,可以来执行某个类的静态代码块。
-
当我们只需要加载静态代码快,类的其他内容不执行时,我们可以通过Class.forName(“完整类名”)来实现。
-
该方法会导致类加载时,静态代码块执行。(jdbc注册驱动使用)。
-
public class ReflectTest03 { public static void main(String[] args) { try { Class.forName("lession_21_12_06_reflect.User"); } catch (ClassNotFoundException e) { e.printStackTrace(); } } } public class User { static {System.out.println("这是User类的静态代码块");} }
-
-
-
对象.class()
-
public class ReflectTest01 { public static void main(String[] args) { String str = "123"; Class<? extends String> x = str.getClass(); System.out.println(aClass);//class java.lang.String Date date = new Date(); Class<? extends Date> y = date.getClass(); System.out.println(y);//class java.util.Date } }
-
进行对比
-
Class c1 = Class.forName("java.lang.String"); Class<? extends Date> y = date.getClass(); System.out.println(c1==x);//true System.out.println(c2==y);//true
-
-
-
任意类型.class;
-
public class ReflectTest01 { public static void main(String[] args) { //方式三: Class<String> s = String.class; Class<Date> d = Date.class; Class<Integer> i = int.class; Class<Character> c = char.class; } }
-
汇总
public class ReflectTest01 {
public static void main(String[] args) {
//方式一:
Class c1 =null;
Class c2=null;
try {
c1 = Class.forName("java.lang.String");//c1表示String.class文件,表示String类型
c2 = Class.forName("java.util.Date");//表示Date类型
Class c3 = Class.forName("java.lang.Integer");//表示Integer类型
Class c4 = Class.forName("java.lang.System");//表示System类型
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//方式二:
String str = "123";
Class<? extends String> x = str.getClass();
System.out.println(x);//class java.lang.String
Date date = new Date();
Class<? extends Date> y = date.getClass();
System.out.println(date.getClass());//class java.util.Date
System.out.println(c1==x);
System.out.println(c2==y);
//方式三:
Class<String> s = String.class;
Class<Date> d = Date.class;
Class<Integer> i = int.class;
Class<Character> c = char.class;
}
}
创建对象
通过反射创建对象的,使用的类的newInstance()方法,该方法在Java9已经淘汰。
new Instance()方法会调用User这个类的无参数构造方法,完成对象的创建。因此,如果一个类没有提供无参构造方法,是会出错的。
public class ReflectTest02 {
public static void main(String[] args) {
//创建对象
User user = new User();
System.out.println(user);//lession_21_12_06_reflect.User@1540e19d
//使用反射创建对象
try {
Class aClass = Class.forName("lession_21_12_06_reflect.User");
//通过反射机制来获取实例
Object object = aClass.newInstance();
System.out.println(object);//lession_21_12_06_reflect.User@677327b6
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
获取父类或接口
class Father{
}
interface Action{
}
public class Student extends Father implements Action{}
public class QueryMethodTest {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Class studentClass = Class.forName("lession_21_12_06_reflect.Student");
Class superclass = studentClass.getSuperclass();
System.out.println(superclass.getSimpleName());
Class[] interfaces = studentClass.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
System.out.println(interfaces[i].getSimpleName());
}
}
课堂练习
public class Student {
public static String name;
static {
System.out.println("静态代码块执行了");
name = "123456";
}
}
public class ReflectClass {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException, InstantiationException, IllegalAccessException {
//热身
Class threadClass = Class.forName("java.lang.Thread");
String threadClassName = threadClass.getName();
System.out.println("类名称"+threadClassName);
String threadSimpleName = threadClass.getSimpleName();
System.out.println("类简称"+threadSimpleName);
System.out.println("直接父类"+threadClass.getSuperclass());
Class[] interfaces = threadClass.getInterfaces();
for (Class item:interfaces
) {
System.out.println("接口"+item);
}
Constructor constructor = threadClass.getConstructor();
System.out.println("构造方法"+constructor);
Method[] methods = threadClass.getMethods();
for (Method method : methods
) {
System.out.println("普通方法"+method);
}
Field[] fields = threadClass.getFields();
for (Field field : fields
) {
System.out.println("公共属性"+field);
}
Field[] declaredFields = threadClass.getDeclaredFields();
for (Field field : declaredFields
) {
System.out.println("所有属性"+field);
}
int modifiers = threadClass.getModifiers();
System.out.println("类的修饰符"+Modifier.toString(modifiers));
Field threadInitNumber = threadClass.getDeclaredField("threadInitNumber");
System.out.println(Modifier.toString(threadInitNumber.getModifiers()));
//使用反射,获取类的几种方式。
//1. 使用Class.forName()获取
Class<?> stringClass = Class.forName("java.lang.String");
System.out.println(stringClass);
//2. 通过实例获取
String str = new String("张三");
Class strClass = str.getClass();
System.out.println(strClass);
System.out.println(stringClass==strClass);
//3. 通过类获取
Class stringClass1 = String.class;
System.out.println(strClass==stringClass1);
//使用反射,进行类加载,静态代码块执行。
Class studentClass = Class.forName("lession_21_12_08_reflect.Student");
Student student1 = (Student)studentClass.newInstance();
System.out.println(student1);
Student student2 = (Student)studentClass.newInstance();
System.out.println(student2);
}
}
获取属性
-
int getModifiers() 返回的 Field对象表示的java语言修饰符为整数。
-
String getName() 返回的 Field对象表示的字段的名称。
-
Object get(Object obj) 返回的 Field表示字段的值,指定对象上。
-
类<?> getType() 返回一个 类对象标识声明类型的 Field对象表示的类型。
-
int hashCode() 返回该 Field hashCode。
-
boolean isEnumConstant() 返回 true如果该字段表示枚举类型的元素;否则返回 false。
-
void set(Object obj, Object value) 为对象obj的当前属性设置值为value
-
void setAccessible(boolean flag) true,允许访问和修改private等权限修饰符修饰的属性。
基本使用
我们可以通过反射机制,获取每一个类的属性名称,属性类型,修饰符等信息。
常用API:
public class Student {
//使用不同的权限修饰符修饰
public static final int no=0;
private String name;
protected int age;
boolean sex;
}
public class ReflectFiledTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class studentClass = Class.forName("lession_21_12_06_reflect.Student");
//获取完整类名
String className = studentClass.getName();
System.out.println("完整类名"+className);
//获取简写类名
String simpleName = studentClass.getSimpleName();
System.out.println("简写类名"+simpleName);
//获取类中的所有public修饰的属性
Field[] fields = studentClass.getFields();
System.out.println(fields.length);
Field field = fields[0];
//获取属性名
String fieldName = field.getName();
System.out.println("属性名"+fieldName);
//获取所有的属性
Field[] allFields = studentClass.getDeclaredFields();
System.out.println(allFields.length);
for (int i =0; i <allFields.length; i++) {
System.out.println("属性"+i);
Field field1 = allFields[i];
System.out.println("属性名"+field1.getName());
//获取修饰符列表
int modifiers = field1.getModifiers();
String modifierName= Modifier.toString(modifiers);
System.out.println("权限修饰附:"+modifiers+"——"+modifierName);
//获取属性类型
Class<?> type = field1.getType();
String typeName = type.getName();
System.out.println("属性类型名称:"+typeName);
}
}
}
获取/修改属性值
修改属性时,对于私有属性的修改和获取,我们需要设置nameField.setAccessible(true); 允许我们对私有属性参与修改。(有风险)
nameField.setAccessible(true); 打破封装,让外部类也可以访问私有成员
修改属性的要素:1. 对象 2. 属性 3.属性值
获取属性的要素:1. 对象 2. 属性
反射机制让我们代码更复杂,但是让程序更灵活,我们可以通过配置文件,来创建对象并为对象赋值(JavaEE企业级开发都是用这种方式。)
public class Student {
//使用不同的权限修饰符修饰
public static final int no=0;
private String name;
protected int age;
boolean sex;
}
public class QueryFiledTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException {
//设置producted修饰的属性
Class studentClass = Class.forName("lession_21_12_06_reflect.Student");
Object o = studentClass.newInstance();
Field ageField = studentClass.getDeclaredField("age");
ageField.set(o, 18);
System.out.println(ageField.get(o));
//设置private修饰的属性
Field nameField = studentClass.getDeclaredField("name");
nameField.setAccessible(true);//设置为可以修改私有成员
nameField.set(o, "张三");
System.out.println(nameField.get(o));
}
}
课堂练习
public class Student {
public static String name;
private Integer age;
private String school;
static {
System.out.println("静态代码块执行了");
name = "123456";
}
public static String getName() {
return name;
}
public static void setName(String name) {
Student.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", school='" + school + '\'' +
'}';
}
}
public class ReflectField {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
//获取属性信息
Class aClass = Class.forName("lession_21_12_08_reflect.Student");
Field schoolName = aClass.getDeclaredField("school");
System.out.println("修饰符"+ Modifier.toString(schoolName.getModifiers()));
System.out.println("类型"+schoolName.getType());
System.out.println("属性名称"+schoolName.getName());
System.out.println("GenericType"+schoolName.getGenericType());
Student student = new Student();
student.setAge(18);
student.setSchool("河南师范大学");
System.out.println(student);
schoolName.setAccessible(true);
schoolName.set(student,"河南大学");
System.out.println(schoolName.get(student));
System.out.println(student);
//获取属性值和修改属性值
Student student1 = new Student();
student1.setAge(3);
student1.setSchool("阳光幼儿园");
Class<? extends Student> student1Class = student1.getClass();
Field age = student1Class.getDeclaredField("age");
Field school = student1Class.getDeclaredField("school");
age.setAccessible(true);
school.setAccessible(true);
System.out.println(age.get(student1));
System.out.println(school.get(student1));
System.out.println(student1);
//修改属性
age.set(student1,8);
school.set(student1,"太阳小学");
System.out.println(student1);
//设置静态属性值
Field name = studentClass.getDeclaredField("name");
name.set(null,"李四");
System.out.println(name.get(null));
}
}
获取方法
- int getModifiers() 返回该对象表示可执行的java语言 modifiers。
- 类<?> getReturnType() 返回一个 类表示这 方法对象表示法的形式返回类型。
- String getName() 返回的 方法对象表示的方法的名称,作为一个 String。
- 类<?> getDeclaringClass() 返回表示的类或接口声明可执行该对象表示的类对象。
- int getParameterCount() 返回该对象表示的可执行文件的形式参数(无论是否显式声明或隐式声明或隐式声明的或不隐式声明的)的数量。
- 类<?>[] getParameterTypes() 返回表示形式参数类型 类对象的数组,在声明顺序,该对象表示的可执行文件。
- TypeVariable<方法>[] getTypeParameters() 返回表示该类型声明的变量
- 类<?>[] getExceptionTypes() 返回表示异常的类型声明是由底层执行该对象表示的 类抛出对象数组。
- Object invoke(Object obj, Object… args) 调用底层的方法,这 方法对象表示,对指定对象的指定参数。
方法要素:
- 对象
- 方法名
- 参数
- 返回值类型
基本使用
public class QueryMethodTest {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
Class studentClass = Class.forName("lession_21_12_06_reflect.Student");
Object o = studentClass.newInstance();
Method[] methods = studentClass.getDeclaredMethods();
for (Method method : methods
) {
System.out.println("方法-------------");
int modifiers = method.getModifiers();
System.out.println("修饰符"+Modifier.toString(modifiers));
System.out.println("返回值类型"+method.getReturnType().getName());
System.out.println("方法名"+method.getName());
Class[] parameters = method.getParameterTypes();
for (Class parameter : parameters
) {
System.out.println("参数"+parameter.getSimpleName());
}
}
}
}
调用方法
public class QueryMethodTest {
public static void main(String[] args) throws Exception {
Method setNameAndAge = studentClass.getDeclaredMethod("setNameAndAge", String.class, int.class);
Method setName = studentClass.getDeclaredMethod("setName", String.class);
Object o1 = setNameAndAge.invoke(o, "张三", 18);
//o为对象,o1,o2为方法的返回值
System.out.println(o);
System.out.println(o1);
Object o2 = setName.invoke(o, "张三");
System.out.println(o2);
}
}
public class Student {
//使用不同的权限修饰符修饰
public static final int no = 0;
private String name;
protected int age;
boolean sex;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String setName(String name) {
this.name = name;
return this.name;
}
public Student setNameAndAge(String name, int age) {
this.name = name;
this.age = age;
new Student();
return new Student(name, age);
}
}
课堂练习
public class Student {
public static String name;
private Integer age;
private String school;
static {
System.out.println("静态代码块执行了");
name = "123456";
}
public static String getName() {
return name;
}
public static void setName(String name) {
Student.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getSchool() throws Exception {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", school='" + school + '\'' +
'}';
}
}
public class ReflectMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
//获取方法
Class aClass = Class.forName("lession_21_12_08_reflect.Student");
Method[] methods = aClass.getDeclaredMethods();
//获取方法信息
for (Method method : methods
) {
System.out.println("修饰符" + Modifier.toString(method.getModifiers()));
System.out.println("返回值类型" + method.getReturnType());
System.out.println("名称" + method.getName());
System.out.println("参数" + method.getParameters());
System.out.println("泛型" + method.getTypeParameters());
System.out.println("异常" + method.getExceptionTypes());
System.out.println("注解" + method.getAnnotations());
System.out.println(method);
}
Student student = new Student();
System.out.println("初始" + student);
Class studentClass = student.getClass();
Method nameMethod = studentClass.getDeclaredMethod("setName", String.class);
nameMethod.invoke(student, "张三");
Method ageMethod = studentClass.getDeclaredMethod("setAge", Integer.class);
ageMethod.invoke(student, 198);
Method schoolMethod = studentClass.getDeclaredMethod("setSchool", String.class);
schoolMethod.invoke(student, "河南大学");
System.out.println(student);
//调用静态方法
nameMethod.invoke(null,"张三");
System.out.println(Student.getName());
}
}
获取构造器
-
类 getDeclaringClass() 返回表示的类或接口声明可执行该对象表示的类对象。
-
类<?>[] getExceptionTypes() 返回 类对象表示异常的类型声明是由底层执行该对象表示抛出数组。
-
int getModifiers() 返回该对象表示可执行的java语言 modifiers。
-
String getName() 返回这个构造函数的名称,作为一个字符串。
-
int getParameterCount() 返回该对象表示的可执行文件的形式参数(无论是否显式声明或隐式声明或隐式声明的或不隐式声明的)的数量。
-
类<?>[] getParameterTypes() 返回表示形式参数类型 类对象的数组,在声明顺序,该对象表示的可执行文件。
-
TypeVariable<Constructor>[] getTypeParameters() 返回 TypeVariable对象表示的类型变量的声明通过 GenericDeclaration对象表示的泛型声明一个数组,在声明顺序。
-
T newInstance(Object… initargs) 利用这 Constructor对象创建和初始化的构造函数的声明类的一个新实例构造函数,用指定的初始化参数。
public class Student {
//使用不同的权限修饰符修饰
public static final int no = 0;
private String name;
protected int age;
boolean sex;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class ConstructTest {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class studentClass = Class.forName("lession_21_12_06_reflect.Student");
Constructor[] constructors = studentClass.getConstructors();
for (Constructor constructor:constructors
) {
System.out.println("权限修饰符"+Modifier.toString(constructor.getModifiers()));
System.out.println("名称"+constructor.getName());
}
Constructor constructor = studentClass.getDeclaredConstructor(String.class, int.class);
Object o = constructor.newInstance("张三", 18);
System.out.println(o);
Constructor constructor2 = studentClass.getDeclaredConstructor();
Object o2 = constructor2.newInstance();
System.out.println(o2);
}
}
课堂练习
public class Student {
public static String name;
private Integer age;
private String school;
public Student() {
System.out.println("无参构造");
}
public Student(String school) {
System.out.println("单参构造");
this.school = school;
}
public Student(Integer age, String school) {
System.out.println("全参构造");
this.age = age;
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", school='" + school + '\'' +
'}';
}
}
public class ReflectConstructor {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//获取操作
Class studentClass = Class.forName("lession_21_12_08_reflect.Student");
Constructor constructor0 = studentClass.getConstructor();
Constructor constructor1 = studentClass.getConstructor(String.class);
Constructor constructor2 = studentClass.getConstructor(Integer.class, String.class);
Constructor[] constructors = studentClass.getConstructors();
for (Constructor constructor : constructors
) {
System.out.println("修饰符" + Modifier.toString(constructor.getModifiers()));
System.out.println("名称" + constructor.getName());
System.out.println("注解");
Arrays.stream(constructor.getParameterAnnotations()).forEach(System.out::println);
System.out.println("参数" + constructor.getParameterTypes());
}
//使用
Constructor constructor = studentClass.getConstructor(Integer.class, String.class);
Student student = (Student)constructor.newInstance(18, "河南师范大学");
System.out.println(student);
}
}
4. 实战
需求:写一个判断对象的属性值是否全为null的工具类。
全为null:返回true
有一个不为null:返回false
final类型和静态类型的变量不需要判断
class Employee {
Integer id;
String name;
Integer age;
}
class Teacher {
private Integer id;
static final String name = "123";
static final Integer age = 123;
}
public class ReflectUtils {
public static void main(String[] args) throws IllegalAccessException {
Employee employee1 = new Employee();
employee1.name = "雇员1";
employee1.age = 19;
Employee employee2 = new Employee();
// employee2.name = "雇员2";
Teacher teacher1 = new Teacher();
System.out.println(ReflectUtils.isAllNull(employee1));//false
System.out.println(ReflectUtils.isAllNull(employee2));//true
System.out.println(ReflectUtils.isAllNull(teacher1));//false
System.out.println(ReflectUtils.isAllNull(new Teacher()));//true
}
public static boolean isAllNull(Object obj) throws IllegalAccessException {
Class objClass = obj.getClass();
Field[] fields = objClass.getDeclaredFields();
for (Field field : fields
) {
if (Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) continue;
field.setAccessible(true);
if (field.getName().equals("id")) continue;
if (field.get(obj) != null) return false;
field.setAccessible(false);
}
return true;
}
}
2. 文件处理优化
公共Student类
public class Student {
public static String name;
private Integer age;
private String school;
public Student() {
System.out.println("无参构造");
}
static {
System.out.println("静态代码块执行了");
name = "123456";
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", school='" + school + '\'' +
'}';
}
}
文件处理
我们可以使用反射来读取相应的配置文件,并且生成相应对象,这样更加灵活。我们可以根据配置文件,在系统中创建不同的对象。
优点:代码不用改动,修改配置文件来创建不同对象。非常灵活
高级框架底层都采用了反射机制。
public class ReflectFile {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, FileNotFoundException {
//普通方式创建对象,代码写死,只能创建Student对象
// Student u = new Student();
// System.out.println(u);
//通过反射创建对象
FileReader reader = new FileReader("user.properties");
Properties properties = new Properties();
properties.load(reader);
reader.close();
String user = properties.getProperty("user");
String age = properties.getProperty("age");
String name = properties.getProperty("name");
String school = properties.getProperty("school");
Class aClass = Class.forName(user);
Object o = aClass.newInstance();
Class<?> studentClass = o.getClass();
Field[] fields = studentClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
if (fields[i].getName().equals("name")) {
fields[i].set(o, name);
continue;
} else if (fields[i].getName().equals("age")) {
fields[i].set(o, Integer.valueOf(age));
continue;
} else if (fields[i].getName().equals("school")) {
fields[i].set(o, school);
continue;
}
}
System.out.println(o);
}
}
user.properties在当前项目目录下,内容是:
user=lession_21_12_08_reflect.Student
age=18
name=张三
school=河南师范
文件路径优化
在idea中,我们使用相对路径默认是以当前工程目录为起始目录。如果我们在此写死,很可能使用其他软件运行项目的时候,运行不起来。因为不同的编辑器对路径的处理不同。
如果我们使用绝对路径,以盘符开始,此时当我们切换操作系统运行代码时,如我们的代码在mac和linux上运行,他们就没有盘符,因此此方法也不可行。
推荐优先使用类路径存储文件,即当前模块的src目录。我们的文件都存储在这个下面,这样就可以适配不同的编辑器和操作系统。
通过类路径获取文件(麻烦)
user.properties在当前模块的src目录下。
public class ReflectFileTest {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
String path = Thread.currentThread()//当前线程对象
.getContextClassLoader()//当前类加载器
.getResource("user.properties")//获取资源(默认从类路径(当前模块的src)下加载资源)
.getPath();//获取绝对路径(该方法跨平台)
System.out.println(path);
FileReader reader = new FileReader(path);
Properties properties = new Properties();
properties.load(reader);
String user = properties.getProperty("user");
String age = properties.getProperty("age");
String name = properties.getProperty("name");
String school = properties.getProperty("school");
Class studentClass = Class.forName(user);
Object o = studentClass.newInstance();
Field[] fields = studentClass.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
if (fields[i].getName().equals("name")) {
fields[i].set(o, name);
continue;
} else if (fields[i].getName().equals("age")) {
fields[i].set(o, Integer.valueOf(age));
continue;
} else if (fields[i].getName().equals("school")) {
fields[i].set(o, school);
continue;
}
}
System.out.println(o);
//优化
InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("user.properties");
Properties properties1 = new Properties();
properties1.load(resourceAsStream);
String user = properties1.getProperty("user");
Class aClass = Class.forName(user);
Object o = aClass.newInstance();
System.out.println(o);
}
}
path打印: /G:/ComputerStudy/JavaLession/hsdlession/lession_12/data_lesssion/out/production/ready_lession/user.properties
使用资源绑定器(简单)
资源绑定器ResourceBundle可以帮助我们快速获取并处理类路径下的文件。
注意:
- ResourceBundle只能获取类路径下的properties文件,不能获取其他文件
- 文件不能带后缀名
public class ReflectFileTest {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//使用资源绑定器
ResourceBundle bundle = ResourceBundle.getBundle("lession_21_12_08_reflect.user");
String user = bundle.getString("user");
Class aClass = Class.forName(user);
Object o = aClass.newInstance();
System.out.println(o);
}
}
文件获取路径和Class路径的写法区别
使用Stream获取文件的时候,我们必须填写文件的后缀名。因此路径是以/或者\ \进行分割
使用Class.forName()获取的是某个包下的类,因为我们的写法是所在包的写法,以.进行分割
使用ResourceBundle,.进行分割。只不过它只能获取类路径(src)下的properties文件。
3. 类加载器
类加载器是负责加载类的命令/工具。ClassLoader
JDK中自带了3个类加载器
- 启动类加载器:C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar
- 扩展类加载器: C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext/*.jar
- 应用类加载器: classpath中配置的路径.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
String s = “abc”;代码的执行步骤
代码在开始执行之前,会将所需要类全部加载到JVM当中。
通过类加载器加载,看到以上代码类加载器会找String.class文件,找到就加载。
加载流程:
- 首先通过"启动类加载器"加载。
- 注意:启动类加载器专门加载:C:\Program Files\Java\jdk1.8.0_131\jre\lib\rt.jar
- rt.jar中都是JDK最核心的类库。
- 如果通过"启动类加载器"加载不到的时候,会通过"扩展类加载器"加载。
- 注意:扩展类加载器专门加载: C:\Program Files\Java\jdk1.8.0_131\jre\lib\ext/*.jar
- 如果"扩展类加载器"没有加载到,那么会通过"应用类加载器"加载。
- 注意:应用类加载器专门加载: classpath中的类。
双亲委派机制
java中为了保证类加载的安全,使用了双亲委派机制。
优先从启动类加载器中加载,,这个称为"父",父"无法加载到,再从扩展类加载器中加载,这个称为"母"。
双亲委派。如果都加载不到,才会考虑从应用类加载器中加载。直到加载到为止。
注解
1. 注解的定义
注解的定义
**注解:**又称注释类型,是类的一种,编译后生成.class文件。
语法格式:
//自定义注解
[修饰符列表] @interface 注解类型名称 {}
注解使用地方:
注解可以定义的地方:注解,接口,抽象类,类,类成员变量,类方法,构造方法,实例变量,实例方法,形参注解,
注解不可以定义的地方:静态代码块,初始化块
//自定义注解
public @interface MyAnnotation {}
//注解在注解上使用
@MyAnnotation
public @interface MyAnnotationTwo {}
//注解在类里面使用
@MyAnnotation
public class AnnotationTest {
@MyAnnotation
String name;
@MyAnnotation
static String age;
@MyAnnotation //报错
static {}
@MyAnnotation
public AnnotationTest(){}
@MyAnnotation
public static void getName(){}
@MyAnnotation //报错
{}
@MyAnnotation
public void getAge(@MyAnnotation String name,@MyAnnotation Integer age){ }
}
内置注解
java.lang包下提供了三个注解:
Deprecated:用@Deprecated注释的程序元素,不鼓励程序员使用这样的元素,通常是因为它很危险或存在更好的选择或已过时。
Override:表示一个方法声明打算重写超类中的另一个方法声明。
Suppresswarnings指示应该在注释元素(以及包含在该注释元素中的所有程序元素)中取消显示指定的编译器警告
两个元注解:
Target和Retention
Overide注解
标识性注解,给编译器作参考。
该注解只能注解方法,编译器看到方法上有这个注解后,会自动检查该方法是否重写了父类的方法,如果没有重写则报错。
该注解只在编译阶段起作用,和运行期无关!
表示一个方法声明打算重写超类中的另一个方法声明。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Override
public String toString() {
return "AnnotationTest{" +
"name='" + name + '\'' +
'}';
}
Deprecated注解
标注的元素已过时,有更好的方法或很危险
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
使用
public class DeprecatedTest {
@Deprecated
public static void eat(){ }
@Deprecated
public static void drink(){ }
}
元注解
标注“注解类型的注解”称为原注解。
常用的元注解:
-
Target:“被标注的注解”可以出现在那些位置上
-
Target(ElementType枚举的实例)
-
public enum ElementType { TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE, TYPE_PARAMETER, TYPE_USE }
-
如注解:
-
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { }
-
-
-
-
Retention(保留):“被标注的注解”最终保存在哪里
-
Retention(RetentionPolicy枚举的实例)
-
public enum RetentionPolicy { SOURCE,//表示该注解只被保留在Java源文件中(编译后生成的.class文件中不包含本注解) CLASS,//便是该注解被保存在class文件中 RUNTIME//表示该注解被保存在class文件中,并且可以被反射机制读取到 }
-
-
如注解
-
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
-
-
2. 注解的使用
注解的使用事项:
- 注解内只能创建属性
- 创建属性时,可以设置默认值,设置过默认值的注解在使用的时候可以不重新赋值。如果重新赋值,新附的值会覆盖默认值。
- 使用注解的时候,没有默认值的注解要赋值,赋值的时候要指定属性名称
- 如果注解中只有一个注解,并且注解的名字是value,在使用注解的时候进行赋值时,可以不指定属性名称。
创建注解
@Target({ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String name();
String color();
int age() default 25;
String[] arr();
Season season();
}
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotaionTwo {
String value();
}
使用注解
@MyAnnotation(name = "张三", color = "红色", arr = {"12", "34"}, season = Season.SPRING)
public class MyAnnotationTest02 {
@MyAnnotation(name = "张三", color = "红色", arr = {"12", "34"}, season = Season.SPRING)
@MyAnnotaionTwo("王者")
public void eat() {
}
@MyAnnotaionTwo("王者")
public void drink() {
}
}
通过反射机制获取注解
@MyAnnotation(name = "张三", color = "红色", arr = {"12", "34"}, season = Season.SPRING)
public class MyAnnotationTest02 {
@MyAnnotation(name = "张三", color = "红色", arr = {"12", "34"}, season = Season.SPRING)
@MyAnnotaionTwo("王者")
public void eat() {
}
@MyAnnotaionTwo("王者")
public void drink() {
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class aClass = Class.forName("lession_21_12_07_annotation.MyAnnotationTest02");
//获取类注解
if (aClass.isAnnotationPresent(MyAnnotation.class)) {
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);
MyAnnotation myAnnotation= (MyAnnotation)annotation;
System.out.println(myAnnotation.age());
System.out.println(myAnnotation.name());
System.out.println(myAnnotation.color());
}
//获取方法注解
Method name = aClass.getDeclaredMethod("eat");
System.out.println(name);
System.out.println(name.isAnnotationPresent(MyAnnotaionTwo.class));
if (name.isAnnotationPresent(MyAnnotaionTwo.class)){
Annotation annotation = name.getAnnotation(MyAnnotaionTwo.class);
MyAnnotaionTwo myAnnotation= (MyAnnotaionTwo)annotation;
System.out.println(myAnnotation.value());
}
}
}
3. 注解实战
创建一个@ID注解,该注解只能修饰类,如果该类中没有int 类型的id属性,则进行报错。
注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ID {
}
逻辑判断
public class AnnotationTest03 {
public static void main(String[] args) throws ClassNotFoundException {
Class aClass = Class.forName("lession_21_12_07_annotation.Student");
boolean flag = false;
if (aClass.isAnnotationPresent(ID.class)){
Field[] declaredFields = aClass.getDeclaredFields();
for (Field field:declaredFields
) {
field.setAccessible(true);
if ("id".equals(field.getName())&&"int".equals(field.getType().getSimpleName())){
flag=true;
}
}
}
if (!flag){
throw new HasNotIdPropertiesException("被@ID注解的属性必须有属性名");
}
}
}
自定义异常类
public class HasNotIdPropertiesException extends RuntimeException {
public HasNotIdPropertiesException(String s) {
super(s);
}
}
动态代理
简介
功能:
可以增强方法传入的参数,还可以增强方法的本身
-
源代码不动的情况下,增加功能
-
- 在原来的类不变的情况下,通过动态代理,给原来的类添加一些功能。
- 可以在程序的执行过程中,创建代理对象。通过代理对象执行方法,给目标类的方法增加额外的功能(功能增强)
- 可以增强方法传入的参数,还可以增强方法的本身
-
减少代码的重复
-
专注于业务逻辑
-
解耦合,让你的业务功能和日志,事务非业务功能分离。
实现方式
-
jdk动态代理
-
- jdk动态代理要求目标对象必须实现接口,java设计上的要求
-
CGLIB动态代理 (spring框架的)
-
- 不需要接口,原理是继承,目标类必须有继承(不一定),不能是final类,方法也不能是final
- 原理是生成目标类的子类,而子类是增强过得,这个子类对象就是代理对象。
- 对类的要求低,性能比较高
- 不是最终类不可代理,其他的都可以代理。
-
使用spring框架动态代理时,有接口,默认使用jdk,没有接口,自动转到cglib
-
如果有接口,想用cglib实现动态代理,可以在主配置文件中加入
-
- <aop**:aspectj-autoproxy** proxy-target-class**=“true”**/>
举例
假如现在又增删改查四个方法,我想要获取这四个方法的执行时间。
- 在每个方法前输入一个new Date()对象实现
- 写一个工具类,在这些方法的前面调用
问题:
如果采用上面这两种方式,我们都对方法内部进行了添加修改,这些添加的代码与源代码执行的结果是无关的,属于赋予它的功能。直接添加到原方法里面,会降低可读性,同时也降低了性能。假如有1000个方法,我们不可能为这1000个方法都添加这样的代码。
我们想要的效果是:原方法不改动,但是调用这些方法时,我们也要得到一些方法之外的信息。即增强方法。我们可以使用动态代理来实现。
jdk动态代理
使用步骤
- 创建目标类,Student目标类。目的:为他的drink,eat增加输出时间,事务。
- 创建一个InvocationHandler接口的实现类,在这个类实现给目标方法增强功能。
- 使用jdk中类 Proxy,创建代理对象。实现创建对象的能力。
目标类
public class Student implements Person{
@Override
public void drink(String food) {
}
@Override
public void eat(String food) {
}
}
public interface Person {
void drink(String food);
void eat(String food);
}
工具类
public class MyLogUtil {
public static void operateTime() {
System.out.println("代理运行:操作时间" + new Date());
}
public static void operateContent() {
System.out.println("代理完毕:调用了方法");
}
}
InvocationHandler接口的实现类
- 创建一个类实现InvocationHandler接口。该类为代理对象的代理功能的实现类
- 创建一个对象,用来存储目标类
- 写加强目标类的内容,增强方法或者增强参数
- 执行目标类的方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method:目标类当前调用的方法
args:目标类当前调用的方法的参数
}
res = method.invoke(target, args);暂时理解为调用目标类的方法 args为目标类的参数
代理实现
public class MyIncationHandler implements InvocationHandler {
//定义一个对象,保存目标(要代理谁)Student
private Object target;
public MyIncationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object res = null;
//在目标方法之前输出时间(工具类)
MyLogUtil.operateTime();
//执行目标类的方法,通过Method类实现
System.out.println("当前方法名:" + method.getName());//获取被代理对象要调用的方法的方法名称
//获取目标类的参数
System.out.println("当前参数:" + args[0]);
//被代理对象的方法的执行 target表示被代理的对象。args表示方法的参数
res = method.invoke(target, args);
//在目标方法之后输出执行完毕(工具类)
MyLogUtil.operateContent();
return res;
}
}
创建代理对象
- 创建目标类,以及增强目标类功能的类的对象。将目标类作为参数传入进去
- 创建代理对象,将目标类,目标类的接口,以及增强目标类功能的类的对象传入
- 通过代理对象,调用目标类的相关方法,实现方法增强。
public class JdkProxyTest {
public static void main(String[] args) {
//使用jdk的Proxy创建代理对象
//创建目标对象
Person student = new Student();
//创建InvocationHandler对象
InvocationHandler handler = new MyIncationHandler(student);
//使用Proxy创建代理
Person proxy = (Person) Proxy.newProxyInstance(
student.getClass().getClassLoader(),
student.getClass().getInterfaces(), handler);
//调用代理的方法。
proxy.drink("牛奶");
proxy.eat("面包");
}
}
案例:买电脑
客户去原厂买电脑,需要花费8000元,通过代理购买,也需要花费8000元,但是代理商只用给原厂掏4800元,净赚3200.但是用户通过代理商够吗,代理商可以提供相关的服务,比如车接车送,赠送物品等。
interface SaleComputer {
String saleComputer(double money);
void show();
}
class Lenovo implements SaleComputer {
//真实类
@Override
public String saleComputer(double money) {
System.out.println("我买了一台" + money + "的电脑");
return "购买成功";
}
@Override
public void show() {
System.out.println("展示电脑");
}
}
//代理类
public class Proxyer {
public static void main(String[] args) {
//1.创建真实对象
Lenovo lenovo = new Lenovo();
//2.动态代理增强lenovo对象
//三个参数
//类加载器:真实对象.getClass().getClassLoader()
//接口数组:真实对象.getClass().getInterfaces()
//处理器: new InvocationHandler()
//此处实现的是接口方法,所以可以强制转换成接口类型
SaleComputer saleComputer = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
/**
* 代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行。
* 参数:
* 1.proxy:代理对象(基本不用)
* 2.method:代理对象调用的方法被封装成的对象
* 3. args:代理对象调用方法时,传递的实际参数。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//使用真实对象调用该方法
//1.判断方法名是够是saleComputer方法
if (method.getName().equals("saleComputer")) {
//获取参数
double money = (double) args[0];
//修改参数
money = money * 0.6;
//修改方法体
System.out.println("接你.....");
//使用真实对象调用该方法
//原因:代理商并不具备真正卖电脑的功能,真正有卖电脑功能的是lenove公司
//参数:真实对象和代理对象传入的参数
String obj = (String) method.invoke(lenovo, money);
System.out.println(obj);
System.out.println("免费送货");
//2.增强返回值
return obj + "_鼠标垫";
} else {
Object obj = method.invoke(lenovo, args);
return obj;
}
}
});
System.out.println("电脑原价");
lenovo.saleComputer(8000); //返回8000
//代理商购买价
System.out.println("代理商购买价");
saleComputer.saleComputer(8000); // 返回4800
// 相当于客户去原厂买电脑,需要花费8000元,通过代理购买,也需要花费8000元,但是代理商只用给原厂掏4800元,净赚3200.但是代理商可以提供相关的服务,比如车接车送,赠送物品等。
}
}
送你一个鼠标
送你一个鼠标垫
送你一个键盘
联想
代理商
给你一个电脑