1、String类的基本概念
String一直是用来表示字符串数据的,但是String本身也有一些自己的特点。
1.1、String类对象的两种实例化方式介绍
String是一个较为特殊的类,而这个类的对象可以使用两种模式进行实例化;
· 形式一:直接赋值,使用“"”定义的内容都是字符串;
public class StringDemo { public static void main(String args[]) { String str = "Hello" ; System.out.println(str) ; } } |
· 形式二:String本身是一个类,那么既然是类,就一定会提供有构造方法,所以在String类里面提供有以下的一个构造方法:public String(String str)。
public class StringDemo { public static void main(String args[]) { String str = new String("Hello") ; System.out.println(str) ; } } |
类的对象开辟一定要使用关键字“new”,而且每当使用一次“new”表示新的堆内存空间产生。
现在的两种方式的最终结果是一样的,而对于两者的区别,随后介绍。
1.2、字符串比较
String类是整个Java之中特殊的类之一,它可以直接像int型数据初始化那样,直接采用“=”赋值的形式完成。
范例:int数据判断相等
public class StringDemo { public static void main(String args[]) { int x = 10 ; int y = 10 ; System.out.println(x == y) ; } } |
而在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 } } |
此时命名内容都是一样的,但是判断的结果却不同,下面通过内存图来描述。
通过内存图的分析可以发现,虽然三个字符串对象所保存的内容都是相同的,但是这三个对象的地址数值是有区别的,其中strA自己有一个自己的保存地址,而strB和strC同时指向一块堆内存空间。
那么现在就可以得出一个结论:“==”可以用在字符串对象的比较上,但是最终比较的形式是根据堆内存的地址进行比较的,所以“==”进行的是数值比较。
结论:在引用数据类型操作之中,永远都可以使用“==”进行比较,而比较的永恒都是地址数值。
但是真正有用的应该是比较字符串的内容,String本身是一个类,所以在这个类之中为了方便用户进行字符串的比较,提供了一个比较方法(由于知识的学习层次问题,所以此方法暂时和给定的方法修改):public boolean equals(String str)。
范例:利用equals()进行比较
public class StringDemo { public static void main(String args[]) { String strA = "Hello" ; // 直接赋值 String strB = new String("Hello") ; // 构造方法 String strC = strB ; // 引用传递 System.out.println(strA.equals(strB)) ; // true System.out.println(strA.equals(strC)) ; // true System.out.println(strB.equals(strC)) ; // true } } |
在以后所有进行字符串相等的判断之中,都要去使用“equals()”完成比较。
面试题:请解释在String比较之中“==”和“equals()”区别?
· “==”:是进行数值比较的,如果用在字符串对象比较上,比较的是两个对象的内存地址数值;
· “equals()”:是String类之中定义的方法(public boolean equals(String str)),可以进行字符串内容比较的;
1.3、字符串常量是String的匿名对象
在任何的编程语言之中都不可能直接提供有字符串这种类型,但是在所有的项目之中字符串绝对是一个不可避免的概念,那么像Java、C#这样的应用层的代表性编程语言,为了方便用户开发,都专门提供有String这种类型。但是String并不是一个基本类型,而是一个引用类型,所以来将每一个使用“"”声明的字符串实质上都是String类的一个对象。
范例:验证String为匿名对象
public class StringDemo { public static void main(String args[]) { String str = "Hello" ; System.out.println("Hello".equals(str)) ; } } |
发现现在可以利用字符串调用equals()方法,所以就得出结论:字符串常量就是String的匿名对象。
小技巧:关于用户输入字符串与一个固定内容的比较
在开发之中经常会出现这样一种情况,用户输入一些字符串数据,如果用户输入的某一个字符串正好是“Hello”,那么就出现欢迎信息,而在这种情况下,有两种比较形式。
范例:可能存在隐患的形式
public class StringDemo { public static void main(String args[]) { String inputMessage = null ; // 理解为由用户输入 if(inputMessage.equals("Hello")) { // NullPointerException System.out.println("欢迎光临!") ; } } } |
如果此时用户没有输入数据,那么内容就是null,而一旦是null调用了方法就会出现NullPointerException,所以此代码会存在有安全隐患。
范例:换种形式比较
public class StringDemo { public static void main(String args[]) { String inputMessage = null ; // 理解为由用户输入 if("Hello".equals(inputMessage)) { System.out.println("欢迎光临!") ; } } } |
首先equals()方法具备判断为null的能力,如果发现为null则直接返回false,同时使用字符串常量“Hello”永恒都是一个不可能为null的字符串对象,所以这种形式就可以帮助用户有效的避免NullPointerException。
1.4、两种实例化方式的区别
String类的对象有两种实例化方式,那么这两种方式有什么区别?在实际的工作之中应该使用何种方式?下面做一个简单分析。
分析一:直接赋值进行String类对象实例化
public class StringDemo { public static void main(String args[]) { String msg = "Hello" ; } } |
此时只开辟了一块堆内存空间和一块栈内存空间,但是除了此特征之外,继续观察如下代码。
范例:使用直接赋值的形式定义多个String类对象
public class StringDemo { public static void main(String args[]) { String msgA = "Hello" ; String msgB = "Hello" ; String msgC = "Hello" ; System.out.println(msgA == msgB) ; // true System.out.println(msgA == msgC) ; // true System.out.println(msgB == msgC) ; // true } } |
此时发现这三个对象的地址比较结果都是true,从而可以得出一个结论,三个对象指向同一块堆内存空间。
提示:关于String类所使用的设计模式问题 —— 共享设计模式
在String类设计的过程之中,考虑到用户频繁使用的情况(所有的项目都一定会有String),所以为了提升其性能,为String采用了一个“共享设计模式”(可以简单理解为对象数组,但是这种对象数组和之前学习的不一样,属于动态扩充的对象数组),每当用户使用直接赋值的形式定义String类对象时。第一次会在堆内存之中开辟新的字符串对象,同时将这个对象保存在Java的一个底层“字符串池”(Object Pool)之中,当第二次如果还有String类对象使用直接赋值的话,会首先判断此对象池之中是否存在有指定的内容,如果存在,则直接引用此内容,如果不存在则开辟一个新的对象内容,同时将这个对象内容继续保存在字符串池之中。
分析二:采用构造方法实例化
如果从标准来讲,构造方法实例化对象应该是最正统的,下面继续使用程序来分析问题。
public class StringDemo { public static void main(String args[]) { String msg = new String("Hello") ; System.out.println(msg) ; } } |
此时可以发现,使用构造方法实例化String类对象,那么会开辟两块内存空间,其中有一块内存空间将成为垃圾,所以属于严重的空间浪费,那么除了以上的特征之外,还会存在以下的问题。
范例:观察构造方法实例化的问题
public class StringDemo { public static void main(String args[]) { String msgA = new String("Hello") ; String msgB = "Hello" ; String msgC = "Hello" ; System.out.println(msgA == msgB) ; // false System.out.println(msgA == msgC) ; // false System.out.println(msgB == msgC) ; // true } } |
如果采用直接赋值的形式会发现,字符串的对象内容可以自动的保存在对象池之中,但是当使用构造方法实例化对象的时候,发现内容不会自动入池,但是在String类里面提供有一个intern()方法,可以手工实现入池操作:
· 手工入池:public String intern()。
public class StringDemo { public static void main(String args[]) { String msgA = new String("Hello").intern() ; // 手工入池 String msgB = "Hello" ; String msgC = "Hello" ; System.out.println(msgA == msgB) ; // true System.out.println(msgA == msgC) ; // true System.out.println(msgB == msgC) ; // true } } |
面试题:请解释String类对象两种实例化方式的区别?
· String类的对象可以使用直接赋值字符串的形式实例化或者使用关键字new调用构造方法实例化;
· 直接赋值(String str = "字符串"):此字符串数据可以自动的保存在对象池之中,供下次使用,同时只会开辟一块内存空间;
· 构造方法(String str = new String("字符串")):会开辟两块内存空间,其中有一块内存将成为垃圾,同时字符串对象不会自动入池,用户可以使用intern()方法实现手工入池保存。
1.5、字符串一旦声明则内容不可改变
字符串如果继续追溯那么换到程序之中一定是一个字符数组,那么只要是数组一旦定义了长度都是无法进行改变的,所以字符串内容一旦声明了,则就无法改变。
范例:观察改变问题
public class StringDemo { public static void main(String args[]) { String msg = "Hello " ; msg += "World "; msg = msg + "!!!" ; System.out.println(msg) ; } } |
现在这个结果之中字符串对象msg的内容是在改变着,那么下面来分析问题。
可以发现,整个代码之中,字符串的内容实际上没有任何的改变,而字符串对象的改变依靠的是“地址引用关系”的变化而实现,这样的操作不仅性能很差,而且还会产生有大量的垃圾空间,所以在工作之中不允许出现大规模的。
范例:大规模改变
public class StringDemo { public static void main(String args[]) { String msg = "" ; for (int x = 0 ; x < 1000 ; x ++) { msg += x ; // 任何数据遇见String的+都表示连接 } System.out.println(msg) ; } } |
这种大规模的修改字符串一定会产生大量的垃圾空间,所以此类操作在所有的开发里面绝对不允许出现。
2、String类常用方法
在所有的开发之中,String类一定会使用,而且基本上每一个*.java程序都或多或少存在String。那么在String类之中除了之前讲解过的两个方法(equals()、intern())之外,还存在有其它的方法。那么文档之中有每一个类的详细解释,基本的组成顺序如下:类的声明、类的简短说明、成员摘要(属性就属于一种成员)、构造方法摘要、方法摘要、最后是成员&构造方法&普通方法的详细解释。
下面将根据方法的功能对String类之中的方法做一个说明。请回去之后将方法的名称、返回值类型、方法的参数类型及个数、作用全背下来,下周一测试用。
2.1、字符串与字符
字符串就是由字符数组所组成,所以在String类里面提供有以下的与字符有关的操作方法。
No. | 方法名称 | 类型 | 描述 |
1 | public String(char[] value) | 构造 | 将指定的字符数组变为字符串 |
2 | public String(char[] value, int offset, int count) | 构造 | 将指定范围的字符数组变为字符串 |
3 | public char charAt(int index) | 普通 | 取得字符串之中指定索引位置的字符 |
4 | public char[] toCharArray() | 普通 | 将字符串变为字符数组 |
范例:取得指定索引位置的字符
public class StringDemo { public static void main(String args[]) { String msg = "Hello World ." ; char c = msg.charAt(2) ; System.out.println(c) ; } } |
Java中的字符串索引下标都是从0开始的。
范例:将字符串与字符数组进行互操作
public class StringDemo { public static void main(String args[]) { String msg = "hello" ; // 完全是由小写字母组成 char data [] = msg.toCharArray() ; // 将字符串变为字符数组 for (int x = 0 ; x < data.length ; x ++) { data[x] -= 32 ; } System.out.println(new String(data)) ; } } |
范例:现在给出一个字符串,要求判断字符串是否由数字所组成
思路:既然要判断,暂时没有学习过整体的判断,那么就可以将字符串变为字符数组,而后按位进行判断。同时为了简化主方法的代码,可以直接编写一个验证的判断方法,而这个方法返回的一定是boolean,那么应该以is开头。
public class StringDemo { public static void main(String args[]) { String msg = "234a2343432" ; System.out.println(isNumber(msg)) ; } public static boolean isNumber(String str) { char [] data = str.toCharArray() ; // 将字符串变为字符数组 for (int x = 0 ; x < data.length ; x ++) { if (data[x] > '9' || data[x] < '0') { // 不是数字 return false ; } } return true ; } } |
本题目也有可能出现在笔试题里,因为太简单了。但是从实际的操作来看,此种方式的使用情况有限。
2.2、字符串与字节
字符串也可以与字节数据进行相互转换,使用的操作方法如下:
No. | 方法名称 | 类型 | 描述 |
1 | public String(byte[] bytes) | 构造 | 将字节数组变为字符串 |
2 | public String(byte[] bytes, int offset, int length) | 构造 | 将指定范围的字节数组变为字符串 |
3 | public byte[] getBytes() | 普通 | 将字符串变为字节数组 |
4 | public byte[] getBytes(String charsetName) throws UnsupportedEncodingException | 普通 | 编码转换 |
范例:实现字符串与字节的转换
public class StringDemo { public static void main(String args[]) { String msg = "helloworld" ; byte data [] = msg.getBytes() ; // 将字符串变为字节数组 for (int x = 0 ; x < data.length ; x ++) { System.out.print(data[x] + "、") ; data[x] -= 32 ; } System.out.println() ; System.out.println(new String(data)) ; // 将字节数组变为字符串 } } |
在日后学习到IO编程和网络编程的时候一定会使用到此类代码。
2.3、字符串比较
在之前已经学习过了equals()方法,但是在String类里面字符串的比较一共定义了三个方法。
No. | 方法名称 | 类型 | 描述 |
1 | public boolean equals(String anObject) | 普通 | 判断两个字符串是否相等,区分大小写 |
2 | public boolean equalsIgnoreCase(String anotherString) | 普通 | 不区分大小写判断两个字符串是否相等 |
3 | public int compareTo(String anotherString) | 普通 | 比较两个字符串的大小关系 |
范例:观察两种类型的equals()
public class StringDemo { public static void main(String args[]) { String msgA = "helloworld" ; String msgB = "HELLOWORLD" ; System.out.println(msgA.equals(msgB)) ; // false System.out.println(msgA.equalsIgnoreCase(msgB)) ; // true } } |
例如,在一些用户登录上会出现验证码,这个时候都不区分大小写。
在String类里面提供了一个比较两个字符串大小关系的方法compareTo(),此方法返回的是一个int型数据,而对于这种数据它有三类结果:大于(>0)、小于(<0)、等于(=0)。
范例:使用compareTo()比较
public class StringDemo { public static void main(String args[]) { String msgA = "He" ; String msgB = "HE" ; System.out.println(msgA.compareTo(msgB)) ; } } |
日后对于compareTo()还有更加深入的学习,但是现在必须清楚它的返回值作用。
2.4、字符串查找
从一个指定的字符串之中,判断某一个子字符串是否存在,就是字符串的查找功能,对于字符串的查找存在有以下的几个操作方法。
No. | 方法名称 | 类型 | 描述 |
1 | public boolean contains(String s) | 普通 | 判断字符串是否存在,在JDK 1.5之后引入 |
2 | public int indexOf(String str) | 普通 | 从头查找指定字符串的位置,如果找到了则返回位置索引,如果找不到返回-1 |
3 | public int indexOf(String str, int fromIndex) | 普通 | 从指定位置开始由前向后查找 |
4 | public int lastIndexOf(String str) | 普通 | 从后向前查找指定字符串位置 |
5 | public int lastIndexOf(String str, int fromIndex) | 普通 | 从指定位置由后向前查找字符串位置 |
6 | public boolean startsWith(String prefix) | 普通 | 判断是否以指定的字符串开头 |
7 | public boolean startsWith(String prefix, int toffset) | 普通 | 从指定位置开始判断是否以指定的字符串开头 |
8 | public boolean endsWith(String suffix) | 普通 | 判断是否由指定的字符串结尾 |
范例:使用contains()查询
public class StringDemo { public static void main(String args[]) { String msg = "Hello World ." ; if (msg.contains("Hello")) { System.out.println("已经查找到了指定字符串。") ; } } } |
对于contains()方法是在JDK 1.5之后才引入的新方法,但是在最早的时候都使用的是indexOf()方法。
范例:利用indexOf()来判断指定字符串是否存在
public class StringDemo { public static void main(String args[]) { String msg = "Hello World ." ; System.out.println(msg.indexOf("W")) ; // 6 System.out.println(msg.indexOf("Hello")) ; // 0 System.out.println(msg.indexOf("NIHAO")) ; // -1 if (msg.indexOf("Hello") != -1) { System.out.println("查找到数据。") ; } } } |
public class StringDemo { public static void main(String args[]) { String msg = "Hello World ." ; System.out.println(msg.indexOf("l")) ; // 2 System.out.println(msg.indexOf("l",3)) ; // 3 System.out.println(msg.indexOf("l",5)) ; // 9 System.out.println(msg.lastIndexOf("l")) ; // 9 } } |
对于一些很老的系统还可能会存在有indexOf()方法的使用,但是一些新系统上一定都用contains。
范例:判断是否以指定的内容开头或结尾
public class StringDemo { public static void main(String args[]) { String msg = "**Hello$$World .##" ; System.out.println(msg.startsWith("**")) ; System.out.println(msg.startsWith("$$",7)) ; System.out.println(msg.endsWith("##")) ; } } |
在日后讲解购物车模型的时候就会使用到类似的判断,因为要处理动态生成的表单数据。
2.5、字符串替换
将某一个字符串替换为其它的数据就是替换的功能,替换的操作有如下的方法定义:
No. | 方法名称 | 类型 | 描述 |
1 | public String replaceAll(String regex, String replacement) | 普通 | 全替换 |
2 | public String replaceFirst(String regex, String replacement) | 普通 | 替换首个 |
范例:替换操作
public class StringDemo { public static void main(String args[]) { String msg = "Hello World ." ; System.out.println(msg.replaceAll("l","_")) ; System.out.println(msg.replaceFirst("l","_")) ; } } |
替换操作本身很好理解,但是对于替换操作,今天只是一个基本的介绍,日后会有更详细讲解。
2.6、字符串拆分
将一个字符串按照指定的分隔标记进行拆分为多个字符串,所以拆分之后返回的类型一定是字符串数组。
No. | 方法名称 | 类型 | 描述 |
1 | public String[] split(String regex) | 普通 | 全拆分 |
2 | public String[] split(String regex, int limit) | 普通 | 拆分部分 |
范例:实现数据的拆分操作
public class StringDemo { public static void main(String args[]) { String msg = "Hello World Hello SUN" ; String data [] = msg.split(" ") ; // 按照空格拆分 for (int x = 0 ; x < data.length ; x ++) { System.out.println(data[x]) ; } } } |
范例:拆分IP地址
public class StringDemo { public static void main(String args[]) { String msg = "192.168.1.1" ; String data [] = msg.split("\\.") ; for (int x = 0 ; x < data.length ; x ++) { System.out.println(data[x]) ; } } } |
在日后的操作之中一定会牵扯到拆不开的问题(拆不开是因为正则表达式原因),那么这个时候可以加上“\\”(\)进行转义后拆分。
2.7、字符串截取
在Oracle学习过substr()函数,那么String类也有支持,方法如下。
No. | 方法名称 | 类型 | 描述 |
1 | public String substring(int beginIndex) | 普通 | 由开始截取到结尾 |
2 | public String substring(int beginIndex, int endIndex) | 普通 | 设置开始和结束索引进行截取 |
范例:字符串截取
public class StringDemo { public static void main(String args[]) { String msg = "Hello World ." ; System.out.println(msg.substring(6)) ; System.out.println(msg.substring(0,5)) ; } } |
索引是从0开始的。
2.8、其它方法
以上的方法基本上都是可以归类的,而还有几个方法功能比较单一,就一起表示了。
No. | 方法名称 | 类型 | 描述 |
1 | public String concat(String str) | 普通 | 字符串连接,一般都使用“+”表示了 |
2 | public String intern() | 普通 | 入池 |
3 | public boolean isEmpty() | 普通 | 判断是否是空字符串("") |
4 | public int length() | 普通 | 取得字符串长度 |
5 | public String toLowerCase() | 普通 | 转小写 |
6 | public String toUpperCase() | 普通 | 转大写 |
7 | public String trim() | 普通 | 去掉左右空格 |
范例:验证几个相关操作方法
public class StringDemo { public static void main(String args[]) { String msg = " Hello World . " ; System.out.println(msg.isEmpty()?"是空字符串!":"不是空字符串!") ; System.out.println("字符串长度:" + msg.length()) ; System.out.println("原始字符串【" + msg + "】,处理后字符串【" + msg.trim() +"】,长度:" + msg.trim().length()) ; System.out.println("转大写:" + msg.toUpperCase()) ; System.out.println("转小写:" + msg.toLowerCase()) ; }} |
发现在String类里面有一个length()方法,而数组上有一个length的属性,这两个操作的含义不同。
范例:在Oracle的字符串函数里面有一个initcap()函数,此函数的功能是将首字母大写,而后字母小写,这功能Java本身没提供,只能够自己定义方法去实现。
public class StringDemo { public static void main(String args[]) { String msg = "name" ; System.out.println(initcap(msg)) ; } public static String initcap(String str) { return str.substring(0,1).toUpperCase().concat(str.substring(1).toLowerCase()) ; }} |
不要小看此功能,日后的所有框架原理必然要具备这样的功能,否则无法使用。虽然Java SE本身没有提供首字母大写的方法,但是在Apache的commons组件包里面有所提供。