`
tang9140
  • 浏览: 34580 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

java能不能自己写一个类叫java.lang.System/String正确答案

 
阅读更多

最近学习了下java类加载相关的知识。然后看到网上有一道面试题是

能不能自己写个类叫java.lang.System?

网上提供的答案:通常不可以,但可以采取另类方法达到这个需求。所谓的另类方法指自己写个类加载器来加载java.lang.System达到目的。

首先表明下我的观点。上述答案完全是误导读者,是不正确的答案。我就纳闷了网上怎么把这种完全不正确的搜索结果排在前面,而且几乎搜到的都是这种不正确的答案。可能很多不明真相的朋友就这么被误导了,所以还是希望大家对网上的内容先持怀疑态度为好。下面详细说明为什么。

首先,摘抄网上错误答案的详细解释

“为了不让我们写System类,类加载采用委托机制,这样可以保证爸爸们优先,爸爸们能找到的类,儿子就没有机会加载。而System类是Bootstrap加载器加载的,就算自己重写,也总是使用Java系统提供的System,自己写的System类根本没有机会得到加载。
但是,我们可以自己定义一个类加载器来达到这个目的,为了避免双亲委托机制,这个类加载器也必须是特殊的。由于系统自带的三个类加载器都加载特定目录下的类,如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。”

然后,说明下上面解释中提到的一些概念

类加载器可分为两类:一是启动类加载器(Bootstrap ClassLoader),是C++实现的,是JVM的一部分;另一种是其它的类加载器,是Java实现的,独立于JVM,全部都继承自抽象类java.lang.ClassLoader。jdk自带了三种类加载器,分别是启动类加载器(Bootstrap ClassLoader),扩展类加载器(Extension ClassLoader),应用程序类加载器(Application ClassLoader)。后两种加载器是继承自抽象类java.lang.ClassLoader。关于这三种加载器各自的作用这里不做详细说明,有兴趣的可以自己了解下。

类加载器是有层次的

一般是: 自定义类加载器 >> 应用程序类加载器 >> 扩展类加载器 >> 启动类加载器

上面的层次关系被称为双亲委派模型(Parents Delegation Model)。除了最顶层的启动类加载器外,其余的类加载器都有对应的父类加载器。

再简单说下双亲委托机制:如果一个类加载器收到了类加载的请求,它首先不会自己尝试去加载这个类,而是把这个请求委派给父类加载器,每一个层次的类加载器都是加此,因此所有的加载请求最终到达顶层的启动类加载器,只有当父类加载器反馈自己无法完成加载请求时(指它的搜索范围没有找到所需的类),子类加载器才会尝试自己去加载。

再回去看下解释内容,我相信前面的部分大家应该很看懂了,也没什么大问题。最后的如果部分“如果我们自己的类加载器放在一个特殊的目录,那么系统的加载器就无法加载,也就是最终还是由我们自己的加载器加载。” 我就不明白所以了,逻辑完全不通。我想它的本意可能是,将自己的java.lang.System类放置在特殊目录,然后系统自带的加载器无法加载,这样最终还是由我们自己的加载器加载(因为我们自己的加载器知道其所在的特殊目录)。这种说法好像逻辑上没有问题,那么我们就来实验下了。

代码验证

测试类结构及内容如下:


public class MyClassLoader extends ClassLoader{
    
    public MyClassLoader() {
        super(null);
    }
    
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try{
            String className = null;
            if(name.startsWith("java.lang")){
                className = "/" + name.replace('.', '/') + ".class";
            }else{
                className = name.substring(name.lastIndexOf('.') + 1) + ".class";
            }
            System.out.println(className);
            InputStream is = getClass().getResourceAsStream(className);
            System.out.println(is);
            if(is == null)
                return super.loadClass(name);
            
            byte[] b = new byte[is.available()];
            is.read(b);
            return defineClass(name, b, 0, b.length);
        }catch (Exception e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }
}
public class ClassLoaderTest {

    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        ClassLoader myLoader = new MyClassLoader();
        Object obj = myLoader.loadClass("java.lang.Math").newInstance();
        System.out.println(obj);
    }
    
}
public final class Math {
    
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}
public class MyMath {
    
    public static void main(String[] args) {
        System.out.println("hello world");
    }
}
上面的测试代码没用自定义java.lang.System类,因为测试代码用到了JDK自带的System类进行输出打印,会冲突,所以改用为自定义的java.lang.Math类。如果自定义的Math类能加载,那么自定义的System类同样能加载。

我们先直接运行下Math类,输出如下:

java.lang.NoSuchMethodError: main
Exception in thread "main"

提示Math类没有main方法。首先大家要明白一个概念,当类首次主动使用时,必须进行类的加载,这部分工作是由类加载器来完成的。根据双亲委托原则,Math类首先由启动类加载器去尝试加载,很显然,它找到rt.jar中的java.lang.Math类并加载进内存(并不会加载我们自定义的Math类),然后执行main方法时,发现不存在该方法,所以报方法不存在错误。也就是说,默认情况下JVM不会加载我们自定义的Math类。

再直接运行MyMath类,输出如下:

java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:479)

at java.lang.ClassLoader.defineClassCond(ClassLoader.java:625)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
Exception in thread "main"

注意红色部分的内容。由堆栈异常信息可知道,当应用程序类加载器类(AppClassLoader)尝试加载MyMath类时,ClassLoader.java的479行抛出了SecurityException

禁止使用包名:java.lang。

直接查看抽象类java.lang.ClassLoader的preDefineClass方法代码,摘抄如下:

    private ProtectionDomain preDefineClass(String name,
					    ProtectionDomain protectionDomain)
    {
	if (!checkName(name))
	    throw new NoClassDefFoundError("IllegalName: " + name);

	if ((name != null) && name.startsWith("java.")) {
	    throw new SecurityException("Prohibited package name: " +
					name.substring(0, name.lastIndexOf('.')));
	}
	if (protectionDomain == null) {
	    protectionDomain = getDefaultDomain();
	}

	if (name != null)
	    checkCerts(name, protectionDomain.getCodeSource());

	return protectionDomain;
    }

可以看到如果加载的类全名称以“java.”开头时,将会抛出SecurityException,这也是为什么直接执行MyMath类会出现SecurityException。

照这样,我们自定义的类加载器必须继承自ClassLoader,其loadClass()方法里调用了父类的defineClass()方法,并最终调到preDefineClass()方法,因此我们自定义的类加载器也是不能加载以“java.”开头的java类的。我们继续运行下ClassLoaderTest类,输出如下:

/java/lang/Math.class
sun.net.www.protocol.jar.JarURLConnection$JarURLInputStream@a981ca
java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:479)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:625)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
at java.lang.ClassLoader.defineClass(ClassLoader.java:465)
at com.tq.MyClassLoader.loadClass(MyClassLoader.java:28)
at com.tq.ClassLoaderTest.main(ClassLoaderTest.java:8)
Exception in thread "main" java.lang.ClassNotFoundException
at com.tq.MyClassLoader.loadClass(MyClassLoader.java:31)
at com.tq.ClassLoaderTest.main(ClassLoaderTest.java:8)

红色部分清楚表明,也是在preDefineClass方法中抛出的SecurityException。

通过代码实例及源码分析可以看到,对于自定义的类加载器,强行用defineClass()方法去加载一个以"java."开头的类也是会抛出异常的。

总结

不能自己写以"java."开头的类,其要么不能加载进内存,要么即使你用自定义的类加载器去强行加载,也会收到一个SecurityException。

码字不易,源头来自http://blog.csdn.net/tang9140。

那些抓取网上内容,放在自家网站上的请自觉。百度搜索你为啥老把原创网页排后面呢,请反思,不要只想着赚钱。



版权声明:本文为博主原创文章,未经博主允许不得转载。

分享到:
评论

相关推荐

    java.lang.NumberFormatException For inputstring 4294967295处理方法

    在Java编程中,`java.lang.NumberFormatException`是一个常见的运行时异常,它通常发生在尝试将一个字符串转换为数值类型(如int、long、float或double)时,但该字符串无法被解析为有效的数值。"For input string: ...

    java.lang包介绍

    1. **Cloneable**: 标识一个类的实例可以被克隆。 2. **Comparable**: 接口定义了`compareTo()`方法,使得类的对象可以进行比较。 3. **Runnable**: 实现此接口的类的实例可以作为线程的目标对象。 在Java 2(J2SE...

    串口操作组建及示例(串口 java )

    2. 使用`CommPortIdentifier`获取可用的串口列表,根据需要选择一个合适的串口。 3. 打开串口,通过`CommPortIdentifier.open()`方法创建一个`SerialPort`实例。 4. 配置串口参数,如波特率、数据位、停止位和校验位...

    java rt.jar包

    - `java.lang`: 包含了所有Java程序都默认导入的基本类,如`Object`、`String`、`System`和`Exception`。 - `java.util`: 提供了各种工具类和集合框架,如`ArrayList`、`HashMap`、`Date`和`Calendar`。 - `java....

    java用类实现异常的处理的实例

    import java.util.*; class MyException extends Exception{ String message; MyException(int age){ message=age+"年龄异常"; } public String getMessage(){ return message; } } class People{ ...

    java中String类型转换方法.pdf

    Java 中 String 类型转换方法是一种常见的编程操作,涉及到各种数据类型之间的转换,包括整型、浮点型、长整型、浮点型等。本文将详细介绍 Java 中 String 类型转换方法的各种形式。 整型到字符串 在 Java 中,将...

    jboss-exp.rar

    某大牛写的jboss-exp 1. 查看系统名称 java -jar jboss_exploit_fat.jar -i http://192.168.7.84:10081/invoker/JMXInvokerServlet get jboss.system:type=ServerInfo OSName 2. 查看系统版本 java -jar jboss_...

    jboss_exploit_fat

    1. 查看系统名称 ...java.lang.String;java.lang.String;java.lang.String;java.lang.Boolean $content$内容是同目录下的shell.jsp文件内容 即:将shell.jsp文件上传到服务器上,最终部署的war访问: ...

    jboss xml漏洞exp

    1. 查看系统名称 ...java.lang.String;java.lang.String;java.lang.String;java.lang.Boolean $content$内容是同目录下的shell.jsp文件内容 即:将shell.jsp文件上传到服务器上,最终部署的war访问: ...

    支支持处理asp的web service调用

    &lt;item name="bBOOLsmNationalRoam" type="java.lang.String" /&gt; &lt;/return&gt; &lt;/operation&gt; &lt;!-- OM模型 调用web service ,action 为soapaction 参见wsdl--&gt; namespace=...

    java.lang.ExceptionInInitializerError异常的解决方法

    以下是一个复现java.lang.ExceptionInInitializerError异常的示例程序: ```java package com.lang.ininitialException; import java.util.ArrayList; import java.util.List; public class StaticParamsSingle {...

    java_lang包详解.pdf

    String类是另一个重要的成员,它代表不可变的字符序列,广泛用于文本处理。String类提供了许多操作字符串的方法,如concat()、indexOf()、substring()等,使得字符串的处理变得方便。 Character类提供了处理Unicode...

    opencv_java340-x64.dll

    java 调用 opencv处理图像时所需的dll文件 将文件放在工程根目录...如果不添加,会报错 Exception in thread "main" java.lang.UnsatisfiedLinkError: org.opencv.imgcodecs.Imgcodecs.imread_1(Ljava/lang/String;)J

    Java通过Apache提供的Commons Email工具类实现邮箱发送验证码

    Apache Commons Email库为Java开发者提供了一个便捷的方式来实现这个功能。本篇将深入讲解如何使用Apache Commons Email工具类来发送带有验证码的电子邮件。 首先,Apache Commons Email是Apache软件基金会的一个...

    java.lang研究

    `String`类是另一个非常重要的类,它代表不可变的字符序列。`String`类提供了大量的方法,用于字符串的操作,如拼接、查找、替换、分割等。`StringBuffer`类则提供线程安全的字符串操作,适用于多线程环境中的字符串...

    java面试题--反射机制

    - `java.lang.Class`:表示一个类的运行时信息。 - `java.lang.reflect.Method`:表示类的方法。 - `java.lang.reflect.Field`:表示类的字段。 - `java.lang.reflect.Constructor`:表示类的构造器。 #### 五、`...

    org.xbill.DNS_java工具类.

    在Java编程领域,有一个强大的库——`org.xbill.DNS`,它提供了一套完整的DNS(Domain Name System)操作工具类,帮助开发者处理DNS相关的各种任务。这个库是Java DNS解析器和更新器的实现,为开发者提供了灵活且...

    java 数组下标越界异常(ArrayIndexOutOfBoundsException)

    当你尝试访问数组的一个不存在的位置,即下标超出数组实际长度的范围时,就会抛出一个`ArrayIndexOutOfBoundsException`。这个异常属于`java.lang.IndexOutOfBoundsException`类的子类,是Java运行时系统在检测到...

    java 判断对象是否为空

    在Java编程中,判断对象是否为空是一个常见的操作,特别是在处理数据结构、进行异常处理或确保方法参数正确性时。本文将深入探讨如何有效地检查对象是否为空,并提供相关的工具类和源码分析。 首先,Java中对象的...

Global site tag (gtag.js) - Google Analytics