1.1、什么是反射?
反射机制是JAVA语言提供的一种基础功能,赋予程序在运行时自省的能力,通过反射,我们可以直接操作类或者对象,必须获取类的定义,获取类声明的属性与方法,调用方法或者构造对象,甚至可以在运行时修改类的定义。
1.2、反射机制的作用?
可以通过java语言中的反射机制可以操作字节码文件。
(反射机制相关的重要的类都在java.lang.reflect.*
下)
1.3、反射机制相关的重要的类有哪些?
java.lang.Class
代表整个字节码,代表一个类型,代表整个类
java.lang.reflect.Method
代表字节码中的方法节码,代表类中的方法
java.lang.reflect.Constructor
代表字节码中的构造方法字节码,代表类中的构造方法
java.lang.reflect.Field
代表字节码中的属性字节码。代表类中的成员变量(静态变量+实例变量)
2.1、第一种方式:Class c=Class.forName(“完整类名带包名”);
java.lang.Class
类中的方法Class.forName()
:
(1)静态方法
(2)方法的参数是一个字符串
(3)字符串需要的是一个完整的类名
(4)完整的类名必须带有包名。java.lang也不能省略
(5)会导致类加载
public class Test01 {
public static void main(String[] args) {
try {
Class c1=Class.forName("java.lang.String");//c1代表String.class,或者说c1代表String类型
Class c2=Class.forName("java.util.Date");//c2代表Date类型
Class c3=Class.forName("java.lang.Integer");//c3代表Integer类型
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
验证Class.forName()
方法导致类加载:
public class Test01 {
public static void main(String[] args) {
try {
Class.forName("Fs.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MyClass{
//静态代码块在类加载时执行,并且只执行一次
static {
System.out.println("MyClass类的静态代码块执行了");
}
}
获取类名:(java.lang.Class
中的方法)
String getName()
获取类的完整类名
String getSimpleName()
获取类的简类名
//获取整个类
Class studentClass=Class.forName("Fs.Student");
//获取类的完整类名
String className=studentClass.getName();
System.out.println(className);//Fs.Student
//获取类的简类名
String simpleName=studentClass.getSimpleName();
System.out.println(simpleName);//Student
2.2、第二种方式:Class c=对象.class();
java中任何一个对象都有一个方法:getClass()
public class Test01 {
public static void main(String[] args) {
Class c1=null;
Class c2=null;
try {
c1=Class.forName("java.lang.String");
c2=Class.forName("java.util.Date");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//java中任何一个对象都有一个方法:getClass()
String s="abc";
Class x=s.getClass();//x代表String.class字节码文件,x代表String类型
System.out.println(c1==x);//true(==判断的是对象的内存地址)
Date time=new Date();
Class y=time.getClass();
System.out.println(c2==y);//true (c2和y两个变量中保存的内存地址都是一样的,都指向方法区中的字节码文件)
}
}
2.3、第三种方式:Class c=任何类型.class;
java语言中任何一种类型,包括基本数据类型,它都有.class属性。
public class Test01 {
public static void main(String[] args) {
Class z=String.class;//z代表String类型
Class k= Date.class;//k代表Date类型
Class f=int.class;//f代表int类型
}
}
2.4、获取到Class,能干什么?
(通过反射实例化对象)
public class User {
public User() {
System.out.println("无参数构造方法!");
}
}
public class Test01 {
public static void main(String[] args) {
try {
//通过反射机制,获取Class,通过Class来实例化对象
Class c=Class.forName("Fs.User");
//newInstance()这个方法会调用User这个类的无参数构造方法,完成对象的创建
Object obj=c.newInstance();
System.out.println(obj);//Fs.User@4eec7777
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
2.5、研究一下文件路径的问题
FileReader reader=new FileReader("Fs\\user.properties");
这种方式的路径缺点:移植性差,在IDEA种默认的当前路径是project的根。
这个代码假设离开了IDEA,换到其他位置,可能当前路径就不是project的根了,这个路径就失效了。
接下来说一种比较通用的一种路径。即使代码换位置了,这种编写仍然是通用的。
注意:使用一下通用方式的前提的:这个文件必须在类路径下。
public class Test01 {
public static void main(String[] args) {
//Thread.currentThread() 当前线程
//getContextClassLoader() 是线程对象的方法,可以获取到当前线程的类加载器对象
//getResource() (获取资源)这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
String path=Thread.currentThread().getContextClassLoader().getResource("user.properties").getPath();
采用以上代码可以拿到一个文件的绝对路径
System.out.println(path);///D:/IDEA/Project/Serein/out/production/Serein/user.properties
}
}
直接以流的形式返回:
public class Test01 {
public static void main(String[] args) throws Exception {
//获取一个文件的绝对路径
/*
String path=Thread.currentThread().getContextClassLoader().getResource("user.properties").getPath();
FileReader reader=new FileReader(path);
*/
//直接以流的形式返回
InputStream reader=Thread.currentThread().getContextClassLoader().getResourceAsStream("user.properties");
Properties pro=new Properties();
pro.load(reader);
reader.close();
//通过key获取value
String className=pro.getProperty("");
System.out.println(className);
}
}
java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容。(属性配置文件(xxx.properties)必须放在类路径下)
public class Test01 {
public static void main(String[] args) throws Exception {
//资源绑定器,只能绑定xxx.properties文件,并且这个文件必须在类路径下。
//文件扩展名也必须是properties,并且在写路径的时候后面的扩展名不能写
ResourceBundle bundle =ResourceBundle.getBundle("user");
String className=bundle.getString("className");
System.out.println(className);//java.util.Date
}
}
4.1、获取类中的属性:(java.lang.Class
中的方法)
Field[] getFields()
获取类中所有public修饰的的属性
Field[] getDeclaredFields()
获取所有的属性
Field getDeclaredField(String name)
根据属性名称来获取属性
public class Student {
//Field翻译为字段,其实就是属性/成员
//4个Field,分别采用了不同的访问控制权限修饰符
public int no;
private String name;
protected int age;
boolean sex;
public static final double PI=3.1415926;
}
//获取整个类
Class studentClass=Class.forName("Fs.Student");
//获取类中所有public修饰的的Field:Field[] getFields()
Field[] fields =studentClass.getFields();
System.out.println(fields.length);//测试数组中元素个数:2
//获取所有的Field
Field[] fs=studentClass.getDeclaredFields();
System.out.println(fs.length);//测试数组中元素个数:5
4.2、获取属性的修饰符列表:
int getModifiers()
获取修饰符,以整数编码
static String toString(int mod)
将这个修饰符的整数编码转换成字符串()(java.lang.reflect.Modifier中的静态方法)
//获取属性的修饰符列表
int i=field.getModifiers();//返回的修饰符是一个数字,每个数字是修饰符的代号
//System.out.println(i);
//可以将这个“代号”数字转换成“字符串”吗?
String modifierString= Modifier.toString(i);
System.out.println(modifierString);
4.3、获取属性的类型:
类> getType()
返回属性的类型(java.lang.reflect.Field
中的方法)
//获取属性的类型
Class fieldType=field.getType();
String fName=fieldType.getName();
System.out.println(fName);
4.4、获取属性的名字:
String getName()
获取属性的名字
//获取属性的名字
System.out.println(field.getName());
public class Student {
//Field翻译为字段,其实就是属性/成员
//4个Field,分别采用了不同的访问控制权限修饰符
public int no;
private String name;
protected int age;
boolean sex;
public static final double PI=3.1415926;
}
public class Test01 {
public static void main(String[] args) throws Exception {
//获取整个类
Class studentClass=Class.forName("Fs.Student");//这里的异常懒得弄,就直接上抛了
Object obj=studentClass.newInstance();//obj就是Student对象(底层调用无参数构造方法实例化对象)
//获取no属性(根据属性名称来获取属性)
Field noField=studentClass.getDeclaredField("no");
//给obj对象(Student对象)的no属性赋值
/*
普通赋值方法:s.no=1111;给s对象的no属性赋值1111
要素1:对象s ;要素2:no属性 ;要素3:1111值。
虽然使用了反射机制,但是赋值的三要素还是缺一不可:
要素1:obj对象
要素2:no属性
要素3:2222值
*/
noField.set(obj,2222);//给obj对象的no属性赋值2222
//读取属性的值
/*
System.out.println(s.no);//获取s对象的no属性
两要素:1、s对象 2、no属性
下面的也是一样,两个要素:1、s对象 2、no属性
*/
System.out.println(noField.get(obj));//2222
//可以访问私有的属性
Field nameField=studentClass.getDeclaredField("name");
//打破封装
//这样设置完后,在外部也是可以访问private的
nameField.setAccessible(true);
//给name属性赋值
nameField.set(obj,"Serein");
//获取name属性的值
System.out.println(nameField.get(obj));//Serein
}
}
语法:
类型...(注意:一定是三个点)
1、可变长度参数要求参数个数是:0~N个
2、可变长度参数在参数列表中必须在最后一个位置上,而且可变长度参数只能有1个。
3、可变长度参数可以当做一个数组来看待
public class Test02 {
public static void main(String[] args) {
m();
m(10);
m(10,20);
m3("a","b","c");
//也可以传一个数组
//String[] strs={"x","y","z"};
//m3(strs);
}
public static void m(int...args){
System.out.println("m方法执行了");
}
public static void m3(String...args){
for (int i=0;i< args.length;i++){
System.out.println(args[i]);
}
}
}
public class UserService {
//登录方法
public boolean login(String name,String password){
if ("admin".equals(name)&&"123".equals(password)){
return true;
}
return false;
}
//退出系统方法
public void logout(){
System.out.println("系统已经安全退出!");
}
}
6.1、获取所有的方法:方法[] getDeclaredMethods()
(java.lang.Class
中的方法)
方法 getDeclaredMethod(String name, 类>... parameterTypes)
根据方法名和方法参数获取方法
Method[] methods=userService.getDeclaredMethods();
6.2、获取修饰符列表(和上面的一样)
System.out.println(Modifier.toString(method.getModifiers()));
6.3、获取返回值类型:类> getReturnType()
(java.lang.reflect.Method
中的方法)
System.out.println(method.getReturnType().getSimpleName());
6.4、获取方法名:getName()
System.out.println(method.getName());
6.5、获取方法参数的修饰符列表(一个方法的参数可能会有多个):类>[] getParameterTypes()
(java.lang.reflect.Method
中的方法)
Class[] parameterTypes= method.getParameterTypes();
for (Class parameterType:parameterTypes) {
ystem.out.println(parameterType.getSimpleName());
}
6.6、反射机制调用方法:Object invoke(Object obj, Object... args)
public class UserService {
//登录方法
public boolean login(String name,String password){
if ("admin".equals(name)&&"123".equals(password)){
return true;
}
return false;
}
//可能还有一个同名login方法
public void login(int i){
}
//退出系统方法
public void logout(){
System.out.println("系统已经安全退出!");
}
}
public class Test03 {
public static void main(String[] args) throws Exception {
//获取类
Class userService=Class.forName("Fs.UserService");
//获取所有的Method
Method[] methods=userService.getDeclaredMethods();
//创建对象
Object obj=userService.newInstance();
//获取方法
//获取到三个参数的login方法
Method loginMethod1 =userService.getDeclaredMethod("login", String.class, String.class);
//获取到一个参数的login方法
Method loginMethod2 =userService.getDeclaredMethod("login",int.class);
//调用方法:Object invoke(Object obj, Object... args)
/*
boolean loginSuccess=userService.login("admin","123");
调用方法要素分析:
要素1:对象userService
要素2:login方法名
要素3:实参列表
要素4:返回值
*/
Object retValue=loginMethod1.invoke(obj,"admin","123");
System.out.println(retValue);//true
}
}
(和反射方法差不多)
public class Vip {
int no;
String name;
String birth;
boolean sex;
public Vip() {
}
public Vip(int no) {
this.no = no;
}
}
public class Test04 {
public static void main(String[] args) throws Exception{
//获取类
Class vipClass=Class.forName("Fs.Vip");
//获取所有的构造方法:Constructor>[] getDeclaredConstructors()
// 获取单个的构造方法:Constructor getDeclaredConstructor(类>... parameterTypes)
Constructor[] constructors=vipClass.getDeclaredConstructors();
//遍历
for (Constructor constructor:constructors){
//获取构造方法的修饰符列表
System.out.println(Modifier.toString(constructor.getModifiers()));
//获取构造方法的方法名
System.out.println(vipClass.getSimpleName());
//获取构造方法参数列表的返回值类型
Class[] parameterTypes =constructor.getParameterTypes();
for(Class parameterType: parameterTypes){
System.out.println(parameterType.getSimpleName());
}
}
}
}
反射机制调用构造方法:
public class Vip {
int no;
String name;
String birth;
boolean sex;
public Vip() {
}
public Vip(int no, String name, String birth, boolean sex) {
this.no = no;
this.name = name;
this.birth = birth;
this.sex = sex;
}
}
public class Test04 {
public static void main(String[] args) throws Exception{
//获取类
Class vipClass=Class.forName("Fs.Vip");
//调用无参数构造方法
Object obj=vipClass.newInstance();
//调用有参数的构造方法
//第一步:先获取到这个有参数的构造方法
Constructor con=vipClass.getDeclaredConstructor(int.class,String.class,String.class,boolean.class);
//第二:调用构造方法new对象
Object newObj=con.newInstance(100,"Sersin","2001-01-01",true);
//获取无参构造new对象
Constructor con2=vipClass.getDeclaredConstructor();
Object newObj2=con2.newInstance();
}
}