学习王争的Java编程之美,总结一下引用类型篇
一,Java类型:基础类型与引用类型
Java的类型
1)基本类型
java的基本类型一共有8种,分别为:byte, char, short, int, long, float, double, boolean。
而这几种又可以按以下分类
整数类型:byte,short, int, long
字符型:char
布尔类型:boolean
浮点型:float
2)基本类型的JVM图
基本类型一般用在局部变量,类的属性,参数。
以局部变量,如下代码来示例:
public class Demo{
private int c = 1;
private int d = 2;
public static void main(String[] args){
int a = 1;
int b = 2;
a = b;
}
}
函数的局部变量是存在于栈上的,所以a和b对应的内存单元都存在栈上,并将其对应的值存为1和2,执行a=b时,则是将b内存单元中的值赋值给a的内存单元,如下:
而像内的属性并不是存在于栈中,而是在堆中(如上代码的c和d),但其内存单元存储的结构是一样的。只是一个在栈中一个在堆中。
3)引用类型:对象的引用
基本类型的存储比较简单,其内存单元中直接存储的就是值。而引用类型相比较复杂。
如下代码
public class Student{
private int a = 1;
private int b = 2;
public Student(int a, int b){
this.a = a;
this.b = b;
}
}
Student stu = new Student(1, 3);
要理解引用,首先要理解什么是“对象本身”,什么是“对象的引用”,如上面的代码stu并不是对象的本身,而是对象的引用,它引向的是对象在堆中的内存地址的首地址,如下图。
再看下面的赋值操作代码
Student stu1 = new Student(1, 3);
Student stu2 = new Student(2, 5);
stu1 = stu2;
在执行stu1 = stu2之前,其内存结构大概如下,stu1和stu2分别指向其对象本身所在的内址首地址,在堆中的内存单元中存储的也是对象本身所以内存的首地址。
而执行stu1 = stu2之的操作后如下,其本质是将stu2的内存首地址的值赋值给stu1,这时stu1和stu2引用相同的变量。
同时说明了一个引用变量,在不同时期可以指向不同的对象,而一个对象可以被多个引用变量所引用。
而对于复杂一些的对象(对象的属性不是基本类型,而是引用类型的),其本质和上面的类似,只不过一个引用存在于栈中,另一个存在于堆中。
public class Group{
public int gradNo;
public int classNo;
public Group(int gradNo, int classNo){
this.gradNo = gradNo;
this.classNo = classNo;
}
}
public class Student{
public int id;
public int age;
public Group group;
public Student(int id, int age){
this.id = id;
this.age = age;
}
}
public class Demo{
public static void main(String[] arg){
Student stu = new Student(3, 6);
stu.group = new Group(1, 4);
}
}
上面的代码在没有执行stu.group = new Group(1, 4);之前,在Student的对象属性group里存的为null(对应的二进制就是0),而赋值以后其内存结构如下:
4) 引用类型:数组
数组的引用更为复杂,有基本类型数组和对象数组,而从维度上又有一维数组和二维数组
1,一维数组
基本类型的数组,以int为例
int[] arr = new int[5];
arr[0] = 3;
其在内存的结构如下图,其会在内存中分配一整块连续的内存(堆),而arr是对象的引用,存储于栈中,其值为int[5]在堆中内存的首地址:
再看对象的数组:
Student[] arr = new Student[3];
arr[0] = new Student(1,1);
arr[1] = new Student(2,2);
其内存结构如下图,也会分配一整块的内存在堆中,但其内存储的值为对象所在堆中内存地址的首地址,如果为空存储的是null,而不像基本类型数组直接存储的是值,对象数组存储的是引用
2,二维数组
java的二维数组并不是定义上的传统意义上的二维数组,其一维是固定的大小的,其二维长度是可变的,另外二维数组不是一整块的完整内存。
基本类型的二维数组:
int[][] arr = new int[3][];
arr[0] = new int[2];
arr[2] = new int[4];
其内存结构如下图,发现一维是一整块的内存,但里面存的都是二维的首地址,而每一个二维也是一整块的内存。
再来看看对象的二维数组:
Student[][] arr = new Student[3][];
arr[0]= new Student[2];
arr[0][0]= new Student(2,2);
arr[2]= new Student[3];
arr[2][1]= new Student(4,4);
arr[2][2]=new Student(5,5);
其内存结构如下图,一维存的是二维的地址,而二维里面存的又是对象本身所在的首地址
4)链表的引用说明
链表作为一种基础的数据结构,其中利用了大量的引用。
public class Node{
public int data;
public Node next;
public Node(int data, Node next){
this.data = data;
this.next = next;
}
}
public class Demo{
public void traverse(Node head){
Node p = head;
while(p != null){
System.out.println(p.data);
p = p.next
}
}
public void delete(Node head){
p.next = p.next.next;
}
}
而其中要注意理解的就是p = head, p = p.next; p.next = p.next.next;
p = head如下图:
p = p.next如下
p.next = p.next.next.
二,参数传递:值传递or引用传递
基本类型参数
以代码来说明
public class Demo{
public static void main(String[] arg){
int a = 1;
fa(a);
System.out.println(a);
}
private static void fa(int va){
va = 2;
System.out.println(va);
}
}
其中fa中的va函数是一个单独的变量,存储于fa的栈帧中,其并不能访问main函数里的数据,而main函数也并不能访问fa函数里的数据。最初va会被赋值为a的值,但只是赋值与a本身并无关联了,所以va的改变不会影响a。
引用类型参数
引用类型参数就比较有意思了,这是很容易让人以为java是引用传递的地方。
public class Demo{
public static void main(String[] args){
Student stu = new Student(1,1);
fa(stu);
System.out.print(stu.age);
}
private static void fa(Student vstu){
vstu.age = 3;
}
private static void fb(Student vstu){
vstu = new Student();
}
}
在fa的函数栈帧中,vstu被赋值为stu中的值,即Student对象的首地址。此时这两个引用变量stu与vstu都指向了同一个对象。因此改变了vstu中age的值,stu的也改变了,因为本质是同一个对象。
而像fb中的更改。
本质是修改了vstu的值,指向一个新的对象,但是因为只是值传递,而不是对stu的引用,没法更改stu里的值,所以stu没有变化。
值传递or引用传递。
先说结论,是值传递。
因为看上面的就发现传递的是值,而不是引用,真正的引用传递是可以通过更改vstu指向新对象,stu也指向新对象的。
三,equals和==
其本类型
其本类型比较简单,直接使用== 判断是否等于。因为基本类型的值直接存储于其对应的内存单元,也就是判断两个的内存单元的值是否相等。
引用类型
引用类型使用equals一般指判断某个具体的值是否相等。
Object默认的如下
但是大多都会重写。如String,而平时使用中可以根据自己的需求重写。
另外也要注意hashCode的重写,因为在一些hash容器中先会利用hash值定位存储位置,然后再是判断是否是相同。
而使用==判断,则一般指是否是同一个对象,本质和基本类型一样,判断两个引用变量存储的值是否一致。