几乎所有的项目开发过程之中都一定会存在String类的使用,但是String本身的定义有一些差别,以及在使用上是有一些注意事项的。
String类两种实例化方式
String可以直接采用赋值的形式进行处理,这一点感觉上和基本数据类型是非常相似的。
范例: 直接复制实例化对象
public class StringDemo {
public static void main(String args[]) {
//str 是一个对象,那么"hello"就应该保存在堆内存之中
String str = "hello";
System.out.println(str);
}
}
但是String本身毕竟是一个类。那么既然是类,类中就一定会提供有构造方法,而在String类里面恰好提供了以下的一个构造方法。
构造: public String(String str)
public class StringDemo {
public static void main(String args[]) {
// 该过程符合传统做法,有类之后通过关键字new进行对象实例化
String str = new String("hello");
System.out.println(str);
}
}
暂时不需要考虑这两者的区别以及使用,关键是要清楚String类现在提供有两种实例化对象的模式。
字符串相等比较
如果说现在有两个int型的变量判断其相等可以使用"=="
完成。
范例: 观察基本数据类型比较
public class StringDemo {
public static void main(String args[]) {
int x = 10;
int y = 10;
System.out.println(x == y);
}
}
那么如果说现在在String类的对象使用了"=="
呢?
范例: 观察String直接使用"=="
比较
public class StringDemo {
public static void main(String args[]) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == str2);
}
}
现在两个字符串的内容是相同的,而使用"=="
比较之后内容是不同的,于是要想得出结论,需要进行内存分析。
字符串常量是String类的匿名对象
在任何语言的底层上面都不可能会提供有直接字符串类型,现在所谓的字符串只是高级语言提供给用户方便开发的支持而已。所以在Java里面本身也没有提供有字符串常量的概念,所以使用“""”定义的内容本质上来讲都是String的匿名对象。
范例: 观察字符串操作
public class StringDemo {
public static void main(String args[]) {
String str = "hello";
System.out.println(str.equals("hello"));
System.out.println("hello".equals(str));
}
}
那么在之前出现的String str = "hello";
,本质上就是将一个匿名的String类对象设置有名字,而且匿名对象一定保存在堆内存之中。
提醒: 在日后的的开发的过程之中,如果要判断用户输入的字符串是否等同与指定的字符串,那么一定要将字符串写在前面。
-
比较的操作方法一:
public class StringDemo { public static void main(String args[]) { String input = null; System.out.println(input.equals("hello")); } }
在进行输入数据接受的时候必须考虑用户没有输入数据的问题,如果以上面的代码为例,用户真没有输入,则执行的时候一定会出现
NullPointerException
问题。 -
任何的字符串常量都是String匿名对象,所以该对象永远不可能为
null
。public class StringDemo { public static void main(String args[]) { String input = null; System.out.println("hello".equals(input)); } }
那么在以后进行比较的时候强烈建议如上的写法,把字符串写在前面。
String类两种对象实例化的区别
现在已经给出了String类的两种实例化操作模式,那么在实际的开发之中使用哪一种会更好,以及彼此之间的区别有哪些呢?
-
采用直接赋值
public class StringDemo { public static void main(String args[]) { String str = "hello"; } }
那么下面按照同样的模式继续进行新的字符串对象创建。
public class StringDemo { public static void main(String args[]) { String str = "hello"; String str2 = "hello"; String str3 = "hello"; System.out.println(str == str2); //true System.out.println(str == str3); //true System.out.println(str2 == str3); //true } }
以上是通过程序的运行结果来分析出内存关系,但是从另外一个方面来讲为什么现在并没有开辟新的堆内存空间。
String类的设计使用了一个共享设计模式。
在JVM底层实际上会自动维护一个对象池(字符串对象池),如果采用了直接复制的模式进行String类实例化操作,那么该实例化对象(字符串)将自动保存到这个对象池之中,如果下次继续有人使用了直接复制的模式声明了String类的对象,那么如果此时对象池之中有指定的内容,那么将直接进行引用,如果没有,则开辟新的字符串对象,而后将其保存在对象池之中以供下次使用。(所谓的对象池就是一个对象数组)。
范例: 观察与常量的比较
public class StringDemo { public static void main(String args[]) { String str = "hello"; System.out.println(str == "hello"); // true System.out.println("hello" == "hello"); // true } }
此时返回的全部都是
true
,这一方面是与共享设计有关,另一方面就是JDK的版本不同可能也会存在有若干差异。 -
采用构造方法
类对象使用构造方法进行实例化才属于标准做法,那么来分析如下的一段程序:
String str = new String("hello");
那么通过分析可以确认一点的是,如果使用了构造方法将会开辟两块堆内存空间,并且其中有一块堆内存空间将成为垃圾空间,除了这一缺点之外,实际会也会对字符串共享产生问题。范例: 观察字符串共享问题
public class StringDemo { public static void main(String args[]) { //该字符串常量并没有保存在对象池之中 String str = new String("hello"); String str2 = "hello"; System.out.println(str == str2); //false } }
那么这个时候并不表示不能够进入到对象池保存,关键的问题是:需要手工来处理,在String类里面有一个方法可以自动实现入池的操作:
public String intern();
public class StringDemo { public static void main(String args[]) { //该字符串常量并没有保存在对象池之中 String str = new String("hello").intern(); String str2 = "hello"; System.out.println(str == str2); //true } }
面试题:请解释String类中两种实例化的区别:
- 直接赋值:只会开辟一块堆内存空间,并且该字符串对象可以保存在对象池中以供下次使用。
- 构造方法:会开辟两块堆内存空间,一块将成为会成为垃圾,并且不会自动保存在对象池中,可以使用
intern()
方法手工入池。
字符串常量不可变更
所有的语言对于字符串的底层实现都是字符数组,数组的最大缺陷就是长度固定,所以在定义字符串常量的时候,本身它的内容是不可改变的。
范例: 观察以下代码
public class StringDemo {
public static void main(String args[]) {
String str = "hello";
str = str + " world";
str += "!!!";
System.out.println(str); // hello world!!!
}
}
那么以上的字符串的变更是字符串对象的变更,而并不是字符串的内容,以上的代码执行操作如下:
可以发现字符串上没有发生任何的变化,但是字符串对象的引用却一直在改变,而且会形成大量的垃圾,正是因为String的这个特点,所以如下的代码不应该在开发之中出现。
public class StringDemo {
public static void main(String args[]) {
String str = "hello";
for (int i = 0; i < 1000; i++) {
str += i;
}
System.out.println(str);
}
}
如果说现在有很多的用户都使用同样的操作,那么产生的垃圾数量就相当客观了。
总结
- 字符串的使用就采用直接赋值的模式完成。
- 字符串的比较就是用
equals()
方法实现。 - 字符串没事别改变太多。