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

2024-01-08 22:56:27

目录

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

结尾:

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

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

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

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

?

文章来源:https://blog.csdn.net/qq_75024941/article/details/135444441
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。