首先介绍关键字修饰变量
关键字所代表的概念是全局的、静态的,用它修饰的变量称为静态变量。
public class TestStatic {
static int i = 10; // 定义了一个静态变量 i
}
静态变量也称为类变量。 静态变量属于此类。 这意味着什么? 这实际上意味着关键字只能定义在类的{}中,而不能定义在任何方法中。
即使去掉方法中的关键字,也是一样的。
属于类,修改后的变量直接被类调用。 它不需要手动实例化该类来调用它。
public class TestStatic {
static int i = 10;
public static void main(String[] args) {
System.out.println(TestStatic.i);
}
}
这里需要理解几个变量的概念
详情请参阅
修改方法
方法可以修改。 修改后的方法称为静态方法。 其实就是在方法定义中添加关键字来修改,比如下面
static void sayHello(){}
《Java编程思想》P86页有经典描述
方法就是没有这个的方法。 非静态方法不能在内部调用,但反之亦然。 您可以仅通过类本身调用方法,而无需创建任何对象。 这实际上是方法的主要目的。
很重要的一句话是,没有这个方法就是方法。 换句话说,您可以在不创建对象的情况下访问方法。 怎么做到这一点?看下面这段代码
在上面的例子中,由于它是一个静态方法,因此可以使用类名和变量名来调用它。
因此,如果想在不创建对象的情况下调用方法,可以将此方法设置为 。 我们最常看到的方法就是main方法。 至于为什么一定是main方法,现在应该清楚了。 因为程序在执行main方法时并没有创建任何对象,所以只能通过类名来访问。
修改方法时的注意事项
装饰代码块
关键字可用于修改代码块。 代码块分为两种,一种是使用{}代码块; 另一个是{}静态代码块。 修改后的代码块称为静态代码块。 静态代码块可以放置在类中的任何位置。 类中可以有多个块。 当类第一次加载时,会按照代码块的顺序执行。 每个修改的代码块只能执行一次。 我们见面的时候会讲一下代码块的加载顺序。下面是一个静态代码块的例子
代码块可以用来优化程序执行顺序,因为它们具有以下特点:它们只会在类加载时执行一次。
用作静态内部类
内部类的使用场景相对较少,但是内部类还是有一些有用的用途的。在了解静态内部类之前,我们先来看看内部类的分类
静态内部类是修改的内部类。 静态内部类可以包含静态成员或非静态成员,但不能在非静态内部类中声明静态成员。
静态内部类有很多功能。 由于非静态内部类实例的创建需要引用外部类对象,因此非静态内部类对象的创建必须依赖于外部类的实例; 而静态内部类实例的创建只需要依赖外部类对象。 种类;
并且由于非静态内部类对象持有外部类对象的引用,因此非静态内部类可以访问外部类的非静态成员; 而静态内部类只能访问外部类的静态成员;
public class ClassDemo {
private int a = 10;
private static int b = 20;
static class StaticClass{
public static int c = 30;
public int d = 40;
public static void print(){
//下面代码会报错,静态内部类不能访问外部类实例成员
//System.out.println(a);
//静态内部类只可以访问外部类类成员
System.out.println("b = "+b);
}
public void print01(){
//静态内部内所处的类中的方法,调用静态内部类的实例方法,属于外部类中调用静态内部类的实例方法
StaticClass sc = new StaticClass();
sc.print();
}
}
}
静态导包
不知道大家有没有注意到这个现象。 例如,使用java.util中的工具类时,需要导入java.util包才能使用其内部工具类,如下
但是还有另一种方法可以使用静态导入来导入包。 静态导入是使用静态方法或静态变量来导入某个类或包。
import static java.lang.Integer.*;
public class StaticTest {
public static void main(String[] args) {
System.out.println(MAX_VALUE);
System.out.println(toHexString(111));
}
}
先进知识
了解了关键词的用法后,我们来看看深入的用法,即由浅入深,慢慢来,前戏充足~
关于类别
修改的属性和方法属于类,不会属于任何对象; 它们通过类名、属性名/方法名来调用,实例变量和局部变量属于特定的对象实例。
修改变量的存储位置
首先,我们来了解一下JVM的不同存储区域。
可变的生命周期
变量的生命周期与类的生命周期相同。 它在类加载时创建,在类销毁时销毁。 普通成员变量与其所属的成员变量具有相同的生命周期。
序列化
我们知道,序列化的目的是将Java对象转换为字节序列。 该对象被转换为有序的字节流,以便可以通过网络传输或保存在本地文件中。
声明为 和 类型的变量无法序列化,因为修改后的变量存储在方法区中,只有堆内存会被序列化。 关键字的作用是防止对象被序列化。
类加载顺序
前面我们提到过类加载顺序的概念。 修改的变量和静态代码块在使用前已经初始化。 类的初始化顺序是
加载父类的静态字段 -> 父类的静态代码块 -> 子类的静态字段 -> 子类的静态代码块 -> 父类的成员变量(非静态字段)
-> 父类非静态代码块 -> 父类构造函数 -> 子类成员变量 -> 子类非静态代码块 -> 子类构造函数
常用于日志打印
在开发过程中,我们经常会使用关键字进行日志打印。 您应该经常看到以下代码行。
private static final Logger LOGGER = LogFactory.getLoggger(StaticTest.class);
但是,您可以通过删除和最终打印日志。
private final Logger LOGGER = LogFactory.getLoggger(StaticTest.class);
private Logger LOGGER = LogFactory.getLoggger(StaticTest.class);
但是这种打印日志的方式有一个问题
每个实例化对象都会有一个。 如果创建1000个对象,就会多出1000个对象,造成资源浪费。 因此,对象通常被声明为变量,这样可以减少内存资源的使用。 占据。
通常用作单例模式
由于单例模式意味着不同的类只有一份副本,因此它可以完全匹配单例模式。
下面是双重验证锁实现单例模式的经典场景。
public class Singleton {
private static volatile Singleton singleton;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
我们对上面的代码进行简单的说明
使用保证变量是静态的,使用保证变量的可见性,使用私有构造函数保证不能被new实例化。
使用.()获取对象时,会先进行判断。 如果为空,则类对象将被锁定。 这里有的朋友可能不知道为什么需要两个判断。 这是一个解释。
如果线程t1执行到==null,则判定该对象为null。 此时,线程将执行权交给t2。 T2判断对象为null,锁定类对象,并进行如下判断和实例化过程。 如果不进行第二次判断,t1在第一次空判断后也会走一遍实例化过程,此时仍然会创建多个对象。
类的构造函数是否为
相信大多数朋友都没有考虑过这个问题。 在Java编程思想中,有一种说法,类的构造函数虽然没有被修饰,但实际上是一个方法,但是没有给出实际的解释,但是这个问题可以从以下几个方面来回答
public class StaticTest {
public StaticTest(){}
public static void test(){
}
public static void main(String[] args) {
StaticTest.test();
StaticTest staticTest = new StaticTest();
}
}
我们看一下javap -c生成的字节码
public class test.StaticTest {
public test.StaticTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void test();
Code:
0: return
public static void main(java.lang.String[]);
Code:
0: invokestatic #2 // Method test:()V
3: new #3 // class test/StaticTest
6: dup
7: invokespecial #4 // Method "":()V
10: astore_1
11: return
}
我们发现调用方法时使用该指令,new对象调用该指令,并且在JVM规范#jvms-6.5中。
从这个角度来看,指令是专门用来执行方法的指令; 指令专门用于执行实例方法; 从这个角度来看,构造函数不是静态的。