java面试题


1、类加载机制(过程)

jvm把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成jvm可以直接使用的java类型的过程。

(1)加载

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。

(2) 验证

确保加载的类信息符合jvm规范,没有安全方面的问题。

(3)准备

正式为类变量(static变量)分配内存并设置类变量初始值(默认值)的阶段,即在方法区分配这些变量所需的内存空间。

(4)解析

虚拟机常量池内的符号引用替换为直接引用的过程。(比如String s =”aaa”,转化为 s的地址指向“aaa”的地址)

符号应用:引用的目标并不一定要已经加载到内存中。各种虚拟 机实现的内存布局可以各不相同

直接引用:可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有 了直接引用,那引用的目标必定已经在内存中存在

(3)初始化

初始化阶段是执行类构造器方法的过程。类构造器方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static块)中的语句合并产生的。

当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先初始化其父类的初始化

虚拟机会保证一个类的构造器方法在多线程环境中被正确加锁和同步

当访问一个java类的静态域时,只有真正声明这个静态变量的类才会被初始化。

2、==、equals()和hashCode()区别

(1)==

如果是基本数据类型,则比较值是否相等;如果是引用类型,则比较引用的地址值。

(2)equals()

equals()是Object类的方法,Object类的equals()方法直接使用“==”比较对象,所以在没有覆盖Object的equals()方法时,equals用法和“==”一样。

一般来说,重写equals都是用来比较值(可用于比较对象内容)

(3)hashCode()

它返回的就是根据对象的内存地址换算出的一个值。这样一来,当集合 要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。 如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上 已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地 址。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

一般来讲,equals()是程序员调用的,而hashCode()是其他的代码调用的,比如hashMap()判断key的不重复。

一般覆盖了equals()方法也要覆盖hashCode()方法,否则就不能使用HashMap、HashSet等集合。

如果两个对象equals()为true,则hashCode()返回相等;如果equals()为false,则hashCode()可能相等也可能不等。

如果hashCode()不相等,则equals()不相等;hashCode()相等,则equals()可能相等也可能不等。

为什么重写 equals() 时必须重写 hashCode() 方法?

因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。

如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。

思考:重写 equals() 时没有重写 hashCode() 方法的话,使用 HashMap 可能会出现什么问题。

总结

  • equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
  • 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)。

3、接口和实现类区别

抽象类是对类的抽象,接口是对功能的抽象

定义方式

//实现类,可以有抽象方法,也可以有具体方法
public abstract class Test2 {
    public abstract void method1();

    public void method2(){
        System.out.println("llll");
    }
}

//接口
public interface Test1{
    public abstract void method3();
}

4、String的intern()方法

本地方法,如果字符串常量池中已经包含一个等于此String对象的字符串,则返回池中这个字符串对象的引用;否则,会将此String对象包含的字符串添加到字符串常量池中,并返回此字符串的引用。

String s1 = new StringBuilder("ja").append("va").toString();
System.out.println(s1);     //java
System.out.println(s1.intern());    //java
System.out.println(s1==s1.intern());    //false

System.out.println("================================");

String s2 = new StringBuilder("hello").append("world").toString();
System.out.println(s2);     //helloworld
System.out.println(s2.intern());    //helloworld
System.out.println(s2==s2.intern());    //true

为什么s1==s1.intern()s2==s2.intern()不一样?

实际上,只有java字符串会出现true。那么另外一个java字符串是如何加载进来的?

有一个初始化的java字符串(JDK娘胎自带的),在加载sun.misc.version这个类的时候进入常量池。

//字符串常量池中本身自带一个java
//new一个字符串对象“java”
String s1 = new StringBuilder("ja").append("va").toString();
//s1指向的是自己new的java字符串
System.out.println(s1);     //java
//因为字符串常量池中原本就有“java”字符串,所以s1.intern()指向的原有的java,而不是我们new出来的
System.out.println(s1.intern());    //java
System.out.println(s1==s1.intern());    //false

5、四种引用(强软弱虚)

(1)强引用

强引用是平常中使用多的引用,强引用在程序内存不足(OOM)的时候也不会被回收,使用方 式:

String str = new String("str");

(2)软引用

软引用在程序内存不足时,会被回收;而在内存充足的时候,和强引用是一样的,使用方式:

// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的, // 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T 
SoftReference<String> wrf = new SoftReference<String>(new String("str"));

可用场景: 创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的 对象。

加入需要读取大量的图片:

  • 每次从硬盘读取,会严重影响性能
  • 一次性全部加载到内存,又可能导致内存溢出

此时可用软引用解决这个问题。

设计思路:用HashMap保存图片的路径(key)和相应的图片对象的软引用(value)。当内存不足时,jvm会自动回收缓存图片对象所占的空间,从而有效的避免OOM。

(3)弱引用

弱引用就是只要JVM垃圾回收器发现了它,就会将之回收,使用方式:

WeakReference<String> wrf = new WeakReference<String>(str); 

可用场景: Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用,我的理解就是, 一旦我不需要某个引用,JVM会自动帮我处理它,这样我就不需要做其它操作。

(4)虚引用

虚引用的回收机制跟弱引用差不多,但是它被回收之前,会被放入 ReferenceQueue 中。注意 哦,其它引用是被JVM回收后才被传入 ReferenceQueue 中的。由于这个机制,所以虚引用大多被 用于引用销毁前的处理工作。还有就是,虚引用创建的时候,必须带有 ReferenceQueue ,使用 例子:

PhantomReference<String> prf = new PhantomReference<String>(new String("str"), new ReferenceQueue<>());

可用场景: 对象销毁前的一些操作,比如说资源释放等。Object.finalize() 虽然也可以做 这类动作,但是这个方式即不安全又低效

6、接口、实体类、抽象类关系

(1)继承:获得父类的方法和属性,所以接口不能继承实体类和抽象类,因为接口不能有具体的方法实现,但接口可以继承接口。

(2)实现:实现接口的所有抽象方法,只有接口能被实现

//实体类不能继承接口,但能实现多个接口
//实体类可以继承抽象类,必须实现抽象类的抽象方法;
//抽象类可以继承抽象类,且不必实现抽象方法,
//接口只能继承接口,但不能实现接口
//抽象类和借口哦都不能实例化

7、finally

(1)不要在 finally 语句块中使用 return

finally 语句块将在方法返回之前被执行。当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句会被忽略。这是因为 try 语句中的 return 返回值会先被暂存在一个本地变量中,当执行到 finally 语句中的 return 之后,这个本地变量的值就变为了 finally 语句中的 return 返回值。

public static int f() {
    try {
        return 1;
    } finally {
        return 0;
    }
}

上面的方法返回0

(2)finally 中的代码一定会执行吗?

在以下 2 种特殊情况下,finally 块的代码也不会被执行:

  1. finally 之前虚拟机被终止运行
  2. 程序所在的线程死亡。
  3. 关闭 CPU。

  目录