这篇文章得沉下心,仔细看,一天看完这一篇,我觉得你这一天都是赚的。
一.首先java的数据类型分类为:
java的世界里面应该是万物皆对象,为什么会存在基本类型呢?
(1)由于性能问题不得不用 :java基于性能的考虑,用c写的基本数据类型,基本数据类型存在栈里(存取速度比堆要快,仅次于直接位于CPU中的寄存器)速度比堆存储特别快,再有就是基本类型定义的变量创建和销毁很快,而类定义的变量还需要JVM去销毁。
(2)为了满足面向java面向对象:装箱和拆箱: 包装类型将基本类型的值包装在对象中,这样就可以使用对象的方法操作基本类型,类型之间的转换需要使用包装类型的方法才能完成,因此需要有包装类型。
装箱:将基本类型转换成包装类的过程
拆箱:将包装类转换成基本类型的过程
(3)【对象与基本数据类型的区别】
基本数据类型在栈中进行分配,而对象类型在堆中进行分配。
所有方法的参数都是在传递引用而非本身的值(基本类型例外)。
对象之间的赋值只是传递引用,基本类型之间的赋值是创建新的拷贝。
二.八大基本类型
Java中的八个基础类型有:byte、short、int、long、float、double、char和boolean.
先来说第一个问题:
(1).类型转换数据溢出问题
首先:1个字节占8位。
分为如下几类:
整型:byte、short、int、long 分别占用1、2、4、8个字节的空间;
浮点型:long、float 分别占用4、8个字节;
char型:char 占用2个字节;
boolean型:boolean 占用1位.
其中需要穿插一个概念:字节和位的概念
位概念
二进制数系统中,每个0或1就是一个位(bit),位是数据存储的最小单位。其中8bit就称为一个字节(Byte)。计算机中的CPU位数指的是CPU一次能处理的最大位数。例如32位计算机的CPU一次最多能处理32位数据。
二进制位:
二进制位简称“位”,是二进制记数系统中表示小于2的整数的符号,一般用1或 0表示,是具有相等概率的两种状态中的一种。
二进制位的位数可表示一个机器字的字长,一个二进制位包含的信息量称为一比特。
比特(BIT,binary system):
计算机专业术语,是信息量单位,是由英文BIT音译而来。同时也是二进制数字中的位,信息量的度量单位,为信息量的最小单位。在需要作出不同选择的情况下把备选的刺激数量减少半所必需的信息。即信号的信息量(比特数)等于信号刺激量以2为底数的对数值。L.哈特莱1928年认为对信息量选用对数单位进行度量最合适。如二进制数0100就是4比特。
拓展问题:
(1).为什么一个要设计1个字节=8Bit?
所谓字节,原意就是用来表示一个完整的字符的。最初的计算机性能和存储容量都比较差,所以普遍采用4位BCD编码(这个编码出现比计算机还早,最早是用在打孔卡上的)。BCD编码表示数字还可以,但表示字母或符号就很不好用,需要用多个编码来表示。后来又演变出6位的BCD编码(BCDIC),以及至今仍在广泛使用的7位ASCII编码。不过最终决定字节大小的,是大名鼎鼎的System/360。当时IBM为System/360设计了一套8位EBCDIC编码,涵盖了数字、大小写字母和大部分常用符号,同时又兼容广泛用于打孔卡的6位BCDIC编码。System/360很成功,也奠定了字符存储单位采用8位长度的基础,这就是1字节=8位的由来
以下两个问题的链接:为什么Byte是8位,但是却只能表示到127,而不是255? - 简书
(2)我们都知道一个二进制8位能表示的最大值是 1111 1111 == 255,但为什么最大表示到127?
因为对于计算机来说,一个二进制的数字它的最高位是符号位,0表示正数,1表示负数。
所以 1111 1111 表示的 -127, 而 0111 1111 表示的是127,范围区间应该是[-127,127]之间
(3).我们都知道一个Byte能表达的数字范围是[-128,127],那么这个-128是怎么来的呢?
这个涉及到原码、反码、和补码的相关知识,这里稍微拓展一下,自己想了解更清楚可以深入学习。
背景:原码,反码,补码的产生过程,就是为了解决,计算机做减法和引入符号位(正号和负号)的问题。
相关链接:转---原码,反码,补码的深入理解与原理。_原码反码补码-CSDN博客
字节和位的概念,到这里讲解结束
知道字节和占位的关系后继续来说类型转换数值溢出的问题:
由于存储位数不一样所以,不同基本类型存储的数值范围不一样
类型转换的时候由于类型使用的字节空间大小不一样,所以向上转换:可以直接进行隐式转换,向下转换,可能会出现数据溢出情况。如下转换逻辑:
详情转换逻辑看这个:java基本类型转换--精度丢失问题_long转换为double精度损失-CSDN博客
具体转换java测试demo例子看这个:Java 八大基本数据类型简述_在java中简述java中的8种基本数据类型,并说明每种数据类型所占的存储空间大小-CSDN博客
(2).值传递还是引用传递问题
(1)基本类型的创建:声明并初始化基本数据类型的局部变量时,变量名以及字面量值都是存储在栈中,而且是真实的内容。
具体过程:比如 int age=50;
首先JVM创建一个名为age的变量,存于局部变量表中,然后去栈中查找是否存在有字面量值为50的内容,如果有就直接把age指向这个地址,如果没有,JVM会在栈中开辟一块空间来存储“50”这个内容,并且把age指向这个地址。因此我们可以知道:
我们声明并初始化基本数据类型的局部变量时,变量名以及字面量值都是存储在栈中,而且是真实的内容。
(2)引用类型的创建:引用数据类型的对象/数组,变量名存在栈中,变量值存储的是对象的地址,并不是对象的实际内容。
具体过程:比如 Person p=new Person();
在执行Person per;时,JVM先在虚拟机栈中的变量表中开辟一块内存存放per变量,在执行per=new Person()时,JVM会创建一个Person类的实例对象并在堆中开辟一块内存存储这个实例,同时把实例的地址值赋值给per变量。因此可见:
对于引用数据类型的对象/数组,变量名存在栈中,变量值存储的是对象的地址,并不是对象的实际内容。
详细相关链接:CSDN
(3)值传递和引用传递理解
这两个传递都可以理解为,外部变量调用一个方法的时候,方法内部操作会不会改变这个变量的值。
用代码来demo一下所有情况:
基本类型以int为例:
1.int基本类型,方法内重新赋值和方法内自增参数,都不会影响外部参数的值。
public class IntPassByValue {
public static void main(String[] args) {
int a = 6;
System.out.println("输出开始的值:" + a);
changeIntValue(a);
System.out.println("输出结束1的值:" + a);
addIntValue(a);
System.out.println("输出结束2的值:" + a);
}
/**
*重新赋值
*/
static void changeIntValue(int a) {
a = 18;
System.out.println("输出重新赋值方法中间的值:" + a);
}
/**
*内容自增
*/
static void addIntValue(int a) {
a += 1;
System.out.println("输出自增方法中间的值:" + a);
}
}
结果,调用的方法内操作不会影响外部参数:
输出开始的值:6
输出重新赋值方法中间的值:18
输出结束1的值:6
输出自增方法中间的值:7
输出结束2的值:6
2.类引用类型
public class ClassPassByReference {
public static void main(String[] args) {
Student student = new Student(1, "张三");
System.out.println("输出开始的值:" + student.toString());
changeClassWholeValue(student);
System.out.println("输出结束1的值:" + student.toString());
changeClassLocalValue(student);
System.out.println("输出结束2的值:" + student.toString());
}
/**
*修改引用指向的地址
*/
static void changeClassWholeValue(Student student) {
student = new Student(2,"李四");
System.out.println("输出重新赋值方法中间的值:" + student.toString());
}
/**
*修改引用指向的地址对应的内容
*/
static void changeClassLocalValue(Student student) {
/*只修改name,不改id*/
student.setName("王五");
System.out.println("输出自增方法中间的值:" + student.toString());
}
}
结果:
输出开始的值:Student{id=1, name='张三'}
输出重新赋值方法中间的值:Student{id=2, name='李四'}
输出结束1的值:Student{id=1, name='张三'}
输出自增方法中间的值:Student{id=1, name='王五'}
输出结束2的值:Student{id=1, name='王五'}
附上student类:
public class Student {
private Integer id;
private String name;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(Integer id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
3.引用类型String类
public class StringPassByReference {
public static void main(String[] args) {
String a = "最初1";
System.out.println("输出开始的值:" + a);
changeStringWholeValue(a);
System.out.println("分割线+++++++string结束++++++++输出结束1的值:" + a);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("stringBuilder开始");
System.out.println("输出stringBuilder开始的值:" + stringBuilder);
changeStringBuilderWholeValue(stringBuilder);
System.out.println("输出stringBuilder修改1的值:" + stringBuilder);
changStringBuilderLocalValue(stringBuilder);
System.out.println("输出stringBuilder修改2的值:" + stringBuilder);
}
/**
* 修改引用指向的地址
*/
static void changeStringWholeValue(String a) {
a = "修改1";
System.out.println("输出重新赋值方法中间的值:" + a);
}
/**
* 修改StringBuilder引用指向的地址
*/
static void changeStringBuilderWholeValue(StringBuilder a) {
StringBuilder changeStringBuilder = new StringBuilder();
changeStringBuilder.append("修改指向引用地址");
a = changeStringBuilder;
System.out.println("输出修改StringBuilder引用指向的地址:" + a.toString());
}
/**
* 修改StringBuilder引用指向的地址对应的内容
*/
static void changStringBuilderLocalValue(StringBuilder a) {
a.append("增加str");
System.out.println("输出修改StringBuilder引用指向的地址对应的内容:" + a);
}
}
结果:1.由于String是不可变的源码string用final修饰,所以不管怎么操作string都是新生成一个str地址,调用方法操作不会影响到外部参数。2.StringBuilder如果在方法内改变引用参数的指向,是不会影响外部的参数。3.StringBuilder在方法内使用当前引用地址,修改这个地址的内容是会影响外部参数内容的。
输出开始的值:最初1
输出重新赋值方法中间的值:修改1
分割线+++++++string结束++++++++输出结束1的值:最初1
输出stringBuilder开始的值:stringBuilder开始
输出修改StringBuilder引用指向的地址:修改指向引用地址
输出stringBuilder修改1的值:stringBuilder开始
输出修改StringBuilder引用指向的地址对应的内容:stringBuilder开始增加str
输出stringBuilder修改2的值:stringBuilder开始增加str
总结:
- 形参和实参,外部的参数是实参,调用方法的时候形参是新的引用参数,代替了实参。(详情自己了解)
- 类型分为基本类型和引用类型两种。
- 基本类型是把内容放在栈内的变量上,新建内容的话就需要新建变量的话,调用方法的话,形参和实参是两个不同的引用变量,所以内容改变不互相影响,所以基本类型调用的方法内操作形参不会影响到外部实参。
- 引用类型类是把引用创建在栈上,地址指向堆上创建的内容,类调用方法形参代表的实参是类的地址,如果改变形参的地址指向,是不会影响到外部实参的地址和内容。如果根据形参修改指向地址的内容,那么会影响到外部实参的内容。
- 引用类型有个String,由于string是final类型,创建一个string就不能再改变(这个具体可以深入了解),所以每次修改形参,都会生成一个新的地址和内容,不会修改原来指向地址的内容。