拾肆——关于类与关键字的专题研究
一、众类鼻祖——Object 类
Object 类是 Java 中一个比较特殊的类,它是类层次结构的根,位于继承树的顶层,即 Java 中所有的类从根本上都继承自 Object 类。它是 Java 中唯一没有父类的类。如果一个类没有使用 extends 关键字明确标识继承另外一个类,那么这个类就默认继承 Object 类,因此,Object 类是 Java 类层中的最高层类,是所有类的超类。换句话说,Java 中任何一个类都是它的子类。由于所有的类都是由 Object 类衍生出来的,所以 Object 类中的方法适用于所有类,因此下面的两种类的定义形式,从本质上讲是完全一样的。
public class Person //当没有指定父类时,会默认Object类为其父类
{
……
}
等价于:
public class Person extends Object
{
……
}
Object 类中常用方法及操作功能如下表所示:
返回类型 | 方法名称 | 输入参数 | 抛出异常 | 操作功能 |
构造方法 | Object | 创建 Object 对象 | ||
Object | clone | CloneNotSupportedException | 创建并返回此对象的一个副本 | |
boolean | equals | Object | 提示其他某个对象是否与此对象 “ 相等 ” | |
void | finalize | Throwable | 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法 | |
Class | getClass | 返回此 Object 的运行时类 | ||
int | hashCode | 返回该对象的哈希码值 | ||
void | notify | IllegalMonitorStateException | 唤醒在此对象监视器上等待的单个线程 | |
void | notifyAll | IllegalMonitorStateException | 唤醒在此对象监视器上等待的所有线程 | |
void | wait | InterruptedException | 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待 | |
String | toString | 返回该对象的字符串表示 | ||
void | wait | long | InterruptedException | 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待 |
void | wait | long,int | InterruptedException | 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待 |
1.取得对象信息:toString()
Object 类的 toString() 方法是打印对象时被调用,将对象信息变为字符串返回。默认的 toString() 方法有一个特点:为了适用于所有的子类,toString() 在默认情况下输出对象地址,当然,每一个子类也可以自己进行修改。
举例:
//Object类的使用
class Person extends Object
{
String name = "张三";
int age = 25;
}
class ObjectDemo1
{
public static void main(String[] args)
{
Person p = new Person();
System.out.println(p);//直接输出对象
System.out.println(p.toString());
}
}
对象输出时,会默认调用 Object 类的 toString() 方法,将对象信息变为字符串返回。下面举例覆写 Object 类中的 toString() 方法。
//覆写Object类的方法
class Person extends Object
{
String name = "张三";
int age = 25;
//覆写 Object类中的toString()方法
public String toString()
{
return "name:" + this.name + "age:" + this.age;
}
}
class OverrideObject
{
public static void main(String[] args)
{
Person p = new Person();
System.out.println(p);//直接输出对象
}
}
与上一个示例相比,此例在 Person 类中明确覆写了 toString() 方法,使输出清晰明了。
2.对象相等判断方法:equals()
Object 类是所有类的父类,其中的 toString() 方法是可以被覆写的,在 Object 类中还有一个比较重要的方法:equals(),此方法用于比较对象是否相等,而且此方法必须被覆写。
如果没有覆写,则两个比较内容分别在不同的内存空间指向了不同的内存地址。在用 equals 对两个对象进行比较时,实际上是比较两个对象的地址。
举例:
//equals方法的覆写
class Person
{
private String name;
private int age;
public Person(String name,int age)
{
this.name = name;
this.age = age;
}
//覆写父类(Object类)中的equals方法
public boolean equals(Object o)
{
boolean temp = true;
//声明 p1对象,此对象实际上就是当前调用equals方法的对象
Person p1 = this;
//判断Object类对象是否是Person的实例
if(o instanceof Person)
{
//如果是Person类实例,则进行向下转型
Person p2 = (Person)o;
//调用String类中的equals方法
if(!(p1.name.equals(p2.name)&&p1.age==p2.age))
{
temp = false;
}
}else{
//如果不是Person类实例,则直接返回false
temp = false;
}
return temp;
}
}
class ObjectDemo
{
public static void main(String[] args)
{
Person t_p1 = new Person("张三", 25);
Person t_p2 = new Person("张三", 25);
//判断p1和p2的内容是否相等
System.out.println(t_p1.equals(t_p2)?"相同":"不同");
}
}
通过覆写后的 equals 方法能够准确的对两个对象进行比较,所以在开发中往往需要覆写 equals 方法。
3.对象签名 hashcode()
Object 类有两种方法来推断对象的标识:equals() 和 hashcode()。如果根据 equals() 方法判断两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode() 方法都必然生成相同的整数结果。但是反过来,如果两个 hashCode() 返回的结果相等,两个对象的 equals() 方法却不一定相等。在默认情况下 equals() 方法用来比较两个对象的地址值,而原始的 hashCode() 方法用来返回其所在对象的物理地址。
举例:
//比较两个对象的hashCode
class Person
{
int id; //编号
String name; //姓名
public Person(int id,String name)
{
this.id = id;
this.name = name;
}
public int hashCode() //覆写hashCode方法
{
return id*(name.hashCode());
}
public boolean equals(Object o) //覆写equals方法
{
Person p = (Person)o;
return(this.id==p.id)&&(this.name.equals(p.name));
}
}
public class ObjectHashCode
{
public static void main(String[] args)
{
Person p1 = new Person(1,"张三");
Person p2 = new Person(2,"李四");
Person p3 = new Person(3,"王五");
System.out.println(p1.equals(p2)); //输出p1与p2比较的结果
System.out.println(p1.equals(p3)); //输出p1与p3比较的结果
System.out.println(p1.hashCode()); //输出p1的hashCode
System.out.println(p2.hashCode()); //输出p2的hashCode
System.out.println(p3.hashCode()); //输出p3的hashCode
}
}
由于重写了 equals(),此时比较的不再是对象地址而是对象内容。此时 p1 和 p2 的 hash 值相同,p1 和 p3 的 hash 值是不同的。由于重写了 hashCode(),此时返回的不再是地址,而是根据对象内容算出的一个值。
在覆写 equals() 方法的时候也必须覆写 hashCode() 方法。这样才能确保相等的两个对象拥有相等的 .hashCode。
4.使用 Object 接收任意引用数据类型对象
由于 Object 类是所有类的父类,所有类的对象都可以使用 Object 接收,Object 类不光可以接收对象,还可以接收任意的引用数据类型( 类、接口、数组 )。
举例:
//使用Object接收数组
public class ObjectArray
{
public static void main(String[] args)
{
int temp[] = {1,3,5,7,9}; //定义数组
Object obj = temp; //使用Object接收数组
print(obj);
}
public static void print(Object o)
{
if (o instanceof int[]) { //判断是否是整型数组
int x[] = (int[])o;
for (int i = 0; i < x.length; i++) {
System.out.print(x[i] + "\t");
}
}
}
}
虽然数组和 Object 类之间是不存在任何的定义关系的,但是因为数组是引用类型,所以照样可以使用 Object 接收。
接口和 Object 更不会有任何的关系,因为接口本身不可能去继承任何的一个类,可是在 Java 设计的时候由于很多地方都需要接口的支持,所以 Object 依然可以接收接口类型。
举例:
//使用Object接收接口对象
interface A
{
public String getInfo();
}
class B implements A
{
public String getInfo()
{
//覆写方法
return "Hello";
}
}
public class ObjectInterface
{
public static void main(String[] args)
{
A a = new B(); //向上转型,为接口实例化
Object obj = a; //使用Object接收,向上转型
A x = (A)obj; //向下转型
System.out.println(x.getInfo());
}
}
只要是引用类型,都可以通过 Object 接收,可以先把 a 向上转型为 Object 类型,因为 Object 是所有类的父类,也就是基类,所以所有的类都可以向上转型成为 Object,然后又将 obj 强制转回 A。
二、内部类
所谓的内部类就是指在一个类的内部又定义了其他类的情况。如果在 Outer 类的内部在定义一个 Inner 类,此时 Inner 类就称为内部类,而 Outer 类则称为外部类。内部类可声明为 public 或 private。当内部类声明为 public 或 private 时,对其访问的限制与成员变量和成员方法完全相同。
1.内部类的基本定义
内部类的定义格式如下:
标识符 class 外部类的名称
{
//外部类的成员
标识符 class 内部类的名称
{
//内部类的成员
}
}
内部类主要有如下作用:
(1)内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。
(2)内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以相互访问。但外部类不能访问内部类的实现细节,例如内部类的成员变量。
(3)匿名内部类适合用于创建那些仅需要一次的类。
举例:
//内部类的使用
class Outer
{
int score = 100;
void inst()
{
Inner in = new Inner();
in.display();
}
public class Inner
{
//在内部类中声明一个name属性
String name = "张三";
void display()
{
//输出外部类中的属性
System.out.println("成绩:"+score);
}
}
}
public class ObjectInnerDemo
{
public static void main(String[] args)
{
Outer outer = new Outer();
outer.inst();
}
}
内部类 Inner 可以直接调用外部类 Outer 中的 score 属性,现在如果把内部类拿到外面来单独声明,那么在使用外部类中的 score 属性时,则需要先产生 Outer 类的对象,再由对象去调用 Outer 类的 score 属性。
由于使用了内部类操作,所以程序在调用 score 属性的时候减少了创建对象的操作,从而省去了一部分的内存开销。但是内部类在声明时,会破坏程序的结构,因此在开发中不建议使用。
2.使用 static 定义的内部类就是外部类
如果使用 static 来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用 static 修饰的内部类被称为类内部类,有的地方也称为静态内部类。
static 关键字的作用是把类的成员变成类相关,而不是实例相关,即 static 修饰的成员属于整个类,而不属于单个对象。
静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实力成员,只能访问外部类的静态成员。
静态内部类是外部类的一个静态成员,因此外部类的静态方法、静态初始化也可以使用静态内部类来定义变量、创建对象等。
外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。
举例:
//用static声明的内部类访问非static的外部类属性
class StaticInnerClassTest
{
private int p1 = 5;
private static int p2 = 10;
void display()
{
StaticInnerClass in = new StaticInnerClass();
in.display();
}
static class StaticInnerClass
{
//静态内部类里可以包含静态成员
private static int age;
public void display()
{
//下面代码出现错误
//静态内部类无法访问外部类的实例成员
System.out.println(p1);
//下行代码正常
System.out.println(p2);
}
}
}
public class ObjectStaticDemo
{
StaticInnerClassTest outer = new StaticInnerClassTest();
outer.display();
}
由于内部类 StaticInnerClass 声明为 static 类型,所以无法访问外部类中的非 static 类型属性 p1,但是可以访问外部类中的 static 类型属性 p2。
3.在方法中定义内部类
内部类不仅可以在类中定义,也可以在方法中定义。
举例:
//在方法中定义内部类
class InnerClass Test
{
int score = 100;
void inst()
{
class Inner
{
void display()
{
System.out.println("成绩:" + score);
}
}
Inner in = new Inner();
in.display();
}
}
public class ObjectInnerClass
{
public static void main(String[] args)
{
InnerClassTest outer = new InnerClassTest();
outer.inst();
}
}
三、匿名内部类
匿名内部类由于没有名字,所以它的创建方式也比较特别。创建格式如下:
new 父类构造器(参数列表)|实现接口()
{
//匿名内部类的类体部分
}
使用匿名内部类必须要继承一个父类或者实现一个接口。同时它没有 class 关键字,这是因为匿名内部类是直接使用 new 来生成一个对象的引用。当然这个引用是隐式的。
举例:
//匿名内部类使用实例
abstract class Bird
{
private String name;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public abstract int fly();
}
public class AnonymousInnerClass
{
public void test(Bird bird)
{
System.out.println(bird.getName()+"能飞:"+bird.fly()+"米");
}
public static void main(String[] args)
{
AnonymousInnerClass test = new AnonymousInnerClass();
test.test(new Bird()
{
public int fly()
{
return 1000;
}
public String getName()
{
return "鸟儿";
}
});
}
}
在 Test 类中,test() 方法接受一个 Bird 类型的参数,同时我们知道一个抽象类是没有办法直接 new 的,我们必须要先有实现类才能 new 出来它的实现类实例。所以在 main 方法中直接使用匿名内部类来创建一个 Bird 实例。
匿名内部类不能是抽象类,所以必须要实现它的抽象父类或者接口里面所有的抽象方法。
匿名内部类存在一个缺陷,就是它仅能被使用一次,创建匿名内部类时它会立即创建一个该类的实例,该类的定义会立即消失,所以匿名内部类不能够被重复使用。
四、匿名对象
匿名对象,顾名思义就是没有明确的声明的对象。我们可以简单的理解为只使用一次的对象,即没有任何一个具体的对象名称引用它。如下所示:
//匿名对象的使用
class Person
{
private String name = "张三";
private int age = 25;
public String talk()
{
return "name:" + name + ",age:" + age;
}
}
public class AnonymousObject
{
public static void main(String[] args)
{
System.out.println(new Person().talk());
}
}
我们可以看到用 new Person() 声明的对象并没有赋给任何一个 Person 类对象的引用,所以此对象只使用了一次,之后就会被 Java 的垃圾收集器回收。
五、this 关键字的使用
在整个 Java 的面向对象程序设计中,this 是一个比较难理解的关键字。不过我们只要遵循一个原则即可,就是 this 表示当前对象,而所谓的当前对象就是指调用类中方法或属性的那个对象。
举例:
//利用this判断两个对象是否相等
class Person
{
String name;
int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
boolean compare(Person p)
{
if (this.name.equals(p.name)&&this.age==p.age)
{
return true;
}else{
return false;
}
}
}
public class ThisCompareDemo
{
public static void main(String[] args)
{
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
System.out.println(p1.compare(p2)?"相等":"不相等");
}
}
由此代码可不难理解 this 是表示当前对象这一重要概念,所以程序的最后会输出 “ 相等 ” 的正确判断信息。
如果在程序中想用某一个构造方法调用另一个构造方法,也可以用 this 来实现。具体调用形式如下:
this();
举例:
//用this调用构造方法
class Person
{
String name;
int age;
public Person()
{
System.out.println("1.public Person()");
}
public Person(String name,int age)
{
//调用本类中无参构造方法
this();
this.name = name;
this.age = age;
System.out.println("2.public Person(String name,int age)");
}
}
public class ThisConstructor
{
public static void main(String[] args)
{
new Person("张三", 20);
}
}
从中可以看到,虽然在前面调用了 Person 中有两个参数的构造方法,但由于使用了 this() 调用本类中的无参构造方法,所以程序先去执行 Person 中的无参构造方法,之后再去继续执行其他的构造方法。
构造方法是在实例化一个对象时被自动调用的,也就是说在类中的所有方法里,只有构造方法是被有限调用的,所以使用 this 调用构造方法必须也只能放在类中,不能随意调换 this() 调用的无参构造方法的位置。
六、static 关键的使用
在 Java 之中,使用 static 关键字可以定义属性和方法。
1.使用 static 定义属性
在程序中如果用 static 定义属性的话,则此变量称为静态属性。
举例:
//static关键字的使用
class Person
{
String name;
static String city = "北京";
int age;
Person(String name,int age)
{
this.name = name;
this.age = age;
}
public String talk()
{
return "姓名:" + this.name + "年龄:" + this.age + "城市:" + city;
}
}
public class StaticDemo
{
public static void main(String[] args)
{
Person p1 = new Person("张三", 20);
Person p2 = new Person("李四", 25);
Person p3 = new Person("王五", 30);
System.out.println("修改之前信息:"+p1.talk());
System.out.println("修改之前信息:"+p2.talk());
System.out.println("修改之前信息:"+p3.talk());
System.out.println("——————修改之后信息——————");
//修改后的信息
p1.city = "上海";
System.out.println("修改之后信息:"+p1.talk());
System.out.println("修改之后信息:"+p2.talk());
System.out.println("修改之后信息:"+p3.talk());
}
}
只修改了一个对象的 city 属性,在再次输出时,可以使全部的 city 属性发生更改,这说明用 static 声明的属性是所有对象共享的。
另外需要注意的是,用 static 方式声明的属性,也可以用类名直接访问。如果想修改 city 属性中的内容,可以用如下的方式:
Person.city = "上海";
所以有些地方也把用 static 类型声明的变量称之为 “ 类变量 ”。
既然 static 类型的变量是所有对象共享的内存空间,也就是说无论最终有多少个对象产生,也都只有一个 static 类型的属性。
2.使用 static 定义方法
static 既可以在声明变量时使用,也可以用其来声明方法,用它声明的方法有时也被称为 “ 类方法 ”。使用 static 定义的方法可以由类名称直接调用。如下所示:
//静态方法的声明
class Person
{
String name; //定义name属性
private static String city = "北京"; //定义静态属性city
int age; //定义age属性
Person(String name,int age) //声明一个有参的构造方法
{
this.name = name;
this.age = age;
}
public String talk() //声明了一个talk()方法
{
return "姓名:" + this.name + "年龄:" + this.age + "城市:" + city;
}
public static void setCity(String c) //声明一个静态方法
{
city = c;
}
}
public class StaticMethod
{
public static void main(String[] args)
{
Person p1 = new Person("张三", 20);
Person p2 = new Person("李四", 25);
Person p3 = new Person("王五", 30);
System.out.println("修改之前信息:"+p1.talk());
System.out.println("修改之前信息:"+p2.talk());
System.out.println("修改之前信息:"+p3.talk());
System.out.println("——————修改之后信息——————");
//修改后的信息
Person.setCity = "上海";
System.out.println("修改之后信息:"+p1.talk());
System.out.println("修改之后信息:"+p2.talk());
System.out.println("修改之后信息:"+p3.talk());
}
}
在使用 static 类型声明的方法时需要注意的是,如果在类中声明了一个 static 类型的属性,则此属性既可以在非 static 类型的方法中使用,也可以在 static 类型的方法中使用。若要用 static 类型的方法调用非 static 类型的属性,就会出现错误。
3.static 主方法( main )
如果一个类要被 Java 解释器直接装载运行,这个类中必须有 main() 方法。
由于 Java 虚拟机需要调用类的 main() 方法,所以该方法的访问权限必须是 public,又因为 Java 虚拟机在执行 main() 方法时不必创建对象,所以该方法必须是 static 的,该方法接收一个 String 类型的数组参数,该数组中保存执行 Java 命令时传递给所运行的类的参数。
向 Java 中传递参数可以使用如下的命令。
java 类名称 参数1 参数2 参数3
举例:
//向类中传递参数
public class TestMain
{
/*
* public:表示公共方法
* static:表示此方法为一个静态方法,可以由类名直接调用
* void:表示此方法无返回值
* main:系统定义的方法名称
* String args[]:接收运行时参数
*/
public static void main(String[] args)
{
//取得输入参数的长度
int j = args.length;
if (j!=2) {
System.out.println("输入参数个数有错误");
//退出程序
System.exit(1);
}
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
所有接收的参数都被存放在 args[] 字符串数组之中,用 for 循环可输出全部内容。
4. static应用——static 代码块及静态导入
一个类可以使用不包含在任何方法体中的静态代码块,当类被载入时,静态代码块被执行,且只执行一次。静态代码块经常用来进行类属性的初始化。
举例:
//静态代码块的使用
class Person
{
public Person()
{
System.out.println("1.public Person()");
}
//此段代码会首先被执行
static
{
System.out.println("2.Person类的静态代码块被调用");
}
}
public class TestStaticDemo
{
//运行本程序时,静态代码块会被自动执行
static
{
System.out.println("3.测试类的静态代码块被调用");
}
public static void main(String[] args)
{
System.out.println("4.程序开始执行");
//产生两个实例化对象
new Person();
new Person();
}
}
放在 TestStaticDemo 类中的静态代码块首先被调用,这是因为程序首先执行 TestStaticDemo 类,所以此程序的静态代码块会首先被执行。而 Person 类中的静态代码块只执行了一次,并且静态代码块优先于静态方法,由此可知,静态代码块可以对静态属性初始化。
七、final 关键字的使用
final 在 Java之中称为终结器,在 Java 之中 final 可以做三件事情,定义类、定义方法、定义变量。
(1)final 标记的类不能被继承。
(2)final 标记的方法不嗯能够被子类覆写。
(3)final 标记的变量( 成员变量或局部变量 )即为常量,只能赋值一次。
举例:
//final标记的变量只能赋值一次实例
class TestFinalDemo
{
public static void main(String[] args)
{
final int i = 10;
//修改用 final修饰的变量 i
i++;
}
}
final 修饰的变量为终态局部变量,对于终态局部变量,不能进行赋值操作。
举例:
//final标记的方法不能被子类覆写实例
class Person
{
//此方法声明为 final不能被子类覆写
final public String talk()
{
return "Person:talk()";
}
}
class Student extends Person
{
public String talk()
{
return "Student:talk()";
}
}
public class TestFinalDemo
{
public static void main(String[] args)
{
Student s1 = new Student();
System.out.println(s1.talk());
}
}
八、instanceof 关键字的使用
可以用 instanceof 判断一个类是否实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。instanceof 的语法格式如下:
对象 instanceof 类(或接口)
它的返回值是布尔型的,或真( true )或假( false )。
举例:
//instanceof关键字使用实例
class Person
{
public void fun1()
{
System.out.println("1.Person{fun1()}");
}
public void fun2()
{
System.out.println("2.Person{fun2()}");
}
}
//Student类继承自Person类,也就继承了Person类中的fun1()、fun2()方法
class Student extends Person
{
//在这里覆写了Person类中的fun1()方法
public void fun1()
{
System.out.println("3.Person{fun1()}");
}
public void fun3()
{
System.out.println("4.Person{fun2()}");
}
}
class TestInstance
{
public static void main(String[] args)
{
//声明一父类对象并通过子类对象对其进行实例化
Person p = new Student();
//判断对象 p是否是 Student类的实例
if (p instanceof Student) {
//将 Person类的对象 p转型为 Student类型
Student s = (Student)p;
s.fun1();
}else{
p.fun2();
}
}
}
用 instanceof 关键字判断 p 对象是否是 Student 的实例,因为 p 是通过 Student 类实例化的,所以此条件满足。然后将 p 对象强制转换为 Student 类的对象,并调用 fun1() 方法时,实际上调用的是被子类覆写了的 fun1() 方法。
九、本文注意事项
1.匿名类使用注意事项
(1)使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
(2)匿名内部类中是不能定义构造函数的。
(3)匿名内部类中不能存在任何的静态成员变量和静态方法。
(4)匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
(5)匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
2. static 的两大特点
static 的最大特点主要有两个:
(1)static 属性或方法可以由类名称直接调用。
(2)static 属性是一个共享属性。
3.深入理解 final 的好处
(1)final 关键字提高了性能。JVM 和 Java 应用都会缓存 final 变量。
(2)final 变量可以安全的在多线程环境下进行共享,而不需要额外的同步开销。
(3)使用 final 关键字,JVM 会对方法、变量及类进行优化。
(4)创建不可变类要使用 final 关键字。不可变类是指它的对象一旦被创建了就不能被更改了。String 是不可变类的代表。不可变类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等。