Core Java Volume Ⅰ
v11
2 Java程序设计环境
2.1 JShell
$ jshell
jshell> "Core".length()
$1 ==> 4
jshell> 5*$1+2
$2 ==> 22
jshell> /exit
| 再见
tab补全 public 访问修饰符(access modifier) 驼峰命名法(camel case) main 方法必须是public,否则,可以通过编译,但是运行报错
错误: 在类 Welcome 中找不到 main 方法, 请将 main 方法定义为:
public static void main(String[] args)
Java中的所有函数都是某个类的方法。 main方法必须是静态的。
3 基本程序设计结构
3.1 注释
有三种注释,
// 单行注释
/*
多行注释
*/
/**
* 文档注释:用来自动的生成文档。
*/
3.2 数据类型
8中基本类型,4种整型、2种浮点型、1种字符型、1种布尔型 Java数据类型所占据的字节数与平台无关。 Java没有无符号类型。 如果非得用正数,可以使用Byte.toUnsignedInt(b)
jshell> a = 0
a ==> 0
jshell> Byte.toUnsignedInt(a)
$6 ==> 0
jshell> a = -1
a ==> -1
jshell> Byte.toUnsignedInt(a)
$8 ==> 255
jshell> a = -2
a ==> -2
jshell> Byte.toUnsignedInt(a)
$10 ==> 254
if(x == Double.NaN) // 不能检测一个特定的值是否等于NaN
if(Double.isNaN(x)) // 可以用isNaN方法来判断
char类型的范围从\u0000到\uFFFF
Unicode**转义序列会在解析代码之前得 到处理**。例如"\u0022+\u0022"并不是一个由引号包围加号构成的字符串,是加上会在解析之前转换为",得到""+"",也就是一个空串
// 当心注释中的\u。下面注释会产生语法错误
// \u000A is a newline
// 语法错误,因为\u后面没有跟着4个十六进制数
// look inside c:\users
char类型描述UTF-16编码中的一个代码单元,但是有些字符的编码为两个代码单元,所以不建议程序中使用char类型,除非确实需要处理UTF-16代码单元,最好将字符串作为抽象数据处理。
3.3 变量与常量
变量
变量名必须是以字母开头并由字母数字构成的序列。Java中的数字和字母的范围更大
// 判断哪些Unicode字符属于Java中的“字母”
// java中的内置方法,该方法确定指定的字符是否允许作为Java标识符中的第一个字符。
public static boolean Character.isJavaIdentifierStart(char ch)
//java中的一个内置方法,用于确定指定的字符是否可以作为第一个字符以外的Java标识符的一部分。
public static boolean Character.isJavaIdentifierPart(int codePoint)
public static boolean isJavaIdentifierPart(char ch)
尽管$是合法字符,不要用在自己的代码中,他只用在java编译器或其他工具生成的名字中。 Java9中 单下划线_ 不能作为变量名,将来可能用作通配符 可以在一行中声明多个变量,但是不提倡,逐一声明可以提高可读性。 许多程序员习惯将变量名命名为类型名
Box box;
Box aBox;
使用未初始化的变量会编译报错。 变量声明尽可能靠近第一次使用的地方,这是一种良好的程序编写风格。 从Java10开始,对于局部变量如果可以从变量的初始值推断出它的类型,就不需要使用声明类型
var days = 12;
var greeting = "Hello!";
常量
使用关键字final,习惯上常量名使用全大写。
final double WIDTH = 8.5;
类常量,方法外面。
public class Student{
public static final int COUNT = 8;
public static void main(String[] args){
}
}
枚举类型
enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE};
Size s = Size.MEDIUM;
// 枚举类型只能是枚举值或者null
3.4 运算符
整数被0除会产生一个异常,浮点数被0除会得到无穷大或NaN结果。 println不是静态方法,Math.sqrt是静态方法。 floorMod方法 计算一个时钟时针的位置。这里要做一个时间调整,而且要归一化为一个0-11之间的数。这很简单:(position + adjustment) % 12”。不过,如果这个调整为负会怎么 样呢?你可能会得到一个负数。所以要引入一个分支,或者使用((position + adjustment) % 12+12)%12。不管怎样都很麻烦。 floorMod方法就让这个问题变得容易了:flooraMod(position + adjustment,12)总会得到一个0-11之间的数。 调整为正则顺时针转,调整为负逆时针旋转,需要注意的是如果除数为负,结果为负 参考文档 提示:不必在每个方法名前都加Math,可以静态引入
import static java.lang.Math.*
在Math类中,为了达到最佳的性能,所有方法都是用附件单元中的例程。如果得到一个完全可预测的结果比运行速度更重要的话,那么就应该使用StrictMath类。它实现了“可自由分发的数学库”的算法,确保在所有平台上得到的相同的结果。 Math提供了一些使函数使整数有更好的运算安全性。如果溢出,数 学运算符只是悄悄的返回错误的结果不做任何提醒,例如Math.multiplyExact(100000000,0),就会产生一个异常,可以进行捕获或者让程序终止。还有一些方法(addExact、subtractExact、incrementExact、decrementExact、negateExact)
数值类型之间的转换
虚线有精度损失。
int、long转float可能有精度损失,long转double有可能精度损失 int转double无信息丢失。
强制类型转换
舍入运算用Math.round(),返回long类型,可以进行int强制类型转换。
结合赋值和运算符
如果运算符得到一个值,其类型与左侧的类型不同,就会发生强制类型转换。例如x是int类型,则一下语句
x += 3.5
// 只去整数部分
是合法的,将把x设置为(int)(x+3.5) 建议不要在表达式中使用++,因为这样的代码很容易让人困惑,而且会带来烦人的bug
关系和boolean运算符
&&和||短路效应 expression1&&expression2 若第一个为false,后面的就不用计算了 expression1||expression2 若第一个为true,后面的就不用计算了 三目运算符
位运算
求某一位或者将其他位掩掉只留下某一位。 位运算没有短路效应
>>>会用0填充高位
>>会用符号位填充高位
不存在<<<运算符
警告:对于int类型1<<35等同于1<<3
运算符优先级
从右向左:医院运算和强制类型转换、三目运算符、赋值运算符
3.5 字符串
Java字符串就是Unicode字符序列。
子串
String.substring(0,3); // 前三个字符即0 1 2
拼接
非字符串的值与字符串+,会转换成字符串。 String.join("Hello,", "world!");
Java 11中提供了repeat方法 String repeated = "Java".repeat(3);
不可变字符串
Java文档中将String类对象成为不可变的(immutable) greeting = greeting.substring(0,3) + "p!"; 拼接字符串的效率确实不高。但是有个优点是可以让字符串共享。 Java设计者认为共享带来的高效率远远胜过提取子串、拼接子串所带来的低效率。 (例外情况,对于单字符的操作,Java单独提供了类)
检测字符串是否相等
==是检测位置 "a".equals("a")是判断字符是否相等。
String greeting = "Hello";
if(greeting == "Hello") // 可能是true
if(greeting.subtring(0,3) == "Hel") // 可能是false
空串和null串
// 要检查一个字符串是否为null,要使用以下条件
if(str == null)
// 检查一个字符串既不是null,又不是空串
if(str != null && str.length != 0)
码点与代码单元
char数据类型是一个采用UTF-16编码标识Unicode码点的代码单元。常用字符使用个代码单元就可以,辅助字符需要一对代码单元表示。
// length方法返回代码单元的数量
String greeting = "Hello";
int n = greeting.length();
// 获取码点数量
int cpCount = greating.codePoint(0, greeting.length());
// 返回位置n的代码单元,n介于0~length()-1之间。
char c = greeting.charAt(n)
// 得到第i个码点
int index = greeting.offsetByCodePoints(0, i)
int cp = greeting.codePointAt(index);
emoji表情符号可能会在U+FFFF以上 codePoints方法可以生成一个int值的“流”,每个int值对应一个码点。
// 字符串转码点
int[] codePoints = str.codePoints().toArray();
// 码点转字符串
String str = new String(codePoints, 0, codePoints.length);
虚拟机不一定把字符串实现为代码单元序列。在java9中只包含单字节代码单元的字符串使用byte数组实现,所有其他字符串使用char数组。
StringAPI
// 按字典顺序比较
int compareTo(String other)
// 空或者由空格组成
boolean blank()
//
int indexOf(String str)
int lastIndexOf(String str)
String toLowerCase()
String toUpperCase()
String trim()
...
构建字符串
有些时候需要由较短的字符串构建字符串,例如,按键或来自文件中的单词。如果采用字符串拼接的方式效率会比较低。每次拼接字符串时,都会构建一个String对象,既耗时又浪费空间。使用StringBuilder可以避免这个问题的发生。
StringBuilder builder = new StringBuilder();
builder.append(ch);
builder.append(str);
String s = builder.toString();
StringBuilder在Java5中引入,他的前身是StringBuffer,效率较低,但允许采用多线程的方式添加或删除字符。如果所有字符串编辑操作大都在单个线程中执行(通常都这样),则应该使用StringBuilder。这两个类的API是一样的。
3.6 输入与输出
读取输入
Scanner in = new Scanner(System.in);
String name = in.nextLine();
nextLine是因为输入行中可能包含空格。想要读取一个单词,可以调用in.next()
nextInt()
nextDouble()
import java.util.*;
Scanner类在util包中。
不在java.lang包中的类都要引入。
Console cons = System.console();
String username = cons.readLine("User name: ");
char[] passwd = cons.readPassword("Password: ");
Console没有Scanner方便,每次读取一行输入,没有能够读取单个单词或数值的方法。
格式化输出
System.out.printf("%8.2f", x); // 3333.33
System.out.printf("%,.2f", x); // 3,333.33
System.out.printf("%,(.2f", -3333.333); //(3,333.33)
格式 | 含义 |
---|---|
+ | 打印正负号 |
0 | 前面补0 |
%1$d %1$x | 以十进制和十六进制格式打印第一个参数 |
%d%<x | 以十进制和十六进制格式打印同一个数值 |
// 创建一个格式化的字符串而不打印输出
String message = String.format("name: %s, age:%d", name, age);
时间相关的方法
老的代码使用java.util.Date
新代码使用java.time包
System.out.printf("%tc", new Date());
// 周六 1月 13 18:10:47 CST 2024
System.out.printf("%1$s %2$tB %2$te, %2$tY", "Due date:", new Date());
// Due date: 一月 13, 2024
System.out.printf("%s %tB %<te, %<tY", "Due date:", new Date());
文件输入与输出
// 如果文件名中有反斜杠,则需要转义:c:\\mydirectory\\myfile.txt
Scanner in = new Scanner(Path.of("myfile.txt", StandardCharsets.UTF_8));
// 如果文件不存在,创建该文件
PrintWriter out = new PrintWriter("myfile.txt", StandardCharsets.UTF_8);
// 获取创建目录位置
String dir = System.getProperty("user.dir");
// 用一个不存在的文件构造一个Scanner或者用一个无法创建的文件名构造一个PrintWriter会产生异常
public static void main(String[] args) throws IOException {
Scanner in = new Scanner(Path.of("myfile.txt"), StandardCharsets.UTF_8);
}
java MyProg < myfile.txt > output.txt
# 关联到System.in 和System.out 就不必担心处理IOException异常了。
3.7 控制流程
块作用域
不能在嵌套的两个块中声明同名的变量。
条件语句
if (confition) statement1 else statement2
循环
while (condition) statement do statement while(condition);