为了验证java的双亲委派模型模型,小编我决定自己做个试验。
包名和java自身String所在的java.lang包名不一致时的情况
首先,我们自定义一个包名不是java.lang的String(注意如果包名不是java.lang直接将java自定的String拷贝过来会报错,所以为了省去麻烦,此处小编没有使用从官方拷贝过来的String)
package com.school.eution.accommodation;
public final class String {
/** The value is used for character storage. */
private final char value[] = {};
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
public String(int hash) {
this.hash = hash;
}
public String(){
}
static{
System.out.println("静态--自定义String");
}
{
System.out.println("动态--自定义String");
}
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
}
测试代码中自定义了自己的类加载器,去加载自定义com.school.eution.accommodation.String
package com.school.eution.accommodation;
import java.io.IOException;
import java.io.InputStream;
public class Test {
public static void main(java.lang.String[] args) {
ClassLoader myLoad = new ClassLoader() {
@Override
public Class<?> loadClass(java.lang.String name)throws ClassNotFoundException {
java.lang.String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (null == is) {
return super.loadClass(name);
}
try {
byte[] b= new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
};
try {
Object obj = myLoad.loadClass("com.school.eution.accommodation.String").newInstance();
System.out.println(obj.getClass());
// 这个obj使用的是自定义的classLoad 与 虚拟机自带的不是一个类加载器,所以返回false
System.out.println(obj instanceof String);
System.out.println();
} catch (InstantiationException | IllegalAccessException
| ClassNotFoundException e) {
e.printStackTrace();
}
}
}
输出结果:
静态--自定义String
动态--自定义String
class com.school.eution.accommodation.String
false
分析:小编使用了自定义的类加载器,如果自定义String和java自带的String不在同一个包下是可以加载的,而且我们看到了自定义String代码块的输出。
自定义String包名也叫java.lang时的情况
1、自定义String在自己起的名字也叫java.lang包下,测试用例在另一个包下
自定义String中加了代码块标识(这里之所以使用从官方拷贝来的String,是因为编写自己的类加载器的时候会用到String中的一些方法,所以使用的从官方拷贝过来的String):
测试用例:
总结:发现测试结果中并没有打印出自己String代码块中的标识。原因就在于双亲委派机制,用户自定义的加载器会将加载请求向上委派给父类加载器,如果父类加载器还有自己的父加载器,就继续向上委派。最终会由最顶层的加载器执行本次请求。顶层的加载器就是启动类加载器(Bootstrap ClassLoader),启动类加载器会在自己的加载范围内(比如rt.jar包)下查找java.lang.String(原本的String就是在rt.jar包下)。此时在rt.jar包下找到了java.lang.String进行加载,所以自己写的String虽然也在自定的包java.lang下也没用呀。除非父类加载器在自己的加载范围内找不到要加载的类,才会反过来向下让子类加载器加载。
2、测试用例和自定义String都在java.lang包下
首先,拷贝java自带的String放到自己的java.lang包下,然后在自己拷贝的String中加上代码块标识,以便测试的时候能和java自带的String区别开来。(这里之所以使用从官方拷贝来的String,是因为编写自己的类加载器的时候会用到String中的一些方法,所以使用的从官方拷贝过来的String)
package java.lang;
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
static {
System.out.println("静态代码块--自定义String");
}
{
System.out.println("一般代码块--自定义String");
}
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
/**
* Initializes a newly created {@code String} object so that it represents
* an empty character sequence. Note that use of this constructor is
* unnecessary since Strings are immutable.
*/
public String() {
this.value = "".value;
}
//由于String代码太多,此处省略
// ....
}
测试用例:
package java.lang;
import java.io.IOException;
import java.io.InputStream;
/**
* 类加载器与instanceof关键字演示
*
* @author pocher
*/
public class ClassLoaderTest {
public static void main(String[] args) {
ClassLoader myLoad = new ClassLoader() {
@Override
public Class<?> loadClass(String name)throws ClassNotFoundException {
String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (null == is) {
return super.loadClass(name);
}
try {
byte[] b= new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
};
try {
Object obj = myLoad.loadClass("java.lang.String").newInstance();
System.out.println(obj.getClass());
// 这个obj使用的是自定义的classLoad 与 虚拟机自带的不是一个类加载器,所以返回false
System.out.println(obj instanceof ClassLoaderTest);
System.out.println();
} catch (InstantiationException | IllegalAccessException
| ClassNotFoundException e) {
e.printStackTrace();
}
}
}
测试结果:
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:662)
at java.lang.ClassLoader.defineClass(ClassLoader.java:761)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:338)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
Error: A JNI error has occurred, please check your installation and try again
Exception in thread "main" Disconnected from the target VM, address: '127.0.0.1:56457', transport: 'socket'
点击第一行的错误发现是这样官方定义的:这个包名受保护。
/* Determine protection domain, and check that:
- not define java.* class,
- signer of this class matches signers for the rest of the classes in
package.
*/
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
总结:自己虽然可以自定义一个叫java.lang.String的类的,但是因为java会检查包名,如果在java.lang包下自定义一个String类,并在改包下启动测试不管有没有用到String,只要在自定义的java.lang包下启动就会报“java.lang.SecurityException: Prohibited package name: java.lang”的错。但是如果启动类和自定义String不在同一个包下是可以的。但是这样的话,除了类名和java自带的String一样外,就和普通类没啥区别了。就算代码完全和官方的String代码一直,但由于双亲委派模型的存在也没有任何鸟用呀,又不会加载自定的String(即便改自定义String的包名也叫java.lang)