Skip to content

Object ​

深克隆于浅克隆 ​

  • 克隆是所有对象都继承自Object的一个方法,调用clone得到一个新副本,和new一个新对象一样。默认是浅克隆。
  • 如果对象中的成员有引用数据类型,浅克隆得到的副本和原先的引用成员都指向同一个对象
  • 深克隆意味着克隆后的对象要和原先的对象完全独立

OOP ​

成员内部类不能有静态声明

什么是多态 ​

super和this ​

  • super是关键字,this是引用
  • this代表当前类的当前对象,super指向当前对象中父类成员区域

方法重载 vs 方法重写 ​

  • 发生时期:重载在编译期,重写在运行期
  • 发生条件:同一类中,方法名相同,形参列表不同;重写:继承关系,子类和父类的方法名形参列表相同
  • 重写的返回值必须跟父类兼容,访问权限更宽松,抛异常更少
  • 重载就是同一个类中,根据参数不同,来执行不同的逻辑处理。重写是子类继承父类相同的方法,输入数据一样,做出不同于父类的处理。

局部变量 vs 成员变量 ​

  • 生存周期:局部变量随着方法的执行而被创建,成员变量和对象同生共死
  • 存储位置:局部变量在栈区的方法栈帧,成员变量在堆区上对象中
  • 有无默认初始化:局部变量没有,必须手动初始化。成员变量有
  • 作用范围:局部变量在大括号里,成员变量在整个类的成员方法中都可以使用
  • 定义位置:局部变量在局部位置{}里。成员变量在成员位置

static和非static ​

  • 存储位置:static在类的字节码文件对象中。非static在堆上的对象中
  • 所属不同:static是属于类的,非static是属于对象的
  • 内存中出现的时间:static随类的加载而存在,非static随对象的创建而存在
  • 调用方法:类名.static,对象名.非static

类加载时机 ​

  • 第一次new对象

  • 执行main函数,它所在的public class会加载

  • 首次访问类的静态成员

  • 子类加载会先加载其父类

  • 反射强制创建类或接口对应的Class对象

    这个时机下,类的加载是不完全的。使用反射仅仅是将类加载到内存中,但并不会导致类的初始化,因此静态代码块不会被执行。只有在实际使用时,比如通过反射调用静态方法、创建实例等操作,才会触发类的初始化。

String ​

String & StringBuilder & StringBuffer ​

String是不可变的,后面两个是可变的。

StringBuffer是线程安全的

StringBuilder是线程不安全的,但效率高

简要描述String对象不可变 ​

String对象是一个不可变对象。这意味着某个String对象一旦被创建,那么就不可以改变它的状态了。具体而言,就是这个字符串的内容就不可以再改变了。我们常做的字符串操作,比如拼接,截取,替换等,实际上都会创建一个新的String对象,而不是直接在原先对象上进行操作。

String对象之所以是一个不可变对象,依赖于String类优秀封装设计:

  1. String用一个char类型的value数组成员变量,来封装字符串数据。而这个value是private和final修饰的,外界不能直接访问和修改这个value。
  2. 通过轻巧小心的设计,String类的所有操作都没有暴露这个value数组或者修改这个value数组。String类提供的字符串操作,都是创建了新String对象,新的value数组而不是直接修改原先的value。而典型toCharArray方法也没有直接返回value数组而是返回它的拷贝。
  3. String类用数组赋值的构造器,没有直接用传入的数组对象给value赋值,而是用它的拷贝给value赋值。这样就避免了外界通过修改传参的数组去修改String。

String的不可变特性,给String类本身带来了很多优点,比如:

  1. 安全性。字符串在Java程序当中,经常用来存储一些敏感信息,如密码、配置信息、文件路径等。如果String是可变对象,那么这些敏感信息就可能被其它代码无意或恶意的修改,从而引发安全问题。不可变的 String 可以确保这些信息在整个应用程序运行期间都不会被修改。
  2. 线程安全。由于字符串是不可变的,因此它们在多线程环境中是绝对线程安全的。这使得String对象在多线程环境下,无需通过线程同步来保证数据安全,这提高了并发编程时,程序的效率。
  3. 哈希值不变。String对象的哈希值在调用一次hashCode方法后会直接用成员变量hash存储,并且由于String对象不可变,该哈希值hash在整个对象的生命周期中都不会改变。如果用String对象做哈希表的key,那么由于哈希值不变,就无需多次重复计算哈希值,就可以显著提高哈希表的性能。实际上,字符串String就是Java当中最常用的哈希表的key。
  4. 支持字符串常量池。正是由于String对象是不可变对象,字符串常量池中的String对象才有了共享使用的可能性。如果String对象是可变的,那么字符串常量池也就失去意义了。

当然String对象的不可变特性,也带来了一些弊端,比如:

  1. 时间耗费。字符串往往每操作一次就会创建一个新的字符串对象,如果遇到需要频繁操作的场景,时间耗费非常大。
  2. 空间浪费。频繁操作字符串时使用String,就会导致频繁创建String对象。这就可能造成大量的内存空间浪费。
  3. 性能损耗。频繁操作String对象,需要频繁创建对象,时间耗费大,空间浪费严重。同时也给GC的垃圾回收带来了负担,进一步加剧了性能损耗。

为了解决这些问题,Java提供了StringBuilder和StringBuffer两个类,这两个类表示的字符串是可变的,可以直接在原先字符串对象上进行各种操作,从而解决了由于String对象不可变性引起的性能问题。

简要描述字符串常量池 ​

字符串常量池(String Constant Pool)是JVM的一部分,在Java8的环境下,存在于堆上。

用于存储所有的字符串字面量,每当代码中出现一个字符串字面量,JVM就会检查常量池:

​ 1. 如果已经存在相同内容的字符串,直接返回已有字符串对象的引用

​ 2. 如果不存在,则创建新的字符串对象,并将其引用放入常量池

因为String对象是不可变的,Java通过字符串常量池,实现了对String字面量对象的复用。这样设计减少了内存开销,提高了程序的性能

但是要注意,只有String字面值对象才会通过常量池共享。任何new创建的String对象都不会自动将引用存储在常量池中

异常 ​

常见的异常 ​

  • 编译期异常

    FileNotFoundException

    CloneNotSupporedException

    IOException

  • 运行期异常

    NullPointerException

    ArrayIndexOutOfBoundsException

    ClassCastException

    ArithmeticException

    NumberFormatException

Thread ​

wait和sleep ​

  • 休眠时,对锁对象的持有:wait会释放锁,sleep不会
  • 唤醒条件:wait会无限等待,直到其他线程,在同一锁对象上调用notify。sleep到时间会自动进入就绪状态
  • 调用方法:wait是由锁对象调用的,必须是当前线程持有锁对象。sleep是Thread的静态方法,没有前提条件
  • 所属不同:wait是object的非静态方法,sleep是Thread的静态方法

释放锁的场景 ​

wait,发生异常,出了Synchronized

泛型 ​

泛型的擦除 ​

java的泛型只在编译期,编译后的运行期编译器会将泛型转换成Object和类型强转