【Java核心计算 基础知识(第9版)】第3章 Java的基本程序设计结构
本章要点
- 一个简单的Java应用程序
- 注释
- 数据类型
- 变量
- 运算符
- 字符串
- 输入输出流
- 控制流
- 大数值
- 数组
3.1 一个简单的Java应用程序
上图是一个最简单的Java程序,它表示:声明一个名为FirstSample的public类,当运行这个类时,JVM会调用类中的main方法,该方法向控制台打印“Hello, world!”这么一句话。麻雀虽小,但是Java程序的基本要素都包含在里面。
1)Java对大小写敏感
- 这意味着,对Java而言,Main和main是两个不同的名词。
2)public:访问修饰符
- 它用于控制程序的其他部分对这段代码的访问权限,有public,protected,default,private四种。
3)class:声明一个类
- class用于声明一个类。可以将类视为一个加载程序逻辑的容器,程序逻辑定义了应用程序的行为。
- 类是构建所有Java应用程序的构建块。
- 一个源文件(.java文件)最多只能包含一个public类,且源文件与该类名字必须相同。
- enum用于声明一个枚举,声明方法不需要特定的标识符。
4)编译与运行
- 确保已安装JDK且配置环境变量。
- 编译:javac FirstSample.java,即javac + 空格 + 源文件名(含扩展名.java),可不区分大小写(因Windows不区分大小写)。
- 运行:java FirstSample,即java + 空格 + 程序名(不含扩展名.class),严格区分大小写。
- 运行程序时,JVM将从public类中的main方法开始执行,这里就是程序的入口。所以一个源文件中只能包含一个public类,这个类中只能有一个main方法,这个方法必须拥有最高的访问权限(public)。
5)代码块与语句
- 用{}来区分程序的各个部分,如类与类,方法与方法,局部代码块等。{为该部分的起点,}为该部分的结束。
- 在一对{}之间声明的标识符只在这部分有效。
- “语句”表示一个句子,可视为代码的基本单元,必须以;结束。
6)对成员的调用
- 成员指类的方法和变量
- .用于表示对成员的调用,如object.method(param)表示调用一个方法,object.variable表示调用一个变量。
3.2 注释
- 注释不会出现在编译后的可执行程序中,它用于程序员对所写代码的说明,如这段代码为什么要这么写,别人调用我的接口需要注意什么,这个变量是用来做什么的等。
- Java有三种注释。
- // 单行注释:注释本行。
- /* */ 多行注释:注释被包裹的内容。
- /** */ 文档注释:注释被包裹的内容,并且可以用javadoc命令自动生成文档。
3.3 数据类型
-
Java是一种强类型语言,即每个变量必须声明一种类型,一旦声明之后不可以改变。
-
8种基本数据类型:
类型 | 存储需求 | 最大值 | 最小值 | 示例 | 备注 |
---|---|---|---|---|---|
long | 8 Byte | −263 | 263−1 | 123456789L | |
int | 4 Byte | −231 | 231−1 (约21亿) | 123456789 | 整数默认类型 |
short | 2 Byte | −215 | 215−1 (32767) | 12345 | |
byte | 1 Byte | −27 | 27−1(127) | 123 | |
double | 8 Byte | 约-1.798E+308 | 约1.798E+308(21024) | 123456789.0 | 小数默认类型,有效位数15位 |
float | 4 Byte | 约-3.4E+38F | 约3.4E+38F (2128) | 123456789.0F | 有效位数6~7位 |
char | 2 Byte | 0 (\u0000) | 65535 (\uffff) | ‘A’ | 转换为数值时无负值 |
boolean | true/false | 无法转换为其他类型 |
注1:double和float计算遵循IEEE754规范。
注2:三个特殊值常量
- 正无穷 Double.POSITIVE_INFINITY,如 1/0.0
- 负无穷 Double.NEGATIVE_INFINITY,如 -1/0.0
- 非数值 Double.NaN,如 0/0.0
- 所有”非数值“均不相等,检测非数值只能用Double.isNaN(x)
-
java中的进制表示
- 十进制 1234567890
- 十六进制 0x123ABCDEF
- 八进制 012345678
- 二进制 0b00001111
-
十进制转换为二进制:除以2取余 ,直到商为0,然后将余数倒序排列
-
二进制转换为十进制:各位乘以2n−1并求和
如 110: 1∗22+1∗21+0∗20=4+2+0=6 -
负数的二进制表现形式:绝对值得二进制形式各位取反,然后+1
-
引用数据类型: class, enum, interface, array
3.4 变量
在Java中,每一个变量属于一种类型。在声明变量时,变量所属的类型位于变量名之前。
如: int counter;
double salary;
3.4.1 变量初始化
- 声明一个变量之后,必须用赋值语句对变量进行显式初始化(原文如此,这里应特指局部变量),变量被初始化之前不能使用。
- 变量的赋值和使用:
int i;
i = 20;
i = 30;
String str = "abc";
str = str + "def";
3.4.2 常量
- 常量用关键字”final”指示,表示这个变量只能被赋值一次,即一旦被赋值之后,就不能再更改。
- 声明为final的成员变量无默认值,必须显式初始化。
- 常量的赋值和使用:
class Demo{
static final int sCounter = 0; // 静态常量必须在声明时显式初始化
final int iSalary = 25000; // 非静态的常量可在声明时初始化或在构造方法中初始化
final int iDays;
Demo(){
iDays = 10;
}
}
3.5 运算符
- +, -, *, /, % : 加、减、乘、除、取余
3.5.1 自增运算符与自减运算符
- ++, –
- ++i : 先进行加1运算,再参与其他运算;
- i++ : 先参与其他运算,再进行加1运算;
- 因容易混淆,并且可能产生bug(曾听说过在某些版本的JDK下向控制台打印i++与++i输出同样的值),不建议在其他表达式内部使用自增/自减运算;
int i = 1;
int j = 1;
int k = i++ * j++; // 不建议使用
System.out.println(++i); // 2
System.out.println(j++); // 1
3.5.2 关系运算符与boolean运算符
- ==, != :相等,不相等(用于基本类型时判断字面值,用于引用类型时判断引用地址)
- <, <=, >, >=
- &, &&, |, ||
- 三元操作符?:,如String str = 1 == 2 ? “1等于2” : “1不等于2”;
3.5.3 位运算符
- 在处理整型数值时,可以直接对组成整型数值的各个位进行操作。
- &, |, ^, ~, >>, >>>, << 与,或,异或,非,右移,无符号右移,左移
- 应用例子:用一个变量的每个二进制位各代表一个flag:
class Test{
int mod1 = 0b1;
int mod2 = 0b10;
int mod3 = 0b100;
int mod4 = 0b1000;
int mod5 = 0b10000;
int mod6 = 0b100000;
int mod7 = 0b1000000;
int mod8 = 0b10000000;
boolean isMod1(int mod){
return ((mod & mod1) != 0) ? true : false;
}
boolean isMod2(int mod){
return ((mod & mod2) != 0) ? true : false;
}
...
}
3.5.4 数学函数与常量
- 请查阅java.lang.Math类
3.5.5 数值类型之间的转换
- byte, short, char三种类型之间的运算自动提升为int
- int -> long -> float -> double,自动向上转型
3.5.6 强制类型转换
- 转换方式:(targerType) variable。
- 通过截断小数部分而不是四舍五入将浮点值转换为整型。
- 当变量超过目标类型的表示范围时,会根据补码截断,而不是保留目标类型的最大/最小值。
3.5.7 括号与运算符级别
优先级 | 运算符 | 结合性 |
---|---|---|
1 | [] . ()(方法调用) | 从左向右 |
2 | ! ~ ++ – +(正号) -(负号) ()(强制类型转换) new | 从右向左 |
3 | */% | 从左向右 |
4 | +- | 从左向右 |
5 | << >> >>> | 从左向右 |
6 | < <= > >= instanceof | 从左向右 |
7 | == != | 从左向右 |
8 | & | 从左向右 |
9 | ^ | 从左向右 |
10 | | | 从左向右 |
11 | && | 从左向右 |
12 | || | 从左向右 |
13 | ?:(三元运算符) | 从右向左 |
14 | = += -= *= /= %= &= |= ^= <<= >>= >>>= | 从右向左 |
3.5.8 枚举类型
- 枚举类型包括有限和命名的值。如 enum Size{S,M,L,XL,XXL,XXXL}
class Demo{
enum Size{
S,M,L,XL,XXL,XXXL
}
void test(){
Size s = Size.S;
}
}
3.6 字符串
- 从概念上讲,Java字符串就是Unicode字符序列。Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义类String,每个用”“括起来的字符串都是String类的一个实例。
3.6.1 子串
- 一个字符串中“一段”称为它的一个子串。
- 空串”“是任何字符串的子串。
- String类的substring(a,b)方法可以从截取子串,截取范围为[a,b)。contains(str)方法判断是否包含该子串。
3.6.2 拼接
- 可以使用+号拼接任意两个字符串。
- 当将一个字符串与任意非字符串的值拼接时,后者被转换为字符串(基本类型根据字面值转换,引用类型根据toString()方法转换)。
public class Demo {
public static void main(String[] args) {
Demo1 demo1 = new Demo1();
Demo2 demo2 = new Demo2();
System.out.println(demo1 + ""); // 输出demo1的引用地址
System.out.println(demo2 + ""); // 输出“Demo”
}
}
class Demo1{
}
class Demo2{
@Override
public String toString() {
return "Demo";
}
}
3.6.3 不可变字符串
- 由于不能修改Java字符串中的字符,所以在Java文档中将String类对象称为不可变字符串。例如,”Hello”永远包含字符H,e,l,l,o的代码单元序列,一旦改变,就不再是”Hello”。
- 字符串不可改变,但字符串变量所引用的字符串对象是可以改变的。
- 一个经典的面试题:String s = new String(“abc”)共创建立多少个对象。
- String s: 一个String类型的变量,位于stack区。
- “abc”: 一个String类型对象,位于PermGen区的常量池(如果池中已有字面值为”abc”的对象则不会重复创建)。
- new String(“abc”): 一个String类型对象,位于heap区。
-
String类中用于存储对象的值的变量使用了final修饰,只能在构造时赋值一次。即,一个String对象所记的value永远不能改变,这就是不可变字符串的代码实现。
-
我对这里有一个疑惑
书中表示“原始字符串放置在堆中”,可以自动回收。但是,根据我查阅的其他资料,原始的字符串”abc”应该位于方法区,方法区在主程序运行期间是不会自动回收的。这里似乎有矛盾。
简单看了下《深入理解Java虚拟机》:Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做“非堆”,目的应该是与Java堆区分开来。也就是说,方法区应该是堆的一部分,但方法区的实现属于虚拟机实现细节,不受虚拟机规范约束。为防止内存溢出以及极少数方法在不同虚拟机下有不同的表现,目前倾向于不将方法区实现为“永久代”,但需要为方法区单独编写内存管理代码。由于对这一块了解较少,这个问题尚待深入了解。
private final char value[];
public String() {
this.value = "".value;
}
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
...
3.6.4 检测字符串是否相等
- 可以使用equals(Object)和equalsIgnoreCase(String)方法检测字符串是否相等(后者忽略大小写。)为避免NullPointerException,当比较常量与变量时,应该以常量调用equals()方法。
- equals(Object)虽然形式参数被声明为Object,但实际上在比较时会先做instanceof String判断,所以实际上只能比较String类型。
String string = "abc";
StringBuffer sBuffer = new StringBuffer("abc");
System.out.println(string.equals(sBuffer)); // false
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
3.6.5 空串与Null串
- 空串”“是长度为0的字符串对象,可以用来调用String类的所有成员方法和属性。
- Null串null表示字符串变量目前没有引用任何字符串对象,不能作任何调用。
3.6.6 代码点与代码单元
- char数据类型是一个采用UTF-16编码表示Unicode代码点的代码单元。大多数常用的Unicode字符使用一个代码单元就可以表示(即一个char对应一个实际字符),而辅助字符需要一对代码单元表示(即两个char对应一个实际字符)。
- 一个实际字符为一个代码点(codePoint)。
- leng()和charA()方法都是基于代码单元,所以在字符串中含有辅助字符的情况下,返回的结果可能不是想要的。这时候,需要用codePointCount()和offsetByCodePonits()+codePointAt()方法代替。
3.6.7 字符串API(java.lang.String)
- char charAt(int index)
返回给定位置的代码单元。不建议调用这个方法获取实际字符。 - int codePointAt(int index)
返回从给定位置开始或结束的代码点。主要这里的index是代码单元的index,而不是代码点的index。建议使用该方法获取实际字符。 - int offsetByCodePoints(int startIndex, int cpCount)
返回从startIndex开始,位移cpCount代码点后的index。 - int compareTo(String other)
- boolean endsWith(String suffix)
- bollean equals(Object other)
- equalsIgnoreCase(String other)
- int indexOf(String subString)
- int indexOf(String subString, int fromIndex)
- int indexOf(int cp)
- int indexOf(int cp, int fromIndex)
- int lastIndexOf(String subString)
- int lastIndexOf(String subString, int fromIndex)
- int lastIndexOf(int cp)
- int lastIndexOf(int cp, int fromIndex)
- int length()
- int codePointCount(int startIndex, int endIndex)
- String replace(CharSequence oldString, CharSequence newString)
- boolean startsWith(String prefix)
- String substring(int beginIndex)
- String substring(int beginIndex, int endIndex)
- String toLowerCase()
- String toUpperCase()
- String trim()
注意:凡是(begin, end)形式的参数,对应的都是半闭合区间,即[begin, end),包含开始而不包含结束。
3.6.9 构建字符串
- 由于String对象是不可变的,当需要用多个较短的字符串拼接成一个长字符串时,一般应使用StringBuilder或StringBuffer以提高效率。
- StringBuilder()/StringBuffer()
- int length()
- StringBuilder append(String str)
- StringBuilder append(char c)
- StringBuilder appendCodePoint(int cp)
- void setCharAt(int index, char c)
- StringBuilder insert(int offset, String str)
在offset位置插入字符串 - StringBuilder insert(int offset, char c)
- StringBuilder delete(int startIndex, int endIndex)
- String toString()
- 注意:以上返回值类型为StringBuilder的方法返回的均是调用者自身。
3.7 输入输出
3.7.1 读取输入
- java.lang.Scanner
- Scanner(InputStream in)
- String nextLine()
- String next()
- int nextInt()
- double nextDouble()
- boolean hasNext()
- boolean hasNextInt()
- boolean hasNextDouble()
- java.lang.System
- static Console console()
- java.io.Console
- char[] readPassword(String prompt, Object… args)
提供一个格式化提示,然后从控制台读取密码,禁用回显。 - String readLine(String prompt, Object… args)
提供一个格式化提示,然后从控制台读取单行文本。
- char[] readPassword(String prompt, Object… args)
3.7.2 格式化输出
- System.out.print(Object obj)
- System.out.printf(String format, Object… args)
3.7.2 文件输入与输出
- java.util.Scanner
- Scanner(File f)
- Scanner(Path p)
- Scanner(String data)
- java.io.PrintWriter
- PrintWriter(String fileName)
- java.nio.file.Paths
- static Path get(String pahtName)
3.8 控制流程
3.8.1 块作用域
- 块(即复合语句)是指由一对花括号括起来的若干条简单的Java语句。块确定了变量的作用域。一个块可以嵌套在另一个块中。
- 不能再嵌套的两个块中声明同名的变量。
3.8.2 条件语句
if(condition){
statement; // 如果condition为true,执行statement;否则什么都不执行。
}
if(condition){
statement1; // 如果condition为true,执行statement1;
}else{
statement2; // 否则,执行statement2。
}
if(condition1){
statement1; // 如果condition1为true,执行statement1;
}else if(condition2){
statement2; // 如果condition2为true,执行statement2;依次类推;
}...
else{
statement(n+1); // 如果所有condition都为false,执行statement(n+1)。
}
3.8.3 循环语句
while(condition){
statement; // 循环判断condition,当为true时执行statement;当为false时停止循环。
}
do{
statement; // 执行statement,然后循环判断condition,当为true时执行statement;当为false时停止循环。
}while(condition)
3.8.4 确定循环
for(initialize counter; condition; update counter){
statment; // 执行initialize counter,然后循环判断condition,当为true时依次执行statement及update counter;当为fasle时停止循环。
}
3.8.5 多重选择:switch语句
switch(key){ // 可以是char,byte,short,int,enum,String(1.7)。
case value1:
statement1; // 当key的值等于value1时,执行statement1。
break; // break或}表示退出switch语句。
case value2:
statement2; // 当key的值等于value2时,执行statement2,依次类推。
break;
...
default:
statement(n+1); // 当key的值不等于任何value,执行statement(n+1)。
break;
}
3.8.6 中断控制流程语句
- break:可用于循环语句或switch语句。跳出循环语句或switch语句,不再执行剩余部分(停止循环)。
A: for(int i = 0; i <= 9; i ++){
B: for(int j = 0; j <= 9; j ++){
C: for(int k = 0; k <= 9; k ++){
if(k == 1){
break; // 当k=1时,跳出所在循环(C)
}
System.out.println("" + i + j + k);
}
}
}
A: for(int i = 0; i <= 9; i ++){
B: for(int j = 0; j <= 9; j ++){
C: for(int k = 0; k <= 9; k ++){
if(k == 1){
break A; // 当k=1时,跳出所在循环A
}
System.out.println("" + i + j + k);
}
}
}
- coutinue:仅用于循环语句,用法语break相同,但只跳过当次循环的剩余部分,跳转到下一次判断(while)或更新计数器(for)部分。
3.9 大数值
- BigInteger可以表示任意精度的整数,BigDecimal可以表示任意精度的浮点数。
- java.math.BigInteger
- BigInteger add(BigInteger other)
- BigInteger subtract(BigInteger other)
- BigInteger multiply(BigInteger other)
- BigInteger divide(BigInteger other)
- BigInteger mod(BigInteger other)
- int compareTo(BigInteger other)
- static BigInteger valueOf(long x)
- java.math.BigDecimal
- BigDecimal add(BigDecimal other)
- BigDecimal subtract(BigDecimalother)
- BigDecimal multiply(BigDecimalother)
- BigDecimal divide(BigDecimal other, RoundingMode mode) // mode表示舍入方式,RoundingMode.HALF_UP表示四舍五入。
- int compareTo(BigDecimal other)
- static BigDecimal valueOf(double x)
3.10 数组
- 数组是一种数据结构,用来存储同一类型值的集合。通过一个整型下标可以访问数组中的每一个值。
- 声明数组:
- int[] a = new int[n];
- int a[] = new int[n];
- 数组元素存储在heap中,因此具有默认初始化值。数值类型为0,char为\u0000,boolean为false,引用类型为null。
- 一旦创建了数组,就不能再改变它的大小。
- Arrays.toString(Object[] arr)将返回一个包含数组所有元素的字符串,格式为”[e0, e1,e2…]”。
- 数组长度可以为0.
3.10.1 for each循环(增强for循环)
- for (type variable : collection){ statement },意为”for each element in collection”。
- collection可以是数组或任意实现了Iterable接口的 类对象。
3.10.2 数组初始化以及匿名数组
// 先声明一个数组,然后初始化每个元素。
int[] arr = new int[5];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
arr[3] = 4;
arr[4] = 5;
// 声明数组的同时初始化。
int[] arr = new int[]{1,2,3,4,5};
// 简写。
int[] arr = {1,2,3,4,5};
// 创建一个匿名数组。
new int[]{1,2,3,4}; // 注意,不能单独创建一个匿名数组而没有其他任何操作(如传值、打印等),编译会报错。
// 用一个匿名数组重新初始化一个数组变量。
arr = new int[]{1,2,3,4};
// 简写。
arr = {1,2,3,4};
// 以下的写法是错误的,不能同时声明数组的长度和具体元素。
int[] arr = new int[5]{1,2,3,4,5};
3.10.3 数组拷贝
- Java中允许将一个引用变量拷贝给另一个引用变量,此时,两个变量引用同一个对象。
int[] a = new int[5];
int[] b = a;
b[0] = 1;
System.out.println("a[0]=" + a[0]); // a[0]=1
- 可以使用Arrays.copyOf(Object[] obj, int newLength)方法将一个数组中的全部或部分元素拷贝到一个新的数组中。
int[] a = new int[]{1,2,3,4,5};
int[] b = Arrays.copyOf(a, 3); // 只拷贝前3个元素,超出新数组长度部分被舍弃。
System.out.println(Arrays.toString(b)); // [1, 2, 3]
int[] c = Arrays.copyOf(a, 7); // 拷贝全部元素,新数组不足部分用默认值补足。
System.out.println(Arrays.toString(c)); [1, 2, 3, 4, 5, 0, 0]
3.10.4 命令行参数
- Java中的main方法接收一个字符串数组,这个数组可以通过命令行参数传入。
// java Demo -1 2 3
public class Demo {
public static void main(String[] args) {
System.out.println(Arrays.toString(args)); // [1,2,3]
}
}
3.10.5 数组排序
- java.util.Arrays
- static String toString(type[] a)
- static type[] copyOf(type[] a, int newLength)
- static type[] copyOfRange(type[] a, int start, int end)
- static void sort(type[] a)
- static int binarySearch(type[] a, type v)
- static int binarySearch(type[] a, int start, int end, type v)
- static void fill(type[] a, type v)
- static boolean equals(type[] a, type[] b)
3.10.6 多维数组
- 多维数组将使用多个下标访问数组元素,它适用于表示表格或更加复杂的排列形式。二维数组也称为矩阵。
- Arrays.deepToString(Object[] arr)将返回包含一维数组和二维数组全部元素的字符串。
3.10.7 不规则数组
- Java实际上没有多维数组,只有一维数组。多维数组被解释为“数组的数组”,即元素是另一个数组的数组。
- 数组的每一行(即每个元素数组)长度不相等的二维数组,称为不规则数组。
int[][] odds = new int[10][];
odds[0] = new int[1];
odds[1] = new int[2];
// ...