之前也有很多时候学习过反射是什么,但是也没有彻底搞懂,今天来细致的学习一下。
1、认识反射
1、既然有反,那么就有正。正常的思路中,只有知道一个类之后才能进行实例化对象。
- 代码范例
public class Main {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date);
}
}
- 运行结果
2、到底什么是反呢?
所谓的反就是通过对象找到对象的来源。对象要找到它的来源,那就必须依靠Object类提供的一个方法。
- public final Class<?> getClass() {};
public class Main {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date);
System.out.println(date.getClass());
}
}
结果为
所以:反射的大致意思就是通过对象去找它的来源
- 通过getClass()方法取得Class类对象,就可以直接找到该对象的来源
- 反射反射就是通过Class进行反。
2、Class类介绍(反射操作的源头)
有了反射的大致概念后,而且我们也知道了通过Class进行反,那到底怎么进行射呢?
首先了解如下的知识
- 在java中Class类是操作反射的源头
- 实例化Class对象有三种方式
- 在程序运行期间,java运行时,系统始终为我们所有对象维护一个运算时类型标记,这个信息会跟踪到每个对象所属的类,虚拟机通过运行标记类信息选择要执行的方法。(但是这个都是底层的我们看不见,但是可以通过Class来处理看见)
(1)第一种:Object中getClass方法
1、看效果
public class Main {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
Class<?> cls = date.getClass();
System.out.println(cls);
}
}
//输出结果
class java.util.Date//因为调用默认的toString实现,所以就有了class的前缀。
2、cls是一个类的对象
首先这里要了解如下知识
- Class是操作反射的源头
- Class是支持泛型的类,你使用的时候都是“?”问号来处理,没有为什么?因为java在这个搞的很垃圾
public class Main {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
Class<?> cls = date.getClass();
System.out.println(cls);
System.out.println("全名:" + cls.getName());
System.out.println("类名:" + cls.getSimpleName());
}
}
输出结果
class java.util.Date
全名:java.util.Date
类名:Date
以上方式在开发中,我们是不经常用的,因为我们都是在用别人开发好的工具进行代码编码,看下面一种实例化Class对象
(2)第二种:利用“类.class”操作
1、看效果
public class Main {
public static void main(String[] args) {
Class<?> cls = java.util.Date.class;
System.out.println(cls);
System.out.println("全名:" + cls.getName());
System.out.println("类名:" + cls.getSimpleName());
}
}
输出结果
class java.util.Date
全名:java.util.Date
类名:Date
(3)第三种:利用Class的static方法取得
- public static Class<?> forName(String className) {};
1、看效果
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> cls = Class.forName("java.util.Date");
System.out.println(cls);
System.out.println("全名:" + cls.getName());
System.out.println("类名:" + cls.getSimpleName());
}
}
输入情况
class java.util.Date
全名:java.util.Date
类名:Date
2、这种情况要分析了
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
- 传入参数时String:是String 是String 是String啊,有什么有呢?做了上面牛逼的操作呢
- 通过一个String对象分析出你的类,全程来帮你处理。
- 好处在于你直接编写字符串,然后对你的类进行反射。
(4)看java反射的概念
-
Java反射机制的核心是在程序运行时动态加载类并获取类的详细信息,从而操作类或对象的属性和方法。本质是JVM得到class对象之后,再通过class对象进行反编译,从而获取对象的各种信息。
-
Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类,这些类因为之前用不到,所以没有被加载到JVM。通过反射,可以在运行时动态地创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
(5)反射的原理总结
我们写的java代码在XXX文件夹下面的YYY.java文件里面
在加载时候,虚拟机会将YYY.java的文件处理为YYY.class的文件。
- 当我们new对象的时候:new YYY();的时候,虚拟机JVM会去磁盘里面加载YYY.class的类。
- 然后虚拟机JVM会自动的创建一个YYY的一个Class的对象(这个是唯一性的)。
- 也就是上面的通过new 反得到类,然后通过射,也就是通过反过来的类将类的方法映射为某个东西保存起来,然后供对象去调用(比如说去点某个方法的时候)也就是反编译进行处理得到。
- 加载机制
3、通过反射实例化对象
(1)之前的对象获得方式
实例化对象:第一想到的是new吧,但是现在打破这种思想来。
(2)改变固有思维
- Class类里面的public T newInstance()
public class Main {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("java.util.Date");
//之前的实例化对象
Date date = new Date();
System.out.println(date);
//现在的实例化
Date date1 = (Date)cls.newInstance();
System.out.println(date1);
}
}
Thu Nov 18 11:43:00 GMT+08:00 2021
Thu Nov 18 11:43:00 GMT+08:00 2021
(3)实例化自己编写的类
package cn.mldn;
import java.util.Date;
public class Main {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.Person");
//之前的实例化对象
Person date = new Person();
System.out.println(date);
//现在的实例化
Person date1 = (Person)cls.newInstance();
System.out.println(date1.toString());
}
}
class Person {
public Person() {
System.out.println("实例化了无参构造");
}
@Override
public String toString() {
return "实例化了吧";
}
}
输出结果
实例化了无参构造
实例化了吧
实例化了无参构造
实例化了吧
原来如此:反射实例化也是要通过调用无参构造方法。
如果没有无参构造方法的时候:是会报错的,
- 由此要知道我们自己编写的代码时候要编写无参构造方法的原因之一了。
4、反射的用处。
(1)反射之构造方法
我们之前是可以通过newInstance来new一个对象的。但是有弊端是调用了我们的无参构造
1、没有提供无参构造
package cn.mldn;
import java.util.Date;
public class Main {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.Person");
Person person = (Person)cls.newInstance();
System.out.println(person);
}
}
class Person {
private String name;
private Integer age;
public Person(String name,Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
输出结果
Exception in thread "main" java.lang.InstantiationException: cn.mldn.Person
at java.lang.Class.newInstance(Class.java:427)
at cn.mldn.Main.main(Main.java:9)
Caused by: java.lang.NoSuchMethodException: cn.mldn.Person.<init>()
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.newInstance(Class.java:412)
... 1 more
2、指定我们的构造方法
- 构造的英语翻译为:Constructor
- 去找我们的Constructor类有这么一个方法:public Constructor getConstructor(Class<?>… parameterTypes),是可变长参数
- Constructor类里面的public T newInstance(Object … initargs)方法进行实例化。就可以传参数了
public class Main {
public static void main(String[] args) throws Exception {
Class<?> cls = Class.forName("cn.mldn.Person");
Constructor<?> cons = cls.getConstructor(String.class,Integer.class);
Person person = (Person)cons.newInstance("张三",1);
System.out.println(person);
}
}
class Person {
private String name;
private Integer age;
public Person(String name,Integer age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
Person{name='张三', age=1}
Process finished with exit code 0
3、总结:麻烦吧
所以还是自己提供无参构造方法好啊
(2)普通方法
类中的方法是在实例化了才能进行调用方法吧。所以不管你反射还是什么都要实例化了才能去调用方法。
- 在Class里面有:Method getMethod(String name,Class<?> —parsxx);
- java.lang.reflect.Method类
明天来了,具体得去上课了,可以先去看工厂模式。
(3)反射之成员表量
(4)还有就是实现类型的比较
5、最后的讲解(前面不是很懂都要来看这里)
相信前面的讲解各位应该对反射有了一个基本的概念了吧。
- 首先我们要编写一个xxx.java的文件
- 对已经编写完的文件进行整合为一个能启动的程序。点击启动。
- 然后jvm会对其所有的xxx.java的文件处理为xxx.class的文建。
- 然后这时候当我们new的时候(可以是new xxx()无参构造,或者有参构造),jvm会自动的提供一个Class类的对象,全程跟着这个对象,当然后续包括对这个对象的回收等。通过这个Class对象进行反编译进行映射,翻过来得到对象的类的所有方法。
- 这就是我们编写程序的new对象的执行流程了。
(1)工厂设计模式
首先接口是不能直接被实例化的啊,但是我们可以用工厂设计模式来设计生产。(像我们自己写接口都基本是要写工厂模式的)
1、传统的工厂模式
public class Main {
public static void main(String[] args) throws Exception {
Fruit f = Factory.getInstance("apple");
f.eat();
}
}
//编写一个工厂
class Factory {
public static Fruit getInstance(String ClassName) {
if ("apple".equalsIgnoreCase(ClassName)) {
return new Apple();
}
return null;
}
}
class Apple implements Fruit {
@Override
public void eat() {
System.out.println("xxx 吃了苹果");
}
}
interface Fruit {
public void eat();
}
2、现在的挑战,当我们增加子类的时候
public class Main {
public static void main(String[] args) throws Exception {
Fruit f = Factory.getInstance("apple");
Fruit f1 = Factory.getInstance("orange");
f.eat();
f1.eat();
}
}
//编写一个工厂
class Factory {
public static Fruit getInstance(String ClassName) {
if ("apple".equalsIgnoreCase(ClassName)) {
return new Apple();
}
if ("orange".equalsIgnoreCase(ClassName)) {
return new Orange();
}
return null;
}
}
class Apple implements Fruit {
@Override
public void eat() {
System.out.println("xxx 吃了苹果");
}
}
class Orange implements Fruit {
@Override
public void eat() {
System.out.println("xxx chil橛子");
}
}
interface Fruit {
public void eat();
}
3、改变已有的工厂设计模式
对上面的问题分析:这个new出了问题了吧。所有的对象要通过new来实现。改变new吧
public class Main {
public static void main(String[] args) throws Exception {
Fruit f = Factory.getInstance("cn.mldn.Orange");
f.eat();
}
}
//编写一个工厂
class Factory {
public static Fruit getInstance(String classname) {
try {
return (Fruit)Class.forName(classname).newInstance();
} catch (Exception e) {
}
return null;
}
}
class Apple implements Fruit {
@Override
public void eat() {
System.out.println("xxx 吃了苹果");
}
}
class Orange implements Fruit {
@Override
public void eat() {
System.out.println("xxx chil橛子");
}
}
interface Fruit {
public void eat();
}
是不是能适应你的一切变化。
这就是很多框架的设计思想了。