Fork me on GitHub
0%

对象和数组的创建简单实践

在《Java 中对象和数组的创建》一文中提到了很多 JVM 的理论知识,而且里面可能还有很多关联的 JVM 知识点没有介绍到,所以可能初次看起来都比较抽象,但是没关系,先硬着头皮坚持看下去,对这些东西有个初步的印象,等到后面关联的这些知识点都提到了之后你再回过头来看可能就会有种打通任督二脉的感觉了。这里为了加深一下这些理论知识的印象,下面就来看下上一篇文章中提到的有关理论知识的简单实践和验证。

加载加密之后的 Class 文件

关于类的加载过程中加载阶段,通过上篇文章中我们已经知道是需要借助类加载器来完成的,而类加载器除了 Java 提供的这些类加载器,我们也可以自定义我们自己的类加载器。怎么来实现呢,下面就来看下如何自定义一个类加载器来加载经过 Base64 加密之后的 Class 文件,直接上代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class HelloClassLoader1 extends ClassLoader{

@Override
protected Class<?> findClass(String name) {
byte[] bytes = getClassBytes();
if(bytes.length == 0){
throw new IllegalArgumentException("the provided byte file is illegal");
}
return defineClass(name, bytes, 0, bytes.length);
}

private byte[] getClassBytes(){
// Hello 类编译后的 class 文件加密之后的字符串
try(FileInputStream fileInputStream = new FileInputStream(new File("java-basic-sample/src/main/resources/Base64Hello.blass"))) {
// 替换掉最后生成的换行符,否则解密可能会失败
String str = new String(fileInputStream.readAllBytes(), StandardCharsets.UTF_8).replaceAll("[\r\n]","");
// 解密
return Base64.getDecoder().decode(str.getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
e.printStackTrace();
}
return new byte[0];
}
}

自定义类加载器首先继承 ClassLoader 类,然后重写 findClass 方法,在该方法中读取我们加密好的 class 文件,然后进行解密再将解密之后的字节数组作为参数调用 defineClass 方法即可。

这里有个替换换行符的操作,主要是因为我在 Mac 终端下使用 Base64 命令加密生成的文件最后一个换行,导致解密时失败,所以先将换行符替换掉再进行解密。上面代码中我加密的 Hello 类代码如下:

1
2
3
4
5
6
public class Hello {

public void sayHello(){
System.out.println("Hello World");
}
}

编译该类生成 Hello.class 文件,然后再加密生成 Base64Hello.blass 文件,命令如下:

1
2
javac net/gittab/basic/jvm/practice/Hello.java
Base64 -i Hello.class -o Base64Hello.blass

测试的 main 方法如下:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
HelloClassLoader classLoader = new HelloClassLoader();
Class<?> helloClass = classLoader.findClass("net.gittab.basic.jvm.practice.Hello");
try {
Object hello = helloClass.getDeclaredConstructor().newInstance();
Method method = helloClass.getDeclaredMethod("sayHello");
method.invoke(hello);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {
e.printStackTrace();
}
}

测试代码中主要就是创建自定义的类加载器对象,加载 Hello 类,创建 Hello 对象,最后调用 Hello 类中的 sayHello 方法打印出 Hello World。

类的初始化顺序简单验证

先准备两个类,一个 Parent 类和一个 Sub 类,其中 Sub 类继承 Parent 类,两个类的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Parent {

public static int parent = 1;

public static int getParent(){
return parent;
}

static {
System.out.println("parent");
}

}
1
2
3
4
5
6
7
8
public class Sub extends Parent {

public static int sub = 2;

static {
System.out.println("sub");
}
}
  • 通过 new 创建对象时会触发类的初始化

    1
    2
    3
    public static void main(String[] args) {
    System.out.println(new Parent());
    }

    测试结果:打印 parent 字符串。

  • 访问类的静态变量或者调用静态方法会触发类的初始化

    1
    2
    3
    4
    public static void main(String[] args) {
    System.out.println(Parent.parent);
    // System.out.println(Parent.getParent());
    }

    测试结果:打印 parent 字符串。

  • 子类的初始化会触发父类的初始化

    1
    2
    3
    public static void main(String[] args) {
    System.out.println(Sub.sub);
    }

    测试结果:先打印 parent,再打印 sub,最后输出 2。

  • 通过子类调用父类中的静态变量则只会触发父类初始化

    还是利用上面的父子类,测试代码如下:

    1
    2
    3
    public static void main(String[] args) {
    System.out.println(Sub.parent);
    }

    测试结果:只会先打印 parent,再输出 1,而不会打印 sub 出来。

  • 通过反射创建类的对象会触发该类的初始化

    1
    2
    3
    4
    public static void main(String[] args) {
    Class<Parent> clazz = Parent.class;
    Object object = clazz.getDeclaredConstructor().newInstance();
    }

    测试结果:打印 parent 字符串。

  • 调用类中的静态常量不会触发该类的初始化

    1
    2
    3
    public static void main(String[] args) {
    System.out.println(Parent.FINAL_PARENT);
    }

    测试结果:打印输出 2。

  • 定义指定元素类型的数组不会触发该元素类的初始化

    1
    2
    3
    public static void main(String[] args) {
    Parent[] parents = new Parent[1];
    }

    测试结果:没有任何输出

上面给了一个自定义类加载器的例子,加载经过 Base64 加密之后的 Class 文件,然后再通过加载的类创建对象的过程,然后是对于类加载过程中的初始化顺序的验证,根据类的初始化会执行静态代码块的特性验证了一些关于类的初始化的触发规则,通过这些例子可以让我们加深对类加载过程的印象同时也对类加载有进一步的认识。

 wechat
扫描上面图中二维码关注微信公众号