String 是一个字符串类型的类,使用双引号定义的内容都是字符串,但是 String 本事是一个类,使用上会有一些特殊,但是我们必须从类的角度与内存关系上来分析这个类的作用。
在之前使用过 String,最早使用的时候是直接采用了 " String 变量 = “字符串”;"语法形式定义的。这也称为 直接赋值。
public class StringDemo{
public static void main(String args[]){
String str = "Hello World!";
System.out.println(str);
}
}
以上就是 String 对象的直接赋值,代码并没有使用关键字 new 进行。在 String 类里面实际上也定义了构造方法。
构造方法:public String(String str),在构造方法里面依然要接收一个本类对象;
举例:利用构造方法实例化
public class StringDemo{
public static void main(String args[]){
String str = new String("Hello World");
System.out.println(str);
}
}
String 类有两种形式,主观上会认为第二种构造方法的形式更加适合于我们,因为只要是类就要用关键字 new 的做法似乎很符合道理的。
通过内存关系比较,理解 == 和 equals() 的区别。
判断两个基本数据类型的数据是否相等,可以使用 “ == ” 来完成。
但是需要知道的是在 String 上也可以使用 “ == ” 比较,那比较的结果呢?
举例:
public class StringDemo{
public static void main(String args[]){
String stra = "hello";
String strb = new String("hello");
String strc = strB;
System.out.println(stra == strb); // false
System.out.println(stra == strc); // false
System.out.println(strb == strc); // true
}
}
从内存关系分析来看,== 比较的是内存地址的数值,并不是字符串包含的内容。所以 == 属于数值比较,比较的是内存地址。
而如果想比较字符串内容的话,可以使用 String 类里定义的方法:
比较内容(与原始定义有一些差别):public boolean equals(String str);
举例:实现内容比较
public class StringDemo{
public static void main(String args[]){
String strA = "Hello World";
String strB = new String("Hello World");
String strC = strB;
System.out.println(strA.equals(strB)); // true
System.out.println(strA.equals(strC)); // true
System.out.println(strB.equals(strC)); // true
System.out.println(strA == strB); // false
System.out.println(strA == strC); // false
System.out.println(strB == strC); // true
}
}
此时实现了字符串内容的比较,在以后的开发之中,只要是进行字符串的判断,千万不要使用 == 完成。
面试题:请解释在字符串比较中, == 与 equals() 的区别。
举例:观察字符串是匿名对象的验证
public class StringDemo{
public static void main(String args[]){
String str = "Hello";
System.out.println("Hello".equals(str));
}
}
那么所谓的直接赋值实际上就是相当于将一个匿名对象设置了一个名字。但是唯一的区别是: String 类的匿名对象是由系统自动生成的,不再由用户自己创建。
小小技巧:为了避免空指向异常的出现,如果要判断输入的内容是否是某一字符串,请一定要将字符串写在前面调用方法。
直接赋值就是将一个字符串的匿名对象设置了一个名字。
String str = "Hello";
此时在内存之中会开辟一块堆内存,并且由一块栈内存指向此堆内存。
但是使用直接赋值还需要多观察一下。
public class StringDemo{
public static void main(String args[]){
String strA = "Hello";
String strB = "Hello";
String strC = "Hello";
System.out.println(strA == strB); // true
System.out.println(strA == strC); // true
System.out.println(strB == strC); // true
}
}
发现以上的最终结果是:所有的采用直接赋值的 String 类对象的内存地址完全相同,即 strA、strB、strC 指向了同一块堆内存空间。
共享设计模式:在 JVM 的底层实际上会存在一个对象池(不一定只保存 String 对象),当代码之中使用了直接赋值的方式定义了一个 String 类对象时,会将此字符串对象所使用的匿名对象入池保存,而后如果后续还有其它 String 类对象也采用直接赋值方式,那么将不会开辟新的堆内存空间,而是使用已有的对象引用的分配,继续使用。
构造方法如果要使用一定要用关键字 new ,一旦使用了关键字 new 就表示要开辟新的堆内存空间。
public class StringDemo{
public static void main(String args[]){
String strA = new String("Hello");
String strB = new String("Hello");
String strC = new String("Hello");
System.out.println(strA == strB); // false
System.out.println(strA == strC); // false
System.out.println(strB == strC); // false
}
}
通过内存分析发现,如果使用的是构造方法方式进行 String 类对象实例化的时候,开辟了两块堆内存空间,并且其中有一块堆内存空间将成为垃圾空间。
除了内存的浪费之外,如果使用了构造方法定义的 String 类对象,其内容不会保存到对象池之中,因为是使用了关键字 new 开辟了新内存。如果希望开辟的新内存数据也可以进行对象池的保存,那么可以采用 String 类定义的手工入池的方法: public String intern();
举例:手工入池
public class StringDemo{
public static void main(String args[]){
String strA = new String("Hello").intern();
String strB = "Hello";
System.out.println(strA == strB); // true
}
}
面试题:请解释 String 类对象实例化的两种方式的区别?
工作中,使用直接赋值方式。
字符串一旦定义则不可改变。
观察一段代码:
public class StringDemo{
public static void main(String args[]){
String str = "Hello";
str += " World";
str += "!!!";
System.out.println(str);
}
}
运行结果是: Hello World!!!
以上代码最终结果实际上 str 对象的内容被改变了,下面通过内存关系分析:
以上的操作发现,所谓的字符串的内容根本就没有改变( Java 就定义好了 String 的内容不能够改变),而对于字符串对象内容的改变是利用了引用关系的变化而实现的,但是每一次的变化都会产生垃圾空间。所以 String 类的内容不要频繁的修改。