Static关键字
java的关键字static用于声明静态,所谓静态,一旦创建出来就不在改变,但和常量是有区别,常量的值不会改变,静态量的值可以改变只是它的存储位置不变
静态变量
变量 i 为非静态的时候
package JavaSE;
public class Static {
public static void main(String[] args) {
// 没有static 初始化时 i = 0; 使用构造方法时进行了 i++ 使i = 1
StaticClass s1 = new StaticClass();
System.out.println(s1.i);
// 没有static 初始化时 i = 0; 使用构造方法时进行了 i++ 使i = 1
StaticClass s2 = new StaticClass();
System.out.println(s2.i);
}
}
class StaticClass{
int i = 0;
public StaticClass() {
i++;
}
}
这里两条输出语句打印的都会是1
加上关键字则第一个打印1 其他两个打印2
类共享:类的所有对象共享此方法或属性
就意味着可以通过 类.属性或方法 代替对象.属性或方法 来访问
变量 i 为静态的时候
package JavaSE;
public class Static {
public static void main(String[] args) {
// 有static 初始化时 i = 0; 使用构造方法时进行了 i++ 使i = 1
StaticClass s1 = new StaticClass();
System.out.println(s1.i);
// 有static 因为i属性类共享,所以现在的i是被s1访问后的i = 1
// 接着使用了构造函数进行了 i++ 使 i = 2
StaticClass s2 = new StaticClass();
System.out.println(s2.i);
System.out.println(StaticClass.i); // 可以代替对象s2或者s1类访问
}
}
/**
* static: 静态变量、类变量
* 是类共享的变量,该类的所有对象共享,意思是不同于一般情况,访问类的属性或方法的是对象
* 访问该类被 static修饰的属性或者方法实际上是类在访问
*/
class StaticClass{
static int i = 0;
public StaticClass() {
i++;
}
}
所有的差异都是Java在存储时的差异以下为内存模型
被static修饰的变量称为静态变量
静态变量:给这个变量分配了一个特定的空间存储,既不在堆也不在栈
它被所有的对象共享,任意一个对象对这个变量的改变会影响其他的对象对它的使用
在加载过程中 类 / 静态变量 – >对象,两者先于对象存在
在访问中既可以通过类 .调用访问 也可以通过 对象 .调用访问,不过规范的为类 .调用访问
在类中 没有被static修饰的变量就统称为成员变量,属于对象(调用的时候要对象来调用),在内存模型中存放在栈中,当对象被创建后进行初始化时才诞生
使用场景:
如配置信息,文件路径,编码字符等这些不需要去改变的
静态方法
被static修饰后的方法类的所有对象共享该方法,在没有对象被创建时,可以直接通过类访问到这个方法
public class Static {
public static void main(String[] args) {
System.out.println(Child.geteTotal());
}
}
/**
* static: 静态方法、类方法
* 是类共享的方法,该类的所有对象共享,意思是不同于一般情况,访问类的属性或方法的是对象
* 访问该类被 static修饰的属性或者方法实际上是类在访问
*/
class Child{
static int total = 0;
public void jion(){
total++;
}
public static int geteTotal(){
// 对于total本身就是一个静态变量,在使用了静态变量的方法中,将方法也变为静态方法
return total;
}
}
程序的入口main方法其实就是一个静态方法
同理构造函数在对象没有创建的时候可以使用,默认也是一个静态方法,先于对象存在可以通过类调用
静态的 语法规则:
静态方法中不能访问类的非静态成员变量和成员方法,但可以访问静态的
class Child{
int i;
static int total = 0;
public void jion(){
total++;
}
public static int geteTotal(){
i++; // i是非静态属性(成员变量),访问会报错
jion(); // jion()是非静态方法,访问会报错,无法调用
return total;
}
}
从之前内存模型可以看出非静态是在静态之前就已经加载了,如果在静态的访问非静态的属性方法,这个时候堆里面是没有这个变量的,也就找不到就不能用。
静态块
所谓块就是程序中被{}括起来的一段代码,经常被称为代码块
主要的作用就是把程序中不变的并且需要重复使用的量存储在静态变量区,一次初始化,多次使用,就不需要每次都在堆空间里创建一个新的空间造成浪费
package JavaSE;
public class Static {
public static void main(String[] args) {
Person p1 = new Person();
p1.birthDate = 1992;
Person p2 = new Person();
p2.birthDate = 1984;
System.out.println(p1.isBorn());
System.out.println(p2.isBorn());
}
}
class Person{
int birthDate;
static int start,end;
/**
* 静态代码块:一次初始化,多次使用
* 同样的非静态的变量不能写到静态块中
*/
static {
start = 1980;
end = 1990;
}
boolean isBorn(){
return birthDate >= start && birthDate <= end;
}
}
附Java代码运行过程
.java源代码 ---- .class字节码 ---- JVM ---- 运行
- 编译期间:转换成.class文件
- 类加载:字节码放到JVM上时相关数据在内存中加载(以下两类会被加载)
(1)常量池(包括但不限于String常量池)
(2)方法区:主要是static方法的代码 - 堆内存:在JVM上运行时new产生对象(以下几类会在此阶段产生)
(1)堆空间会被开辟
(2)堆空间中相关对象被建立,属性被赋默认值
(3)对属性进行赋值操作
(4)初始化块(就是一段一段的代码)
(5)构造函数初始化
内存空间模型:
java对象的初始化顺序:
- 静态变量
- 静态初始化块
- 对象创建
- 成员变量
- 初始化块
- 构造函数
package JavaSE;
public class Static {
public static void main(String[] args) {
new Student();// 类加载,堆栈空间创建
}
}
class Student{
String name;
static String degree;
{
System.out.println("初始化代码块");
name = "张三";
}
static {
System.out.println("静态初始化块(代码块)");
degree = "大学";
}
public Student(){
System.out.println("构造函数");
}
}
根据输出可以看出:
静态初始化块(代码块)
初始化代码块
构造函数
静态区域的代码块比非静态先加载
非静态的代码块比构造函数先加载
然后因为两个都用了变量必然会先有变量的加载
所以 静态变量 – 静态块 – 非静态变量 – 非静态块 – 构造函数
当没有对象创建的时候
package JavaSE;
public class Static {
public static void main(String[] args) {
System.out.println(Student.degree); // 没有创建对象通过类访问静态属性degree
}
}
输出为
静态初始化块(代码块)
大学
这里只有静态块和静态变量加载了所以对象的创建在 静态数据后非静态数据前
静态变量 – 静态块 – 创建对象 – 非静态变量 – 非静态块 – 构造函数
关于加载的次数
package JavaSE;
public class Static {
public static void main(String[] args) {
new Student();
new Student();
}
}
通过输出可以看到,静态的只加载一次而且和类的加载在一个时期,非静态的每创建一个对象并且调用了就会加载一次
静态初始化块(代码块)
初始化代码块
构造函数
初始化代码块
构造函数