static是什么?
static关键字就像是程序界的“共享单车”,它可以让某些东西变成“公共财产”。
——拿生活中的例子来说,你们宿舍楼有一台公共洗衣机(static变量),所有宿舍成员(对象)都可以使用它,不需要每个宿舍都买一台。
——正经一点来说,static是java中的一个关键字,用于表示某个成员(属性/方法)属于类本身而不是类的实例。
一、三大用法
下面 三种用法 光看可能有点干巴,有不懂的地方也没事,介绍完我会一一解释说明。
1、修饰变量
作用:
修饰成员变量,使其变成静态成员变量,静态成员变量属于类。
特性:
① 这个类下面的所有对象都可以使用;
② 对象能够访问静态成员属性,一般建议,对象做查询;
类也能够访问静态成员属性,一般建议,类做修改;
③ 它随着类的加载而加载,先于对象存在;
④ 尽量不要在构造方法中设置静态成员变量的值,否则每次新建对象,这个值都会被重新设置。
好,那么问题来了!
为什么建议对象做查询,而类做修改呢?
首先我们回忆一下前面说的,静态成员变量属于类
既然如此,我们为什么要多此一举用“ 对象名.静态成员变量 ”呢?
所以,为了避免修改数据时,误认为这个变量是属于对象的
在对值进行操作的时候,一般使用“ 类名.静态成员变量 ”
而在查询时,一般是要查询各个对象单独的状态,这个时候可以使用“对象名.静态成员变量 ”
好,那么问题又来了!
静态成员变量是怎么随着类加载而加载的呢?它先于对象又有什么作用吗?
这个问题我们放到后面,讨论完静态方法和静态代码块再来详细解释
2、修饰方法
作用:
同样将成员方法修饰为静态,可以通过类名直接调用,主要用来操作static成员(属性/方法)
特性:
① static方法只能访问static成员(属性/方法);
② 也可以用对象来调用,但是推荐用类名;
③ static方法中调用的方法只能是static方法;
④ static方法中没有 this 和 super 关键字;
为什么static方法不可以访问非static成员,而非static方法可以访问static成员呢?
前面提到了:静态成员随着类的加载而加载,先于对象存在
这句话先用在这里,等会我们再解释
那么也就是说,只要类存在,静态成员如果定义了就会跟着存在
此时聪明的同学已经发现了
非 static 方法访问时,static 成员一定存在;
而 static 方法访问时,对象 不一定被创建了。
所以 static 方法不能访问 非static 的成员,不存在还访问个der
第二个问题,为什么static方法中没有 this 关键字?
这就不得不提到内存图了,我们通过内存图看一下 this 是什么作用,来吧展示:
public static void main(String[] args){
Student stu1 = new Student("jack",21);
stu1.sayHello();
Student stu2 = new Student("Tom".18);
stu2.sayHello();
}
通过这个图不难发现,此时 this 的作用是代表当前对象实例的引用
既然我们static属于类,都不需要对象,没有对象哪来的 this?
super 呢也是大同小异,super代表的是当前对象的父类实例的引用
那么 this 和 super 的详细功能有哪些呢?
内个,this 我暂时还没写哈,等我写完把链接放到这里~
super 在下文讲加载顺序时会提到
3、修饰代码块
static{
//代码
}
特性:
同样是类加载的时候执行
这个没什么好说的,我们直接老规矩吧
什么是代码块?
代码块其实就是用 { } 括起来的代码
可以分为三类:
1、局部代码块
方法中的“临时工”,用完就消失
权限修饰符 数据类型 方法名(形参列表){
//局部代码块
}
定义位置:
类的方法中定义
作用:
用于限定变量的生命周期,使其尽早释放,提高栈空间的内存利用率
2、构造代码块(匿名代码块)
每个对象创建前的“热身运动”,没名字所以又叫匿名代码块
{
//构造代码块
}
定义位置:
类中,方法外
作用:
每次构造方法执行前,都会先执行该代码中的代码块
如果每个构造方法中都有相同的方法,可以抽取出来放在构造代码中
提高代码复用率
3、静态代码块
类加载时的“开机自检”,只执行一次
static{
//静态代码块
}
定义位置:
类中,方法外
作用:
初始化静态变量或者执行只需要执行一次的代码
二、静态成员变量和非静态成员变量的区别
接下来我们从不同角度分析一下这俩 “双胞胎” 的对比
1、存储位置
静态成员变量:存储在方法区中的静态区
非静态成员变量:存储在堆内存的对象内
public class StorageExample {
// 静态成员变量 – 存储在方法区的静态区
static int staticVar = 10;
// 非静态成员变量 – 存储在堆内存的对象内
int instanceVar = 20;
public static void main(String[] args) {
StorageExample obj1 = new StorageExample();
StorageExample obj2 = new StorageExample();
// 静态变量被所有实例共享
System.out.println("obj1.staticVar: " + obj1.staticVar); // 10
System.out.println("obj2.staticVar: " + obj2.staticVar); // 10
// 修改静态变量影响所有实例
StorageExample.staticVar = 30;
System.out.println("obj1.staticVar: " + obj1.staticVar); // 30
System.out.println("obj2.staticVar: " + obj2.staticVar); // 30
// 非静态变量每个实例独立拥有
obj1.instanceVar = 40;
obj2.instanceVar = 50;
System.out.println("obj1.instanceVar: " + obj1.instanceVar); // 40
System.out.println("obj2.instanceVar: " + obj2.instanceVar); // 50
}
}
2、生命周期
静态成员变量:从类加载开始到程序结束
非静态成员变量:从对象创建到对象被垃圾回收器回收
3、出现顺序
静态成员变量:随着类加载而加载出现
非静态成员变量:对象实例化时出现
4、调用方式
静态成员变量:通过类名直接调用
非静态成员变量:通过对象实例调用
5、初始化时机
静态成员变量:累加载时初始化
非静态成员变量:对象创建时初始化
6、内存占用
静态成员变量:每个类只有一份
非静态成员变量:每个对象都有一份
7、共享
静态成员变量:同一个类的所有对象共享一份数据
非静态成员变量:每个对象有独立的数据
这些比较容易理解,这里就偷个懒不解释啦
我们着重讨论后面的重点
三、static 方法和 非static 方法的区别,什么情况下用static方法?
区别:
除了前面提到的互相能否访问、能否使用 this 和 super 之外,还有以下区别:
static 方法和 非static 方法虽然字节码都存储在方法区
但是在运行时,非static 方法与 堆内存 有着密不可分的联系。
原因在于,static 方法可以被类直接调用,而 非static 方法需要对象实例调用
非static 方法操作的成员变量存储在对象实例中,而 对象实例 存储在堆内存中
所以需要 this 指针隐式访问堆内存中的对象
使用:
1、工具类里的方法
2、若方法只需要访问静态成员,可以生命 static 防止冗余的对象创建
3、实现单例模式
诶
什么是单例模式?
单例模式就是一个类中只有一个实例(就像地球只有一个太阳),并且私有化构造方法,防止外部 new 操作,同时提供一个全局可以访问该实例的方法,直接上代码:
public class Singleton {
// 1. 静态变量存储唯一实例(类加载时立即初始化)
private static final Singleton instance = new Singleton();
// 2. 私有构造器阻断外部实例化
private Singleton() {
System.out.println("Singleton instance created");
}
// 3. 全局访问点
public static Singleton getInstance() {
return instance;
}
// 示例方法
public void showMessage() {
System.out.println("Hello from Singleton!");
}
}
注意,这里的数据类型就是我们创建的类,返回值就是对象:
public static Singleton getInstance() {
return instance;
}
四、能否继承 static 成员变量或方法?
在权限允许的情况下是可以的(比如 private 不可以)
继承后,子类可以直接访问父类的静态成员变量和方法,但子类无法覆盖父类的静态方法,只能隐藏。
什么意思呢?
如果子类 没写 和父类同名的静态成员,那么 子类名.静态成员 或 父类名.静态成员 是等效的;如果写了同名静态成员,那么 子类名.静态成员 调用的就是子类的,父类名.静态成员 调用的就是父类的
子类与父类共享同一个静态成员内存空间,修改一处全局生效
五、运行顺序
前面我们提到了很多概念,比如说 类加载、各种代码块、静态方法等等
那么它们的执行顺序是什么呢?我们先来看三个案例:
public class T1 {
public T1() {
System.out.println("构造器");
}
public void info() {
System.out.println("info");
}
static {
System.out.println("test static 1");
}
public static void main(String[] args) {
new T1().info();
}
{
System.out.println("代码块");
}
static {
System.out.println("test static 2");
}
}
结果:
test static 1
test static 2
代码块
构造器
Info
原因:
首先进入main,需要创建Test对象,然后调用Test的方法,检查是否加载Test类,发现没有,开始加载Test类:
由于没有静态成员初始化,直接加载两个静态代码块,然后开始创建对象,构造代码块执行顺序在构造方法之前,最后调用info方法
public class T2 {
public static void main(String[] args) {
Child child = new Child();
}
}
class Parent {
static {
System.out.println("静态代码块Parent");
}
{
System.out.println("构造代码块Parent");
}
public Parent() {
System.out.println("构造方法Parent");
}
}
class Child extends Parent {
static {
System.out.println("静态代码块Child");
}
{
System.out.println("构造代码块Child");
}
public Child() {
System.out.println("构造方法Child");
}
}
结果:
静态代码块Parent
静态代码块Child
构造代码块Parent
构造方法Parent
构造代码块Child
构造方法Child
原因:
在子类的构造方法中,编译器自动添加了无参的super()调用
都看到这了,给孩子点个赞吧~
super关键字:
作用:
用于引用当前对象的直接父类实例
1、调用父类的构造方法
语法:
super() 或 super(参数列表)
必须在子类构造方法的第一行
如果子类构造方法中没有显示调用 super() 或者 this() ,编译器会自动添加无参的super()调用。调用 this() 之后,指向本类另一个构造方法,不论这个调用链式有多长,最终都会调用到super()。
2、访问父类成员变量
当子类和父类有同名成员变量时:
super.变量名 //调用父类的成员变量
3、调用父类成员方法
当子类重写了父类的方法,但仍想调用父类的原方法:
super.方法名 //调用父类方法
class B {
public static B b = new B();
public static B b2 = new B();
{
System.out.println("构造块");
}
static {
System.out.println("静态块");
}
}
public class T3 {
public static void main(String[] args) {
B b = new B();
}
}
结果:
构造块
构造块
静态块
构造块
原因:
最开始进入main之后发现需要创建对象,然后检查B类是否加载,发现没有加载于是开始加载B类:
静态代码块需要在静态成员变量初始化之后执行,而此时静态成员变量恰好是对象,对象初始化需要通过构造来创建,所以先执行了两次构造代码块,然后才是静态代码块
加载完B类,继续创建对象,执行构造代码块
六、总结及易踩坑的点
1、构造方法中修改静态变量
// 不推荐的做法
class Example {
static int count = 0;
public Example() {
count = 0; // 每次创建对象都会重置静态变量
}
}
2、静态方法访问非静态成员
class Example {
int instanceVar = 10;
public static void staticMethod() {
// 错误:无法直接访问实例变量
// System.out.println(instanceVar);
}
}
3、静态方法调用非静态方法
class Example {
public void instanceMethod() {
System.out.println("实例方法");
}
public static void staticMethod() {
// 错误:无法直接调用实例方法
// instanceMethod();
}
}
4、静态上下文中的 this 关键字
class Example {
static int staticVar = 10;
public static void staticMethod() {
// 错误:静态方法中没有 this
// this.staticVar = 20;
}
}
5、初始化顺序问题
多个静态变量和静态代码块同时存在时,谁在后最后结果就是谁的
public class Static {
//静态成员变量显示初始化
private static String Library = "栋梁图书馆";
//静态代码块给静态成员变量赋值
static{
Library = "凌云图书馆";
}
static{
Library1 = "凌云图书馆";
System.out.println("静态代码块");
}
public Static(){
System.out.println("构造方法");
}
private static String Library1 = "栋梁图书馆";
public static void main(String[] args) {
Static s = new Static();
System.out.println(Library);
System.out.println(Library1);
}
}
//结果为:
//静态代码块
//构造方法
//凌云图书馆
//栋梁图书馆
6、工具类设计
对于只包含静态方法的工具类:
——应该声明为 final 类防止继承
——构造方法应设为私有防止实例化
诶,
这次没有问题了
final关键字先欠着,下次再分享~
评论前必须登录!
注册