一 字符串创建、判断相等/为空
1.1 创建字符串:双引号与new
字符串对象的创建很特殊,除了像一般对象那样通过new创建对象外,还能用双引号直接为字符串引用变量指定字符串对象的值。其实,考虑到字面值常量的概念,双引号的字符串内容其实也可以理解为一个引用(变量),只不过这个引用(变量)的名称和其指向的对象代表的值一样。如例子所示,双引号字符串可以像引用变量一样直接调用字符串类的方法,可见其确实是一个引用(变量)
System.out.println("abc".length()); // 3
引号创建,意味着在是在程序中写死的,在程序运行之前其对应的字符串对象就是确定的了。引号创建的字符串对象就是常量,会在程序加载时创建(猜的),而且被存放到JVM维护的字符串实例池。
new创建,则字符串对象是在程序运行时创建的,和普通对象一样,保存在堆中。
引号创建时,JVM会先在字符串实例池中是否已存在,如果已存在直接返回地址,不重复创建,所以多次创建实际为同一个对象。new创建时,总是会在堆中开辟新的内存区域,多次创建为不同对象。引号创建和new创建的字符串对象所在的内存区域都不同,二者自然不是同一个对象。
链接中博客说:new创建字符串对象时,也会先在字符串实例池中查找,如果没有,也会同时在字符串实例池中创建。那么下面这种情况也是吗?
String str1 = "abc";
String str2 = str1.substring(0,1);
System.out.println(str2);
str2也会在字符串实例池中创建吗?
总结:
在Java中与字符串相关的内存区域有三个:字符串实例池、堆、栈。代码中用双引号写明的字符串对象都存放在字符串实例池,new创建的字符串对象放在堆,字符串对象的引用放在栈。
补充:
- 考虑到字面值常量概念,那字符串对象存在的两种创建方式、判断值相等得用equals等问题其实也不算特殊。因为所有内置类型都存在字面值常量,所以其他内置对象也存在一样的问题,如下面例子所示。
- 之所以对其他内置类型感觉没这么明显,是因为:从理解上看,String对标的应该是Integer、Character这些对象;但在操作上,Java中为这些内置数据类型提供了其他关键字,如:int、char,所以Integer、Character就用的少了,问题就不突出了。但是字符串对应的只有String一个关键字。【int类型其实比String更复杂,具体见下面例子】
- 判断Integer对象的值是否相等使用的依然是equals(),可见equals相当重要。equals可是从Object中继承的方法。
- 字面值常量:1、2、‘a’、‘b’…保存在哪?常量池,具体参考以下博文:
Integer i = new Integer(1);
Integer i1 = 1;
Character ch = new Character('a');
Character ch1 = 'a';
String str = new String("abc");
String str1 = "abc";
System.out.println(i==i1); // false
System.out.println(ch==ch1); // false
System.out.println(str==str1); // false
System.out.println(i.equals(i1)); // true
System.out.println(ch.equals(ch1)); // true
System.out.println(str.equals(str1)); // true
再补充一个int的例子,这才叫跌破眼镜:
Integer i = new Integer(1);
Integer j = 1;
int k =1;
System.out.println(i==j); // false
System.out.println(i==k); // true
System.out.println(j==k); // true
1. 注意看,i和j不等,这在上面已经分析过了;但是二者竟然都能和k相等;注意看,是对象相等。
2. 换句话说:k==i、k==j,推不出i==j,怎么解释?
3. 个人理解这是操作符重载,==操作符在涉及int、char...时,不再表示对象相等,而是表示值相等。
4. 可见基本数据类型int、char等真是极其特殊,只用考虑值,不做对象考虑。
5. int i=1 和 int j=3-2,是等价的,没有区别,Integer i=1 和 Integer j=3-2,也是等价的,是同一个对象
1.2 字符串对象判断相等/为空:
字符串判断相等/为空这种说法本身是不明确的,应该说判断:字符串对象值相等、字符串对象相等、字符串对象值为空、字符串对象为空。
等号运算符"==",用于判断对象相等,即同一块内存单元。equals()方法,用于判断对象的值相等,所有类都存在equals方法(继承自Object)。
判断字符串对象相等:==
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2); // true
判断字符串对象值相等:equals
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1.equals(str2)); // true
判断字符串对象为空:== null
【注意null对象也能进行== 运算】
String str1 = null;
System.out.println(str1==null); // true
判断字符串对象值为空: equals("")、length()==0
【确定字符串对象不为null的情况下才能按以下写法】
String str1 = "";
System.out.println(str1.equals(""));
System.out.println(str1.length()==0);
【不确定是否为null,更稳妥的写法如下:】
String str1 = "";
System.out.println("".equals(str1)); // true
System.out.println(str1!==null && str1.length()==0); // true
综上所述:
1.与字符串对象本身相关的判断,都用==,且只能用==
2.与字符串对象值相关的判断,都用equals(),判断值为空也可以使用length()
补充:
什么叫对象为空?什么叫对象没有初始化?有什么区别?
- 对象为空并不是说对象没有值,而是说对象为空值,也即对象的值为null。这个null值,可以是手动赋的,也可以是第三方函数的返回结果,也可能是静态变量由JVM默认初始化的。
- 对象没有初始化说的是对象没有值,局部变量定义后没有赋初值就会出现这个问题。
- 调用没有初始化的对象是编译时问题,在IDE中就会报红;调用为空值的对象是运行时的问题,程序运行时才会报错。【空指针异常】
二 字符串常量和字符串变量
字符串变量变量的说法是错误的,应该叫字符串缓冲变量,为了好记,将错就错
2.1 String、StringBuffer、StringBuilder的区别
- String变量不应该叫字符串对象,而应该叫字符串对象的引用变量。实际上,创建任意对象时,都不应该把XXX叫做XXX对象,而应该叫做XXX对象的引用变量。对象存放在堆,引用变量存放在栈。
- 字符串对象(不是指引用变量)是一个常量,一旦创建值就不能改变,也就是所对应的内存单元的值不能改变,长时间不使用,会被垃圾回收;
- 对字符串对象的各种改变实际上是创建了新的字符串对象,分配了新的内存单元,并让原来的引用变量指向这个新的字符串对象。
根据上面的叙述,显而易见,程序中要频繁修改字符串对象的值时,使用字符串对象是很不利的。此时,应该使用字符串缓冲对象StringBuffer、StringBuilder,这两个(指向的对象)是可变的,不会产生新的对象,可在修改完成后再转为String对象。【不仅比String占内存小,执行的也更快】
StringBuffer和StringBuilder的区别在于:
二者方法和功能完全是等价的,区别在于:StringBuffer是线程安全的,有同步机制;StringBuilder不是线程安全的,但因此更快。单线程时优先使用StringBuilder,多线程时使用StringBuffer。
2.2 StringBuffer、StringBuilder
二者API一致,且访问对象的操作与String对象也类似,主要是使用修改操作,包括:append、insert、delete、replace、reverse。
StringBuilder stringBuilder = new StringBuilder("123");
stringBuilder.append("456"); // 123456
stringBuilder.insert(0,"abc"); // abc123456
stringBuilder.delete(0,2); // c123456
stringBuilder.replace(0,1,"xyz"); // xyz123456
stringBuilder.reverse(); // 654321zyx
System.out.println(stringBuilder.toString());
注意:
1.String类没有reverse方法,StringBuilder才有。在字符串表示大数时,可能涉及reverse操作,因为数值高位在左边,而数组高位在右边。
2.append(),参数可以是string,也可以是char
三 字符串常用操作
3.1 字符串与字符数组转换
String s = "123";
char[] chars = s.toCharArray();
String str = String.valueOf(chars);
注意:
若用字符串表示数值,在转为字符数组时要特别注意,数值高位在数组低位
3.2 字符串截取
四 正则表达式