大家好,我是💖星仔💖。一个致力于为大家分享各种Java知识的博主。
✨专栏汇总✨
本博客收录于《华星详谈-学习中心》。本学习中心收集了Java整个技术体系的所有技术要点。每篇博客后面或者知识点结尾都附带有面试题,提供给大家巩固本章内容。
为各位同胞们能够系统性的掌握整个Java技术体系而建立的学习中心。星仔正在努力的更新学习中心中的内容。望诸君共勉!!!
当调用java命令来运行某个Java程序时,该命令将会启动一个JVM进程。同一个JVM中的所有线程变量都处于同一个进程中,共享该JVM的内存区域。
当出现以下情况时JVM会退出,JVM进程一旦结束,该进程中内存中的数据将会丢失。
1)程序正常执行结束;
2)使用System.exit(0)方法;
3)出现异常时没有捕获异常;
4)平台强制结束JVM进程;
当程序主动使用到某个类时,如果该类还未被加载进内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化操作。
1)类的加载
类加载时指将类的class文件(字节码文件)载入内存中,并为之创建一个java.lang.Class对象,我们称之为字节码对象。类的加载过程由类加载器(ClassLoader)完成,类加载器通常有JVM提供,我们称之为系统类加载器。我们也可以继承ClassLoader类来提供自定义类加载器。不同的类加载器可以实现加载本地字节码文件,jar包中的字节码通过网络加载字节码。
2)类的连接
当类被加载进内存之后,系统为之生产一个对应的Class对象,接着把类的二进制数据合并到JRE中。
①、验证:检测被加载的类是否有正确的内部结构;
②、准备:负责为类的static变量分配内存,并设置默认值;
③、解析:把类的二进制数据中的符号引用替换为直接引用(深入分析JVM);
3)类的初始化
在此阶段JVM负责对类进行初始化。主要就是对static变量进行初始化。类的初始化一个类包含以下几个步骤
①、如果该类还未被加载和连接,则程序先加载并连接该类;
②、如果该类的直接父类还未被初始化,则先初始化其父类;
③、如果类中有初始化语句(静态代码块),则系统依次执行这些初始化语句;
符号引用:符号引用是一个字符串,它给出了被引用的内容的名字并且可能会包含一些其他关于这个被引用项的信息——这些信息必须足以唯一的识别一个类、字段、方法。这样对于其他类的符号引用必须给出类的全名。
Object obj = new java.util.Date();
编译类型: Object
运行类型: java.util.Date
需求:通过obj对象调用java.util.Date类中的toLocaleString方法。
obj.toLocaleString(); 此时编译报错。原因是编译时会检查该编译类型中是否存在toLocaleString方法,如果存在则会编译成功,否则编译失败。
解决方案
因为obj的真实类型是java.util.Date类,所以我们可以把obj对象强制转换为java.util.Date类型。
java.util.Date d = (java.util.Date)obj;
d.toLocaleString();//调用成功
如果不知道obj的真实类型,就不能强转(底层有一个方法,返回一个Object类型的java.util.Date对象)。此时问题如何解决?
Class类:用来描述类或者接口的类型,描述类的类。
Class类的实例:指在JVM中的一份份字节码,Class实例表示在JVM中的类或者接口或者枚举。是一种特殊的类,注解是一种特殊的接口。
当程序第一次使用某一个java.util.Date类的时候,就会把该类的字节码对象加载进JVM,并创建出一个Class对象。此时的Class对象就表示java.util.Date的字节码。
Class类可以表示N个类的字节码对象,那么到底怎么区分Class类此时表示的那一个类的字节码呢?为了解决该问题,Class类的设计者提供了泛型Class
java.lang.String类的字节码类型: Class<java.lang.String>;
java.util.Date类的字节码类型: Class
; java.util.ArrayList类的字节码类型: Class
;
如何创建Class对象,如何来表示一个字节码对象?如以下代码所示:
注意:同一个类在JVM中只存在一份字节码对象,也就说上述代码中claz1 == clz2 == clz3。我们一般使用最多的是第三种,其在框架中也大量使用。
问题:在上述讲了三种获取Class对象的方式,基本数据类型不能表示为对象,也就不能使用getClass的方式,并且基本类型没有类名的概念,也不能使用Class.forName的方式。那要如何表示基本类型的字节码对象呢?
解答:其实所有的数据类型都有class属性。
Class clz = 数据类型.class;
Java中九大内置Class实例:JVM中预先提供好的Class实例,分别是byte、short、int、long、float、double、boolea、char、void。表示byte.class、short.class、int.class ....void.class等。在8大基本数据类型的包装类中,都有一个常量(TYPE),用于返回该包装类对应基本类的字节码对象。
System.out.println(Integer.TYPE == int.class);//true
注意:Integer和int是不同的数据类型
System.out.println(Integer.class == int.class);//false
数组的Class实例:数组是引用数据类型,数组其实是对象。
如何来表示数组的Class实例
方式1:数组类型.class;
方式2:数组对象.getClass();
注意:所有的具有相同的维数和相同元素类型的数组,共享同一份字节码对象,和元素没有关系。
Class:描述所有的类型,所以Class类中应该具有所有类型的相同的方法。
Object:描述所有的对象,所以在Object类中应该具有所有对象的共同的方法。
需求:通过反射来获取某一个类的构造器
1)获取该类的字节码对象。
2)从该字节码对象中去找需要获取的构造器。
Class类获取构造器方法
Constructor类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器。
public Constructor>[] getConstructors():该方法只能获取当前Class所表示类的public修饰的构造器。
public Constructor>[] getDeclaredConstructors():获取当前Class所表示类的所有的构造器,和访问权限无关。
public Constructor
getConstructor(Class>... parameterTypes):获取当前Class所表示类中指定的 一个public的构造器。参数:parameterTypes表示:构造器参数的Class类型
如:public User(String name)
Constructor c = clz.getConstructor(String.class);
public Constructor
get DeclaredConstructor(Class>... parameterTypes) :获取当前Class所表示类中指定的一个的构造。
构造器最大的作用:创建对象。
使用反射创建对象步骤:
1)找到构造器所在类的字节码对象。
2)获取构造器对象。
3)使用反射,创建对象。
Constructor
常用方法:
public T newInstance(Object... initargs):如调用带参数的构造器,只能使用该方式。
参数:initargs,表示调用构造器的实际参数。
返回:返回创建的实例,T表示Class所表示类的类型。
如果一个类中的构造器是外界可以直接访问,同时没有参数,那么可以直接使用Class类中的newInstance方法创建对象。
public Object newInstance():相当于new 类名();
使用反射获取类中的方法步骤:
1)获取方法所在类的字节码对象。
2)获取方法。
Class类中常用方法:
public Method[] getMethods():获取包括自身和继承过来的所有的public方法
public Method[] getDeclaredMethods():获取自身类中所有的方法(不包括继承的,其和访问权限无关)
public Method getMethod(String methodName,Class>... parameterTypes):表示调用指定的一个公共的方法(包括继承的)
参数:
methodName:表示被调用方法的名字
parameterTypes:表示被调用方法的参数的Class类型如String.class
public Method getDeclaredMethod(String name,Class>... parameterTypes):表示调用指定的一个本类中的方法(不包括继承的)
参数
methodName:表示被调用方法的名字
parameterTypes:表示被调用方法的参数的Class类型如String.class
使用反射调用方法的步骤:
1)获取方法所在类的字节码对象;
2)获取方法对象;
3)使用反射调用方法;
如何使用反射调用一个方法?
在Method类中有方法:public Object invoke(Object obj,Object... args),表示调用当前Method所表示的方法。
public Object invoke(Object obj,Object... args)
参数:
obj:表示被调用方法底层所属对象
Method m = clz.getMethod("sayHi",String.class);
args:表示调用方法是传递的实际参数
返回:
底层方法的返回结果
调用私有方法:
在调用私有方法之前,应该设置该方法为可访问的,又因为Method是AccessibleObject子类,所以Method中具有该方法:sayGoodByeMethod.setAccessible(true);
静态方法不属于任何对象,静态方法属于类本身。此时把invoke方法的第一个参数设置为null即可。
使用反射调用数组参数(可变参数),在调用方法的时候把实际参数统统作为Object数组的元素即可。
Method对象.invoke(方法底层所属对象,new Object[]{ 所有实参 });
加载properties文件,只能使用Properties类的load方法。
方式1:使用相对路径 - 相对于classpath的根路径(字节码输出目录)
此时得使用ClassLoader(类加载器),类加载器默认就是从classpath根路径去寻找文件的。
方式2:使用相对路径 - 相对于当前加载资源文件的字节码路径
在这里使用的是当前类的字节码路径去寻找db.properties文件。