JavaSE - 变量与数据类型
本节学习目标:
- 认识变量,了解变量的数据类型;
- 熟悉掌握基本数据类型变量的定义方法与使用方式;
- 基本数据类型变量之间的运算及转换;
- 认识String类,了解String类的使用方式;
- 基本数据类型变量与String之间的转换;
- 不同进制之间的转换。
1. 变量概述
1.1 什么是变量
变量来源于数学,是计算机语言中能储存计算结果或能表示值的抽象概念。
变量可以通过变量名访问。在指令式语言中,变量通常是可变的;但在纯函数式语言(如Haskell)中,变量可能是不可变的。
在一些语言中,变量可能被明确为是能表示可变状态、具有存储空间的抽象(如在Java和Visual Basic中);但另外一些语言可能使用其它概念(如C的对象)来指称这种抽象,而不严格地定义“变量”的准确外延。
变量(计算机名词)- 百度百科
变量是内存中的一小块存储区域,该区域的数据可以在同一类型范围内不断变化,变量是程序中最基本的数据单元。包含数据类型,变量名和存储的值。
使用变量要注意:
- Java是强类型语言,每个变量必须明确指定数据类型;
- Java中每个变量要先声明,后使用;
- 使用变量名来访问这块内容的数据;
- 变量只有在其作用域内才有效;
- 同一个作用域内,不能定义重名的变量。
1.2 变量的数据类型与作用域
变量分为两种数据类型:
- 基本数据类型:八大基本类型。
- 整数类型(
byte
、short
、int
、long
):就是整数,比如1
,-35
,948734
等; - 浮点类型(
float
、double
):就是小数,比如0.6
,7.63
,-91.8407
等; - 字符类型(
char
):就是单个字符,比如'G'
,'2'
,'男'
等; - 布尔类型(
boolean
):就是真true
和假false
;
- 整数类型(
- 引用数据类型:由类型的实际值引用(类似于指针)表示的数据类型。
- 字符串(String):比如
Hello World!
,Nice to meet you!
等; - 基本类型的包装类:比如
Integer
,Double
等; - 数组类型:比如
int[]
,String[]
等; - 集合类型:比如
List
,Map
等; - 类和接口。
- 字符串(String):比如
变量根据作用域可以划分为三种类型:
- 局部变量:声明在方法,构造方法或者语句块中。
- 局部变量在方法、构造方法、或者语句块被执行的时候创建,当它们执行完成后,变量将会被销毁;
- 访问修饰符(如
public
)不能用于局部变量; - 局部变量只在声明它的方法、构造方法或者语句块中可见;
- 局部变量是在栈上分配的。
- 局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。
- 实例变量(成员变量):声明在一个类中,但在方法、构造方法和语句块之外。
- 当一个对象被实例化之后,每个实例变量的值就跟着确定;
- 实例变量在对象创建的时候创建,在对象被销毁的时候销毁;
- 实例变量的值应该至少被一个方法、构造方法或者语句块引用,使得外部能够通过这些方式获取实例变量信息;
- 实例变量可以声明在使用前或者使用后;
- 访问修饰符(如
public
)可以修饰实例变量; - 实例变量对于类中的方法、构造方法或者语句块是可见的。一般情况下应该把实例变量设为私有。通过使用访问修饰符可以使实例变量对子类可见;
- 实例变量具有默认值。数值型变量的默认值是
0
,布尔型变量的默认值是false
,引用类型变量的默认值是null
。变量的值可以在声明时指定,也可以在构造方法中指定; - 实例变量可以直接通过变量名访问。但在静态方法以及其他类中,就应该使用完全限定名:
ObjectReference.VariableName
。
- 类变量(静态变量):在类中以 static 关键字声明,但必须在方法之外。
- 无论一个类创建了多少个对象,类只拥有类变量的一份拷贝。
- 静态变量除了被声明为常量外很少使用,静态变量是指声明为
public
/private
,final
和static
类型的变量。静态变量初始化后不可改变。 - 静态变量储存在静态存储区。经常被声明为常量,很少单独使用
static
声明变量。 - 静态变量在第一次被访问时创建,在程序结束时销毁。
- 与实例变量具有相似的可见性。但为了对类的使用者可见,大多数静态变量声明为
public
类型。 - 默认值和实例变量相似。数值型变量默认值是
0
,布尔型默认值是false
,引用类型默认值是null
。变量的值可以在声明的时候指定,也可以在构造方法中指定。此外,静态变量还可以在静态语句块中初始化。 - 静态变量可以通过:
ClassName.VariableName
的方式访问。
变量的作用域只在其声明所在的一对大括号{}里。
2. 基本数据类型
2.1 整数类型
整数类型:byte
、short
、int
和long
。
- Java各整数类型有固定的数值范围和字段长度,不受具体系统的影响,以保证Java程序的可移植性;
- Java的整型常量默认是
int
类型,如果要声明long
类型的常量须后加l
或L
(规范使用L
); - Java程序中变量通常声明为
int
类型,除非不足以表示较大的数才使用long
类型。
四种整数类型可表示的数值范围和占用存储空间大小一览:
整数类型 | 占用存储空间大小 | 可表示的数值范围 |
---|---|---|
byte | 1 Byte | -27 ~ 27-1(-128 ~ 127) |
short | 2 Bytes | -215 ~ 215-1(-32768 ~ 32767) |
int | 4 Bytes | -231 ~ 231-1(-2147483648 ~ 2147483647) |
long | 8 Bytes | -263 ~ 263-1(-9223372036854775808 ~ 9223372036854775807) |
- Byte(字节,简写为B):计算机中基本存储单元,由8 bit(位,简写为b)组成。
- bit:计算机中的最小存储单位。
- 1 Byte = 8 bit;1 KB = 1024 B;1 MB = 1024 KB;1 GB = 1024 MB…
位是二进制的,只能表示0和1,1个字节一共使用了8个位。
以byte
类型为例,它占用一个字节,范围是从0000 0000
到1111 1111
,由于第一个位是符号位(表示正负),所以它可以表示的范围就是1111 1111
(-128)到0111 1111
(127)。
编写代码进行测试:
public class Main {
public static void main(String[] args) {
byte b = -21;
short s = 28746;
int i = -213487687;
long l = 4564864165464845648L; // 注意:如果数值大小已超过int类型表示的范围,必须要加后缀“l”或“L”
System.out.println(b);
System.out.println(s);
System.out.println(i);
System.out.println(l);
}
}
运行结果:
-21
28746
-213487687
4564864165464845648
2.2 浮点类型
浮点类型:float
和double
。
- 与整数类型类似,Java浮点类型也有固定的数值范围和字段长度,不受具体系统的影响。
- 浮点型常量有两种表示形式:
- 十进制数表示:如
5.12
,512.0F
,.512
(必须有小数点); - 科学计数法表示:如
5.12e2
(5.12*102),512E2
(512*102),100E-2
(100*10-2)(必须有E
或e
);
- 十进制数表示:如
两种浮点类型可表示的数值范围、占用存储空间大小和精度一览:
浮点类型 | 占用存储空间大小 | 精度 | 可表示的数值范围 |
---|---|---|---|
float | 4 Bytes | 7~8位有效数字 | -3.403E38 ~ 3.403E38(-3.403*1038 ~ 3.403*1038) |
double | 8 Bytes | 16~17位有效数字 | -1.798E308 ~ 1.798E308(-1.798*10308 ~ 1.798*10308) |
float
:又叫做单精度浮点类型,尾数可以精确到7位有效数字,很多情况下精度很难满足需求。声明float
型常量须在后加f
或F
(规范使用F
);double
:又叫做双精度浮点类型,精度为float
的两倍,通常采用此类型。Java的浮点型常量默认是double
型,可在后加d
或D
(规范不加)。
关于浮点数的精确与否的问题,可以查看此文章:浮点数为什么不精确?为什么银行的金额不能用浮点数计算 - CSDN/keke_Xin 。
编写代码进行测试:
public class Main {
public static void main(String[] args) {
float f = -52.148F;
double d = 948.426;
System.out.println(f);
System.out.println(d);
}
}
运行结果:
-52.148
948.426
2.3 字符类型
字符类型:char
。
char
类型是一个单一的16位Unicode字符;char
类型的数据用来表示通常意义上的“字符”;- Java中的所有字符都是用Unicode编码,故一个字符可以存储一个字母,一个汉字或其他书面语的一个字符;
- 字符类型数据的三种表现形式:
- 字符常量:用单引号(’’)括起来的单个字符,如
char a1 = 'a'
、char s = '中'
等; - 转义字符:Java中允许使用转义符号(\)来将其后面的字符转变为特殊的字符类型常量。如
char c3 = '\n'
(\n
表示换行符); - Unicode值:可以直接使用Unicode值来表示字符类型常量:
\uXXXX
(XXXX是一个十六进制整数)。如\u000a
表示\n
。
- 字符常量:用单引号(’’)括起来的单个字符,如
char
类型是可以进行运算的,因为它们都有对应的Unicode码。进行加减运算时会使用它们对应的Unicode码进行运算;char
类型变量不可赋值为空(如char a = '';
),否则将会报错。但可以赋值为空格(如char a = ' ';
);char
类型可以存储任何字符。
字符类型的数值范围和占用存储空间大小一览:
字符类型 | 占用存储空间大小 | 数值范围 |
---|---|---|
char | 2 Bytes | 0 ~ 65535(\u0000 ~ \uffff ) |
编写代码进行测试:
public class Main {
public static void main(String[] args) {
char a = 'A';
char b = '强';
char c = '\'';
char d = '\u4e2d';
System.out.println((int)a); // 将字符类型转换成int类型
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
运行结果:
65
强
'
中
延伸知识:ASCII码
上个世纪60年代,美国制定一套字符编码,对英语字符与二进制位之间的关系做了统一规定。这个规定被称为ASCII码。
ASCII码一共规定了128个字符的编码,比如空格是32(二进制0010 0000
),大写字母A是65(二进制0100 0001
)。
这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的一位统一规定为0。特性:
- 32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。
- 常见ASCII码的大小规则:0~9 < A~Z < a~z。
缺点:
- 不能表示所有字符;
- 相同的编码表示的字符不一样:比如130在法语编码中代表了é,而希伯来语编码中却代表了字母Gimel。
延伸知识:UTF-8
UTF-8(Universal Character Set/Unicode Transformation Format)是针对Unicode的一种可变长度字符编码。
它可以用来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部分修改后,便可继续使用。
因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。
2.4 布尔类型
布尔类型:boolean
。
boolean
类型只有两个值:true
(真)和false
(假);boolean
类型通常用来判断条件是否成立;boolean
类型可作为逻辑运算的返回值类型;boolean
类型没有给出明确的占用字节数(《Java虚拟机规范》给出了4个字节的占用数,但具体还要看虚拟机实现是否安装规范);boolean
类型的默认值为false
。
3. 基本数据类型变量之间的运算和转换
只讨论7种基本数据类型变量之间的运算,不包含boolean
类型(boolean
类型无法转换为其他基本数据类型)。
整型、实型(常量)、字符型数据可以混合运算。运算中,不同类型的数据先转化为同一类型,然后进行运算。
3.1 自动类型转换
当可表示的范围小的数据类型变量与范围大的数据类型变量进行运算时,结果自动提升为范围大的数据类型。
范围大小从左到右依次增大:byte
、char
、short
-> int
-> long
-> float
-> double
。
特别的:当byte
、char
和short
三种类型的变量互相进行运算时,结果int
类型。
编写代码进行测试:
public class Main {
public static void main(String[] args) {
byte b1 = 17;
char c1 = 'A';
short s1 = 23;
int i1 = 8;
long l1 = 875;
float f1 = 85.4F;
double d1 = -75.1;
System.out.println("byte类型和int类型运算,结果为" + getType(b1 + i1) + "类型。" + b1 + "+" + i1 + "=" + (b1 + i1));
System.out.println("char类型和int类型运算,结果为" + getType(c1 + i1) + "类型。" + c1 + "+" + i1 + "=" + (c1 + i1));
System.out.println("short类型和int类型运算,结果为" + getType(s1 + i1) + "类型。" + s1 + "+" + i1 + "=" + (s1 + i1));
System.out.println("byte类型和char类型运算,结果为" + getType(b1 + c1) + "类型。" + b1 + "+" + c1 + "=" + (b1 + c1));
System.out.println("char类型和short类型运算,结果为" + getType(c1 + s1) + "类型。" + c1 + "+" + s1 + "=" + (c1 + s1));
System.out.println("short类型和byte类型运算,结果为" + getType(s1 + b1) + "类型。" + s1 + "+" + b1 + "=" + (s1 + b1));
System.out.println("int类型和long类型运算,结果为" + getType(i1 + l1) + "类型。" + i1 + "+" + l1 + "=" + (i1 + l1));
System.out.println("long类型和float类型运算,结果为" + getType(l1 + f1) + "类型。" + l1 + "+" + f1 + "=" + (l1 + f1));
System.out.println("float类型和double类型运算,结果为" + getType(f1 + d1) + "类型。" + f1 + "+" + d1 + "=" + (f1 + d1));
}
/**
* 获取参数的类型
* @param obj 参数
* @return 类型
*/
private static String getType(Object obj) {
return obj.getClass().getName();
}
}
运行结果:
char类型和int类型运算,结果为java.lang.Integer类型。A+8=73
short类型和int类型运算,结果为java.lang.Integer类型。23+8=31
byte类型和char类型运算,结果为java.lang.Integer类型。17+A=82
char类型和short类型运算,结果为java.lang.Integer类型。A+23=88
short类型和byte类型运算,结果为java.lang.Integer类型。23+17=40
int类型和long类型运算,结果为java.lang.Long类型。8+875=883
long类型和float类型运算,结果为java.lang.Float类型。875+85.4=960.4
float类型和double类型运算,结果为java.lang.Double类型。85.4+-75.1=10.300001525878912
- 整型常量如果不加
l
或L
,则为int
类型(如123456
)。如果常量超出int
类型的数值范围则必须要加l
或L
,不然报错。 - 浮点型常量如果不加
f
或F
、d
或D
,则为double
类型(如1234.56
)。
3.2 强制类型转换
强制类型转换可以看做自动类型转换的逆运算。
- 条件是转换的数据类型必须是兼容的;
- 格式:
(需要转换的类型) 标识符
; - 范围大的数据类型变量强转为范围小的数据类型时,有可能会损失精度。
编写代码进行测试:
public class Main {
public static void main(String[] args) {
double d1 = 12.9;
int i1 = (int) d1;
System.out.println("double类型的" + d1 + "强制转换为int后变为了" + i1);
long l1 = 123;
int i2 = (int) l1;
System.out.println("long类型的" + l1 + "强制转换为int后变为了" + i2);
short s1 = 128;
byte b1 = (byte) s1;
System.out.println("short类型的" + s1 + "强制转换为byte后变为了" + b1);
}
}
运行结果:
double类型的12.9强制转换为int后变为了12
long类型的123强制转换为int后变为了123
short类型的128强制转换为byte后变为了-128
4. String(字符串)类型
字符串类型:String
;
String
字符串类型不是基本数据类型,属于引用数据类型;- 字符串类型的使用方式与基本数据类型一致(例如
String str = "Hello World!";
); - 一个字符串可以使用运算符
+
串接另外一个字符串,也可以直接串接其他类型的数据。 - 字符串常量必须使用英文双引号包括起来(如
"Hello World!"
),长度无限制; - 其他基本数据类型的常量
不可
直接赋给字符串变量(如String str = 4;
),如果要实现这一操作,仅需和一个空字符串进行运算(如String str = 4 + "";
)。 - 字符串类型变量可以赋值为空(如
String str = "";
); - 字符串类型变量可以和8种基本数据类型变量进行运算(仅限
+
连接运算),运算结果统一为字符串类型。运算效果就是直接把基本类型变量的值当做字符串进行串接; - 字符串变量无法强制转换为其他基本数据类型变量。
编写代码进行测试:
public class Main {
public static void main(String[] args) {
char c = 'A';
int i = 10;
String str = "Hello";
System.out.print("char + int + String = ");
System.out.println(c + i + str); // 运算顺序遵循从左到右
System.out.print("char + String + int = ");
System.out.println(c + str + i);
System.out.print("int + char + String = ");
System.out.println(i + c + str);
System.out.print("int + String + char = ");
System.out.println(i + str + c);
System.out.print("String + char + int = ");
System.out.println(str + c + i);
System.out.print("String + int + char = ");
System.out.println(str + i + c);
}
}
运行结果:
char + int + String = 75Hello
char + String + int = AHello10
int + char + String = 75Hello
int + String + char = 10HelloA
String + char + int = HelloA10
String + int + char = Hello10A
解析:运算顺序遵循从左到右,如果char
类型优先和int
类型进行运算,结果为int
类型。而字符串类型与任何基本数据类型运算,结果都为字符串类型。
5. 不同进制之间的转换
所有数字在计算机底层均以二进制形式存在。
对于整数,有四种表示方式:
- 二进制(binary,简写为BIN):表示方式只有0和1,满二进一,以0b或0B开头表示。比如0B1011、0B101100等;
- 八进制(octal,简写为OCT):表示方式0-7,满八进一,以0开头表示。比如0712、06347等;
- 十进制(decimal,简写为DEC):表示方式0-9,满十进一。
- 十六进制(hexadecimal,简写为HEX):表示方式0-9及A-F,满十六进一,以0x或0X开头表示。A-F不区分大小写。比如0x84F34,0X6A9C7等。
下方列出了不同进制的进制表:
十六进制 | 十进制 | 八进制 | 二进制 |
---|---|---|---|
0 | 0 | 0 | 0 |
1 | 1 | 1 | 1 |
2 | 2 | 2 | 10 |
3 | 3 | 3 | 11 |
4 | 4 | 4 | 100 |
5 | 5 | 5 | 101 |
6 | 6 | 6 | 110 |
7 | 7 | 7 | 111 |
8 | 8 | 10 | 1000 |
9 | 9 | 11 | 1001 |
A | 10 | 12 | 1010 |
B | 11 | 13 | 1011 |
C | 12 | 14 | 1100 |
D | 13 | 15 | 1101 |
E | 14 | 16 | 1110 |
F | 15 | 17 | 1111 |
10 | 16 | 20 | 10000 |
编写代码进行测试:
public class Main {
public static void main(String[] args) {
int bin = 0B1001;
int oct = 0127;
int dec = 248;
int hex = 0X94F2;
System.out.println(bin);
System.out.println(oct);
System.out.println(dec);
System.out.println(hex);
}
}
运行结果:
9
87
248
38130
5.1 二进制与十进制之间的转换
先讨论正数之间的转换:
一个字节为8个位:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
---|
第一位(符号位)用来表示正负,0为正数,1为负数。第一位不参与计算。所以byte
类型正数范围是0000 0001
(1)到0111 1111
(127)。
二进制转换十进制可以使用权相加法:从最低位开始,每个位计算数字x2当前位数-1,然后把所有位(不包括符号位)的计算结果相加就得到了转换后的十进制数。
比如看下面的例子:
0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 二进制数0101 1001 |
---|---|---|---|---|---|---|---|---|
不参与计算 | 1x26 | 0x25 | 1x24 | 1x23 | 0x22 | 0x21 | 1x20 | 对这七个数求和 转换成十进制为89 |
十进制转换二进制可以使用“除二取余,逆序排列”法:用2整除十进制整数,可以得到一个商和余数;再用2去除商,又会得到一个商和余数,如此进行,直到商为小于1时为止,然后把先得到的余数作为二进制数的低位有效位,后得到的余数作为二进制数的高位有效位,依次排列起来。
比如看下面的例子:
十进制数67(0100 0011):
符号位 | 1/2=0余1 | 2/2=1余0 | 4/2=2余0 | 8/2=4余0 | 16/2=8余0 | 33/2=16余1 | 67/2=33余1 |
---|---|---|---|---|---|---|---|
0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 |
负数转换略微麻烦一点:
二进制负数的符号位为1,表示这是一个负数。则byte类型负数范围是1111 1111
(-1)到1000 0000
(-128)。
以-67(1011 1101)转化为二进制数为例,先写出67的二进制数的原码(正数的原码和补码相同):
0 | 1 | 0 | 0 | 0 | 0 | 1 | 1 |
---|
把最高位符号位改为1,然后将剩余七位取反(1改为0,0改为1),得到-67的二进制数的反码:
1 | 0 | 1 | 1 | 1 | 1 | 0 | 0 |
---|
最后将反码-1,得到-67的补码:
1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 |
---|
计算机中表示的数字都是以补码的形式存储的。