想要清晰理解java语法,不了解java和jvm的机制是不行的,以前不理解java中用static修饰方法和变量为什么不可以访问非静态方法和数据,现在明了,如果你也有相同的困惑,这篇博客足以解惑,原创不易,转载请声明出处。 本文分为3大部分
static的用法和例子简析java类加载机制为何java中static静态数据无法访问非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(初始化变量,如果变量是对象的话,会声明一个这个对象的句柄,但是不会为这个对象分配空间)。接着继续执行构造函数构造对象,以确保构造出来的对象没有问题。
类加载器顾名思义,类加载器(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 类。类的生命周期:
终于到了解答自己内心困惑的时候了 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这条规则满足我们的需要。