栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java 核心技术 (三)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java 核心技术 (三)

3.6.3 不可变字符串
  • String类没有提供根据修改字符串中某个字符的方法。如果希望将 greeting的内容修改为Help!,不能直接将greeting的最后两个位置的字符修改为 'p'和'!'。对于 C 程序员来说,这会让他们茫然无措。如何修改这个字符串呢?在 Java 中实现这项操作非常容易。可以提取想要保留的子串,再与希望替换的字符串拼接:
    greeting = greeting.substring(0,3) + "p!";
  • 上面这条语句将 greeting变量的当前参数值修改为"Help!"。
  • 由于不能修改 Java 字符串中的单个字符,所以在 Java 文档中将 String类对象称为是不可变的(immutable),如同数字 3 永远是数字 3 一样,字符串"Hello"永远包含字符 H、e、l、l和 o 的代码单元序列。你不能修改这些值,不过,可以修改字符串变量 greeting,让它引用另外一个字符串,这就如同可以让原本存放 3 的数值变量改成存放 4 一样。
  • 这样做是否会降低运行效率?看起来好像修改一个代码单元要比从头创建一个新字符串更加简洁。答案是:也对,也不对。的确,通过拼接"Hel"和"p!"来创建一个新字符串的效率确实不高。但是,不可变字符串却有一个优点:编译器可以让字符串共享。
  • 为了弄清具体的工作方式,可以想象将各种字符串存放在公共的存储池中。字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串与复制的字符串共享相同的字符。
  • 总而言之,Java 的设计者认为共享带来的效率远远胜过于提取子串、拼接字符串所带来的低效率。可以看看你自己的程序,我们发现:大多数情况下都会修改字符串,而只是需要对字符串进行比较(有一种例外情况,将来自于文件或磁盘的单个字符或较短的字符串组装成字符串。为此,Java 提供了一个单独的类)。

C ++ 注释: 在 C 程序员第一次接触 Java 字符串的时候,常常会感到迷惑,因为他们总将字符串认为是字符数组:
char greeting[] = "Hello";
这种认识是错误的,Java 字符串大致类似于 char*指针,
char* greeting = "Hello";
当把 greeting替换为另一个字符串的时候,Java 代码大致进行下列操作:

char* temp = malloc(6);
strncpy(temp,greeting,3);
strncpy(temp + 3, "p!" , 3);
greeting  = temp; 

的确,现在 greeting指向字符串"Help!"。即使最顽固的 C 程序员也得承认 Java 语法要比一连串的strncpy调用舒服得多。不过,如果对 greeting做另一个赋值会怎么样?
greeting = "Howdy";
这样做会不会产生内存泄漏?毕竟,原始字符串的堆中分配。十分幸运,Java 将自动地进行垃圾回收。如果一个内存块不再 使用了,系统最终会将其回收。
如果你是 C++ 程序员,习惯使用 ANSI C++定义的string类,会感觉使用 Java 的String类型更舒服。C++ string对象也会自动地进行内存的分配与回收。内存管理是通过构造器、赋值操作和析构器显式执行的。不过,C++ 字符串是可修改的,也就是说,可以修改字符串的单个字符。

3.6.4 检测字符串是否相等
  • 可以使用 equals()方法检测两个字符串是否相等。对于表达式:
    s.equals(t);
  • 如果字符串 s 与字符串 t 相等,则返回true;否则,返回false。需要注意的是,s 与 t 可以是字符串变量,也可以是字符串字面量。例如,以下表达式是合法的:
    "Hello".equals(greeting);
  • 要想检测两个字符串是否相等,而不区分大小写,可以使用 equalsIgnoreCase()方法。
    "Hello".equalsIgnoreCase("hello");
  • 一定不要使用 ==运算符检测两个字符串是否相等!这个运算符只能够确定两个字符串是否存放在同一个位置上。当然,如果字符串在同一个位置上,它们必然相等。但是,完全有可能将内容相同的多个字符串副本放置在不同的位置上。
    String greeting = "Hello";// initialize greeting to a string
    if(greeting == "Hello"){
    // probably true
    }
    if(greeting.substring(0,3) =="Hel"){
    //probably false
    }
    
  • 如果虚拟机始终将相同的字符串共享,就可以使用 ==运算符检测是否相等。但实际上只有字面量是共享的,而 +或 substring()等操作得到的字符串并不共享。因此,千万不要使用==运算符测试字符串的相等性,以免在程序中出现这种最糟糕的 BUG,看起来这种 BUG 就像随机产生的间歇性错误。

C++注释: 对于习惯使用 C++ 的string类的人来说,在进行相等性检测的时候一定要特别小心。C++ 的string类重载了==运算符以便检测字符串内容的相等性。可惜 Java 没有采用这种方式,它的字符串“”外观“” 看起来就像数值一样,但进行相等性测试时,表现得又类似于指针。Java 语言的设计者本来也可以像对+那样进行特殊处理,为字符串重新定义==运算符。当然,每一种语言都会存在一些不大一致的地方。
C 程序员从不使用 ==对字符串进行比较,而是使用 strcmp函数。Java 的compareTo()方法完全类似于strcmp,因此,可以如下这样使用:
if(greeting.compareTo("Hello") == 0). . .
不过,使用 equals()看起来更为清晰。

3.6.6 码点与代码单元
  • Java 字符串由char值序列组成。从 3.3.3 节已经看到,char数据类型是一个采用UTF-16编码表示Unicode码点的代码单元。最常用的Unicode字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示。
  • lenght()方法将返回采用UTF-16编码表示给定字符串所需要的代码单元数量。例如:
    String greeting = "Hello";
    int n = greeting.lenght();//is 5
    
  • 要想得到实际的长度,即码点数量,可以调用:
    int cpCount = greeting.codePointCount(0,greeting.lenght());
    
  • 调用 s.charAt(n)将返回位置 n 的代码单元,n 介于 0~s.lenght()-1之间。例如:
    char first = greeting.charAt(0);// first is 'H'
    char last = greeting.charAt(4);// last is 'o'
    
  • 要想得到第 i 个码点,应该使用下列语句:
    int index = greeting.offsetByCodePoint(0,i);
    int cp = greeting.codePointAt(index);
    
  • 为什么会对代码单元如此大惊小怪?请考虑以下语句:
    Ⓞ is the set of octonions.
  • 使用 UTF-16编码表示字符Ⓞ(U+1D546)需要两个代码单元。调用
    char ch = sentence.charAt(1);
  • 返回的不是一个空格,而是 Ⓞ 的第二个代码单元。为了避免这个问题,不要使用char类型。这太底层了。

 注释: 不要以为可以忽略包含 U+FFFF以上代码单元的奇怪字符,喜欢 emoji表情符号的用户可能会在字符串中加入类似(U+1F37A,啤酒杯)的字符。

  • 如果想要遍历一个字符串,并且依次查看每一个码点,可以使用下列语句:
    int cp = sentence.codePointAt(i);
    if(Character.isSupplementaryCodePoint(cp)) i+=2;
    else i++;
    
  • 可以使用下列语句实现反向遍历:
    i--;
    if(Character.isSurrogate(sentence.charAt(i)))i--;
    int cp = sentence.codePointAt(i);
    
  • 显然,这很麻烦。更容易地办法是使用 codePoints()方法,它会生成一个 int值的"流" ,每个int值对应一个码点。可以将它转换为一个数组,再完成遍历。
    int[] codePoints = str.codePoints().toArray();
  • 反之,要把一个码点数组转换为一个字符串,可以使用构造器。
    String str = new String(codePoints,0,codePoints.lenght);

 注释: 虚拟机不一定把字符串实现为代码单元序列。在 Java 9 中,只包含单字节代码单元的字符串使用 byte数组实现,所有其他字符串使用 char数组。

3.6.7 String API
  • Java中的 String类包含了50多个方法。令人惊讶的是它们绝大多数都很有用,可以想见使用频率非常高。下面的API注释汇总了一部分最常用的方法。
  • 本文中给出的API注释可以帮助你理解Java 应用变成接口(API)。每一个API的注释都以类名开始,如java.lang.String。类名之后是一个或多个方法的名字、解释和参数描述。
  • 在这里,一般不会列出某个类的所有方法,而是选择一些最常用的方法,并以简洁的方式给予描述。
  • 这里还会列出所介绍的类的版本号。如果某个方法是在这个版本之后添加的,就会给出一个单独的版本号。

java.lang.String 1.0

  • char charAt(int index);
    • 返回给定位置的代码单元。除非对底层的代码单元感兴趣,否则不需要调用这个方法。
  • int codePointAt(int index); 5
    • 返回从给定位置开始的码点。
  • int offsetByCodePoints(int startIndex , int cpCount); 5
    • 返回从startIndex码点开始,cpCount个码点后的码点索引。
  • int compareTo(String other);
    • 按照字典顺序,如果字符串位于 other之前,返回一个负数;如果字符串位于other之后,返回一个正数;如果两个字符串相等,返回0。
  • IntStream codePoints(); 8
    • 将这个字符串的码点作为一个流返回。调用toArray将它们放在一个数组中。
  • new String(int[] codePoints , int offset , int count); 5
    • 用数组中从 offset开始的count个码点构造一个字符串。
  • boolean empty()
  • boolean blank() 11
    • 如果字符串为空或者由空格组成,返回true。
  • boolean equals(Object other);
    • 如果字符串与other相等,返回true。
  • boolean equalsIgnoreCase(String other);
    • 如果字符串与other相等(忽略大小写),返回 true。
  • boolean startsWith(String prefix);
  • boolean endsWith(String suffix);
    • 如果字符串以 prefix开头或以suffix结尾,则返回true。
  • int indexOf(String str);
  • int indexOf(String str , int fromIndex);
  • int indexOf(int cp);
  • int indexOf(int cp , int fromIndex);
    • 返回与字符串 str或码点 cp匹配的第一个子串的开始位置。从索引 0 或 fromIndex开始匹配。如果在原始字符串中不存在str,则返回 -1。
  • int lastIndexOf(String str);
  • int lastIndexOf(String str , int fromIndex);
  • int lastIndexOf(int cp);
  • int lastIndexOf(int cp , int fromIndex);
    • 返回与字符串 str或码点cp匹配的最后一个子串的开始位置。从原始字符串末尾或fromIndex开始匹配。
  • int lenght();
    • 返回字符串代码单元的个数。
  • int codePointCount(int startIndex , int endIndex); 5
    • 返回startIndex和endIndex-1之间的码点个数。
  • String replace(CharSequence oldString , CharSequence newString);
    • 返回一个新字符串。这个字符串用newString代替原始字符串中所有的oldString。可以用 String或 StirngBuilder对象作为 CharSequence参数。
  • String substring(int beginIndex);
  • Stirng substring(int beginIndex , int endIndex);
    • 返回一个新字符串。这个字符串包含原始字符串中从beginIndex到字符串末尾或endIndex-1的所有代码单元。
  • String toLowerCase();
  • String toUpperCase();
    • 返回一个新字符串。这个字符串将原始字符串中的大写字母改成小写字母,或者将原始字符串中的所有小写字母改成大写字母。
  • String trim();
  • String strip(); 11
    • 返回一个新字符串。这个字符串将删除原始字符串头部和尾部小于等于U+0020的字符(trim) 或 空格(strip)。
  • String join(CharSequence delimiter , CharSequence... elements); 8
    • 返回一个新字符串,用给定的定界符连接所有元素。
  • String repeat(int count); 11
    • 返回一个字符串,将当前字符串重复 count次。

 注释: 在 API注释中,有一些CharSequence类型的参数。这是一种接口类型,所有字符串都属于这个接口。看到一个CharSequence形参(parameter)时,完全可以传入String类型的实参(argument)。

3.6.8 阅读联机 API 文档
  • 正如前面所看到的,String类包含许多方法。而且,在标准库中有几千个类,方法数量更加惊人。要想记住所有的类和方法是一件不太可能的事情。因此,学会使用联机 API 文档十分重要,从中可以查阅标准类库中的所有类和方法。可以从 Oracle 下载 API 文档,并保存在本地。也可以在浏览器中访问http://docs.oracle.com/javase/9/docs/api。
  • 在 Java 9 中,API 文档有一个搜索框(见图 3-2)。较老的版本则有一些窗框,分别包含包列表和类列表。仍然可以点击 frames 菜单项得到这些列表。例如,要获得有关 String类方法的更多信息,可以在搜索框中键入 “String” ,选择类型 java.lang.String,或者在窗框中找到 String链接,然后点击这个链接。你会看到这个类的描述,如图 3-3 所示。
  • 接下来,向下滚动,直到看见字母顺序排列的所有方法的小结(请参看图 3-4)。点击任何一个方法名便可以查看这个方法的详细描述(参见图 3-5)。例如,如果点击compareToIgnoreCasw链接,就会看到 compareToIgnoreCase方法的描述。

提示: 如果还没有下载,现在就请按第2大节的介绍下载 JDK 文档。马上在浏览器中为 jdk-9-docs/index.html 页面建一个书签。


3.6.9 构建字符串
  • 有些时候,需要由较短的字符串构建字符串,例如,按键或来自文件中的单词。如果采用字符串拼接的方式来达到这个目的,效率会比较低。每次拼接字符串时,都会构建一个新的String对象,既耗时,又浪费空间。使用 StringBuilder类可以避免这个问题的发生。
  • 如果需要用许多小段的字符串来构建一个字符串,那么应该按照下列步骤进行。首先,构建一个空的字符串构建器:
    StringBuilder builder = new StringBuilder();
    • 当每次需要添加一部分内容时,就调用append()方法。
    builder.append(ch);//appends a single character
    builder.append(str);//appends a string
    
  • 在字符串构建完成时就调用 toString()方法,将可以得到一个String对象,其中包含了构建器中的字符序列。
    Stirng completedString = builder.toString();

 注释: StirngBuilder类在Java 5 中引入。这个类的前身是StringBuffer,它的效率稍有些低,但允许采用多线程的方式添加或删除字符。如果所有字符串编辑操作都在单个线程中执行(通常都是这样),则应该使用 SreingBuilder。这两个类的API 是一样的。

  • 下面的API注释包含了StringBuilder 类中的重要方法。

java.lang.StringBuilder 5

  • StringBuilder();
    • 构造一个空字符串构建器。
  • int length();
    • 返回构建器或缓冲器中的代码单元数量。
  • StringBuilder append(Stirng str);
    • 追加一个字符串并返回 this。
  • StringBuilder append(char c);
    • 追加一个代码单元并返回this。
  • StringBuilder appendCodePoint(int cp);
    • 追加一个码点,并将其转换为一个或两个代码单元并返回 this。
  • void setCharAt(int i , char c);
    • 将第 i个代码单元设置为c。
  • StringBuilder insert(int offset , Stirng str);
    • 在 offset位置插入一个字符串并返回 this。
  • StringBuilder insert(int offset , char c);
    • 在 offset 位置插入一个代码单元并返回this。
  • StringBuilder delete(int startIndex , int endIndex);
    • 删除偏移量从startIndex到 endIndex-1的代码单元并返回this。
  • Stirng toString();
    • 返回一个与构建器或缓冲器内容相同的字符串。
3.7 输入与输出
  • 为了增加后面示例程序的趣味性,需要程序能够接受输入,并适当地格式化程序输出。当然,现代的程序都是用 GUI 收集用户的输入,然而,编写这种界面的程序需要使用较多工具与技术,目前还不具备这些条件。我们的第一要务是熟悉 Java 程序设计语言,因此我们要使用基本的控制台来实现输入输出。
3.7.1 读取输入
  • 前面已经看到,将输出打印到 “标准输出流” (即控制台窗口)是一件非常容易地事情,只要调用 System.out.println();即可。然而,读取“标准输入流” System.in();就没那么简单了。要想通过控制台进行输入,首先需要构造一个与“标准输入流” System.in();关联的 Scanner对象。
    Scanner in = new Scanner(System.in);
  • 现在,就可以使用 Scanner类的各种方法读取输入了。例如,nextLine()方法读取一行输入。
    System.out.prinln("What is your name?");
    String name = in.nextLine();
    
  • 在这里,使用nextLine();方法是因为在输入行中有可能包含空格。要想读取一个单词(以空白符作为分隔符),可以调用 Stirng firstName = in.next();
  • 要想读取一个整数,就调用 nextInt();方法。
    System.out.println("How old are you?");
    int age = in.nextInt();
    
  • 与此类似,要想读取下一个浮点数,就调用 nextDouble();方法。
  • 在程序清单 3-2 的程序中,首先询问用户姓名合年龄,然后打印一条如下的消息:
    Hello , Cay , Next year , you'll be 57
  • 最后,在程序的最前面添加一行代码:import java.util.*;
  • Scanner类定义在 java.util包中。当使用的类不是定义在基本 java.lang包中时,一定使用 import指令导入相应的包。

程序清单 3-2 InputTest/InputTest.java

import java.util.*;


public class InputTest{
	public static void main(String[] args){
		Scanner in = new Scanner(System.in);
		// get first input(得到第一个输入)
		System.out.println("你的名字叫什么?");
		String name = in.nextLine();
		
		// get second input(得到第二个输入)
		System.out.println("你年龄多大?");
		int age = in.nextInt();

		// display output in consle (在控制台显示输出)
		System.out.println("你好," + name + ",下一年,你将是" + (age + 1));
	}
}

 注释: 因为输入是可见的,所以 Scanner类不适用于从控制台读取密码。 Java 6 特别引入了 Console类来实现这个目的。要想读取一个密码,可以使用以下代码:

Console cons = System.console();
String username = cons.readLine("User name:");
char[] passwd = cons.readPassword("Password:");

为安全起见,返回的密码存放在一个字符数组中,而不是字符串中。在对密码处理完成之后,应该马上用一个填充值覆盖原数组元素。
采用 Console对象处理输入不如采用 Scanner方便。必须每次读取一行输入,而没有能够读取单个单词或数值的方法。

java.util.Scanner 5

  • Scanner(InputStream in);
    • 用给定的输入流创建一个 Scanner对象。
  • String nextLine();
    • 读取输入的下一行内容。
  • String next();
    • 读取输入的下一个单词(以空格作为分隔符)。
  • int nextInt();
  • double nextDouble();
    • 读取并转换下一个表示整数或浮点数的字符序列。
  • boolean hasNext();
    • 检测输入中是否还有其他单词。
  • boolean hasNextInt();
  • boolean hasNextDouble();
    • 检测是否还有下一个表示整数或浮点数的字符序列。

java.lang.System 1.0

  • static Console console();
    • 如果可以进行交互,就返回一个 Console对象通过控制台窗口与用户交互,否则返回 null。对于任何一个在控制台窗口启动的程序,都可以使用 Console对象。否则,是否取决于所使用的系统。

java.io.Console 6

  • static char[] readPassword(String prompt , Object... args);
  • static String readLine(String prompt, Object... agrs);
    • 显示字符串 prompt(提示符)并读取用户输入,直到输入行结束。args参数可以用来提供格式参数。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/272378.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号