Java系列(五)__String类

来源:互联网 发布:泸州市网络问政平台 编辑:程序博客网 时间:2024/06/05 07:15
 

Java系列(五)__String类

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组件包里面有所提供。

0 1