通过以前的学习,我们知道了成员变量是类的重要组成部分。对象的属性以变量形式存在,下面我们就来详解的学习一下类中的变量
成员变量
成员变量的分类
- 实例变量:没有static修饰,也叫对象属性,属于某个对象的,通过对象来使用
- 类变量:有static修饰,也叫类变量,属于整个类的,不是属于某个实例
如何声明成员变量
属性的类型可以是Java的任意类型,包括基本数据类型、引用数据类型(类、接口、数组等)。
例如:声明一个中国人的类
class Chinese{
static String country;//类变量
String name;//默认值
char gender = '男';//显式赋值
}
如何在类外面访问成员变量
类变量
成员变量/实例变量
代码示例
class Chinese {
static String country;
String name;
char gender = '男';
}
public class Demo {
public static void main(String[] args) {
//System.out.println(Chinese.name);错误,非静态成员变量必须通过对象.进行访问
//创建对象
Chinese c1 = new Chinese();
//对象名.非静态成员变量名
System.out.println(c1.name);//null
//对象名.非静态成员变量名
System.out.println(c1.gender);//男
//类名.静态成员变量名,推荐
System.out.println(Chinese.country);//null
//静态的成员变量也可以通过对象.进行访问,不推荐
System.out.println(c1.country);//null
}
}
成员变量的默认值
每个成员变量声明之后,系统会其数据类型的不同自动赋予不同的默认值,如下图所示
类变量的值是所有对象共享的,当类变量发生改变的时候,任何此类的对象访问该变量都会发生改变。而实例变量的值是每个对象独立的。
代码示例
class Chinese {
static String country;
String name;
char gender = '男';
}
public class Demo {
public static void main(String[] args) {
//创建对象c1
Chinese c1 = new Chinese();
//创建对象c2
Chinese c2 = new Chinese();
//给类变量赋值
Chinese.country = "中国";//推荐
//给对象c1的name属性赋值张三
c1.name = "张三";
//给对象c2的name属性赋值李四
c2.name = "李四";
//给对象c2的gender属性赋值女
c2.gender = '女';
//访问各个对象的成员变量
System.out.println("c1.country = " + c1.country); //中国
System.out.println("c1.name = " + c1.name); //张三
System.out.println("c1.gender = " + c1.gender);//男
System.out.println("c2.country = " + c2.country); //中国
System.out.println("c2.name = " + c2.name);//李四
System.out.println("c2.gender = " + c2.gender);//女
}
}
成员变量的内存图
内存是计算机中重要的部件之一,它是与CPU进行沟通的桥梁。其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来。我们编写的程序是存放在硬盘中的,在硬盘中的程序是不会运行的,必须放进内存中才能运行,运行完毕后会清空内存。Java虚拟机要运行程序,必须要对内存进行空间的分配和管理,每一片区域都有特定的处理数据方式和内存管理方式。
详解
下面我们使用画图的方式来分析一下为什么,类变量的值是当前类创建的所有对象共享的,而实例变量的值是每个对象独立
静态成员变量和实例变量的异同
相同点:
- 位置类中方法外
不同点:
有无static修饰
- 静态变量被static修饰
- 实例变量不能被static修饰
内存中的分数不同
- 类变量:内存中只有一份,当前类创建的对象共享
- 实例变量:每创建一个该类对象都会开辟一份空间,来存储不同对象的实例变量。
调用方式不同
- 类变量: 可以使用对象名.属性名和类名.属性名调用,推荐使用类名.属性名调用调用
- 实例变量:对象名.属性名
生命周期不同:
- 类变量:随着类的加载而加载 随着类的消亡而消亡
- 实例变量:随着对象的创建 ,对象消失(当对象没有引用指向时) 被垃圾回收器回收
存储的位置不一样
- 类变量:方法区
- 实例变量:堆中
成员变量和局部变量的区别
查看下面代码
public class Car {
String color;// 成员变量
// 成员方法
public void drive(String name) {
//声明在方法上的name和定义在方法里的speed都是局部变量
int speed = 80;
System.out.println("汽车正在以" + speed + "迈的速度行驶...");
}
}
成员变量和局部变量的主要区别如下:
定义的位置不同:
- 成员变量定义在类中方法外,
- 局部变量定义在方法中或者是方法声明上
在内存中的位置不同:
- 成员变量是在堆区,
- 局部变量是在栈区
生命周期不同:
- 成员变量是随着对象的创建而存在,随着对象的销毁而销毁
- 局部变量是随着方法的调用而存在,随着方法调用完毕而销毁
默认值不同:
- 成员变量有默认值
- 局部变量没有默认值,不赋值不能直接使用
成员变量初始化方式有下面几种
- 成员变量有默认值
- 显式赋值
- 代码块
- 构造器只为实例变量初始化,不为静态类变量初始化
- 使用setXxx(参数列表)赋值
初始化详解
类初始化
- 类初始化的目的:为类中的静态变量进行赋值。
- 实际上,类初始化的过程时在调用一个<clinit>()方法,而这个方法是编译器自动生成的。编译器会将如下两部分的所有代码,按顺序合并到类初始化<clinit>()方法体中。
- 静态类成员变量的显式赋值语句
- 静态代码块中的语句
- 整个类初始化只会进行一次,如果子类初始化时,发现父类没有初始化,那么会先初始化父类
结论:
每一个类都有一个类初始化方法<clinit>()方法,然后子类初始化时,如果发现父类加载和没有初始化,会先加载和初始化父类,然后再加载和初始化子类。一个类,只会初始化一次。
实例初始化
- 实例初始化的目的:为类中非静态成员变量赋值
- 实际上我们编写的代码在编译时,会自动处理代码,整理出一个<clinit>()的类初始化方法,还会整理出一个或多个的<init>(...)实例初始化方法。一个类有几个实例初始化方法,由这个类就有几个构造器决定。
实例初始化方法的方法体,由四部分构成:
- super()或super(实参列表) 这里选择哪个,看原来构造器首行是哪句,没写,默认就是super()
- 非静态实例变量的显示赋值语句
- 非静态代码块
- 对应构造器中的代码
特别说明:其中(2)和(3)是按顺序合并的,(1)一定在最前面(4)一定在最后面
执行特点:
- 创建对象时,才会执行
- 每new一个对象,都会完成该对象的实例初始化
- 调用哪个构造器,就是执行它对应的<init>实例初始化方法
- 创建子类对象时,父类对应的实例初始化会被先执行,执行父类哪个实例初始化方法,看用super()还是super(实参列表)
总结:
- 类初始化肯定优先于实例初始化。
- 类初始化只做一次。
- 实例初始化是每次创建对象都要进行。