Skip to main content

常见类

David LiuAbout 8 min

常见类

Object

Object 方法

全部方法如下:

/**
 * native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
 */
public final native Class<?> getClass()
/**
 * native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
 */
public native int hashCode()
/**
 * 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
 */
public boolean equals(Object obj)
/**
 * naitive 方法,用于创建并返回当前对象的一份拷贝。
 */
protected native Object clone() throws CloneNotSupportedException
/**
 * 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
 */
public String toString()
/**
 * native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
 */
public final native void notify()
/**
 * native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
 */
public final native void notifyAll()
/**
 * native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
 */
public final native void wait(long timeout) throws InterruptedException
/**
 * 多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫秒。。
 */
public final void wait(long timeout, int nanos) throws InterruptedException
/**
 * 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
 */
public final void wait() throws InterruptedException
/**
 * 实例被垃圾回收器回收的时候触发的操作
 */
protected void finalize() throws Throwable { }

分类

  • JUC

    • notify
    • notifyAll
    • wait,三个
  • Reflect

    • getClass
  • GC:

    finalize:Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the {@code finalize} method to dispose of system resources or to perform other cleanup.

  • Hash

    • hashCode
    • equals
  • basic

    • clone:需要继承 Cloneable 接口才行,默认 native 的实现是浅拷贝

      clone 方法是浅拷贝,对象内属性引用的对象只会拷贝引用地址,而不会将引用的对象重新分配内存,相对应的深拷贝则会连引用的对象也重新创建。

    • toString

== 和 equals() 的区别

== 对于基本类型和引用类型的作用效果是不同的:

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。

equals() 不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals()方法。

Objectequals() 方法:

public boolean equals(Object obj) {
     return (this == obj);
}

equals() 方法存在两种使用情况:

  • 类没有重写 equals()方法 :通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是 Objectequals()方法。
  • 类重写了 equals()方法 :一般我们都重写 equals()方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。

举个例子(这里只是为了举例。实际上,你按照下面这种写法的话,像 IDEA 这种比较智能的 IDE 都会提示你将 == 换成 equals() ):

重写 hashCode() 方法

要重写自己的 hashCode 方法并没有什么绝对正确的答案,但是我们的目标是:不相等的对象尽可能有不同的 hashCode,而且必须满足的一个通用约定是:相等的对象应该具有相同的 hashCode。下面介绍一种 hashCode 的实现方式,这种实现方式对一般的程序来说足够了,至于如何实现更完美的 hashCode 方法就留给数学家或者理论家去讨论吧。

第一步:定义一个初始值,一般来说取 17

int result = 17;

第二步:分别解析自定义类中与 equals 方法相关的字段(假如 hashCode 中考虑的字段在 equals 方法中没有考虑,则两个 equals 的对象就很可能具有不同的 hashCode)

情况一:字段a类型为boolean 则[hashCode] = a ? 1 : 0;

情况二:字段b类型为byte/short/int/char, 则[hashCode] = (int)b;

情况三:字段c类型为long, 则[hashCode] = (int) (c ^ c>>>32);

情况四:字段d类型为float, 则[hashCode] = d.hashCode()(内部调用的是Float.hashCode(d), 而该静态方法内部调用的另一个静态方法是Float.floatToIntBits(d))

情况五:字段e类型为double, 则[hashCode] = e.hashCode()(内部调用的是Double.hashCode(e), 而该静态方法内部调用的另一个静态方法是Double.doubleToLongBits(e),得到一个long类型的值之后,跟情况三进行类似的操作,得到一个int类型的值)

情况六:引用类型,若为null则hashCode为0,否则递归调用该引用类型的hashCode方法。

情况七:数组类型。(要获取数组类型的hashCode,可采用如下方法:s[0]*31 ^ (n-1) + s[1] * 31 ^ (n-2) + ..... + s[n-1], 该方法正是String类的hashCode实现所采用的算法)

第三步:对于涉及到的各个字段,采用第二步中的方式,将其依次应用于下式:

result = result * 31 + [hashCode];

补充说明一点:如果初始值 result 不取 17 而取 0 的话,则对于 hashCode 为 0 的字段来说就没有区分度了,这样更容易产生冲突。比如两个自定义类中,一个类比另一个类多出来一个或者几个字段,其余字段全部一样,分别 new 出来 2 个对象,这 2 个对象共有的字段的值全是一样的,而对于多来的那些字段的值正好都是 0,并且在计算 hashCode 时这些多出来的字段又是最先计算的,这样的话,则这两个对象的 hashCode 就会产生冲突。还是那句话,hashCode 方法的实现没有最好,只有更好。

String

String 为什么是不可变的?

String 类中使用 final 关键字修饰字符数组来保存字符串,所以String 对象是不可变的。

length() 返回值

length()返回值为字符串中 Unicode(utf-16)代码单元(两字节是一个代码单元)的数量。这个值不同于字符串中实际字符的数量,因为某些 Unicode 字符可能由多个代码单元组成,如表情符号、音乐符号、数学符号、某些语音的字符。

要统计字符数,可以使用codePointCount()

String、StringBuffer、StringBuilder 的区别?

字符串常量池的作用了解吗?

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

  • java 中的 length属性是针对数组说的,比如说你声明了一个数组,想知道这个数组的长度则用到了 length 这个属性.
  • java 中的 length() 方法是针对字符串说的,如果想看这个字符串的长度则用到 length() 这个方法.
  • java 中的 size() 方法是针对泛型集合说的,如果想看这个泛型有多少个元素,就调用此方法来查看!

String 最大长度是多少

String 类提供了一个 length 方法,返回值为 int 类型,而 int 的取值上限为 2^31 -1。所以理论上 String 的最大长度为 2^31 -1。

达到这个长度的话需要多大的内存吗

String 内部是使用一个 char 数组来维护字符序列的,一个 char 占用两个字节。如果说 String 最大长度是 2^31 -1 的话,那么最大的字符串占用内存空间约等于 4GB。

也就是说,我们需要有大于 4GB 的 JVM 运行内存才行。

那 String 一般都存储在 JVM 的哪块区域呢

字符串在 JVM 中的存储分两种情况,一种是 String 对象,存储在 JVM 的堆栈中。一种是字符串常量,存储在常量池里面。

什么情况下字符串会存储在常量池呢

当通过字面量进行字符串声明时,比如 String s = "程序新大彬";,这个字符串在编译之后会以常量的形式进入到常量池。

那常量池中的字符串最大长度是 2^31-1 吗

不是的,常量池对 String 的长度是有另外限制的。。Java 中的 UTF-8 编码的 Unicode 字符串在常量池中以 CONSTANT_Utf8 类型表示。

CONSTANT_Utf8_info {
    u1 tag;
    u2 length;
    u1 bytes[length];
}

length 在这里就是代表字符串的长度,length 的类型是 u2,u2 是无符号的 16 位整数,也就是说最大长度可以做到 2^16-1 即 65535。

不过 javac 编译器做了限制,需要 length < 65535。所以字符串常量在常量池中的最大长度是 65535 - 1 = 65534。

最后总结一下:

String 在不同的状态下,具有不同的长度限制。

  • 字符串常量长度不能超过 65534
  • 堆内字符串的长度不超过 2^31-1