《Java从入门到失业》第四章:类和对象(4.2):String类

2020-09-27 14:59:54 浏览数 (1)

4.2String类

       这一节,我们学习第一个类:String类。String翻译成汉语就是“字符串”,是字符的序列。我们知道,在Java中,默认采用Unicode字符集,因此字符串就是Unicode字符的序列。例如字符串“Java大失叔”,就是由7个Unicode字符‘J’、‘a’、‘v’、‘a’、‘大’、‘失’、‘叔’组成。在JDK中,把字符串抽象成一个类String提供给我们使用。String类在java.lang包中。

4.2.1构造String对象

  上面我们说了,想看电视得先买一台电视,电视在出厂的时候厂家会初始化它的状态。想使用String类,得先得到一个String的对象,然后指定属性的初始状态,然后才能使用它。得到对象的过程,叫做构造对象。在Java中,我们用构造器(constructor)来构造实例,构造器其实是一种特殊的方法,用来构造并初始化对象。我们采用在构造器前面加上new关键字来实现,例如:

new 构造器();

我们查看String类的API文档(怎么查这里不再赘述),构造方法截图如下:

发现String类的构造方法有几个特点:

  • 足足有15个构造方法
  • 有的方法上标有Deprecated,这个标签的含义是不推荐使用,将来在新版本中可能会移除
  • 构造方法的名字和类名相同

构造方法的名字和类名相同,这是Java构造器的特点,也是规定。我们挑选其中一个构造方法:String(char[] value)

我们看到,这其实就是用一个char数组来构造一个字符串,那么首先我们得有一个char数组才行,例如我们想要得到一个字符串“Java大失叔,你真棒”。那么代码如下:

代码语言:javascript复制
char[] a = { 'J', 'a', 'v', 'a', '大', '失', '叔', ',', '你', '真', '棒' };  
String s = new String(a);  
System.out.println(s);// 结果输出:Java大失叔,你真棒 

事实上,由于String太常用了,Java给我们提供了更加简便的构造方法,直接用双引号将一段字符序列包起来,就得到了一个String的实例:

代码语言:javascript复制
String s = "Java大失叔,你真棒";  

OK,我们得到了一个String对象了,下面我们来使用这个对象。我们可以看到,API中有几十个方法,我们挑选一些常用的演示一下。

4.2.2代码点和代码单元

       首先,我们回忆一下关于char和Unicode的知识。Unicode定义了U 0000到U 10FFFF一共1114112个码位(code point),英文直译为代码点。一个代码点表示一个字符。char是用来存放UTF-16编码中的一个代码单元(code unit),即2个字节。平面0的代码点用一个代码单元即一个char就可以表示,其余的代码点需要用2个代码单元即2个char才能表示。

       我们知道Stirng是Unicode字符的序列,但是底层的实现实际上是用char构成的。String类提供了一些关于代码点和代码单元相关的方法,请看下面摘抄的几个方法:

修饰和类型

方法

描述

int

length()

返回字符串的长度

int

codePointCount(int beginIndex, int endIndex)

返回beginIndex和endIndex-1之间的代码点的数量。

char

charAt(int index)

返回index索引处的char

int

codePointAt(int index)

返回index索引处的代码点

我们想获得字符的数量(即代码点的数量),需要用codePointCount方法,而length方法返回的是char的数量(即代码单元的数量)。调用对象的方法很简单,用如下形式:

对象.方法();

代码示例如下:

代码语言:javascript复制
String s = "大失叔喜欢打麻将??????";//  
System.out.println("字符串s的代码单元数量为:"   s.length());  
System.out.println("字符串s的代码点数量为"   s.codePointCount(0, s.length())); 

输出结果:

代码语言:javascript复制
字符串s的代码单元数量为:20  
字符串s的代码点数量为:14 

我们可以看到,对于??????,这6个字符,每个字符占用2个代码单元,所以length方法的结果是20,而codePointCount方法的结果是14。

       我们再看看后面2个方法,这应该就相对简单了,一个是返回index处的代码单元,一个是返回index处的代码点。我们直接看代码:

代码语言:javascript复制
String s = "大失叔喜欢打麻将??????";//  
int c = s.charAt(8);// 把char赋值给一个int,对应这个代码单元对应的十进制,结果是55356,十六进制为0xD83C
int d = s.codePointAt(8);// 结果是126976,十六进制为0x1F000 

4.2.3对象与变量

       上面我们看到,创建出来一个String对象,一般我们会赋值给一个变量。那么对象和变量之间有什么关系和区别呢?我们先看几行代码:

代码语言:javascript复制
String a; 
String b;
a = "大失叔喜欢打麻将";  
b = a;

这几行代码,会涉及到下面一些行为:

  • 第1、2行,我们定义了2个String类型的变量a和b。这时候Java会在内存中分别分配一块空间给a和b,但是这时候这2块内存空间中没有存放任何值。
  • 第3行,我们把一个字符串赋值给变量a。Java会在内存中分配一块空间,存放这个字符串,然后把这块空间的地址存放到变量a的内存空间中。
  • 第4行,把变量a赋值给b,相当于把变量a内存空间中的地址存放到变量b的内存空间中,这时候a和b同时指向字符串“大失叔喜欢打麻将”对应的内存空间。

我们用一张图示意如下:

我们需要牢牢记住一点:在Java中,任何对象的值都是存放在堆内存中的,而对象类型的变量对应的内存中保存的是对象的内存地址,我们称之为对象引用。因此new操作符返回的结果其实是一个引用。

       我们可以显式的把一个对象变量设置为null,这时候该变量的内存存放的将是空值,表明它不引用任何对象。如果我们对一个值为null的变量进行方法调用,程序在运行时则会抛出异常。

4.2.4字符串拼接

       在Java中,字符串的拼接有一种很简单的方法,就是用加号( )连接两个字符串,结果会构造出一个新的字符串对象。我们看代码:

代码语言:javascript复制
String a = "Java大失叔";  
String b = "喜欢打麻将";  
String c = a   b;  
System.out.println(c);// 结果将输出:"Java大失叔喜欢打麻将"

在这段代码中,堆内存中将会分配3块空间,分别对应字符串"Java大失叔"、"喜欢打麻将"、" Java大失叔喜欢打麻将"。我们用一张图来演示这个过程:

我们还可以将一个字符串和一个非字符串用 连接起来,这时候非字符串对象会被转换为字符串(具体如何转换,后续会详细探讨)。例如:

代码语言:javascript复制
String a = "Java大失叔卡里只有";  
int b = 200;  
String c = "元钱了";  
System.out.println(a   b   c);// 结果将输出:Java大失叔卡里只有200元钱了

String类的API中还提供了一个方法concat用来拼接字符串,方法摘抄如下:

修饰和类型

方法

描述

String

concat(String str)

将str拼接在本字符串后面

使用起来也很简单,代码如下:

代码语言:javascript复制
String a = "Java大失叔";  
String b = "喜欢打麻将";  
String c = a.concat(b);  
System.out.println(c);// 结果将输出:Java大失叔喜欢打麻将

  有的时候,需要将很多个字符串拼接成一个大字符串,这时,如果用 的方式,不是很合适了。因为用 的方式,每次都会构建一个新的对象,比较耗时,还占内存,效率比较低。好在Java提供了另外一种方式,就是采用StringBuilder类和StringBuffer类。一般情况下我们都会采用StringBuilder类,因为它的效率略高。而Stringbuffer类是线程安全的,关于线程会在后面专门讨论。这2个类的API几乎完全一样。用StringBuilder非常简单,代码演示如下:

代码语言:javascript复制
StringBuilder sb = new StringBuilder();// 首先构建StringBuilder对象  
sb.append("Java");// 然后用append方法添加小字符串  
sb.append("大失叔");  
sb.append("太帅了");  
String s = sb.toString();// 最后调用toString()方法,返回一个字符串对象  
System.out.println(s);// 结果将输出:Java大失叔太帅了

其实append方法返回的依然是StringBuilder对象,因此还可以采用一种更为简洁的方式:

代码语言:javascript复制
String s = new StringBuilder().append("Java").append("大失叔").append("太帅了").toString();  
System.out.println(s);// 结果将输出:Java大失叔太帅了

关于加号、concat、StringBuilder这三者的比较,笔者给出如下结论:

  1. 对于拼接少量的字符串,用哪种方式都差不多,加号书写起来更加方便。笔者几乎没用过concat方法。
  2. 加号和StringBuilder都可以拼接非字符串类型(可以查看API,有很多个append方法)。
  3. 对于需要拼接多个字符串的时候,强烈建议使用StringBuilder。(笔者在早年编写一个网络程序的时候,吃过亏)

4.2.5字符串截取和比较

       关于字符串还会经常使用比较和截取的方法,先列出方法如下:

修饰和类型

方法

描述

boolean

startsWith(String prefix)

检查字符串是否以指定的前缀prefix开始

boolean

endsWith(String suffix)

检查字符串是否以指定的后缀suffix结尾

String

trim()

删除字符串前后的空白,并返回一个新字符串

boolean

equals(Object anObject)

检测2个字符串是否相等

boolean

equalsIgnoreCase(String anotherString)

检测2个字符串在忽略大小写的情况下是否相等

String

substring(int beginIndex)

截取从beginIndex到末尾的字符串并返回

String

substring(int beginIndex, int endIndex)

截取从beginIndex到endIndex的字符串并返回,不包括endIndex

我们经常会比较一个字符串是否以某个字符串开头或结尾,代码如下:

代码语言:javascript复制
String a = "Java大失叔";  
boolean b1 = a.startsWith("Java");// 结果为true  
boolean b2 = a.startsWith("java");// 结果为false  
boolean b3 = a.endsWith("叔");// 结果为true  

  有时候,经过网络传输后的字符串经常前后会带一些空白,眼睛又看不见,很不利于比较,会用trim方法去掉前后的空白:

代码语言:javascript复制
String a = "   Java大失叔       ";  
String b = a.trim();  
System.out.println(b);// 结果将输出:Java大失叔 

需要注意,这里的空白指的是Unicode编码小于或等于”u0020”的字符。

       对于字符串的截取,用subString方法将非常方便:

代码语言:javascript复制
String a = "Java大失叔 ";  
String b = a.substring(4);// 结果是:大失叔  
String c = a.substring(2, 6);// 结果是:va大失

这里要注意的是,返回的结果字符串是包括beginIndex位置的代码单元,但是不包括endIndex位置的代码单元。

       比较2个字符串是否相等,用equals方法,如果相等返回ture,否则返回false。如果想不区分大小写比较是否相等,则可以使用equalsIgnoreCase方法。表达式为:

a.equals(b)

其中,a和b即可以是变量,也可以是字符串常量。

代码语言:javascript复制
String a = "Java大失叔";  
String b = "java大失叔";  
System.out.println(a.equals(b));// 结果为false  
System.out.println(a.equalsIgnoreCase(b));// 结果为true
System.out.println("JAVA大失叔".equalsIgnoreCase(b));// 结果为true

这里需要特别注意,千万不能用==运算符来比较2个字符串是否相等。因为==运算符比较的是2个字符串是否存放在同一个内存位置上。但是事实上,对于2个字符内容完全一样的字符串,是很有可能存放在不同的内存空间的,因此用==比较结果将为false。这个问题Java新手经常会犯。

  最后我们很容易发现,String的API中没有提供修改字符串内容的方法。这其实是因为String类被定义为final的(关于final后面也会介绍),我们看一下String的源代码(在Eclipse中,可以很轻松的查看源代码,鼠标移动的任意一个String字符上,按住Ctrl键后,点击鼠标左键):

代码语言:javascript复制
public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence 

用final修饰一个类后,这个类的对象将不能被修改。

       String类提供了50个多个方法,这些方法都很有用,但是我们不可能记住所有的方法名和参数要求,这里还有一个Eclipse的小技巧,当我们敲完变量名加“点”后,Eclipse会自动弹出提示,或者还可以用Ctrl /自动补全,如下图:

0 人点赞