零基础学Java第四节(字符串相关类)

本篇文章是《零基础学Java》专栏的第四篇文章,文章采用通俗易懂的文字、图示及代码实战,从零基础开始带大家走上高薪之路!

String

本文章首发于公众号【编程攻略】

在Java中,我们经常使用字符串,所有的字符串值的类型均为String,它不属于基本类型,它的全名为java.lang.String,我们有必要在这里学习掌握一些它的基本使用方法。

  • 字符串常量:在Java中所有的字符串常量均是以双引号括起来的,比如: "abc"等。因为它的类型是String类型,自然,每个字符串常量均为String的对象,也自然可以调用String中的public(所谓public就是全部开放给程序员使用的方法、属性或者类)方法。比如:"abc".indexof('b'),它的意义就是在"abc"中,'a'第一次出现的位置(从0开始),它的结果就是1。

  • 字符串比较:

    • ==!=在比较引用类型的数据时是比较引用是不是相同,而不是比较对象中的内容。比如:"abc" == "abc"结果是什么?答案是true,这意味着什么?意味着==左右的这两个常量其实是同一个对象,并不是两个不同的具有相同字符组合的对象。所以,在你的程序中,不管你写多少个"abc",这些"abc"都是同一个字符串。

    • 那么如何比较两个字符串的内容是否一样呢?我们使用String中equals()方法,比如:比较"abc""abd"的内容,我们可以这么写:

      "abc".equals("abd") 或者 "abd".equals("abc")
      
  • 字符串变量的初始化:所谓初始化就是获得第一个值。因为String类的构造方法有好几个,所以字符串变量的初始化也会有相应的几个形式,我们这里只了解常用的方式,其他方式,大家自己查看JDK说明书进行了解。

    • 第一种形式:

        String s = "asdf";
      

      我们在学习Java的时候,一定要知道原理,不要只知其一不知其二。这条语句的含义是什么呢?我们知道字符串常量也是对象,所以它的意义就是把"asdf"的引用放入s这个变量中,那么 s == "asdf" 的结果呢?自然也是true

    • 第二种形式:

        String s = new String("asdf");
      

      大家可以看到这种形式是标准的生成类对象的形式,那么这条语句执行以后,s == "asdf" 的结果呢?此时,就不再是true,而是false,它说明s引用的"asdf"和作为参数传递给String构造方法的"asdf"是两个不同的字符串了。这条语句的含义就是以"asdf"为模板,再创建一个内容为asdf的字符串对象。但是,s.equals("asdf")的值呢?因为这两个字符串的字符序列是一致的,所以,结果为true

  • 常用方法表

方法首部 方法功能 可能抛出的异常
public char charAt(int index) 返回指定索引处的 char 值。索引范围为从 0 到 length() – 1。序列的第一个 char 值位于索引 0 处,第二个位于索引 1 处,依此类推,这类似于数组索引。 index的值如果不在字符串长度范围内,会产生IndexOutOfBoundsException,该异常可以不进行捕获。
public boolean contains(CharSequence s) 当且仅当此字符串包含指定的s字符序列时,返回 true。 NullPointerException – 如果 s 为 null,该异常可以不进行捕获
public boolean equals(Object anObject) 将此字符串与指定的对象比较。当且仅当该参数不为 null,并且是与此对象表示相同字符序列的 String 对象时,结果才为 true。
public boolean equalsIgnoreCase(String anotherString) 将此 String 与另一个 String 比较,不考虑大小写。如果两个字符串的长度相同,并且其中的相应字符都相等(忽略大小写),则认为这两个字符串是相等的。
public int compareTo(String anotherString) 如果参数字符串等于此字符串,则返回值 0;如果此字符串按字典顺序小于字符串参数,则返回一个小于 0 的值;如果此字符串按字典顺序大于字符串参数,则返回一个大于 0 的值。
public int compareToIgnoreCase(String str) 按字典顺序比较两个字符串,不考虑大小写。根据指定 String 大于、等于还是小于此 String(不考虑大小写),分别返回一个负整数、0 或一个正整数。
public int indexOf(int ch) 在此对象表示的字符序列中第一次出现该字符的索引;如果未出现该字符,则返回 -1。
public int indexOf(int ch,int fromIndex) 从指定的索引(含)开始搜索,返回在此字符串中第一次出现指定字符处的索引,否则返回 -1。
public int indexOf(String str) 如果字符串参数作为一个子字符串在此对象中出现,则返回第一个这种子字符串的第一个字符的索引;如果它不作为一个子字符串出现,则返回 -1。
public int indexOf(String str,int fromIndex) 从指定的索引开始,返回指定子字符串在此字符串中第一次出现处的索引,否则返回 -1。
public boolean isEmpty() 如果 length() 为 0,则返回 true;否则返回 false。
public int lastIndexOf(int ch) 在此字符序列中最后一次出现该字符的索引;如果未出现该字符,则返回 -1。
public int lastIndexOf(int ch,int fromIndex) 从指定的索引处开始进行反向搜索,返回指定字符在此字符串中最后一次出现处的索引。如果在该点之前未出现该字符,则返回 -1。
public int lastIndexOf(String str) 如果字符串参数作为一个子字符串在此对象中出现一次或多次,则返回最后一个这种子字符串的第一个字符。如果它不作为一个子字符串出现,则返回 -1。
public int lastIndexOf(String str,int fromIndex) 返回指定子字符串在此字符串中最后一次出现处的索引,从指定的索引开始反向搜索。如果在该点之前未出现该字符串,则返回 -1。
public int length() 返回此字符串的长度。
public String replace(char oldChar,char newChar) 返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。
public String replace(CharSequence target,CharSequence replacement) 使用指定的replacement替换此字符串所有匹配target的子字符串。该替换从字符串的开头朝末尾执行,例如,用 “b” 替换字符串 “aaa” 中的 “aa” 将生成 “ba” 而不是 “ab”。 NullPointerException – 如果 target 或 replacement 为 null。。该异常不必捕获。
public String substring(int beginIndex) 返回一个新的字符串,它是此字符串的一个子字符串。该子字符串从指定索引处(含)的字符开始,直到此字符串末尾。示例: “unhappy”.substring(2) 返回 “happy” IndexOutOfBoundsException – 如果 beginIndex 为负或大于此 String 对象的长度。该异常不必捕获。
public String substring(int beginIndex,int endIndex) 返回一个新字符串,它是此字符串的一个子字符串。该子字符串从指定的 beginIndex 处开始,直到索引 endIndex – 1 处的字符。因此,该子字符串的长度为 endIndex-beginIndex。示例: “hamburger”.substring(4, 8) returns “urge” IndexOutOfBoundsException – 如果 beginIndex 为负,或 endIndex 大于此 String 对象的长度,或 beginIndex 大于 endIndex。该异常不必捕获。
public String toLowerCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为小写。
public String toUpperCase() 使用默认语言环境的规则将此 String 中的所有字符都转换为大写。
public String trim() 返回字符串的副本,忽略前导空白和尾部空白。
public static String valueOf(boolean b) 如果参数为 true,则返回一个等于 “true” 的字符串;否则,返回一个等于 “false” 的字符串。
public static String valueOf(char c) 返回 char 参数的字符串表示形式。例如:String.valueOf(‘a’)的值为”a”
public static String valueOf(char[] data) 一个新分配的字符串,它表示包含在字符数组参数中的相同字符序列。
public static String valueOf(char[] data,int offset,int count) 返回由data数组中从offset开始的count个数组元素组成字符串 IndexOutOfBoundsException – 如果 offset 为负,count 为负,或者 offset+count 大于 data.length。该异常可以不捕获。
public static String valueOf(double d) 返回 double 参数的字符串表示形式。同单参数的 Double.toString 方法返回的结果一致。
public static String valueOf(float f) 返回 float 参数的字符串表示形式。 同单参数的 Float.toString 方法返回的结果一致。
public static String valueOf(int i) 返回 int 参数的字符串表示形式。 同单参数的 Integer.toString 方法返回的结果一致。
public static String valueOf(long l) 返回 long 参数的字符串表示形式。 同单参数的 Long.toString 方法返回的结果一致。
public static String valueOf(Object obj) 如果参数为 null,则字符串等于 “null”;否则,返回 obj.toString() 的值。
  • 补充解释
    • 从上面的方法列表,我们看到有些方法名字相同,但是参数不同的情况,这种情况为方法的重载(overload),比如valueOf方法。所谓重载,是在同一个类中,同名但参数表不同的方法的多次定义,这些重载的方法在被调用时,Java会根据实参的不同而决定调用不同的重载方法。Java是根据什么区分不同的参数的呢?是根据对应位置参数的类型来区分的。
    • 有些方法的前面带有static这个修饰词,那么这种方法,我们称之为静态方法,这种方法是通过类名直接调用,而不必通过对象来调用。例如上表中valueOf这个方法,调用的时候,形如:String.valueOf(123)
    • 前表中,我们看到所有的方法前面都有个public,类对外提供服务的方法,都是通过public这个修饰词进行标识的。我们在定义类的方法时,不是所有的方法都是public的,有些方法只供类(或包内、或子类可用)内部使用,这就好比大家在超市结账的时候,只需要把货物和钱款交给收银员就行了,至于收银员在随后如何盘存,都是超市内部的机制,我们无需关注一样。
    • 有些方法,如:charAt可能会抛出异常,但是这些异常在程序中又不必捕获,也不必声明,这是什么情况?我们可以看看这些异常都继承自哪个类:java.lang.RuntimeException 。我们这里说:凡继承自这个类的异常子类在你的程序中可以不进行捕获,也不进行声明,但是一旦发生这种类型的异常,你的程序会被毫不犹豫的中断,由系统对该异常进行处理,而系统处理的很简单,只是打印出出错栈信息,然后中断掉你的程序。所以,如果从你的程序的健壮性考虑,我们最好还是进行捕获并进行处理。

StringBuffer和StringBuilder

String对象一旦创建,它的内容是不能改变的,大家可能说String的replace方法不是在替换子字符串吗?我们要明确,replace得到的是一个新的字符串,对原字符串没有任何影响。有时候,我们需要在原有字符串的基础上操作,这个时候就需要使用StringBuffer或者StringBuilder了。

StringBuffer用于多线程环境,StringBuilder用于单线程环境。这两个类中提供public方法是一致的。在这两个类上的主要操作是 appendinsert 方法,这两个方法以各种类型重载,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符添加或插入到字符串生成器中。append 方法始终将这些字符添加到生成器的末端;而 insert 方法则在指定的点添加字符。其他还有一些方法,大家可以参考JDk说明书。

我们在生成这两种类的对象时,如果不带参数,比如:StringBuffer sb = new StringBuffer(),它会构造一个其中不带字符的字符串缓冲区,初始容量为 16 个字符。 但是如果使用的是带参数构造方法,比如:StringBuffer sb = new StringBuffer("abc");它会构造一个字符串缓冲区,并将其内容初始化为 abc 。该字符串的初始容量为 16 加上字符串参数的长度,既是19。也可以在创建对象的时候,通过传递一个数值来设置字符串缓冲区的大小,比如:StringBuffer sb = new StringBuffer(20);,这里20就是字符串缓冲区的大小。

数组概念

一个数组就是一组数据的序列,该序列中每个元素的类型相同,可以是基本类型,也可以是引用类型。如果是基本类型,每个数组元素所在的内存空间中存放的是基本类型的数值;如果是引用类型,每个数组元素所在的内存空间中存放的是引用。如图:

  • 数组的定义形式(两种):

    • int[] a1; 这种形式表明a1这个变量是数组变量,它的数组元素类型为int类型
    • int a1[]; 这种形式表明a1[]数组元素的类型为int类型,a1是数组变量

    不管哪种形式,我们在定义的时候都不能像C语言一样指定数组的大小,我们通过下面的这个例子,来进一步说明它们之间的区别:

    int[] a, b, c; 这里我们可以知道a、b、c这三个变量均为数组变量
    int a[], b, c; 这里我们知道只有a是数组变量,而b、c均为一般变量,而非数组变量
    
  • 数组变量的意义:数组变量是引用类型的变量,这意味着,数组变量中存放的是数组的引用,而非数组本身,数组的存储空间是在初始化的时候在堆(所谓堆,大家可以理解做一个大仓库)中分配的,这一点同C语言有很大区别,这也成为Java数组的一个优势,数组的大小可以在运行的时候确定,而不必在定义的时候就确定下来。

  • 数组的初始化:数组的初始化像其他类型的变量一样,既可以在定义的同时初始化,也可以在定义以后,在第一次使用的使用初始化。初始化的形式用两种:

    • int a[] = new int[10]; 这种形式,在堆中分配一段能够放下10个int类型数据的存储空间,并将其引用放在a这个数组变量中;
    • int a [] = { 1, 2, 3, 4, 5 }; 这种形式其实是把数组{ 1, 2, 3, 4, 5 }的引用放入了a中,而且这种形式只能在定义数组的同时进行。
      • 如果数组元素为引用类型,有两种使用大括号的对数组初始化的形式:
      public class ArrayInit {
      	public static void main(String[] args) {
      		Integer[] a = {
      				new Integer(1),
      				new Integer(2),
      				new Integer(3),
      		};
      		Integer[] b = new Integer[] {
      				new Integer(1),
      				new Integer(2),
      				new Integer(3),
      		};
      	}
      }
      
  • 数组元素的引用:数组元素的引用也是通过下标进行的,下标可以是一个int类型的表达式,但是值的范围必须在0数组大小-1这个范围内。数组元素的类型既是定义数组时所指定的类型。

多维数组

二维以上的数组就看作多维数组,数组在Java中的实现是采用链式存储实现的,如图:

多维数组的定义和初始化原则同一维是一样的,如下:

  • 第一种形式,
    int[][] a1 = {
    	{ 1, 2, 3},
    	{ 4, 5, 6},
    	{ 7, 8, 9}
    };  //每个向量用大括号括起来。
    
  • 使用new定义a2的大小:
    int[][][] a2 = new int[2][2][4];
    

由于在Java中采用链式存储数组,数组中向量的大小不必相同,比如:

int[][] a1 = {
		{ 1, 2},
		{ 3, 4, 5, 6},
		{ 7, 8, 9}
	};

甚至还可以如下例:

int b[][]; //定义一个二维数组
b = new int[ 2 ][ ]; // b引用一个具有两个子数组的数组 
b[ 0 ] = new int[ 5 ]; // b[0]引用一个具有5个元素的数组 
b[ 1 ] = new int[ 3 ]; // b[1]引用一个具有3个元素的数组 

数组作为方法的参数

方法的参数可以是数组,在使用数组参数时需要注意以下事项:

  • 在形参表中,数组名后的方括号不能省略,方括号个数和数组的维数相等
  • 实参表中,数组名后不需括号
  • 参数是数组时,形参和实参传递的是引用

示例:

class A{
	void f(int va[]){
		for(int i = 0; i < va.length; i++)//va.length为va这个数组的大小
			va[i]++;
	}

	public static void main(String args[]){

		int[] aa = new int[10];
		A ta = new A();

		for(int i = 0; i < aa.length; i++) 
			aa[i] = i;

		System.out.println("执行f()之前");
		for(int i = 0; i < aa.length; i++) 
			System.out.print(aa[i] + " ");

		//把aa作为实参传递给f方法
		ta.f(aa); //f这个方法的调用必须使用对象,因为它是一个非静态方法
 
		System.out.println("\n执行f()之后");
		for(int i = 0; i < aa.length; i++) 
			System.out.print(aa[i] + " ");
	}
}

数组的复制

把一个数组中的内容复制到另一个数组不能使用赋值语句a = b,这种形式使得a引用和b相同的数组。如果需要复制数组,我们可以使用System类中的 arraycopy方法,它的方法首部如下:

public static void arraycopy(Object src,
                             int srcPos,
                             Object dest,
                             int destPos,
                             int length)

从指定源数组src中复制一个数组,从指定位置srcPos开始,srcPossrcPos+length-1 之间的length个数组元素,到目标数组dest的指定位置destPos开始,destPosdestPos+length-1 位置。

如果参数 srcdest 引用相同的数组对象,则复制的执行过程就好像首先将 srcPossrcPos+length-1 位置的元素复制到一个有 length 个元素的临时数组,然后再将此临时数组的内容复制到目标数组的 destPosdestPos+length-1 位置一样。

以下三种情况会抛出异常:

  • 如果 src或者destnull,则抛出 NullPointerException 异常。

  • 只要下列任何情况为真,则抛出 ArrayStoreException 异常并且不会修改目标数组:

    • src 参数不是数组对象。
    • dest 参数不是数组对象。
    • srcdest 引用的数组元素的类型是不一致的基本类型。
    • srcdest参数引用的数组的元素为一个为基本类型,另一个为引用类型
  • 如果源数组中 srcPossrcPos+length-1 位置上的实际元素通过分配转换并不能全部转换成目标数组的元素类型,则抛出 ArrayStoreException 异常。在这种情况下,假设复制过程已经进行到k个(k < length)这么多,此时抛出异常,从 srcPossrcPos+k-1 位置上的源数组元素已经被复制到目标数组中的 destPosdestPos+k-1 位置,而目标数组中的其他位置不会被修改。

  • 只要下列任何情况为真,则抛出 IndexOutOfBoundsException 异常,并且不会修改目标数组:

    • srcPosdestPoslength 参数为负。
    • srcPos+length 大于 src.length,即源数组的长度。
    • destPos+length 大于 dest.length,即目标数组的长度

String与字符数组

在Java中字符数组不能当作字符串来看待,但是我们可以使用字符数组作为模板来创建字符串,如下:

char data[] = {'a', 'b', 'c'}; //这里data不能当作字符串
String str = new String(data); //str引用的既是字符串 "abc"

对数组的操作

对数组遍历

所谓遍历(Traversal),是指按照某种方式,依次对某种数据结构中的每个元素做一次且仅做一次的访问。对数组进行遍历通常可以使用循环语句,这里我们再介绍一个专门针对遍历的foreach语句,它的语法格式如下:

//这里type为被遍历结构中元素的类型名,x为结构中的元素,collection为被遍历的结构对象
for(type x : collection){ 
	...//循环体
}

如下例:

int[] a = new int[10];
//这里为一般的for循环
for(int i = 0; i < a.length; i++) a[i] = i;
//这里为foreach语句
for(int x : a){//foreach语句中无法使用下标
	System.out.print(x + " ");
}

对数组的排序

对数组的排序,我们当然可以自己写出各种标准的排序算法,这里介绍一个工具类java.util.Arrays(注意是复数)。此类包含用来操作数组(比如排序和搜索)的各种方法。除非特别注明,否则如果该类中的方法的数组参数引用值为 null,则会抛出 NullPointerException

升序排序

该类中有一系列对数组进行排序的方法,方法名为sort,它的一系列重载实现,可以针对各种数组元素类型的数组进行升序排序。典型的,我们看下面的方法首部:

public static void sort(int[] a)

该方法对传入的 int 型数组a按数字升序进行排序。该排序算法是一个经过调优的快速排序算法。

我们也可以只对数组中的某一部分进行排序,方法首部如下:

public static void sort(int[] a,
                        int fromIndex,
                        int toIndex)

该方法对传入的 int 型数组a中从fromIndextoIndex-1的元素按数字升序进行排序。同样,它也是一个经过调优的快速排序算法。
该方法可能会抛出下面的异常:

  • IllegalArgumentException – 如果 fromIndex > toIndex
  • ArrayIndexOutOfBoundsException – 如果 fromIndex < 0toIndex > a.length

上面的两个方法,经过重载,第一个参数可以是其他各种类型,包括基本类型和引用类型。

大家可能注意到了,上述的sort只能进行升序的排序,如果是其他复杂的排序方式,则就不适用了。

带有 Comparator的排序

JDK为我们提供了强大的排序支持,因为涉及到一些我们尚未接触的知识,这里我先只做了解。

public static <T> void sort(T[] a, Comparator<? super T> c)
与
public static <T> void sort(T[] a,
                            int fromIndex,
                            int toIndex,
                            Comparator<? super T> c)

这两个的区别在于第一个对整个数组进行排序,第二个可以选择排序范围。

数组元素的查找

对数组中元素进行查找,我们最简单但是效率可能最低下的方法就是对数组进行遍历。同样工具类java.util.Arrays也为我们提供了可以直接使用的查找方法binarySearch,该方法也有一系列的重载。使用该方法的前提,该数组必须是通过sort进行过排序的。它的方法首部如下:

public static int binarySearch(int[] a, int key)
或者
public static int binarySearch(int[] a,
                               int fromIndex,
                               int toIndex,
                               int key)

这两个的区别在于第一个对整个数组进行排序,第二个可以选择排序范围。经过重载,第一个参数可以是其他各种类型,包括基本类型和引用类型。

方法中a为被查找数组,key是需要在此数组中查找的键值,fromIndex为起始位置,toIndex-1为终止位置。

如果key值包含在数组中,则返回它的索引值;否则返回 (-(插入点) - 1)。插入点 被定义为将键插入数组的那一点:即第一个大于此键的元素索引,如果数组中的所有元素都小于指定的键,则为 a.length或者toIndex,这保证了当且仅当此键被找到时,返回的值将 >= 0,否则为负值。

同样,该方法也有二个带有Comparator的方法重载,这里不再赘述。

关于工具类java.util.Arrays中的其他方法,大家可以查看JDK说明书。

问题

用筛法求1000以内的素数,并按每行10个输出出来。

最后

本文章来自公众号【编程攻略】,更多Java学习资料见【编程攻略】

Tags: