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类优秀封装设计:
- String用一个char类型的value数组成员变量,来封装字符串数据。而这个value是private和final修饰的,外界不能直接访问和修改这个value。
- 通过轻巧小心的设计,String类的所有操作都没有暴露这个value数组或者修改这个value数组。String类提供的字符串操作,都是创建了新String对象,新的value数组而不是直接修改原先的value。而典型toCharArray方法也没有直接返回value数组而是返回它的拷贝。
- String类用数组赋值的构造器,没有直接用传入的数组对象给value赋值,而是用它的拷贝给value赋值。这样就避免了外界通过修改传参的数组去修改String。
String的不可变特性,给String类本身带来了很多优点,比如:
- 安全性。字符串在Java程序当中,经常用来存储一些敏感信息,如密码、配置信息、文件路径等。如果String是可变对象,那么这些敏感信息就可能被其它代码无意或恶意的修改,从而引发安全问题。不可变的 String 可以确保这些信息在整个应用程序运行期间都不会被修改。
- 线程安全。由于字符串是不可变的,因此它们在多线程环境中是绝对线程安全的。这使得String对象在多线程环境下,无需通过线程同步来保证数据安全,这提高了并发编程时,程序的效率。
- 哈希值不变。String对象的哈希值在调用一次hashCode方法后会直接用成员变量hash存储,并且由于String对象不可变,该哈希值hash在整个对象的生命周期中都不会改变。如果用String对象做哈希表的key,那么由于哈希值不变,就无需多次重复计算哈希值,就可以显著提高哈希表的性能。实际上,字符串String就是Java当中最常用的哈希表的key。
- 支持字符串常量池。正是由于String对象是不可变对象,字符串常量池中的String对象才有了共享使用的可能性。如果String对象是可变的,那么字符串常量池也就失去意义了。
当然String对象的不可变特性,也带来了一些弊端,比如:
- 时间耗费。字符串往往每操作一次就会创建一个新的字符串对象,如果遇到需要频繁操作的场景,时间耗费非常大。
- 空间浪费。频繁操作字符串时使用String,就会导致频繁创建String对象。这就可能造成大量的内存空间浪费。
- 性能损耗。频繁操作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和类型强转