java里的static/final含义

时间:2023-03-09 19:32:02
java里的static/final含义

java里的static/final含义

static

  • static可以修饰:属性,方法,代码段,内部类(静态内部类或嵌套内部类)
  • static修饰的属性的初始化在编译期(类加载的时候),初始化后能改变。因此static修饰的属性必须有初始值(数值型变量默认值为0,布尔型默认值为false,引用类型默认值为null)。类加载后,存储在方法区(metaspace)
  • static修饰的属性所有对象都只有一个值(但是多个实例都可以同时修改,因此不具有线程安全性)。
  • static修饰的属性强调(在多个实例中)只有一个。
  • static修饰的属性、方法、代码段跟该类的具体对象无关,不创建对象也能调用static修饰的属性、方法等
  • static和“this、super”势不两立,static跟具体对象无关,而this、super正好跟具体对象有关。
  • static不可以修饰局部变量。

final

  • final可以修饰:属性,方法,类,局部变量(方法中的变量)
  • final修饰的属性的初始化可以在编译期,也可以在运行期,初始化后不能被改变。final修饰的属性没有默认值
  • final修饰的属性跟具体对象有关,在运行期初始化的final属性,不同对象可以有不同的值。
  • final修饰的属性表明是一个常数(创建后不能被修改)。
  • final修饰的方法表示该方法在子类中不能被重写,它可以防止任何继承类修改方法的意义和实现,而且,使用final修饰方法的执行效率一般高于普通方法
  • 当final修饰一个类时,表明其为最终类,它不能被继承,并且类中所有的属性和方法都默认是final类型,如String,Integer等包装类均为final类
  • 对于基本类型数据,final会将值变为一个常数(大写字母表示,创建后不能被修改)
  • 对于对象句柄(亦可称作引用或者指针),final会将句柄变为一个常数(进行声明时,必须将句柄初始化到一个具体的对象。而且不能再将句柄指向另一个对象。但是,对象的本身是可以修改的。这一限制也适用于数组,数组也属于对象,数组本身也是可以修改的。方法参数中的final句柄,意味着在该方法内部,我们不能改变参数句柄指向的实际东西,也就是说在方法内部不能给形参句柄再另外赋值
  • final修饰的参数有一个只读的属性,即可以读取该参数,但是无法更改参数的值,同修饰变量一样,当参数为基本类型时,该参数的值不可改变;当参数为引用类型时,参数的引用地址不可改变

demo

由于final修饰的String变量不可更改,所以,当一个String变量被final修饰时,这个值在编译期就可以确定,所有将该变量直接替换为它对应的值,如下:

public class test {
public static void main(String[] args) {
final String a = "hello";
String b = "hello"; // String是对象,一旦赋值不可以改变,但是b可以执行其他对象
// b = "test";
final String c = "world";
String d = "hello" + "world";
String e = a + c;
String f = b + c; // 运行时,在堆内分配内存
String g = "helloworld"; // 在字符串常量池内
StringBuilder y = new StringBuilder("test");
y.append("demo");
y.append("good");
System.out.println(g == d);//true,“==”表示判断二者的内存地址是否相同
System.out.println(g == e);//true,“==”号在比较对象时是比较对象在内存中的地址是否一致
System.out.println(g == f);//false
System.out.println(y);
}
}

在编译期,由于a和c的值已经确定并且不会再更改(效果同d),所以e的值能够在编译期就确定下来,直接指向了常量区的g,前两个均为true;再看下f,由于b值的不确定性,所以在编译期不能确定其值,只能在运行时确认,所以二者内存地址不同,(g == f)为false。

static final

  • 修饰的属性没有默认值,必须进行初始化
  • static final:static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。
  • static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。
  • static final也可以修饰方法,表示该方法不能重写,可以在不new对象的情况下调用

final变量初始化

public class MainTest {
//-----------------成员变量(实例变量)------------------//
// 初始化方式一(编译时赋值),在定义变量时直接赋值,如果不赋值,不会有默认值,必须后续进行赋值
private final int i = 3; // 初始化方式二(运行时赋值),声明变量后在构造方法中为其赋值,如果采用用这种方式,那么每个构造方法中都要有j赋值的语句
private final int j;
public MainTest() {
j = 3;
}
// 为了方便我们可以这样写,final变量多于1个时,就没有办法使用这种方式实现了
public MainTest(int int1) {
this(3);
} // 初始化方式三(运行时赋值),声明变量后在构造代码块中为其赋值,如果采用此方式,就不能在构造方法中再次为其赋值,构造代码块中的代码会在构造方法之前执行,如果在构造方法中再次赋值,就会造成final变量的重复赋值
private final int k;
{
k = 4;
} //-----------------类变量(静态变量)------------------//
// 初始化方式一,在定义时直接赋值(不赋值使用默认值)
public static int p = 3;
// 初始化方式二,在静态代码块中赋值
public static int q;
static {
q = 3;
}
// 成员变量可以在构造方法中赋值,但是类变量却不可以。因为成员变量属于对象(类的实例)独有,每个对象创建时都必然调用一次构造方法而切只会调用一次构造方法,因此可以保证该成员变量只被初始化一次;而类变量是该类的所有对象共有,如果每个对象创建时都会对该变量赋值,这样就会造成变量的重复赋值。 }
实例变量具有默认值,数值型变量的默认值是0,布尔型变量的默认值是false,应用类型变量的默认值是null

不同变量类型在JVM内的存储

  1. 基本局部变量存在栈中(虚拟机栈),但是方法里的数组和对象存在于堆中
  2. 静态变量储存在方法区
  3. 实例变量/成员变量储存在堆中

常量池(jdk>=1.7)

  1. class常量池:位于方法区,存放字面量和符号引用

    • 字面量类似与我们平常说的常量,主要包括:

      • 文本字符串:就是我们在代码中能够看到的字符串,例如String a = "aa"。其中"aa"就是字面量。
      • 被final修饰的变量
    • 符号引用:

      • 类和接口和全限定名:例如对于String这个类,它的全限定名就是java/lang/String。
      • 字段的名称和描述符:所谓字段就是类或者接口中声明的变量,包括类级别变量(static)和实例级的变量。
      • 方法的名称和描述符。所谓描述符就相当于方法的参数类型+返回值类型
  2. 运行时常量池:class文件中的常量池,它会在类加载后进入方法区中的运行时常量池。并且需要的注意的是,运行时常量池是全局共享的,多个类共用一个运行时常量池。并且class文件中常量池多个相同的整数在运行时只会存一份在字符串常量池

  3. 字符串常量池:存在堆中。class文件中的文本字符串在运行时就存在这里。并且class文件中常量池多个相同的字符串在运行时只会存一份在字符串常量池

  4. 整数常量池:方法区,在运行时常量池内,范围-128~127

然后看一段代码,问:这里产生了几个对象?:

string s1= new string("abc")

根据上面的意思,我们可以这样说,如果常量池中已经有了abc对象,那么就只会在堆中产生一个对象。如果没有,那么先在常量池中创建一个对象,然后复制到堆内存中再创建一个,所以创建了两个。

参考

https://zhuanlan.zhihu.com/p/42184392

https://zhuanlan.zhihu.com/p/40798413