深入java static关键字 浅析java类加载机制(解答java静态方法或变量无法访问非静态数据)

来源:互联网 发布:各种编程语言的用途 编辑:程序博客网 时间:2024/06/03 19:19

想要清晰理解java语法,不了解java和jvm的机制是不行的,以前不理解java中用static修饰方法和变量为什么不可以访问非静态方法和数据,现在明了,如果你也有相同的困惑,这篇博客足以解惑,原创不易,转载请声明出处。
本文分为3大部分

  • static的用法和例子
  • 简析java类加载机制
  • 为何java中static静态数据无法访问非static数据,但是反过来却可以

    1.static用法和解析

    类中静态数据是在类被加载(ClassLoader类的loadClass方法,大致的情况我们待会在第二部分说明)时初始化的。为什么会加载这个类,因为我们尝试使用这个类的时候,类就会被加载进内存里。
    举例说明:

/** 1. Created by Yangsheng on 2017/4/12. */public class StaticTest {    //private int allNum = changeNum *2;    private int changeNum = 0;    public static int X;//X放在后面会提醒X非法    public static int Y = X * 2;    StaticTest(int changeNum){        this.changeNum = changeNum;    }    int getChangeNum(){        return changeNum;    }    void setChangeNum(int changeNum){        this.changeNum = changeNum;    }    {        X = 10;//非static模块可以访问static变量        changeNum = 100;    }    {        changeNum = 99;    }    static {        Y = 1;        System.out.println("YYY");    }    static {        X = 30;       //changeNum = 100;static模块不可以访问非static变量    }    int getLength(String s){        return s.length()*2;    }    public static void main(String[] args) {        System.out.println(X); //输出60        StaticTest staticTest =  new StaticTest(1);        System.out.println(staticTest.Y);        System.out.println(X);        System.out.println(staticTest.getChangeNum());        /**         * static属性会在类加载进如虚拟机的时候声明,必须按照顺序声明,因为虚拟机一看到static就会将它对应的属性和函数块作为         *         * static{}(静态方法会在JVM将类加载进来的时候按照顺序从上往下执行         * {}会在对象生成的时候按照顺序从上往下执行         * static{}         *         */    }}

这里写图片描述
我们先看一下运行的结果:
这里写图片描述
1.程序运行的时候,我们从main方法进入,因为main方法是在StsticTest类中的,JVM会先加载StaticTest这一个类,当程序在加载Static变量的时候,JVM会将类里面声明到的静态变量在栈内存里面开辟空间保存好,这里就是X、Y,可是这个时候并没有初始化。这里我们还需要注意到:
我们在程序里面声明了:
public static int X;
public static int Y = X * 2;
若X放在后面会提醒X非法,因为虚拟机在加载类的时候是将static变量从上到下依次进栈,如果还没声明就使用那肯定就非法了。

2.程序会从上到下依次执行static的代码块(因为static的变量和块是属于类的),这个时候Y = 1;X = 30;

3.我们在执行new StaticTest( ),在堆内存里面开辟空间生成StaticTest对象的时候,程序执行构造函数 StaticTest(int changeNum),但是在正式构造对象之前,会将非静态代码块
这里写图片描述
依次执行,执行完毕之后,接着在栈内存中初始化int changeNum = 0(初始化变量,如果变量是对象的话,会声明一个这个对象的句柄,但是不会为这个对象分配空间)。接着继续执行构造函数构造对象,以确保构造出来的对象没有问题。

2.简析java类加载机制(Classloader具体的实现以后学完java虚拟机再写)

类加载器顾名思义,类加载器(classloader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用 Java 类的方式如下:

  • Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。
  • 类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。
  • 实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
    基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。下面详细介绍这个 Java 类。
    java.lang.ClassLoader类介绍java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载 Java 应用所需的资源,如图像文件和配置文件等。不过本文只讨论其加载类的功能。为了完成加载类的这个职责,ClassLoader提供了一系列的方法,挑选其中几个很重要的方法先看一下:
  • 1. getParent() 返回该类加载器的父类加载器。
  • 2. loadClass(String name) 加载名称为 name的类,返回的结果是java.lang.Class类的实例。
  • 3. findClass(String name)查找名称为 name的类,返回的结果是java.lang.Class类的实例。
  • findLoadedClass(String name)查找名称为 name的已经被加载过的类,返回的结果是 java.lang.Class类的实例。
  • 4. defineClass(String name, byte[] b, int off, int len)把字节数组 b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为 final的。
  • 5. resolveClass(Class c)链接指定的 Java 类。

类的生命周期:
这里写图片描述

3.为何java中static静态数据无法访问非static数据,但是反过来却可以

终于到了解答自己内心困惑的时候了
1.在类被调用的时候,类加载器根据一个指定的类的名称,找到或者生成其对应的字节代码(.class文件),然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例。
这里写图片描述
加载完毕后,只要不卸载这个类,那这个类就会一直存在内存当中,所以我们常常会说“类只会被加载一次”。(
类卸载满足的条件:
- 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
- )
2.在定义类的时候,虚拟机会将字节码对应的static变量从上到下依次初始化,然后执行static代码块。这个时候内存中就有了这些Static变量。

3.在类加载完毕,同时我们生成这个类的实例的时候,这个时候类执行构造函数,在构造对象之前,会先执行非静态代码块,然后初始化非static变量,最后在执行构造函数里面的构造内容构造出一个对象。

重点:

1.类在加载的时候会初始化static变量,但是没有对非static变量声明和初始化,如果我们在static方法中调用类非static变量的话,就极有可能出错,当然java是不允许的。所以在编译阶段,就会报错。

2.我们是先“生成”类,在“生成”对象(类的实例);所以当我们用非static的类方法去访问类的static变量的时候(static变量是在类加载的时候初始化的),static变量一定是存在内存里面了,不会出现任何问题。

3.为什么会有static关键字(个人理解):因为针对一些开发场景,我们想要一些关键数据在类加载的时候就被初始化,而且这些数据类对象的所有成员都可以访问;例如游戏。
对于static方法,我认为是:假如我们想“制造”一些方法,这些方法不想通过生产实例去调用,想直接使用,在类加载的时候就硬编码进去,提升效率,而且不许要复杂的对象生成的过程。天才的设计师们就用static这条规则满足我们的需要。

1 0
原创粉丝点击