写点什么

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

作者:编程攻略
  • 2022 年 5 月 20 日
  • 本文字数:6363 字

    阅读完需:约 21 分钟

零基础学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"的内容,我们可以这么写:

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

  • 第一种形式:

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

  • 第二种形式:

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

  • 常用方法表









  • 补充解释

  • 从上面的方法列表,我们看到有些方法名字相同,但是参数不同的情况,这种情况为方法的重载(overload),比如valueOf方法。所谓重载,是在同一个类中,同名但参数表不同的方法的多次定义,这些重载的方法在被调用时,Java 会根据实参的不同而决定调用不同的重载方法。Java 是根据什么区分不同的参数的呢?是根据对应位置参数的类型来区分的。

  • 有些方法的前面带有 static 这个修饰词,那么这种方法,我们称之为静态方法,这种方法是通过类名直接调用,而不必通过对象来调用。例如上表中valueOf这个方法,调用的时候,形如:String.valueOf(123)

  • 前表中,我们看到所有的方法前面都有个 public,类对外提供服务的方法,都是通过 public 这个修饰词进行标识的。我们在定义类的方法时,不是所有的方法都是public的,有些方法只供类(或包内、或子类可用)内部使用,这就好比大家在超市结账的时候,只需要把货物和钱款交给收银员就行了,至于收银员在随后如何盘存,都是超市内部的机制,我们无需关注一样。

  • 有些方法,如:charAt可能会抛出异常,但是这些异常在程序中又不必捕获,也不必声明,这是什么情况?我们可以看看这些异常都继承自哪个类:java.lang.RuntimeException 。我们这里说:凡继承自这个类的异常子类在你的程序中可以不进行捕获,也不进行声明,但是一旦发生这种类型的异常,你的程序会被毫不犹豫的中断,由系统对该异常进行处理,而系统处理的很简单,只是打印出出错栈信息,然后中断掉你的程序。所以,如果从你的程序的健壮性考虑,我们最好还是进行捕获并进行处理。

ringBuffer 和 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 学习资料见【编程攻略】

发布于: 刚刚阅读数: 2
用户头像

编程攻略

关注

还未添加个人签名 2022.05.08 加入

还未添加个人简介

评论

发布
暂无评论
零基础学Java第四节(字符串相关类)_java编程_编程攻略_InfoQ写作社区