1 字符串的声明与创建
学习String的第一步就是创建(声明)字符串,我们在这里之所以分为创建和声明(其实是一个意思,都是创建字符串,但两者却有本质的区别)是因为String是一个很特殊的类,它的对象产生在五种创建对象之外,还有另外一种方式,下面我们就来详细了解一下.
1.1 声明字符串
Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义的不可改变(final)的类,很自然的叫做String。在Java语言中字符串必须包含在一对””双引号中.例如:“23.3”,”adc”,”ad%-”,”1+3”.”你好,安静”,这些都是字符串常量(这里所说的常量并非是final的,不可更改的,而是可改变的,这里的改变也并非是改变字符串内容,而是改变String变量的指向),字符串常量是系统能够显示的任何文字信息,甚至是单个字符。在这里我们必须再强调一下凡是被双引号“”包含的都是字符串,不能作为其它数据类型使用,若要使用需要转型(可能出现类型转换异常),如上“1+2”如果输出的话是不会输出3的,输出结果是1+2.
可以通过一下语法格式来声明字符串:
String str = [null];
- String:描述符,指定该变量为字符串变量;
- str:任意有效的标识符,表示字符串变量的名称;
- =:赋值运算符,在数学上我们可以理解为等号,在内存层面我们可以理解为将str指向一个内存地址;
- Null:在这里表示赋值的内容,如果未赋值,默认是初始化为null,意思是String变量没有指向任何对象。否则表明声明的字符串值为null.注意:成员字段可以不初始化,虚拟机给予初始化,局部变量必须进行初始化.
package cn.stringPractise.create; //字符串创建的两种方法--声明字符串 public static void test1(){ String str1 = "abc"; String str2 = null; // String str3;如果没有初始化就是用,会提示未曾初始化错误 //System.out.println(str3);//The local variable str3 may not have been initialized System.out.println("str1:"+str1+"--hashcode:"+str1.hashCode()); System.out.println(str2); //获取hashcode,此时null根本没有分配内存,自然是一个空指针,下面会报出空指针异常 // System.out.println("str2:"+str2+"--hashcode:"+str2.hashCode()); java.lang.NullPointerException }
通过这种声明的方式创建的字符串对象会首先到字符串常量池中去寻找是否存在这样一个字符串,如果有那么直接返回该字符串的地址,否则会在字符串常量池中创建该字符串然后使String变量指向该字符串。
1.2 创建字符串
字符串除了可以直接赋值外,还可以由另一种方式来创建,下面我们来介绍一下.
在Java中将字符串作为对象来管理,因此可以像创建其它类对象一样来创建字符串对象。创建对象要用到构造方法。String类的常用构造方法如下:
String()
初始化新创建的 String
对象,使其表示空字符序列。
String(char a[],int offset,int length) 提取字符数组的一部分创建一个字符串对象.参数offset表示开始截取字符串的位置,length表示截取字符串的长度.
String(char[] value) 该构造方法可分配一个新的String对象,使其表示字符数组参数中所有的元素连接的结果。
String(byte[] bytes)
通过使用平台的默认字符集解码指定的字节数组来构造新的 String
。
String(byte[] bytes, Charset charset)
构造一个新的String
由指定用指定的字节的数组解码charset 。
String(byte[] bytes, int offset, int length)
通过使用平台的默认字符集解码指定的字节子阵列来构造新的 String
。
String(byte[] bytes, int offset, int length, Charset charset)
构造一个新的String
通过使用指定的指定字节子阵列解码charsetString(byte[] bytes, int offset, int length, String charsetName)
构造一个新的 String
通过使用指定的字符集解码指定的字节子阵列。除了上面几种使用String类的构造方法来创建字符串变量外,还可通过字符串常量的引用赋值给一个字符串变量,以及提供的其它构造方法等(关于构造函数,我们在本节的最后会简单总结一下)。
【例7.1.2.1】验证创建字符串的几种方式。
//字符串创建的第二种方法--创建字符串 public static void test2(){ char[] ch = {'花','褪','残','红','青','杏','小'}; //使用字符数组创建字符串对象 String str1 = new String(ch); System.out.println(str1); //提取字符数组中的一部分,创建爱你字符串对象 String str2 = new String(ch, 1, 3); System.out.println(str2); String str3 = str1; System.out.println(str3); System.out.println(str1 == str3); }
1.3 两种字符串创建的对比
上面我们分别针对两种字符串的声明进行了分析,下面我们在来看一段代码,然后进行内存分析:
//两种字符串创建的对比 public static void test3(){ String str1 = "123"; String str2 = new String("123"); String str3 = new String("234"); String str4 = "234"; System.out.println("str1:"+str1+"--hashcode:"+str1.hashCode()); System.out.println("str2:"+str2+"--hashcode:"+str2.hashCode()); System.out.println("str3:"+str3+"--hashcode:"+str3.hashCode()); System.out.println("str4:"+str4+"--hashcode:"+str4.hashCode()); String str5 = new String("123"); System.out.println("str5:"+str5+"--hashcode:"+str5.hashCode()); System.out.println((str2 == str5)+":"+(str1 == str2)); System.out.println((str2.equals(str5))+":"+(str1.equals(str2))); }
通过上面的比对,我们明白了一个问题,那就是hashcode一般情况下作为真实的内存地址的映射,我们知道String中”==”比较的是地址的内存地址,而equals()的源码如下:
public boolean equals(Object anObject) { if (this == anObject) {//此时通过对象地址判断,地址一样自然是同一个对象 return true; } if (anObject instanceof String) {//判断该对象是否是String对象 String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
String重写了Object的equals(),通过比对hashcode和字符串内容来确定equals的返回值,当他们有一点满足true就是true.下面我们来分析一下上面的test3()在内存中的情形:
美术功底太差,将就看吧,从上面我们可以看出,通过声明字符串的形式进行字符串的创建的时候,会先在字符串常量池中查看是否有这个字符串,如果有那么就将这个引用指向这个字符串的地址,如果没有那么就在字符串常量池中创建该字符串,并将指针指向该字符串的内存地址.
而通过new创建字符串则不同,它会在堆中创建一个字符串对象,然后才是在字符串常量池中查找是否有这个字符串,如果有那么会在堆中的对象中放入一个该字符串常量池中字符串的地址,如果没有在字符串常量池中创建字符串并将地址放在堆中对象里边,而我们的指针是指向堆中的字符串对象的.所以说通过声明方式创建字符串能够减少对象的创建和内存的消耗,这也是开发中我们建议采取和常用的做法。有了这个分析,我们思考一道面试题有关字符串常量池和String.intern;
1.4 String的构造方法简略
在1.2创建字符串中,我们简单了解了几种String的构造方法,本节我们将String的构造方法罗列出来,我们可以作为一个消遣内容,红色的为个人认为重要的构造方法,蓝色的是弃用的无需关注.
String()
初始化新创建的String
对象,使其表示空字符序列。String(byte[] bytes)
通过使用平台的默认字符集解码指定的字节数组来构造新的String
。String(byte[] bytes, Charset charset)
构造一个新的String
由指定用指定的字节的数组解码charset 。String(byte[] ascii, int hibyte)
已弃用 此方法无法将字节正确转换为字符。 从JDK 1.1开始,首选的方法是通过String
构造函数获取Charset
,字符集名称,或者使用平台的默认字符集。String(byte[] bytes, int offset, int length)
通过使用平台的默认字符集解码指定的字节子阵列来构造新的String
。String(byte[] bytes, int offset, int length, Charset charset)
构造一个新的String
通过使用指定的指定字节子阵列解码charset 。String(byte[] ascii, int hibyte, int offset, int count)
已弃用此方法无法将字节正确转换为字符。 从JDK 1.1开始,首选的方式是通过String
构造函数获取Charset
,字符集名称,或使用平台的默认字符集。String(byte[] bytes, int offset, int length, String charsetName)
构造一个新的String
通过使用指定的字符集解码指定的字节子阵列。String(byte[] bytes, String charsetName)
构造一个新的String
由指定用指定的字节的数组解码charset 。String(char[] value)
分配一个新的String
,以便它表示当前包含在字符数组参数中的字符序列。String(char[] value, int offset, int count)
分配一个新的String
,其中包含字符数组参数的子阵列中的字符。String(int[] codePoints, int offset 起始位置, int count)
分配一个新的String
,其中包含 Unicode code point数组参数的子阵列中的 字符 。String(String original)
初始化新创建的String
对象,使其表示与参数相同的字符序列; 换句话说,新创建的字符串是参数字符串的副本。String(StringBuffer buffer)
分配一个新的字符串,其中包含当前包含在字符串缓冲区参数中的字符序列。String(StringBuilder builder)
分配一个新的字符串,其中包含当前包含在字符串构建器参数中的字符序列。
1.5 空串与null串
字符串中关于空串和null串,是很容易弄混的一个地方,在这里特地说一下:
空串“”是长度为0的字符串。空串是一个Java对象,有自己的长度(0)和内容(空)。不过String变量还可以存放一个特殊的值,名为null,这表示目前没有任何对象与该变量关联,null也并非是String特有的,它是一个关键字,属于Java。要检查一个字符串是否为null,可以通过(string == null)判断。非空判断也是程序中我们常见的操作,我们通过下面代码来实现;
if(str != null && str.length() != 0)
关于空串,我们就讲解到这里;