美文网首页
【JAVA】面试知识点一

【JAVA】面试知识点一

作者: Y了个J | 来源:发表于2019-05-24 22:03 被阅读0次

    short s1=1;s1=s1+1有什么错?
    short s1=1;s1+=1;有什么错?
    第一个是有错的,short在内存中占2个字节,而整数1默认为int型占4个字节,s1+1其实这个时候就向上转型为int类型了,因此第一行代码必须强转才行。第二个之所以可以,是因为这句话翻译过来就是s1++,也就是short类型的数据自身加增1,因此不会有问题。


    静态成员类、非静态成员类有什么区别?什么是匿名内部类?
    静态成员类相当于外部类的静态成员,是外部类在加载的时候进行初始化,非静态成员类相当于外部类的普通成员,当外部类创建对象的时候才会初始化。匿名内部一般都是在方法里面直接通过new ClassName(){};形式的类。比如我们new Thread(new Runnable(){}).start();就用到了匿名内部类。


    abstract class 和 interface有什么区别?
    前者是抽象类,可以有抽象方法,也可以没有。后者是接口,只能有抽象方法。他们都不能创建对象,需要被继承。


    ArrayList是不是线程安全的?如果不是,如何是ArrayList成为线程安全的?
    不安全的。可以使用Collections.synchronizedList(list)将list变为线程安全的。


    当一个对象被当做参数传递到一个方法后,此方法可以改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递?
    java中只有值传递,没有引用传递。这里的引用本身就是值,传递的是引用这个值。


    请描述下JVM加载class文件的原理机制。
    JVM加载class是动态性的,也就是当“需要”的时候才会加载,这是也是为节约JVM内存来考虑的。同时JVM的类加载是父类委托机制,这个机制简单来讲,就是“类装载器有载入类的需求时,会先请示其Parent使用其搜索路径帮忙载入,如果Parent 找不到,那么才由自己依照自己的搜索路径搜索类”。


    我们知道了ThreadLocal是如何实现线程私有变量的。
    但是问题来了,如果线程数很多,一直往ThreadLocalMap中存值,那内存岂不是要撑死了?当然不是,设计者使用了弱引用来解决这个问题:

    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    

    不过这里的弱引用只是针对key。每个key都弱引用指向ThreadLocal。当把ThreadLocal实例置为null以后,没有任何强引用指向ThreadLocal实例,所以ThreadLocal将会被GC回收。然而,value不能被回收,因为当前线程存在对value的强引用。只有当前线程结束销毁后,强引用断开,所有值才将全部被GC回收,由此可推断出,只有这个线程被回收了,ThreadLocal以及value才会真正被回收。听起来很正常?

    那如果我们使用线程池呢?常驻线程不会被销毁。这就完蛋了,ThreadLocal和value永远无法被GC回收,造成内存泄漏那是必然的。
    而我们的请求进入到系统时,并不是一个请求生成一个线程,而是请求先进入到线程池,再由线程池调配出一个线程进行执行,执行完毕后放回线程池,这样就会存在一个线程多次被复用的情况,这就产生了这个线程此次操作中获取到了上次操作的值。

    怎么办呢?
    解决办法就是每次使用完ThreadLocal对象后,都要调用其remove方法,清除ThreadLocal中的内容。

    public class Test {
    
        static ThreadLocal<AtomicInteger> sequencer = ThreadLocal.withInitial(() -> new AtomicInteger(0));
    
        static class Task implements Runnable {
            @Override
            public void run() {
                int initial = sequencer.get().getAndIncrement();
                System.out.println(initial);
                sequencer.remove();//这里不删有问题
            }
        }
    
        public static void main(String[] args) {
            ExecutorService executor = Executors.newFixedThreadPool(2);
            executor.execute(new Task());
            executor.execute(new Task());
            executor.execute(new Task());
            executor.execute(new Task());
            executor.execute(new Task());
            executor.execute(new Task());
            executor.shutdown();
        }
    }
    

    Java 7中switch开始支持String。
    对于编译器来说,switch中其实只能使用整型,任何类型的比较都要转换成整型。比如byte。short,char(ackii码是整型)以及int。
    那么接下来看下switch对String得支持,有以下代码:

    public class switchDemoString {
        public static void main(String[] args) {
            String str = "world";
            switch (str) {
                case "hello":
                    System.out.println("hello");
                break;
                case "world":
                    System.out.println("world");
                break;
                default:
                            break;
            }
        }
    }
    

    反编译后内容如下:

    public class switchDemoString
    {
        public switchDemoString() {
        }
        public static void main(String args[])  {
            String str = "world";
            String s;
            switch((s = str).hashCode())  {
                default:
                    break;
                case 99162322:
                    if(s.equals("hello"))
                        System.out.println("hello");
                break;
                case 113318802:
                    if(s.equals("world"))
                        System.out.println("world");
                break;
            }
        }
    }
    

    看到这个代码,你知道原来字符串的switch是通过equals()和hashCode()方法来实现的。还好hashCode()方法返回的是int,而不是long。

    仔细看下可以发现,进行switch的实际是哈希值,然后通过使用equals方法比较进行安全检查,这个检查是必要的,因为哈希可能会发生碰撞。因此它的性能是不如使用枚举进行switch或者使用纯整数常量,但这也不是很差。


    泛型——当泛型遇到重载

    public class GenericTypes {
    
        public static void method(List<String> list) {  
            System.out.println("invoke method(List<String> list)");  
        }  
    
        public static void method(List<Integer> list) {  
            System.out.println("invoke method(List<Integer> list)");  
        }  
    }  
    

    上面这段代码,有两个重载的函数,因为他们的参数类型不同,一个是List<String>另一个是List<Integer>,但是,这段代码是编译通不过的。因为我们前面讲过,参数List<String>和List<Integer>编译之后都被擦除了,变成了一样的原生类型List,擦除动作导致这两个方法的特征签名变得一模一样。

    相关文章

      网友评论

          本文标题:【JAVA】面试知识点一

          本文链接:https://www.haomeiwen.com/subject/kqmnzqtx.html