Java核心技术·卷一
第三章:Java的基本程序设计结构
3.1一个简单的Java应用程序
类名应该首字母大写,并采用驼峰命名法
public class 名应该与文件名相同,(才注意到原来自己写的class全是public的)
main函数也应是public的
与c++不同Java中的所有函数都必须是某个类的方法,main函数也不例外(所以mian函数会有一个外壳类)
因为main函数为void的,所以都会返回0,若想返回其他代码,需要使用System.exit方法。
public class Application {
public static void main(String[] args) {
System.out.println("Test");
System.exit(-1);
}
}
3.2注释
/** */ 可以自动生成文档
3.3数据类型
3.3.1整型
Java没有任何无符号整型
long a = 8L;
int b = 4;
short c = 2;
byte d = 1;
a = 100_000_000;//编译器会去掉这些下划线。
3.3.2浮点类型
默认为double类型,float类型需要在后面加f
Double.POSITIVE_INFINITY,Double.NEGETIVE_INFINITY,Doubel.NaN,即正无穷大,负无穷大,不是一个数字。他们不能用于比较,要检测是不是,只能使用方法
if(x==Double.NaN) //is never true
if(Double.isNaN(x))//可
3.3.3char类型
\u0000 ~ \uFFFF转义序列可以出现在加引号的字符常量和字符串之外
public static void main(String\u005B\u005D args)
//完全符合语法规则,\u005B\u005D分别为[]的转义序列
一些特殊的转义字符
\b:退格,\t:Tab,\n,\r回车,\",\,\’
**注意:**Unicode转义序列会在解析代码之前得到处理,可以立即为宏的处理方式,而且很可能带来隐式的错误
例如
"\u0022+\u0022" //不会得到"+",而是""+"",即空字符串
//一定要消息注释里面的/u
// \u000A is a newline 会产生一个语法错误,因为提前处理之后,\u000A被替换成了一个换行符
// look inside c:\users 也会产生一个语法错误,因为\u之后并没有跟着一个4位16进制数
UnionCode的码点解决方案:没看懂,反正建议不要用char类型
3.3.5boolean类型
不能与0,1直接比较
3.4变量与常量
不要使用$字符,这是编译器用的
Java中不区分变量的定义与声明
int i = 0 ;//定义
extern int i;//声明
对于局部变量,如果在初始化的时候便可判断其类型,可以使用var关键字
var a = 12;
var b = "wuhu";
用final关键字设置常量,const关键字被保留了,但是没有使用,还是必须使用final
3.5运算符
strictfp:严格浮点运算,采用64位截断的浮点运算。例如intel的80位寄存器会先储存80位,运算后在截断为64位,这种称之为扩展的精度计算,可能会造成移植性问题。
例如标记为: public static strictfp main(String[] args)。但是这个仅作了解即可
StrictMath类在牺牲性能的前提下解决了Math类欠缺的在不同机器上运算结果可能不同的缺陷
Math的**Exact方法解决了溢出却不报错的问题:multiplyExact,addExact…
不要在boolean类型与任何一个其他类型强制转换
&& ||有短路性,|与&没有
”>>>"会使用0来填充高位,而“>>"会使用符号位来填充
3.6字符串
3.6.1字串
substring方法substring(start,end),从start开始,到end结束,但是不包括end。
3.6.2拼接
直接连接,而且会自动转换成string类型(例如int,boolean什么的)
String all = String.join("/","S","M","L","XL");
// resule is "S/M/L/XL"
String repeated = "Java".repeat(3);
// result is "JavaJavaJava"
3.6.3不可变字符串
不能修改字符串中的单个字符,String类对象是不可变的的,类似于常量“4”等等
但是可以修改变量
String gereting = "hello";
greeting = greeting.substring(0,3)+"p";
// result is "help"
不可变带来的是共享的高效率,字符串变量指向公共的储存池里面的字符串常量
得益于垃圾回收机制,对string赋新值并不会产生内存泄漏
3.6.4检测字符串是否相等
equals方法,与常量比较时应该调用常量的equals方法,这样可以减少空指针异常
不能用==来比较两个字符串是否相等,比较的实际上为他们的位置(上面讲的string类似于char *)
String example = "hello";
if("hello" == example)
//probably true
if("hel" == example.substring(0,3))
//probably false
如果虚拟机始终将相同的字符串共享,那么就可以用 == 来比较,但是实际上用+,substring等获得的字符串并不共享。
3.6.5空串与null串
3.6.6码点与代码单元
用char[i]指向的是代码单元,而辅助字符一个码点会包括两个代码单元,这会造成一些错误
(可能这也是哔哩哔哩禁用emoji字符的原因之一吧)
但是基本上码点和代码单元都是相等的
apl文档:Java™ EE 7 Specification APIs (oracle.com)
3.6.9构建字符串
需要由较短的字符串持续的构造字符串(例如从键盘读入),使用+,因为会不停的构造新的对象,效率较低。
可以使用StringBuilder类来解决这个问题
StringBuilder bulider = new StringBuilder();
builder.append(ch);
bulider.append(str);
//finished
String result = bulider.toString();
3.7输入与输出
3.7.1读取输入
Scanner对象
Scanner scanner = new Scanner(System.in);
System.out.println("Input test1");
String test1 = scanner.nextLine();//nextLine方法读取一行,不忽略空格
System.out.println("Input test2");
String test2 = scanner.next();//next遇到空格会停止读入
System.out.println("Input int a");
int a = scanner.nextInt();
注意:当使用的类不是java.lang包里面的,需要import导包(String是util里面的)
可以使用console类来实现密码输入(因为显示的密码输入不安全)
但是绝大多数ide会屏蔽console造成空指针异常;
import java.io.Console;
public class Test {
public static void main(String[] args) {
Console console = System.console();
String username = console.readLine("Username:");
char[] password = console.readPassword("Password:");
System.out.println("Username:"+username+"password:"+password);
}
}
3.7.2格式化输出
可以使用C语言的printf函数。具体的使用这里不做阐述
3.7.3文件输入与输出
//构造scanner对象输入
Scanner in = new Scanner(Path.of("myfile.txt"),StandardCharsets.UTF_8);//似乎不行
Scanner in = new Scanner(new File("myfile.txt"),StandardCharsets.UTF_8);
//输出
PrintWriter out = new PrintWriter("myfile.txt",StandardCharsets.UTF_8);
特别注意,可以构造一个带有字符参数的Scanner对象,但是Scanner会把这个字符参数解释为数据,而不是文件名
Scanner test = new Scanner("myfile");
test.nextLine();//result is "myfile";
文件的位置:文件位于相对于Java虚拟机启动的位置,如果使用ide,启动目录由ide控制
String dir = System.getProperty("myfile");
//可以使用这个获得绝对路径
3.8循环控制流程
Java不能在两个嵌套的块中声明两个同名的变量
public class Test {
public int m = 0 ;
public void test1(int m) {
this.m = m;
//Java中这里是 . 而不是-> ,不要被c++腐蚀了
//注意,这里的两个m并没有在嵌套的块中,所以是被允许的
}
}
public class Test {
public static void main(String[] args) {
int m = 0 ;
if(m>=0) {
int m;
}
//而这里就会报错了,两个m在嵌套的块中
}
}
for循环中定义的变量的作用域为for循环块,出了代码块就没了。
在switch中使用枚举常量时,不必再每个标签中指明枚举名
没有goto语句,但是有带标签的break。
break_tag:
//break标签需要紧跟一个循环语句
for(int i = 0; i<10; i++){
System.out.println("break_tag");
break break_tag;
}
//break之后会跳过该循环,正常执行下面的代码
for(int i = 0; i<2; i++){
System.out.println("other loop");
}
System.out.println("Break Successfully");
//result:
// break_tag
// other loop
// other loop
// Break Successfully
break_tag:
//break标签需要紧跟一个循环语句
for(int i = 0; i<2; i++){
System.out.println("break_tag");
}
//break之后会跳过该循环,正常执行下面的代码
for(int i = 0; i<2; i++){
System.out.println("other loop");
break break_tag;
//而这样会报错:未定义的标签: break_tag
//所以说使用标签只能在它紧挨着的loop下
}
System.out.println("Break Successfully");
也有带标签的continue语句,但是本书的作者不喜欢使用switch语句,break和continue语句。
3.9大数
BigInteger 和 BigDecimal类可以实现任意精度的整数和浮点数运算
不能用+,-等处理大数,而需要使用大数类的add,mutiply方法
可以使用ValueOf方法快速转换为大数。
BigInteger a = BigInteger.valueOf(10000000);
BigInteger b = new BigInteger("100000000000000");//注意这里使用字符串来构造
BigInteger c = a.add(b);
System.out.println(c);
Java中没有运算符重载功能
compareTo方法,比较,返回int,前者大返回正数,相等返回0,后者大返回负数
具体的使用参见api文档
3.10数组
声明和创建是分开的。数组声明时长度可以为变量,但在声明过后长度就不变。
int[] a;
int a[];
//都是可以的,但是推荐使用第一种,它将类型和变量名清晰的分开
//还可以声明一个匿名数组
smallPrimes = new int[] {1,2,4,5};
//same to:
int[] anonymous = {1,2,4,5};
samllPrimes = anonymous;
foreach循环 for(variable : collection) statement
foreach循环最大的好处是简洁和自动对下标的控制。但是灵活性没传统的for循环更高。
int[] a = new int[10];//默认全为0
int[] b = a;//地址传递,b会影响a
b[0]= 10;
System.out.println(a[0]);
int[] c = Arrays.copyOf(a,a.length);//重新开辟内存,c不会影响a
c[1] = 11;
System.out.println(a[1]);
//result:10 0
与c++相比,数组更多的是等同于指针
int[] a = new int[100];//java
int * a = new int[100];//c++
在Java应用程序的main函数中,程序名并没有存储在args数组中
java Message -h world
//args[0]是-h,而不是world
可以用Arrays的sort方法进行排序
Math.random()方法返回【0,1)直接的随机浮点数
程序清单3-7中提供了一种不重复随机抽取的方案:
用n记录被抽取的数组大小,使用 (int)n*Math.random()获得抽取的下标r,然后再用数组中最后一个值去覆盖r对于的值。最后n自减,进行下一次抽取。
Arrays的一些常用方法
Static String toString(xxx[] a)//返回包含a中元素的字符串
static xxx[] copyOf(xxx[] a,int length)
static xxx[] copyOfRange(xxx[] a,int start,int end)//返回与a类型相同的数组,长度为length或者end-start
static void sort[xxx[] a]//用优化过的快排对a进行排序
static int binarySearch(xxx[] a, xxx v)
static int binarySearch(xxx[] a, int start, int end, xxx v)//用二分法对v进行查找,找到返回相应的下标,未找到返回一个负数值
static void fill(xxx[] a, xxx v)//将数组所以的数据元素设置为v
static boolean equals(xxx[] a, xxx[] b)//比较,只有长度和每一个对应的值都相同才返回true
二维数组
foreach循环不能自动处理二维数组的每一个元素,而是循环处理行。
可以调用Arrays的deepToString方法快速打印二位数组。
本质上二维数组是数组的数组,所以可以对数组的行进行操作
int[][] a = {
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,16}
};
int[] temp = new int[4];
temp = a[0];
a[0] = a[1];
a[1] = temp;
System.out.println(Arrays.deepToString(a));
//result is [[5, 6, 7, 8], [1, 2, 3, 4], [9, 10, 11, 12], [13, 14, 15, 16]]
还可以构建一个不规则数组
//Java竟然允许不指明列数
int[][] odds = new int[4][];
for(int n = 0; n < 4; n++){
odds[n] = new int[n+1];
}
for(int i = 0; i < odds.length; i++){
for(int j = 0; j < odds[i].length ; j++){
odds[i][j] = i+j;
}
}
System.out.println(Arrays.deepToString(odds));
//result is [[0], [1, 2], [2, 3, 4], [3, 4, 5, 6]]
//芜湖!Java真是太有趣了