1、Class类的理解
2、提供丰富的类
先提供一个父类和一个子类: —— 备用
父类Person:
public class Person { //作为一个父类
private int age;
public String name;
private void eat(){
System.out.println("Person---eat");
}
public void sleep(){
System.out.println("Person---sleep");
}
}
子类Student:
public class Student extends Person { //作为 子类
//属性
private int sno; //学号
double height; //没有修饰符,则默认default修饰
protected double weight;
public double score;
//方法
public String showInfo(){
return "我是一名三好学生";
}
private void work(){
System.out.println("我以后会找工作--->成为码农");
}
//构造器
public Student(){
System.out.println("空参构造器");
}
private Student(int sno){
this.sno = sno;
}
Student(int sno, double weight){
this.sno = sno;
this.weight = weight;
}
}
3、获取字节码信息的四种形式
代码示例: —— 方式3最常用:Class c3 = Class.forName("test2_class_.Person");
package test2_class_;
/**
* @Auther: zhoulz
* @Description: test2_class_ — 获取字节码信息的四种形式
* @version: 1.0
*/
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//案例:以Person的字节码信息为案例
//方式1:通过getClass()方法获取
Person p = new Person();
Class c1 = p.getClass();
System.out.println(c1);
//方式2:通过内置class属性
Class c2 = Person.class;
System.out.println(c2);
//方式1和方式2得到的字节码是一个吗?是的。因为字节码信息的加载只会加载一次。
//验证:
System.out.println(c1 == c2); //true
//注意:方式1和方式2 —— 不常用(因为都是通过Person类去获取的,而如果已经知道person类了,就没有必要再去获取其字节码信息了)
//方式3:用的最多- 调用Class类提供的静态方法forName()
Class c3 = Class.forName("test2_class_.Person");
System.out.println(c3);
//方式4:利用类的加载器(了解技能点)
ClassLoader loader = Test.class.getClassLoader();//(通过Test类)获取系统的类加载器
Class c4 = loader.loadClass("test2_class_.Person");
System.out.println(c4);
}
}
4、可以作为Class类的实例的种类
Class类的具体的实例:
(1)类:外部类,内部类
(2)接口
(3)注解
(4)数组
(5)基本数据类型
(6)void
验证:
package test2_class_;
/**
* @Auther: zhoulz
* @Description: test2_class_
* @version: 1.0
*/
public class Test2 {
public static void main(String[] args) {
/*
Class类的具体的实例:
(1)类:外部类,内部类
(2)接口
(3)注解
(4)数组
(5)基本数据类型
(6)void
*/
//依次对应上面
Class c1 = Person.class;
//System.out.println(c1);
Class c2 = Comparable.class;
//System.out.println(c2);
Class c3 = Override.class;
//System.out.println(c3);
//数组
int[] arr1 = {1,2,3};
Class c4 = arr1.getClass();
System.out.println(c4);
int[] arr2 = {4,5,6};
Class c5 = arr2.getClass();
System.out.println(c5);
System.out.println(c4 == c5); //结果:true .同一个维度,同一个元素类型,得到的字节码就是同一个
Class c6 = int.class;
System.out.println(c6);
Class<Void> c7 = void.class;
System.out.println(c7);
}
}
5、获取运行时类的完整结构
(1)补充完善上面提供的丰富的类
父类Person:
//(随便)实现一个接口
public class Person implements Serializable{ //作为一个父类
private int age;
public String name;
private void eat(){
System.out.println("Person---eat");
}
public void sleep(){
System.out.println("Person---sleep");
}
}
子类Student:
package test2_class_;
import jdk.nashorn.internal.codegen.CompilerConstants;
/**
* @Auther: zhoulz
* @Description: test2_class_
* @version: 1.0
*/
//使用自定义的注解 - 修饰类
@MyAnnotation(value = "hello")
//子类也实现一个接口(自定义的)
public class Student extends Person implements MyInterface{ //作为 子类
//属性
private int sno; //学号
double height; //没有修饰符,则默认default修饰
protected double weight;
public double score;
//方法
//使用自定义的注解 - 修饰类
@MyAnnotation(value = "himethod")
public String showInfo(){
return "我是一名三好学生";
}
//重载上面的showInfo()方法
public String showInfo(int a, int b){
return "重载方法===我是一名三好学生";
}
private void work(){
System.out.println("我以后会找工作--->成为码农");
}
//加上default、protected修饰的方法
void happy(){
System.out.println("开心每一天!!!");
}
protected int getSno(){
return sno;
}
//构造器
public Student(){
System.out.println("空参构造器");
}
private Student(int sno){
this.sno = sno;
}
Student(int sno, double weight){ //default 修饰的
this.sno = sno;
this.weight = weight;
}
//再加一个protected修饰的
protected Student(int sno, double height, double weight){
this.sno = sno;
this.height = height;
this.weight = weight;
}
//重写的方法已经有注解@Override了,还能再加吗?可以。
//重写接口-MyInterface中的方法
@Override
@MyAnnotation(value = "hellomyMethod")
public void myMethod() {
System.out.println("我重写了接口MyInterface中的方法!");
}
@Override
public String toString() {
return "Student{" +
"sno=" + sno +
", height=" + height +
", weight=" + weight +
", score=" + score +
'}';
}
}
自定义的接口 MyInterface :
public interface MyInterface { //自定义的接口
//随便定义一个抽象方法
void myMethod();
}
自定义的注解 MyAnnotation:
package test2_class_;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
/**
* @Auther: zhoulz
* @Description: test2_class_
* @version: 1.0
*/
//参考:
//@SuppressWarnings()
/*
* @Target:定义当前注解能够修饰程序中的哪些元素
* @Retention:定义注解的声明周期
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
//原先是:RetentionPolicy.SOURCE,其中的SOURCE是指:只在编译期进行判断
//而我现在写注解的目的是为了在运行时的时候能够获取到注解,故要用RUNTIME
public @interface MyAnnotation {
String value(); //属性,这里的属性是带 () 的
}
上面,进行了如此的丰富(加构造器、方法、自定义注解、自定义接口等)
目的:为了后面通过反射(或者说字节码信息?)获取里面的东西 。
下面开始获取里面的东西: —— 首先得获取字节码信息
(2)获取构造器和创建对象
package test2_class_;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @Auther: zhoulz
* @Description: test2_class_
* @version: 1.0
*/
public class Test3_get_constructor {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取字节码信息:
Class cls = Student.class;
//通过字节码信息可以获取构造器
//getConstructors只能获取当前运行时类的被public修饰的构造器
Constructor[] c1 = cls.getConstructors(); //返回值类型为Constructor[]数组
//遍历:
for (Constructor c : c1){
System.out.println(c); // public test2_class_.Student()
}
System.out.println("---------");
//上面的方法只能获取public获取的构造器
//换其他的方法:
//getDeclaredConstructors:获取运行时类的全部修饰符的构造器
Constructor[] c2 = cls.getDeclaredConstructors();
//遍历:
for (Constructor c : c2){
System.out.println(c);
}
System.out.println("获取指定的构造器-----");
//注意:不是getConstructors()了,少了一个 s
Constructor con1 = cls.getConstructor(); // ...代表可变参数,可以不填
System.out.println(con1);//得到的是空构造器
//再:得到两个参数的有参构造器
Constructor con2 = cls.getConstructor(double.class, double.class);
System.out.println(con2);
//上面都是针对于public的
//如:
//现想,得到一个参数的有参构造器:并且是private修饰的,看行不行?
//Constructor con3 = cls.getConstructor(int.class);
//System.out.println(con3);// —— 运行,发现报错了
//换一个:getDeclaredConstructor
Constructor con3 = cls.getDeclaredConstructor(int.class);
System.out.println(con3); // —— 可以正常获取
//总结上面:加个declare,那些非public构造器的就都能获取了
System.out.println("创建对象-----");
//有了构造器以后(如上面的con1、con2、con3),我就可以创建对象:
Object o1 = con1.newInstance();
System.out.println(o1); //因为重写了toString()方法,所以会这么输出
//再:两个参数的构造器
Object o2 = con2.newInstance(175.5, 180.3);
System.out.println(o2);
}
}
(3)获取属性和对属性进行赋值
package test2_class_;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
/**
* @Auther: zhoulz
* @Description: test2_class_ —— 可参考:获取构造器和创建对象
* @version: 1.0
*/
public class Test4_get_property {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException {
//获取运行时类的字节码信息
Class cls = Student.class;
//获取属性
//getFields:获取运行时类和其父类中被public修饰的属性
Field[] fields = cls.getFields();
for (Field f : fields){
System.out.println(f);
}
System.out.println("加了declare后-----");
//getDeclaredFields:获取运行时类中的所有属性
Field[] declaredFields = cls.getDeclaredFields();
for (Field f:declaredFields){
System.out.println(f);
}
System.out.println("获取指定的属性-----");
Field score = cls.getField("score");
System.out.println(score);
//同“获取构造器”原理:
//想获取public外的属性:—— 用带declare的
Field sno = cls.getDeclaredField("sno");
System.out.println(sno);
System.out.println("获取-属性的具体结构---");
//分为:(以 private int sno; 为例)
//获取属性的修饰符
int modifiers = sno.getModifiers();//有s的原因是,有可能修饰符不为一个,如public static int sno;
System.out.println(modifiers);//2 — (2不是个数)而是不同的数值对应不同的修饰符
//下面获取该修饰符的名称
System.out.println(Modifier.toString(modifiers));
//上面可整合成一句:
System.out.println(Modifier.toString(sno.getModifiers()));
//获取属性的数据类型
Class type = sno.getType();
System.out.println(type);
//获取属性的名字
String name = sno.getName();
System.out.println(name);
System.out.println("给属性赋值----");
//现获取指定的属性
Field sco = cls.getField("score");
//通过set()方法赋值的时候,处理指定属性、设置属性值外,还要指定:给哪个对象的属性赋值
//即:给属性赋值:(给属性设置值,必须要有对象)
Object obj = cls.newInstance();
sco.set(obj,66);//给obj这个对象的score属性设置具体的值,这个值为66
System.out.println(obj);
}
}
(4)获取方法和调用方法
package test2_class_;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* @Auther: zhoulz
* @Description: test2_class_
* @version: 1.0
*/
public class Test5_get_method {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//获取字节码信息
Class cls = Student.class;
System.out.println("获取方法-----");
//getMethods:获取运行时类的方法还有所有父类中的方法(被public修饰)
Method[] methods = cls.getMethods();
for (Method m:methods){
System.out.println(m);
}
System.out.println("加了declare后-----");
//getDeclaredMethods:获取运行时类(注意:没有父类的了)中的所有方法:
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method m:declaredMethods){
System.out.println(m);
}
//接下来,同样,要获取指定的方法
System.out.println("获取指定的方法----");
Method showInfo1 = cls.getMethod("showInfo");
System.out.println(showInfo1);
Method showInfo2 = cls.getMethod("showInfo", int.class, int.class);
System.out.println(showInfo2);
//同样,上面是针对于public的,下面要获取非public的方法
//加上declare的就行了
Method work = cls.getDeclaredMethod("work",int.class);
System.out.println(work);
System.out.println("获取方法的具体结构-----");
/*
@注解
修饰符 返回值类型 方法名(参数列表) throws XXXXX{}
*/
//名字
System.out.println(work.getName());
//修饰符
int modifiers = work.getModifiers();
System.out.println(Modifier.toString(modifiers));
//返回值
System.out.println(work.getReturnType());
//参数列表
Class[] parameterTypes = work.getParameterTypes();
for (Class c:parameterTypes){
System.out.println(c);
}
System.out.println("获取方法的注解------");
//先获取方法myMethod() —— 其有两个注解
Method myMethod = cls.getMethod("myMethod");
Annotation[] annotations = myMethod.getAnnotations();
//数组,遍历
for (Annotation a:annotations){
System.out.println(a); //只获取了一个,因为这个注解是RUNTIME的,运行时可获取
//而设置了SOURCE的获取不到,SOURCE代表在编译器进行的一个检验
}
System.out.println("获取异常------");
Class[] exceptionTypes = myMethod.getExceptionTypes();
for (Class c:exceptionTypes){
System.out.println(c);
}
//获取完后,要开始调用了
System.out.println("方法的调用了----");
//同样,首先要有一个对象
Object o = cls.newInstance();
myMethod.invoke(o);//调用o对象的mymethod方法,有参数记得传进去
//showInfo2.invoke(o,77,88); //showInfo()方法有返回值
System.out.println(showInfo2.invoke(o,77,88));
}
}
(5)获取类的接口,所在包,注解
package test2_class_;
import java.lang.annotation.Annotation;
/**
* @Auther: zhoulz
* @Description: test2_class_ —— 获取类的接口,所在包,注解
* @version: 1.0
*/
public class Test6_get_class {
public static void main(String[] args) {
//获取字节码信息
Class<Student> cls = Student.class;
//System.out.println(cls);
System.out.println("获取运行时类的接口-----");
Class<?>[] interfaces = cls.getInterfaces();
for (Class c:interfaces){
System.out.println(c); //interface test2_class_.MyInterface
}
//上面,能不能得到父类的接口?可以
System.out.println("获取运行时类的父类的接口-----");
//先得到父类的字节码信息
Class<? super Student> superclass = cls.getSuperclass();
//再得到接口
Class<?>[] interfaces1 = superclass.getInterfaces();
for (Class c : interfaces1){
System.out.println(c);
}
System.out.println("获取运行时类所在的包-----");
Package aPackage = cls.getPackage();
System.out.println(aPackage);
System.out.println(aPackage.getName());
System.out.println("获取运行时类的注解-----");
Annotation[] annotations = cls.getAnnotations();
for (Annotation a : annotations){
System.out.println(a);
}
}
}
6、关于反射的面试题
【1】问题1:创建Person的对象,以后用new Person()创建,还是用反射创建?
:一般情况下,创建对象还是按原先的方式。
但是,有时候,只有在程序运行起来后,我们才知道要执行什么操作(做一件什么事),这种情况下可以用反射,体现了反射的一个动态性,此时可以用反射去创建对象。
参考:https://blog.csdn.net/weixin_39619451/article/details/113371162
【2】问题2:反射是否破坏了面向对象的封装性?
“破坏” —— 对于一些private的属性,通过反射也可以操作(调用)。
:封装是为了提高代码的安全性。如private修饰符是为了保护数据,建议外部不要用,但是仅仅是建议。另一方面,反射提出的本质原因也不是为了破坏这种封装性,它有自己的意义(动态性)。
反射机制提供了访问private修饰的属性、方法等,但是封装性(private)是不建议你用的。
(这样说也不算矛盾?)