PS:类体中的成员变量又被称作实例变量、对象变量,因为这一类成员变量位于类体中,表示一种状态信息,该类变量只能依赖于具体的实例(对象)而存在,而不是依赖于一个抽象概念(类),这些变量的值对于每个对象来说又可能是不一样的,如果想要访问到正确的信息,应该通过每个对象去进行访问,所以我们称之为实例变量,因为对象又称之为实例,所以实例变量又被我们称之为对象变量,可以理解为对象级别的变量,依赖于对象的变量。该类变量,只有在创建了具体的对象之后,才会为其分配内存,并隶属于某对象。
1、对象的创建关键字:new,一种运算符,用于创建对象并在jvm的堆内存中为该对象开辟新的内存空间。
语法结构:类名 对象名=new 类名(可选的参数);
——对象所占用的内存是在堆内存中分配的,通过该语句,一个类可以实例化n个对象。
2、分析对象创建语句以学生类Student为例,如果想要创建对象,java语句如下:
Student stu=new Student();
这里stu其实是一个局部变量,局部变量是存放在栈内存中的。
真正的实例化该类并为其在JVM的堆内存中分配空间的是new Student(),所以这才是我们所说的真正的对象;
那么stu是什么呢?一个存储在栈内存中的局部变量,我们一般称之为引用,该局部变量用于接收new出来的内存单元的地址,即对象的内存单元的地址,也可以说该引用指向对象所在的内存单元,但是要知道stu并不是对象,它只是一个指向它的引用,存储了内存地址的局部变量。
例如:
public class Computer { String name; String style; String color;}
import java.util.Scanner;public class test3 { public static void main(String[] args) { Computer computer=new Computer(); System.out.println("电脑品牌是:"+computer.name); computer.name="联想"; System.out.println("电脑品牌是:"+computer.name); }}
输出结果如下:
电脑品牌是:null电脑品牌是:联想进程已结束,退出代码为 0
内存分布图如下:
程序运行分析:
类进行加载,由类加载器将字节码文件加载到内存空间当中,当程序开始运行,执行到某个方法的时候,会在栈内存中逐一进行压栈,运行结束后弹栈释放内存空间并结束运行,所以这里当遇到main方法时,该方法在栈内存中分配内存空间进行压栈, 之后执行实例化类Computer的语句,当语句执行时,会先在JVM的堆内存中开辟一块内存空间,并分配给该对象,并将该块内存单元的地址赋值给存储在栈内存中的局部变量(引用)comuter,我们可以说引用computer指向了地址为0x1234的内存单元,也可以理解为引用和对象是代表的同一个内存单元。
从输出结果可以看出,computer引用所代表的对象的name完成了赋值的操作,进行输出后,我们发现值改变了,我们尝试其他的访问方式,可以发现是无法正确访问的,因为这些状态信息和动作信息只有通过对象,才算有意义,才可以正确的访问。
import java.util.Scanner;public class test3 { static int sum=0; static int n=0; public static void main(String[] args) { Computer computer=new Computer(); System.out.println("电脑品牌是:"+computer.name); computer.name="联想"; System.out.println("电脑品牌是:"+Computer.name); //类名·变量名 }}
D:IdeaJAVATESTsrctest3.java:107:45java: 无法从静态上下文中引用非静态 变量 name
如果想要访问堆内存中的某对象的数据信息,只能通过创建的存储着该块内存变量地址的引用进行访问,屏蔽了C语言中指针的概念,程序员是无法对堆内存进行直接操作的。访问数据的语法格式如下,以变量的访问为例,“·” 可以理解为常规意义 “的”;
读取值:引用.变量名;
修改数据:引用.变量名=新值;
注意引用类型String类,在使用过程中我们可以发现,一般字符串定义都是如下:
String a="abcd";
可以发现String类型的对象并没有利用new关键词去创建,而是直接进行了赋值的操作,因为其内存的分配无需用new关键字,是自动的进行内存分配的,所以上文中的内存分布图其实可以理解为:
对Computer类型的对象的状态信息name进行赋值时的操作,理论上隐含一个String对象的创建和对象内存的一个赋值操作,是将一个String类型的变量的地址,赋值给了局部变量(引用)name。
注意:对象是一种引用数据类型的变量,其存储的不是常规的基本数据类型的值,而是某内存单元的地址,如果未进行人为的赋值,它的默认的初始值为NULL,而不是0,相当于C语言中的空指针的概念。
3、引用类型变量使用引用数据类型和基本数据类型身份是相当的,不用进行特殊化的处理,统一看作”数据类型“处理。
所以引用数据类型变量的定义是可以在类体中充当实例变量的,这种定义的方式可以有效地交代各实体之间的复杂关系。相对的关系之间也更加的直观,更加的容易理解。
例如:学生和电脑两个类之间,每个类都具有各自实体抽象出来的多项共同特征
public class Student { String sno; //学号 String sname; //姓名 Computer myCom; //电脑}
public class Computer { String name; //电脑名称 String style; //电脑款式 String color; //电脑颜色}
在学生类中有一个Computer引用类型的局部变量myCom,用于在创建电脑对象后指向该对象的内存单元,这样表示的是电脑和学生实体之间的关系,如果在Student类当中,只定义一个变量comName,关系表示的并不是很明确,并且对于信息的表示和获取都不方便,并且上述定义方式,二者之间的关联相对灵活,后者关联会更强一些。
主方法如下:
//主方法public class test04 { public static void main(String[] args) { Student stu1=new Student(); stu1.sno="1705113137"; stu1.sname="张梦"; stu1.myCom=new Computer(); stu1.myCom.name="联想"; stu1.myCom.color="black"; stu1.myCom.; System.out.println("学生"+stu1.sname+"的电脑为"+stu1.myCom.name+"的品牌"); }}
当创建Computer类型的对象时,将新的对象的内存单元的地址赋值给了Student类中的引用myCom,这样引用指向的是一个具有多种信息的复杂的对象,可以通过学生对象进行合理的调用,找到它所关联的Computer对象,并访问到全部的相关信息。
在对信息进行访问时,同样采用”·“运算符,从引用开始,逐层引用,例如如上的程序,其内存分布图如下:
因为程序员不能直接的对堆内存进行访问和使用的,所以通过引用stu1来实现,通过引用stu1,可以访问到Student类型的对象内存,而通过该内存中存放的局部变量引用,可以访问到Computer类型的对象的内存单元,这样其实在Student和Computer对象之间就实现了一个关联。
对对象信息的访问有时候相对灵活,根据引用和对象之间的关系合理调用即可。
4、常见错误空指针的异常:
Exception in thread "main" java.lang.NullPointerExceptionat test04.main(test04.java:7)进程已结束,退出代码为 1
来看一下代码的变化,我们讲stu1所指向的Student类型对象内存单元的Computer引用并没有和Computer对象的内存单元产生关联,即表示该引用并不存在任何的实际意义
public class test04 { public static void main(String[] args) { Student stu1=new Student(); stu1.sno="1705113137"; stu1.sname="张梦圆"; //stu1.myCom=new Computer(); stu1.myCom.name="联想"; stu1.myCom.color="black"; stu1.myCom.; System.out.println("学生"+stu1.sname+"的电脑为"+stu1.myCom.name+"的品牌"); }}
在内存分析中,是这样的,myCom的引用断开,即该引用为空,并且因为java中垃圾回收机制的存在,Computer类型对象的内存单元会被垃圾回收。
总的来说,当空的引用访问实例相关的数据时,一定会产生空指针异常,其中实例相关的数据,指的就是如果要访问该信息时,一定要通过对象进行访问,才可以正常的访问到正确的信息。在引用时,注意这种嵌套关系,认真一些。
5、回顾