学点java小知识 | 不可变类(1)

01 基础知识

简单来说,不可变类是以final关键字定义的类,形如:

final class ClassName{
}

我们熟悉的final关键字通常是用来定义常量的。用final声明的方法和变量有如下的特点:

1. 以final声明的方法不允许覆盖。

2. 以final声明的变量不允许更改。
而利用final,我们可以设计出一种特殊的“只读” 的“不可变类 (immutable class)”。当 创建了一个“不可变的类”的对象之后,此对象的属性不可改,而且也 法从此类派生出新子类。JDK中的 String 类就是一个不可变类的实例。

02 一个神奇的例子

观察下面的例子:

public class ExplorationJDKSource {
    public static void main(String[] args) {
        System.out.println(new A());
    }
}

//类A没有定义任何的成员
class A {
}

运行代码,会得到以下的输出:

A@4eec7777

为什么会出现这样的情况呢?其原因是默认情况下,当一个对象被直接打印为字符串时,会调用该对象的`toString()`方法来获取字符串表示形式。

在这个例子中,类A没有定义任何成员,因此它继承了默认的`toString()`方法实现。默认的`toString()`方法会返回一个由类名对象的哈希码组成的字符串。因此,输出的字符串"A@4eec7777"中的"A"是类名,"4eec7777"是对象的哈希码。

反编译编译后的.class文件,会看到以下的输出:

PS E:\ProgrammingFiles\Java\CompilerLab\out\production\CompilerLab> javap -c .\ExplorationJDKSource.class
Compiled from "ExplorationJDKSource.java"
public class ExplorationJDKSource {
  public ExplorationJDKSource();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
  public ExplorationJDKSource();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: new           #13                 // class A
       6: dup
       7: invokespecial #15                 // Method A."<init>":()V
      10: invokevirtual #16                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      13: return
}

从反编译的输出可以看到,代码调用的实际上是 JDK 中的 `public void println(Object x) ` 这个方法,查看源码如下:

    public void println(Object x) {
        String s = String.valueOf(x);
        if (getClass() == PrintStream.class) {
            // need to apply String.valueOf again since first invocation
            // might return null
            writeln(String.valueOf(s));
        } else {
            synchronized (this) {
                print(s);
                newLine();
            }
        }
    }

继续查看 valueOf 函数,看到其源码如下:

    public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

继续查看其中的 toString 函数:

public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

继续查看hashCode:

@IntrinsicCandidate
public native int hashCode();

此时发现,该方法是一个本地方法,由JVM开发者提供具体实现,本地方法暂不做讨论。至此,这个例子为什么输出一串奇怪的字符串就大致清楚了。

03 修改一下例子

下面,把之前的例子稍加修改,此时的输出就完全不同:

public class ExplorationJDKSource {
    public static void main(String[] args) {
        System.out.println(new A());
    }
}

//类A没有定义任何的成员
class A {
    @Override
    public String toString() {
        return "This is class A";
    }

}

此时,运行会输出“This is class A”。为啥呢?因为在修改后的代码中,我们重写了toString方法来返回我们想要的字符串表示形式,而不再去调用默认的方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值