Java 知识点总结之Java 基本API(三)

12、Class.forName和ClassLoader.loadClass的区别

答:(1)class.forName()前者除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块和静态变量。

         Class.forName(name,initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象。

(2)classLoader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容和和静态变量,只有在newInstance才会去执行static块和静态变量

public class Line {
    static{
        System.out.println("执行静态块");
    }

    public static String aa = getString();

    private static String getString() {
        System.out.println("执行静态方法");
        return "静态方法";
    }

    public Line() {
        System.out.println("执行构造函数");
    }
}

public class ClassloaderAndFornameTest {

    /**
     * Class.forName默认会执行static块和静态变量
     */
    @Test
    public void testClassForName1() {
        try {
            Class.forName(Line.class.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * Class.forName(name, initialize, loader)带参函数也可控制是否加载static块和静态变量。
     */
    @Test
    public void testClassForName2() {
        try {
            Class.forName(Line.class.getName(), false, ClassLoader.getSystemClassLoader());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * ClassLoader不会执行static块和和静态变量
     */
    @Test
    public void testClassLoader() {
        try {
            ClassloaderAndFornameTest.class.getClassLoader().loadClass(Line.class.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

13、ClassLoader加载类的原理

双亲委派模型:在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

为什么要使用这种双亲委托模式?(1)避免重复加载(2)考虑到安全因素

protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException{  
         // 首先检查该name指定的class是否有被加载  
         Class c = findLoadedClass(name);  
         if (c == null) {  
             try {  
                 if (parent != null) {  
                     //如果parent不为null,则调用parent的loadClass进行加载  
                     c = parent.loadClass(name, false);  
                 }else{  
                     //parent为null,则调用BootstrapClassLoader进行加载  
                     c = findBootstrapClass0(name);  
                 }  
             }catch(ClassNotFoundException e) {  
                 //如果仍然无法加载成功,则调用自身的findClass进行加载              
                 c = findClass(name);  
             }  
         }  
         if (resolve) {  
             resolveClass(c);  
         }  
         return c;  
    }

当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?

1)首先当前线程的类加载器去加载线程中的第一个类(假设为类A)。

注:当前线程的类加载器可以通过Thread类的getContextClassLoader()获得,也可以通过setContextClassLoader()自己设置类加载器。

2)如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器去加载类B。

3)还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。

自定义ClassLoader需要继承ClassLoader,重写findClass方法,不鼓励重写loadClass

User user = new User();
System.out.println(user.getClass().getClassLoader().toString()); // AppClassLoader
System.out.println(Thread.currentThread().getContextClassLoader().toString()); // AppClassLoader

		new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("==》 " + Thread.currentThread().getContextClassLoader().toString());
				// AppClassLoader
			}

		}).start();

		MyClassLoader myLoader = new MyClassLoader();
		Person p = (Person) myLoader.loadClass("com.sample.model.Person").newInstance();
		System.out.println(p.getClass().getClassLoader().toString()); // AppClassLoader

补充:

类加载器与Web容器

  对于运行在 Java EE容器中的 Web 应用来说,类加载器的实现方式与一般的 Java 应用有所不同。不同的 Web 容器的实现方式也会有所不同。以 Apache Tomcat 来说,每个 Web 应用都有一个对应的类加载器实例。该类加载器也使用代理模式,所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。这是 Java Servlet 规范中的推荐做法,其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。这种代理模式的一个例外是:Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。

  绝大多数情况下,Web 应用的开发人员不需要考虑与类加载器相关的细节。下面给出几条简单的原则:

  (1)每个 Web 应用自己的 Java 类文件和使用的库的 jar 包,分别放在 WEB-INF/classes和 WEB-INF/lib目录下面。

  (2)多个应用共享的 Java 类文件和 jar 包,分别放在 Web 容器指定的由所有 Web 应用共享的目录下面。

(3)当出现找不到类的错误时,检查当前类的类加载器和当前线程的上下文类加载器是否正确。

 两个jar包中包含完全相同的包名和类名的加载问题

(1)自定义两个jar包,其中包含相同包名和类名

与export的导入顺序有关。只会加载第一个,并且运行正常。

(2)自定义jar和jdk包,其中包含相同的包名和类名

与export的导入顺序有关。同样是只会加载第一个,但是如果加载自定义的jar运行会报错。加载 jdk正常。

Tomcat类加载机制,参考:https://www.cnblogs.com/xing901022/p/4574961.html

当应用需要到某个类时,则会按照下面的顺序进行类加载:

  1 使用bootstrap引导类加载器加载

  2 使用system系统类加载器加载

  3 使用应用类加载器在WEB-INF/classes中加载

  4 使用应用类加载器在WEB-INF/lib中加载

  5 使用common类加载器在CATALINA_HOME/lib中加载


14、TreeMap和LinkedHashMap如何保证顺序?

答:(1)LinkedHashMap

继承HashMap、底层使用哈希表与双向链表来保存所有元素

LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了数组中保存的元素Entry,该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。

/**
 * LinkedHashMap的Entry元素。
 * 继承HashMap的Entry元素,又保存了其上一个元素before和下一个元素after的引用。
 */ 
private static class Entry<K,V> extends HashMap.Entry<K,V> { 
    Entry<K,V> before, after; 
    …… 
} 

 

(2)TreeMap

可参考:http://blog.csdn.net/chenssy/article/details/26668941

TreeMap的实现是红黑树算法的实现,是复杂而高效的,其检索效率O(log n)

红黑树可参考:http://www.cnblogs.com/yangecnu/p/Introduce-Red-Black-Tree.html

15、LinkedHashMap其他问题

答:(1)LinkedHashMap的accessOrder的作用

accessOrder   false: 基于插入顺序(默认)     true:  基于访问顺序

基于访问的顺序,get一个元素后,这个元素被加到最后(使用了LRU 最近最少被使用的调度算法)

内部实现:将访问过的元素指向tail。也就是before、after起作用

(2)LinkedHashMap如何利用LRU算法实现LRU缓存

accessOrder设置为true

//实现LRU的关键方法,如果map里面的元素个数大于了缓存最大容量,则删除链表的顶端元素  
    @Override  
    public boolean removeEldestEntry(Map.Entry<K, V> eldest){     
        return size()>capacity;  
    }  

16、JDK7以上的Collections.sort(list, c)的Timsort排序算法

可参考:http://blog.sina.com.cn/s/blog_8e6f1b330101h7fa.html有具体算法过程。

 答:Timsort算法是结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法,最大的特点就是充分利用数组中已经存在顺序。

Timsort的核心过程

TimSort 算法为了减少对升序部分的回溯和对降序部分的性能倒退,将输入按其升序和降序特点进行了分区。排序的输入的单位不是一个个单独的数字,而是一个个的块-分区。其中每一个分区叫一个run。针对这些 run 序列,每次拿一个 run 出来按规则进行合并。每次合并会将两个 run合并成一个 run。合并的结果保存到栈中。合并直到消耗掉所有的 run,这时将栈上剩余的 run合并到只剩一个 run 为止。这时这个仅剩的 run 便是排好序的结果。

综上述过程,Timsort算法的过程包括

(0)如何数组长度小于某个值,直接用二分插入排序算法

(1)找到各个run,并入栈

(2)按规则合并run

Java7对对象进行排序,没有采用快速排序,是因为快速排序是不稳定的,而Timsort是稳定的。

Timsort是稳定的算法,当待排序的数组中已经有排序好的数,它的时间复杂度会小于n logn。与其他合并排序一样,Timesrot是稳定的排序算法,最坏时间复杂度是O(n log n)。在最坏情况下,Timsort算法需要的临时空间是n/2,在最好情况下,它只需要一个很小的临时存储空间。

 可参考:http://blog.sina.com.cn/s/blog_8e6f1b330101h7fa.html

17、Comparator使用需要注意什么?

答:Comparator用于列表排序

JDK6版本,使用的是合并排序;JDK7以上,默认使用的是Timsort排序,Comparator要满足三个条件(自反性、传递性、对称性)

JDK7 版本以上,Comparator 要满足自反性,传递性,对称性,不然 Arrays.sort,

Collections.sort 会报IllegalArgumentException 异常:Comparison method violates its general contract!

Comparator要满足三个条件:

1) 自反性:x,y 的比较结果和 y,x 的比较结果相反。

2) 传递性:x>y,y>z,则 x>z。

3) 对称性:x=y,则 x,z 比较结果和 y,z 比较结果相同。

// 报“Comparison method violates its general contract! ”的例子
//参考:http://blog.sina.com.cn/s/blog_8e6f1b330101h7fa.html
public class ReproJava7Exception {
    public static void main(String[] args) {
        int[] sample = new int[]
              {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
                0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,-2,1,0,-2,0,0,0,0};
        List list = new ArrayList();
        for (int i : sample)
            list.add(i);
        // use the native TimSort in JDK 7
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                // miss the o1 = o2 case on purpose
                return o1 > o2 ? 1 : -1;
            }
        });
    }
}




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值