static 的基本用法
🐱 static 关键字的概念
简单用一句话来概括就是:放在没有创建对象的情况下调用,直接用类名去访问就可以 。
🐱 static 关键字修饰类
package keyword.kStatic;
public class StaticTest {
public static class InnerClass {
public InnerClass() {
System.out.println("构造器输出 -----> 静态内部类!");
}
public void InnerMethod() {
System.out.println("静态内部方法!");
}
}
class notStaticInnerClass {
public void notStaticInnerClassMethod() {
System.out.println("非静态内部类方法!");
}
}
public static void main(String[] args) {
// 直接通过 StaticTest 类名访问静态内部类 InnerClass
new StaticTest.InnerClass(); // 输出: 构造器输出 -----> 静态内部类!
// 静态内部类可以和普通类一样使用
System.out.println("----------------------------");
InnerClass innerClass = new StaticTest.InnerClass();
innerClass.InnerMethod();
new StaticTest.InnerClass().InnerMethod(); // 输出: 静态内部方法!
System.out.println("----------------------------");
// 非静态类实例化
StaticTest.notStaticInnerClass notStaticInnerClass = new StaticTest().new notStaticInnerClass();
// 非静态类中的方法引用,必须通过实例名点出方法
notStaticInnerClass.notStaticInnerClassMethod(); // 输出: 非静态内部类方法!
}
}
访问静态内部类和非静态内部类的区别
Ⅰ. 访问区别
1. 静态内部类时可以不用通过实例名访问,可以直接在创建时使用类名访问。
非静态内部类只能先创建外部实例,在通过外部实例创建内部类
🐱 static 关键字修饰方法
修饰方法的时候跟类一样,可以直接通过类名来进行调用。
package keyword.kStatic;
public class StaticMethod {
public static void test() {
System.out.println("静态方法!");
}
public void test2() {
System.out.println("非静态方法!");
}
public static void main(String[] args) {
// 静态方法调用
StaticMethod.test();
// 非静态方法调用
StaticMethod staticMethod = new StaticMethod();
staticMethod.test2();
}
}
区别:静态方法可以直接用类名访问。非静态类需要先实例化,通过实例名访问方法。
🐱 static 修饰变量
package keyword.kStatic;
public class StaticVar {
private String unStaticName = "非静态变量!";
private static String name = "我是静态变量!";
public static void main(String[] args) {
// 用类名点静态变量
System.out.println(StaticVar.name);
// 获取非静态变量
StaticVar staticVar = new StaticVar();
System.out.println(staticVar.unStaticName);
}
}
区别:静态变量可以直接用类名访问。非静态类需要先实例化,通过实例名访问变量。
🐱 static 关键字修饰代码块
静态代码块在类第一次被载入时执行,在这里主要是验证一下,类的初始化顺序。
父类静态变量
父类静态块代码
子类静态变量
子类静态块
父类普通变量
父类普通代码块
父类构造函数
子类普通变量
子类普通代码块
子类构造函数
package keyword.kStatic;
public class Father {
static {
System.out.println("父类静态块");
}
public Father() {
System.out.println("父类构造函数");
}
}
package keyword.kStatic;
public class Son extends Father{
static {
System.out.println("子类静态块!");
}
public Son() {
System.out.println("子类构造函数!");
}
}
package keyword.kStatic;
public class ExecutionOrderTest {
public static void main(String[] args) {
new Son();
}
/**
*父类静态块
* 子类静态块!
* 父类构造函数
* 子类构造函数!
* */
}
深入分析 static 关键字
上面简单的描述了一下 static 的概念和基本使用场景。下面主要解析一下 static 关键字的深层原理。要理解 static 为什么会有上面的特性,首先我们还需要从 jvm 内存说起。下面是一张 Java 的内存结构图,然后通过案例描述一下 static 修饰变量存放在哪里。
从上图可以看出,静态变量放在方法区中,并且是被所有线程共享的,下面我们说一下每个区块的作用。
🐱 堆区
1. 堆区中存储的全部都是对象,每个对象都包含一个与之对应的 class 的信息。(class 的目的是得到操作指令)
2. jvm 只有一个堆区(heap)被线程所共享,堆中不存放基本类型和引用,只存放对象本身。
🐱 栈区
1. 每个线程都包含一个栈区,栈中只保存变量名、自定义对象的引用。
2. 每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3. 栈分为 3 个部分:基本类型变量区、执行环境上下文、操作执行区(存放操作之令)。
🐱方法区
1. 又叫静态区,跟堆一样,被所有线程共享。方法区包含所有的 class 和 static 变量。
2. 方法区中包含的都是在整个程序中永远唯一的元素,如 class、static 变量。
下面我们通过举例说明:
package keyword.kStatic;
public class Person {
static String firstName; // 静态
String lastName;
public void showName() {
System.out.println(firstName + lastName);
}
// 静态
public static void viewName() {
System.out.println(firstName);
}
public static void main(String[] args) {
Person p = new Person();
Person.firstName = "张";
p.lastName = "三";
p.showName(); // 输出张三
Person p2 = new Person();
p2.firstName = "李";
p2.lastName = "四";
p.showName(); // 输出:李四
Person.viewName(); // 输出张
}
}
从内存角度出发分解上面代码
🐱 下面对 static 关键字做一个总结
Ⅰ.特点
1. static 是一个修饰符,用于修饰成员(成员变量、成员函数、内部类),static 修饰的成员变量称之为静态变量、静态方法(函数)、静态类。
2. static 修饰的成员被所有线程所共享。
3. 被 static 所修饰的成员都优先于对象存在,因为 static 是随着类的加载就已经存在。
4. 被 static 所修饰的成员多了一种调用方式(类名.静态成员)。
5. static 修饰的数据都是共享数据,对象中存储的数据都是特有数据。
Ⅱ.成员变量和静态变量
1. 生命周期不同
成员变量随着对象的创建而存在,随着对象的回收而释放。
静态变量随着类的加载而加载,随着类的消失而消失。
2.调用方式不同
成员变量只能先实例化再调用。
静态变量可以被对象调用,也可用类名调用。
3. 别名不同
成员变量也称为实例变量。
静态变量也成为类变量。
4. 存储位置不同
成员变量存储在堆区中,所以也叫对象特有数据。
静态变量数据存储在方法区中(共享区域),所以也叫对象的共享数据。
Ⅲ. 静态使用注意事项
1. 静态方法只能访问静态成员。非静态可以访问静态,也可以访问非静态。
2. 静态方法中不可以使用 this、supper 关键字。
3. 主函数是静态的。