1. 前言
- 本篇文章主要介绍Java语言中的两大关键字satic和final,还有main方法与static的
渊源
。 - static的中文意思是静态的,final的中文意思是最终的,不可更改的。
我们将从以下三大主线讲解:
- static 修饰属性、方法、代码块、内部类
- main 方法与 static 的渊源
- final 修饰类、变量和方法
2. 简介JVM内存模型的虚拟机栈,堆和方法区
这里只介绍虚拟机栈,堆和方法区
,因为这篇文章需要使用到。
-
通常所说的
栈(Stack)
,是指虚拟机栈
。虚拟机栈用于存储局部变量
等。局部变量表存放了编译期可知长度的各种基本数据类型(boolean、byte、char、short、int、float、long、double ),对象引用( reference类型,它不等同于对象本身,是对象在堆内存的首地址)。方法执行完, 自动释放,从栈中弹出。 -
堆(Heap)
,此内存区域的唯一目的就是存放对象实例
,几乎所有的对象实例都在这里分配内存。这一点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配
。 -
方法区(Method Area)
,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
。
3.static
2.1 为什么需要static?
- 有时候我们想要让所有同一个类的实例对象共享同一个数据,那么我们就需要用到类变量,也就是static修饰的变量。
- 这个数据可以是属性、方法、代码块、内部类
2.2 static修饰的属性、方法、代码块、内部类的特点
- 随着类的加载而加载;无论是否产生对象,static修饰的属性、方法、代码块、内部类都会在JVM内存区的方法区存在一份
- 优先于对象存在,类加载时static域已经在内存中加载了,加载到了JVM内存的
方法区
- 修饰的成员,被同一个类实例的所有对象共享
- 访问权限允许时,可不创建对象,直接被类调用,即通过类名访问static修饰的成员,classname.staticmember
2.2 static 属性
- static修饰的类变量(类属性)由该类的所有实例共享
- static修饰的属性可以被继承
- 当某一个实例改变了static修饰的属性的值时,其它实例变量使用时是修改后的值,如下图所举例子所示:
2.3 static 方法
-
static修饰的方法简称类方法
-
没有对象的实例时,可以用
类名. 方法名()
的形式访问由static修饰的类方法。 -
在
static 方法内部只能访问类的static 修饰的属性或方法, 不能访问类的非static 的结构
。 -
因为不需要实例就可以访问static 方法,因此static 方法内部不能有this 。当然也不能有super
-
static修饰的方法不可以重写
,重写的定义是:方法名、返回值类型、参数列表 与父类中某个方法一样。经检验子类必须写的和父类一模一样加static才可以覆盖static方法,而这不满足重写的定义,所以不能叫重写 -
static修饰的属性和方法可以继承,可以使用子类类名访问,但是不能通过子类对象访问。
2.4 static的属性和方法在父类与子类之间的继承和重写关系
下面一个例子测试了static的属性和方法在父类与子类之间的继承和重写关系:
public class Person {
public static String s="Person";//测试重写
public static void display() {//测试重写
System.out.println("Person display");
}
public static String ss="Person two"; //测试继承性
public static void displayPerson() {测试继承性
System.out.println("Person displayPerson");
}
}
public class Man extends Person {
public static String s="Man";
public static void display() {//将static去掉报错
System.out.println("Man display");
}
private static int total = 0;
private int id=0;
public Man() {
total++;
}
public static int getTotalPerson() {
//id++; //静态方法不能访问非静态属性和方法
//displayA();
//this.total=10;
//this.id=10;
return total;
}
}
public class TestMain {
public static void main(String[] args) {
/************ 打印Person的属性和方法做对比 ************/
System.out.println(Person.s);
System.out.println(Person.ss);
Person.display();
Person.displayPerson();
System.out.println("---------");
/************ 测试继承性 ************/
System.out.println(Man.ss);
Man.displayPerson();
System.out.println("---------");
/************ 测试重写性 ************/
System.out.println(Man.s);
Man.display();
/************ 测试多态性 ************/
Person p=new Man();//不能调用Person和Man的任何属性方法,只能调用Object的属性和方法
Man mm=new Man();//mm不能访问Person的static属性和static方法
System.out.println("---------");
System.out.println("total is " + Man.getTotalPerson());
Man p1 = new Man();//执行构造器total+1
System.out.println("total is " + Man.getTotalPerson());
}
}
运行结果:
Person
Person two
Person display
Person displayPerson
---------
Person two
Person displayPerson
---------
Man
Man display
---------
total is 1
total is 2
2.5 static代码块与非static代码块
既然已经说到了代码块,那么就一次性把静态和非静态代码块一次性尽量总结完叭
- 代码块(或初始化块)的作用:
对Java 类或对象进行初始化 - 代码块(或初始化块)的分类:
一个类中代码块若有修饰符,则只能被static修饰
,称为静态代码块
(static block),没有使用static修饰的,为非静态代码块
。 - static 代码块化 通常用于初始化static
class Person {
public static int total;
static {
total = 100;// 为total 赋初值
}
…… //其它属性或方法声明
}
静态代码块:用static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
- 若有多个静态的代码块,那么按照从上到下的顺序依次执行。
- 静态代码块的执行要先于非静态代码块。
静态代码块随着类的加载而加载,且只执行一次
。
非静态代码块:没有static 修饰的代码块
- 可以有输出语句。
- 可以对类的属性、类的声明进行初始化操作。
- 除了调用非静态的结构外,还可以调用静态的变量或方法。
- 若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
每次创建对象的时候,都会执行一次。且先于构造器执行
。
- 静态代码块不可以继承,只随类的加载执行一次
- 非静态代码块可继承,每次创建对象的时候,都会执行一次。且先于构造器执行
下面看个例子:
public class Person {
Person(){
System.out.println("person construct");
}
public int a=1,b=1;
{//`每次创建对象的时候,都会执行一次。且先于构造器执行`
a++;
int x=20;
int b=2;
{
// int b=2;//不能在嵌套的两个{{}}定义相同的变量
a++;
}
a++;
System.out.println("no static Person a="+a);
}
public static int c=1,d=1;
static{//`静态代码块随着类的加载而加载,且只执行一次`
c++;
int b=2;
int yy=20;
{
// int b=2;//不能在嵌套的两个{{}}定义相同的变量
c++;
}
c++;
System.out.println("static Person c="+c);
}
}
public class Man extends Person {
Man(){
this.a=10;//不能this.c this.d
this.b=10;
System.out.println("Man a="+a);
System.out.println("Man b="+b);
}
}
public class TestMain {
public static void main(String[] args) {
Person p=new Person();
System.out.println("-----------");
Man m=new Man();
System.out.println("---------");
System.out.println(Man.c);
System.out.println(Man.d);
System.out.println(m.a);
System.out.println(m.b);//没有m.c m.d
//m.xx 报错,就像方法里面的局部变量一样不可以使用对象或类名访问
}
}
运行结果:
static Person c=4
no static Person a=4
person construct
-----------
no static Person a=4
person construct
Man a=10
Man b=10
---------
4
1
10
10
2.6 程序中成员变量赋值的执行顺序
2.7 静态内部类
- 关于静态内部类可参考此篇文章里面写到的静态内部类:https://www.bithachi.cn/posts/bf7b3e18.html
4.static与main
4.1 static与main方法简介
-
由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
-
又
因为main() 方法是静态的,我们不能直接访问该类中的非静态成员
,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
命令行参数用法举例:
public class CommandPara {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
4.2 面试题
此处,Something类的文件名叫OtherThing.java
class Something {
public static void main(String[] something_to_do) {
System.out.println("Do something ...");
}
}
上述程序是否可以正常编译、运行?
答案是可以正常编译执行
5.final
在Java中声明类、变量和方法时,可使用关键字final来修饰,表示“最终的”。
- final 标记的类不能被继承 。提高安全性,提高程序的可读性。
String类、System类、StringBuffer类 - final 标记的方法不能被子类重写。
比如:Object类中的getClass()。 - final 标记的变量( 成员变量或局部变量) 即称为常量 。名称大写,且只能被赋值一次。
- final标记的成员变量必须在声明时或在每个构造器中或代码块中显式赋值,然后才能使用。
final double MY_PI = 3.14;
5.1 final 修饰变量—— 常量
class A {
private final String INFO = "BitHachi"; //声明常量
public void print() {
//The final field A.INFO cannot be assigned
//INFO = "Hachi";//Error
}
}
5.2 final修饰类
final class A{
}
class B extends A{ //错误,不能被继承。
}
5.3 final修饰方法
class A {
public final void print() {
System.out.println("A");
}
}
class B extends A {
public void print() { // 错误,不能被重写。
System.out.println("B");
}
}
5.4 static final—全局常量
- static final:全局常量
- static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。
- static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问
5.5 面试题
面试一:
public class Something {
public int addOne(final int x) {
return ++x;//Error
// return x + 1;//OK
}
}
调用addOne方法时,不能对final常量改变值,只能使用其值
面试二:
public class Something {
public static void main(String[] args) {
Other o = new Other();
new Something().addOne(o);
}
public void addOne(final Other o) {
// o = new Other();//o是常量不可以再变
o.i++;//i是变量可以变
}
}
class Other {
public int i;
}