目录
java中变量是最基本的存储单元,要创建变量时,需要指定类型并可为其赋值。语法如下:
type variable = value;
其中type是 Java 类型之一(例如int或String),variable是变量名(例如age或name)。
创建变量就是申请内存来存储值。也就是说创建变量的时候,需要根据变量类型在内存中申请空间,申请的空间只能储存该类型的数据。
数据类型可以划分两大类:基本数据类型(内置数据类型)和引用数据类型 。简单理解的话,所有非基本数据类型的数据类型都是引用类型。
基本数据类型
java语言一共提供了8种基本类型( primitive type ),其中 4 种整型(注意是:带符号的以二进制补码表示的整数)、2 种浮点类型(符合IEEE 754标准的浮点数)、 1 种用于表示 Unicode 编码的字符单元的字符类型 char 和 1 种用于表示真值的 boolean 类型。
类型 | 分类 | 字节长度 | 封装类 | 取值范围 |
byte | 整型 | 1 | Byte | -128(-2^7)~ 127(2^7-1) |
short | 整型 | 2 | Short | 32768(-2^15)~ 32767(2^15 - 1) |
int | 整型 | 4 | Integer | (-2^31)~ (2^31-1) |
long | 整型 | 8 | Long | (-2^63)~ (2^63-1) |
float | 浮点型 | 4 | Float | -3.4E38~3.4E38 |
double | 浮点型 | 8 | Double | -1.7E308~1.7E308 |
char | 字符型 | 2 | Character | \u0000(0)~ \uffff(65,535) |
boolean | 布尔型 | - | Boolean | true,false |
对于数值类型的取值范围无需强行记忆,因为对应的包装类都以常量的形式定义了。下面是示例和运行结果例子:
System.out.println("基本类型:int 位数:" + Integer.SIZE);
System.out.println("最小值:" + Integer.MIN_VALUE);
System.out.println("最大值:" + Integer.MAX_VALUE);
了解了基本数据类型的分类概念后,我们重点关注以下几个比较不好理解的问题:
整型取值范围为何负数比正数多1
以byte(1字节8位)为例最高位为符号,0值有2种表达方式:
0000 0000【正零】
1000 0000【负零】
从而出现浪费,于是在存储上约定当符号位位0时表示0,符号位1的用来表示另一个数,取反0111 1111 补码 1000 0000计算值是128,因符号位为负所以得出-128。
布尔型为什么没有指定存储大小
以下四种理解方式:
1、1个bit(位):boolean类型的值只有true和false两种逻辑值,在编译后会使用1和0来表示仅需1位(bit)即可存储,位是计算机最小的存储单位。
2、1个字节:虽然编译后1和0只需占用1位空间,但计算机处理数据的最小单位是1个字节8位,实际存储的空间是:用1个字节的最低位存储,其他7位用0填补。
3、4个字节:在《Java虚拟机规范》一书中的描述:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。也就是说JVM规范指出boolean当做int处理,也就是4字节,boolean数组当做byte数组处理,这样我们可以得出boolean类型占了单独使用是4个字节,在数组中是确定的1个字节。
综上总结:java规范中,没有明确指出boolean的大小。在《Java虚拟机规范》给出了4个字节,和boolean数组1个字节的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。
浮点的取值范围为何比long还要大
浮点型的空间大小并不比long大但是取值范围却更广,这是因为浮点型的存储遵循 IEEE 754标准
符号位(S):1bit | 指数位(E):8bit | 尾数位(M):23bit |
指数位E:8位【0~255】,为了方便比较计算存储的都是正数,实际指数要减掉127;
尾数位M:23位,整数位被舍弃固定1,M取值范围[1.0,2.0);尾数23位取二进制1.11111(23位),约等于2-2^-23≈2
所以float的绝对值最大为:2^127*2=2^128=3.4E+38(科学计数法)
浮点型的有效位数怎么来的
如 float,由其存储结构可看出有23位用于存放尾数, 带有一个固定隐含位… 所以float的有24个二进制有效位位数。2^24=16777216共有8个十进制位. 所以有些编译器 float的有效数字位是 8位 , 有些有效数字位是 7位.(注意不是小数的位数, 是有效数字位)
为什么说浮点型计算往往不准确
尝试System.out.println( 2.0-1.1 ) 将打印出 0.8999999999999999,而不是人们想象的0.9。原因是浮点型的二进制存储表示方法,而在二进制中无法精确表示小数。
十进制小数到二进制小数一般是整数部分除 2 取余,逆序排列,小数部分使用乘 2 取整数位,顺序排列。二进制小数到十进制小数还是使用按权相加法,实践可知很多十进制无法转成精确的二进制表示(无限循环),连表示都无法准确,计算就无法保证精确了。如果在数值计算中不允许有任何舍入误差,就应该使用 BigDecima类。
引用数据类型
java语言中引用数据类型非常多,比如:类class引用、 接口interface引用、 数组类型、 枚举等等;例如String
类就是引用类型;引用类型的赋值传递的是内存的地址。所有引用类型的默认值都是null。
一个引用变量可以引用任何相兼容的类型。
基本数据类型和引用数据类型的区别
1、内存空间指向
基本数据类型指向具体数值,引用数据类型指向该数据存储地址值,赋值和传递同理。
基本数据类型数值存在栈内存;而引用数据类型在堆内存存储数据信息,同时在栈内存中分配空间指向堆中的对象地址。
通过以下代码示意两种存储
public static void main(String[] args) {
//基本数据类型
int age=18;
int age1=30;
double price=1.2;
char code='A';
//引用数据类型
String name="lilei";
String name2="lilei";
String name3="hanmeimei";
}
基本数据类型会存在两个相同的值,而引用型类型就不会存在相同数据。
创建声明 String name="lilei";时会在堆内存中查询是否有这个地址,如果堆内存中已经存在则指向这个地址,如果没有就会在堆内存创建并将变量指向地址。
2、判断数据是否相等
基本数据类型用==和!=判断,引用数据类型:用equals()方法;==和!=是比较数值,而equals()方法是比较内存地址。
3、参数传递方式
基本数据类型调用方法时作为参数是按数值传递的,调用方法时在栈中开辟新内存空间,方法结束时释放,方法内的操作都是操作当前方法执行的栈中的值;
引用数据类型变量调用方法时作为参数传递的是引用的副本(堆内存的地址),调用时为形参(入参变量)在栈中开辟新空间,并指向实参的具体内容(堆内存地址),方法内是直接指向的堆空间中引用类型的值,方法执行完毕后释放栈内存;
据此总结:
- 参数是基本数据类型,为数值传递,方法内部不会改变外部对象值。
- 参数为引用数据类型,为地址传递,方法内部会改变外部对象值。
- 需要特别说明:参数为基本数据类型的包装类型,为引用传递,方法内部改变参数的值,外部变量不会发生变换。
这是因为包装类型虽然为引用类型传递,但由于自动装箱的机制,在改变包装类对象的值时,是创建一个新的对象,栈中的地址指向了新的对象,不会改变原有的包装类对象。
代码示例:
public static void main(String[] args) {
//基本数据类型
int age=18;
//引用数据类型
String name="lilei"; //基本包装类
Person person = new Person("张三",18);
System.out.println("基础数据类型调用前:"+age);
System.out.println("引用数据类型调用前(包装类):"+name);
System.out.println("引用数据类型调用前:"+person.getInfo());
func(age,name,person);
System.out.println("基础数据类型调用后:"+age);
System.out.println("引用数据类型调用后(包装类):"+name);
System.out.println("引用数据类型调用后:"+person.getInfo());
}
public static void func(int age,String name,Person person){
age=30;
name="hanmeimei";
person.setAge(30);
}
@Data
static class Person{
String name;
int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public String getInfo(){
return "姓名:"+ name + ",年龄:" + age;
}
}
类型之间的转换
数据运算处理中会存在类型转换,java中的类型转换分为两类:自动类型转换和强制类型转换。
从小类型到大类型(或者子类到父类)是可以自动转换,在基本数据类型中其顺序如下:
而大类型转小类型(或者父类到子类)则需要强制转换符进行强制转换
需要注意:
- 布尔类型不能参与类型转换。
- 引用类型的转换必须要是发生在有继承关系的对象之间
自动类型转换
必须满足转换前的数据类型的范围要低于转换后的数据类型,否则编译报错
int age=18;
double age1 =age;
int age3=age1; // 类型转换异常
上面的代码中,int->doubles 自动转换的,反之不行,编译异常
强制类型转换
int age=18;
double age1 =age;
int age3= (int) age1;
上例double->int 需要加(int)强制转换符号;注意强制转换可能导致精度丢失
可以使用instanceof关键字判断对象是否属于某个数据类型
if(引用变量 instanceof 数据类型){
可以转换
}else{
不能转换
}
基本数据类型及其包装类的转换
基本数据类型对应的包装类也是引用数据类型。他们之间的转换过程存在装拆箱机制。
把基本数据类型转换成包装类的过程就叫装箱,把包装类转换成基本数据类型的过程就是拆箱;在运算和比较操作时会进行自动拆装箱。需要注意:
- equals()方法不会进行类型转换
- 如果数字在 -128 至 127 之间时,自动装箱会直接使用缓存中的对象,而不是重新创建一个对象。