Java养成计划(打卡第22天)
排除学业过于繁忙的时间还有昨天娱乐停更,我们的分享文章已经进行到了22天,再前面的时间里我们一直再扎实java SE的基础,当然这些基础是非常重要的,今天和大家分享的是Java的反射机制,当然这个模块的内容是十分庞大的,我们会慢慢分享
我们开始今天的内容:Java反射机制
在说反射机制之前,我们知道计算机将数据存储在数据库里面,数据库就像是一个仓库一样,我们常见的数据库是Oracle,MySQL,DB2,SQLSever,SQLLite -----关系型数据库;Redis,MongoDB,HBase-----非关系型数据库
大概分析一下
现在的问题是,一个公司随着业务的扩大需要将所有与MySQL绑定的Java业务全部换成存储量更大的Oracle,你发现java业务中一共几千个文件,那难道要累死累活几天一个个点开文件,将所有的MySOL改成Oracle? 那效率太低下了
集群:一堆完成同一功能的服务器搭建的架构
比如现在用50台服务器作为数据库存储,那用MySOL又划算了,像再把Oracle换成MySOL,这时的文件规模估计又大了许多,所以再点开文件一个一个修改就不可行了
实际上这个绑定不是直接绑定的,在实际开发中往往是采用了Strategy模式,面向接口编程,既然有很多数据库类型,那就不要直接定义成普通类
数据处理部分Service -----操作数据库接口Dao(定义了所有操作数据库的方法)----Dao的具体实现(mySQLDao)-------MySOL
我们在service中就会定义接口的实现类 Dao d = new OracleDao();上转型变量,实现多态,所以这就是所谓的绑定,我么如果真修改就是将所有代码中的子类对象给修改了
这就是 耦合–两个模块之间相互关联
所以说耦合在实际开发中要解耦
那怎么解耦呢,我们可以定义一个文件dao.properties,在这个文件中我们写入dao = MySOLDao;
我们调用实现类对象时就不直接创建对象了,我们就直接读取dao.proprties文件,根据文件内容来创建实现类对象
那这里我们读取到的是文件中的字符串,如何根据字符串来判定我们到底创建哪类对象呢?这就利用到了反射机制
反射机制就是将文件中的内容映射成类
万物皆可对象
我们知道Java文件编译后称为字节码的文件,那这个字节码就可以抽象成类,而字节码是由类得到的,所以我们就可以抽象出
反射:在获取这个类的字节码的基础上来解剖这个类
我们既然知道这是类,怎么使用,如果直接创建对象 new,那么是会报错的,因为这个位于lang包的类代表的是类的类,所以它创建出的对象都是代表一个具体的类,new出来的对象是没有意义的,那我们要如何使用class
class是对字节码的抽象,那么某一个具体的类的字节码是不是就是Class的实例码呢,那我们就直接定义一个Class变量,之后将某个具体的类的字节码赋值,那就可以得到具体的类了
public class Luogu extends Thread{ public static void main(String[] args) { Class<String> stz = String.class;//接受类的字节码,这里<>里和后面都是某个具体的类 System.out.println(stz); } }
得到的结果就是
class java.lang.String
这里就获取到String类的字节码了
那接口可以吗,比如传入List;
也可以的
class java.awt.List //这里它调取了另外一个List
public class Luogu extends Thread{ public static void main(String[] args) { Class<Listener> stz= Listener.class; System.out.println(stz); } }
interface java.net.http.WebSocket$Listener
那除了类和接口,那数组可不可以获取字节码呢也是可以的
Class<int[]> stz = int[].class;
得到的结果是
class [I
从这个结果我们可以看出数组也是一个类,所以数组变量也是和其他类的一样都是对象变量
我们获取Class对象–实例码的方式
Object o = "abs"; Class<Object> stz = o.getClass();//得到对象所对应的实例类的字节码
我们其实还可以**利用Class的forName方法来获取,直接通过字符串就能获取到字节码
Class<String> stz = (Class<String>)Class.forName("Date");
这里我们要求的式前后相同,所以要使用一个强制类型转换
但是会出现异常,因为直接给个类型,它找不到的
这里的解决办是我们直接给全路径
public static void main(String[] args) throws ClassNotFoundException { @SuppressWarnings("unchecked") Class<String> stz = (Class<String>)Class.forName("java.util.Date"); System.out.println(stz); }
运行之后就可以打印出类所对应的字节码了
class java.util.Date
Class<String> cla = (Class<String>)Class.forName("java.lang.String"); String str = Cla.newInstane();
这里我们如果正常创建一个Integer对象
Integer in = new Integer(5);
将对像设置为5;那我们就不能使用上的无参的newInstance了,这里就要使用Constructor了
Class<Integer> stz = (Class<Integer>)Class.forName("java.lang.Integer");Constructor<Integer> c = stz.getConstructor(int.class); //获取到了其构造方法Integer in = c.newInstance(5);//由构造方法初始化对象System.out.println(in);
这和上面的语句是等价的,这里我们就是将抽象方法也可以由字节码给反射,但是只能获取到非public的方法
那我们如何获取到非public的构造方法,这时就使用getDeclareConstructor();就可以获取了,传入所需要参数的字节码,就可以构造成功;
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException{ Class<String> stz = (Class<String>)Class.forName("java.lang.String"); Constructor<String> c = stz.getDeclaredConstructor(char[].class,boolean.class); //Integer in = c.newInstance(5); c.setAccessiable(true);//暴力破解,将非法变为合法 System.out.println(c.newInstance(new char[]{'a'}),true); }
使用该方法就不用区分构造方法的访问权限,不管是public还是非public都可以使用
也就是说类中的访问权限修饰符在反射面前是无效的
我们还可以直接使用getDclareConstructors获取类的所有构造方法;
比如String类的
@SuppressWarnings("rawtypes") public static void main(String[] args) { Class<String> stz = String.class; Constructor[] sc = stz.getDeclaredConstructors(); for(Constructor c:sc)//使用的for-each循环 { System.out.println(c); } }
输出的结果是
public java.lang.String(byte[])
public java.lang.String(byte[],int,int)
public java.lang.String(byte[],java.nio.charset.Charset)
public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int,int,java.nio.charset.Charset)
java.lang.String(char[],int,int,java.lang.Void)
java.lang.String(java.lang.AbstractStringBuilder,java.lang.Void)
public java.lang.String(java.lang.StringBuilder)
public java.lang.String(java.lang.StringBuffer)
java.lang.String(byte[],byte)
public java.lang.String(char[],int,int)
public java.lang.String(char[])
public java.lang.String(java.lang.String)
public java.lang.String()
public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException
public java.lang.String(byte[],int)
public java.lang.String(byte[],int,int,int)
public java.lang.String(int[],int,int)
这就将String类的所有构造方法给打印出来了
Method m = stz.getDeclaredMethod(“charAt”,int.class);
char c = (char) m.invoke(str,5);
上面杂七杂八说了很多,总的来说,反射就是我们可以通过字节码的方式反射出相应的类,方法,构造方法,属性等
我们的获取类的字节码的方式有三种,一种是直接类名.class;还有就是由具体的对象获取 Object obj; obj.getClass就可以获取字节码;还有就是使用class类的forName方法,但是这种方法我们需要注意就是要将类名加在<>里,还有就是要给完全路径
我们获取到类的字节码,我们就可以根据字节码获取到相关的Constructor,Method对象, 比如存储字节码的是stz, 那么通过stz.getDeclaredConstructor()就可以获取到相关的含参构造方法,对其实现就newInstance就可以使用
同理我们使用方法也可以根据stz,stz.getDeclaredMethod()就可以获取方法,只是在获取方法时要传入参数的字节码,比如int.class ,String.class;实现就invoke()—传入具体的参数就可
最关键的就是我们获取的方法都是不区分访问权限的,以前普通的方法或许存在不可见的问题,但是反射直接可以暴力破解 setAccessiable(true);这样我们就可以获取到私有的方法了
好了,今天的分享就到这里,感觉有些东西没有说清楚,明天会继续分析,望海涵~~