ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Static由浅入深

2021-02-15 12:30:25  阅读:139  来源: 互联网

标签:由浅入深 调用 静态方法 变量 static println Static 父类


转载请标明文章出处:https://blog.csdn.net/zengsao/article/details/113797489

文章目录


前言

我们在使用一个类时,通常要先申明一个指向这个类对象的引用,通过这个引用,我们可以使用类中的相关变量和方法。但经过static修饰的变量和方法,我们可以直接通过类调用,即类名.xxx的形式。这是为什么呢?本篇博文将带大家从基础开始了解,并逐渐深入底层,带你走近Static。


一、认识Static

1、Static的基本介绍

在《Java编程思想》P86页有这样一段话:
  “static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。”
  从这句话我们能够得出static基本用途之一:方便在没有对象的时候直接调用类的变量与方法。
  另外也可以编写static代码块来优化程序性能

2、Static的用法

  1)static常量
  2)static方法
  3)static代码块

3、Static用法实例

1)static常量
  这个没什么好说的,值的注意的是:在一个类中,就算常量被static修饰,我们依旧可以在非静态方法中用this调用。

public class Demo03 {
    public static void main(String[] args) {
        new staclass().method01();
        //运行结果:我不是静态方法2 1

    }
}
class staclass{
    static int i = 1;
    public void method01(){
        int i = 2;
        //this代表当前对象,当前对象的i就是成员变量i
        System.out.println("我不是静态方法"+i+" "+this.i);
    }
    public static void method02(int i){
        System.out.println("我是静态方法");
    }

}

2)static方法
  static方法就是没有this的方法,因此他可以直接通过类名来调用。这里说一下,什么叫做没有this的方法呢?
  细心的朋友可能疑问,为什么我们在非静态的方法里可以用this来调用类的变量和方法呢,这是因为,在编译阶段,this就作为参数被隐式传到非静态方法中,而静态方法则没有。

下面举例上面代码两个方法的局部变量表,从中可以看出:
method01:
在这里插入图片描述
method02:
在这里插入图片描述
实例代码如下:

public class Demo03 {
    public static void main(String[] args) {
        staclass.method03();
        //运行结果
        //1
        //我是静态方法

    }
}
class staclass{
    static int i = 1;
    int j = 2;
    public void method01(){
        System.out.println("我不是静态方法");
    }
    public static void method02(){
        System.out.println("我是静态方法");
    }
    public static void method03(){
        //静态方法可以调用静态变量和方法
        System.out.println(i);
        method02();
        //编译报错,静态方法不能调用非静态变量和方法
        //System.out.println(j);
        //method01();
        //静态方法内也没有this参数
        //this.method01();

    }

}

3)static代码块
  静态初始化块,用于类的初始化操作。在静态初始化块中不能直接访问非staic成员。
  static块的作用:静态初始化块的作用就是:提升程序性能。为什么能提升性能呢?且看下面代码(转载而来):

class Person{
    private Date birthDate;

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    boolean isBornBoomer() {
        Date startDate = Date.valueOf("1946");
        Date endDate = Date.valueOf("1964");
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

isBornBoomer是用来这个人是否是1946-1964年出生的,而每次isBornBoomer被调用的时候,都会生成startDate和endDate两个对象,造成了空间浪费,如果改成这样效率会更好:

class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    static{
        startDate = Date.valueOf("1946");
        endDate = Date.valueOf("1964");
    }

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    boolean isBornBoomer() {
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

上面的代码中,每次在调用isBornBoomer时,startDate和endDate两个对象只需要在第一次初始化被创建即可。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行
  静态初始化块可以置于类中的任何地方,类中可以有多个静态初始化块。在类初次被加载时,会按照静态初始化块的顺序来执行每个块,并且只会执行一次。
  另外,static不会改变类中成员的访问权限,什么意思呢?被private修饰的成员变量不会因为被static修饰就丧失其隐蔽性。并且,static不能作用于局部变量。

二、Static的使用情形


1、调用父类static修饰的常量和方法

  1)首先,当子类继承父类后,在子类中是可以调用父类的静态方法,当父类静态方法被子类相同的静态方法隐藏后将会调用子类隐藏父类静态方法的方法。
  2)再次,明确一点,当类中的变量或者方法声明为static时,这个方法就和此类创建的对象脱离了关系
  3)假如声明一个父类引用,创建了一个子类对象。调用父类的静态方法,不管这个方法有没有被子类隐藏,依然会执行父类的静态方法。
  4)当你声明一个子类引用,其指向子类自己的对象,再次执行相应的静态方法,这时,没有隐藏父类静态方法的子类执行的是父类的静态方法,有隐藏父类静态方法的子类执行的是子类自己的静态方法。
  5)对于被static修饰的变量和方法,一般采用类名直接调用
  下面我们举一个梨子来说明:

public class Demo02 {
    public static void main(String[] args) {
        Father Foldson = new oldSon();
        System.out.println("--------------向上转型调用父类的静态方法---------------------");
        Foldson.fmethod01();
        Father Fyoungson = new youngSon();
        Fyoungson.fmethod01();
        System.out.println("-------------子类对象调用父类的静态方法----------------------");
        oldSon oldson = new oldSon();
        oldson.fmethod01();
        youngSon youngson = new youngSon();
        youngson.fmethod01();
        System.out.println("-------------在子类中调用父类的静态方法----------------------");
        oldson.omethod();
        youngson.ymethod();
        System.out.println("-------------在子类中调用父类的静态变量----------------------");
        System.out.println(oldson.i);
        System.out.println(youngson.i);
        youngson.i = 2;
        System.out.println(oldson.i);
        System.out.println(oldson.j);
        /*从上面调用父类的静态变量的结果可以看出:
        1.子类是不会继承父类静态变量修饰的变量和方法的
        2.当子类中有和父类相同的变量和方法时,则可以用子类引用调用的是自己的变量和方法
        (因为父类的变量和方法被隐藏了)*/
        /*运行结果
        --------------向上转型调用父类的静态方法---------------------
        我是父类的静态方法
        我是父类的静态方法
        -------------子类对象调用父类的静态方法----------------------
        我是父类的静态方法
        我是小儿子中覆盖父亲的静态方法
        -------------在子类中调用父类的静态方法----------------------
        我是大儿子的独有方法,调用没有被覆盖的父类的静态方法,调用结果为:
        我是父类的静态方法
        我是小儿子的独有方法,调用被我覆盖的父类静态方法,调用结果:
        我是小儿子中覆盖父亲的静态方法
        -------------在子类中调用父类的静态变量----------------------
        1
        1
        2
        22
        * */


    }
}
class Father{
    static int i = 1;
    static int j = 11;
    public static void fmethod01(){
        System.out.println("我是父类的静态方法");
    }
}
class oldSon extends Father{
    int j = 22;
    public void omethod(){
        System.out.println("我是大儿子的独有方法,调用没有被覆盖的父类的静态方法,调用结果为:");
        fmethod01();
    }
}
class youngSon extends  Father{
    public static void fmethod01(){
        System.out.println("我是小儿子中覆盖父亲的静态方法");
    }
    public void ymethod(){
        System.out.println("我是小儿子的独有方法,调用被我覆盖的父类静态方法,调用结果:");
        fmethod01();
    }


}

2、调用接口中的静态方法

  直接抛结论,接口中的静态方法是不能被他的实现类调用的,只能以 接口名.xxx() 这样的形式调用,可能有人要问,那接口的静态变量呢,在这里要说一句,接口的变量默认都是 public final static修饰,可以被调用,但不可以被改变,因为其就是一个全局常量。同样的,当其实现类有相同的变量名时,子类引用调用就是自己的变量。

示例代码如下:

public class Demo05 {
    public static void main(String[] args) {
        Human.say();
        Human man = new Man();
        //man.say();编译报错
        System.out.println(man.i);
        Man man1 = new Man();
        System.out.println(man1.i);
        /*运行结果:
          我是人类
          1
          22*/
    }

}
interface Human{
     public final static int i = 1;
     public static void say(){
        System.out.println("我是人类");
    }

}
class Man implements Human {
     int i = 22;
     public static void say(){
        System.out.println("我是男人");
    }


}

拓展:
  1. 在 jdk 7 或更早版本中,接⼝⾥⾯只能有常量变量和抽象⽅法。这些接⼝⽅法必须由选择实
    现接⼝的类实现。

  2. jdk 8 的时候接⼝可以有默认⽅法和静态⽅法功能。

  3. Jdk 9 在接⼝中引⼊了私有⽅法和私有静态⽅法。


三、Static变量底层细节

1、类加载过程

  为什么静态变量在没有对象实例的时候就可以访问呢?当静态变量被static修饰的时候,它就称为类变量,与类的对象就脱离了关系,在类加载阶段,其值就已经确定,如果没有给他赋值,则值是0,这点跟非静态的一样,注意,虽然一样,但可不是同时赋值的。要想了解具体的过程,我们首先来看下面这张图:
  这是字节码文件加载(跟图里面的加载是两回事)到内存中的步骤:

在这里插入图片描述

加载阶段:读取字节码文件的二进制流,在方法区(概念)中生成类模板数据,在堆内存中生成一个代表这个类并指向方法区中类模板数据的java.lang.Class对象,作为方法区这个类各种数据的访问入口。

验证阶段:主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证(其中格式验证会和加载阶段一起执行,验证通过后,类加载器才会成功将类的二进制数据信息加载到方法区,其余三个验证操作在方法区中运行)。

准备阶段:为类变量分配内存并且设置该类变量的默认初始值,即0值真正的值将在初始化阶段赋值,如果程序中没有给static变量赋值,那这里的0就是导致我们前面说的值为0的原因)。这里不包括final修饰的static,final在编译的时候就会分配,准备阶段会显式初始化,即值是多少就赋值多少。这里也不会为实例变量(非静态)分配初始化。

解析阶段:将常量池内的符号引用转换为直接引用的过程。事实上,解析操作往往会伴随着jvm在执行完初始化之后再执行。

初始化阶段:执行类构造器()的过程,注意此方法不是类的构造器,它是由类静态成员的赋值语句以及static语句块合并产生的。简而言之,为类的静态变量赋值正确的初始值父类的()总是在子类()之前被调用,也就是说,父类的static块优先级高于子类。
  这就是我们能直接通过类调用静态变量的原因。下面通过一道笔试题加深一下印象:

2、static笔试题(转载)

public class Test {
    Person person = new Person("Test");
    static{
        System.out.println("test static");
    }

    public Test() {
        System.out.println("test constructor");
    }

    public static void main(String[] args) {
        new MyClass();
    }
}

class Person{
    static{
        System.out.println("person static");
    }
    public Person(String str) {
        System.out.println("person "+str);
    }
}


class MyClass extends Test {
    Person person = new Person("MyClass");
    static{
        System.out.println("myclass static");
    }

    public MyClass() {
        System.out.println("myclass constructor");
    }
}

输出结果为:

test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor

分析过程:
  找到main方法入口,main方法是程序入口,但在执行main方法之前,要先加载Test类

  加载Test类的时候,发现Test类有static块,而是先执行static块,输出test static结果

  然后执行new MyClass(),执行此代码之前,先加载MyClass类,发现MyClass类继承Test类,而是要先加载Test类,Test类之前已加载

  加载MyClass类,发现MyClass类有static块,而是先执行static块,输出myclass static结果

  然后调用MyClass类的构造器生成对象,在生成对象前,需要先初始化父类Test的成员变量,而是执行Person person = new Person(“Test”)代码,发现Person类没有加载

  加载Person类,发现Person类有static块,而是先执行static块,输出person static结果

  接着执行Person构造器,输出person Test结果

  然后调用父类Test构造器,输出test constructor结果,这样就完成了父类Test的初始化了

  再初始化MyClass类成员变量,执行Person构造器,输出person MyClass结果

  最后调用MyClass类构造器,输出myclass constructor结果,这样就完成了MyClass类的初始化了

3、Static变量位置变动

  在网上看了很多资料,发现很多人的内容都很老,其中对静态变量的位置有很多错误,这里强调一下:
  从jdk1.7开始,静态变量和字符串常量池就已经迁移到堆中了,静态变量在此类的java.lang.Class对象的尾部。详细可以参考这篇博客:https://blog.csdn.net/x_iya/article/details/81260154

总结

  1.当被static修饰后,静态变量或者静态方法就与类相关联了起来,可以通过 类名.xxx或类名.xxx()调用。

  2.在继承父类或者是实现接口的时候,静态变量和方法不会被继承,只会被子类相同的变量和方法隐藏,没隐藏之前,子类引用是可以调用到的,接口实现类和子类对static方法的区别是,接口实现类不能调用接口的静态方法,只能通过 接口名.xxx() 调用。

  3.静态变量在类加载阶段的初始化阶段完成真正赋值,如果代码中没有给静态变量赋值,则默认的值是准备阶段的0值,父类的类构造器总是在子类的类构造器之前先执行。(由父及子,静态先行)

  4.从jdk1.7开始,静态变量和字符串常量池就已经迁移到堆中了,静态变量在此类的java.lang.Class对象的尾部。

标签:由浅入深,调用,静态方法,变量,static,println,Static,父类
来源: https://blog.csdn.net/zengsao/article/details/113797489

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有