《Java 核心技术卷1 基础知识》第三章 Java 的基本程序设计结构 笔记(下)

简介: 《Java 核心技术卷1 基础知识》第三章 Java 的基本程序设计结构 笔记

《Java 核心技术卷1 基础知识》第三章 Java 的基本程序设计结构 笔记(上):https://developer.aliyun.com/article/1391462

3.6 字符串

从概念上讲,Java 字符串就是 Unicode 字符序列


Java 没有内置的字符串类型,而是在标准 Java 类库中提供了一个预定义类,很自然地叫做 String


3.6.1 子串

String 类的 substring 方法可以从一个较大的字符串提取出一个子串


String greeting = "Hello";


String s = greeting.substring(0, 3);


在 substring 中从 0 开始计数,直到 3 为止,但不包含 3


3.6.2 拼接

Java 语言允许使用 + 号连接(拼接)两个字符串


当一个字符串与一个非字符串的值进行拼接时,后者会转换成字符串


任何一个 Java 对象都可以转换成字符串


把多个字符串放在一起,用一个界定符分隔,可以使用静态 join 方法


String all = String.join(" / ", "s", "m", "l", "xl");


在 Java 11 中,还提供了一个 repeat 方法


String repeated = "Java".repeat(3);


3.6.3 不可变字符串

String 类没有提供修改字符串中某个字符的方法


如何修改这个字符串呢?可以提取想要保留的子串,再与希望替换的字符拼接


由于不能修改 Java 字符串中的单个字符,所以在 Java 文档中将 String 类对象称为不可变的


不过,可以修改字符串变量,让它引用另外一个字符串


好像修改一个代码单元要比从头创建一个新字符串更加简洁


通过拼接来创建一个新字符串的效率确实不高


不可变字符串却有一个优点:编译器可以让字符串共享


可以想象将各种字符串放在公共的存储池中。字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串与复制的字符串共享相同的字符。


Java 的设计者认为共享带来的高效率远远胜过于提取子串、拼接字符串所带来的低效率。可以看看你自己的程序,我们发现:大多数情况下都不会修改字符串,而只是需要对字符串进行比较


Java 字符串大致类似于 char* 指针:


char* greeting = "Hello";


当把 greeting 替换为另一个字符串的时候,Java 代码大致进行下列操作:


char* temp = malloc(6);


strncpy(temp, greeting, 3);


strncpy(temp + 3, "p!", 3);


greeting = temp;


3.6.4 检测字符串是否相等

使用 equals 方法检测两个字符串是否相等


检测两个字符串是否相等,而不区分大小写,可以使用 equalsIgnoreCase 方法


一定不要使用 == 运算符检测两个字符串是否相等!这个运算符只能够确定两个字符串是否存放在同一个位置上。当然,如果字符串在同一个位置上,它们必然相等。但是,完全有可能将内容相同的多个字符串副本放置在不同的位置上


如果虚拟机始终将相同的字符串共享,就可以使用 == 运算符检测是否相等。但实际上只有字符串字面量是共享的,而 + 和 substring 等操作得到的字符串并不共享。因此,千万不要使用 == 运算符测试字符串的相等性,以免在程序中出现这种最糟糕的 bug,看起来这种 bug 就像随机产生的间歇性错误


3.6.5 空串与 Null 串

空串 “” 是长度为 0 的字符串,检查一个字符串是否为空


if (str.length() == 0)


if (str.equals(""))


空串是一个 Java 对象,有自己的串长度(0)和内容(空)


String 变量还可以存放一个特殊的值,名为 null,表示目前没有任何对象与该变量关联


要检查一个字符串是否为 null,要使用以下条件:


if (str == null)


有时要检查一个字符串既不是 null 也不是空串


if (str != null && str.length() != 0)


首先要检查 str 不为 null


如果在一个 null 值傻姑娘调用方法,会出现错误


3.6.6 码点与代码单元

Java 字符串是由 char 值序列组成


char 数据类型是一个采用 UTF-16 编码表示 Unicode 码点的代码单元


最常用的 Unicode 字符使用一个代码单元就可以表示,而辅助字符需要一对代码单元表示


length 方法将返回采用 UTF-16 编码表示给定字符串所需要的代码单元数量


String greeting = "Hello";


int n = greeting.length(); // is 5


得到实际的数量,即码点数量


int cpCount = greeting.codePointCount(0, greeting.length()); // is 5


调用 s.charAt(n) 将返回位置 n 的代码单元, n 介于 0 ~ s.length() - 1 之间


char first = greeting.charAt(0); // first is 'H'


char last = greeting.charAt(4); // last is 'o'


遍历一个字符串,并且依次查看每一个码点,更容易的办法是使用 codePoints 方法,它会生成一个 int 值的“流”,每个 int 值对应一个码点


int[] codePoints = str.codePoints().toArray();


要把一个码点数组转换为一个字符串,可以使用构造器


String str = new String(codePoints, 0, codePoints.length);


3.6.7 String API

Java 中的 String 类包含了 50 多个方法。令人惊讶的是它们绝大多数都很有用,可以想见使用的频率非常高


java.lang.String


在 API 注释中,有一些 CharSequence 类型的参数。这是一种接口类型,所有字符串都属于这个接口。


当看到一个 CharSequence 形参时,完全可以传入 String 类型的实参


3.6.8 阅读联机 API 文档

可以在浏览器中访问 http://docs.oracle.com/javase/9/docs/api


3.6.9 构造字符串

有些时候,需要由较短的字符串构建字符串。如果采用字符串拼接的方式来达到这个目的,效率会比较低。每次拼接字符串时,都会构建一个新的 String 对象,既耗时,又浪费空间。使用 StringBuilder 类就可以避免这个问题的发生。


如果需要用许多小段的字符串构建一个字符串,那么应该按照下列步骤进行。


首先构建一个空的字符串构建器:


StringBuilder builder = new StringBuilder();


当每次需要添加一部分内容时,就调用 append 方法。


builder.append(ch);


builder.append(str);


在字符串构建完成时就调用 toString 方法,将可以得到一个 String 对象,其中包含了构建器中的字符序列。


String copletedString = builder.toString();


StringBuilder 类在 Java 5 中引入。这个类的前身是 StringBuffer,它的效率稍有些低,但允许采用多线程的方式添加或删除字符。如果所有字符串编辑操作都在单个线程中执行,则应该使用 StringBuilder。


3.7 输入与输出

3.7.1 读取输入

要想通过控制台进行输入,首先需要构造一个与“标准输入流” System.in 关联的 Scanner 对象


Scanner in = new Scanner(System.in);


使用 Scanner 类的各种方法读取输入


nextLine 方法将读取一行输入


String name = in.nextLine();


使用 nextLine 是因为在输入航中有可能包含空格。要想读取一个单词,可以调用


String firstName = in.next();


要想读取一个整数,就调用 nextInt 方法


int age = in.nextInt();


要想读取一个浮点数,就调用 nextDouble 方法


Scanner 类定义在 java.util 包中。当使用的类不是定义在基本 java.lang 包中时,一定要使用 import 指令倒入相应的包


程序清单 3-2

package chapter3.InputTest;
import java.io.Console;
import java.util.Scanner;
public class InputTest
{
    public static void main(String[] args)
    {
        Scanner in = new Scanner(System.in);
        System.out.println("What is your name?");
        String name = in.nextLine();
        System.out.println("How old are you?");
        int age = in.nextInt();
        System.out.println("Hell, " + name + ". Next year, you'll be " + (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


boolean hasNext()


boolean hasNextInt()


boolean hasNextDouble()


3.7.2 格式化输出

Java 5 沿用了 C 语言函数库中的 printf 方法


System.out.printf("%8.2f", x);


会以一个字段宽度打印 x:这包括 8 个字符,另外精度为小数点后 2 个字符


可以为 printf 提供多个参数


System.out.printf("Hello, %s. Next year, you'll be %d", name, age)


每一个以 % 字符开始的格式说明符都用相应都参数替换。格式说明符尾部都转换符指示要格式化的数值的类型:f 表示浮点数,s 表示字符串,d 表示十进制整数。


可以使用静态的 String.format 方法创建一个格式化的字符串


String message = String.format("Hello, %s. Next year, you'll be %d", name, age);


3.7.3 文件输入与输出

要想读取一个文件,需要构造一个 Scanner 对象


Scanner in = new Scanner(Path.of("myfile.txt"), StandardCharsets.UTF_8);


读取一个文件时,要知道它的字符编码


要想写入文件,就需要构造一个 PrintWriter 对象。在构造器中,需要提供文件名和字符编码


PrintWriter out = new PrintWriter("myfile.txt", StandardCharset.UTF_8);


如果文件不存在,创建该文件。可以像输出到 System.out 一样使用 print、println 以及 printf 命令


当指定一个相对文件时,文件位于相对于 Java 虚拟机启动目录的位置


java MyProg


启动目录就是命令解释器的当前目录


使用集成开发环境,那么启动目录将由 IDE 控制。可以使用下面的调用找到这个目录的位置:


String dir = System.getProperty("user.dir");


如果觉得定位文件太麻烦,可以考虑使用绝对路径名


如果用一个不存在的文件构造一个 Scanner,或者用一个无法创建的文件名构造一个 PrintWriter,就会产生异常


java.nio.file.Path


static Path of(String pathname) 根据给定的路径名构造一个 Path


3.8 控制流程

Java 中没有 goto 语句,但 break 语句可以带标签,可以利用它从内层循环跳出


3.8.1 块作用域

块(block)


块(即复合语句)是指由若干条 Java 语句组成的语句,并用一对大括号括起来。块确定了变量的作用域。一个块可以嵌套在另一个块中。


不能在嵌套的块中声明同名的变量


3.8.2 条件语句

在 Java 中,条件语句的形式为


if (condition) statement


条件必须用小括号括起来


块语句(block statement)


更一般的条件语句如下所示


if (condition) statement1 else statement2


else 部分总是可选的。else 子句与最邻近的 if 构成一组


3.8.3 循环

当条件为 true 时,while 循环执行一条语句(也可以是一个块语句)。一般形式如下:


while (condition) statement


如果开始时循环条件的值就为 false,那么 while 循环一次也不执行


如果希望循环体至少执行一次,需要使用 do/while 循环将检测放在最后。它的语法如下:


do statement while (condition)


这种循环语句先执行语句(通常是一个语句块),然欧再检测循环条件。如果为 true,就重复执行语句,然后再次检测循环条件,以此类推。


3.8.4 确定循环

for 循环语句是支持迭代的一种通用结构,由一个计数器或类似的变量控制地带次数,每次迭代后这个变量将会更新


for 语句的第 1 部分通常是对计数器初始化;第 2 部分给出每次新一轮循环执行前要检测的循环条件;第 3 部分指定如何更新计数器


尽管 Java 允许在 for 循环的各个部分放置任何表达式,但有一条不成文但规则:for 语句的 3 个部分应该对同一个计数器变量进行初始化、检测和更新。若不遵守这一规则,编写的循环常常晦涩难懂。


当在 for 语句的第 1 部分中声明一个变量之后,这个变量的作用域就扩展到这个 for 循环体的末尾


如果在 for 语句内部定义一个变量,这个变量就不能在循环体之外使用。因此,如果希望在 for 循环体之外使用循环计数器的最终值,就要确保这个变量在循环之外声明。


可以在不同的 for 循环中定义同名的变量


for 循环语句只不过是 while 循环的一种简化形式


3.8.5 多重选择:switch 语句

Java 有一个与 C/C++ 完全一样的 switch 语句


switch 语句将从与选项值相匹配的 case 标签开始执行,直到遇到 break 语句,或者执行到 switch 语句的结束处为止。如果没有相匹配的 case 标签,而有 default 子句,就执行这个子句。


有可能触发多个 case 分支。如果在 case 分支语句的末尾没有 break 语句,那么就会接着执行下一个 case 分支语句。


编译代码时可以考虑加上 +Xlint:fallthrough 选项,如下:


javac -Xlint:fallthrough Test.java


这样一来,如果某个分支最后缺少一个 break 语句,编译器就会给出一个警告消息


如果你确实正是想使用这种“直通式”(fallthrough)行为,可以为其外围方法加一个注解 @SuppressWarnings("fallthrough")。这样就不会对这个方法生成警告了。


注解是为编译器或处理 Java 源文件或类文件的工具提供信息的一种机制


case 标签可以是:


类型为 char、byte、short 或 int 的常量表达式

枚举常量

从 Java 7 开始,case 标签还可以是字符串字面量

当在 switch 语句中使用枚举常量时,不必在每个标签中指明枚举名,可以由 switch 的表达式值推倒得出。


Size sz = ...;
switch (sz)
{
case SMALL: // no need to use Size.SMALL
...
break;
...
}


3.8.6 中断控制流程的语句

Java 的设计者将 goto 作为保留字


通常,使用 goto 语句被认为是一种拙劣的程序设计风格


偶尔使用 goto 跳出循环还是有益处的。Java 设计者同意这种看法,甚至在 Java 语言中增加了一条新的语句:带标签的 break,以此来支持这种程序设计风格。


Java 还提供了一种带标签的 break 语句,用于跳出多重嵌套的循环语句


在嵌套很深的循环语句中会发生一些不可预料的事情。此时可能更加希望完全跳出所有嵌套循环之外


标签必须放在希望跳出的最外层循环之前,并且必须紧跟一个冒号


执行带标签的 break 会跳转到带标签的语句块末尾


continue 语句将控制转移到最内层循环的首部


如果将 continue 语句用于 for 循环中,就可以跳到 for 循环的“更新”部分


还有一种带标签的 continue 语句,将跳到与标签匹配的循环的首部


3.9 大数

java.math 包中两个很有用的类:BigInteger 和 BigDecimal。这两个类可以处理包含任意长度数字序列的数值。BigInteger 类实现任意精度的整数运算,BigDecimal 实现任意精度的浮点数运算。


使用静态的 valueOf 方法可以将普通的数值转换为大数:


BigInteger a = BigInteger.valueOf(100);


对于更大的数,可以使用一个带字符串参数的构造器:


BigInteger reallyBig = new BigInteger("1234567890987654321");


另外还有一些常量:BigInteger.ZERO、BigInteger.ONE 和 BigInteger.TEN,Java 9 之后还增加了 BigInteger.TWO


不能使用人们熟悉的算术运算符处理大数,而需要使用大数类中的方法


BigInteger c = a.add(b); // c = a + b


BigInteger d = c.multiply(b.add(BigInteger.valueOf(2))); // d = c * (b + 2)


Java 没有提供运算符重载功能


3.10 数组

数组存储相同类型的序列


3.10.1 声明数组

数组是一种数据结构,用来存储同一类型值的集合。通过一个整型下表可以访问数组中的每一个值。


在声明数组变量时,需要指出数组类型和数组变量的名字。


int[] a;


这条语句只声明类变量 a,并没有将 a 初始化为一个真正的数组。应该使用 new 操作符创建数组。


int[] a = new int[100];


这条语句声明并初始化类一个可以存储 100 个整数的数组。


数组长度不要求是常量:new int[n] 会创建一个长度为 n 的数组


一旦创建了数组,就不能再改变它的长度


如果程序运行中需要经常扩展数组大的大小,就应该使用另一种数据结构 —— 数组列表(array list)


一种创建数组对象并同时提供初始化值的简写形式


int[] smallPrimes = {2, 3, 4, 5, 11, 13};


这个语法不需要使用 new,甚至不用指定长度


声明一个匿名数组:


new int[] {17, 19, 23, 29, 31, 37};


这会分配一个新数组并填入大括号中提供的值。它会统计初始化个数,并相应地设置数组大小。可以使用这种语法重新初始化一个数组而无须创建新变量。


int[] anonymous = {17, 19, 23, 29, 31, 37};


smallPrimes = anonymous;


在 Java 中,允许有长度为 0 的数组


3.10.2 访问数组元素

一旦创建了数组,就可以在数组中填入元素


创建一个数字数组时,所有元素都初始化为 0。boolean 数组的元素会初始化为 false。对象数组都元素则初始化为一个特殊值 null,表示这些元素未存放任何对象。


String[] names = new String[10];


会创建一个包含 10 个字符串的数组,所有字符串都为 null。如果希望这个数组包含空串,必须为元素指定空串:


for (int i = 0; i < 10; i ++) names[i] = "";


如果创建了一个 100 个元素的数组,并且试图访问元素 a[101],就会引发“array index out of bounds”异常


要想获得数组中的元素个数,可以使用 array.length


3.10.3 for each 循环

增强的 for 循环的语句格式为:


for (variable : collection) statement


它定义一个变量用于暂存集合中的每一个元素,并执行相应的语句。collection 这一集合表达式必须是一个数组或者一个实现了 Iterable 接口的类对象。


for each 循环语句显得更加简洁、更不易出错,因为你不必为下标的起始值和终值而操心


在很多情况下还是需要使用传统的 for 循环。例如,如果不希望遍历整个集合,或者在循环内部需要使用下标值。


调用 Arrays.toString(a),返回一个包含数组元素的字符串,这些元素包围在中括号内,并用逗号分隔。


System.out.println(Arrays.toString(a));


3.10.4 数组拷贝

在 Java 中,允许将一个数组变量拷贝到另一个数组变量。这时,两个变量将引用同一个数组


int[] luckyNumbers = smallPrimes;


luckyNumber2[5] = 12; // now smallPrimes[5] is also 12


如果希望将一个数组到所有值拷贝到一个新的数组中去,就要使用 Arrays 类的 copyOf 方法:


int[] coiedLucyNumbers = Arrays.copyOf(luckyNumbers, luckyNumbers.length);


第 2 个参数是新数组的长度。这个方法通常用来增加数组的大小:


luckyNumbers = Arrays.copyOf(luckyNumbers, 2 * luckyNumbers.length);


如果长度小于原始数组的长度,则只拷贝前面的值


C++ 注释:基本上与在堆(heap)上分配的数组指针一样。也就是说,


int[] a = new int[100];


不用于


int a[100];


而等同于


int *a = new int[100];


Java 中的 [] 运算符被预定义为会完成越界检查,而没有指针运算,即不能通过 a 加 1 得到数组中的下一个元素


3.10.5 命令行参数

每一个 Java 应用程序都有一个带 String args[] 参数的 main 方法。这个参数表明 main 方法将接收一个字符串数组,也就是命令行上指定的参数。


C++ 注释:程序名并没有存储在 args 数组中


3.10.6 数组排序

要想对数值型数组进行排序,可以使用 Arrays 类中的 sort 方法


int[] a = new int[100];


...


Arrays.sort(a);


这个方法使用了优化的快速排序算法。快速排序算法对于大多数数据集合来说都是效率比较高的。


Math.random 方法将返回一个 0 到 1 之前(包含 0、不包含 1)的随机浮点数。用 n 乘以这个浮点数,就可以得到从 0 到 n - 1 之间的一个随机数


sort


copyOf


copyOfRange


binarySearch


fill


equals


3.10.7 多维数组

多维数组将使用多个下标访问数组元素,它适用于表示表格或更加复杂的排列形式


声明一个二维数组相当简单


double[][] balance;


对数组进行初始化之前是不能使用的。在这里可以如下初始化:


balance = new double[NYEAR][NRATE];


如果直到数组元素,就可以不调用 new ,而直接使用简写形式对多维数组进行初始化。

int[][] magicSquare =
{
{1, 2, 3, 4},
{2, 3, 4, 5},
{3, 4, 5, 6}
};


一旦数组初始化,就可以利用两个中括号访问各个元素


for each 循环语句不能自动处理二维数组对每一个元素。它会循环处理行,而这些行本身就是一维数组。要想访问二维数组 a 的所有元素,需要使用两个嵌套的循环,循环如下:

for (double[] row: a)
for (double value : row)
do someting with value


要想快速地打印一个二维数组的数据元素列表,可以调用


System.out.println(Arrays.deepToString(a));


3.10.8 不规则数组

Java 实际上没有多维数组,只有一维数组。多维数组被解释为“数组的数组”


由于可以单独地访问数组的某一行,所以可以让两行交换


double[] temp = balances[i];
balance[i] = balances[i + 1];
balance[i + 1] = temp;


还可以方便地构造一个“不规则”数组,即数组的每一行有不同的长度

int[][] odds = new int[NMAX][];
for (int n = 0; n
odds[n] = new int[n + 1];


相关文章
|
23天前
|
Java 开发工具 Android开发
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
本系列教程笔记详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。若需快速学习Kotlin,建议查看“简洁”系列教程。本期重点介绍了Kotlin与Java的共存方式,包括属性、单例对象、默认参数方法、包方法、扩展方法以及内部类和成员的互操作性。通过这些内容,帮助你在项目中更好地结合使用这两种语言。
40 1
|
24天前
|
Java 开发工具 Android开发
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
Kotlin语法笔记(26) -Kotlin 与 Java 共存(1)
32 2
|
22天前
|
Java 测试技术 开发者
💡Java 零基础:彻底掌握 for 循环,打造高效程序设计
【10月更文挑战第15天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
108 63
|
14天前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
14天前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
14天前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
|
23天前
|
Java 编译器 Android开发
Kotlin语法笔记(28) -Kotlin 与 Java 混编
本系列教程详细讲解了Kotlin语法,适合需要深入了解Kotlin的开发者。对于希望快速学习Kotlin的用户,推荐查看“简洁”系列教程。本文档重点介绍了Kotlin与Java混编的技巧,包括代码转换、类调用、ProGuard问题、Android library开发建议以及在Kotlin和Java之间互相调用的方法。
20 1
|
23天前
|
安全 Java 编译器
Kotlin语法笔记(27) -Kotlin 与 Java 共存(二)
本教程详细讲解Kotlin语法,适合希望深入了解Kotlin的开发者。若需快速入门,建议查阅“简洁”系列教程。本文重点探讨Kotlin与Java共存的高级话题,包括属性访问、空安全、泛型处理、同步机制及SAM转换等,助你在项目中逐步引入Kotlin。
20 1
|
24天前
|
Java 编译器 Android开发
Kotlin语法笔记(28) -Kotlin 与 Java 混编
Kotlin语法笔记(28) -Kotlin 与 Java 混编
25 2
|
24天前
|
安全 Java 编译器
Kotlin语法笔记(27) -Kotlin 与 Java 共存(二)
Kotlin语法笔记(27) -Kotlin 与 Java 共存(二)
29 0