【Java】基础 - 未分类

134 阅读6分钟

抽象、类与对象

TODO

接口与抽象类

相同点:

  1. 二者都代表了多个类相同特性的抽象,代表了 Java 的多态。
  2. 二者都无法直接实例化。
  3. 二者都可以定义静态常量和静态方法,因为静态属性的东西是类级别的,不是实例级别的。

不同点:

  1. 定义的关键字不同。抽象类依旧是一个 class,要以 abstract 关键字指定;接口是 interface,不是类。
  2. 使用的方式不同。抽象类使用 extends 关键字被子类继承使用;接口通过 implements 关键字被实现类实现。
  3. 使用的要求不同。Java 中的类是单继承的,因此一个类只能继承一个抽象类;但是一个类可以实现多个接口。
  4. 二者的功能不同。抽象类可以定义自己的数据成员、静态变量,还有普通实例方法;但接口只能定义静态常量,且只能定义接口方法和静态方法,虽然在 JDK1.8 之后可以定义 default 方法,但是建议尽量少用。

如何选择:

在 Java 里面,由于单继承机制,每个类只有一次继承的机会,非常珍贵,因此我们设计时应该优先考虑接口而非抽象类。一旦我们需要用到的功能接口没有,我们才应该考虑抽象类。那怎么界定我们是否需要用到抽象类的功能呢?得从抽象类得本质思考。

从面向对象的角度看,抽象类和接口都代表了多态的特性。但抽象类的本质就是一个无法实例化的类,因此它必定是一个父类,因此它又代表了继承这一特性。继承这一特性解决的是代码的复用问题。因此如果多个类有相同的特性,同时多个类具有相同的属性、特征而导致代码可以复用,那么我们就应当定义为抽象类

对象

对象从创建到销毁的过程

  1. 根据对象类型的符号引用去运行时常量池获取该类的解析后在方法区中的指针。
  2. 如果没有被解析过则进行类加载,包括加载、验证、准备、解析和初始化五个步骤。
  3. 决定对象分配区域,如果对象过大直接在老年区进行分配,否则在伊甸区分配。
  4. 检查内存是否足够分配,如果无法分配对象,则进行垃圾收集操作。对新生代进行 Minor GC,对老年代进行 Major GC。
  5. 为新生的对象分配内存。分配方式的话包括指针碰撞和空闲列表两种方式。指针碰撞是对于标记-整理搜集算法的垃圾收集器使用的,而空闲列表是对于标记-清楚收集算法的垃圾收集器使用的。
  6. 在多线程条件下,由于存在并行内存分配的可能性,可能导致多个线程在同一块内存区域进行内存分配,因此需要解决并行分配的问题。可以采用 CAS 加失败重试的方式保证分配操作的原子性。还有一种方法是本地线程分配缓冲(TLAB),每个线程都有一块内存分配缓冲区,线程默认在自己的缓冲区分配内存,只有缓冲区满了,分配新的缓冲区时才需要同步锁定。
  7. 分配完内存之后,虚拟机开始对对象的对象头进行设置。每个对象的起始内存区域是对象的对象头,对象头中保存了对象最重要的信息。包括类型指针、数组长度、分代年龄、锁状态和哈希码等。
  8. 执行对象初始化,调用构造方法。对于构造代码块,在编译时编译器会将构造代码块中的代码合并到构造方法中,而且所有构造代码块语句都将放在原构造方法语句前。

对象引用的指针实现

实现对象引用有两种方式,使用句柄与使用直接指针。HotSpot 使用第二种:

  1. 句柄:通过维护一个句柄池,对象句柄中保存实例数据的指针和类型数据的指针。使用句柄的好处在于对象数据在内存中位置发生变化时,不需要修改所有引用变量的指针,直接修改句柄的指针即可;但缺点是访问对象数据需要两步才能访问到。

  2. 直接指针:直接指针的方式,每个对象引用都指向对象的实例数据,对象的实例数据头部是对象头,对象头中保留指向对象类型数据的指针。直接指针可以直接通过引用访问到对象,访问对象类型则需要两步。考虑到对象的访问才是最频繁的操作,因此大多数使用直接对象指针的方式来实现。

引用的类型

在 Java 中,引用分为强引用、软引用、弱引用和虚引用。

  • 强引用:我们平时使用等号赋值的引用就属于强引用,强引用意味着对象不可或缺,是垃圾收集中引用链分析的基础,作为对象存活的标志,因此垃圾收集时不会收集强引用在引用链上的对象,即使发生 OOM,也不会收集这些对象。

  • 软引用:使用 SoftReference 包装的对象属于软引用,软引用意味着对象可以清除,但最好以最大限度保留。因此普通垃圾收集不会收集软引用,仅在发生 OOM 前的 Full GC 会收集软引用。可用于一些非核心对象,在负载过高时释放掉以保护核心业务对象。

  • 弱引用:使用 WeakReference 包装的对象属于弱引用,弱引用意味着对象可以清除,但不碍事的话不妨留着。因此弱引用的对象可以熬过一次垃圾收集,在面临第二次垃圾收集时将被回收。可用于一些缓存或可复用的对象。

  • 虚引用:使用 PhantomReference 包装的对象属于虚引用,虚引用无法对对象提供保护,通过搭配 ReferenceQueue 可以在对象被回收时可以执行一个事件,甚至是拯救对象,但拯救对象的话就破坏了设计原则了。