文章目录
基本程序设计结构
数据类型
整型
Java提供了4种整形,
- byte 1字节
- short 2字节
- int 4字节
- long 8字节
在Java中,整型的范围与运行Java代码的机器无关。这就解决了软件从一个平台移植到另一个平台,或者在同一个平台中的不同操作系统之间进行移植给程序员带来的诸多问题。
八进制有一个前缀0,例如,010对应八进制中的8。
十六进制数值有一个前缀0x或0X(如0xCAFE)。
长整型数值有一个后缀L或l(如4000000000L)。
从Java 7开始,加上前缀0b或0B就可以写二进制数。例如,0b1001就是9。另外,同样是从Java 7开始,还可以为数字字面量加下划线,如用1_000_000(或0b1111_0100_0010_0100_0000)表示一百万。这些下划线只是为了让人更易读。Java编译器会去除这些下划线。
注意,Java没有任何无符号(unsigned)形式的int、long、short或byte类型。
浮点类型
在Java中有两种浮点类型
- float 4字节
- double 8字节
float类型的数值有一个后缀F或f(例如,3.14F)。没有后缀F的浮点数值(如3.14)默认为double类型。当然,也可以在浮点数值后面添加后缀D或d(例如,3.14D)。
所有的浮点数值计算都遵循IEEE 754规范。具体来说,下面是用于表示溢出和出错情况的三个特殊的浮点数值:
- 正无穷大
- 负无穷大
- NaN(不是一个数字)
例如,一个正整数除以0的结果为正无穷大。计算0/0或者负数的平方根结果为NaN。
常量Double.POSITIVE_INFINITY、Double.NEGATIVE_INFINITY和Double.NaN(以及相应的Float类型的常量)分别表示这三个特殊的值,但在实际应用中很少遇到。
特别要说明的是,不能这样检测一个特定值是否等于Double.NaN:
if (x == Double.NaN) //执行永远是错的,
所有“非数值”的值都认为是不相同的。然而,可以使用Double.isNaN方法:
if (Double.isNaN(x)) //校验x是不是一个数值
警告:浮点数值不适用于无法接受舍入误差的金融计算中。例如,命令System.out.println(2.0-1.1)将打印出0.8999999999999999,而不是人们想象的0.9。
这种舍入误差的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确地表示分数1/10。这就好像十进制无法精确地表示分数1/3一样。如果在数值计算中不允许有任何舍入误差,就应该使用BigDecimal类。
char类型
char类型原本用于表示单个字符。不过,现在情况已经有所变化。如今,有些Unicode字符可以用一个char值描述,另外一些Unicode字符则需要两个char值。
char类型的值可以表示为十六进制值,其范围从\u0000到\Uffff。例如:\u2122表示注册符号(TM), \u03C0表示希腊字母π。
除了转义序列\u之外,还有一些用于表示特殊字符的转义序列:
转义序列 名称 Unicode值
- \b 退格 \u0008
- \t 制表 \u0009
- \n 换行 \u000a
- \r 回车 \u000d
- \" 双引号 \u0022
- \’ 单引号 \u0027
- \\ 反斜杠 \u005e
Unicode转义序列会在解析代码之前得到处理。例如,"\u0022+\u0022"并不是一个由引号(U+0022)包围加号构成的字符串。实际上,\u0022会在解析之前转换为",这会得到""+"",也就是一个空串。
更隐秘地,一定要当心注释中的\u。
// \u00A0 is a new line.
上面的注释会产生一个语法错误,因为读程序时\u00A0会替换为一个换行符。
// Look inside c:\users
上面也回产生一个语法错误,因为\u后面没有跟4个16进制数。
Unicode和char类型
在Unicode出现之前,已经有许多种不同的标准:美国的ASCII、西欧语言中的ISO 8859-1、俄罗斯的KOI-8、中国的GB 18030和BIG-5等。
这样就产生了下面两个问题:一个是对于任意给定的代码值,在不同的编码方案下有可能对应不同的字母;二是采用大字符集的语言其编码长度有可能不同。例如,有些常用的字符采用单字节编码,而另一些字符则需要两个或更多个字节。
设计Unicode编码的目的就是要解决这些问题。
在Java中,char类型描述了UTF-16编码中的一个代码单元。(UTF-16编码采用不同长度的编码表示所有Unicode码点。在基本的多语言级别中,每个字符用16位表示,通常被称为代码单元(code unit);)
char a = '赵';
boolean类型
整型值和布尔值之间不能进行相互转换。
在C++中,数值甚至指针可以代替boolean值。值0相当于布尔值false,非0值相当于布尔值true。在Java中则不是这样。
if(x = 0) //在C++中可以这么写,java中不行,不能通过编译
变量
变量名必须是一个以字母开头并由字母或数字构成的序列。
需要注意,与大多数程序设计语言相比,Java中“字母”和“数字”的范围更大。字母包括’A’~’Z’、‘a’~’z’、’_’、’$'或在某种语言中表示字母的任何Unicode字符。
例如,德国的用户可以在变量名中使用字母‘ä’;希腊人可以用π。同样,数字包括’0’~’9’和在某种语言中表示数字的任何Unicode字符。
但’+’和’©’这样的符号不能出现在变量名中,空格也不行。
尽管$是一个合法的Java字符,但不要在你自己的代码中使用这个字符。它只用在Java编译器或其他工具生成的名字中。
可移植性是Java语言的设计目标之一。无论在哪个虚拟机上运行,同一运算应该得到同样的结果。对于浮点数的算术运算,实现这样的可移植性是相当困难的。double类型使用64位存储一个数值,而有些处理器使用80位浮点寄存器。这些寄存器增加了中间过程的计算精度。
例如:
double w = x * y / z;
很多Intel处理器计算x * y,并且将结果存储在80位的寄存器中,再除以z并将结果截断为64位。这样可以得到一个更加精确的计算结果,并且还能够避免产生指数溢出。
但是,这个结果可能与始终在64位机器上计算的结果不一样。因此,Java虚拟机的最初规范规定所有的中间计算都必须进行截断。这种行为遭到了数值计算团体的反对。截断计算不仅可能导致溢出,而且由于截断操作需要消耗时间,所以在计算速度上实际上要比精确计算慢。为此,Java程序设计语言承认了最优性能与理想结果之间存在的冲突,并给予了改进。在默认情况下,虚拟机设计者允许对中间计算结果采用扩展的精度。但是,对于使用strictfp关键字标记的方法必须使用严格的浮点计算来生成可再生的结果。
如果将一个类标记为strictfp,这个类中的所有方法都要使用严格的浮点计算。
实际的计算方式将取决于Intel处理器的行为。在默认情况下,中间结果允许使用扩展的指数,但不允许使用扩展的尾数(Intel芯片在截断尾数时并不损失性能)。因此,这两种方式的区别仅仅在于采用默认的方式不会产生溢出,而采用严格的计算有可能产生溢出。
数学函数和常量
在Math类中,包含了各种各样的数学函数。Math中包含的数学函数都是静态方法。
floorMod方法的目的是解决一个长期存在的有关整数余数的问题。考虑表达式n% 2。所有人都知道,如果n是偶数,这个表达式为0;如果n是奇数,表达式则为1。当然,除非n是负数。如果n为负,这个表达式则为-1。为什么呢?设计最早的计算机时,必须有人制定规则,明确整数除法和求余对负数操作数该如何处理。数学家们几百年来都知道这样一个最优(或“欧几里德”)规则:余数总是要≥0。不过,最早制定规则的人并没有翻开数学书好好研究,而是提出了一些看似合理但实际上很不方便的规则。
下面考虑这样一个问题:计算一个时钟时针的位置。这里要做一个时间调整,而且要归一化为一个0~11之间的数。这很简单:(position + adjustment) % 12。不过,如果这个调整为负会怎么样呢?你可能会得到一个负数。所以要引入一个分支,或者使用((position +adjustment) % 12 + 12) % 12。不管怎样,总之都很麻烦。
floorMod方法就让这个问题变得容易了:floorMod(position + adjustment, 12)总会得到一个0~11之间的数。(遗憾的是,对于负除数,floorMod会得到负数结果,不过这种情况在实际中很少出现。)
不必在数学方法名和常量名前添加前缀“Math”,只要在源文件的顶部加上下面这行代码就可以了。
import static java.lang.Math.*;
然后就可以直接使用:
System.out.println(random());
在Math类中,为了达到最快的性能,所有的方法都使用计算机浮点单元中的例程。如果得到一个完全可预测的结果比运行速度更重要的话,那么就应该使用StrictMath类。它使用“自由发布的Math库”(fdlibm)实现算法,以确保在所有平台上得到相同的结果。有关这些算法的源代码请参看www.netlib.org/fdlibm(当fdlibm为一个函数提供了多个定义时,StrictMath类就会遵循IEEE 754版本,它的名字将以“e”开头)。
import static java.lang.StrictMath.*;
数值类型之间的转换
无信息丢失的转换:
- byte -> short -> int -> long
- float -> double
- int -> double
- char -> int
可能存在精度损失的转换:
- int -> float
- long -> double
- long -> float
类型计算时的自动类型转换:
- 如果两个操作数中有一个是double类型,另一个操作数就会转换为double类型。
- 否则,如果其中一个操作数是float类型,另一个操作数将会转换为float类型。
- 否则,如果其中一个操作数是long类型,另一个操作数将会转换为long类型。
- 否则,两个操作数都将被转换为int类型。
位运算符
位运算符包括:
- 按位与 &
- 按位或 |
- 异或 ^
- 取反 ~
- 左移 <<
- 带符号位右移 >>
- 无符号位右移 >>>
这些运算符按位模式处理。例如,如果n是一个整数变量,
int result = (n & 0b1000) / 0b1000;
如果用二进制表示的n从右边数第4位为1,则会返回1,否则返回0。利用&并结合使用适当的2的幂,可以把其他位掩掉,而只保留其中的某一位。
应用在布尔值上时,&和|运算符也会得到一个布尔值。这些运算符与&&和||运算符很类似,不过&和|运算符不采用“短路”方式来求值,也就是说,得到计算结果之前两个操作数都需要计算。
另外,还有>>和<<运算符将位模式左移或右移。需要建立位模式来完成位掩码时,这两个运算符会很方便:
// 1 << 3 和上面的0b1000含义一致
int result = (n & (1 << 3)) >> 3;
最后,>>>运算符会用0填充高位,这与>>不同,它会用符号位填充高位。不存在<<<运算符。
移位运算符的右操作数要完成模32的运算(除非左操作数是long类型,在这种情况下需要对右操作数模64)。例如,1 << 35的值等同于1 << 3或8。
字符串
从概念上讲,Java字符串就是Unicode字符序列。
例如,串“Java\u2122”由5个Unicode字符J、a、v、a和 TM。Java没有内置的字符串类型,而是在标准Java类库中提供了一个预定义类,很自然地叫做String。
如果需要把多个字符串放在一起,用一个定界符分隔,可以使用静态join方法:
String s = String.join(" -> " , "a","c","d","s");
// s的值为:a -> c -> d -> s
String类没有提供用于修改字符串的方法。由于不能修改Java字符串中的字符,所以在Java文档中将String类对象称为不可变字符串。
为了弄清具体的工作方式,可以想象将各种字符串存放在公共的存储池中。字符串变量指向存储池中相应的位置。如果复制一个字符串变量,原始字符串与复制的字符串共享相同的字符。总而言之,Java的设计者认为共享带来的高效率远远胜过于提取、拼接字符串所带来的低效率。
一定不要使用==运算符检测两个字符串是否相等!
如果虚拟机始终将相同的字符串共享,就可以使用==运算符检测是否相等。但实际上只有字符串常量是共享的,而+或substring等操作产生的结果并不是共享的。因此,千万不要使用==运算符测试字符串的相等性,以免在程序中出现糟糕的bug。从表面上看,这种bug很像随机产生的间歇性错误。
在API注释中,有一些CharSequence类型的参数。这是一种接口类型,所有字符串都属于这个接口。只要看到一个CharSequence形参,完全可以传入String类型的实参。
字符串拼接
在JDK5.0中引入StringBuilder类。这个类的前身是StringBuffer,其效率稍有些低,但允许采用多线程的方式执行添加或删除字符的操作。如果所有字符串在一个单线程中编辑(通常都是这样),则应该用StringBuilder替代它。这两个类的API是相同的。
输入输出
package com.demo.test.demo.api;
import java.io.Console;
import java.util.Arrays;
import java.util.Scanner;
/**
* 测试标准输入功能
*
* 因为输入是可见的,所以Scanner类不适用于从控制台读取密码。JavaSE 6特别引入了Console类实现这个目的。
* 采用Console对象处理输入不如采用Scanner方便。每次只能读取一行输入,而没有能够读取一个单词或一个数值的方法。
*
* @author zhao.hualuo
* Create at 2021/8/6
*/
public class InputDemo {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
//在Eclipse或者IDEA中是获取console始终是null
Console console = System.console();
System.out.print("请输入账号:");
String account = input.nextLine();
System.out.print("请输入密码:");
String passport = input.nextLine();
System.out.printf("账号:%s,密码:%s", account, passport);
System.out.println();
String account2 = console.readLine("请输入账号2:");
char[] passport2 = console.readPassword("请输入密码2:");
System.out.printf("账号2:%s,密码2:%s", account2, Arrays.toString(passport2));
}
}
可以使用静态的String.format方法创建一个格式化的字符串,而不打印输出:
String eat = String.format("中午吃什么?%s!晚上吃什么?%s","米饭","水果");
// 中午吃什么?米饭!晚上吃什么?水果
文件输入与输出
要想对文件进行读取,就需要一个用File对象构造一个Scanner对象,如下所示:
Scanner fileInput = new Scanner(new File("D:\\文档\\test.csv"),"utf-8");
while (fileInput.hasNext()) {
//处理每一行
System.out.println(fileInput.nextLine());
}
要想写入文件,就需要构造一个PrintWriter对象。在构造器中,只需要提供文件名:
//如果文件不存在,创建该文件。可以像输出到System.out一样使用print、println以及printf命令。
PrintWriter printWriter = new PrintWriter("D:\\文档\\myFile.txt","utf-8");
printWriter.println("我是第一行的内容");
printWriter.println("adadaadd16514536a1d23a156da123s1a");
printWriter.println("我是第3行的内容");
printWriter.close();
控制流程
switch语句将从与选项值相匹配的case标签处开始执行直到遇到break语句,或者执行到switch语句的结束处为止。如果没有相匹配的case标签,而有default子句,就执行这个子句。
有可能触发多个case分支。如果在case分支语句的末尾没有break语句,那么就会接着执行下一个case分支语句。这种情况相当危险,常常会引发错误。为此,我们在程序中从不使用switch语句。
Java还提供了一种带标签的break语句:
breakPoint:
//标签和循环之间不能有其他代码
while (true) {
while (true) {
while (true) {
break breakPoint;
}
}
}
大数值
如果基本的整数和浮点数精度不能够满足需求,那么可以使用java.math包中的两个很有用的类:BigInteger和BigDecimal。这两个类可以处理包含任意长度数字序列的数值。BigInteger类实现了任意精度的整数运算,BigDecimal实现了任意精度的浮点数运算。
BigInteger big10 = BigInteger.valueOf(10);
BigInteger big15 = BigInteger.valueOf(15);
BigInteger big5 = BigInteger.valueOf(5);
System.out.println("(10 + 15) / 5 = " + (big10.add(big15)).divide(big5));
//(10 + 15) / 5 = 5
数组
在Java中,允许将一个数组变量拷贝给另一个数组变量。这时,两个变量将引用同一个数组
如果希望将一个数组的所有值拷贝到一个新的数组中去,就要使用Arrays类的copyOf方法:
//Arrays.copyOf的第一个参数时旧数组,第二个参数是新数组长度
//如果数组元素是数值型,那么多余的元素将被赋值为0;
//如果数组元素是布尔型,则将赋值为false。
//相反,如果长度小于原始数组的长度,则只拷贝最前面的数据元素。
int[] a = {1,2,4,5,6,8};
int[] copy = Arrays.copyOf(a, 10);
要想快速地打印一个二维数组的数据元素列表,可以调用:
int[][] x = {
{1,2,3,4,5,99},
{2,5,0,6,1,23}
};
System.out.println(Arrays.deepToString(x));
//[[1, 2, 3, 4, 5, 99], [2, 5, 0, 6, 1, 23]]