案例:美团外卖 --> 付款 --> 要么微信支付,要么支付宝支付
#那么这里由美团制定规则
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
在编译后产生字节码文件的时候,类加载器子系统通过二进制字节流,负责从文件系统加载class文件。在执行程序(java.exe)时候,将字节码文件读入jvm中-->这个过程叫做类的加载。然后在内存中对应创建一个java.lang.Class对象-->这个对象会被放入字节码信息中,这个class对象对应加载那个字节码3信息,这个对象被做为程序访问方法区中的这个类的各和数据的外部接口。
所以:我们可以通过这个对象看到类的结构,这个对象是一面镜子,透过镜子看到类的各种信息,我们形象的称之为反射,这种”看透”class能力被称为introspection(内省,内现,反省)。Reflection和instrospection是常被提的两个术语。
说明:在运行期间,如果我们要产生某个类的对象,java虚拟机(jvm)会检查该类型的class对象是否已被加载。
如果没有加载,jvm会根据类的名称找到.class文件并加载它。一旦某个类型的class对象已经被加载到内存中,就可以用它来产生该类型的所有对象。
面向对象思维:万物皆对象, 将“类”当成一个对象,Class将这个“类”的抽象成对象,
想要获取到属性:class类--> 得到具体的实例(对象) --> 通过对象得到属性
提前准备下面这几个类、接口,注解
public class AliPay implements Mtwm {
@Override
public void payOnline() {
System.out.println("我已经点了外卖,正在使用支付宝支付");
}
}
public class Wechat implements Mtwm {
@Override
public void payOnline() {
// 具体实现微信支付的功能
System.out.println("我已经点了外卖,正在使用微信支付");
}
}
public interface Mtwm {
void payOnline();
}
// 处定义的接口
public interface MyInterface {
void myMethod();
}
/**
* @Target: 定义当前的注解可以修饰程序中的那些元素
* @Retention: 定义注解的生命周期
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
// 属性
}
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");
}
}
@MyAnnotation(value = "hello")
public class Student extends Person implements MyInterface {
// 属性
private int sno;
double height;
protected double weight;
public double score;
// 方法
@MyAnnotation(value = "method")
public String showInfo(){
return "我是一名三好学生";
}
public String showInfo(int a, int b){
return "重载---我是一名三好学生";
}
private void work(){
System.out.println("我以后会找工作 --> 成码农");
}
private void work(int a){
System.out.println("重载 === 我以后会找工作 --> 成码农");
}
// 构造器
private Student(int sno) {
this.sno = sno;
}
public Student() {
}
Student(int sno, double weight) {
this.sno = sno;
this.weight = weight;
}
protected Student(int sno, double weight, double hight){
this.sno = sno;
this.weight = weight;
this.height = hight;
}
void happy(){
System.out.println("做人最重要的就是开心");
}
@Override
@MyAnnotation(value = "helloMyMethod")
public void myMethod() throws RuntimeException{
System.out.println("我重写了myMethod方法");
}
protected int getSno(){
return sno;
}
@Override
public String
toString() {
return "Student{" +
"sno=" + sno +
", height=" + height +
", weight=" + weight +
", score=" + score +
'}';
}
}
引入:我们在点外卖的时候选择支付方式,可以是微信,支付宝,或者是美团支付,如果是常规的写法,通过判断来确定是new那一个对象。如果说后期我需要引入其它的支付方式来进行支付,那我们又要加入其它的判断来扩展其它的支付方式。那这里程序的扩展性就不好
public class TestReflect {
public static void main(String[] args) {
// 定义一个字符串用来模拟前台的支付方式
int i = 1;
if (i == 1) {
// new Wechat().payOnline();
pay(new Wechat());
} else {
// new AliPay().payOnline();
pay(new AliPay());
}
}
// public static void pay(Wechat wc){
// wc.payOnline();
// }
// public static void pay(AliPay ap) {
// ap.payOnline();
// }
// 方法的形参是接口,具体传入的是接口中的实现类的对象 --> 多态的一种体现
public static void pay(Mtwm m){
m.payOnline();
}
}
// 多态确实可以提高代码的扩展性,但是:扩展性没有达到最好.
// 怎么没有达到最好:上面的分支,还是需要手动的删除或者添加
// 解决办法: 反射机制
上面的代码我们通过反射来做,可以明显的看出来我们只需通过修改以对应class的路径就可以new出不同的对象,这样我们的代码就不用修改,后期扩展比较方便
public class TestReflect2 {
public static void main(String[] args) throws Exception{
// 字符串,实际上:就是微信类的全限定路径
String str = "com.mashibing.sync.entity.Wechat";
//下面的代码 就是利用反射
Class<?> cls = Class.forName(str);
Object o = cls.newInstance();
Method method = cls.getMethod("payOnline");
method.invoke(o);
}
}
--------------------------------------------------------------------------------------------
4种获取字节码的方式
public class TestReflect3 {
// 案例: 以person的字节码信息为案例
public static void main(String[] args) throws Exception{
Person p = new Person();
Class c1 = p.getClass();
System.out.println(c1);
// class com.mashibing.sync.entity.Person
Class c2 = Person.class;
System.out.println(c2);
// 用的最多的
Class<?> c3 = Class.forName("com.mashibing.sync.entity.Person");
System.out.println(c3);
// 利用类加载器(了解)
ClassLoader classLoader = TestReflect3.class.getClassLoader();
Class c4 = classLoader.loadClass("com.mashibing.sync.entity.Person");
System.out.println(c4);
}
}
Class类的具体的实例
- 类,外部类,内部类
- 接口
- 注解
- 数组
- 基本数据类型
- Void
public class TestReflect4 {
public static void main(String[] args) {
Class c1 = Person.class;
Class c2 = Comparable.class;
Class c3 = Override.class;
int[] arr1 = {1, 2, 3};
Class c4 = arr1.getClass();
int[] arr2 = {2, 3, 4};
Class c5 = arr2.getClass();
// 同一个维度的数组,同一个元素类型,得到的字节码就是同一个
System.out.println(c4 == c5);
Class c6 = int.class;
Class c7 = void.class;
}
}
获取对象的构造器
public class TestReflect5 {
public static void main(String[] args) throws Exception {
// 获取字节码信息
Class cls = Student.class;
// 通过字节码信息获取构造器
// 只能获取当前运行时类的被public修饰的构造器
Constructor[] constructors = cls.getConstructors();
for (Constructor c: constructors) {
System.out.println(c);
}
System.out.println("----------------------------------------");
// 获取到所有的构造器
Constructor[] declaredConstructors = cls.getDeclaredConstructors();
for (Constructor d: declaredConstructors) {
System.out.println(d);
}
System.out.println("------------------------------------------");
// 什么都不传入,获取到的是空参构造器, 只能获取到 public修饰的
Constructor c1 = cls.getConstructor();
System.out.println(c1);
System.out.println("-----------------------------------------");
// 获取到两个参数的构造器, 可以获取到private, protected修饰的
Constructor c2 = cls.getDeclaredConstructor(int.class, double.class);
System.out.println(c2);
// 有了构造器以后,我们就可以创建对象了
Object o = c1.newInstance();
System.out.println(o);
// 调用非空参构造
Object o1 = c2.newInstance(10, 12D);
System.out.println(o1);
}
}
获取对象属性,及属性结构
public class TestReflect6 {
public static void main(String[] args) throws Exception {
Class cls = Student.class;
// 获取到属性, 可以获取到当前类与父类中被public修饰的属性
Field[] fields = cls.getFields();
for (Field f: fields){
System.out.println(f);
}
System.out.println("--------------------------");
// 获取到所有的属性, 当前类中所有的属性,不包括父类中的所有属性
Field[] f1 = cls.getDeclaredFields();
for (Field f: f1) {
System.out.println(f);
}
System.out.println("-----------------------------");
// 获取到指定的属性, 只能获取到public修饰的
Field score = cls.getField("score");
System.out.println(score);
System.out.println("-----------------------------");
// 获取到被private修饰的属性
Field sno = cls.getDeclaredField("sno");
System.out.println(sno);
System.out.println("-----------------------------");
// 属性的具体结构
// 获取修饰符 public static final String
int modifiers = sno.getModifiers();
System.out.println(Modifier.toString(modifiers));
// 获取属性的数据类型
Class clazz = sno.getType();
System.out.println(clazz.getName());
// 获取属性的名字
String name = sno.getName();
System.out.println(name);
System.out.println("------------------------------");
Field sco = cls.getField("score");
Object obj = cls.newInstance();
// 给属性赋值必须有对象
sco.set(obj, 98);
System.out.println(obj);
}
}
获取对象所在的包,注解、接口
public class TestReflect7 {
public static void main(String[] args) {
Class cls = Student.class;
// 获取运行时类的接口
Class[] interfaces = cls.getInterfaces();
for (Class c : interfaces) {
System.out.println(c);
}
System.out.println("-----------------------------");
// 得到父类的接口
// 获取到父类的字节码信息
Class superclass = cls.getSuperclass();
// 得到接口
Class[] i1 = superclass.getInterfaces();
for (Class i : i1) {
System.out.println(i);
}
System.out.println("-----------------------------");
// 获取到运行时类所在的包
Package aPackage = cls.getPackage();
System.out.println(aPackage);
System.out.println(aPackage.getName());
System.out.println("-----------------------------");
// 获取到运行时类的注解, 必须是运行时注解 声明:@Retention(RetentionPolicy.RUNTIME)
Annotation[] annotations = cls.getAnnotations();
for (Annotation a: annotations) {
System.out.println(a);
}
}
}
获取对象的异常、方法以调用方法
public class TestReflect8 {
public static void main(String[] args) throws Exception {
Class cls = Student.class;
// 获取于是所有的public的方法, 会包含其它所有父类中的方法
Method[] methods = cls.getMethods();
for (Method m: methods) {
System.out.println(m);
}
System.out.println("-----------------------------");
// 获取到 当前运行时类中的所有的方法
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method d : declaredMethods) {
System.out.println(d);
}
System.out.println("-----------------------------");
// 获取到当前运行时类指定的方法
Method showInfo = cls.getMethod("showInfo", int.class, int.class);
System.out.println(showInfo);
System.out.println("-----------------------------");
// 获取到private修饰的方法
Method work = cls.getDeclaredMethod("work", int.class);
System.out.println(work);
// 获取方法的具体的结构
// 修饰符: 返回值类型 方法名(参数列表) throw XXXX{}
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 par: parameterTypes) {
System.out.println(par);
}
System.out.println("-----------------------------");
// 获取注解, 只能获取到运行时的注解
Method myMethod = cls.getMethod("myMethod");
Annotation[] annotations = myMethod.getAnnotations();
for (Annotation a : annotations) {
System.out.println(a);
}
System.out.println("-----------------------------");
// 获取异常
Class [] exceptionTypes = myMethod.getExceptionTypes();
for (Class e : exceptionTypes) {
System.out.println(e);
}
System.out.println("-----------------------------");
// 调用方法
Object o = cls.newInstance();
// 调用o对象的myMethod方法
myMethod.invoke(o);
// 调用带参的方法
System.out.println(showInfo.invoke(o, 12, 45));
}
}
关于反射的面试题:
- 创建person的对象,以后用new Person()创建还是用反射创建
需要体现程序的动态性的时候使用反射来创建对象;不知道是创建那个对象,只有在运行的时候才知道是创建那一个对象,比如说我们在点外卖的时候选择支付方式,可以微信支付、支付宝支付、或者是美团支付只有在用户选择后我们才知道去创建那个对象,这个时候我们就需要修改反射来创建对象。
- 反射是否破坏了面向对象的封装性
破坏了面向对象的封装性,但是二者并不冲突。对象的封装主要是解决安全性,反射的提出主要是为了解决程序的动态性。 对象的privte修饰的属性、方法是就明确的告诉就不要使用这里的属性和方法; 但反射确实是调用到或者使用到private修饰的方法或属性。一般来说是不建议使用这里对象里面的private修饰的属性或者方法。因此这里说他们是不冲突的。