java编程基础知识讲解(JAVA编程思想.姊妹篇.JAVA编程讲义.第2章)
上一章在简单了解Java语言的基础上搭建了Java开发环境,并安装了Java开发最流行的IDE。从本章起,我们就正在进入Java基础知识的学习。就像人与人之间交流使用的语言需要遵循一定的语法规则一样,Java语言也离不开特定语法的支持,如基本语法、数据类型、变量、常量、运算符与表达式、类型转换和输入输出等,只不过这些语法要比日常生活中语言的语法更加严谨。本章我们先来学习这些基本语法,为后续学习打下扎实的根基。
2.1 Java基本语法
作为一门流行的编程语言,Java有着它自己特定的语法格式,要想使用Java语言开发出一款功能完善的软件产品,就必须熟练掌握这些基本语法。当然,这些基本语法都是简单易懂的,包括基本格式、关键字、标识符、注释、编程风格等。
2.1.1 Java语言的基本格式学习任意一门编程语言,首先都要掌握其基本格式,Java语言的基本格式如下:
- 语句:语句是程序执行的基本单位。Java程序由两种语句组成,一种是结构定义语句,用于声明一个类或者方法;另一种是功能执行语句,用于实现具体的功能,以分号(;)结束。
- 语句块:一对大括号{}包含的一系列语句称为语句块(代码块),语句块可以嵌套,即语句块内可以嵌套子语句块。
- 空格:在Java程序中,为了增加程序的可读性,允许在程序元素之间增加任意数量的空格,编译器会自动忽略多余的空格。在Java程序中,回车键及换行符都可以表示一行的结束,可以被看作是空格,空格键、Tab键也是空格。
- 区分大小写:Java语言严格区分大小写。例如,在Java程序中,age和Age是意义完全不同的两个符号。
- 换行:当一个表达式无法容纳在一行内时,可以依据如下一般规则断开:在一个逗号后面断开;在一个操作符前面断开;宁可选择较高级别(higher-level)的断开,而非较低级别(lower-level)的断开;新的一行应该与上一行同一级别表达式的开头处对齐;如果以上规则导致代码混乱或者使代码都堆挤在右边,那就代之以缩进8个空格。
- 其他:Java程序中,一句连续的字符串不能分开在两行中书写,例如下面的代码是错误的:
System.out.println("hello
Java编程讲义");
为了便于阅读,需要将一个比较长的字符串分在两行中书写,可以先将一个长字符串分成两个字符串,再用加号( )连接起来,在( )处断行,例如:
System.out.println("hello"
"Java编程讲义");
2.1.2 Java关键字关键字也称保留字,是编程语言里事先定义好并赋予特殊含义的英文单词,在程序设计中不能再将它定义成别的用途。在开发工具中(IDEA)中,关键字会以特殊颜色(默认蓝色)显示,用于提示其为关键字,避免被误用。Java中保留了许多关键字,这些关键字的所有字母都是小写的,具体如表2.1所示。
表2.1 Java关键字
abstract |
assert |
boolean |
break |
byte |
case |
catch |
char |
class |
const |
continue |
default |
do |
double |
else |
enum |
extends |
final |
finally |
float |
for |
goto |
if |
implements |
import |
instanceof |
int |
interface |
long |
native |
new |
package |
private |
protected |
public |
return |
strictfp |
short |
static |
super |
switch |
synchronized |
this |
throw |
throws |
transient |
try |
void |
volatile |
while |
在上面列举的关键字中,每个关键字都有他自己特殊的作用。例如,package关键字用于包的声明,import关键字用于引入包,class关键字用于类的声明。在本书后面的章节中,将会逐步对使用到的关键字进行讲解,所以大家不需要现在就将所有的关键字全部记住,只需要了解即可。
2.1.3 Java标识符在编程过程中,我们经常需要在程序中定义一些符号来标记一些名称,比如编程中用到的变量名、包名、类名以及方法名、参数名等,这些符号被称为标识符。在Java语言中,标识符可以由编程人员自由指定,但是需要遵循如下规定:
- 标识符可以由任意顺序的大小写字母、数字、下划线和美元符号($)组成。
- 标识符不能以数字开头。
- 标识符不能是Java中的关键字。
- 标识符区分大小写,且长度没有限制。
在Java程序中,定义的标识符必须严格遵守上面列出的规范,否则程序无法完成编译。下面的这些标识符都是合法的:
Test
Demo123
aaa_zhang
userName
$Demo
下面的这些标识符都是不合法的:
123Demo // 不能以数字开头
package // 不能是关键字
Hello year // 不能包含空格特殊字符
在实际使用标识符时,应该使标识符能够在一定程度上反映它所表示的变量、常量、对象或类的含义,达到“见名知意”的效果,这样程序的可读性更好。
注意:Java的标识符可以使用中文,但是习惯上以英文为主;标识符内可以包含关键字,但不能与关键字完全一样,例如,“thisTea”是一个合法的标识符,但“this”是关键字,不能用作标识符。
2.1.4 Java注释真正开发一个应用程序,大多情况下都是团队合作,所以在编写程序时,为了使代码更易于阅读,通常会在实现功能的同时为代码加一些注释。注释是对程序的某个功能模块或者某行代码的解释说明,以便其他人能轻松地阅读代码,了解其意图。Java程序同样需要添加必要的注释,以增强可读性,这些注释只Java源文件中有效,在编译程序时,编译器会自动忽略这些注释信息,不会将其编译到class字节码文件中去。另外,注释还可以屏蔽一些暂时不用的语句,等需要时直接将此语句的注释取消即可。例如,在调试代码的时候彻底删除代码可能会误删,造成程序彻底瘫痪,这时候使用注释就显得异常轻松了。根据功能的不同,Java语言提供了如下3种注释方式:
- 单行注释:用于对程序的某一行代码进行解释。在注释内容前面加双斜杠“//”,Java编译器会忽略掉这一行双斜杠以后的信息,并且不会对其他代码造成影响,使用比较灵活。单行注释一般用来对声明的变量、一行程序作用进行简要说明。具体示例如下:
String tea_year = nul1; // 定义一个String字符串,并为其赋值
- 多行注释:用于注释内容有多行的情况。在注释内容前面以单斜杠加一个星号“/*”开头,并在注释内容末尾以一个星号加单斜杠“*/”结束,常用于注释掉暂时不用的代码、说明方法的功能等。具体示例如下:
public class JavaaMultiline{ // 定义一个类
int age; // 定义一个变量
/**
* 主方法,程序的入口地址
*/
public static void main(String[]args){
System.out.println("多行注释");
}
}
- 文档注释:用于对程序的结构、方法和属性等进行说明,以单斜杠加两个星号“/**”开头,并以一个星号加单斜杠“*/”结束。在实际开发中,开发人员可以使用JavaDoc命令将文档注释内容提取生成正式的HTML格式的帮助文档。对于初学者而言,文档注释并不是很重要,了解即可。文档的具体格式如下:
/**
*作者:张晨光
*公司:AAA软件教育
*功能:文档注释讲解
*/
public class JavaDoc{ // 定义一个类
int age; // 定义一个变量
/**
* 主方法,程序的入口地址
*/
public static void main(String[]args){
System.out.println("文档注释");
}
}
知识点拨:javadoc是API文档生成器,该工具解析一组Java源文件中的声明与文档注释,生成一组HTML页面,描述这些源程序中定义的类、内部类、接口、构造方法、成员变量等,JDK的API文档就是javadoc工具生成的。
2.1.5 Java编程风格编程风格是编程的规范,即程序开发者一般约定的一些编程规则、格式等。在日常开发过程中,一些比较大的项目通常都是由很多人合作完成的,所以遵守一门语言的编程风格至关重要,否则如果大家都按自己的喜好来进行编码,会导致代码阅读性大大降低,在后期维护的时候会非常不方便。例如,有的程序员可能会养成不换行的习惯,一串代码整行排列,除了本人,其他人是很难去阅读他的代码的。目前,Java程序的编写流行着两种代码块的写法,分别为Allmans风格和Kernighan风格。
1.Allmans风格
Allmans风格又称为独行风格,大括号左右两边都独占一行,大括号和具体的代码分隔开,在代码量少的时候,代码布局清晰,可读性强。Allmans风格的代码如下:
Public Class Allmans
{
public static void main(String[] args)
{
System.out.println("我是独行风格");
}
}
在代码量少的时候适合使用Allmans风格编码,但是如果代码量多的话,会导致代码左边出现很多大括号,反而不利于阅读。
2.Kernighan风格
Kernignan风格又称为“行尾”风格,左边的大括号在上一行代码的行尾,右边的大括号则独占一行,这样既能将大括号和代码分隔开,又不至于使代码看着过于冗余。当代码量较多时使用“行尾”风格,可使代码层次更加简洁清晰。Kernighan风格的代码如下:
Public Class Kernignan{
public static void main(String[] args){
System.out.println("我是行尾风格");
}
}
2.2 Java基本数据类型在程序执行过程中,数据是程序必不可少的一部分,也是程序处理的对象。不同的数据有不同的数据类型。通常将数据按照性质进行分类,每一类称为一种数据类型。也就是说,数据类型定义了数据的性质、取值范围、存储方式和对数据能够进行的运算和操作。强类型语言是一种强制类型定义的语言,一旦某一个变量被定义类型,如果不经过强制转换,则它永远就是该数据类型了,强类型语言包括Java、.C 、C 等语言。Java是一种强类型语言,数据类型可以分为两大类,一类是基本数据类型,另一类是引用数据类型(简称引用类型)。基本数据类型的数据在内存中存放的是数据值本身,每种基本数据类型的数据所占用内存的大小是固定的,与软硬件环境无关。引用数据类型的知识将在第2.6.1节详细讲解。
Java基本数据类型分为4类,分别是整数类型、浮点类型、字符型和布尔类型,其中有4种整数类型、2种浮点类型、1种字符类型和1种布尔类型,一共8种类型。它们的分类及对应关键字如下:
- 整数类型:byte、short、int、long。
- 浮点类型:float、double。
- 字符类型:char。
- 布尔类型:boolean。
当数据不带小数点或不是分数时,该数据可以声明为整数类型。Java语言中,所有整数类型的数据均带有符号,分为正数和负数,整数数值的最高位表示符号,其中0表示正数,1表示负数,其余位表示值。Java语言的每种数据类型都对应一个默认值,使这种数据类型变量的取值总是确定的,所有整数类型的变量的默认值都是0。
整数类型可分为byte、short、int和long 4种,其长度、取值范围如表2.2所示。由于char(字符)类型可以转换为int类型,所以表2.2将这4种类型和char类型的长度与取值范围一起列出。
表2.2 整数类型长度与取值范围
数据类型 |
长度 |
取值范围 |
byte |
8位 |
-27~27-1,即-128~127 |
short |
16位 |
-215~215-1,即-32768~32767 |
int |
32位 |
-231~231-1,即-2147483648~2147483647 |
long |
64位 |
-263~263-1,即-9223372036854775808~9223372036854775807 |
char |
16位 |
'\u0000'~'\uffff',即0~65535 |
在计算机中,整数类型常量可以使用十进制、八进制和十六进制3种形式表示。以1~0开头的数为十进制数,0除外,如1、2、889、0、9898等;以0开头的数为八进制数,如077、011等;以0x或0X开头的数为十六进制数,如0xBAB、0x3a、0xBFAL等(十六进制里面A、B、C、D、E、F大小写均可)。
注意:当一个整数定义为long型时,需要在数值后面加L或l,因为小写的L看起来和数字1很像,所以建议使用大写L。
2.2.2 浮点类型浮点类型用来存储小数,当需要进行涉及小数的计算或精确度比较高的计算时,就需要使用浮点类型。在Java语言中,将小数又分为float单精度浮点数和double双精度浮点数两种类型,单精度浮点数以f或F结尾,双精度浮点数以d或D结尾,不加后缀会默认为double双精度浮点数。如果考虑到要节省运行时的资源,并且运算时对于数据取值范围不大,同时对运算精度的要求也不太高,可以使用单精度类型。Java使用如下两种方式表示浮点数:
- 标准计数法:由整数部分、小数点和小数部分组成,如2.8、58.98等。
- 科学计数法:由整数部分、小数点和指数部分组成,其中指数部分由字母E或e加上带正负符号的整数表示,如805.18可以表示为8.0518E 2。
在java中,float是单精度,32位.浮点数,默认是0.0f;double是双精度,64位,浮点数,默认是0.0d。 在内存中存储为float类型符号位1bit、指数8bit、尾数23bit;double类型符号位1bit、指数11bit、尾数52bit。
浮点类型的长度和取值范围如表2.3所示。
表2.3 浮点类型长度与取值范围
数据类型 |
长度 |
取值范围 |
float |
32位 |
1.4E-45~3.4028235E 38 |
double |
64位 |
4.9E-324~1.7976931348623157E 308 |
字符类型用来存储单个字符,用char表示。Java语言中的字符采用Unicode字符集编码格式,在内存中占用两个字节,共16位,属于无符号整数,一共有65536个,字符的取值范围从0到65535,表示其在Unicode字符集中的排序位置。字符类型有如下3种形式:
- 用单引号括起来的字符。例如,'Z'、'G'、'7'等都是合法的字符串常量,哪怕是整数,被单引号包起来以后也是字符常量。
- 转义字符。Java语言中的一些特殊字符,称为转义字符。如'\b'表示退格、'\n'表示换行、'\t'表示制表符,调到下一个TAB位置。
- 用Unicode值表示的字符。格式是'\uXXXX',其中XXXX代表一个十六进制的整数,如'\u08B3'。
知识点拨:Unicode标准字符集表最多可以识别65536个字符,在其中前128个字符刚好是ASCII码字符。由于Java语言的字符类型采取了Unicode这种国际标准编码格式,所以使Java语言能够极为方便地处理各种语言。例如,可以将汉字作为字符类型变量的值,这为程序的国际化提供了方便。
2.2.4 布尔类型在程序中表示判断条件,改变程序执行的流程,可以使用布尔类型。布尔类型也称为逻辑类型,用boolean表示,只有true和false两种取值。其中,true表示逻辑“真”,false表示逻辑“假”。所有关系运算(如a>c)的返回值都是布尔类型的值,可以用于if、while、for等控制语句,由运算结果所传回的布尔值来决定程序的执行流程。
注意:与其他高级语言不同,Java中的布尔值和数字之间不能来回转换,即false和true不对应于0或非0的整数值。
2.3 Java中的变量与常量在计算机程序中,数据存储在内存中的一块区域内,要获取该数据,就必须知道其在内存区域中的位置。为了方便使用,程序设计语言使用变量名来表示该数据存储区域的位置,一个变量代表一块内存区域,数据就存储在这个内存区域中,使用变量名获取数据非常方便。如果从程序开始到结束,变量的值保持不变,则视为常量。在程序开发过程,可以根据自身需求,选择使用变量还是常量。
2.3.1 变量及其声明在Java语言中,变量是内存中的一个存储区域,该区域有自己的名称(变量名)和类型(数据类型)和值,该区域的数据可以在同一类型范围内不断变化。也就是说,Java 中的变量有四个基本属性:变量名,数据类型,存储单元和变量值。变量在使用之前,需要先声明(或称定义),声明的目的是给变量指定数据类型和名称,方便程序在编译时告知编译程序在内存中要占据多少个存储单元来存放该变量的内容。在声明变量时应选择合适的数据类型,长度太小时无法容纳数据,长度太大时会占用太多内存区域。
变量名称的格式需要严格遵守标识符的命名规则。Java声明变量的语法格式如下:
数据类型 变量名1[,变量名2][,变量名3]…;
在变量声明的时候,变量名的长度没有限制,需要是合法的标识符,[]中的内容是可选项。例如,“int salary;”表示声明了salary是int数据类型的变量,声明之后,系统将会给变量分配内存区域,每一个被声明的变量都有一个内存的地址。当有多个变量属于同一个类型时,各个变量可以在同一行定义,它们之间使用逗号隔开。
当一个变量没有赋初值或需要重新对变量赋值时,就需要使用赋值语句。Java中的赋值语句格式如下:
变量名 = 值;
例如,针对salary进行赋值,赋值语句如下:
salary = 8899; // 给salary变量赋值为8899
在声明变量的同时也可以对变量进行初始化赋值。例如,“double height=1.88;”表示声明的height是double类型的变量,且height的值是1.88,该语句也可以分成两行语句来书写:
double height; // 定义了一个double类型的变量height
height = 1.88; // 给变量height赋值为1.88
注意:变量必须遵循“先声明、后使用”的原则,即在第一次使用一个变量之前就必须声明其属于哪一种数据类型。
2.3.2 常量及其声明常量是指在程序中不能被改变的数据,一般是不变的数字或字符串等,如圆周率、固定日期、税率、数理化常数等。在程序运行过程中,变量会随着程序运行而改变,但是常量一旦声明,在整个程序过程中会保持声明时的值不变。按照数据类型的不同,常量可以分为整型常量、浮点型常量、布尔型常量、字符常量等。常量声明后,在程序其他位置不能修改常量值。常量使用关键字final(final关键字在后续第6.4章节会继续深入讲解)进行声明,常量名使用全部大写字母,语法示例如下:
final 数据类型 常量名1 = 值1[,常量名2 = 值2][,常量名3 = 值3]…;
例如,声明圆周率的常量PI,值为3.1415926,其代码如下:
final double PI = 3.1415926; // PI常量表示圆周率,double类型
程序中使用常量的好处:首先,可以增加程序的可读性,从常量名可以知道常量的含义;其次,增强程序的可维护性,当程序中多次使用常量时,只需要修改一处即可。
2.3.3 var变量及其声明在JDK10中引入了局部类型变量推断,也就是var关键字,之前var关键字更多地使用在JavaScript中,并不需要指定变量的数据类型。在使用var关键字来声明变量时,不需指定该变量的类型,编译器能根据右边的表达式来自动判断类型,这样可以减少代码的冗余,更便于阅读。
在引入var关键字之前,变量声明方式如下:
String name = "孙悟空";
int age = 500;
在引入var关键字以后,无需在表达式左边指定变量类型,采用“var 变量名 = 值”格式即可,具体示例如下:
var name = "齐天大圣";
var age = 500;
注意:使用var声明变量时候,必须同时赋初值,不能拆成两行语句;var只能声明局部变量,不能用于声明方法的返回值类型、类的成员变量。
2.3.4 变量作用域变量需要先定义再使用,但不是说在变量定义之后的任意地方都可以使用该变量,变量需要在它的有效范围内才可以被使用,这个有效范围称为变量的作用域。
变量的作用域可以分为局部变量作用域、成员变量作用域。在类体内定义的变量称为成员变量,它的作用域是整个类,也就是说在这个类中都可以访问到定义的这个成员变量;在一个方法或方法内代码块中定义的变量称为局部变量,局部变量的作用域为定义其的方法体或代码块。本节重点对局部变量作用域进行举例讲解,成员变量作用域在第5.2.7节继续讲解。
接下来,通过案例来分析局部变量的作用域,如例2-1所示。
例2-1 Demo0201.java
- public class Demo0201 {
- public static void main(String[] args) {
- int num = 372;
- {
- double height = 1.80;
- }
- System.out.println("num=" num);
- System.out.println("外部的height=" height);
- }
- }
程序编译报错,提示“Cannot resolve symbol 'height'”,中文含义为“不能识别标识符'height'”,出现错误的原因在于第8行的变量height超出了作用域。将第8行代码放置在第5行代码之后,且使之与第5行代码位于同一代码块中,再次编译程序不再报错,程序的运行结果如下:
height=1.8
num=372
通过例2-1可以看出,局部变量num的作用域为整个main()方法,而局部变量height的作用域为其所在的代码块。事实上,当方法或代码块被调用时,虚拟机为其内部的局部变量分配内存,当调用结束后,则释放局部变量占用的内存空间,同时销毁局部变量。
2.4 基本数据类型的转换在Java语言中,数据类型在定义时就已经决定,但是允许用户有限度地将数据从一种类型转换为另外一个种类型,简称类型转换。类型转换分为自动类型转换和强制类型转换两种。
2.4.1 自动类型转换自动类型转换也称隐式类型转换,指不需要额外书写代码,由系统根据一定条件自动完成的类型转换。自动类型转换需要满足两个条件:
- 转换前后的数据类型必须兼容。例如,int型与long型都是整型数据,所以彼此兼容;布尔型不能与整型进行自动类型转换,二者是不兼容的。
- 转换后的数据类型范围比转换前的大。就像两个不同的箱子,我们可以把小箱子放进大箱子里,但是不可以把大箱子放进小箱子里。
事实上,自动类型转换只有在将取值范围小的变量直接赋值给取值范围大的变量的时候,即将占用内存小的数据类型转换为占用内存大的数据类型的时候,才可以使用。Java支持自动类型转换的类型,如图2.1所示。
图2.1 自动数据类型转换图
自动类型转换的具体示例如下:
byte b = 98; // 声明byte型变量值为97
int a = b; // 正确,byte取值范围比int小,可以直接转换
2.4.2 强制类型转换强制类型转换也称为显式转换,适用于把取值范围大的数据类型转换为取值范围小的数据类型,如int类型的值赋值给short类型的变量。强制类型转换的语法格式如下:
(指定转换的目标数据类型)需要转换的变量/数值;
经过强制类型转换,将得到括号中的目标数据类型的数据,该数据是从指定变量名或数值中转换而来的,但不影响原来的变量名或数值。
接下来,通过案例来演示强制类型转换的使用,如例2-2所示。
例2-2 Demo0202.java
- public class Demo0202 {
- public static void main(String[] args) {
- double d = 800.104;
- int i = (int)d;
- System.out.println("i=" i);
- }
- }
程序的运行结果如下:
i=800
例2-2中,原来变量d的值是800.104,经过强制类型转换之后,精度丢失,因为浮点类型强制转换为整型,采用的是“去1法”,即无条件地舍弃小数位数字,不会进行四舍五入运算,所以该案例最后结果是800。
知识点拨:类型转换只限于该行语句,并不会影响原先变量的类型,不会影响原来数据的精度。
2.5 运算符运算符是用来表示某一种运算的符号,它指明了对操作数所进行的运算。操作数是指运算的对象,可以是变量、常量或表达式。将操作数和运算符结合起来的计算式称为表达式。Java语言涉及多种运算符,这些运算符按照操作数的数目可以分为3种:一元运算符、二元运算符、三元运算符;按照运算的性质则可分为7种:算术运算符、关系运算符、逻辑运算符、赋值运算符、位运算符、条件运算符、其他运算符(下标运算符[]、实例运算符instanceof、分量运算符->等,在后续章节进行讲解(第4.1.1节、第6.5.4节、第10.2.1节分别讲解)。
2.5.1 算术运算符算术运算符是用来执行一般的数学运算的符号,这类运算符是最基本、最常见的,作用于整数类型、浮点类型数据,用来完成相应的算术运算。Java语言的算术运算符可以分为一元运算符和二元运算符。一元运算符只有一个操作数参与运算,二元运算符则有两个操作数参与运算。一元算术运算符有4种,如表2.4所示。
表2.4 一元算术运算符
运算符 |
运算 |
用法 |
算术混合运算的精度 |
|
正号 |
a |
算术混合运算的精度 |
- |
负号 |
-a; |
算术混合运算的精度 |
|
加1 |
b = a |
算术混合运算的精度 |
b = a |
算术混合运算的精度 | ||
-- |
减1 |
b = a-- |
算术混合运算的精度 |
b = --a |
算术混合运算的精度 |
需要注意的是,针对自增运算符和自减运算符而言,写在变量后面的为后置式,写在变量前面的为前置式。二者的区别在于:后置式是先将值赋值给接受的其它变量,然后再加1(减1),前置式是先加1(减1),然后再将变量的值赋值给其它变量。
接下来,通过案例来演示一元算术运算符的使用,如例2-3所示。
例2-3 Demo0203.java
- public class Demo0203 {
- public static void main(String[] args) {
- System.out.println("------------正号/负号--------------");
- short a = 22;
- //short b = a; // 正号起类型提升作用
- int d = a;
- int e = -d; // 负号取反
- System.out.println("d=" d);
- System.out.println("e=" e);
- System.out.println("-----------自增自建运算符-----------");
- int i = 11; // 声明i为整数类型,初始值为11
- System.out.println("i=" i);
- int j = i ; // 自增运算符为后置式
- System.out.println("j=i i=" i ", j=" j);
- j = i; // 自增运算符为前置式
- System.out.println("j=i i=" i ", j=" j);
- j = i--; // 自减运算符为后置式
- System.out.println("j=i-- i=" i ", j=" j);
- j = --i; // 自减运算符为前置式
- System.out.println("j=i-- i=" i ", j=" j);
- }
- }
程序的运行结果如下:
------------正号/负号--------------
d=22
e=-22
-----------自增自建运算符-----------
i=11
j=i i=12, j=11
j=i i=13, j=13
j=i-- i=12, j=13
j=i-- i=11, j=11
例2-3中,如果不将第5行代码注释掉,则会编译报错,原因是在byte类型的变量a前面加了“ ”号,表示将其类型提升为了int类型,所以报错。但是,第6行使用int类型的变量d来接收,则不会报错。第13行代码中,因为i 的自增运算符为后置式,所以会先将i变量11赋值给变量j,然后i变量加1,执行后结果为i=12,j=10。第15行代码中,因为 i的自增运算符为前置式,所以会先将i变量先加1,然后再赋值给j变量,执行后结果为i=13,j=13。第17行代码中,因为i--的自减运算符为后置式,所以会先将i变量13赋值给变量j,然后i变量减1,执行后结果为i=12,j=13。第19行代码中,因为--i的自减运算符为前置式,所以会先将i变量减1,再赋值给j变量,执行后结果为i=11,j=11。
Java中的二元算术运算符有5种,如表2.5所示。
表2.5 二元算术运算符
运算符 |
运算 |
用法 |
功能描述 |
|
加 |
a b |
求a加b的和 |
- |
减 |
a - b |
求a减b的差 |
* |
乘 |
a * b |
求a乘以b的积 |
/ |
除 |
a / b |
求a除以b的商 |
% |
取模 |
a % b |
求a除以b的余数 |
对于上述二元算术运算符,需要特别注意以下几点:
- 对于除法运算符“/”,若两个整数之间做除法,则计算结果也是整型,除数不能为0;若两个操作数只要有一个是浮点数,则计算结果也是浮点数。
- 取模运算符“%”也称为求余运算符,其操作数可以是浮点数。只有单精度操作数的浮点表达式按照单精度运算求值,结果是单精度;如果包含一个或一个以上的双精度操作数,则按双精度运算,结果是双精度。运算结果的正负取决于被取模数(被除数)的符号,与模数(除数)的符号无关。
- 加法运算符“ ”可用于字符串连接符。Java语言针对“ ”号运算符进行了扩展,使它可以进行字符串的拼接,如“hi” “boy”,得到就是字符串“hiboy”。
接下来,通过案例来演示二元算术运算符的使用,如例2-4所示。
例2-4 Demo0204.java
- public class Demo0204 {
- public static void main(String[] args) {
- int a = 66,b = 9;
- System.out.println("--------算术运算符和算术表达式案例--------");
- System.out.println(a " " b "=" (a b));
- System.out.println(a "-" b "=" (a - b));
- System.out.println(a "*" b "=" (a * b));
- System.out.println(a "/" b "=" (a / b));
- System.out.println(a "%" b "=" (a % b));
- }
- }
程序的运行结果如下:
--------算术运算符和算术表达式案例--------
66 9=75
66-9=57
66*9=594
66/9=7
66%9=3
例2-4中使用算术运算符 、-、*、/、%进行了算术计算,优先计算小括号内的表达式。第8行代码中,因为两个变量都为整数类型,且66 / 9无法整除,所以会将小数点以后的位数省略,结果为7;如果要保留小数点以后的位数,则必须声明变量a和b为浮点数据类型。
2.5.2 算术混合运算的精度在Java程序开发中,经常会遇到混合运算。在混合运算中,常常会出现精度丢失现象,主要表现如下:
- 第一种情况是从大的数据类型向小的数据类型转化的过程中,可能会出现精度丢失,具体情况已经在第2.4.2节讲过。
- 第二种情况是浮点数在进行混合运算的过程可能会有精度丢失的情况。
接下来,通过案例来演示算术混合运算精度丢失的情况,如例2-5所示。
例2-5 Demo0205.java
- public class Demo0205 {
- public static void main(String[] args) {
- char c1 = 'a';
- int num1 = c1;
- System.out.println("num1=" num1);
- System.out.println("0.05 0.1 1=" (0.05 0.1 1));
- System.out.println("1.0-0.42 1=" (1.0 - 0.42 1));
- System.out.println("0.05 0.1=" (0.05 0.1));
- System.out.println("1.0-0.42=" (1.0 - 0.42));
- }
- }
代码运行结果如下:
num1=97
0.05 0.1 1=1.15
1.0-0.42 1=1.58
0.05 0.1=0.15000000000000002
1.0-0.42=0.5800000000000001
例2-5中,第4行代码针对char类型的变量c1进行了提升,输出为97,即c1在计算中对应的ASCII码。第6行和第7行代码有浮点数和整数参与运算,输出内容正确。但是,第8行和第9行代码只有浮点数进行运算,计算结果则出现误差,这是CPU所采用的浮点数计数法造成的。
float和double的精度是由尾数的位数来决定的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。
float:2^23 = 8388608,一共7位,由于最左为1的一位省略了,这意味着最多能表示8位数,即 2 * 8388608 = 16777216 。有8位有效数字,但绝对能保证的为7位,也即float的精度为7~8位有效数字。
double:2^52 = 4503599627370496,一共16位。同理,double的精度为16~17位。例如,对于double类型0.3-0.1的情况,需要将0.3转成二进制再进行运算,转换后的结果为“0.0100110011001100110011001100110011001100110011001101”,但是因为超出计算精度,最多保留16位,所以最终结果为“0.0100110011001100”,这就是导致计算结果误差的原因。
因此,Java的浮点类型只能用来进行科学计算或工程计算,在大多数的商业计算中,一般采用java.math.BigDecimal类(后续用法在第9.6.2章节讲解)来进行精确计算。针对上述问题,可以修改为如下代码:
BigDecimal b1 = BigDecimal.valueOf(0.05);BigDecimal b2 = BigDecimal.valueOf(0.1);BigDecimal b3 = BigDecimal.valueOf(1.0);BigDecimal b4 = BigDecimal.valueOf(0.42);System.out.println(b1.add(b2));System.out.println(b3.subtract(b4));
修改之后的输出结果如下:
0.15
0.58
2.5.3 关系运算符关系运算符又叫比较运算符,用于比较两个操作数之间的关系,其运算的结果是一个布尔值,即true或false,经常用于控制语句(条件语句或循环语句)中。Java中的关系运算符和使用范例,如表2.6所示
表2.6 关系运算符
运算符 |
运算 |
用法 |
结果 |
== |
相等于 |
2 == 3 |
false |
!= |
不等于 |
2 != 3 |
true |
< |
小于 |
2 < 3 |
true |
<= |
小于等于 |
2 <= 3 |
true |
> |
大于 |
2 > 3 |
false |
>= |
大于等于 |
2 >= 3 |
false |
除==运算符之外,其他关系运算符都只支持左右两边的操作数都是数值类型。基本数据类型变量、常量不能和引用类型的变量、常量使用“==”进行比较,如果引用数据类型之间没有继承关系,也不能使用“==”进行比较。
接下来,通过案例来演示关系运算符的使用,如例2-6所示。
例2-6 Demo0206.java
- public class Demo0206 {
- public static void main(String[] args) {
- int num1 = 39,num2 = 80;
- System.out.println("--------关系运算符和关系表达式案例--------");
- System.out.println("num1 == num2 = " (num1 == num2) );
- System.out.println("num1 != num2 = " (num1 != num2) );
- System.out.println("num1 > num2 = " (num1 > num2) );
- System.out.println("num1 < num2 = " (num1 < num2) );
- System.out.println("num1 >= num2 = " (num1 >= num2) );
- System.out.println("num1 <= num2 = " (num1 <= num2) );
- }
- }
程序的运行结果如下:
--------关系运算符和关系表达式案例--------
num1 == num2 = false
num1 != num2 = true
num1 > num2 = false
num1 < num2 = true
num1 >= num2 = false
num1 <= num2 = true
例2-6中使用关系运算符进行了关系表达式计算,优先计算小括号内的表达式。第10行和第11行代码,使用了关系运算符“>=”和“<=”,表示大于或等于、小于或等于二者符合一个条件即可返回true,否则返回false,因为num1小于num2,所以第10行返回值为false,第11行返回值为true。
注意:关系运算符是一个整体,>=、<=、!=不可以在中间插入空格,如“==”是对的,而“= =”是错的。
2.5.4 逻辑运算符逻辑运算符用于对布尔类型的值或者表达式进行操作,其结果仍然是一个布尔值。换句话说,布尔类型的值与逻辑运算符构成逻辑表达式,其值是一个布尔值,而一个或多个关系表达式还可以进行逻辑运算,其值仍然是一个布尔值。Java中的逻辑运算符和使用范例,如表2.7所示。
表2.7 逻辑运算符
运算符 |
运算 |
示例 |
结果 |
& |
与 |
a & b |
a、b均为true时,结果才为true |
| |
或 |
a | b |
a、b均为false时,结果才为false |
^ |
异或 |
a ^ b |
a、b操作数不同时,结果为true,相同时为false |
! |
非 |
! a |
将操作数a取反 |
&& |
短路与 |
a && b |
a、b均为true时,结果才为true |
|| |
短路或 |
a || b |
a、b均为false时,结果才为false |
逻辑运算符中&和&&、|和||的运算结构都相同,区别在于&&和||属于短路运算,也称为简洁运算,只需要执行逻辑运算符左侧表达式即可,无需计算右侧表达式,这样可以加快执行效率;而当使用&和|时,必须计算左右两个表达式之后,才能决定运算结果。
例如,当进行&逻辑运算时,必须两侧的值都是true,其结果才是true,若其中一个是false结果就为false;而对于&&逻辑运算,只要左侧表达式为false,其结果就是false,之后的值就不再进行判断;对于||,只要左侧表达式为true,其结果就是true,之后的值不再做判断。
^表示异或运算符,两个操作数结果相同则为false,两个操作数结果不同则为true,可以使用“两值相异即为真;两值相同即为假”来简记。
接下来,通过案例来演示逻辑运算符和逻辑表达式的使用,如例2-7所示。
例2-7 Demo0207.java
- public class Demo0207 {
- public static void main(String[] args) {
- int num1 = 11,num2 = 9,num3 = 22;
- System.out.println("--------逻辑运算符和逻辑表达式案例--------");
- System.out.println(!(num1 > num2));
- System.out.println((num1 > num3) & (num1 > num2));
- System.out.println((num1 > num3) && (num1 > num2));
- System.out.println((num1 > num2) || (num1 > num3));
- System.out.println((num1 > num2) | (num1 > num3));
- System.out.println((num1 > num2) ^ (num1 > num3));
- }
- }
程序的运行结果如下:
--------逻辑运算符和逻辑表达式案例--------
false
false
false
true
true
true
例2-7中,第5行代码中num1 > num2结果为true,取反后结果为false;第6行代码“&”符号两边逻辑表达式都要执行,第1个表达式结果为false,第2个表达式结果为true,运算后结果为false;第7行代码,第1个表达还是结果为false,因为“&&”为短路运算,第2个式子不会执行;第8行代码第1个式子结果为true,因为“||”为短路运算,第2个式子不会执行;第9行代码中,两个式子都要判断,第1个结果为true,第2个结果为false,所以结果为true;第10行代码,因为两个表达式结果不同,执行异或操作,所以结果为true。
2.5.5 赋值运算符赋值运算符就是将常量、变量、表达式的值赋给某一个变量或对象,赋值表达式由变量、赋值运算符和表达式组成。赋值运算符包括=赋值运算符和扩展赋值运算符两种。Java中的赋值运算符和使用范例,如表2.8所示。
表2.8 赋值运算符
运算符 |
运算 |
示例 |
结果 |
= |
赋值 |
a = 5;b = 2; |
a = 5;b = 2; |
= |
加等于 |
a = 5;b = 2;a = b; |
a = 7;b = 2; |
-= |
减等于 |
a = 5;b = 2;a -= b; |
a = 3;b = 2; |
*= |
乘等于 |
a = 5;b = 2;a *= b; |
a = 10;b = 2; |
/= |
除等于 |
a = 5;b = 2;a /= b; |
a = 2;b = 2; |
%= |
模等于 |
a = 5;b = 2;a %= b; |
a = 3;b = 2; |
赋值语句的结果是将右侧的值(或表达式结果)赋给左边的变量。变量在进行普通赋值时,如果赋值运算符两侧的类型彼此不一致,或者左边类型取值范围小于右边类型时,需要进行自动或强制类型转换。也就是说,变量从占用内存较少的短数据类型转换为占用内存较多的长数据类型时,会自动进行隐式转换;而将变量从较长的数据类型转换为较短的数据类型时,则必须进行强制类型转换,也就是采用“(类型)表达式”。赋值运算符也可以采取右端表达式继续赋值的方式,形成连续赋值的情况,但是一般不建议使用该方式进行赋值,会降低程序的可读性。赋值运算符的使用示例如下:
int n = 5; // 声明并赋值
int a, b, c; // 连续声明
a = b = c = 5; // 多个变量同时赋值,表达式等价于c = 5;b = c;a = b;
int a = 1; // 声明变量b
byte b = 3;
b = a b; // 错误,将int类型赋值给byte需要强制转换
b = (byte)(a b); // 正确,自动完成强制类型转换;
在赋值运算符“=”前加上其他运算符,即构成扩展赋值运算符,如a = 7等价于a = 7。也就是说扩展赋值运算符是先进行某种运算之后,再对运算的结果进行赋值。扩展赋值运算符的优点是可以使程序表达简洁,并且能提高程序的编译速度。编译器首先会进行运算,再将运算结果赋值给变量。具体示例如下:
int a = 3; // 声明变量a
a = 1; // a = 4;等价于a = a 1;
a *= 2; // a = 6;等价于a = a * 2;
接下来,通过案例来演示赋值运算符和赋值表达式的使用,如例2-8所示。
例2-8 Demo0208.java
- public class Demo0208 {
- public static void main(String[] args) {
- int a,b,c; // 声明变量a、b、c
- a = 18; // 将18赋值给变量a
- c = b = a - 9; // 将a与9的差赋值给变量b,然后再赋值给变量c
- System.out.println("--------赋值运算符和赋值表达式案例--------");
- System.out.println("a=" a ",b=" b ",c=" c);
- a = c; // 相当于a = a c
- System.out.println("a =c;a=" a);
- a -= c; // 相当于a = a - c
- System.out.println("a-=c;a=" a);
- a *= c; // 相当于a = a * c
- System.out.println("a*=c; a=" a);
- a /= c; // 相当于a = a / c
- System.out.println("a/=c; a=" a);
- a %= c - 2; // 相当于a = a % c - 2
- System.out.println("a%=c-2;a=" a ",c=" c);
- }
- }
程序的运行结果如下:
--------赋值运算符和赋值表达式案例--------
a=18,b=9,c=9
a =c;a=27
a-=c;a=18
a*=c; a=162
a/=c; a=18
a%=c-2;a=4,c=9
例2-8中,第5行代码使用了赋值运算符的连续赋值,先将差值赋值给b,然后再赋值给c。第8行、第10行、第12行、第14行代码分别表示操作数a对c的相加后赋值、相减后赋值、相乘后赋值、相除后赋值。第16行代码,先计算右侧c - 2,因为c的值是9,所以右侧减2之后结果为7,a的值为18,对7求余结果为4。
注意:使用赋值运算符需要注意如下两点:
(1)要注意数据类型匹配,如boolean flag = 23就时类型不匹配,无法自动转换。
(2)不能为运算式赋值,如“int a = 8,b = 9;a b = 28;”,这是语法错误。
2.5.6 位运算符位运算符是对操作数以二进制(bit比特)为单位进行操作和运算,应用在整型和字符类型数据上。运算符可以利用屏蔽位置和置位技术来设置或获得一个数字中的单个位或几位,或者将一个位模式向右或向左移动。对于正数而言,最高位为0,其余各位代表数值本身;对于负数而言,把该数绝对值的补码按位取反,然后对整个数加1,即得到该数的补码。位运算符包括位逻辑运算符和移位运算符。位逻辑运算符如表2.9所示。
表2.9 位逻辑运算符
运算符 |
运算 |
示例 |
结果 |
& |
按位与 |
a&b |
将a和b按bit位相与 |
| |
按位或 |
a|b |
将a和b按bit位相或 |
~ |
取反 |
~a |
将a按位取反 |
^ |
按位异或 |
a^b |
将a和b按bit位相异或 |
若x,y相当于二进制中的某个位,其值只能取0或1,则x和y有表2-9中的4种状态组合。针对&、|、~、^这4种位运算符,其结果如表2.10所示。
表2.10 位运算结果表
x |
y |
x&y |
x|y |
~x |
x^y |
0 |
1 |
0 |
1 |
1 |
1 |
1 |
0 |
0 |
1 |
1 |
0 |
1 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
位逻辑运算符只能操作整数类型的变量或常量。位逻辑运算的运算法则,具体如下:
- &:按位与运算符,参与按位与运算的两个操作数相对应的二进制位上的值同为1,则该位运算结果为1,否则为0。例如,将byte型的数值5与8进行与运算, 5对应二进制为0000 0101,数值8对应二进制为0000 1000,运算结果为0000 0000,对应数值0。具体演算过程如图2.2所示。
- |:按位或运算符,参与按位或运算的两个操作数相对应的二进制位上的值有一个为1,则该位运算结果为1,否则为0。例如,将byte型的数值5与9进行或运算,运算结果为0000 1101,对应数值13。具体演算过程如图2.3所示。
图2.2 &运算 图2.3 |运算
- ~:取反运算符,是单目运算符,即只有一个操作数,二进制位值为1,则取反值为0;值为0,则取反值为1。例如,将byte型的数值9进行取反运算,运算结果为1111 0110,对应数值-10。具体演算过程如图2.4所示,
- ^:按位异或运算符,参与按位异或运算的两个操作数相对应的二进制位上的值相同,则该位运算结果为0,否则为1。例如,将byte型的常量5与9进行或运算,运算结果为0000 1100,对应数值12。具体演算过程如图2.5所示。
图2.4 ~运算 图2.5 ^运算
移位运算符如表2.11所示。
表2.11 移位运算符表
运算符 |
运算 |
示例 |
结果 |
<< |
左移 |
a << b |
将a各bit位向左移b位 |
>> |
右移 |
a >> b |
将a各bit位向右移b位 |
>>> |
无符号右移 |
a >>> b |
将a各bit位向右移b位,左侧的空位填充0 |
<<= |
左移后赋值 |
a <<= n |
将a按位左移n位,结果赋值给a |
>>= |
右移后赋值 |
a >>= n |
将a按位右移n位,结果赋值给a |
>>>= |
左端补零右移后赋值 |
a >>> n |
将a无符号右移n位,结果赋值给a |
移位运算符是把一个二进制数值向左或向右按位移动。移位运算的运算法则如下:
- <<:左移运算符,将操作数的二进制位整体向左移动指定位数,符号位保持不变,左边高位移出舍弃,右边低位的空位补0。例如,将byte型的数值9进行左移3位运算,运算结果为0100 1000,对应数值72。具体移位过程如图2.6所示。
图2.6 <<运算
- >>:右移运算符,将操作数的二进制位整体向右移动指定位数,符号位保持不变,右边低位移出舍弃,左边低位的空位补符号位,即正数补0,负数补1。例如,将byte型的数值8进行右移2位运算,运算结果为0000 0010,对应数值2,具体移位过程如图2.7所示。再如,将byte型的数值-9(二进制码为9的补码形式)进行右移2位运算,运算结果为1111 1101,对应数值-3(再次用补码形式),具体移位过程如图2.8所示。
图2.7 正数>>运算 图2.8 负数>>运算
- >>>:无符号右移运算符,将操作数的二进制位整体向右移动指定位数,符号位保持不变,右边低位移出舍弃,左边低位的空位补0。因为正数符号位是0,所以针对正数而言>>和>>>符号,没有差别,主要是负数在使用>>>进行移位的时候,原来左边符号位需要补1,现在换成了补0。例如,将byte型的数值-9进行右移3位运算,按之前移位运算结果为00011110,结果应该是30,但是实际输出数值536870910。原因在于使用无符号右移运算符,对于低于int类型(byte、short和char)的操作数总是需要先将byte类型转换为int类型,则-9的二进制数值为:11111111111111111111111111110111,按位服务号右移3位后的二进制数值为11111111111111111111111111110,对应的十进制是536870910。
接下来,通过案例来演示位运算符的使用,如例2-9所示。
例2-9 Demo0209.java
- public class Demo0209 {
- public static void main(String[] args) {
- byte a = 5,b = 8,c = 9,d = -9; // 声明变量a、b、c、d,并赋值
- System.out.println("--------位运算符案例--------");
- System.out.println("5&8=" (a & b)); // 位与运算
- System.out.println("5|9=" (a | c)); // 位或运算
- System.out.println("~9=" ( ~ c)); // 位取反运算
- System.out.println("5^9=" (a ^ c)); // 位异或运算
- System.out.println("9<<3=" (c << 3)); // 左移运算
- System.out.println("b>>2=" (b >> 2)); // 正数右移运算
- System.out.println("-9>>3=" (d >> 2)); // 负数右移运算
- System.out.println("-9>>>3=" (d >>> 3)); // 负数无符号右移运算
- }
- }
程序的运行结果如下:
--------位运算符案例--------
5&8=0
5&9=13
~9=-10
5^9=12
9<<3=72
b>>2=2
-9>>2=-3
-9>>>3=536870910
例2-9中,第5~8行代码对上面的“&、|、~、^”位运算符运算做了验证。通过案例可以发现数值每次左移一位,其值会变成原来的2倍。第9行代码中,c值为9,向左移动3位时,结果变为72(9×2×2×2=72);同样地,右移一个位时是除以2,第10行代码中b值为8,向右移动2位时,移位结果为2(8/2/2=2)。第11行代码和第12行代码的区别是,“>>”是负数的右移运算,高位补1,而“>>>”是无符号右移运算,高位补零。
2.5.7 条件运算符条件运算符也称三元运算符,由符号“?”和“:”组合构成,需要三个操作数据,其语法格式如下:
(逻辑表达式) ? 结果表达式1 : 结果表达式2;
条件运算符的运算规则是:先对布尔类型的表达式求值,如果结果为true,就执行冒号“:”前面的结果表达式1,否者就执行后面的结果表达式2。
接下来,使用案例来演示条件运算符的使用,如例2-10所示。
例2-10 Demo0210.java
- package com.aaa;
- public class Demo0210 {
- public static void main(String[] args) {
- int a = 5,b = 4,max;
- max = a > b? a : b; // max求a、b中的最大值
- System.out.println("最大值max=" max);
- }
- }
运行结果如下所示:
max=5
在例2-10中,因为a的值为5,b的值为4,表达式“a > b”结果为true,则执行“?”后的语句,即将a的值赋给变量max,最终max的值为5。
如果要通过测试某个表达式的值来选择两个表达式中的一个进行计算时,使用条件运算符无疑是一种简练的方法,当然在后面第3.2节中,也可以使用if-else来实现同样的功能。
2.5.8 运算符的优先级在表达式运算中,用括号指明每一步的操作是非常麻烦的,因此程序设计语言引入了运算符的优先级来简化编程工作。运算符的优先级与日常数学运算规则基本类似,如先乘除后加减等。对于一个表达式进行运算时,要按照运算符的优先顺序从高到低进行,同级别的运算符则按从左到右的方向进行。运算符的优先级如表2.12所示。
表2.12 运算符的优先级
优先级 |
运算符 |
运算符说明 |
结合性 |
1 |
() [] . , ; |
分隔符 |
从左向右 |
2 |
! (正) –(负) ~ -- |
一元运算符 |
从右向左 |
3 |
* / % |
算术运算符 |
从左向右 |
4 |
(加) –(减) | ||
5 |
<< >> >>> |
位运算符 |
从左向右 |
6 |
< <= >= > instanceof |
关系运算符 |
从左向右 |
7 |
== != | ||
8 |
& |
位异或运算符 |
从左向右 |
9 |
^ | ||
10 |
| | ||
11 |
&& |
逻辑运算符 |
从左向右 |
12 |
|| | ||
13 |
?: |
条件运算符 |
从右向左 |
14 |
= = *= /= %= &= |= ^= <<= >>= >>>= |
赋值运算符 |
从右向左 |
从前面的章节中,我们知道数据类型分为基本数据类型和引用数据类型,为了更好的理解两种类型变量的区别,需要了解栈存储区(Stack)、堆存储区(Heap)的基本知识。
2.6.1 栈存储区声明属于基本数据类型的变量都存储在栈(Stack)存储区域内,也简称为栈。栈是一种数据“先进后出”的数据结构,Java栈的操作只有两个:每个方法执行,伴随着进栈;方法执行结束后,进行出栈操作。栈不需要进行垃圾回收。
栈内存分配运算位于处理器的指令集里面,这样效率很高,但是分配的内存容量是有限的,它的内存是由编译器自动管理的。接下来,使用3条语句来说明栈区存放和使用变量的方式,如图2.9所示。
图2.9 栈存储区基本类型变量赋值
基本数据类型变量在内存中存储的是一个数值。图2.9中的第1条语句表示在栈区给a分配地址并存放66;第2条语句表示在栈区给b分配地址,然后将a的值66赋值给b,其中b和a的地址不同;第3条语句表示将栈区原来a的值修改为88。
2.6.2 堆存储区与引用数据类型堆(heap)是计算机科学中一类特殊的数据结构的统称,是Java虚拟机所管理的内存中最大的一块。在Java中,堆的作用的就是用来存放对象实例。堆是应用程序在运行的时候由操作系统分配给的内存,在操作系统进行内存的分配时,分配和销毁都需要占用时间,所以导致堆的效率很低。但是,堆也有自己的优点,编译器不用知道要为数据分配多少堆里的存储空间,也不需要考虑存储的数据要在堆里停留多久,所以堆保存的数据有更大的灵活性。
堆内存用来存放由new关键字创建的对象,由Java虚拟机的自动垃圾回收器来进行管理。当一个系统产生很多实例时,会耗费大量的堆内存,可能会产生堆空间不足的问题,这时系统会抛出内存溢出提示。
引用数据类型也被称为符号数据类型,在有的程序设计语言中称为指针。引用数据类型在内存中存放的是指向该数据的地址,不是数值本身。数组、类、字符串等都属于引用数据类型(字符串用String类表示,因为比较常用,语法格式仍采用“String 变量名 = 值”形式)。当声明一个引用数据类型对象后,一般会在栈中分配一各存储空间来存放该对象,当使用new 创建对象后, 会在堆中分配一段内存空间来存放该对象内部封装的数据,此时在栈中对象存放的是堆中该对象的地址。
接下来,通过引用变量声明和实例化语句的执行来进行分析,理解一下JVM对引用变量的处理。例如下面语句:
- class Circle{ // 定义圆类
- int radius = 1; // 定义圆类的属性,并赋值为1
- }
- Circle cir;
- cir = new Circle(); // 给字符串赋值
上述代码中,第1~3行定义了一个圆类,定义了其半径属性,在后续第5章会详细讲解。第4条语句声明了Circle类的对象cir变量,此时将给cir变量在栈区分配一个保存引用地址的空间,如图2.10(a)所示。第5条语句的执行分两步骤执行,首先执行“=”号右侧部分,表示在堆区给cir变量分配内存空间,然后将堆区的地址赋值给cir变量,如图2.10(b)所示。
(a)声明Circle类对象cir (b)实例化Circle类对象cir
图2.10 堆存储区引用变量声明和赋值
2.7 输入和输出所有的Java程序会自动加载java.lang这个包内的所有类,该包里面有一个名称为System的类,前面案例的输出都用到了该类。System类代表了当前Java程序的运行平台,程序不能创建System类的对象。System类提供了一些类变量和方法,允许直接使用System类来调用,分别是:
- out:标准输出,默认输出设备是控制台。
- in:标准输入,默认设备是键盘设备。
- err:标准错误输出,默认输出设备是控制台。
System.out对象的println()、print()、printf()和format()方法都可以用来输出数据,一般使用println()输出后换行的方法比较多,这个方法的语法格式如下:
System.out.println(待输出的数据);
System.out.print(待输出的数据);
System.out.printf(格式化字符串,参数1,参数2...);
System.out.format(格式化字符串,参数1,参数2...);
printf()和format()方法一般用于格式化输出数据,后面的参数需要符合一定的格式输出。常用的转换字符串如表2.13所示。
表2.13 输出格式转换字符表
转换字符 |
说明 |
转换字符 |
说明 | |
%c |
按字符格式 |
%s |
按字符串格式 | |
%d |
按十进制整型格式 |
%b |
按布尔型格式 | |
%e |
按科学计数法格式 |
%x |
按十六机制格式 | |
%f |
按浮点数格式 |
%n |
换行 | |
%o |
按八进制格式 |
%a |
按十六机制浮点数格式 | |
%% |
输出% |
%g |
按短浮点数格式 |
%是转换字符中必要的字符,后面可以加上格式字符来进一步指定输出格式。常见的格式字符如下所示:
- -:有-表示左对齐输出,如省略表示右对齐输出。
- 0:有0表示指定空位填0,如省略表示指定空位不填。
- m.n:m指域宽,即对应的输出项在输出设备上所占的字符数。n 指精度,用于说明输出的实型数的小数位数。未指定n时,隐含的精度为n=6位。
- l或h:l对整型指long型,对实型指double型。h用于将整型的格式字符修正为short型。
- -:加入正负号。
- ,:数值加上千位分割。
在IDEA开发工具中,提供了控制台以便代码运行后进行输出,比如要输出一段话,可以直接在main函数里编写输出语句,代码编译运行后即可在控制台进行输出打印。
接下来,通过案例来演示输出方法的使用,如例2-11所示。
例2-11 Demo0211.java
- public class Demo0211 {
- public static void main(String[] args) {
- double a = 8301.678;
- String s = "欢迎学习Java编程技术";
- int i = 8898;
- System.out.printf("%f%n",a); // "f"表示格式化输出浮点数
- // "8.2"中的8表示输出的长度,2表示小数点后的位数
- System.out.printf("%8.2f%n",a);
- System.out.printf("% 8.2f%n",a); // " "表示输出的数带正负号
- System.out.printf("%-8.4f%n",a); // "-"表示输出的数左对齐
- System.out.printf("% -8.3f%n",a); // " -"表示输出的数带正负号左对齐
- System.out.printf("%d%n",i); // "d"表示输出十进制整数
- System.out.printf("%o%n",i); // "o"表示输出八进制整数
- System.out.printf("%x%n",i); // "d"表示输出十六进制整数
- System.out.printf("%#x%n",i); // "d"表示输出带有十六进制标志的整数
- System.out.printf("%s%n",s); // "d"表示输出字符串
- System.out.printf("浮点数:%f,一个整数:%d,一个字符串:%s%n",a,i,s);
- // 可以输出多个变量,注意顺序
- System.out.printf("字符串:%2$s,%1$d的十六进制数:%1$#x",i,s);
- }
- }
运行结果如下:
8301.678000
8301.68
8301.68
8301.6780
8301.678
8898
21302
22c2
0x22c2
欢迎学习Java编程技术
浮点数:8301.678000,一个整数:8898,一个字符串:欢迎学习Java编程技术
字符串:欢迎学习Java编程技术,8898的十六进制数:0x22c2
在例2-11第8行代码中,“%8.2f”表示总长度为8,小数位后2位,因为少1位前面补一位空格;第9行代码中,“% 8.2f”表示在前面输出 号,正好补够8位。,%f、%n、%d、%x、%s分别对应不同类型的转换字符,可以结合使用,本处不再赘述。
2.7.2 Scanner获取键盘数据在前面的学习中,代码中的变量都是在编写在中为其赋值的,这时候有些读者可能会比较好奇,难道说变量只能程序员为其赋值么?当然不是,程序编写是给用户用的,当然能实现互动,比如我们变量的值就可以通过控制键盘来获取数据,从而为程序中的变量进行赋值。
在控制台输入数据,可以使用Scanner类的对象来实现或IO来实现(这种方式在后面第13.3节讲解)。Scanner类在java.util包中,需要在程序前面编写import java.util.Scanner来加载该类,这样在程序中可以直接使用Scanner类。在使用该类前需要使用new关键字来创建一个该类的对象,语法格式如下:
Scanner 对象名 = new Scanner(System.in); // 输入参数
Scanner类包含几个常用方法,如下所示:
- next():获取用户输入的字符串,不包含空格和Tab字符。
- nextLine():获取输入的整行字符,可以包含空格和Tab字符。
- nextInt():获得一个整数;其他类似方法包括nextByte()、nextDouble()等获得对应类型数据。
- close():关闭Scanner对象。
接下来,通过案例来演示Scanner类的使用,如例2-12所示。
例2-12 Demo0212.java
- import java.util.Scanner;
- public class Demo0212 {
- public static void main(String[] args) {
- Scanner input = new Scanner(System.in);
- System.out.println("请输入您的姓名:");
- // 将输入的内容作为字符串类型赋值给变量name
- String name = input.nextLine();
- System.out.println("请输入您的学校");
- // 按Enter键后next()方法将回车符和换行符去掉
- String school = input.next();
- System.out.println(name "在" school "学习Java");
- input.close();
- }
- }
当代码编译运行时,控制台可以输入信息“AAA软件教育”,运行结果如下:
请输入您的姓名:
张三
请输入您的学校
AAA软件教育
张三在AAA软件教育学习Java
在例2-12中,第1行代码使用了import导入java.util.Scanner类。因为Scanner类是引用数据类型,第2行代码使用new关键字创建了一个Scanner类的对象input。第12行代码,使用close()方法关闭了input对象。
知识点拨:next()方法一定要读取到空格、Tab键和换行字符前的数据,并且不会处理换行字符。如输入“AAA 软件教育”,则next()方法只会读取到“AAA”,剩下的字符串“ 软件教育”会继续保留在内存,但是使用nextLine()可以直接读取完字符串“AAA 软件教育”。
2.8 本章小结- Java语言的数据类型可分为基本数据类型和引用数据类型两种。
- 标识符可以由任意顺序的大小写字母、数字、下划线和美元符号($)组成,不能以数字开头,标识符不能是Java中的关键字,标识符区分大小写,且长度没有限制。
- 变量是在程序运行过程中可以改变的量,在使用变量之前需要先声明;常量是在程序运行过程中保持不变的量。
- Java语言的基本数据类型包含有byte、short、int、long、float、double、char、boolean。
- 数据类型的转换包括自动类型转换和强制类型转换。
- 运算符是用来表示某一种运算的符号,它指明了对操作数所进行的运算。操作数是指运算的对象,可以是变量、常量或表达式。
- 运算符按照操作数的数目可以分为一元运算符、二元运算符和三元运算符。
- 运算符的按照性质可以分为算术运算符、关系运算符、逻辑运算符、位运算符、条件运算符、其他运算符。
- 运算符是有优先级和结合性的。优先级决定了表达式中不同运算执行的先后顺序,结合性决定了并列的多个同级运算符的先后执行顺序。
- 布尔型只有true和false两种取值,不能转换成其他的类型。
- Java语言中,存储区分为全局数据区、栈存储区和堆内存区。
- System.out对象的println()、print()、printf()和format()方法都可以用来输出数据,在控制台输入数据,可以使用Scanner类的对象来实现。
- 由键盘输入数据时,可以使用Scanner类来调用相应的netXXX()方法直接读取从键盘输入的相应类型的数据。
1.1 在Java中,byte类型数据占______个字节,short类型数据占______个字节,int类型数据占______个字节,long类型数据占______个字节。
1.2 数据类型转换方式分为自动类型转换和 两种。
1.3 Java中声明包的关键字为________。
1.4 变量的作用域以____符号进行分组。
1.5 若int a ==4;b=3; a =b;执行后,变量a的值为______。
2.选择题2.1 假设int x = 3,三目表达式 x>3? x 1: 5 的运行结果是以下哪一个?( )
A.1 B.0 C.5 D.6
2.2 已知y=2, z=3, n=4,则经过z=y<3?n -y*z/n :--z n/2运算后z的值为( )
A.-12 B.-1 C. 3 D.-3
2.3 下面的运算符中,用于执行取模运算是( )
A./ B.% C.\ D.mod
2.4 以下关于变量的说法正确的是( )
A.变量名必须是一个有效的标识符
B.变量在定义时必须有初始值
C.变量一旦被定义,在程序中的任何位置都可以被访问
D.在程序中,可以将一个int类型的值赋给一个byte类型的变量,不需要特殊声明
2.5 变量的基本数据类型有几种( )
A.4 B.7 C.1 D.8
3.思考题3.1 请简述什么叫类型转换?
3.2 请简述 | 和 || 的区别,并举例说明?
3.3 请描述一下运算符优先级的特点。
4.编程题4.1 使用三目运算符在控制台输出当Java成绩大于60时,获奖旅游一次,否则抄写3遍代码。
4.2 编写程序,在键盘上输入圆柱体的半径和高,计算其体积并且输出到控制台。
4.3 编写程序,在键盘上输入用户的姓名和密码,然后在控制台显示出来。姓名和密码使用变量来接收。
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com