文章目录
- Java标识符和关键字
- Java注释:单行、多行和文档注释
- Javadoc(文档注释)详解
- Java常量:Java常量的定义和分类
- Java变量的声明和初始化
- Java变量的作用域:静态变量、全局变量和局部变量
- Java数据类型:基本数据类型和引用数据类型
- Java数据类型转换:强制类型转换+自动类型转换
- Java算术运算符
- Java赋值运算符(=)
- Java逻辑运算符(&&、||和!)
- Java关系运算符
- Java自增和自减运算符(++和--)
- Java位运算符:Java移位运算符、复合位赋值运算符及位逻辑运算符
- Java三目运算符(条件运算符? :)
- Java运算符优先级
- Java直接量(字面量)
http://c.biancheng.net/java/80/
要想编写规范、可读性高的 Java 程序,就必须对 Java 基本语法有所了解。基本语法是所有编程语言都必须掌握的基础知识,也是整个程序代码不可缺少的重要部分。
一个 Java 程序通常由数据类型、变量、运算符和控制流程语句4部分组成。其中数据类型和运算符不仅定义了语言的规范,还决定了可以执行什么样的操作,变量是用来存储指定类型的数据,其值在程序运行期间是可变的;与变量对应的是常量,其值是固定的。
本章将详细介绍 Java 程序中的基本数据类型、变量、常量、运算符等相关知识。对初学者来说,应该对本章的每个小节进行仔细阅读、思考,这样才能达到事半功倍的效果。
Java标识符和关键字
任何一种计算机语言都离不开标识符和关键字,因此下面将详细介绍 Java 的标识符、关键字和保留字。
标识符
Java 中标识符是为方法、变量或其他用户定义项所定义的名称。标识符可以有一个或多个字符。在 Java 语言中,标识符的构成规则如下。
- 标识符由数字(09)和字母(AZ 和 a~z)、美元符号($)、下划线(_)以及 Unicode 字符集中符号大于 0xC0 的所有符号组合构成(各符号之间没有空格)。
- 标识符的第一个符号为字母、下划线和美元符号,后面可以是任何字母、数字、美元符号或下划线。
另外,Java 区分大小写,因此 myvar 和 MyVar 是两个不同的标识符。
提示:标识符命名时,切记不能以数字开头,也不能使用任何 Java 关键字作为标识符,而且不能赋予标识符任何标准的方法名。
标识符分为两类,分别为关键字和用户自定义标识符。
- 关键字是有特殊含义的标识符,如 true、false 表示逻辑的真假。
- 用户自定义标识符是由用户按标识符构成规则生成的非保留字的标识符,如 abc 就是一个标识符。
提示:使用标识符时一定要注意,或者使用关键字,或者使用自定义的非关键字标识符。此外,标识符可以包含关键字,但不能与关键字重名。
例如以下合法与不合法标识符。
- 合法标识符:date、KaTeX parse error: Expected group after '_' at position 13: 2011、_date、D_̲date 等。
- 不合法的标识符:123.com、2com、for、if 等。
标识符用来命名常量、变量、类和类的对象等。因此,一个良好的编程习惯要求命名标识符时,应赋予它一个有意义或有用途的名字。
关键字
关键字(或者保留字)是对编译器有特殊意义的固定单词,不能在程序中做其他目的使用。关键字具有专门的意义和用途,和自定义的标识符不同,不能当作一般的标识符来使用。例如,在《使用记事本编写运行Java程序》一节的实例 HelloJava.java 中的 class 就是一个关键字,它用来声明一个类,其类名称为 HelloJava。public 也是关键字,它用来表示公共类。另外,static 和 void 也是关键字,它们的使用将在本教程后面的章节中详细介绍。
Java 的关键字对 Java 编译器有特殊的意义,它们用来表示一种数据类型,或者表示程序的结构等。保留字是为 Java 预留的关键字,它们虽然现在没有作为关键字,但在以后的升级版本中有可能作为关键字。
Java 语言目前定义了 51 个关键字,这些关键字不能作为变量名、类名和方法名来使用。以下对这些关键字进行了分类。
- 数据类型:boolean、int、long、short、byte、float、double、char、class、interface。
- 流程控制:if、else、do、while、for、switch、case、default、break、continue、return、try、catch、finally。
- 修饰符:public、protected、private、final、void、static、strict、abstract、transient、synchronized、volatile、native。
- 动作:package、import、throw、throws、extends、implements、this、supper、instanceof、new。
- 保留字:true、false、null、goto、const。
提示:由于 Java 区分大小写,因此 public 是关键字,而 Public 则不是关键字。但是为了程序的清晰及可读性,要尽量避免使用关键字的其他形式来命名。
Java注释:单行、多行和文档注释
注释是对程序语言的说明,有助于开发者和用户之间的交流,方便理解程序。注释不是编程语句,因此被编译器忽略。
Java 支持以下三种注释方式:
1)单行注释
以双斜杠“//”标识,只能注释一行内容,用在注释信息内容少的地方。打开 Eclipse,在 Java 代码中使用单行注释,如图 1 所示。
图 1 单行注释
2)多行注释
包含在“/”和“/”之间,能注释很多行的内容。为了可读性比较好,一般首行和尾行不写注释信息(这样也比较美观好看),如图 2 所示。
注意:多行注释可以嵌套单行注释,但是不能嵌套多行注释和文档注释。
图 2 多行注释
3)文档注释
包含在“/**”和“*/”之间,也能注释多行内容,一般用在类、方法和变量上面,用来描述其作用。注释后,鼠标放在类和变量上面会自动显示出我们注释的内容,如图 3 所示。
注意:文档注释能嵌套单行注释,不能嵌套多行注释和文档注释,一般首行和尾行也不写注释信息。
图 3 文档注释
文档注释可以通过 Javadoc 命令把文档注释中的内容生成文档,并输出到 HTML 文件中,方便记录程序信息。还可以包含一个或多个 @ 标签,每个 @ 标签都在新的一行开始。关于 Javadoc 的具体标签和使用可阅读学习《Javadoc入门教程》一节。
在 Java 中,一行注释以双斜杠“//”标识;多行注释包含在“/”和“/”之间;文档注释包含在“/”和“/”之间。当编译器执行到“//”时,就会忽略该行“//”之后的所有文本;当执行到“/”时,会扫描下一个“/”并忽略“/”和“*/”之间的任何文本;当执行到“/”时,也会扫描下一个“/”并忽略“/**”和“/”之间的任何文本内容。
Javadoc(文档注释)详解
Java 支持 3 种注释,分别是单行注释、多行注释和文档注释。文档注释以/**
开头,并以*/
结束,可以通过 Javadoc 生成 API 帮助文档,Java 帮助文档主要用来说明类、成员变量和方法的功能。
文档注释只放在类、接口、成员变量、方法之前,因为 Javadoc 只处理这些地方的文档注释,而忽略其它地方的文档注释。
Javadoc 是 Sun 公司提供的一种工具,它可以从程序源代码中抽取类、方法、成员等注释,然后形成一个和源代码配套的 API 帮助文档。也就是说,只要在编写程序时以一套特定的标签注释,在程序编写完成后,通过 Javadoc 就形成了程序的 API 帮助文档。
API 帮助文档相当于产品说明书,而说明书只需要介绍那些供用户使用的部分,所以 Javadoc 默认只提取 public、protected 修饰的部分。如果要提取 private 修饰的部分,需要使用 -private。
Javadoc标签
Javadoc 工具可以识别文档注释中的一些特殊标签,这些标签一般以@
开头,后跟一个指定的名字,有的也以{@
开头,以}
结束。Javadoc 可以识别的标签如下表所示:
标签 | 描述 | 示例 |
---|---|---|
@author | 标识一个类的作者,一般用于类注释 | @author description |
@deprecated | 指名一个过期的类或成员,表明该类或方法不建议使用 | @deprecated description |
{@docRoot} | 指明当前文档根目录的路径 | Directory Path |
@exception | 可能抛出异常的说明,一般用于方法注释 | @exception exception-name explanation |
{@inheritDoc} | 从直接父类继承的注释 | Inherits a comment from the immediate surperclass. |
{@link} | 插入一个到另一个主题的链接 | {@link name text} |
{@linkplain} | 插入一个到另一个主题的链接,但是该链接显示纯文本字体 | Inserts an in-line link to another topic. |
@param | 说明一个方法的参数,一般用于方法注释 | @param parameter-name explanation |
@return | 说明返回值类型,一般用于方法注释,不能出现再构造方法中 | @return explanation |
@see | 指定一个到另一个主题的链接 | @see anchor |
@serial | 说明一个序列化属性 | @serial description |
@serialData | 说明通过 writeObject() 和 writeExternal() 方法写的数据 | @serialData description |
@serialField | 说明一个 ObjectStreamField 组件 | @serialField name type description |
@since | 说明从哪个版本起开始有了这个函数 | @since release |
@throws | 和 @exception 标签一样. | The @throws tag has the same meaning as the @exception tag. |
{@value} | 显示常量的值,该常量必须是 static 属性。 | Displays the value of a constant, which must be a static field. |
@version | 指定类的版本,一般用于类注释 | @version info |
对两种标签格式的说明:
- @tag 格式的标签(不被
{ }
包围的标签)为块标签,只能在主要描述(类注释中对该类的详细说明为主要描述)后面的标签部分(如果块标签放在主要描述的前面,则生成 API 帮助文档时会检测不到主要描述)。 - {@tag} 格式的标签(由
{ }
包围的标签)为内联标签,可以放在主要描述中的任何位置或块标签的注释中。
Javadoc 标签注意事项:
- Javadoc 标签必须从一行的开头开始,否则将被视为普通文本。
- 一般具有相同名称的标签放在一起。
- Javadoc 标签区分大小写,代码中对于大小写错误的标签不会发生编译错误,但是在生成 API 帮助文档时会检测不到该注释内容。
Javadoc命令
Javadoc 用法格式如下:
javadoc [options] [packagenames] [sourcefiles]
对格式的说明:
- options 表示 Javadoc 命令的选项;
- packagenames 表示包名;
- sourcefiles 表示源文件名。
在 cmd(命令提示符)中输入javadoc -help
就可以看到 Javadoc 的用法和选项(前提是安装配置了JDK),下面列举 Javadoc 命令的常用选项:
名称 | 说明 |
---|---|
-public | 仅显示 public 类和成员 |
-protected | 显示 protected/public 类和成员(默认值) |
-package | 显示 package/protected/public 类和成员 |
-private | 显示所有类和成员 |
-d | 输出文件的目标目录 |
-version | 包含 @version 段 |
-author | 包含 @author 段 |
-splitindex | 将索引分为每个字母对应一个文件 |
-windowtitle
| 文档的浏览器窗口标题 |
DOS命令生成API帮助文档
新建一个空白记事本,输入下列代码:
/**
* @author C语言中文网
* @version jdk1.8.0
*/
public class Test{
/**
* 求输入两个参数范围以内整数的和
* @param n 接收的第一个参数,范围起点
* @param m 接收的第二个参数,范围终点
* @return 两个参数范围以内整数的和
*/
public int add(int n, int m) {
int sum = 0;
for (int i = n; i <= m; i++) {
sum = sum + i;
}
return sum;
}
}
将文件命名为 Test.java,打开 cmd 窗口,输入javadoc -author -version Test.java
命令。如图 1 所示。
图 1 cmd 运行窗口
打开 Test.java 文件存储的位置,会发现多出了一个 Test.html 文档。打开文档,文档页面如图 2 和图 3 所示。
图 2 Student.html 页面(1)
图 3 Student.html 页面(2)
注意:以上没有考虑编码格式的问题,注释中有汉字可能会乱码。使用javadoc -encoding UTF-8 -charset UTF-8 Test.java
会解决编码问题。
MyEclipse生成API帮助文档
1)在 MyEclipse 中新建一个 Test 类,代码如下:
package test;
/**
* @author C语言中文网
* @version jdk1.8.0
*/
public class Test {
public static void main(String[] args) {
/**
* 这是一个输出语句
*/
System.out.println("C语言中文网Java教程访问地址:http://c.biancheng.net/java/");
}
}
注意:代码 9~11 行没有放在类、成员变量或方法之前,所以 Javadoc 会忽略这个注释。
2)在项目名处单击鼠标右键,然后选择Export...
,如图 4 所示。
图 4
3)在弹出窗口中选择 Java 文件夹,点击 Java 文件夹下面的 Javadoc,然后点击“Next”,如图 5 所示。
图 5
4)选择你要生成 Javadoc 的项目,并更改你想保存的 API 帮助文档地址(默认为工程目录下,建议不要修改)。点击“Finish”,如图 6 所示。
图 6
5)点击“Finish”之后会问是否更新 Javadoc 文件的位置,只需要点击“Yes To All”即可,如图 7 所示。
图 7
6)这时可以看到控制台输出生成 Javadoc 的信息,如图 8 所示。
图 8
7)打开保存的文件夹,找到 Test.html 文件并打开,如图 9 所示。
图 9
以上就是使用 MyEclipse 简单建立一个 API 帮助文档的过程。
文档注释的格式
在编写文档注释的过程中,有时需要添加 HTML 标签,比如:需要换行时,应该使用<br>
,而不是一个回车符;需要分段时,应该使用<p>
。
例如把上面 Test 类改为以下代码:
package test;
/**
* @author C语言中文网<br>
* 严长生
* @version 1.8.0<br>
* 1.9.0
*/
public class Test {
public static void main(String[] args) {
System.out.println("C语言中文网Java教程访问地址:http://c.biancheng.net/java/");
}
}
帮助文档格式如图 10 所示。
图 10
Javadoc 并不是将代码中的文档注释直接复制到帮助文档的 HTML 文件中,而是读取每一行后,删除前面的*
号及*
以前的空格再输入到 HTML 文档。
/**
* first line.
******* second line.
* third line.
*/
编译输出后的 HTML 源码如下所示。
first line.
second line.
third line.
注释前面的*
号允许连续使用多个,其效果和使用一个*
号一样,但多个*
前不能有其他字符分隔,否则分隔符及后面的*
号都将作为文档的内容。
Java常量:Java常量的定义和分类
常量是指在程序的整个运行过程中值保持不变的量。在这里要注意常量和常量值是不同的概念,常量值是常量的具体和直观的表现形式,常量是形式化的表现。通常在程序中既可以直接使用常量值,也可以使用常量。
下面我们来系统地认识一下 Java 中的常量值,以及定义常量的方法。
常量值
常量值又称为字面常量,它是通过数据直接表示的,因此有很多种数据类型,像整型和字符串型等。下面一一介绍这些常量值。
整型常量值
Java 的整型常量值主要有如下 3 种形式。
- 十进制数形式:如 54、-67、0。
- 八进制数形式:Java 中的八进制常数的表示以 0 开头,如 0125 表示十进制数 85,-013 表示十进制数 -11。
- 十六进制数形式:Java 中的十六进制常数的表示以 0x 或 0X 开头,如 0x100 表示十进制数 256,-0x16 表示十进制数 -22。
整型(int)常量默认在内存中占 32 位,是具有整数类型的值,当运算过程中所需值超过 32 位长度时,可以把它表示为长整型(long)数值。长整型类型则要在数字后面加 L 或 1, 如 697L,表示一个长整型数,它在内存中占 64 位。
实型常量值
Java 的实型常量值主要有如下两种形式。
- 十进制数形式:由数字和小数点组成,且必须有小数点,如 12.34、-98.0。
- 科学记数法形式:如 1.75e5 或 32&E3,其中 e 或 E 之前必须有数字,且 e 或 E 之后的数字必须为整数。
Java 实型常量默认在内存中占 64 位,是具有双精度型(double)的值。如果考虑到需要节省运行时的系统资源,而运算时的数据值取值范围并不大且运算精度要求不太高的情况,可以把它表示为单精度型(float)的数值。
单精度型数值一般要在该常数后面加 F 或 f,如 69.7f,表示一个 float 型实数,它在内存中占 32 位(取决于系统的版本高低)。
布尔型常量值
Java 的布尔型常量只有两个值,即 false(假)和 true(真)。
字符型和字符串常量值
Java 的字符型常量值是用单引号引起来的一个字符,如 ‘e’、E’。需要注意的是,Java 字符串常量值中的单引号和双引号不可混用。双引号用来表示字符串,像 “11”、“d” 等都是表示单个字符的字符串。
除了以上所述形式的字符常量值之外,Java 还允许使用一种特殊形式的字符常量值来表示一些难以用一般字符表示的字符,这种特殊形式的字符是以开头的字符序列,称为转义字符。
注意:这里表示字符和字符串的单引号和双引号都必须是英语输入环境下输入的符号。
表 1 列出了 Java 中常用的转义字符及其表示的意义。
转义字符 | 说明 |
---|---|
\ddd | 1~3 位八进制数所表示的字符 |
\uxxxx | 1~4 位十六进制数所表示的字符 |
’ | 单引号字符 |
" | 双引号字符 |
\ | 双斜杠字符 |
\r | 回车 |
\n | 换行 |
\b | 退格 |
\t | 横向跳格 |
定义常量
常量不同于常量值,它可以在程序中用符号来代替常量值使用,因此在使用前必须先定义。常量与变量(在《Java变量的声明和初始化》一节中讲解)类似也需要初始化,即在声明常量的同时要赋予一个初始值。常量一旦初始化就不可以被修改。它的声明格式为:
Java 语言使用 final 关键字来定义一个常量,其语法如下所示:
final dataType variableName = value
其中,final 是定义常量的关键字,dataType 指明常量的数据类型,variableName 是变量的名称,value 是初始值。
final 关键字表示最终的,它可以修改很多元素,修饰变量就变成了常量。例如,以下语句使用 final 关键字声明常量。
public class HelloWorld {
// 静态常量
public static final double PI = 3.14;
// 声明成员常量
final int y = 10;
public static void main(String[] args) {
// 声明局部常量
final double x = 3.3;
}
}
常量有三种类型:静态常量、成员常量和局部常量。
代码第 3 行的是声明静态常量,使用在 final 之前 public static 修饰。public static 修饰的常量作用域是全局的,不需要创建对象就可以访问它,在类外部访问形式为 HelloWorld. PI。这种常量在编程中使用很多。
代码第 5 行声明成员常量,作用域类似于成员变量,但不能修改。代码第 9 行声明局部常量,作用域类似于局部变量,但不能修改。
在定义常量时,需要注意如下内容:
- 在定义常量时就需要对该常量进行初始化。
- final 关键字不仅可以用来修饰基本数据类型的常量,还可以用来修饰对象的引用或者方法。
- 为了与变量区别,常量取名一般都用大写字符。
当常量被设定后,一般情况下不允许再进行更改,如果更改其值将提示错误。例如,如下图中定义常量 AGE 并赋予初值,如果更改 AGE 的值,那么在编译时将提示不能重合赋值错误。
Java变量的声明和初始化
Java 语言是强类型(Strongly Typed)语言,强类型包含以下两方面的含义:
- 所有的变量必须先声明、后使用。
- 指定类型的变量只能接受类型与之匹配的值。
这意味着每个变量和每个表达式都有一个在编译时就确定的类型。类型限制了一个变量能被赋的值,限制了一个表达式可以产生的值,限制了在这些值上可以进行的操作,并确定了这些操作的含义。
常量和变量是 Java 程序中最基础的两个元素。常量的值是不能被修改的,而变量的值在程序运行期间可以被修改。本文详细介绍 Java 中变量的声明和赋值方法。
声明变量
对开发人员来说,变量是用来描述一条信息的别名,可以在程序代码中使用一个或多个变量。变量中可以存储各种类型的信息,如登录信息、版本名称、文件的大小、某个英文单词以及飞机票价格等。
在 java 中用户可以通过指定数据类型和标识符来声明变量,其基本语法如下所示:
DataType identifier;
或者
DataType identifier=value;
上述语法代码中涉及 3 个内容:DataType、 identifier 和 value,其具体说明如下:
- DataType:变量类型,如 int、string、 char 和 double 等。
- identifier:标识符,也叫变量名称。
- value:声明变量时的值。
变量标识符的命名规范如下:
- 首字符必须是字母、下划线(―)、美元符号($)或者人民币符号(¥)。
- 标识符由数字(09)、大写字母(AZ)、小写字母(a~z)、下划线(―)、美元符号($)、人民币符号(¥)以及所有在十六进制 0xc0 前的 ASCII 码组成。
- 不能把关键字、保留字作为标识符。
- 标识符的长度没有限制。
- 标识符区分大小写。
如下代码分别声明了 String、boolean 和 int 类型的变量。
String employee; // String 类型的变量
boolean isSave; // boolean 类型的变量
int create_at; // int 类型的变量
变量赋值
初始化变量是指为变量指定一个明确的初始值。初始化变量有两种方式:一种是声明时直接赋值,一种是先声明、后赋值。如下代码分别使用两种方式对变量进行了初始化。
char usersex='女'; // 直接赋值
或者
String username; // 先声明username ="琪琪"; // 后赋值
另外,多个同类型的变量可以同时定义或者初始化,但是多个变量中间要使用逗号分隔,声明结束时用分号分隔。
String username,address,phone,tel; // 声明多个变量int num1=12,num2=23,result=35; // 声明并初始化多个变量
Java 中初始化变量时需要注意以下事项:
- 变量是类或者结构中的字段,如果没有显式地初始化,默认状态下创建变量并默认初始值为 0。
- 方法中的变量必须显式地初始化,否则在使用该变量时就会出错。
Java变量的作用域:静态变量、全局变量和局部变量
变量的作用域规定了变量所能使用的范围,只有在作用域范围内变量才能被使用。根据变量声明地点的不同,变量的作用域也不同。
根据作用域的不同,一般将变量分为不同的类型:成员变量和局部变量。下面对这几种变量进行详细说明。
成员变量
Java 的成员变量有两种,分别是全局变量和静态变量(类变量)。定义在方法体和语句块之外,不属于任何一个方法,作用域是整个类。
名称 | 修饰 | 访问 | 生命周期 |
---|---|---|---|
全局变量(实例变量) | 无 static 修饰 | 对象名.变量名 | 只要对象被当作引用,实例变量就将存在 |
静态变量(类变量) | 用 static 修饰 | 类名.变量名或对象名.变量名 | 其生命周期取决于类的生命周期。类被垃圾回收机制彻底回收时才会被销毁 |
假设在一个类中声明了 4 个变量,下面编写一个测试类输出引起变量的值改变的示例代码。
变量声明代码如下所示:
public class DataClass {
String name; // 成员变量、实例变量
int age; // 成员变量、实例变量
static final String website = "C语言中文网"; // 成员变量、静态变量(类变量)
static String URL = "http://c.biancheng.net"; // 成员变量、静态变量(类变量)
}
测试类代码如下所示:
public class Test {
public static void main(String[] args) {
// 创建类的对象
DataClass dc = new DataClass();
// 对象名.变量名调用实例变量(全局变量)
System.out.println(dc.name);
System.out.println(dc.age);
// 对象名.变量名调用静态变量(类变量)
System.out.println(dc.website);
System.out.println(dc.URL);
// 类名.变量名调用静态变量(类变量)
System.out.println(DataClass.website);
System.out.println(DataClass.URL);
}
}
运行效果如下图 1 所示:
图 1 运行结果
在本例的第一段代码中定义了 4 个成员变量,由输出结果可以看出,name 和 age 显示系统默认初始化的值,website 和 URL 显示初始化的值。且用 static final 修饰的变量必须赋予初始值。
局部变量
局部变量是指在方法或者方法代码块中定义的变量,其作用域是其所在的代码块。可分为以下三种:
- 方法参数变量(形参):在整个方法内有效。
- 方法局部变量(方法内定义): 从定义这个变量开始到方法结束这一段时间内有效。
- 代码块局部变量(代码块内定义):从定义这个变量开始到代码块结束这一段时间内有效。
局部变量在使用前必须被程序员主动初始化值。
1)方法局部变量
声明两个局部变量并输出其值,其实现代码如下:
public class Test2 {
public static void main(String[] args) {
int a = 7;
if (5 > 3) {
int s = 3; // 声明一个 int 类型的局部变量
System.out.println("s=" + s);
System.out.println("a=" + a);
}
System.out.println("a=" + a);
}
}
上述实例中定义了 a 和 s 两个局部变星,其中 int 类型的 a 的作用域是整个 main() 方法,而 int 类型的变量 s 的作用域是 if 语句的代码块内,其执行结果如图 2 所示:
图 2 运行结果
如果在 if 方法外调用变量 s,则会报无法解析该变量的错误。
2)方法参数变量
作为方法参数声明的变量的作用域是整个方法。
声明一个方法参数变量,实现代码如下:
public class Test3 {
public static void testFun(int n) {
System.out.println("n=" + n);
}
public static void main(String[] args) {
testFun(3);
}
}
在上述实例中定义了一个 testFun() 方法,该方法中包含一个 int 类型的参数变量 n,其作用域是 testFun() 方法体内。当调用方法时传递进了一个参数 3,因此其输出控制台的 n 值是 3。
3)代码块局部变量
代码块局部变量常用于 try catch 代码块中,成为异常处理参数变量。
异常处理参数变量的作用域是在异常处理块中,该变量是将异常处理参数传递给异常处理块,与方法参数变量类似。
声明一个异常处理语句,实现代码如下:
public class Test4 {
public static void test() {
try {
System.out.println("Hello!Exception!");
} catch (Exception e) { // 异常处理块,参数为 Exception 类型
e.printStackTrace();
}
}
public static void main(String[] args) {
test();
}
}
在上述实例中定义了异常处理语句,异常处理块 catch 的参数为 Exception 类型的变量 e,作用域是整个 catch 块。
Java数据类型:基本数据类型和引用数据类型
前面我们提到 Java 语言是强类型语言,编译器存储在变量中的数值具有适当的数据类型。学习任何一种编程语言都要了解其数据类型,本文将详细介绍 Java 中的数据类型。
Java 语言支持的数据类型分为两种:基本数据类型(Primitive Type)和引用数据类型(Reference Type)。
Java基本数据类型
基本数据类型包括 boolean(布尔型)、float(单精度浮点型)、char(字符型)、byte(字节型)、short(短整型)、int(整型)、long(长整型)和 double (双精度浮点型)共 8 种,详见表 1 所示。
类型名称 | 关键字 | 占用内存 | 取值范围 |
---|---|---|---|
字节型 | byte | 1 字节 | -128~127 |
短整型 | short | 2 字节 | -32768~32767 |
整型 | int | 4 字节 | -2147483648~2147483647 |
长整型 | long | 8 字节 | -9223372036854775808L~9223372036854775807L |
单精度浮点型 | float | 4 字节 | +/-3.4E+38F(6~7 个有效位) |
双精度浮点型 | double | 8 字节 | +/-1.8E+308 (15 个有效位) |
字符型 | char | 2 字节 | ISO 单一字符集 |
布尔型 | boolean | 1 字节 | true 或 false |
提示:char 代表字符型,实际上字符型也是一种整数类型,相当于无符号整数类型。
所有的基本数据类型的大小(所占用的字节数)都已明确规定,在各种不同的平台上保持不变,这一特性有助于提高 Java 程序的可移植性。
Java 数据类型的结构如图 1 所示。
图 1 Java数据类型结构图
Java 是一种强制类型的语言,所有的变量都必须先明确定义其数据类型,然后才能使用。Java 中所有的变量、表达式和值都必须有自己的类型,没有“无类型”变量这样的概念。
基本数据类型又可分为 4 大类,即整数类型(包括 byte、short,int 和 long)、浮点类型(包括 float 和 double)、布尔类型和字符类型(char),下面分别介绍这 4 大类数据类型。
1)整数类型
Java 定义了 4 种整数类型变量:字节型(byte)、短整型(short)、整型(int)和长整型(long)。这些都是有符号的值,正数或负数。具体说明表 2 所示:
名称 | 说明 |
---|---|
字节型(byte) | byte 类型是最小的整数类型。当用户从网络或文件中处理数据流时,或者处理可能与 Java 的其他内置类型不直接兼容的未加工的二进制数据时,该类型非常有用。 |
短整型(short) | short 类型限制数据的存储为先高字节,后低字节,这样在某些机器中会出错,因此该类型很少被使用。 |
整型(int) | int 类型是最常使用的一种整数类型。 |
长整型(long) | 对于大型程序常会遇到很大的整数,当超出 int 类型所表示的范围时就要使用 long 类型。 |
例 1
创建一个 Java 程序,在 main() 方法中声明各种整型的变量并赋予初值,最后将变量相加并输出结果,代码如下:
public static void main(String[] args) {
byte a = 20; // 声明一个byte类型的变量并赋予初始值为20
short b = 10; // 声明一个short类型的变量并赋予初始值为10
int c = 30; // 声明一个int类型的变量并赋予初始值为30
long d = 40; // 声明一个long类型的变量并赋予初始值为40
long sum = a + b + c + d;
System.out.println("20+10+30+40=" + sum);
}
保存该段代码并运行,输出的最终结果如图 2 所示:
图 2 运行结果
在该示例中,首先依次定义了 byte 类型、short 类型、int 类型和 long 类型的 4 个变量,并赋予了初始值,然后定义了一个 long 类型、名称为 sum 的变量。sum 变量的值为前 4 个变量之和,最后输出 sum 变量的值,即相加之后的结果。
提示:因为 byte 类型、short 类型、int 类型和 long 类型都是整数类型,故可以使用“+”相加,而非字符串之间的连接。
2)浮点类型
浮点类型是带有小数部分的数据类型,也叫实型。浮点型数据包括单精度浮点型(float)和双精度浮点型(double),代表有小数精度要求的数字。
单精度浮点型(float)和双精度浮点型(double)之间的区别主要是所占用的内存大小不同,float 类型占用 4 字节的内存空间,double 类型占用 8 字节的内存空间。双精度类型 double 比单精度类型 float 具有更高的精度和更大的表示范围。
Java 默认的浮点型为 double,例如,11.11 和 1.2345 都是 double 型数值。如果要说明一个 float 类型数值,就需要在其后追加字母 f 或 F,如 11.11f 和 1.2345F 都是 float 类型的常数。
例如,可以使用如下方式声明 float 类型的变量并赋予初值。
float price = 12.2f; // 定义float类型并赋予初值
也可以使用如下的任意一种方式声明 double 类型的变量并赋予初值。
double price = 12.254d; // 定义double类型的变量并赋予初值
或
double price = 12.254; // 定义double类型的变量并赋予初值
注意:一个值要能被真正看作 float,它必须以 f(或 F)后缓结束;否则,会被当作 double 值。对 double 值来说,d(或 D)后缓是可选的。
例 2
假设从 A 地到 B 地路程为 2348.4 米,那么往返 A 和 B 两地需要走多少米?
由于路径数据为浮点类型,在这里定义一个类型为 double 的变量来存储单程距离,并定义一个 int 类型的变量来存储次数。另外,因为计算得到的值为 float 类型,所以可以定义一个 float 类型的变量来存储总距离。
代码如下:
public static void main(String[] args) {
double lutu = 2348.4; // 定义 double 类型的变量,用于存储单程距离
int num = 2; // 定义 int 类型的变量,用于存储次数
float total = (float) (lutu * 2); // 定义 float 类型的变量,用于存储总距离
System.out.println("往返 AB 两地共需要行驶:" + total + " 米");
}
保存文件并运行,输出的结果如图 3 所示:
图 3 输出结果
该示例中首先定义了一个类型为 double、名称为 lutu 的变量用于存储单程距离,然后定义了一个类型为 int、名称为 num 的变量用于存储经过的次数,最后定义了一个类型为 float、名称为 total 的变量用于存储总距离。
其实一个 double 类型的数据与一个 int 类型的数据相乘后得到的结果类型为 double,但是由于单程距离乘以次数为一个单精度浮点型(float 类型)的数,因此可以将总距离转换为 float 类型的数据。
3)布尔类型
布尔类型(boolean)用于对两个数值通过逻辑运算,判断结果是“真”还是“假”。Java 中用保留字 true 和 false 来代表逻辑运算中的“真”和“假”。因此,一个 boolean 类型的变量或表达式只能是取 true 和 false 这两个值中的一个。
在 Java 语言中,布尔类型的值不能转换成任何数据类型,true 常量不等于 1,而 false 常量也不等于 0。这两个值只能赋给声明为 boolean 类型的变量,或者用于布尔运算表达式中。
例如,可以使用以下语句声明 boolean 类型的变量。
boolean isable; // 声明 boolean 类型的变量 isable
boolean b = false; // 声明 boolean 类型的变量 a,并赋予初值为 false
4)字符类型
Java 语言中的字符类型(char)使用两个字节的 Unicode 编码表示,它支持世界上所有语言,可以使用单引号字符或者整数对 char 型赋值。
一般计算机语言使用 ASCII 编码,用一个字节表示一个字符。ASCII 码是 Unicode 码的一个子集,用 Unicode 表示 ASCII 码时,其高字节为 0,它是其前 255 个字符。
Unicode 字符通常用十六进制表示。例如“\u0000”~“\u00ff”表示 ASCII 码集。“\u”表示转义字符,它用来表示其后 4 个十六进制数字是 Unicode 码。
字符型变量的类型为 char,用来表示单个的字符,例如:
char letter = 'D';
char numChar = '5';
第一条语句将字符 D 赋给字符型变量 letter;第二条语句将数字字符 5 赋给字符型变量 numChar。
例 3
下面代码在 main() 方法中定义两个字符类型的变量,并使之相对应的 ASCII(Unicode)值相加,最后将相加后的结果输出。
public static void main(String[] args) {
char a = 'A'; // 向 char 类型的 a 变量赋值为 A,所对应的 ASCII 值为 65
char b = 'B'; // 向 char 类型的 b 变量赋值为 B,所对应的 ASCII 值为 66
System.out.println("A 的 ASCII 值与 B 的 ASCII 值相加结果为:"+(a+b));
}
保存该段代码并运行,输出结果如图 4 所示。
图 4 输出结果
在该程序中,a 变量首先被赋值为“A”,字母 A 在 ASCII(和 Unicode)中对应的值为 65。接着又定义了一个类型为 char 的变量 b,赋值为“B”,字母 B 在 ASCII(和 Unicode)中所对应的值为 66。因此相加后得出的结果为 131。
提示:字符通常用 16 进制表示,范围从“\uOOOO”~“\uFFFF”,即从 0~65535。\uOOOO 和 \uFFFF 中的 u 告诉编译器是用两个字节(16 位)字符信息表示一个 Unicode 字符。
Java引用数据类型
引用数据类型建立在基本数据类型的基础上,包括数组、类和接口。引用数据类型是由用户自定义,用来限制其他数据的类型。另外,Java 语言中不支持 C++ 中的指针类型、结构类型、联合类型和枚举类型。
引用类型还有一种特殊的 null 类型。所谓引用数据类型就是对一个对象的引用,对象包括实例和数组两种。实际上,引用类型变量就是一个指针,只是 Java 语言里不再使用指针这个说法。
空类型(null type)就是 null 值的类型,这种类型没有名称。因为 null 类型没有名称,所以不可能声明一个 null 类型的变量或者转换到 null 类型。
空引用(null)是 null 类型变量唯一的值。空引用(null)可以转换为任何引用类型。
在实际开发中,程序员可以忽略 null 类型,假定 null 只是引用类型的一个特殊直接量。
注意:空引用(null)只能被转换成引用类型,不能转换成基本类型,因此不要把一个 null 值赋给基本数据类型的变量。
Java数据类型转换:强制类型转换+自动类型转换
数据类型的转换是在所赋值的数值类型和被变量接收的数据类型不一致时发生的,它需要从一种数据类型转换成另一种数据类型。数据类型的转换可以分为隐式转换(自动类型转换)和显式转换(强制类型转换)两种。
隐式转换(自动类型转换)
如果以下 2 个条件都满足,那么将一种类型的数据赋给另外一种类型变量的时,将执行自动类型转换(automatic type conversion)。
- 两种数据类型彼此兼容
- 目标类型的取值范围大于源数据类型(低级类型数据转换成高级类型数据)
当以上 2 个条件都满足时,拓宽转换(widening conversion)发生。例如 byte 类型向 short 类型转换时,由于 short 类型的取值范围较大,会自动将 byte 转换为 short 类型。
在运算过程中,由于不同的数据类型会转换成同一种数据类型,所以整型、浮点型以及字符型都可以参与混合运算。自动转换的规则是从低级类型数据转换成高级类型数据。转换规则如下:
- 数值型数据的转换:byte→short→int→long→float→double。
- 字符型转换为整型:char→int。
以上数据类型的转换遵循从左到右的转换顺序,最终转换成表达式中表示范围最大的变量的数据类型。
例 1
顾客到超市购物,购买牙膏 2 盒,面巾纸 4 盒。其中牙膏的价格是 10.9 元,面巾纸的价格是 5.8 元,求商品总价格。实现代码如下:
public static void main(String[] args) {
float price1 = 10.9f; // 定义牙膏的价格
double price2 = 5.8; // 定义面巾纸的价格
int num1 = 2; // 定义牙膏的数量
int num2 = 4; // 定义面巾纸的数量
double res = price1 * num1 + price2 * num2; // 计算总价
System.out.println("一共付给收银员" + res + "元"); // 输出总价
}
上述代码中首先定义了一个 float 类型的变量存储牙膏的价格,然后定义了一个 double 类型的变量存储面巾纸的价格,再定义两个 int 类型的变量存储物品的数量,最后进行了乘运算以及和运算之后,将结果储存在一个 double 类型的变量中进行输出。
程序执行结果如下图 1 所示:
图 1 运行结果
从执行结果看出,float、int 和 double 三种数据类型参与运算,最后输出的结果为 double 类型的数据。这种转换一般称为“表达式中类型的自动提升”。
自动类型提升有好处,但它也会引起令人疑惑的编译错误。例如,下面看起来正确的程序却会引起问题:
byte b = 50;
b = b * 2; // Type mismatch: cannot convert from int to byte
如上所示,第二行会报“类型不匹配:无法从int转换为byte”错误。
该程序试图将一个完全合法的 byte 型的值 50*2 再存储给一个 byte 型的变量。但是当表达式求值的时候,操作数被自动的提升为 int 型,计算结果也被提升为 int 型。这样表达式的结果现在是 int 型,不强制转换它就不能被赋为 byte 型。确实如此,在这个特别的情况下,被赋的值将仍然适合目标类型。
所以应该使用一个显示的强制类型转换,例如:
byte b = 50;
b = (byte)(b*2);
这样就能产生正确的值 100。
注意:char 类型比较特殊,char 自动转换成 int、long、float 和 double,但 byte 和 short 不能自动转换为 char,而且 char 也不能自动转换为 byte 或 short。
显式转换(强制类型转换)
尽管自动类型转换是很有帮助的,但并不能满足所有的编程需要。例如,如果你需要将 double 型的值赋给一个 int 型的变量,你将怎么办?
这种转换不会自动进行,因为 double 型的变化范围比 int 型的要小。这种转换有使成为“缩小转换”,因为你肯定要将源数据类型的值变小才能适合目标数据类型。
所以当两种数据类型不兼容,或目标类型的取值范围小于源类型时,自动转换将无法进行,这时就需要进行强制类型转换。其语法格式如下:
(type)variableName
其中,type 为 variableName 要转换成的数据类型,而 variableName 是指要进行类型转换的变量名称,强制转换的实例如下:
int a = 3;
double b = 5.0;
a = (int)b;
上述代码中首先将 double 类型变量 b 的值强制转换成 int 类型,然后将值赋给 a,但是变量 b 本身的值是没有发生变化的。
在强制类型转换中,如果是将浮点类型的值转换为整数,直接去掉小数点后边的所有数字;而如果是整数类型强制转换为浮点类型时,将在小数点后面补零。
例 2
顾客到超市购物,购买牙膏 2 盒,面巾纸 4 盒。其中牙膏的价格是 10.9 元,面巾纸的价格是 5.8 元,求商品总价格,在计算总价时采用 int 类型的数据进行存储。实现代码如下:
public static void main(String[] args) {
float price1 = 10.9f;
double price2 = 5.8;
int num1 = 2;
int num2 = 4;
int res2 = (int) (price1 * num1 + price2 * num2);
System.out.println("一共付给收银员" + res2 + "元");
}
在上述实例中,有 double 类型、float 类型和 int 类型的数据参与运算,其运算结果默认为 double 类型,题目要求的结果为 int 类型,因为 int 类型的取值范围要小于 double 类型的取值范围,所以需要进行强制类型转换。
程序执行结果如下图 2 所示:
图 2 运行结果
Java算术运算符
运算符丰富是 Java 语言的主要特点之一,它提供的运算符数量之多,在高级语言中是少见的。
Java 语言中的运算符除了具有优先级之外,还有一个结合性的特点。当一个表达式中出现多种运算符时,执行的先后顺序不仅要遵守运算符优先级别的规定,还要受运算符结合性的约束,以便确定是自左向右进行运算还是自右向左进行运算。这些运算符按照操作数的数量可以分为单目运算符、双目运算符和三目运算符。
最基本的运算符包括算术运算符、赋值运算符、逻辑运算符和关系运算符等,本文将详细介绍算术运算符。
Java 中的算术运算符主要用来组织数值类型数据的算术运算,按照参加运算的操作数的不同可以分为一元运算符和二元运算符。
一元运算符
算术一元运算一共有 3 个,分别是 -、++ 和 --。具体说明参见表 1。
运 算 符 | 名 称 | 说 明 | 例 子 |
---|---|---|---|
- | 取反符号 | 取反运算 | b=-a |
++ | 自加一 | 先取值再加一,或先加一再取值 | a++ 或 ++a |
– | 自减一 | 先取值再减一,或先减一再取值 | a-- 或 --a |
表 1 中,-a 是对 a 取反运算,a++ 或 a-- 是在表达式运算完后,再给 a 加一或减一。而 ++a 或 --a 是先给 a 加一或减一,然后再进行表达式运算。
int a = 12;
System.out.println(-a);
int b = a++;
System.out.println(b);
b = ++a;
System.out.println(b);
上述代码第 2 行是 -a,是把 a 变量取反,结果输出是 -12。第 4 行代码是先把 a 赋值给 b 变量再加一,即先赋值后 ++,因此输出结果是 12。第 6 行代码是把 a 加一,然后把 a 赋值给 b 变量,即先 ++ 后赋值,因此输出结果是 14。
输出结果如下图所示:
图 1 输出结果
关于 ++ 和 --,可直接参考《Java自增和自减运算符(++和–)》一节。
二元运算符
Java 语言中算术运算符的功能是进行算术运算,除了经常使用的加(+)、减(-)、乘()和除(\)外,还有取模运算(%)。加(+)、减(-)、乘()、除(\)和我们平常接触的数学运算具有相同的含义。具体说明参见表 2。
运 算 符 | 名 称 | 说 明 | 例 子 |
---|---|---|---|
+ | 加 | 求 a 加 b 的和,还可用于 String 类型,进行字符串连接操作 | a + b |
- | 减 | 求 a 减 b 的差 | a - b |
* | 乘 | 求 a 乘以 b 的积 | a * b |
/ | 除 | 求 a 除以 b 的商 | a / b |
% | 取余 | 求 a 除以 b 的余数 | a % b |
算术运算符都是双目运算符,即连接两个操作数的运算符。优先级上,*、/、% 具有相同运算级别,并高于 +、-(+、- 具有相同级别)。例如:
int a = 4, b = 2, c = 3;
int d = a * (b + c) % c;
这种运算规则与数学运算中的规则是相同的。首先计算赋值符号(=)右边配对的括号内的值,其次按从左向右的结合方向计算乘法,最后做求余运算,表达式的结果为 2, 然后把 2 赋值给 d。
例如:
①int x=2,y=1; 表达式 y/x 的结果是 0。
②float x=2.0f; int y=1; 表达式 y/x 的结果是 0.5。
在 ① 中整型变量 x 和 y 相除,其结果仍为整型数据 0;在 ② 中由于两个不同类型的数据进行运算,此时首先要进行类型转换,会把 int 型的 y 转换成与 x 一样的 float 型,然后相除,最终结果为 float 类型的数字 0.5。
**【例1】**编写一个程序,输出不同类型的两个数,执行相加、相减、相乘、相除和求余后输入结果。
实现代码如下:
public static void main(String[] args) {
float f1 = 9 % 4;// 保存取余后浮点类型的结果
double da = 9 + 4.5; // 双精度加法
double db = 9 - 3.0; // 双精度减法
double dc = 9 * 2.5; // 双精度乘法
double dd = 9 / 3.0; // 双精度除法
double de = 9 % 4; // 双精度取余
System.out.println("整数的算术运算"); // 整数的加、减、乘、除和取余
System.out.printf("9+4=%d \n", 9 + 4);
System.out.printf("9-4=%d \n", 9 - 4);
System.out.printf("9*4=%d \n", 9 * 4);
System.out.printf("9/4=%d \n", 9 / 4);
System.out.printf("9%%4=%d \n", 9 % 4);
System.out.println("\n浮点数的算术运算"); // 浮点数的加、减、乘、除和取余
System.out.printf("9+4.5f=%f \n", 9 + 4.5f);
System.out.printf("9-3.0f=%f \n", 9 - 3.0f);
System.out.printf("9*2.5f=%f \n", 9 * 2.5f);
System.out.printf("9/3.0f=%f \n", 9 / 3.0f);
System.out.printf("9%%4=%f \n", f1);
System.out.println("\n双精度数的算术运算"); // 双精度数的加、减、乘、除和取余
System.out.printf("9+4.5=%4.16f \n", da);
System.out.printf("9-3.0=%4.16f \n", db);
System.out.printf("9*2.5=%4.16f \n", dc);
System.out.printf("9/3.0=%4.16f \n", dd);
System.out.printf("9%%4=%4.16f \n", de);
System.out.println("\n字符的算术运算"); // 对字符的加法和减法
System.out.printf("'A'+32=%d \n", 'A' + 32);
System.out.printf("'A'+32=%c \n", 'A' + 32);
System.out.printf("'a'-'B'=%d \n", 'a' - 'B');
}
保存文件并运行,输出的结果如下所示。
整数的算术运算
9+4=13
9-4=5
9*4=36
9/4=2
9%4=1
浮点数的算术运算
9+4.5f=13.500000
9-3.0f=6.000000
9*2.5f=22.500000
9/3.0f=3.000000
9%4=1.000000
双精度数的算术运算
9+4.5=13.5000000000000000
9-3.0=6.0000000000000000
9*2.5=22.5000000000000000
9/3.0=3.0000000000000000
9%4=1.0000000000000000
字符的算术运算
'A'+32=97
'A'+32=a
'a'-'B'=31
本示例中使用了 4 种类型来执行算术运算。其中,整数类型的结果最容易理解,浮点型和双精度型返回的结果都带有小数,字符型将会把字符转换为 ASCII 码再运算。
从输出结果中可以看到,整数之间的运算结果只保留整数部分,浮点型运算时保留 6 位小数部分,双精度运算时则保留 16 位小数部分。
注意:Java 语言算术运算符的优先级是先乘除后加减。例如在表达式“a-bc”中,b 的左侧为减号,右侧为乘号,而乘号优先级高于减号,因此该表达式可以转换为“a-(bc)”。
如果在一个表达式中的多个算术运算符的优先级别相同,例如“a-b+c”,此时将按照运算符的结合方向决定顺序。算术运算符的结合方向都是“从左至右”,即先左后右。因此 b 先与减号结合,执行“a-b”的运算,再执行加 c 的运算。
算术赋值运算符
算术赋值运算符只是一种简写,一般用于变量自身的变化,具体说明参见表 3。
运 算 符 | 名 称 | 例 子 |
---|---|---|
+= | 加赋值 | a += b、a += b+3 |
-= | 减赋值 | a -= b |
*= | 乘赋值 | a *= b |
/= | 除赋值 | a /= b |
%= | 取余赋值 | a %= b |
示例代码如下:
int a = 1;
int b = 2;
a += b; // 相当于 a = a + b
System.out.println(a);
a += b + 3; // 相当于 a = a + b + 3
System.out.println(a);
a -= b; // 相当于 a = a - b
System.out.println(a);
a *= b; // 相当于 a=a*b
System.out.println(a);
a /= b; // 相当于 a=a/b
System.out.println(a);
a %= b; // 相当于 a=a%b
System.out.println(a);
运行结果如下图所示:
图 2 运行结果
上述例子分别对整型进行了+=、-=、*=、/= 和 %= 运算,具体语句不再赘述。
Java赋值运算符(=)
其语法格式如下所示:
变量名称=表达式内容
在 Java 语言中,“变量名称”和“表达式”内容的类型必须匹配,如果类型不匹配则需要自动转化为对应的类型。
赋值运算符的优先级低于算术运算符,结合方向是自右向左;不是数学中的等号,它表示一个动作,即将其右侧的值送到左侧的变量中(左侧只允许是变量,不能是表达式或其他形式);不要将赋值运算符与相等运算符“==”混淆。
赋值运算符与其他运算符一起使用,可以表达多种赋值运算的变异效果。例如,在基本的赋值运算符的基础之上,可以结合算术运算符,以及后面要学习的位运算符,组合成复合的赋值运算符。赋值运算符和算数运算符组成的复合赋值运算的含义及其使用实例如表 1 所示。
运算符 | 含义 | 实例 | 结果 |
---|---|---|---|
+= | 将该运算符左边的数值加上右边的数值, 其结果赋值给左边变量本身 | int a=5; a+=2; | a=7 |
-= | 将该运算符左边的数值减去右边的数值, 其结果赋值给左边变量本身 | int a=5; a-=2; | a=3 |
*= | 将该运算符左边的数值乘以右边的数值, 其结果赋值给左边变量本身 | int a=5; a*=2; | a=10 |
/= | 将该运算符左边的数值整除右边的数值, 其结果赋值给左边变量本身 | int a=5; a/=2; | a=2 |
%= | 将该运算符左边的数值除以右边的数值后取余,其结果赋值给左边变量本身 | int a=5; a%=2; | a=1 |
在大型程序中,灵活运用这些赋值运算符可以提高程序的易读性,并且使程序更加容易保护。下面是一些使用赋值运算符的示例。
int x, y, z; // 定义3个整型的变量
x = y = z = 5; // 为变量赋初值为5
x += 10; // 等价于x=x+10,结果x=15
y -= 3; // 等价于y=y-3,结果y=2
z *= 5; // 等价于z=z*5,结果z=25
x /= 4; // 等价于x=x/4,结果x=3
z %= x; // 等价于z=z%x,结果z=1
例 1
例如,一件商品的单价从 10.25 元降了 1.25 元,而自己购买的数量由原来的两个增加到 10 个,可以使用复合赋值运算符来计算购买商品的总价。
实现代码如下:
public static void main(String[] args) {
double price = 10.25; // 定义商品的单价,赋值为10.25
double total = 0; // 定义总价初始为0
int count = 2; // 定义购买数量,赋值为2
price -= 1.25; // 减去降价得到当前单价
count *= 5; // 现在需要购买10个,即原来数量的5倍
total = price * count; // 总价=当前单价*数量
System.out.printf("商品当前的单价为:%4.2f \n", price); // 输出当前单价
System.out.printf("购买商品的数量为:%d \n", count); // 输出购买数量
System.out.printf("总价为:%4.2f \n", total); // 输出总价
}
保存代码并运行,输出的结果如下:
图 1 运行结果
注意:虽然 Java 支持这种一次为多个变量赋值的写法,但这种写导致程序的可读性降低,因此不推荐这样写。
在该程序中,表示商品单价的 price 变量值为 10.25,而现在降了 1.25,在原来的基础上减去 1.25 即可获取现在的单价。而原来购买的数量为两个,现在需要购买 10 个,可以使用“count*=5”将数量乘以 5 倍之后的值赋值给 count 本身。
赋值运算符还用于将表达式的值赋给变量,如下代码是正确的。
double d1 = 12.34
double d2 = d1 + 5; // 将表达式的值赋给d2
System.out.println(d2); // 输出 d2 的值,将输出 17.34
赋值运算符还可与其他运算符结合,扩展成功能更加强大的赋值运算符。
Java逻辑运算符(&&、||和!)
逻辑运算符把各个运算的关系表达式连接起来组成一个复杂的逻辑表达式,以判断程序中的表达式是否成立,判断的结果是 true 或 false。
逻辑运算符是对布尔型变量进行运算,其结果也是布尔型,具体如表 1 所示。
运算符 | 用法 | 含义 | 说明 | 实例 | 结果 |
---|---|---|---|---|---|
&& | a&&b | 短路与 | ab 全为 true 时,计算结果为 true,否则为 false。 | 2>1&&3<4 | true |
|| | a||b | 短路或 | ab 全为 false 时,计算结果为 false,否则为 true。 | 2<1||3>4 | false |
! | !a | 逻辑非 | a 为 true 时,值为 false,a 为 false 时,值为 true | !(2>4) | true |
| | a|b | 逻辑或 | ab 全为 false 时,计算结果为 false,否则为 true | 1>2|3>5 | false |
& | a&b | 逻辑与 | ab 全为 true 时,计算结果为 true,否则为 false | 1<2&3<5 | true |
- && 与 & 区别:如果 a 为 false,则不计算 b(因为不论 b 为何值,结果都为 false)
- || 与 | 区别:如果 a 为 true,则不计算 b(因为不论 b 为何值,结果都为 true)
注意:短路与(&&)和短路或(||)能够采用最优化的计算方式,从而提高效率。在实际编程时,应该优先考虑使用短路与和短路或。
结果为 boolean 型的变量或表达式可以通过逻辑运算符结合成为逻辑表达式。逻辑运算符 &&、|| 和 !按表 2 进行逻辑运算。
a | b | a&&b | a||b | !a |
---|---|---|---|---|
true | true | true | true | false |
false | true | false | true | true |
true | false | false | true | false |
false | false | false | false | true |
逻辑运算符的优先级为:!运算级别最高,&& 运算高于 || 运算。!运算符的优先级高于算术运算符,而 && 和 || 运算则低于关系运算符。结合方向是:逻辑非(单目运算符)具有右结合性,逻辑与和逻辑或(双目运算符)具有左结合性。
下面是一些使用逻辑运算符的示例。
x>0 && x<=100 // 第一行语句
y%4==0 || y%3==0 // 第二行语句
!(x>y) // 第三行语句
其中,第一行语句用于判断 x 的值是否大于 0 且小于或等于 100,只有两个条件同时成立结果才为真(true)。第二行语句用于判断 y 的值是否能被 4 或者 3 整除,只要有一个条件成立,结果就为真(true)。第三行语句先比较 x 和 y,再将比较结果取反,即如果 x 大于 y 成立,则结果为假(false),否则为真(true)。
Java关系运算符
关系运算符(relational operators)也可以称为“比较运算符”,用于用来比较判断两个变量或常量的大小。关系运算符是二元运算符,运算结果是 boolean 型。当运算符对应的关系成立时,运算结果是 true,否则是 false。
关系表达式是由关系运算符连接起来的表达式。关系运算符中“关系”二字的含义是指一个数据与另一个数据之间的关系,这种关系只有成立与不成立两种可能情况,可以用逻辑值来表示,逻辑上的 true 与 false 用数字 1 与 0 来表示。关系成立时表达式的结果为 true(或 1),否则表达式的结果为 false(或 0)。表 1 给出了比较运算符的含义及其实例应用。
运算符 | 含义 | 说明 | 实例 | 结果 |
---|---|---|---|---|
> | 大于运算符 | 只支持左右两边操作数是数值类型。如果前面变量的值大于后面变量的值, 则返回 true。 | 2>3 | false |
>= | 大于或等于运算符 | 只支持左右两边操作数是数值类型。如果前面变量的值大于等于后面变量的值, 则返回 true。 | 4>=2 | true |
< | 小于运算符 | 只支持左右两边操作数是数值类型。如果前面变量的值小于后面变量的值,则返回 true。 | 2<3 | true |
<= | 小于或等于运算符 | 只支持左右两边操作数是数值类型。如果前面变量的值小于等于后面变量的值, 则返回 true。 | 4<=2 | false |
== | 相等运算符 | 如果进行比较的两个操作数都是数值类型,无论它们的数据类型是否相同,只要它们的值相等,也都将返回 true。 如果两个操作数都是引用类型,只有当两个引用变量的类型具有父子关系时才可以比较,只要两个引用指向的不是同一个对象就会返回 true。 Java 也支持两个 boolean 类型的值进行比较。 | 44 97’a’ 5.05 truefalse | true true true false |
!= | 不相等运算符 | 如果进行比较的两个操作数都是数值类型,无论它们的数据类型是否相同,只要它们的值不相等,也都将返回 true。 如果两个操作数都是引用类型,只有当两个引用变量的类型具有父子关系时才可以比较,只要两个引用指向的不是同一个对象就会返回 true。 | 4!=2 | true |
注意点如下所示:
- 基本类型的变量、值不能和引用类型的变量、值使用 == 进行比较;boolean 类型的变量、值不能与其他任意类型的变量、值使用 == 进行比较;如果两个引用类型之间没有父子继承关系,那么它们的变量也不能使用 == 进行比较。
- == 和 != 可以应用于基本数据类型和引用类型。当用于引用类型比较时,比较的是两个引用是否指向同一个对象,但当时实际开发过程多数情况下,只是比较对象的内容是否相当,不需要比较是否为同一个对象。
关系运算符的优先级为:>、<、>=、<= 具有相同的优先级,并且高于具有相同优先级的 !=、==。关系运算符的优先级高于赋值运算符而低于算术运算符,结合方向是自左向右。
关系表达式通常用于 Java 程序的逻辑判断语句的条件表达式中。使用关系表达式要注意以下几点:
- 运算符 >=、==、!=、<= 是两个字符构成的一个运算符,用空格从中分开写就会产生语法错误。例如
x> =y;
是错误的,但是可以写成x >= y;
在运算符的两侧增加空格会提高可读性。同样将运算符写反,例如 =>、=<、=! 等形式会产生语法错误。 - 由于计算机内存放的实数与实际的实数存在着一定的误差,如果对浮点数进行 ==(相等)或 !=(不相等)的比较,容易产生错误结果,应该尽量避免。
- 不要将“==”写成“=”。
下面是一些使用关系运算符的示例。
a > b // 比较变量a的值是否大于变量b的值
x+y> = z // 比较变量x与变量y的和是否大于或等于变量z的值
width * width+size != area // 比较变量width的平方加上变量size的值是否与变量area的值不相等
name == "zhht" // 比较变量name的值是否等于字符串nzht
pass != "123456" // 比较变量pass的值是否不等于字符串“123456”
例 1
编写一个程序,使用户可以从键盘输入两个数,并判断这两个数之间的大小。 实现代码如下:
public static void main(String[] args) {
int number1, number2; // 定义变量,保存输入的两个数
System.out.print("请输入第一个整数(number1):");
Scanner input = new Scanner(System.in);
number1 = input.nextInt(); // 输入第一个数
System.out.print("请输入第二个整数(number2):");
input = new Scanner(System.in);
number2 = input.nextInt(); // 输入第二个数
System.out.printf("number1=%d,number2=%d\n", number1, number2); // 输出这两个数
// 判断用户输入的两个数是否相等
if (number1 == number2) {
System.out.println("number1 和 number2 相等。");
}
// 判断用户输入的两个数据是否相等
if (number1 != number2) {
System.out.println("number1 和 number2 不相等。");
// 判断用户输入的数1是否大于数2
if (number1 > number2) {
System.out.println("number1 大于 number2。");
}
// 判断用户输入的数1是否小于数2
if (number1 < number2) {
System.out.println("number1 小于 number2。");
}
}
}
保存程序并运行,运行结果如下所示:
图 1 运行结果
在本程序中,使用 input.nextInt() 接收用户从键盘输入的两个数,然后通过关系运算符来比较这两个数之间的大小。这里用到了 if 语句,它是一个流程控制语句,将在后面的章节中详细讲解。
Java自增和自减运算符(++和–)
在对一个变量做加 1 或减 1 处理时,可以使用自增运算符 ++ 或自减运算 --。++ 或 – 是单目运算符,放在操作数的前面或后面都是允许的。++ 与 – 的作用是使变量的值增 1 或减 1。操作数必须是一个整型或浮点型变量。自增、自减运算的含义及其使用实例如表 1 所示。
运算符 | 含义 | 实例 | 结果 |
---|---|---|---|
i++ | 将 i 的值先使用再加 1 赋值给 i 变量本身 | int i=1; int j=i++; | i=2 j=1 |
++i | 将 i 的值先加 1 赋值给变量 i 本身后再使用 | int i=1; int j=++i; | i=2 j=2 |
i– | 将 i 的值先使用再减 1 赋值给变量 i 本身 | int i=1; int j=i–; | i=0 j=1 |
–i | 将 i 的值先减 1 后赋值给变量 i 本身再使用 | int i=1; int j=–i; | i=0 j=0 |
在使用自增/自减运算时应注意下面几个问题。
- 自增/自减只能作用于变量,不允许对常量、表达式或其他类型的变量进行操作。常见的错误是试图将自增或自减运算符用于非简单变量表达式中。
- 自增/自减运算可以用于整数类型 byte、short、int、long,浮点类型 float、double,以及字符串类型 char。
- 在 Java 1.5 以上版本中,自增/自减运算可以用于基本类型对应的包装器类 Byte、Short、Integer、Long、Float、Double 和 Character。
- 自增/自减运算结果的类型与被运算的变量类型相同。
例 1
编写一个程序,使用不同类型的数据结合自增和自减运算符进行运算,并输出变量的值。实现代码如下:
public static void main(String[] args) {
int x = 5, y; // 声明用于自增和自减的整型变量
char cx = 'B', cy; // 声明用于自增和自减的字符型变量
float fx = 5.5f, fy; // 声明用于自增和自减的浮点型变量
System.out.println("---------对整数的自增和自减---------");
y = x++;
System.out.printf("y=x++ 的结果为:%d ,%d \n", x, y);
y = x--;
System.out.printf("y=x-- 的结果为:%d ,%d \n", x, y);
y = ++x;
System.out.printf("y=++x 的结果为:%d ,%d \n", x, y);
y = --x;
System.out.printf("y=--x 的结果为:%d ,%d \n", x, y);
System.out.println("\n---------对浮点的自增和自减---------");
fy = fx++;
System.out.printf("fy=fx++ 的结果为:%f ,%f \n", fx, fy);
fy = fx--;
System.out.printf("fy=fx-- 的结果为:%f ,%f \n", fx, fy);
fy = ++fx;
System.out.printf("fy=++fx 的结果为:%f ,%f \n", fx, fy);
fy = --fx;
System.out.printf("fy=--fx 的结果为:%f ,%f \n", fx, fy);
System.out.println("\n---------对字符的自增和自减---------");
cy = cx++;
System.out.printf("cy=cx++ 的结果为:%c ,%c \n", cx, cy);
cy = cx--;
System.out.printf("cy=cx-- 的结果为:%c ,%c \n", cx, cy);
cy = ++cx;
System.out.printf("cy=++cx 的结果为:%c ,%c \n", cx, cy);
cy = --cx;
System.out.printf("cy=--cx 的结果为:%c ,%c \n", cx, cy);
}
保存代码并运行,输出的结果如下:
---------对整数的自增和自减---------
y=x++ 的结果为:6 ,5
y=x-- 的结果为:5 ,6
y=++x 的结果为:6 ,6
y=--x 的结果为:5 ,5
---------对浮点的自增和自减---------
fy=fx++ 的结果为:6.500000 ,5.500000
fy=fx-- 的结果为:5.500000 ,6.500000
fy=++fx 的结果为:6.500000 ,6.500000
fy=--fx 的结果为:5.500000 ,5.500000
---------对字符的自增和自减---------
cy=cx++ 的结果为:C ,B
cy=cx-- 的结果为:B ,C
cy=++cx 的结果为:C ,C
cy=--cx 的结果为:B ,B
从运行结果来看,无论是何种类型,只要是自增和自减运算符支持的类型,都可以参与运算。
Java位运算符:Java移位运算符、复合位赋值运算符及位逻辑运算符
Java 定义的位运算(bitwise operators)直接对整数类型的位进行操作,这些整数类型包括 long,int,short,char 和 byte。
位运算符主要用来对操作数二进制的位进行运算。按位运算表示按每个二进制位(bit)进行计算,其操作数和运算结果都是整型值。
Java 语言中的位运算符分为位逻辑运算符和位移运算符两类,下面详细介绍每类包含的运算符。
位逻辑运算符
位逻辑运算符包含 4 个:&(与)、|(或)、~(非)和 ^(异或)。除了 ~(即位取反)为单目运算符外,其余都为双目运算符。表 1 中列出了它们的基本用法。
运算符 | 含义 | 实例 | 结果 |
---|---|---|---|
& | 按位进行与运算(AND) | 4 & 5 | 4 |
| | 按位进行或运算(OR) | 4 | 5 | 5 |
^ | 按位进行异或运算(XOR) | 4 ^ 5 | 1 |
~ | 按位进行取反运算(NOT) | ~ 4 | -5 |
位与运算符
位与运算符为&
,其运算规则是:参与运算的数字,低位对齐,高位不足的补零,如果对应的二进制位同时为 1,那么计算结果才为 1,否则为 0。因此,任何数与 0 进行按位与运算,其结果都为 0。
例如下面的表达式:
100&0
图 1 所示为这个运算过程,结果为 0。
图 1 100位与0的运算过程
下面是两个非零的数字进行位与运算的过程。
int x = 5,y = 12; // 创建整型变量保存两个数
int z = x&y; // 对这两个数进行位与运算,结果保存到z
这两行语句执行后变量 Z 的值是 4,图 2 所示为这个运算过程。
图 2 5位与12的运算过程
位或运算符
位或运算符为|
,其运算规则是:参与运算的数字,低位对齐,高位不足的补零。如果对应的二进制位只要有一个为 1,那么结果就为 1;如果对应的二进制位都为 0,结果才为 0。
下面是一个使用位或运算符的表达式。
11|7
运算结果为 15,图 3 所示为其运算过程。
图 3 11位或7的运算过程
位异或运算符
位异或运算符为^
,其运算规则是:参与运算的数字,低位对齐,高位不足的补零,如果对应的二进制位相同(同时为 0 或同时为 1)时,结果为 0;如果对应的二进制位不相同,结果则为 1。
下面是一个使用位异或运算符的表达式。
11^7
运算结果为 12,图 4 所示为其运算过程。
图 4 11位异或7的运算过程
提示:在有的高级语言中,将运算符^
作为求幂运算符,要注意区分。
位取反运算符
位取反运算符为~
,其运算规则是:只对一个操作数进行运算,将操作数二进制中的 1 改为 0,0 改为 1。
下面是一个使用位取反运算符的表达式。
~10
运算结果为 65525,图 5 所示为其运算过程。
图 5 对10位取反的运算过程
我们可以使用如下的程序来检查这个运算结果。
int i = 10;
System.out.printf("%d \n",~i);
编译执行以上程序,会发现输出的结果是 -11,而不是 65525。这是因为取反之后的结果是十六进制数,而在上面的程序中使用 %d 将输出转换为了十进制数。
可以使用如下语句查看十六进制结果。
int i=10;
System.out.printf("%x \n",~i);
可以看到输出结果为 fff5,将它转换为二进制是 1111111111110101。这个二进制数的最高位为 1,表示这个数为负数。除最高位外,按位取反再加 1,即得到二进制原码 1000000000001011,用十进制数表示即为 -11。
注意:位运算符的操作数只能是整型或者字符型数据以及它们的变体,不用于 float、double 或者 long 等复杂的数据类型。
位移运算符
位移运算符用来将操作数向某个方向(向左或者右)移动指定的二进制位数。表 2 列出了 Java 语言中的两个位移运算符,它们都属于双目运算符。
运算符 | 含义 | 实例 | 结果 |
---|---|---|---|
» | 右移位运算符 | 8»1 | 4 |
« | 左移位运算符 | 9«2 | 36 |
左位移运算符
左移位运算符为«
,其运算规则是:按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。
例如,将整数 11 向左位移 1 位的过程如图 6 所示。
图 6 对11左移1位运算过程
从图 6 中可以看到,原来数的所有二进制位都向左移动 1 位。原来位于左边的最高位 0 被移出舍弃,再向尾部追加 0 补位。最终到的结果是 22,相当于原来数的 2 倍。
右位移运算符
右位移运算符为»
,其运算规则是:按二进制形式把所有的数字向右移动对应的位数,低位移出(舍弃),高位的空位补零。
例如,将整数 11 向右位移 1 位的过程如图 7 所示。
图 7 对11右移1位运算过程
从图 7 中可以看到,原来数的所有二进制位都向右移动 1 位。原来位于右边的最低位 1 被移出舍弃,再向最高位追加 0 补位。最终到的结果是 5,相当于原数整除 2 的结果。
复合位赋值运算符
所有的二进制位运算符都有一种将赋值与位运算组合在一起的简写形式。复合位赋值运算符由赋值运算符与位逻辑运算符和位移运算符组合而成。表 3 列出了组合后的复合位赋值运算符。
运算符 | 含义 | 实例 | 结果 |
---|---|---|---|
&= | 按位与赋值 | num1 &= num2 | 等价于 num 1=num 1 & num2 |
|= | 按位或赋值 | num1 |= num2 | 等价于 num 1=num 1 | num2 |
^= | 按位异或赋值 | num1 ^= num2 | 等价于 num 1=num 1 ^ num2 |
-= | 按位取反赋值 | num1 -= num2 | 等价于 num 1=num 1 - num2 |
«= | 按位左移赋值 | num1 «= num2 | 等价于 num 1=num 1 « num2 |
»= | 按位右移赋值 | num1 »= num2 | 等价于 num 1=num 1 » num2 |
下面的程序定义了几个 int 型变量,然后运用位赋值简写的形式将运算后的值赋给相应的变量:
int a = 1;
int b = 2;
int c = 3;
a &= 4;
a |= 4;
a ^= c;
a -= 6;
b >>= 1;
c <<= 1;
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
该程序的输出为:
a = 1
b = 1
c = 6
Java三目运算符(条件运算符? :)
Java 提供了一个特别的三元运算符(也叫三目运算符)经常用于取代某个类型的 if-then-else 语句。条件运算符的符号表示为“?:”,使用该运算符时需要有三个操作数,因此称其为三目运算符。使用条件运算符的一般语法结构为:
result = <expression> ? <statement1> : <statement3>;
其中,expression 是一个布尔表达式。当 expression 为真时,执行 statement1, 否则就执行 statement3。此三元运算符要求返回一个结果,因此要实现简单的二分支程序,即可使用该条件运算符。
下面是一个使用条件运算符的示例。
int x,y,z;
x = 6,y = 2;
z = x>y ? x-y : x+y;
在这里要计算 z 的值,首先要判断 x>y 表达的值,如果为 true,z 的值为 x-y;否则 z 的值为 x+y。很明显 x>y 表达式结果为 true,所以 z 的值为 4。
技巧:可以将条件运算符理解为 if-else 语句的简化形式,在使用较为简单的表达式时,使用该运算符能够简化程序代码,使程序更加易读。
在使用条件运算符时,还应该注意优先级问题,例如下面的表达式:
x>y ? x-=y : x+=y;
在编译时会出现语法错误,因为条件运算符优先于赋值运算符,上面的语句实际等价于:
(x>y ? x-=y : x)+=y;
而运算符“+=”是赋值运算符,该运算符要求左操作数应该是一个变量,因此出现错误。为了避免这类错误,可以使用括号“0”来加以区分。例如,下面是正确的表达式。
(x>y) ? (x-=y): (x+=y);
例 1
在程序中声明 3 个变量 x、y、z,并由用户从键盘输入 x 的值,然后使用条件运算符向变量 y 和变量 z 赋值。 实现代码如下:
public class Test9 {
public static void main(String[] args) {
int x, y, z; // 声明三个变量
System.out.print("请输入一个数:");
Scanner input = new Scanner(System.in);
x = input.nextInt(); // 由用户输入x的值
// 判断x的值是否大于5,如果是y=x,否则y=-x
y = x > 5 ? x : -x;
// 判断y的值是否大于x,如果是z=y,否则z=5
z = y > x ? y : 5;
System.out.printf("x=%d \n", x);
System.out.printf("y=%d \n", y);
System.out.printf("z=%d \n", z);
}
}
保存程序并运行,运行效果如图 1 和图 2 所示:
图 1 键盘输入58
图 2 键盘输入4
在该程序中,首先输入 x 的值为 58,然后判断 x 的值是否大于 5,显然条件是成立,则 y 的值为 x,即 y=58。接着判断 y 的值是否大于 x,因为 y 的值和 x 的值都为 58,所以该条件是不成立的,则 z=5。再次输入 x 的值为 4,然后判断 x 的值是否大于 5,不成立,则 y=-4;接着判断 y 的值是否大于 x,不成立,则 z=5。
Java运算符优先级
所有的数学运算都认为是从左向右运算的,Java 语言中大部分运算符也是从左向右结合的,只有单目运算符、赋值运算符和三目运算符例外,其中,单目运算符、赋值运算符和三目运算符是从右向左结合的,也就是从右向左运算。
乘法和加法是两个可结合的运算,也就是说,这两个运算符左右两边的操作数可以互换位置而不会影响结果。运算符有不同的优先级,所谓优先级就是在表达式运算中的运算顺序。
一般而言,单目运算符优先级较高,赋值运算符优先级较低。算术运算符优先级较高,关系和逻辑运算符优先级较低。多数运算符具有左结合性,单目运算符、三目运算符、赋值运算符具有右结合性。
Java 语言中运算符的优先级共分为 14 级,其中 1 级最高,14 级最低。在同一个表达式中运算符优先级高的先执行。表 1 列出了所有的运算符的优先级以及结合性。
优先级 | 运算符 | 结合性 |
---|---|---|
1 | ()、[]、{} | 从左向右 |
2 | !、+、-、~、++、– | 从右向左 |
3 | *、/、% | 从左向右 |
4 | +、- | 从左向右 |
5 | «、»、>>> | 从左向右 |
6 | <、<=、>、>=、instanceof | 从左向右 |
7 | ==、!= | 从左向右 |
8 | & | 从左向右 |
9 | ^ | 从左向右 |
10 | | | 从左向右 |
11 | && | 从左向右 |
12 | || | 从左向右 |
13 | ?: | 从右向左 |
14 | =、+=、-=、*=、/=、&=、|=、^=、~=、«=、»=、>>>= | 从右向左 |
使用优先级为 1 的小括号可以改变其他运算符的优先级,即如果需要将具有较低优先级的运算符先运算,则可以使用小括号将该运算符和操作符括起来。例如下面的表达式:
(x-y)*z/5
在这个表达式中先进行括号内的减法运算,再将结果与 z 相乘,最后将积除以 5 得出结果。整个表达式的顺序按照从左向右执行,比较容易理解。
再来看一个复杂的表达式,如下所示。
--y || ++x && ++z;
这个表达式中包含了算术运算符和逻辑运算符。根据表 1 中列出的优先级,可以确定它的执行顺序如下:
① 先计算 y 的自减运算符,即 --y。
② 再计算 x 的自增运算符,即 ++x。
③ 接着计算 z 的自增运算符,即 ++z。
④ 由于逻辑与比逻辑或的优先级高,这里将 ② 和 ③ 的结果进行逻辑与运算,即 ++x && ++z。
⑤ 最后将 ④ 的结果与 ① 进行逻辑或运算,即 --y||++x&&++z。
如果没有上述对该表达式执行顺序的说明,第一眼看到它时将很难识别优先级。对于这类问题,可以通过添加小括号使表达的顺序更加清晰,而不用去查优先级表。如下所示为改进后的表达式。
(--y)||((++x)&&(++z));
技巧:记住这么多运算符的优先级是比较困难的,因此读者应该在实际应用中多多练习。
因为 Java 运算符存在这种优先级的关系,因此在做 SCJP 的时候或者某些公司的面试题,有如下 Java 代码:
int a = 5;
int b = 4;
int c = a+± --b*++a/b-- >>2%a–;
问 c 的值是多少?这样的语句实在太恐怖了,即使多年的老程序员看到这样的语句也会眩晕。这样的代码只能在考试中出现,作为一个程序员如果写这样的代码,恐怕他马上就得走人了,因为他完全不懂程序开发。
源代码就是一份文档,源代码的可读性比代码运行效率更重要。 因此在这里要提醒大家:
- 不要把一个表达式写得过于复杂,如果一个表达式过于复杂,则把它分成几步来完成。
- 不要过多地依赖运算符的优先级来控制表达式的执行顺序,这样可读性太差,尽量使用
()
来控制表达式的执行顺序。
Java直接量(字面量)
直接量是指在程序中通过源代码直接给出的值,例如在int a = 5;
代码中,为变量 a 所分配的初始值 5 就是一个直接量。
直接量的类型
并不是所有的数据类型都可以指定直接量,能指定直接量的通常只有三种类型:基本类型、字符串类型和 null 类型。具体而言,Java 支持如下 8 种类型的直接量。
1)int 类型的直接量
在程序中直接给出的整型数值,可分为二进制、十进制、八进制和十六进制 4 种,其中二进制需要以 0B 或 0b 开头,八进制需要以 0 开头,十六进制需要以 0x 或 0X 开头。例如 123、012(对应十进制的 10)、0x12(对应十进制的 18)等。
2)long 类型的直接量
在整型数值后添加 l 或 L 后就变成了 long 类型的直接量。例如 3L、0x12L(对应十进制的 18L)。
3)float 类型的直接量
在一个浮点数后添加 f 或 F 就变成了 float 类型的直接量,这个浮点数可以是标准小数形式,也可以是科学计数法形式。例如 5.34F、3.14E5f。
4)double 类型的直接量
直接给出一个标准小数形式或者科学计数法形式的浮点数就是 double 类型的直接量。例如 5.34、3.14E5。
5)boolean 类型的直接量
这个类型的直接量只有 true 和 false。
6)char 类型的直接量
char 类型的直接量有三种形式,分别是用单引号括起来的字符、转义字符和 Unicode 值表示的字符。例如‘a’,‘\n’和‘\u0061’。
7)String 类型的直接量
一个用双引号括起来的字符序列就是 String 类型的直接量。
在大多数其他语言中,包括 C/C++,字符串作为字符的数组被实现。然而,在 Java 中并非如此。在 Java 中,字符串实际上是对象类型。在教程后面你将看到,因为 Java 对字符串是作为对象实现的,因此,它有广泛的字符串处理能力,而且功能既强又好用。
8)null 类型的直接量
这个类型的直接量只有一个值,即 null。
在上面的 8 种类型的直接量中,null 类型是一种特殊类型,它只有一个值:null。而且这个直接量可以赋给任何引用类型的变量,用以表示这个引用类型变量中保存的地址为空,即还未指向任何有效对象。
直接量的赋值
通常总是把一个直接量赋值给对应类型的变量,例如下面代码都是合法的。
int a = 5;
char c = ‘a’;
boolean b = true;
float f = 5.12f;
double d = 4.12;
String name = “C语言中文网”;
String url = “http://c.biancheng.net”;
除此之外,Java 还支持数值之间的自动类型转换,因此允许把一个数值直接量直接赋给另一种类型的变量,这种赋值必须是系统所支持的自动类型转换,例如把 int 类型的直接量赋给一个 long 类型的变量。
String 类型的直接量不能赋给其他类型的变量,null 类型的直接量可以直接赋给任何引用类型的变量,包括 String 类型。boolean 类型的直接量只能赋给 boolean 类型的变量,不能赋给其他任何类型的变量。
关于字符串直接量有一点需要指出,当程序第一次使用某个字符串直接量时,Java 会使用常量池(constant pool)来缓存该字符串直接量,如果程序后面的部分需要用到该字符串直接量时,Java 会直接使用常量池(constantpool)中的字符串直接量。
提示:
- 由于 String 类是一个典型的不可变类,因此 String 对象创建出来的就不可能改变,因此无需担心共享 String 对象会导致混乱。
- 常量池(constant pool)指的是在编译期被确定,并被保存在已编译的 .class 文件中的一些数据,它包括关于类、方法、接口中的常量,也包括字符串直接量。
看如下程序:
String s0 = "hello";
String s1 = "hello";
String s2 = "he" + "llo";
System.out.println(s0 == s1);
System.out.println(s0 == s2);
运行结果为:
true
true
Java 会确保每个字符串常量只有一个,不会产生多个副本。例子中的 s0 和 s1 中的“hello”都是字符串常量,它们在编译期就被确定了,所以 s0 = s1
返回 true。而“he”和“llo”也都是字符串常量,当一个字符串由多个字符串常量连接而成时,它本身也是字符串常量,s2 同样在编译期就被解析为一个字符串常量,所以 s2 也是常量池中“hello”的引用。因此,程序输出 s0 == s1
返回 true,s1 == s2
也返回 true。