【Java】静态static关键字

本文详细介绍了Java中的静态关键字及其应用,包括静态属性、静态方法、静态块和静态导入。强调了静态成员的共享性质以及它们在内存中的存储位置。同时,讨论了对象创建时内存的变化、方法的分类、参数传递机制以及装箱/拆箱的概念。此外,还涉及了Java对象的生命周期,包括 finalize 方法和垃圾回收机制。最后,文章探讨了多态性的概念及其在程序设计中的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

静态static关键字

  • 用于修饰成员
  • 随着类加载,随着类消失
  • 优先于对象,用类名直接访问
静态属性

static属性是当前类的所有对象所共有的共同属性(只有一个,而普通属性各个对象都有自己的,相互隔离),任何一个当前类对象修改这个属性,所有其他类对象的这个属性都会受影响

  • 典型应用:统计A1类的构建次数
  • 执行顺序
    静态属性—属性—构造器
  • 访问的方式
    可以使用“类名.静态属性名”或者“对象名.静态属性名”的方式进行访问。【范围限制】
定义常量

命名规则:名称全大写,下划线分词

  • 声明语法:public static final double MIN_NUMBER = 0.1;
  • 声明的同时直接进行初始化
  • 先声明后在static静态块中赋值
静态方法
  • 因为可以直接使用”类名.方法名”的形式直接调用静态方法,静态方法执行时很有可能并没有构建对象,所以在静态方法中不允许使用this/super之类用于指定对象的关键字
  • 当然在静态方法中允许创建对象,并调用对象方法
  • 静态方法只能直接访问静态成员,不能直接访问非静态成员
    static关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。
    Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。
静态块
  • 类在执行时需要通过一个叫作类加载器的组件将程序加载到内存中,类在运行时一般不会发生变化,所以类不会频繁加载,在整个运行过程中只加载一次,而且常驻内存
  • 静态块在类加载完毕后自动执行,而且只执行一次
非静态块
  • 非静态块在类内且在所有的方法之外,非静态块并不会在类加载后自动执行,而是在构建当前对象时自动执行。new一次则会执行一次,执行时机在构造器之前执行
    考试点
  • 当类加载完毕会自动优先处理static属性和static块,这两个优先级是相同的,所以谁在前先处理谁
  • new对象时,处理非静态属性和非静态块,这两个优先级是相同的,所以谁在前先处理谁
  • 最后执行构造器
    使用注意事项
  • 静态方法只能访问静态成员,静态有访问局限性
  • 静态方法中不能有this super关键字
  • 主函数是静态的
    什么时候使用静态
  • 当成员变量的数据各个对象都相同时,可以用static修饰的,让多个对象共享
  • 方法如果访问了特有数据(非静态成员变量),该函数是非静态的。方法如果没有访问特有数据,那么该方法就是静态修饰
  • 如果类中的功能都是静态的,那么该类创建对象是没有意义的,所以构造方法需要私有化
静态导入

在一个类中反复使用到某个类中的静态方法,如果使用静态导入则在当前类中不需要再写类名称

double a1=-12.345;
System.out.println(Math.abs(a1));
System.out.println(Math.cos(a1));
System.out.println(Math.sin(a1));

在JDK5当中提供了导入静态方法的import语句。

  • 语法:import static java.lang.Math.*; - 导入Math类中的所有静态方法
  • 注意要求使用JDK1.5+版本,否则编译不通过
import static java.lang.Math.*;
public class Test1 {
public static void main(String[] args) {
double a1 = -12.345;
System.out.println(abs(a1));
System.out.println(cos(a1));
System.out.println(sin(a1));
}
}

成员应用细节

在程序执行过程中使用new运算符创建的java对象,存储在堆内存中。对象内部有实例变量,所以实例变量存储在堆内存当中
变量分类:

  • 局部变量,方法体中声明
  • 成员变量,方法体外类内声明
    • 实例变量,没有static修饰符,各个不同对象相互隔离
    • 静态变量,有static修饰符,这个类的所有对象共享
    • 静态变量存储在方法区内存中
  • 三块主要内存中变化最频繁的是栈内存,最先有数据的是方法区内存,垃圾回收期主要针对的是堆内存
    自动垃圾回收机制GC什么时候会将java对象的内存回收
  • 当堆内存中的对象称为垃圾数据的时候会被GC回收
    什么时候堆内存对象会变成垃圾?
  • 引用计数法:为每个对象创建一个引用计数器,有对象引用时计数器+1,引用被释放时计数
    器-1,当计数器为0时就可以被回收。它有一个缺点就是不能解决循环引用的问题
  • 可达性算法(引用链法):从GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是可以被回收的。

总结

  • 栈内存存储基本类型的变量和对象的引用变量
  • 堆内存用于存放由new创建的对象和数组。每new一个对象就在堆内存中开辟一个新的存储空间存储此实例对象
  • Person p = new Person() 执行new命令时程序执行两步:a:在堆内存中开辟一段空间,存储new出来的对象;b:在栈内存中添加一个变量p,p中存放的是该对象在堆内存中开始存放处的物理地址
  • p = null; 执行此步骤的时候程序只是更改栈内存中的P变量所保存的地址,把地址指向null,而并没有操作堆内存(把p所指向的对象实例清空回收)
  • 无论是形参或者实参,执行 XXX = null;操作时都是把XXX变量栈中存储的地址改为指向null的地址。不操作堆中的数据。
public void pp(int k){
System.out.println(k);
}
pp(1)
具体问题

由类创建一个对象,JVM 内存中发生了哪些事情?
MyClass mc = new MyClass();以这条语句为例, MyClass mc =使虚拟机栈中生成了一个指向MyClass对象的地址;new MyClass()则在堆中分配了对象mc成员变量的空间。

栈和堆的区别
  • 管理方式:栈自动释放,堆需要GC
  • 空间大小:栈比堆小
  • 碎片相关:栈产生的碎片远小于堆
  • 分配方式:栈支持静态和动态分配,而堆仅仅支持动态分配
  • 效率:栈的效率比堆高
方法的问题
方法的分类:
  • 无参无返(没有参数列表,没有返回值)单纯的作为 功能代码的聚合使用 便于功能复用
  • 无参有返(没有参数列表,有返回值)
  • 有参无返(有参数列表 没有返回值)适用于功能需要根据参数来进行计算的情况,但是计算的最终结果又无需返回处理
  • 有参有返(有参数列表,有返回值)适用于功能需要根据参数来进行计算的情况,而且最终的结果需要返回处理
方法的形参和实参:
  • 形参 :是定义在方法声明上,用于指定该方法需要传递的参数类型的
  • 实参 :是调用方法时,实际传递的参数值
方法参数传递
  • 基本数据类型作为参数传值:传值传的时值的内容,来到另一个方法空间之后,这个值和之前没有任何关系。(如你们拷贝我分享的网盘内容不会改变我原有玩盘内容)
  • 引用数据类型作为参数传值:传值传的时对象在堆的地址值,所以了两个内容指向了同一空间是相互影响的。(如你登陆我的网盘拷贝内容改变的话会改变我的网盘内容)
基本数据类型的对象缓存

在不可变类Integer类定义中查看源代码可以发现一个定义

private static class IntegerCache {}这实际上就是Integer的cache
Integer num1 = 12;
Integer num2 = 12; 这块相等,<=127都是真的
System.out.println(num1 == num2);

是因为在Integer中包含有一个缓存池,缓存值为-128到127之间。

  • 定义Integer k1=12是先在缓存池中查找12这个对象,如果有则直接使用
  • new Integer(12)一定会引发对象的创建,而不管缓存池中是否包含这个对象
Integer k1 = 12;
Integer k2 = 12;
System.out.println(k1 == k2);//true,k1和k2都是引用类型,所以==比较的是地址
Integer k3=new Integer(12);
System.out.println(k1==k3);//false
Integer k1 = 129;
Integer k2 = 129;
System.out.println(k1 == k2);//false,因为缓存只缓存-128到127之间的数据

Integer\Long\Short\Byte中都有缓存池、character中也有缓存池,boolean只有true/false两个

字符串缓存池

String中包含一个缓存池,当使用某个字符串对象时会首先在缓存池中进行查找,如果存在则直接返回这个对象的地址;如果不存在则会在缓存池中进行创建,创建完成后返回地址

String s1="abc";
String s2="abc";
String s3=new String("abc");//这里会创建2个对象,一个在缓存池中,一个是new导致的新创建
对象
System.out.println(s1==s2);//true
System.out.println(s1==s3);//false

注意:如果通过字串拼接所得内容和某个字串内容一致,但是地址不同

装箱/拆箱

值类型自动转换成它对应的类类型-autoboxing、类类型自动转成它所对应的值类型-unboxing

  • 装箱。如:Integer a = 9;
  • 将一个对像自动转成基本类型就叫拆箱 int c = new Integer(9);
java.lang.Object

所有Java类都直接或者间接的继承自Object类,那么Object中的方法也是其他所有类都拥有的

Object类中的常用方法

getClass方法用于获得当前对象的类型
直接输出一个对象时,实际上默认输出的是该对象toString()方法的返回值。所有的Java类中都有这个方法,因为Java类都直接或者间接的继承于Object类,而Object类中定义了toString方法。为了实现用户自定义输出格式,允许在类中覆盖定义toString方法

比较两个字符串是否相等,能用“==”吗?

Java中的约定:重写equals()方法必须重写hasCode()方法
hashCode()方法返回一个整形数值,表示该对象的哈希码值。
hashCode()具有如下约定:
1).在Java应用程序程序执行期间,对于同一对象多次调用hashCode()方法时,其返回的哈希码是相同的,前提是将对象进行equals比较时所用的标尺信息未做修改。在Java应用程序的一次执行到另外一次执行,同一对象的hashCode()返回的哈希码无须保持一致;
2).如果两个对象相等(依据:调用equals()方法),那么这两个对象调用hashCode()返回的哈希码也必须相等;
3).反之,两个对象调用hasCode()返回的哈希码相等,这两个对象不一定相等。
即严格的数学逻辑表示为: 两个对象相等 <=> equals()相等 => hashCode()相等。因此,重写equlas()方法必须重写hashCode()方法,以保证此逻辑严格成立,同时可以推理出:hasCode()不相等 =>equals()不相等 <=> 两个对象不相等。

可能有人在此产生疑问:既然比较两个对象是否相等的唯一条件(也是冲要条件)是equals,那么为什么还要弄出一个hashCode(),并且进行如此约定,弄得这么麻烦?

其实,这主要体现在hashCode()方法的作用上,其主要用于增强哈希表的性能。

以集合类中,以Set为例,当新加一个对象时,需要判断现有集合中是否已经存在与此对象相等的对象,如果没有hashCode()方法,需要将Set进行一次遍历,并逐一用equals()方法判断两个对象是否相等,此种算法时间复杂度为o(n)。通过借助于hasCode方法,先计算出即将新加入对象的哈希码,然后根据哈希算法计算出此对象的位置,直接判断此位置上是否已有对象即可。(注:Set的底层用的是Map的原理实现)

在此需要纠正一个理解上的误区:对象的hashCode()返回的不是对象所在的物理内存地址。甚至也不一定是对象的逻辑地址,hashCode()相同的两个对象,不一定相等,换言之,不相等的两个对象,hashCode()返回的哈希码可能相同。

protected void finalize(); finalize方法主要与Java垃圾回收机制有关。首先我们看一下finalized方法在Object中的具体定义: protected void finalize() throws Throwable { }

我们发现Object类中finalize方法被定义成一个空方法,为什么要如此定义呢?finalize方法的调用时机是怎么样的呢?

首先,Object中定义finalize方法表明Java中每一个对象都将具有finalize这种行为,其具体调用时机在:JVM准备对此对形象所占用的内存空间进行垃圾回收前,将被调用。由此可以看出,此方法并不是由我们主动去调用的(虽然可以主动去调用,此时与其他自定义方法无异)。

多态性

多态性是发送消息给某个对象,让该对象自行决定响应何种行为。通过将子类对象引用赋值给超类对象引用变量来实现动态方法调用

public class Test1 {
public static void main(String[] args) {
Fa cc=new Fa();
//调用的是一个名称为pp的方法,但是参数不同执行的处理不同
cc.pp();//Fa...pp
cc.pp(12);//Fa.pp(int)
}
}
class Fa{
public void pp(){
System.out.println("Fa...pp");
}
public void pp(int k){
System.out.println("Fa.pp(int)");
}
}

多态性通过允许同一界面指定一类动作减少了程序的复杂度,编译器工作就是选择适用于各个情况的特定动作,而程序员则无须手动进行选择,使用者仅仅是记得以及利用这个统一的界面

多态形成的三个必要条件:

  • 有继承,父类定义方法,子类重写方法
  • 父类的引用指向子类的对象
  • 可以使用参数传递时多态,也可以直接创建对象时多态
    多态可以用三个定义和两个方法来总结。三个定义分别是父类定义子类构建、接口定义实现类构建和抽象类定义实体类构建,而两个方法分别是方法重载和方法重写。
    多态分为两种:
  • 编译时多态:方法的重载
  • 运行时多态:JAVA运行时系统根据调用该方法的实例的类型来决定选择调用哪个方法则被称为运行时多态
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值