作者:困了电视剧
专栏:《JavaSE语法与底层详解》
文章分布:这是一篇关于Java核心知识之一的类和对象的文章,在本篇文章中我会详细讲解static修饰符和代码块的内容,包括他们所涉及的底层原理。
目录
static成员
需求
static修饰成员变量
static修饰的静态方法
静态成员变量的初始化
代码块
普通代码块
构造代码块
静态代码块
同步代码块
在日常的生活中,我们可能会需要一个“不变”的量,他独立于一些事物,并且又为这些事物所共享。举个栗子,我们定义一个学生类:
class Student{
String name;
int age;
double score;
}
这个学生类中有名字,成绩和分数三个成员变量,当我们实例化一个学生对象的时候,这些成员变量就变成了描述这个对象的属性。现在我们有了一个新的需求,我通过这个类实例化了三个对象,我现在想把他们归到一个班级里怎么做?
我们现在有一个办法:在学生类中再加入一个成员变量用来表示班级,然后在实例化的时候将这三个对象的班级属性赋上相同的初值,通过这种方式让他们归于同一个班级 。
但是,先不说这样不仅在实际操作上容易出现差错,在逻辑上也不是特别严谨,这样定义的话就相当于每一个学生都有一个“班级”,但逻辑上是一个班级有很多学生。
所以,为了应对这样的问题和其他的方面,Java的开发团队引入了static这个关键字。
之前在Student类中定义的成员变量,每个对象中都会包含一份(称之为实例变量),因为需要使用这些信息来描述 具体的学生。而现在要表示学生上课的教室,这个教室的属性并不需要每个学生对象中都存储一份,而是需要让所 有的学生来共享。在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对 象,是所有对象所共享的。
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共 享的。
特性:
1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中。
2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问。
3. 类变量存储在方法区当中。
4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)。
public class Javabit_Code{
public static void main(String[] args) {
Student s1=new Student("001",15,10);
Student s2=new Student("002",15,20);
Student s3=new Student("003",15,30);
System.out.println(Student.classroom);//体现了类变量中的类
//体现了“是所有对象所共享的”这一概念,即每个对象都可以对其使用,但不推荐
System.out.println(s1.classroom);
System.out.println(s2.classroom);
System.out.println(s3.classroom);
}
}
class Student{
static String classroom="303";
String name;
int age;
double score;
public Student(String name,int age,double score){
this.name=name;
this.age=age;
this.score=score;
}
}
我们将程序debug一下从下方可以清晰地看到学生对象中并没有classroom这个成员变量。
Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。同时,为了体现Java 的封装性,成员变量都会被private所修饰,所以静态成员一般是通过 静态方法来访问的。
注意:普通成员方法内部可以访问静态成员方法,但静态成员方法内部无法访问普通成员方法。
对于这句话我们可以这样进行理解,在前文说了,静态的是不依赖于对象的。我想要调用一个普通的成员变量方法,我需要先实例化一个对象,然后通过对象进行调用,由于静态的不依赖于对象,所以有没有对象静态的都能进行调用,所以编译不会出错。
但当我想调用一个静态成员方法时,由于静态的不依赖于对象所以我不需要实例化一个对象,用类名直接调用就行,此时如果我在静态成员方法中定义了一个普通的成员变量方法,那结果可想而知,编译器会因为无法调用普通的成员变量方法而报错。
1.直接赋值——就地初始化
在类中定义静态成员变量就给其赋上初值,如上述例子的“303”。
2.默认初始化
由于静态成员变量先是一个成员变量,所以其符合成员变量的规则,如果一个成员变量没有被初始化,Java会自动的对其进行初始化,值为默认值。
3.可以通过提供get和set方法来进行初始化
当静态成员变量是用private修饰的私有变量的时候,我们将无法通过直接赋值的方式进行初始化,这时候我们可以采用get和set的方法对其进行初始化。
4.在构造对象的时候,可以在构造方法中进行赋值(不建议这样做)
构造方法中初始化的是与对象相关的实例属性,而类变量是这一个类所实例化的所有对象所共享的一个变量,所以如果这样做就意味着一个对象可以改变其他所有对象的一个属性值,这不仅安全性不高,而且在逻辑上也有误——个体适应环境总不能说是环境适应个体吧。
5.通过代码块进行赋值
代码块分为普通代码块,构造块,静态块,同步代码块(与线程有关)。
使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,可分为以下四种:
普通代码块:定义在方法中的代码块。
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);
}
}
这种代码块应用较少,一般在面试的时候主要考察相关变量的生命周期等知识,我再这篇博客中进行了讲解【剧前爆米花--C语言篇】局部变量和全局变量详解以及static修饰后的相关变化_困了电视剧的博客-CSDN博客
构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。
public class Javabit_Code{
public static void main(String[] args) {
Student s4=new Student();
Student s1=new Student("001",15,10);
Student s2=new Student("002",15,20);
Student s3=new Student("003",15,30);
System.out.println(s4.toString());
System.out.println(s1.toString());
System.out.println(s2.toString());
System.out.println(s3.toString());
}
}
class Student{
static String classroom;
String name;
int age;
double score;
public Student(){}
public Student(String name,int age,double score){
this.name=name;
this.age=age;
this.score=score;
}
//实例代码块
{
this.name="004";
this.age=15;
this.score=50;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
运行结果如图所示:
使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。
注意:
1.静态代码块不管生成多少个对象,其只会执行一次
2.静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
对于上述两点的解释:
我在关于Java专栏的第一篇博客中详细解释了一份存储Java代码的txt文件 在底层中从编译到执行的全部流程https://blog.csdn.net/m0_62815572/article/details/127573240?spm=1001.2014.3001.5501
当一个java文件被javac程序编译成对应的字节码文件后,这个以.class结尾的字节码文件放入jvm虚拟机在java.exe程序运行加载的过程中,静态代码块中的内容会被执行,这时,静态代码块中的静态成员变量得到初始化,但是由于这个文件只需“放入”jvm或者说是文件只需“加载”一次,所以静态代码块只能执行一次。
3.如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
4.实例代码块只有在创建对象时才会执行
同步代码块的具体内容我会放到线程中进行说明。
以上就是这篇博客的全部内容,如果对你有帮助还请三连,同时如有疏漏,欢迎在评论区指正!