Static关键字
Static关键字的使用
static修饰方法
静态方法
当类的字节码文件加载到内存,类方法的入口地址就会分配完成,所以类方法不仅可以被该类的对象调用,也可以直接通过类名完成调用。
声明为static的方法有以下几条限制:
1)它们仅能调用其他的static 方法。
2)它们只能访问static数据。
3)它们不能以任何方式引用this 或super。
实例方法
当类的字节码文件加载到内存中时,类的实例方法并没有被分配入口地址,只有当该类的对象创建以后,实例方法才分配了入口地址。从而实例方法可以被类创建的所有对象调用,该类的所有对象共享实例方法的入口地址,当该类的所有对象被销毁,入口地址才会消失。
实例方法和静态方法的区别
静态方法 | 实例方法 | |
---|---|---|
调用方式 | 依赖于类,通过类的静态方法调用 | 依赖于类的对象,需要创建对象后通过实例方法使用 |
使用 | 静态方法内部可以定义和使用实例变量,但无法直接调用实例方法 | 实例方法内部不能定义静态变量,但可以直接调用是静态方法 |
使用场景 | 适用于方法与类的对象无关 | 适用于依赖类的对象的方法 |
内存占用 | 只占用一份内存空间 | 只占用一份内存空间 |
static修饰内部类
内部类
内部类是Java语言中重要的组成部分之一。
1.内部类,顾名思义就是在一个类的内部再定义了一个类。例如:
public class TestDemo {
//内部类
private class TestInner{
}
}
2.内部类可以访问其所在类的所有属性,内部类创建自身对象需要先创建其所在类的对象
3.可以定义内部接口,且可以定义另外一个内部类实现这个内部接口
4.内部类不能由static修饰的元素
5.内部类可以嵌套
静态内部类
1.静态内部类就是由static所修饰的内部类
public class TestDemo {
//静态内部类
private static class TestInner{
}
}
2.静态内部类可以定义static元素
3.静态内部类创建自身对象时可以一次性创建出,无需通过所在类的类型创建
静态内部类和非静态内部类的区别
静态内部类 | 非静态内部类 | |
---|---|---|
声明 | 不依赖于外部类对象 | 依赖于外部类对象 |
定义 | 类变量、类方法、常量、成员变量和方法 | 常量、成员变量和方法 |
访问 | 能访问外部类的静态成员变量和静态方法,不能引用外部类的对象(this) | 访问外部类的所有成员和方法 |
static修饰变量
静态变量
静态变量由于不属于任何实例对象,属于类的,所以在内存中只会有一份,在类的加载过程中,JVM只为静态变量分配一次内存空间。
实例变量
每次创建对象,都会为每个对象分配成员变量内存空间,实例变量是属于实例对象的,在内存中,创建几次对象,就有几份成员变量。
静态变量和实例变量的区别
静态变量 | 非静态变量 | |
---|---|---|
语法 | 由static修饰 | 没有static修饰 |
运行区别 | 类变量是所有对象共有,其中一个对象将它值改变,其他对象得到的就是改变后的结果 | 而实例变量则属对象私有,某一个对象将其值改变,不影响其他对象 |
内存 | 类的静态变量在内存中只有一个 | 实例变量取决于类的实例,每创建一个实例,分配一次内存 |
类的初始化顺序
无继承:
静态变量/静态代码块 -> main方法 -> 非静态变量/代码块 -> 构造方法
继承:
1.子类的静态变量和静态初始化块的初始化是在父类的变量、初始化块和构造器初始化之前就完成了;
2.静态变量、静态初始化块顺序取决于它们在类中出现的先后顺序
3.变量、初始化块初始化顺序取决于它们在类中出现的先后顺序
设计模式
单例设计模式
单例设计模式:保证一个类只有一个实例,提供一个访问该类对象实例的全局访问点。
(1)不允许其他程序用new对象,保证对象的唯一性。
new就是指开辟新的空间,在这里更改数据只是更改的所创建的对象的数据,每new一次就创建一次新的空间,无法保证不了对象的唯一性。
(2)在该类中创建对象
因为不允许其他程序new对象,所以这里的对象需要在本类中new出来
(3)对外提供一个可以让其他程序获取该对象的方法
因为对象是在本类中创建的,所以需要提供一个方法让其它的类获取这个对象。
单例模式(饿汉式)
1)私有化类的构造器
2)内部创造静态对象
3) 提供公共静态方法,返回类的对象(可以直接用类名调用)
//饿汉式
class testDemo{
//私有化类的构造器
private testDemo() {
}
//内部创造静态对象
private static testDemo instance=new testDemo();
//提供公共静态方法,返回类的对象(可以直接用类名调用)
public static testDemo getinstance(){
return instance;
}
}
单例模式(懒汉式)
1)私有化类的构造器
2)声明当前类对象,不初始化(对象在方法内初始化)
3)提供公共静态方法,返回类的对象(可以直接用类名调用)
//懒汉式
class testDemo1{
//私有化类的构造器
private testDemo1() {
}
//声明类的对象,不初始化
private static testDemo1 instance=null;
//提供公共静态方法,返回类的对象(可以直接用类名调用)
public static testDemo1 getinstance(){
//判断是否存在对象,如果不存在则创建一个,存在则直接返回对象;
if(instance==null) {
instance = new testDemo1();
}
return instance;
}
}
类加载过程
类加载过程主要是加载–连接–初始化,三个方面,其中连接又可以分为验证–准备–解析。
加载
1.通过全类名获取定义此类的二进制字节流,class文件
2.将字节流所代表的静态存储结构转换为方法区运行时的数据结构,即存储到方法区
3.在内存中生成一个代表该类的java.lang.Class 对象,作为方法区这些数据的访问入口
验证
验证的阶段是十分耗时的,它是重要但非必须。验证是为了使得防止字节流中有安全问题产生。Java的安全性是由编译器所保证的,而类加载是在虚拟机中进行的,当无法确保来源的时候则需要验证。
文件格式验证----->元数据验证----->字节码验证---->符号引用验证
文件格式验证
这个阶段主要验证输入的二进制字节流是否符合class文件结构的规范。二进制字节流只有通过了本阶段的验证,才会被允许存入到方法区中。
其是基于二进制字节流的
加载和验证是交叉进行的,在加载开始后,立即启动了文件格式验证,本阶段验证通过后,二进制字节流被转换成特定数据结构存储至方法区中,继而开始下阶段的验证和创建Class对象等操作。
元数据验证
本阶段对方法区中的字节码描述信息进行语义分析,确保其符合Java语法规范。基于类特定的数据结构的。
字节码验证
本阶段是验证过程的最复杂的一个阶段。通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的。基于类特定的数据结构的。
符号引用验证
本阶段验证发生在解析阶段,确保解析能正常执行。基于类特定的数据结构的。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配,类的静态成员变量也存储在方法区中。
为静态成员变量设置初始值初始值为0、false、null等。
解析
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用限定符7类符号引用进行。
初始化
初始化阶段就是执行类构造器()的过程,是类加载的最后一步。
对于方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 () 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起死锁,并且这种死锁很难被发现。
必须对类进行初始化的情况
1.当遇到 new 、 getstatic、putstatic或invokestatic 这4条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
2.使用 java.lang.reflect 包的方法对类进行反射调用时 。
3.初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
4.当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
双亲委派模型
上图是上面所介绍的这几种类加载器的层次关系,称为类加载器的双亲委派模型。该模型要求除了顶层的启动类加载器外,其它的类加载器都要有自己的父类加载器。如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
优点:使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
避免了多份同样字节码的加载。
其中loadClass以双亲委派的方式去加载,findClass找class文件 (重写)defineClass从class字节码文件加载Class对象,然后class对象通过反射生成对象。