原创

Java核心技术--第三章--Java的基本程序设计结构--上部


一个简单的Java应用程序

下面看一个最简单的Java应用程序,它只发送一条消息到控制台窗口中:

public class FirstSample
{
 	public static void main(String[] args)
	{
		System.out.println("We will not use 'Hello,World!'");
	}
}

这个程序虽然简单,但所有的Java应用程序都具有这种结构,因此还是值得花一些时间来研究的。首先,Java区分大小写。如果出现了大小写拼写错误(例如,将main拼写成Main),程序将无法运行。

下面逐行地查看这段代码。关键字 public 称为访问修饰符(access modifier),这些修饰符用于控制程序的其他部分对这段代码的访问级别。关键字 class 表明Java程序中的全部内容都包含在一个类中。现在只需要将类作为程序逻辑的一个容器,程序逻辑定义了应用程序的行为。类是构建所有Java应用程序和applet的构建块。Java应用程序中的全部内容都必须放置在类中。

关键字class后面紧跟类名。Java中定义类名的规则很宽松。名字必须以字母开头,后面可以跟字母和数字的任意组合。长度基本上没有限制。但是不能使用 Java 保留字(例如,public 或 class)作为类名(保留字列表请参看附录。)

标准的命名规范为(类名 FirstSample 就遵循了这个规范):类名是以大写字母开头的名词。如果名字由多个单词组成,每个单词的第一个字母都应该大写(这种在一个单词中间使用大写字母的方式称为驼峰命名法(camel case)。以其自身为例,应该写成 CamelCase)。

需要注意源代码中的大括号 { } 。在Java中,像在C/C++中一样,用大括号划分程序的各个部分(通常称为块)。Java中任何方法的代码都用 “{” 开始,用 “}” 结束。大括号的使用风格曾经引发过许多无意义的争论。我们的习惯是把匹配的大括号上下对齐。不过,由于空白符会被Java编译器忽略,所以你可以选用自己喜欢的大括号风格。

我们暂且不去理会关键字 static void,而仅把它们当做编译Java应用程序必要的部分就行了。

现在需要记住:每个Java应用程序都必须有一个main方法。

接下来研究一下代码:

{
	System.out.println("We will not use 'Hello,World!'");
}

一对大括号表示方法体的开始与结束,在这个方法中只包含一条语句。与大多数程序设计语言一样,可以将Java语句看成是语言中的句子。在Java中,每个句子必须用分号结束。特别需要说明,回车不是语句的结束标志,因此,如果需要可以将一条语句写在多行上。

在这里,我们使用System.out对象并调用了它的println方法。注意,点号(.)用于调用方法。Java使用的通用语法是

object.method(parameters)

这等价于函数调用。

在这个示例中,调用了 println 方法并传递给它一个字符串参数。这方法将传递给它的字符串参数显示在控制台上。然后,终止这个输出行,使得每次调用println都会在新的一行上显示输出。需要注意一点,Java与C/C++一样,都采用双引号界定字符串。

与其他程序设计语言中的函数一样,Java中的方法可以没有参数,也可以有一个或者多个参数(有的程序员把参数叫作实参)。即使一个方法没有参数,也需要使用空括号。例如,不带参数的println方法只打印一个空行。使用下面的语句调用:

System.out.println();

附录:

image-20200305104328136

注释

与大多数程序设计语言一样,Java中的注释也不会出现在可执行程序中。因此,可以在源程序中根据需要添加任意多的注释,而不必担心可执行代码会膨胀。在Java中,有3种标记注释方式。最常用的方式是使用//,其注释内容从//开始到本行结尾。

System.out.println("We not use 'Hello,World!'");//is this too cute?

当需要更长的注释时,既可以在每行的注释前面标记//,也可以使用 /* 和 */ 注释界定符将一段比较长的注释括起来。

最后,第三种注释可以用来自动的生成文档。这种注释以 /** 开始,以 */ 结束。

警告:在Java中,/* */ 注释不能嵌套。也就是说,不能简单地把代码用 /* 和 */ 括起来作为注释,因为这段代码本身可能也包含一个 */界定符。

数据类型

Java是一种强类型语言。这就意味着必须为每个变量声明一种类型。在Java中,一共有8种基本类型(primitive type),其中有4种整型、2种浮点类型、一种字符类型char(用于表示Unicode编码的代码单元)和一种用于表示真值的boolean类型。

注释:Java有一个能够表示任意精度的算数包,通常称为“大数”(big number)。虽然被称为大数,但它并不是一种基本Java类型,而是一个Java对象。

整型

整型用于表示没有小数部分的数值,允许是负数。Java提供了4种整型。

Java 整型
类型储存需求取值范围
int4字节-2 147 483 648 ~ 2 147 483 647 (刚刚超过20亿)
short2字节-32 768 ~ 32 767
long8字节-9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807
byte1字节-128 ~ 127

在通常情况下,int类型最常用。但如果想要表示整个地球的居住人口,就需要使用long类型了。byte和short类型主要用于特定的应用场合,例如,底层的文件处理或者存储空间很宝贵的大数组。

int为整型的默认类型.
在声明long类型变量时,需要数字末尾加上L/l. example: long l1 = 100L;

在Java中,整型的范围与运行Java代码的机器无关。由于Java程序必须保证在所有机器上都能够得到相同的运行结果,所以各种数据类型的取值范围必须固定。

长整型数值有一个后缀L或l(如4000000000L)。十六进制数值有一个前缀 0x 或 0X (如0xCAFE)。八进制有一个前缀0,例如,010对应十进制中的8。很显然,八进制表示法比较容易混淆,所以建议最好不要使用八进制常数。

从Java 7 开始,加上前缀0b或0B就可以写二进制数。例如,0b1001就是9。另外,同样是从Java 7 开始还可以为数字字面量加下划线,如用1_000_000(或0b1111_0100_0010_0100_0000)表示100万。这些下划线只是为了让人更容易读。Java编译器会去除这些下划线。

浮点类型

浮点类型用于表示有小数部分的数值。在Java中有两种浮点类型。

Java 浮点类型
类型存储需求取值范围
float4字节大约 ± 3.402 823 47E+38F(有效位数为 6 ~ 7位)
double8字节大约 ± 1.797 693 134 862 315 70E+308(有效位数为15位)

double 表示这种类型的数值精度是float类型的两倍(有人称之为双精度数值)。在很多情况下,float类型的精度(6 ~ 7 位有效数字)并不能满足需求。实际上,只有很少的情况适合使用float类型,例如,需要单精度数的库,或者需要存储大量数据时。

float 类型的数值有一个后缀F或f(例如,3.14F)。没有后缀F的浮点数值(如3.14)总是默认为double类型。当然,也可以在浮点数值后面添加后缀D或者d(例如,3.14D)。

注释:可以使用十六进制表示浮点数值。例如,0.125 = 2³可以表示成0x1.0p-3.在十六进制表示法中,使用p表示指数,而不是e。(e是一个十六进制数位。)注意,尾数采用十六进制,指数采用十进制。指数的基数是2,而不是10。

所有的浮点数值计算都遵循IEEE 754规范。具体来说,下面是用于表示溢出和出错情况的三个特殊的浮点数值:

  • 正无穷大

  • 负无穷大

  • NaN(不是一个数字)

    例如,一个正整数除以0的结果为正无穷大。计算0/0或者负数的平方根结果为NaN。

    注释:常量Double.POSITIVE_INFITY、Double.NEGATIVE_INFINITY和Double.NaN(以及相应的Float类型的常量)分别表示这三个特殊的值,但在实际应用中很少遇到。特别要说明的是,不能如下检测一个特定值是否等于Double.NaN:

    if(x == Double.NaN) // is never true

    所有“非数值”的值都认为是不相同的。不过,可以如下使用Double.isNaN方法来判断:

    if(Double.isNaN(x)) // check whther x is "not a number"

    ⚠️警告:浮点数值不适合用于无法接受舍入误差的金融计算。例如,命令System.out.println(2.0-1.1)将打印出0.89999999,而不是人们期望的0.9。这种舍入误差的主要原因是浮点数值采用二进制系统表示,而在二进制系统中无法精确的表示分数1/10.这就好像十进制无法精确的表示分数1/3一样。如果在数值计算中不允许有任何舍入误差,就应该使用BigDecimal类。

    char类型

    char类型原本用于表示单个字符。不过,现在有些Unicode字符可以用一个char值描述,另外一些则需要两个char值。

    char类型的字面量值要用单引号括起来。例如:'A'是编码65的字符常量。它与"A"不同,"A"是包含一个字符A的字符串。char类型的值可以表示为十六进制值,其范围从\u0000到\uFFFF。例如,\u2122表示商业符号(™),\u03c0 表示希腊字母π。

    除了转义序列\u之外,还有一些用于表示特殊字符的转义序列,所有这些转义序列都可以出现在加引号的字符常量或字符串中。例如,'\u2122'或"Hello\n"。转义序列\u还可以出现在加引号的字符常量或字符串之外(而其他所有转义序列不可以)。例如:

    public static void mian(String\u005B\u005D args)

    就完全符合语法规则,\u005B 和 \u005D 分别是 [ 和 ] 的编码。

特殊字符的转义序列
转义序列名称Unicode值
\b退格\u0008
\t制表\u0009
\n换行\u000a
\r回车\u000d
\"双引号\u0022
\'单引号\u0027
\\反斜杠\u005c

⚠️警告:一定要当心注释中的\u。例如,注释:

//\u000A is a newline

会产生一个语法错误,因为读程序时\u00A0会替换为一个换行符。类似地,下面这个注释:

look inside c:\users

也会产生一个语法错误,因为\u后面并没有跟着4个十六进制数。

Unicode 和 char 类型

想要弄清楚char类型,就必须了解Unicode编码机制。Unicode打破了传统字符编码机制的限制。在Unicode出现之前,已经有许多不同的标准:美国的ASCII、西欧语言中的ISO 8859-1、俄罗斯的KOI-8、中国的GB 18030和BIG-5等。这样就产生了下面两个问题:

一个是对于任意给定的代码值,在不同的编码方案下有可能对应不同的字母;二是采用大字符集的语言其编码长度有可能不同。例如,有些常用的字符集采用单字节编码,而另一些字符则需要两个或多个字节。

设计Unicode编码的目的就是要解决这些问题。

在Java中,char类型描述了UTF-16编码中的一个代码单元。

我们强烈建议不要在程序中使用char类型,除非确实需要处理UTF-16代码单元。最好将字符串作为抽象数据类型处理。

boolean类型

boolean(布尔)类型有两个值:false和true,用来判定逻辑条件。整型值和布尔值之间不能进行相互转换。

变量与常量

与所有程序设计语言一样,Java也可以用变量来存储值。常量就是值不变的变量。

声明变量

在Java中,每个变量都有一个类型(type)。在声明变量时,先指定变量的类型,然后是变量名。例如:

double salary;
int vacationDays;
long earthPopulation;
boolean done;

可以看到,每个声明都以分号结束。由于声明是一条完整的Java语句,而所有Java语句都以分号结束,所以这里的分号是必须的。

变量名必须是一个以字母开头并由字母或数字构成的序列。字母包括'a-z'、'A-Z'、'-'、'$',数字包括'0-9'和在某种语言中表示数字的任何Unicode字符。但'+'和'©'这样的符号不能出现在变量名中,空格也不行。变量名中所以的字符都是有意义的、统一使用lowerCamelCase风格,必须遵从驼峰形式,并且大小写敏感。变量名的长度基本上没有限制。

提示:如果想要知道哪些Unicode字符数与Java中的“字母”,可以使用Character类的isJavaIdentiferStart和isJavaIdenifierPart方法来检查。

提示:尽管$是一个合法的Java的字符,但不要在你自己的代码中使用这个字符。他只用在Java编译器或其他工具生成的名字中。

变量初始化

声明一个变量之后,必须用赋值语句对变量进行显示初始化,千万不要使用未初始化的变量的值。例如:

int vacationDays;
System.out.println(vacationDays); // ERROR--variable not initialized

要想对一个已经声明过的变量进行赋值,就需要将变量名放在等号(=)左侧,再把一个合适当取值的Java表达式放在等号的右侧。

int vacationDays = 12;

在Java中,变量声明尽可能地靠近变量第一次使用的地方,这是一种良好的程序编写风格。

常量

在Java中,利用关键字final指示常量。

关键字final表示这个变量只能被赋值一次。一旦被赋值之后,就不能再更改了。习惯上,常量名使用全大写。

在Java中,经常希望某个常量可以在一个类的多个方法中使用,通常将这些常量称之为类常量(class constant)。可以使用关键字static final设置一个类常量。

需要注意,类常量的定义位于main方法或者自定义方法的外部。因此,在同一个类的其他方法中也可以使用这个常量。而且,如果一个常量被声明为public,那么其他类的方法也可以使用这个常量。使用结构:类名.常量名。

枚举类型

有时候,变量的取值只在一个有限的集合内。例如,销售的服装或比萨只有小、中、大和超大这四种尺寸。当然,可以将这些尺寸分别编码为1、2、3、4或S、M、L、X。但这种设置很容易出错。很可能在变量中保存的是一个错误的值(如0或m)。

针对这种情况,可以自定义枚举类型。枚举类型包括有限个命名的值。例如,

enum Size { SMLL, MEDIUM, LARGE, EXTRA_LARGE };

现在,可以声明这种类型的变量:

Size s = Size.MEDIUM;

Size类型的变量只能储存这个类型声明中给定的某个枚举值,或者特殊值null,null表示这个变量没有设置任何值。

运算符

运算符用于连接值。

算术运算符

在Java中,使用算术运算符+、-、*、/表示加、减、乘、除运算。整数的求余操作(有时称为取模)用%表示。例如,15/2等于7,15%2等于1,15.0/2等于7.5。

需要注意,整数被0除将会产生一个异常,而浮点数被0除将会得到无穷大或NaN结果。

数值类型之间的转换

我们经常需要将一种数值类型转换为另一种数值类型。

image-20200303171748881

在图上有6个实箭头,表示无信息丢失的转换;另外三个虚线箭头,表示可能有精度损失转换。

当用一个二元运算符连接两个值时(例如 n + f, n是整数,f是浮点数),先要将两个操作数转换为同一种类型,然后再进行计算。

  • 如果两个操作数中有一个是double类型,另一个操作数就会转换为double类型。

  • 否则,如果其中一个操作数是float类型,另一个操作数将会转换为float。

  • 否则,如果其中一个操作数是long类型,另一个操作数将会转换为long类型。

  • 否则,两个操作数都将被转换为int类型。

    	char <-> int 字符型可与整型互相转换
    	   boolean不参与自动类型转换
    	   不能把对象类型转为不相关类的对象
    	   转换过程中可能出现精度损失,浮点数到整数通过舍弃小数得到,而不是四舍五入
    

    强制类型转换

    在必要的时候,int类型的值将会自动地转换为double类型。但另一方面,有时也需要将double转换成int。在Java中,允许进行这种数值之间的类型转换,当然,有可能会丢失一些信息。这种可能损失信息的转换要通过强制类型转换(cast)来完成。强制类型转换的语法格式是在圆括号中给出想要转换的目标类型,后面紧跟待转换的变量名。例如:

    	double x = 9.997;
    	int nx = (int) x;
    

    这样,变量nx的值是9,因为强制类型转换通过截断小数部分将浮点值转换为整型。

如果想对浮点数进行舍入运算,以便得到最接近的整数,那就需要使用Math.round方法:

double x = 9.997;
int nx = (int) Math.round(x);

现在,变量nx的值为10。当调用round的时候,仍然需要使用强制类型转换(int)。其原因是因为round方法返回的结果为long类型,由于存在信息丢失的可能性,所以只有显式(强制类型转换,也称显式类型转换,是指必须书写代码才能完成的类型转换。自动类型转换,也称隐式类型转换,是指不需要书写代码,由系统自动完成的类型转换。)的强制类型转换才能够将long类型转换成int类型。

⚠️警告:如果试图将一个数值从一种类型强制转换为另一种类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。例如,(byte)300的实际值为44。

结合赋值和运算符

可以在赋值中使用二元运算符,这是一种很方便的简写形式。例如,x += 4;等价于:x = x + 4;(一般来说,要把运算符放在=号左边,如 *= 或 %= )。

注释:如果运算符得到一个值,其类型与左侧操作数的类型不同,就会发生强制类型转换。例如,如果x是一个int,则以下语句 x += 3.5; 是合法的,将把x设置为(int) (x + 3.5)。

自增与自减运算符

n++将变量n的当前值加1,n--则将n的值减1。由于这些运算符改变的是变量的值,所以他们不能应用于数值本身。

实际上,这些运算符有两种形式;上面介绍的是运算符放在操作数后面的“后缀”形式。还有一种“前缀”形式:++n。后缀和前缀形式都会使变量值加1或者减1。但用在表达式中时,二者就会有区别了。前缀会先完成加1;而后缀形式会使用变量原来的值。

int m = 7;
int n = 7;
int a = 2 * ++m; // now a is 16,m is 8
int b = 2 * n++; //now b is 14,n is 7

建议不要在表达式中使用++,因为这样的代码很容易让人困惑,而且会带来烦人的bug。

关系和 boolean 运算符

Java包含丰富的关系运算符。要检测相等性,可以使用两个等号 ==。例如,

3 == 7

的值为false。

另外还可以使用 != 检测不相等。例如,

3 != 7

的值为true。

  • 关系运算符
>(大于)、<(小于)
>=(大于等于)、<=(小于等于)
==(等于)、!=(不等于)
instanceof(检查是否为类的对象)
关系运算符的最终结果为布尔型,要么为true,要么为false.

Java沿用了C++的做法使用&&表示逻辑“与”运算符,使用||表示逻辑“或”运算符。从!=运算符可以想到,感叹号!就是逻辑非运算符。&&和||运算符是按照“短路”方式来求值的:如果第一个操作数已经能够确定表达式的值,第二个操作数就不必计算了。第一个表达式的值为false,那么结果就不可能为true。

  • 三目运算符

Java支持三目运算符?:,这个操作符有时很有用。如果条件为true,下面表达式:

condition ? expression1 :expression2 

就为第一个表达式的值,否则计算为第二个表达式的值。例如,

x < y ? x : y

会返回x和y中较小的一个。

位运算符

处理整型类型时,可以直接对组成整数的各个位完成操作。这意味着可以使用掩码技术得到整数中的各个位。位运算包括:

& ("and") | ("or") ^ ("xor") ~ ("not")

这些运算符按位模式处理。例如,如果n是一个整数变量,而且用二进制表示的n从右边数第4位为1,则

int fourthBitFromRight = (n & 0b1000) / 0b1000;

会返回1,否则返回0。利用&并结合使用适当的2的幂,可以把其他位掩掉,而只留下其中的某一位。

注释:应用在布尔值上时,&和|也会得到一个布尔值。不过不采用“短路”方式来求值,得到计算结果之前两个操作数都需要计算。

另外,还有 >> 和 << 运算符可以将位模式左移或右移。需要建立位模式来完成位掩码时,这两个运算符会很方便:

int fourthBitFromRight = (n & (1 << 3)) >>3;

最后,>>>运算符会用0填充最高位,这与>>不同,它会用符号位填充高位。不存在<<<运算符。

⚠️警告:移位运算符的右操作数要完成模32的运算(除非左操作数是long类型,在这种情况下需要对右操作数模64)。例如,1 << 35 的值等同于 1 << 3 或 8。

括号与运算符级别

同一级别的运算符按照从左到右的次序依次计算(除了表中给出的右结合运算符外)。例如:由于&&的优先级比||的优先级高,所以表达式

a && b || c ;

等价于

(a && b)|| c ;

又因为 += 是右结合运算符,所以表达式

a += b += c;

等价于

a += (b += c) ;

也就是将 b += c的结果(加上c之后的b)加到 a 上。

image-20200305114842837

字符串

从概念上讲,Java字符串就是Unicode字符序列。例如,字符串"Java\u2122"由5个Unicode字符J、a、v、a和™组成。Java没有内置的字符串类型,而是在标准Java类库中提供了一个定义预定义类,很自然地叫做String。每个用双引号括起来的字符串都是String类的一个实例:

String e = ""; // an mepty String
String greeting = "Hello";

子串

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

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

创建一个由字符"Hel"组成的字符串。

substring的工作方式有一个优点:容易计算子串的长度。字符串s.substring(a,b)的长度为 b - a 。例如,子串"Hel"的长度为 3 - 0 = 3。

拼接

与绝大多数程序设计语言一样,Java语言允许使用+号连接(拼接)两个字符串。

String expletive = "Expletive";
String PG13 = "deleted";
String message = expletive + PG13;

上述代码将"Expletivedeleted"赋值给变量message(注意,单词之间没有空格,+号完全按照给定的次序将两个字符串拼接起来)。

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

int age = 13;
String rating = "PG" + age;

rating的值为"PG13"。

如果需要把多个字符串放在一起,用一个界定符分割,可以使用静态join方法:

 String all = String.join(" / ", "S","M","L","XL");
// all is the String " S / M / L / XL"

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

String repeated = "Java".repeat(3);
// repeated is "JavaJavaJava"
内功心法
四海八荒
从入门到放手
书中颜如玉
  • 作者:管理员 (联系作者)
  • 发表时间:2020-03-05 12:51
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 公众号转载:请在文末添加作者公众号二维码
  • 评论