美文网首页
Java 面试题

Java 面试题

作者: 课本里的小明 | 来源:发表于2021-07-17 10:53 被阅读0次

1. jdk 和 jre 的区别:

JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。

2. == 和 equals

“==”

对基本类型来说:是比较的值是否相等
对引用类型来说:是比较的引用是否相等

equals

其实也是 值得比较,String 和 Integer 重写了 equals 方法 把它变成了值得比较
例如

    String a1 = new String("a");
    String b1 = new String(“a”);
    a1.equals(b1);   显示是 true

    A a1 = new A("a");
    A b1 = new A(“a”);
    a1.equals(b1);   显示是 false



public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

3. final 的关键词在java 的作用

修饰类 则该类不可被继承
修饰方法 则该方法不可被重写
修饰成员变量 则被修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改

4. 抽象类必须要有抽象方法么?抽象类 可以用 final 修饰么?抽象类和普通类的区别是什么?

4.1 抽象类必须要有抽象方法么? 可以
4.2 抽象类 可以用 final 修饰么? 不可以, 被修饰了就不能被继承了
4.3 抽象类不能直接实例化,普通类可以直接实例化。

普通类不能包含抽象方法,抽象类可以包含抽象方法。

5.接口和抽象类的区别;

抽象类: 修饰符为 abstract
接口: interface
抽象类 被继承 extends ,只能被继承一次。 接口 要用 implements, 可以多个
抽象类 可以有构造函数, 接口没有
接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。

6.List、Set、Map 之间的区别是什么?

WX20210717-104859@2x.png

7.HashMap 和 Hashtable 有什么区别?

1)hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
2)hashTable同步的,而HashMap是非同步的,效率上比hashTable要高。
3)hashMap允许空键值,而hashTable不允许。

8.类的加载过程

java编译器将 .java 文件编译成扩展名为 .class 的文件。.class 文件中保存着java转换后,虚拟机将要执行的指令。当需要某个类的时候,java虚拟机会加载 .class 文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程被称为类的加载。

8.1 加载

ClassLoader 通过一个类的完全限定名(包含类名和包名)查找类的字节码文件,并利用字节码文件创建一个class对象

8.2 验证

为静态变量分配内存并设初始值,这里不包含final修饰的static ,因为final在编译的时候就已经分配了。这里不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象分配到Java堆中。

8.3 解析

是把常量池中的符号引用替换成直接引用

8.4 初始化

是类加载的最后阶段,如果该类具有父类就进行对父类进行初始化,执行其静态初始化器(静态代码块)和静态初始化成员变量。(前面已经对static 初始化了默认值,这里我们对它进行赋值,成员变量也将被初始化)

9.forName和loaderClass区别

Class.forName()得到的class是已经初始化完成的。
Classloader.loaderClass得到的class是还没有 验证,准备,解析三个过程被的。

10.双亲委派

双亲委派模式要求除了顶层的启动类加载器之外,其余的类加载器都应该有自己的父类加载器,但是在双亲委派模式中父子关系采取的并不是继承的关系,而是采用组合关系来复用父类加载器的相关代码。

protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
   // 增加同步锁,防止多个线程加载同一类
    synchronized (getClassLoadingLock(name)) {
         // First, check if the class has already been loaded
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
               if (parent != null) {
                    c = parent.loadClass(name, false);
               } else { // ExtClassLoader没有继承BootStrapClassLoader
                    c = findBootstrapClassOrNull(name);
               }
           } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
               // from the non-null parent class loader
           }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                // AppClassLoader去我们项目中查找是否有这个文件,如有加载进来
                // 没有就到用户自定义ClassLoader中加载。如果没有就抛出异常
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

工作原理
如果一个类收到了类加载的请求,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最后到达顶层的启动类加载器,如果弗雷能够完成类的加载任务,就会成功返回,倘若父类加载器无法完成任务,子类加载器才会尝试自己去加载,这就是双亲委派模式。就是每个儿子都很懒,遇到类加载的活都给它爸爸干,直到爸爸说我也做不来的时候,儿子才会想办法自己去加载。

优势
采用双亲委派模式的好处就是Java类随着它的类加载器一起具备一种带有优先级的层次关系,通过这种层级关系可以避免类的重复加载,当父亲已经加载了该类的时候,就没有必要子类加载器(ClassLoader)再加载一次。其次是考虑到安全因素,Java核心API中定义类型不会被随意替换,假设通过网路传递一个名为java.lang.Integer的类,通过双亲委派的的模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字类,发现该类已经被加载,并不会重新加载网络传递过来的java.lang.Integer.而之际返回已经加载过的Integer.class,这样便可以防止核心API库被随意篡改。

类与类加载器
在JVM中标识两个Class对象,是否是同一个对象存在的两个必要条件
类的完整类名必须一致,包括包名。
加载这个ClassLoader(指ClassLoader实例对象)必须相同。

对象的创建过程
当虚拟机遇到一个new的指令的时候,首先去检查这个指令是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载,解析和初始化过。如果没有则执行相应初始化的过程。在类加载检查通过后,接下来虚拟机将为新生对象分配内存,对象所需要的内存的大小在类加载完成后便可以完成确定。内存分配完成以后,虚拟机需要将分配的内存空间都初始化为零值,保证了对象的实例字段在Java代码中可以不赋予初值就直接使用,程序能访问到这些字段的数据类型对应的零值。再接下来对象需要进行必要的设置,这个对象是哪个类的实例,如何才能找到这个类的元数据信息,如何找到对象的哈希码,对象的GC分带年龄。

Java堆如果是规整的采取:指针碰撞,
Java堆如果不是规整的话:空闲列表,在内存中直接分配一个足够大的内存空间划分给对象。
对象创建是非常平凡的,在多线程的程序中会产生线程安全的问题,所以解决这个问题有两种方式
使用CSA配上失败重试的方式来保证原子性
内存分配动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一个小块的内存成为本地分配缓冲,TLAB,哪个线程需要分配内存就在哪个线程的TALB上分配,只有在TALB用完之后才会重新分配新的TALB的时候才会同步锁定。

相关文章

网友评论

      本文标题:Java 面试题

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