程序运行时,需要处理的数据都临时保存在内存单元中,为了方便记住这些内存单元以存取数据,可以使用标识符来表示每一个内存单元。这些使用了标识符的内存单元称为变量。程序员若要使用某个内存单元,无须记忆内存空间的复杂的内存地址,只需记住代表这个内存空间的变量名称即可,从而大大降低了编程难度。简言之,变量是一个内存单元的名字,代表了这个内存单元,用于存取(读写)数据。之所以称为变量是因为变量代表的内存单元存储的数据在程序运行过程中是可以被改变的,就像一家酒店的同一间客房(变量),第一天入住的客人(数据)与第二天入住的客人可以不同。
使用变量需要进行“三部曲”:声明→赋值→使用。
使用变量前,先要进行声明。变量的声明也叫变量的定义。声明变量的名字及其可以存储的数据类型后,编译器会根据数据类型为变量分配合适的内存空间。不同数据类型的变量分配的空间大小不一样,就像一家酒店,豪华套房类型和普通套房类型的大小不一样(客房类型比作变量的数据类型)。此外,声明了变量也就约束了该变量只能存储什么类型的数据,其他类型的数据存不进来。声明变量的语法如下:
数据类型 变量名称;
· 数据类型是关键字。
· 变量名称是自定义的标识符,尽量使用能见名知义的名字。
参见下面的例子,代码如下。
int age; //声明整型变量age double num; //声明双精度浮点型变量num
图2.1 变量内存示意
int age表示声明一个名为age的整型变量,编译器将为它分配一块32位的内存空间,名称age即代表了该块内存空间;double num表示声明一个名为num的双精度浮点型变量,编译器将为它分配一块64位的内存空间,名称num即代表了该块内存空间。分配多少位的内存空间只取决于变量的数据类型。变量内存示意如图2.1所示。
相同类型的多个变量也可以在同一行一次性声明,代码如下。
int num1,num2,num3; //同一行声明多个相同类型的变量
注意: 不能在同一段程序中(准确来讲是在同一个作用域内)声明两个名称相同的变量。
数据类型除了前面提到过的数值型,还有字符型、日期型等。每个数据类型都有它的取值范围,编译器会根据每个变量或常量的数据类型为其分配内存空间。变量中存储的数据的数据类型应该跟变量声明的数据类型一致,否则会报错。例如,如果变量age声明为整型,则它只能存储1、2、3等整型数据,而不能存储1.0、3.14等浮点型数据或'a'、'b'、'c'等字符型的数据。Java的数据类型包括基本数据类型和引用数据类型,各自下面又有分类,如图2.2所示。其中基本数据类型有8种,如表2.2所示,引用数据类型以后再做介绍。
图2.2 数据类型分类
表2.2 基本数据类型
1.整数类型
Java使用4种类型的整数:byte、short、int和long。编写程序时应该为变量选择最适合的数据类型。例如,知道存储在变量中的整数是在1字节范围内,应将该变量声明为byte型。为了简单和一致性,本书的大部分内容都使用int来表示整数。
long类型的数据,需要在数字后面加上字母L或l(L的小写形式)来表示。如果数据的值不超过int的取值范围,也可省略。示例代码如下。
public static void main(String[]args) { long data1=1000000000; //没有超出int的取值范围,省略字母L long data2=10000000000; //超出了int的取值范围,报错 long data3=10000000000L; //加上字母L就不报错了 }
整数又分为二进制整数、八进制整数、十六进制整数和最常用的十进制整数。
二进制整数:由0和1组成的数字序列,逢二进一,如1101。在Java中二进制整数前面还要加上0b或0B才能表示是二进制数,否则会被当成十进制数,如0b1101或0B1101。
八进制整数:由0、1、2、3、4、5、6、7组成的数字序列,逢八进一,如72。在Java中八进制整数前面还要加上0才能表示是八进制数,否则会被当成十进制数,如072。
十六进制整数:由数字0~9和字母a~f共16个字符组成的序列,其中a代表10,b代表11,依此类推,逢十六进一,如a9。在Java中十六进制整数前面还要加上0x或0X才能表示是十六进制数,否则会报错或被当成十进制数,如0xa9或0Xa9。
二进制、八进制、十六进制的整数在输出时都会被转换成十进制整数。示例代码如下。
public static void main(String[]args) { int num2=0b1101; System.out.println(num2); num2=1101; //删除0b将被当成十进制数 System.out.println(num2); int num8=072; System.out.println(num8); num8=72; //删除0将被当成十进制数 System.out.println(num8); int num16=0xa9; System.out.println(num16); //num16=0xa9; //删除0x将报错 }
运行结果:
13 1101 58 72 169
2.浮点型
Java使用两种类型的浮点数:float和double。double称为双精度(double precision),而float称为单精度(single precision)。通常情况下,应该使用double型,因为它比float型更精确。Java中,在小数后面加上字母F或f表示float型数据,在小数后面加字母D或d表示double型数据。如果一个小数后面不加字母,则默认为double型数据。示例代码如下。
public static void main(String[]args) { double data1=1.87D; //小数后面加D,表示double型数据 double data2=1.87; //小数后面没有加D,默认为double型数据 float data3=1.87; //报错,double型数据不能存入float型变量中 float data4=1.87F; //小数后面加F表示float型数据 }
3.字符型
字符型(char)的变量只能存储单独一个字符。赋值时,需要用一对单引号将一个字符引起来再赋值,如char a='a'。也可以将一个整数赋给char型变量,编译器将自动将整数转换成ASCII编码表对应的字符。示例代码如下。
public static void main(String[]args) { char c1='c'; //赋值字符c System.out.println(c1); char c2=99; //赋值整数99,根据ASCII编码表,99代表字符c System.out.println(c2); }
运行结果:
c c
4.布尔型
在现实生活中经常要拿两种事物进行比较,比较结果可能是“真”,也可能是“假”。例如,太阳比地球大,比较结果是“真”;月亮比地球大,比较结果是“假”。比较结果不是“真”就是“假”,只有这两个取值。布尔型正是用来表示比较结果的,它只有两个取值,即“真”或“假”,在Java中用关键字boolean表示布尔型,它只有两个取值:true和false,分别代表布尔逻辑中的“真”和“假”。若一个变量声明为布尔型,则它只能存储true或false这两个值之一,而不能存储其他值。示例代码如下。
public static void main(String[]args) { boolean bigger; //声明一个布尔型变量 bigger=true; //赋布尔型数据true,正确 bigger=false; //赋布尔型数据false,正确 bigger=100; //赋整型数据,报错 }
5.String型
String型不属于基本数据类型,是类的一种,后面会做详细介绍。但前面有多处用到它,这里要先做简单说明。String型又称字符串型,用于存储字符串,字符串是由一个或多个键盘字符组成的字符序列,用双引号引起来。例如"HelloWorld"就是一个典型的字符串,可以将它赋给一个String类型的变量。示例代码如下。
String hello="HelloWorld"
声明变量是第一步,第二步为给变量赋值,这样变量才能使用。赋值是将一个数据(值)存入变量代表的内存空间,赋值的语法如下:
变量名=值;
【示例】 为变量赋值。代码如下。
int age; //声明整型变量age double num; //声明双精度浮点型变量num age=18; //变量的赋值 num=3.14159; //变量的赋值
也可以将第一步变量的声明与第二步变量的赋值合并为一步,语法如下:
数据类型 变量名称=值;
【示例】 声明变量的同时赋值,代码如下。
int age=18; //声明变量的同时赋值(初始值) double num=3.14; //声明变量的同时赋值(初始值)
这种情况下的变量值通常称为初始值,后面可以根据需要改变变量值。变量赋值后的内存如图2.3所示。
图2.3 变量赋值后的内存
多个相同类型的变量也可以在同一行一次性声明及赋值(或不赋值),多个变量之间用逗号分隔。示例代码如下。
//同一行声明及赋值多个变量,也可不赋值 int num1,num2=10,num3=20; //同一行声明及赋值多个变量,也可不赋值 double num4=10.1,num5,num6=10.2;
通常情况下,一种数据类型的变量只能用同一种类型的数据(值)进行赋值。例如一个int型变量,只能给它赋类似1、10、100的整型值;而double型变量,需要给它赋类似1.1、10.5、100.3的双精度浮点数型值。一种类型的值赋给另一种类型的变量也有可能成功,但需要进行数据类型转换。
数据类型转换是将一种数据类型的值转换成另一种数据类型的值的操作。当把一种数据类型的值赋给另一种数据类型的变量时,需要进行数据类型转换。数据类型转换方式有两种:自动类型转换与强制类型转换。
1.自动类型转换
将取值范围较小的类型的数值赋给取值范围较大的类型的变量时,Java会自动将取值范围较小的数值转换为取值范围较大的类型,这称为自动类型转换。自动类型转换按数据类型取值范围从小到大的顺序转换,不同类型数据间的优先关系为:byte → short → int → long → float →double。
另外,char型可以自动转换为int型。
【示例】 自动类型转换,代码如下。
double num1=100; //将整型值100赋给double型变量num1 System.out.println(num1); //将输出100.0,而不是100
控制台输出100.0,而不是原始值100,证明发生了数据类型转换。这里整型值100赋给double型变量num1,虽然类型不一致,但由于值100作为int型,其取值范围比赋值目标变量的double型的取值范围要小;因此按照上述顺序,它可以自动转换为double型,再赋给double型变量num1。同样,已赋过值的取值范围较小的类型的变量赋给取值范围较大的变量时,会发生自动类型转换。代码如下。
int num1=100; double num2=num1; //int型变量赋给double型变量时,发生自动类型转换 System.out.println(num2);
其他自动类型转换示例程序,代码如下。
public static void main(String[]args) { //自动类型转换 byte b=100; short s=b;//byte-->short System.out.println("byte-->short:"+s); int i=s; //short-->int System.out.println("short-->int:"+i); char c='a'; i=c; System.out.println("char-->int:"+i); long l=i; //int--->long System.out.println("int--->long:"+l); float f=l; //long-->float System.out.println("long-->float:"+f); double d=f; //float-->double System.out.println("float-->double:"+d); }
运行结果:
byte-->short:100 short-->int:100 char-->int:97 int--->long:97 long-->float:97.0 float-->double:97.0
2.强制类型转换
强制类型转换是将取值范围较大的类型的数值转换成取值范围较小的类型的数值。强制类型转换的前提是数据类型要兼容,并且源类型的取值范围大于目标类型。强制类型转换可能会损失精度。强制类型转换的语法:
(目标类型) 源类型;
源类型:取值范围要比目标类型大,可以是值或变量。
【示例】 强制类型转换,代码如下。
int num=(int)2.2; System.out.println(num);
输出结果为2,将double型转换成为int型,小数部分的0.2被截去,所以说强制类型转换可能会损失精度。
int型强制转换为byte型也会有精度损失。
【示例】 强制类型转换造成精度损失,代码如下。
byte b; int i=264; b=(byte)i; System.out.println("i="+i); System.out.println("b="+b);
运行结果:
i=264 b=8
int型值264强制转换为byte型后变成了8,明显发生了精度损失,这是为何呢?因为int型值是32位的,即4字节,整数264存储在计算机中的二进制形式如下所示:
00000000 00000000 00000001 00001000
当强制转换为byte型时,由于byte型只有8位(1字节),这样264的高位的3字节全部丢弃,只剩下最低位的1字节,此时二进制形式如下:
00001000
这样,它的值就变成8了。
int型也可强制转换为char型,如System.out.println((char)98);将输出b,转换依据是ASCII编码表。
变量在经过第一步声明与第二步赋值后,接下来就可以进行第三步使用了。变量的使用包括输出到控制台、参与运算、给其他变量赋值等。代码如下。
int num1=10; //声明变量并赋初始值 System.out.println(num1); //输出变量到控制台 int num2=num1; //给其他变量赋值 int num3=num1+num2; //参与运算
变量的声明、赋值、使用三者顺序不能调,否则会报错。但三者不必紧贴在一起,只要在使用变量前的任何位置声明与赋值过该变量即可。代码如下。
System.out.println(num1); //未声明就使用,控制台将报错:num1 cannot be resolved to a variable(num1 未定义为变量) int num2; System.out.println(num2); //未赋值就使用,控制台将报错:The local variable num2 may not have been initialized(num2 未初始化)
变量的作用域决定了变量名的可见性和生命周期。在Java中,作用域由大括号{}的位置决定。如果一个变量被定义在一对大括号内,则该大括号所包含的代码区域就是这个变量的作用域;如果该大括号内部又嵌套了大括号,那么变量的作用域还包括内嵌大括号所包含的代码区域。
【示例】 变量的作用域,代码如下。
public static void main(String[]args) { int num1=10; System.out.println("第1次输出num1:"+num1); //正确 { int num2=20; num2 num1 System.out.println("第2次输出num1:"+num1); //正确 作用域 作用域 System.out.println("第1次输出num2:"+num2); //正确 } System.out.println("第3次输出num1:"+num1); //正确 System.out.println("第1次输出num2:"+num2); //错误,要注释掉才能运行 }
可以发现变量num1声明与赋初始值后,在第一对大括号范围的任何位置均能正确使用,包括其内嵌的大括号内都能使用;但num2是在内嵌的大括号内定义的,故只能在内嵌的大括号内使用,在内嵌的大括号外无法使用。注释掉最后一行代码后,程序可以运行,结果如下所示:
第1次输出num1:10 第2次输出num1:10 第1次输出num2:20 第3次输出num1:10
注意: 相同的作用域中不能定义两个同名的变量。
常量代表程序运行过程中不能改变的值,常量在整个程序中只能赋值一次。常量有以下作用。
(1)代表常数,便于程序的修改(例如圆周率的值)。
(2)代表多个对象共享的值。多个对象都使用了,若要修改,只需修改常量,无须找出各个对象一一修改。
(3)增强程序的可读性(例如,常量UP、DOWN、LEFT和RIGHT分别代表上、下、左、右,其数值分别是1、2、3和4)。
在Java编程规范中,要求常量名必须大写。常量的语法格式和变量类型基本相同,只需要在变量的语法格式前面添加关键字final即可。
语法如下:
final 数据类型 常量名称=值;
也可以一行定义多个相同数据类型的常量,语法如下:
final 数据类型 常量名称1=值1,常量名称2=值2,…,常量名称n=值n;
【示例】 常量的使用,代码如下。
final double PI=3.14159; final char MALE='M',FEMALE='F';
在Java语法中,常量也可以先声明,再进行赋值,但是只能赋值一次。示例代码如下。
final int UP; UP=1;