java反射机制笔记
一:反射基础
反射机制有两个好处
- 通过反射可以将.class文件反编译成.java文件
- 通过反射机制访问对象的方法,属性,构造方法等。
关于Class类的使用3种方法
注:下面的Class类型的对象等于出来的,就是说所指向的都是类
通过forName();
Class a = Class.forName("your class path");//里面写的是完整的类的路径
通过类名.class
Class b = Student.class
这里的b就是Student类
通过类的对象的.getClass()方法
这个方法是Object对象所拥有的,也就是所有对象所共有的,当然,它只能通过对象去调用Student student = new Student(); Class c = student.getClass();
区别
发现一个问题
假如有这样一个代码class A { static { System.out.println("A...."); } } public class demo1{ public static void main(String [] args) throws Exception{ //使用forName()方式加载类 Class.forName("A");//会输出A....,这就代表A中静态语句块被执行,也就是说明,A这个类被加载到jvm中了 //但是我们使用.class时候 Class a = A.class;//这里是不会输出A....的,就代表并没有加载到jvm中 //使用对象调用.getClass()方法的时候肯定会被执行,因为声明对象的时候就已经被调用了 A b = new A(); Class c = b.getClass(); } }
注意:forName()会将类加载到jvm中,而.class不会
通过forName() 和newInstance()来实例化对象
public class Student { String name ; static { System.out.println("静态语句块被执行"); } Student(){ System.out.println("无参数构造方法被执行"); } Student(String name){ this.name= name; System.out.println("有参数构造方法被执行"); } public void fun() { System.out.println("方法"); } }
import java.util.Date; import java.text.SimpleDateFormat; public class MainTest { public static void main(String[] args) throws Exception { //使用.forName()来实例化对象的步骤 //这里的a 就是指向了类Student的地址,因为加载到jvm中的类只能有一个,所以,无论forName多少次,指向的都是同一个地址 Class a =Class.forName("Student"); //实例化对象a Object b = a.newInstance(); //判断这个b是否属于Stduent类型的对象,如果属于,就强制类型转化 if(b instanceof Student) { Student c =(Student)b; c.fun(); } //同样,我们对Date做次试验 Class date = Class.forName("java.util.Date"); Object oDate = date.newInstance(); if(oDate instanceof Date) { Date rDate = (Date)oDate; System.out.println(new SimpleDateFormat("YYYY-MM-dd HH-mm-ss").format(rDate)); } } }
注意:使用newInstance()方法的一定要是个Class对象,并且返回的是Object对象,需要做强制类型转换。
二:可变长参数
- 可变长参数使用 int… yourname 的方式来实现
- 一个方法中只能有一个可变长参数,且如果还有其它单个参数的时候可变长参数必须放在最后面
- 形参是可变长参数,实参除了通过多个相同数据类型的对象来传给形参,还可以直接传一个数组给形参
代码中我写了详细的注释
public class Student {
String name ;
static {
System.out.println("静态语句块被执行");
}
Student(){
System.out.println("Student无参数构造方法被执行");
}
Student(String name){
this.name= name;
System.out.println("有参数构造方法被执行");
}
public void fun() {
System.out.println("方法");
}
}
import java.util.Date;
public class CanLong {
//可变长参数的使用
//如果只是一个实参传递过来,会精确匹配对应的方法,而不是调用可变长参数
public static void m1(int i) {
System.out.println(i);
}
public static void m1(int... i) {
System.out.println("可变长参数执行");
}
//可变长参数能否变成一个数组来输出
public static void stringTest(String... str) {
//直接使用数组的形式将str这个可变长参数来输出
for(int i=0;i<str.length;i++) {
System.out.println(str[i]);
}
}
//可变长参数使用的位置问题
//一个方法里面只能出现一个可变长参数,如果和其他的单个类型一起出现,可变长参数的位置一定要放在最后面
public static void stringTest2(int i,String... str) {
System.out.println(i);
for(int s=0 ;s<str.length;s++) {
System.out.println(str[s]);
}
}
//在可变长参数中对Class对象的使用
public static void classTest(Class... one) throws Exception {
for(int i=0;i<one.length;i++) {
System.out.println(one[i].newInstance());
}
}
public static void main(String[] args) throws Exception {
//可变长参数匹配问题
m1(2,3,4);//结果是输出可变长参数执行
m1(1);//结果是输出1
//可变长参数传参和传数组问题
stringTest("编程","学习","发疯");//输出编程 学习 发疯
//传数组
String[] str = {"编程","学习","发疯"};
stringTest(str);//输出编程 学习 发疯,也就是说,可变长参数可以传多个相同类型的参数,也可以传递对应类型的数组
//使用位置的问题
stringTest2(55,"学习","编程","很快乐");//输出55 学习 编程 很快乐
//Class对象使用的问题
classTest(Student.class,Date.class);
}
}
三:Properties属性对象和io实现对配置文件的加载和设置
写法与规则
一般在面对需要变换的参数较多时候,比如数据库的用户名和密码,我们会单独建立一个配置文件,这个文件的后缀可以自己命名的,但是,依据java规范,最好是写成.properties结尾
*properties文件的规范
使用类似username=什么神奇
的方式
或者username:什么神奇
或者username 什么神奇
(中间是一个空格)如果出现 user:name=什么神奇 的情况,永远只是第一个符号起作用,这里起作用的就是user后的”:”了,这样我们的key的值 就是user了
读取properties文件的方法
- 首先是创建
Properties
对象 - 然后使用
FileInputStream
去读properties
文件 - 转换
FileInputStream
字节流对象成utf-8
形式(就是使用InputStreamReader(new FileInpurStream(),"utf-8"))
防止中文乱码,(当然也可以使用中间流的形式BufferedReader
), - 将上面的
InputStreamReader
对象加载到properties
对象中.load()
- 通过
properties
对象的.getProperty("key")
方法去得到value
下面是代码
import java.io.FileInputStream; import java.io.InputStreamReader; import java.util.Properties; public class PropertiesDemo1 { public static void main(String[] args) throws Exception { //创建Properties对象 Properties pro = new Properties(); //创建文件字节输入流 FileInputStream stream= new FileInputStream("G:\\aaa\\uers.properties"); //因为碰到中文乱码问题,我们将用到InputStreamReader来将上面的字节流,转换成utf-8的字节流 InputStreamReader reader = new InputStreamReader(stream,"utf-8"); //将流加载到Properties对象中 pro.load(reader); //关闭流 reader.close(); stream.close(); //获得properties文件中的信息 String username =pro.getProperty("username"); String password =pro.getProperty("password"); System.out.println(username+" "+password); } }
- 首先是创建
将Properties对象写入到文件中
直接上代码,代码中写好了注释,但是,这里的.store()方法里面,第二个参数是生成的文件中第一行的注释信息,这里我无法转换成utf-8
import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.util.Properties; public class PropertiesDemo2 { public static void main(String[] args) throws Exception{ //创建Properties对象 Properties pro = new Properties(); //创建字节输出流对象 FileOutputStream stream = new FileOutputStream("G:\\aaa\\write.properties"); //将这个字节流转换成utf-8的字符流 OutputStreamWriter Wstream = new OutputStreamWriter(stream, "utf-8"); //设置Properties对象中的数据 pro.setProperty("username", "什么神奇"); pro.setProperty("password", "yz1998"); //调用Properties对象的store方法绑定到一个字节输出流上 String s = "这是一个配置文件"; pro.store(Wstream,s);//第一个参数 是要绑定的输出流,第二个是注释,会显示在文件的第一行,这里的注释的中文是unicode字符 Wstream.close(); stream.close(); } }
四:反射机制+io+Properties联合使用
直接上代码,代码中写好了注释
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Properties;
public class PropertiesDemo3 {
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//反射机制+IO+Properties的联合使用
//创建properties对象
Properties pro = new Properties();
//创建字符输入流
FileInputStream stream = new FileInputStream("G:\\aaa\\test.properties");
InputStreamReader reader = new InputStreamReader(stream,"utf-8");
//加载到pro对象中
pro.load(reader);
//平常的输出方式
String username =pro.getProperty("username");
System.out.println(username);
//使用反射机制来动态获取配置文件中的类
String className =pro.getProperty("className");
Class relClassName = Class.forName(className);
Object name = relClassName.newInstance();
System.out.println(name);
reader.close();
}
}
五:使用 java.lang.reflect.Field; java.lang.reflect.Modifier;去获得反编译一个类,去获得里面的属性
获得类名和改类的修饰符
Class c = Class.forName(Student); c.getName();//获取类名 c.getModifiers();//这个返回的是修饰符的整数表示 Modifier.toString(c.getModifiers());//获取修饰符的String形式,这才会将对应的修饰符的整数转换成字符串
获得一个类中的所有属性
//将所有的属性变成一个数组 Field[] field = c.getDeclaredFiels(); //如果是以下 Field[] field = c.getFields();//这是只获得public修饰的属性 //获取某属性的修饰符 Modifiers.toString(field.getModifiers()); //获取属性的类型 field.getType().getSimpleName();//如果是.getName()的话,就会连同包一起进行输出 //获取属性名 field.getName();
下面来看完整的代码
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class fanbianyeDemo4 {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("Student");
//获得Student类中的所有属性
Field[] fields = c.getDeclaredFields();
//反编译
StringBuffer sb = new StringBuffer();
sb.append(Modifier.toString(c.getModifiers())+" class "+c.getName()+"{\n");
for(Field field :fields) {
sb.append("\t");
sb.append(
Modifier.toString(field.getModifiers())+" "+
field.getType().getSimpleName()+" "+
field.getName()+";\n");
}
sb.append("}");
System.out.println(sb);
}
}
这样如果联合properties,就能随意反编译你想要的类的代码
六:得到和设置某个类中的属性值
明确以下几点
1. 为类中属性赋值需要通过对象实例化,然后通过对象才能调用属性或者是方法为属性赋值(当然静态的不在考虑中)
比如
Student student = new Student();
student.name="什么神奇";
2. 引出以下方法
//对某个特定属性的赋值
//首先就是要获得对应的属性
Class c = Class.forName("Student");
//获取这个类中的某个属性
Field field = c.getDeclaredField("name");
//因为有可能是private的,我们需要打破封装
field.setAccessible(true);
//还记得我们上面说过,为某个属性赋值,我们需要一个对象才能对属性进行操作
//将Student类进行实例化
Object cl = c.newInstance();
field.set(cl,"什么神奇");//第一个参数是要赋值的对象,第二个是要给这个属性赋上什么值,这个方法会返回空
//获取某个属性的值
field.get(cl);//代表的是获取cl对象的name属性的值,返回的是一个Object,可以强制转化
下面是完整的代码:
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.util.Properties;
public class GetOneAttributeDemo5 {
public static void main(String[] args) throws Exception{
//下面是获得某个类中的某个属性的值,和设置某个类中某个属性的值
//创建Properties对象
Properties pro = new Properties();
//创建输入流
FileInputStream stream = new FileInputStream("G:\\aaa\\test.properties");
//将字符流转换成utf-8的字符流
InputStreamReader reader = new InputStreamReader(stream,"utf-8");
//将字符输入流加载到Properties对象中
pro.load(reader);
String className =pro.getProperty("className");
Class c = Class.forName(className);
//获取名字叫做name的属性
Field field = c.getDeclaredField("name");
//为该属性赋值
//首先凡是为某个属性赋值都需要对象实例化来进行操作
Object k = c.newInstance();
//因为name属性是private的,所以必须先打破封装,不然,获取不了private的属性值
field.setAccessible(true);
//设置属性值
field.set(k, "什么神奇");
//得到属性值
String getString =(String)field.get(k);
System.out.println(getString);
}
}
七:反编译某个类中所有的方法
因为操作和field极其相似,我这里就写出了要注意的地方。
1. 方法是有返回值的,这里不能直接用getType()了,要使用getReturnType()
2. 方法是有形参的参数列表的,所有相对于属性来说,多了一个东西
使用
Class[] classes = method.getParameterTypes();
获得这个方法中的形参的类,放在Class数组中
然后使用classes[i].getSimpleName()
去得到对应的类的名字(形式参数类型名字)
下面来看完整的代码
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Properties;
public class fanbianyiMethodDemo6 {
public static void main(String[] args) throws Exception{
//获取某个类中所有的方法
Properties pro = new Properties();
FileInputStream stream = new FileInputStream("G:\\aaa\\test.properties");
InputStreamReader reader = new InputStreamReader(stream,"utf-8");
pro.load(reader);
String className= pro.getProperty("className");
Class c = Class.forName(className);
StringBuffer buffer = new StringBuffer();
buffer.append(Modifier.toString(c.getModifiers())+" class "+ c.getName()+"{\n");
//获取方法数组
Method[] methods = c.getDeclaredMethods();
for(Method method :methods) {
buffer.append("\t"+Modifier.toString(method.getModifiers())+" ");
buffer.append(method.getReturnType().getSimpleName()+" ");
buffer.append(method.getName()+"(");
Class[] classes = method.getParameterTypes();
for(int i=0;i<classes.length;i++) {
if(i==classes.length-1)
buffer.append(classes[i].getSimpleName());
else
buffer.append(classes[i].getSimpleName()+",");
}
buffer.append("){}\n");
}
buffer.append("\n}");
System.out.println(buffer);
}
}
八:通过发射机制,来调用某个类中的方法
注意:
1. 和设置属性相同,使用方法,也需要对象,所以,这里也肯定要 用到对象的
2. 获取的方法,在使用getDeclaredMethod("Method name",Class...args)
第一个参数是要获取的方法名,第二个是可变长参数,这里写的是改方法的所有形式参数列表
3. 使用该方法
凡是用到方法都要使用到对象
声明对象
Object k = c.newInstance();
通过invoke
方法
method.invoke(k,Object...)
第二个参数是Object类型的可变长参数,相当于给方法传参数值,对应要传给方法的实参
下面来看完整的代码
public class Student {
private String name ;
private static String usrname;
public int a ;
static {
System.out.println("静态语句块被执行");
}
Student(){
System.out.println("Student无参数构造方法被执行");
}
Student(String name){
this.name= name;
System.out.println("有参数构造方法被执行");
}
public void fun() {
System.out.println("方法"+name);
}
public String getStrings(String a,String b) {
return a+""+b;
}
private String setK() {
return "yy";
}
}
import java.lang.reflect.Method;
public class getUseOneMethodDemo7 {
public static void main(String[] args) throws Exception {
Class c = Class.forName("Student");
Method method= c.getDeclaredMethod("getStrings", String.class,String.class);
Object k = c.newInstance();
String str =(String)method.invoke(k,"什么神奇","很天才");
System.out.println(str);
}
}
九:通过反射机制获得某个类中的构造方法
需要知道的一点,就是Constructor关键字通过Constructor[] con = c.getConstructors();
来获得里面所有的构造方法,放到数组中其它的,与前面的获得方法的形式差不多
直接看具体代码
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class GetConstructor {
public static void main(String[] args) throws Exception{
//反编译某个类的构造方法
Class c = Class.forName("java.lang.String");
//得到该类中的所有构造方法
Constructor[] cons = c.getDeclaredConstructors();
StringBuffer sb = new StringBuffer();
sb.append(Modifier.toString(c.getModifiers())+" class "+ c.getSimpleName()+"{\n");
//循环迭代数组Con
for(Constructor con : cons) {
sb.append("\t");
sb.append(Modifier.toString(con.getModifiers())+" ");
sb.append(c.getSimpleName()+" ");
sb.append("(");
//循环迭代形参列表
Class[] parameterTypes =con.getParameterTypes();
for(int i=0;i<parameterTypes.length;i++) {
if(i==parameterTypes.length)
sb.append(parameterTypes[i].getSimpleName());
else
sb.append(parameterTypes[i].getSimpleName()+",");
}
sb.append("){}\n");
}
sb.append("}");
System.out.println(sb);
}
}
十:通过某个有参数的构造方法实例化对象
注:
我们上面是通过
Class c = Class.forName("Student");
Object obj = c.newInstance();
去实例化对象,但是,这只针对于无参数的构造方法的实例化,如果是有参数的呢?我们就需要通过Constructor 对象去实例化了,看完整的代码
import java.lang.reflect.Constructor;
public class GetOneConstructorDemo9 {
public static void main(String[] args) throws Exception{
//获得某个类的某一个特定的构造方法
Class c = Class.forName("Uers");
//里面的参数写的是某个构造方法对应的形式参数的类型,因为构造方法名字都一样,不一样的只是参数
//类型和个数,所以没必要传名字了
Constructor con = c.getDeclaredConstructor(String.class,int.class);
//不同于前面提到过的方法的调用,
//方法和属性的调用都需要用到对象,因此,必须要将类通过c.newInstance()实例化,但是
//这个实例化也只对无参数的构造方法进行的,如果要用有参数的构造方法
//那么必须要通过con 去实例化了
Object obj = con.newInstance("什么神奇",20);//毫无疑问,实例化里面肯定传实参进去
System.out.println(obj);
}
}
class Uers {
String name;
int age;
public Uers(String name ,int age){
this.name=name;
this.age=age;
}
public String toString() {
return this.name+" : "+this.age;
}
}
十一:获得父类的类名和接口
c.getSuperclass()
c.getInterfaces()
看下面的代码
public class getSuperClassAndInterfaceDemo10 {
public static void main(String[] args) throws Exception{
Class c = Class.forName("java.lang.String");
Class superClass =c.getSuperclass();
System.out.println(superClass.getName());
Class[] inter = c.getInterfaces();
for(Class a :inter) {
System.out.println(a.getName());
}
}
}
反射机制基础告一段落