对象的前世今生与和事佬(static)的故事

目录

1.对象村的秘密(对象在内存的实现)

1.1 内存的好兄弟“堆”与“栈”

1.1.1方法喜欢玩泰山压顶

1.1.2 stack的实现

1.2栈上的对象引用

1.2.1有关对象局部变量

1.2.2 如果局部变量生存在栈上,那么实例变量呢?

1.2.3创建对象的奇迹

1.3对象的消亡

1.3.1"life"与“scope”的差别

1.3.2杀死对象的三位杀手

2.和事佬在对象村的处境(static成员)

2.1 和事佬的处事方式(static的特性)

2.2 static修饰成员变量

2.3 static修饰成员方法

 2.4 static成员变量初始化

3.代码块

3.1代码块概念以及分类

3.2 普通代码块

3.3 构造代码块

3.4 静态代码块


导读:上节我们拜访了对象村,我们不禁产生很多疑问。怎么做到遍地都是对象的呢?对象村又是如何出现的?下面就由我来进行揭秘。

前言:

对象有生有死。你必须为对象的生命循环周期负责。你决定着对象何时创建,如何创建,也决定着何时销毁对象。其实你不是真的自消灭对象,只是声明要放弃它而巳。一旦它被放弃了,冷血无情的垃圾收集器(GC)就会将它蒸发掉、回收对象所占用的内存空间。如果你要编写Java程序,就必须创建对象。早晚你得将它们释放掉,不然就会出现内存不足的问题。这一章会讨论对象如何创建、存在于何处以及如何让保存和抛弃更有效率。这代表我们会述及堆、栈、范围、构造器、超级构造器、空引用等。注意:内容含有死亡成份,12岁以下儿童需由家长陪同观赏


1.对象村的秘密(对象在内存的实现)

1.1 内存的好兄弟“堆”与“栈”

在Java中,堆(heap)和栈(stack)是两种不同的内存区域,用于存储程序运行时的不同类型的数据和对象。

1. 栈(Stack):

  • 栈是一种线性数据结构,遵循"先进后出"(Last-In-First-Out, LIFO)的原则。栈中的数据主要包括基本数据类型的变量和对象的引用。
  •  栈内存用于存储局部变量、方法参数、方法调用以及方法返回时的临时数据。当一个方法被调用时,会在栈上创建一个称为"栈帧"的区域,用于存储该方法的局部变量和方法调用信息。栈帧在方法执行完毕后被销毁。
  •  栈的大小在程序运行时是固定的,由Java虚拟机(JVM)进行分配和管理。如果栈的内存空间不足,会抛出 StackOverflowError 错误。

2. 堆(Heap):

  •  堆是用于存储动态分配的对象的一块内存区域。堆是Java中最大的一块内存区域,也是最复杂的一块内存区域。
  • 所有的类实例(对象)和数组对象都存储在堆上。
  • 堆的大小在程序运行时是可变的。Java虚拟机根据堆的使用情况动态地分配和回收内存。
  • 堆的内存分配由垃圾回收器(Garbage Collector)负责,它自动扫描并回收不再使用的对象,使可用内存得到重复利用。

通常情况下,栈的内存空间比较小,用于存储局部变量等临时数据。而堆的内存空间比较大,用于存储动态创建的对象。在Java中,基本类型的变量(如int、boolean等)和对象的引用被存储在栈上,而对象的实际数据(包括成员变量)则存储在堆上。

了解栈和堆的原理对于理解Java的内存管理和内存分配非常重要,它们的不同特点也直接影响到程序的性能和内存使用情况。

如图所示:

b5343cbc9fd04c908c2462e026cf7725.png

实例变量:

  • 实例变量是定义在类中,方法外的变量。
  • 每个实例变量属于该类的每个实例对象,每个对象都有自己的实例变量副本。
  • 实例变量在对象创建时被初始化,并在对象的整个生命周期内都存在。
  • 实例变量可以被类中的任何方法、构造方法或块使用。
  • 访问实例变量时,必须通过对象引用来访问。

示例代码如下:

public class MyClass {
    // 实例变量
    private String name; // 字符串类型的实例变量

    public void setName(String name) {
        this.name = name;
    }

    public void printName() {
        System.out.println("Name: " + name);
    }
}

局部变量:

  • 局部变量是在方法、构造方法或块中定义的变量,只在其所在的作用域内可见。
  • 局部变量仅在其所在的方法或块执行期间存在,并且在方法或块的执行结束后被销毁。
  • 在方法中定义的局部变量在每次方法调用时都会重新创建,每个方法调用都会为其分配内存空间。
  • 访问局部变量时,只能在其声明的作用域内访问。

示例代码如下:

public class MyClass {
    public void printNumber(int number) {
        // 局部变量
        String message = "The number is: "; // 字符串类型的局部变量

        System.out.println(message + number);
    }
}

1.1.1方法喜欢玩泰山压顶

当你调用一个方法时,该方法会放在调用栈的栈顶,实际被堆上栈的是堆栈块。它带有方法的状态,包括执行到哪一行程序以及所有的局部变量的值。

栈顶上的方法是目前正在执行的方法,方法会一直待在这里直到执行完毕,如果foo() 方法调用bar()方法则bar()方法会放在foo()方法的上面。

c0ef4aee1b3f4d599bf30100febe27d2.png

1.1.2 stack的实现

下边有三个方法,第一个方法在执行过程中会调用第二个方法,第二个会调用第三个。每个方法都在内容中声明一个局部变量,而go()方法还有声明一个参数(这代表go()方法有两个局部变量)。

public void doStuff(){
    boolean b = true;
    go(4);
}
public void go(int x){
    int z = x + 24;
    crazy();
    //假设还有很多代码
}
public void crazy(){
    char = 'a';
}

如图

8f881e0c679f4220b8d7536d4a7681d5.png

1.2栈上的对象引用

1.2.1有关对象局部变量

要记得非primitive的变量只是保存对象的引用而已,而不是对象本身。你已经知道对象存在于何处——堆。不论对象是否声明或创建,如果局部变量是个对该对象的引用,只有变量本身会放在栈上
对象本身只会存在于堆上。

primitive:在Java中,primitive(原始类型)是指Java语言中的基本数据类型。Java的原始类型包括boolean、char、byte、short、int、long、float和double。这些原始类型是Java语言的基本构建块,用于存储简单的数据值。

public class stackRef{
public void foof(){
  barf();

  }
  public void barf(){
    Duck d = new Duck(24);
  }


}

19710efaa8b64b95b768e0cb7db13654.png

要点:

我们关心栈 与堆这两种内存空间。

实例变量是声明在类中方法之外的地方。
局部变量声明在方法或方法的参数上。
所有局部变量都存在于栈上相对应的堆栈块中。
对象引用变量 与primitive主数据类型变量都是放在栈上。

不管是实例变量或局部变量, 对象本身都会在堆上。

1.2.2 如果局部变量生存在栈上,那么实例变量呢?

当你要新建一个CellPhone()时,Java必须在堆上帮CellPhone找一个位置。这会需要多少空间呢?足以存放该对象所有实例变量的空间。没错,实例变量存在于对象所属的堆空间上。

记住对象的实例变量的值是存放于该对象中。如果实例变量全都是primitive主数据类型的,则Java会依据primitive主数据类型的大小为该实例变量留下空间。int需要32位,long需要64位,依此类推。Java并不在乎私有变量的值,不管是32或32,000,000的int 都会占用32位


1.2.3创建对象的奇迹

三个步骤的回顾:声明、创建、赋值

981109076e834aa5a34434ce346ec6ad.png

1.3对象的消亡

1.局部变量只会存活在声明该变量的方法中

局部变量只会存活在声明该变量的方法中
 

public void read(){
int s = 42;

//'s'只能用在此方法中
//当方法结束时

// s会完全消失


变量s只能用在read()方法中。换句话说,此变量的范围只会在所属方法的范围内。其余的程序代码完全见不到s。

2.实例变量的寿命与对象相同。 如果对象还活着,则实例变量也会是活的。

 

public class Life {

int size;

public void setSize (int s) {

size = s;

//'s'会在方法结束时

//消失,但size在类中

//到处都可用



此时s变量(这次是方法的参数)的范国同样也只限制在所属的setSize()这方法中。

1.3.1"life"与“scope”的差别

Lite
只要变量的堆栈块还存在于堆栈上,局部变量就算活着。也就是说,活到方法执行完毕为止。
Scope
局部变量的范围只限于声明它的方法之内.当此方法调用别的方法时,该变量还活着,但不在目前的范围内。执行其他方法完毕返回时,范围也就跟着回来。
 

public void doStuff(){
boolean b=true;
go(4) ;
}
public void go(int x){
int z = x + 24;
crazy();
//这里有更多的代码
}
public void crazy(){
char c = 'a';
}

a4fb5f86fa48420aa75e1650de6145c7.png

当局部变量活着的时候,它的状态会被保存。只要doStuff()还在堆栈上,b变量就会保持它的值。但b变量只能在doStuffO待在栈顶时才能使用。也就是说局部变量只能在声明它的方法在执行中才能被使用
 

1.3.2杀死对象的三位杀手

有3种方法可以释放(杀死)对象的引用:
当最后一个引用消失时,对象就会变成可回收的。
1 引用永久性的离开它的范围。

void go() {
Life z=new Life() ;

}
2 引用被赋值到其他的对象上.
Life z = new Life() ;
z = new Life();
3 直接将引用设定为null。
Life z = new Life();
 z = null;

对象杀手1号

引用永久性的离开他的范围

public class StackRef{
public void foof(){
     barf();
    }
    public void barf(){
        
        Duck d = new Duck;
    }

}

04429dd90ef342f5a82c2b63355c20c6.png

对象杀手三号

引用被赋值到其他对象

public class ReRef{
    Duck d = new Duck();
    public void go(){
      d = new Duck;
    } 
}

如下图:

954d97b36170460f954b3f92406dd6a3.png

对象杀手三号

直接将引用设定为null

public class ReRef {
    Duck d = new Duck();
    public void go() {
    d = null;
    }
}

77c6a6f3c4df4344b821e6f298fe66b0.png

那null又是什么呢?

null的真相
当你把引用设为null时,你就等于是抹除遥控器的功能。换句话说,你会拿到一个没有电视的遥控器。null是代表“空” 的字节组合(实际上是什么只有Java虚拟机才会知道)。

如果你真地按下这种逼控器上的按钮,什么事情也不会发生。但在Java.上,你是不能对null引用按钮的。因为Java虚拟机会知道(这是运行期,不是编译时的错误)你期待喵喵叫,但是却没有Cat可以执行!


对null引用使用圆点运算符会在执行期遇到NullPointerException这样的错误。后而会有讨论异常的章节。

2.和事佬在对象村的处境(static成员)

2.1 和事佬的处事方式(static的特性)

通过以下三个代码对学生进行描述,如下所示:

public class Student{
// ...
public static void main(String[] args) {
Student s1 = new Student("Li leilei", "男", 18, 3.8);
Student s2 = new Student("Han MeiMei", "女", 19, 4.0);
Student s3 = new Student("Jim", "男", 18, 2.6);
}
}

得到以下三个对象的特征

de39398b104542ecb98fa3040526b472.png

每个对象中都会包含一份 ( 称之为实例变量 ),因为需要使用这些信息来描述 具体的学生。而现在要表示学生上课的教室,这个教室的属性并不需要每个学生对象中都存储一份,而是需要让所 有的学生来共享。 Java 中,被 static 修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对 象,是所有对象所共享的

2.2 static修饰成员变量

static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。

【静态成员变量特性】
1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
3. 类变量存储在方法区当中
4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
public class Student{
public String name;
public String gender;
public int age;
public double score;
public static String classRoom = "Bit306";
// ...
public static void main(String[] args) {
// 静态成员变量可以直接通过类名访问
System.out.println(Student.classRoom);
Student s1 = new Student("Li leilei", "男", 18, 3.8);
Student s2 = new Student("Han MeiMei", "女", 19, 4.0);
Student s3 = new Student("Jim", "男", 18, 2.6);
// 也可以通过对象访问:但是classRoom是三个对象共享的
System.out.println(s1.classRoom);
System.out.println(s2.classRoom);
System.out.println(s3.classRoom);
    }
}

静态成员变量可以直接通过类名访问

8218e6b7b34e4d17865c3571eeb5023b.png

同学以调试方式运行上述代码,然后在监视窗口中可以看到,静态成员变量并没有存储到某个具体的对象中。
28b2d6598e02415ca651dec8dbf3d5e3.png

2.3 static修饰成员方法

被private修饰的:只有自己知道,其他人都不知道(封装会进行详细讲解)

 一般类中的数据成员都设置为private,而成员方法设置为public,那设置之后,Student类中classRoom属性如何在类外访问呢?

public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom = "Bit306";
// ...
}
public class TestStudent {
public static void main(String[] args) {
System.out.println(Student.classRoom);
}
}
编译失败:
Error:(10, 35) java: classRoom 在 extend01.Student 中是 private 访问控制

 那static属性应该如何访问呢?

Java中,static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。

public class Student{
// ...
private static String classRoom = "Bit306";
// ...
public static String getClassRoom(){
return classRoom;
}
}
public class TestStudent {
public static void main(String[] args) {
System.out.println(Student.getClassRoom());
}
}
输出:Bit306
静态方法特性
1. 不属于某个具体的对象,是类方法
2. 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者
3. 不能在静态方法中(直接)访问任何非静态成员变量​​​​​​

 66aa188c1b77417c8177752c78b1fb94.png

 无法从静态上下文引用非静态变量name

4. 静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用
9b971c68be214320a83523eae60f415a.png

 5. 静态方法无法重写,不能用来实现多态(此处大家暂时不用管,后序多态位置详细讲解)

 2.4 static成员变量初始化

注意:静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性

静态成员变量的初始化分为两种:就地初始化 和 静态代码块初始化。

   1.     就地初始化

​​​​​​​就地初始化指的是:在定义时直接给出初始值

public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom = "Bit306"; 
// ...
}

    2.​​​​​​​    静态代码块初始化

那什么是代码块呢?继续往后看 :) ~~~

3.代码块

3.1代码块概念以及分类

使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:

普通代码块
构造块
静态块
同步代码块(后续讲解多线程部分再谈)

3.2 普通代码块

普通代码块:定义在方法中的代码块

public class Main{
public static void main(String[] args) {
{ //直接使用{}定义,普通方法块
int x = 10 ;
System.out.println("x1 = " +x);
}
int x = 100 ;
System.out.println("x2 = " +x);//外面用不了内部的x
}
}
// 执行结果
x1 = 10
x2 = 100

这种用法较少见

3.3 构造代码块

构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。

public class Student{
//实例成员变量
private String name;
private String gender;
private int age;
private double score;
public Student() {
System.out.println("I am Student init()!");
}
//实例代码块
{
this.name = "bit";
this.age = 12;
this.sex = "man";
System.out.println("I am instance init()!");
}
public void show(){
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
}
}
public class Main {
public static void main(String[] args) {
Student stu = new Student();
stu.show();
}
}
// 运行结果
I am instance init()!
I am Student init()!
name: bit age: 12 sex: man

构造代码块在方法的外面,类的里面

实例代码块比构造方法先执行(和放在哪里没有关系)

8aad817bd7e24d74a9a60d68a4bd02ec.png

6defcb2589ef44dca59b54effae554ce.png

3.4 静态代码块

使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。

public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom;
//实例代码块
{
this.name = "bit";
this.age = 12;
this.gender = "man";
System.out.println("实例代码块");
}
// 静态代码块
static {
classRoom = "bit306";
System.out.println("静态代码块");
}
public Student(){
System.out.println("构造代码块");
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
}
}

 注意事项

静态代码块不管生成多少个对象,其只会执行一次
静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
实例代码块只有在创建对象时才会执行

实例化两个对象:

90b760f65b05403db10a0874f9594431.png

 当没有实例化对象时:

a00c5b1e3c6e4577a996f851fb90964c.png

结尾:

以上就是全部内容,送一句话该大家:

让我们如大自然般悠然自在地生活一天吧,
别因为有坚果外壳或者蚊子翅膀落在铁轨上而翻了车。

让我们该起床时就赶紧起床,
该休息时就安心休息,
保持安宁而没有烦扰的心态;

身边的人要来就让他来,要去就让他去,​​​​​​​
让钟声回荡,让孩子哭喊—
下定决心好好地过一天。

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值