一.类的加载,连接和初始化
1.JVM和类
当调用java命令运行某个java程序时,该命令将会启动一个Java虚拟机进程,同一个JVM的所有线程,所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。
当系统出现以下几种情况时,JVM进程将被终止。
1>程序运行到最后正常结束;
2>程序运行到使用System.ext()或Runtime.getRuntime().exit()代码出结束程序;
3>程序执行过程中遇到未捕获的异常或错误而结束;
4>程序所在平台强制结束了JVM进程。
public class A
{
// 定义该类的类变量
public static int a = 6;
}
public class ATest1
{
public static void main(String[] args)
{
// 创建A类的实例
A a = new A();
// 让a实例的类变量a的值自加
a.a ++;
System.out.println(a.a);//7
}
}
public class ATest2
{
public static void main(String[] args)
{
// 创建A类的实例
A b = new A();
// 输出b实例的类变量a的值
System.out.println(b.a);//6
}
}
先运行ATest1.java,类变量a自增1,可出看出输出是7,而运行ATest2.java结果是6,而不是7,因为第一次运行JVM结束后,它对A类所做的修改将全部丢失----第二次运行JVM时将再次初始化A类。
两次运行Java程序处于两个不同的JVM进程中,两个JVM之间并不会共享数据。
2.类的加载
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三个步骤来对该类进行初始化。
类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。
通过使用不同的类加载器,可以从不同来源加载类的二进制数据,通常有如下几种来源:
1>从本地文件系统加载class文件,前面绝大部分演示程序都是采用类加载方式;
2> 从JAR包加载class文件,这种方式也很常见;
3>通过网络加载class文件;
4>把一个Java源文件动态编译并执行加载。
3.类的连接
4.类的初始化
在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化,在Java类中对类变量指定初始值有两种方式:
声明类变量时指定初始值;
使用静态初始化块为类变量指定初始值;
public class Test {
//声明变量a时指定初始值
static int a = 5;
static int b;
static int c;//默认初始值为0
static {
//使用静态初始化块为变量b指定初始值
b = 6;
}
}
5.类初始化的时机
当使用ClassLoader类的loadClass方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化,使用Class的forName静态方法才会导致强制初始化该类。
class Tester
{
static
{
System.out.println("Tester类的静态初始化块...");
}
}
public class ClassLoaderTest
{
public static void main(String[] args)
throws ClassNotFoundException
{
ClassLoader cl = ClassLoader.getSystemClassLoader();
// 下面语句仅仅是加载Tester类
cl.loadClass("Tester");
System.out.println("系统加载Tester类");
// 下面语句才会初始化Tester类
Class.forName("Tester");
}
}
二.类加载器
类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中,并为之生成对应的java.lang.Class对象。
1.类加载器简介
当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构
Bootstrap ClassLoader:根类加载器
他负责加载Java的核心类,
public class BootstrapTest
{
public static void main(String[] args)
{
// 获取根类加载器所加载的全部URL数组
URL[] urls = sun.misc.Launcher.
getBootstrapClassPath().getURLs();
// 遍历、输出根类加载器加载的全部URL
for (int i = 0; i < urls.length; i++)
{
System.out.println(urls[i].toExternalForm());
}
}
}
Extension ClassLoader: 扩展类加载器
负责加载JRE的扩展目录
System ClassLoader:系统类加载器
负责在JVM启动时加载来自java命令的-classpath选项,java.class.path系统属性。
2.创建并使用自定义的类加载器
JVM中除根类加载器之外的所有类加载器都是ClassLoader子类的实例,开发者可以通过扩展ClassLoader子类,并重写该ClassLoader所包含的方法来实现自定义的类加载器。
3.URLClassLoader类
Java为ClassLoader提供了一个URLClassLoader实现类,该类也是系统类加载器和扩展类加载器的父类,功能比较强大,可以从本地文件系统获取二进制文件来加载类,也可以从远处主机获取二进制文件来加载类。
三.通过反射查看类信息
1.获得Class对象
每个类被加载之后,系统就会为该类生成一个对应的Class对象,通过该Class对象就可以访问到JVM中的这个类,Java程序
中获得Class对象通常有如下三种方式。
1>使用Class类的forName(String clazzName)静态方法,
2>调用某个类的class属性来获取该类对应的Class对象;如Person.class将会返回Person类对应的Class对象;
3>调用某个对象的getClass方法。
一旦获得了某个类所对应的Class对象之后,程序就可以调用Class对象的方法来获得该对象和该类的真实信息了。
2.从Class中获取信息
// 定义可重复注解
@Repeatable(Annos.class)
@interface Anno {}
@Retention(value=RetentionPolicy.RUNTIME)
@interface Annos {
Anno[] value();
}
// 使用4个注解修饰该类
@SuppressWarnings(value="unchecked")
@Deprecated
// 使用重复注解修饰该类
@Anno
@Anno
public class ClassTest
{
// 为该类定义一个私有的构造器
private ClassTest()
{
}
// 定义一个有参数的构造器
public ClassTest(String name)
{
System.out.println("执行有参数的构造器");
}
// 定义一个无参数的info方法
public void info()
{
System.out.println("执行无参数的info方法");
}
// 定义一个有参数的info方法
public void info(String str)
{
System.out.println("执行有参数的info方法"
+ ",其str参数值:" + str);
}
// 定义一个测试用的内部类
class Inner
{
}
public static void main(String[] args)
throws Exception
{
// 下面代码可以获取ClassTest对应的Class
Class<ClassTest> clazz = ClassTest.class;
// 获取该Class对象所对应类的全部构造器
Constructor[] ctors = clazz.getDeclaredConstructors();
System.out.println("ClassTest的全部构造器如下:");
for (Constructor c : ctors)
{
System.out.println(c);
}
// 获取该Class对象所对应类的全部public构造器
Constructor[] publicCtors = clazz.getConstructors();
System.out.println("ClassTest的全部public构造器如下:");
for (Constructor c : publicCtors)
{
System.out.println(c);
}
// 获取该Class对象所对应类的全部public方法
Method[] mtds = clazz.getMethods();
System.out.println("ClassTest的全部public方法如下:");
for (Method md : mtds)
{
System.out.println(md);
}
// 获取该Class对象所对应类的指定方法
System.out.println("ClassTest里带一个字符串参数的info()方法为:"
+ clazz.getMethod("info" , String.class));
// 获取该Class对象所对应类的上的全部注解
Annotation[] anns = clazz.getAnnotations();
System.out.println("ClassTest的全部Annotation如下:");
for (Annotation an : anns)
{
System.out.println(an);
}
System.out.println("该Class元素上的@SuppressWarnings注解为:"
+ Arrays.toString(clazz.getAnnotationsByType(SuppressWarnings.class)));
System.out.println("该Class元素上的@Anno注解为:"
+ Arrays.toString(clazz.getAnnotationsByType(Anno.class)));
// 获取该Class对象所对应类的全部内部类
Class<?>[] inners = clazz.getDeclaredClasses();
System.out.println("ClassTest的全部内部类如下:");
for (Class c : inners)
{
System.out.println(c);
}
// 使用Class.forName方法加载ClassTest的Inner内部类
Class inClazz = Class.forName("ClassTest$Inner");
// 通过getDeclaringClass()访问该类所在的外部类
System.out.println("inClazz对应类的外部类为:" +
inClazz.getDeclaringClass());
System.out.println("ClassTest的包为:" + clazz.getPackage());
System.out.println("ClassTest的父类为:" + clazz.getSuperclass());
}
}
3.Java8新增的方法参数反射
Java8在java.lang.reflect包下新增了一个Executable抽象基类,该对象代表可执行的类成员,该类派生了Constructor,Method两个子类。
四.使用反射生成并操作对象
1.创建对象
通过反射来生成对象有如下两种方式
public class ObjectPoolFactory
{
// 定义一个对象池,前面是对象名,后面是实际对象
private Map<String ,Object> objectPool = new HashMap<>();
// 定义一个创建对象的方法,
// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject(String clazzName)
throws InstantiationException
, IllegalAccessException , ClassNotFoundException
{
// 根据字符串来获取对应的Class对象
Class<?> clazz = Class.forName(clazzName);
// 使用clazz对应类的默认构造器创建实例
return clazz.newInstance();
}
// 该方法根据指定文件来初始化对象池,
// 它会根据配置文件来创建对象
public void initPool(String fileName)
throws InstantiationException
, IllegalAccessException ,ClassNotFoundException
{
try(
FileInputStream fis = new FileInputStream(fileName))
{
Properties props = new Properties();
props.load(fis);
for (String name : props.stringPropertyNames())
{
// 每取出一对key-value对,就根据value创建一个对象
// 调用createObject()创建对象,并将对象添加到对象池中
objectPool.put(name ,
createObject(props.getProperty(name)));
}
}
catch (IOException ex)
{
System.out.println("读取" + fileName + "异常");
}
}
public Object getObject(String name)
{
// 从objectPool中取出指定name对应的对象。
return objectPool.get(name);
}
public static void main(String[] args)
throws Exception
{
ObjectPoolFactory pf = new ObjectPoolFactory();
pf.initPool("obj.txt");
System.out.println(pf.getObject("a")); // ①
System.out.println(pf.getObject("b")); // ②
}
}
如果不想利用默认的构造器来创建Java对象,而想利用指定的构造器来创建Java对象,则需要利用Constructor对象,每个Constructor对应一个构造器,为了利用指定的构造器来创建Java对象,需要
1>获得该类的Class对象;
2>利用Class对象的getConstructor方法来获取指定的构造器;
3>调用Constructor的newInstance方法来创建Java对象;
public class CreateJFrame
{
public static void main(String[] args)
throws Exception
{
// 获取JFrame对应的Class对象
Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
// 获取JFrame中带一个字符串参数的构造器
Constructor ctor = jframeClazz
.getConstructor(String.class);
// 调用Constructor的newInstance方法创建对象
Object obj = ctor.newInstance("测试窗口");
// 输出JFrame对象
System.out.println(obj);
}
}
2.调用方法
当获得某个类对应的Class对象后,就可以通过该Class对象的getMethods方法或者getMethod方法来获取全部方法或指定方法。这两个方法的返回值是Method数组,或Method对象。
每个Method对象对应一个方法,获得Method对象后,程序就可通过该Method来调用它对应的方法,在Method里包含一个invoke方法。
public class ExtendedObjectPoolFactory
{
// 定义一个对象池,前面是对象名,后面是实际对象
private Map<String ,Object> objectPool = new HashMap<>();
private Properties config = new Properties();
// 从指定属性文件中初始化Properties对象
public void init(String fileName)
{
try(
FileInputStream fis = new FileInputStream(fileName))
{
config.load(fis);
}
catch (IOException ex)
{
System.out.println("读取" + fileName + "异常");
}
}
// 定义一个创建对象的方法,
// 该方法只要传入一个字符串类名,程序可以根据该类名生成Java对象
private Object createObject(String clazzName)
throws InstantiationException
, IllegalAccessException , ClassNotFoundException
{
// 根据字符串来获取对应的Class对象
Class<?> clazz =Class.forName(clazzName);
// 使用clazz对应类的默认构造器创建实例
return clazz.newInstance();
}
// 该方法根据指定文件来初始化对象池,
// 它会根据配置文件来创建对象
public void initPool()throws InstantiationException
,IllegalAccessException , ClassNotFoundException
{
for (String name : config.stringPropertyNames())
{
// 每取出一对key-value对,如果key中不包含百分号(%)
// 这就标明是根据value来创建一个对象
// 调用createObject创建对象,并将对象添加到对象池中
if (!name.contains("%"))
{
objectPool.put(name ,
createObject(config.getProperty(name)));
}
}
}
// 该方法将会根据属性文件来调用指定对象的setter方法
public void initProperty()throws InvocationTargetException
,IllegalAccessException,NoSuchMethodException
{
for (String name : config.stringPropertyNames())
{
// 每取出一对key-value对,如果key中包含百分号(%)
// 即可认为该key用于控制调用对象的setter方法设置值,
// %前半为对象名字,后半控制setter方法名
if (name.contains("%"))
{
// 将配置文件中key按%分割
String[] objAndProp = name.split("%");
// 取出调用setter方法的参数值
Object target = getObject(objAndProp[0]);
// 获取setter方法名:set + "首字母大写" + 剩下部分
String mtdName = "set" +
objAndProp[1].substring(0 , 1).toUpperCase()
+ objAndProp[1].substring(1);
// 通过target的getClass()获取它实现类所对应的Class对象
Class<?> targetClass = target.getClass();
// 获取希望调用的setter方法
Method mtd = targetClass.getMethod(mtdName , String.class);
// 通过Method的invoke方法执行setter方法,
// 将config.getProperty(name)的值作为调用setter的方法的参数
mtd.invoke(target , config.getProperty(name));
}
}
}
public Object getObject(String name)
{
// 从objectPool中取出指定name对应的对象。
return objectPool.get(name);
}
public static void main(String[] args)
throws Exception
{
ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
epf.init("extObj.txt");
epf.initPool();
epf.initProperty();
System.out.println(epf.getObject("a"));
}
}
3.访问成员变量值
通过Class对象的getFields或getField方法可以获取该类所包括的全部成员变量或指定成员变量。
class Person
{
private String name;
private int age;
public String toString()
{
return "Person[name:" + name +
" , age:" + age + " ]";
}
}
public class FieldTest
{
public static void main(String[] args)
throws Exception
{
// 创建一个Person对象
Person p = new Person();
// 获取Person类对应的Class对象
Class<Person> personClazz = Person.class;
// 获取Person的名为name的成员变量
// 使用getDeclaredField()方法表明可获取各种访问控制符的成员变量
Field nameField = personClazz.getDeclaredField("name");
// 设置通过反射访问该成员变量时取消访问权限检查
nameField.setAccessible(true);
// 调用set()方法为p对象的name成员变量设置值
nameField.set(p , "Yeeku.H.Lee");
// 获取Person类名为age的成员变量
Field ageField = personClazz.getDeclaredField("age");
// 设置通过反射访问该成员变量时取消访问权限检查
ageField.setAccessible(true);
// 调用setInt()方法为p对象的age成员变量设置值
ageField.setInt(p , 30);
System.out.println(p);
}
}
4.操作数组
在java.lang.reflect包下还提供了一个Array类,Array对象可以代表所有的数组,
public class ArrayTest1
{
public static void main(String args[])
{
try
{
// 创建一个元素类型为String ,长度为10的数组
Object arr = Array.newInstance(String.class, 10);
// 依次为arr数组中index为5、6的元素赋值
Array.set(arr, 5, "疯狂Java讲义");
Array.set(arr, 6, "轻量级Java EE企业应用实战");
// 依次取出arr数组中index为5、6的元素的值
Object book1 = Array.get(arr , 5);
Object book2 = Array.get(arr , 6);
// 输出arr数组中index为5、6的元素
System.out.println(book1);
System.out.println(book2);
}
catch (Throwable e)
{
System.err.println(e);
}
}
}
public class ArrayTest2
{
public static void main(String args[])
{
/*
创建一个三维数组。
根据前面介绍数组时讲的:三维数组也是一维数组,
是数组元素是二维数组的一维数组,
因此可以认为arr是长度为3的一维数组
*/
Object arr = Array.newInstance(String.class, 3, 4, 10);
// 获取arr数组中index为2的元素,该元素应该是二维数组
Object arrObj = Array.get(arr, 2);
// 使用Array为二维数组的数组元素赋值。二维数组的数组元素是一维数组,
// 所以传入Array的set()方法的第三个参数是一维数组。
Array.set(arrObj , 2 , new String[]
{
"疯狂Java讲义",
"轻量级Java EE企业应用实战"
});
// 获取arrObj数组中index为3的元素,该元素应该是一维数组。
Object anArr = Array.get(arrObj, 3);
Array.set(anArr , 8 , "疯狂Android讲义");
// 将arr强制类型转换为三维数组
String[][][] cast = (String[][][])arr;
// 获取cast三维数组中指定元素的值
System.out.println(cast[2][3][8]);
System.out.println(cast[2][2][0]);
System.out.println(cast[2][2][1]);
}
}