冒个险吧!人生本来就是一场探险, 最有成就的是那些敢于尝试的人。
1.静态变量与实例变量(重点)
- 静态变量:又称为类变量,指的是被static修饰的成员变量。静态变量是属于类的,他被该类的所有对象共享,在内存中只存在一份,存在于JVM的方法区中,是线程共享的,并且在JVM加载类时,会为该类的静态变量分配内存,并随着类的卸载而销毁,一般通过类名直接访问。
- 实例变量: 指没有被static修饰的成员变量。实例变量是属于这个类的实例对象的,因此在内存中创建几个对象,就会几份实力变量,存在于堆中,实例变量随着对象的创建而创建,随着对象的销毁而销毁,必须通过对象访问。
public class A {
private int x; // 实例变量
private static int y; // 静态变量
public static void main(String[] args) {
// int x = A.x; // 报错
A a = new A();
int x = a.x;
int y = a.y; //通过对象访问
int y2 = A.y; //通过类名访问
}
}
Tips:
- 静态变量虽然独立于对象,但只要访问权限足够,静态变量和静态方法也是可以根据对象访问的,也就是说静态变量不仅可以通过类名访问,也可通过具体的对象访问,但一般不建议这么做。
- static不能修饰局部变量。
- 可以看出来static关键字可以改变变量的存储位置。
2.静态方法
由static修饰的方法就是静态方法,它随着类的加载而存在,不依赖于任何对象。
public class A {
private static int x;
private int y;
public static void func1(){
int a = x;
// int b = y; // Non-static field 'y' cannot be referenced from a static context
// int b = this.y; // 'A.this' cannot be referenced from a static context
}
}
- 静态方法必须有实现,也就是说他不能是抽象方法。
- 静态方法只能访问所属类的静态变量和静态方法。
- 静态变量中不能有this和super关键字,因为这两个关键字与具体的对象关联。
3.静态代码块(重点)
代码块的分类:
- 构造代码块:在类中定义,每次创建对象都会调用一次。
- 静态代码块:被static修饰的构造代码块,静态代码块的代码会在类的加载时被运行,且只会执行一次。
- 普通代码块:在方法体中定义,完成一定逻辑,可以控制变量的生命周期,提高内存利用率。
public class Test {
{
System.out.println("构造代码块");
}
static {
System.out.println("静态代码块");
}
public void sayHello(){
{
System.out.println("普通代码块");
}
}
}
执行顺序
静态代码块 > 构造代码块 > 构造函数
public class StaticCodeBlockTest {
public StaticCodeBlockTest() {
System.out.println("StaticCodeBlockTest:构造函数");
}
{
System.out.println("StaticCodeBlockTest:构造代码块");
}
static {
System.out.println("StaticCodeBlockTest:静态代码块");
}
public static void main(String[] args) {
StaticCodeBlockTest2 test2 = new StaticCodeBlockTest2();
System.out.println("----------");
StaticCodeBlockTest test = new StaticCodeBlockTest();
}
}
class StaticCodeBlockTest2 {
public StaticCodeBlockTest2() {
System.out.println("StaticCodeBlockTest2:构造函数");
}
{
System.out.println("StaticCodeBlockTest2:构造代码块");
}
static {
System.out.println("StaticCodeBlockTest2:静态代码块");
}
}
输出结果:
StaticCodeBlockTest:静态代码块
StaticCodeBlockTest2:静态代码块
StaticCodeBlockTest2:构造代码块
StaticCodeBlockTest2:构造函数
----------
StaticCodeBlockTest:构造代码块
StaticCodeBlockTest:构造函数
解析:
- main函数作为入口,因此会先加载StaticCodeBlockTest 类,所以执行了StaticCodeBlockTest 的静态代码块,输出:StaticCodeBlockTest:静态代码块
- 接着主函数里要创建一个StaticCodeBlockTest2的对象,发现StaticCodeBlockTest2类还没有加载,所以先加载StaticCodeBlockTest2,就执行了StaticCodeBlockTest2的静态代码块,输出:StaticCodeBlockTest2:静态代码块
- 在创建StaticCodeBlockTest2对象时,构造代码块优先于构造器,所以先输出StaticCodeBlockTest2:构造代码块,再输出StaticCodeBlockTest2:构造函数
- 接着又要创建StaticCodeBlockTest对象,发现StaticCodeBlockTest类已经被加载了,所以先调用StaticCodeBlockTest的构造代码块,然后执行构造函数,所以先输出StaticCodeBlockTest:构造代码块,再输出StaticCodeBlockTest:构造函数
存在继承的情况下的初始化顺序
父类静态代码块 > 子类静态代码块 > 父类构造代码块 > 父类构造器 > 子类构造代码块 > 子类构造器
public class StaticCodeBlockExtendTest {
public static void main(String[] args) {
Zi z = new Zi();
}
}
class Ye{
public Ye() {
System.out.println("Ye:构造函数");
}
{
System.out.println("Ye:构造代码块");
}
static {
System.out.println("Ye:静态代码块");
}
}
class Fu extends Ye{
public Fu() {
System.out.println("Fu:构造函数");
}
{
System.out.println("Fu:构造代码块");
}
static {
System.out.println("Fu:静态代码块");
}
}
class Zi extends Fu{
public Zi() {
System.out.println("Zi:构造函数");
}
{
System.out.println("Zi:构造代码块");
}
static {
System.out.println("Zi:静态代码块");
}
}
输出:
Ye:静态代码块
Fu:静态代码块
Zi:静态代码块
Ye:构造代码块
Ye:构造函数
Fu:构造代码块
Fu:构造函数
Zi:构造代码块
Zi:构造函数
解析:
- 在创建子类对象时,会先去加载子类,而加载子类必须先加载父类;
- 在创建子类对象时,会先调用父类的构造方法,而构造代码块优先于构造方法;
静态域初始化顺序
静态语句块之间的初始化顺序、静态变量和静态语句块之间的初始化顺序 取决于它们在代码中的书写顺序,也就说,静态域按顺序初始化。这是因为在类的初始化阶段,编译器会根据程序员书写顺序,自动收集所有类变量的赋值操作和静态语句块,合并成一个 < clinit >()方法,然后执行。
public class StaticCodeBlockBetweenTest {
public static StaticCodeBlockBetweenTest a = new StaticCodeBlockBetweenTest();
{
System.out.println("StaticCodeBlockBetweenTest:构造代码块");
}
static {
System.out.println("StaticCodeBlockBetweenTest:静态代码块");
}
public StaticCodeBlockBetweenTest() {
System.out.println("StaticCodeBlockBetweenTest:构造函数");
}
public static void main(String[] args) {
}
}
输出:
StaticCodeBlockBetweenTest:构造代码块
StaticCodeBlockBetweenTest:构造函数
StaticCodeBlockBetweenTest:静态代码块
解析:
- main函数作为入口,因此先要加载StaticCodeBlockBetweenTest类,由于静态变量在静态代码块之前,所以先初始化静态变量。
- 所以先初始化静态变量时,要创建StaticCodeBlockBetweenTest对象,由于构造代码块优先于构造函数,所以先输出StaticCodeBlockBetweenTest:构造代码块,再输出StaticCodeBlockBetweenTest:构造函数
- 然后执行静态代码块中的内容,输出StaticCodeBlockBetweenTest:静态代码块,至此StaticCodeBlockBetweenTest类加载完毕。
PS:构造代码块和构造方法在实例化对象的时候也会合并为 < init >() 方法,并且构造代码块中的内容在构造方法中的内容前面。
4.静态内部类
非静态内部类依赖于外部类的实例,也就是说需要先创建外部类实例,才能用这个实例去创建非静态内部类。而静态内部类不需要。
public class OuterClass {
class InnerClass {
}
static class StaticInnerClass {
}
public static void main(String[] args) {
// InnerClass innerClass = new InnerClass(); // 报错 'OuterClass.this' cannot be referenced from a static context
InnerClass innerClass = new OuterClass.new InnerClass();
OuterClass.StaticInnerClass staticInnerClass = new OuterClass.StaticInnerClass();
}
}
tips:
- 静态内部类不能访问外部类中的非静态变量和方法。
- 静态内部类的实例化可以通过外部类.静态内部类的形式进行。
5.静态导包
jdk1.5新特性,使用 import static 可以导入某个类中的指定静态资源,不用再指明 ClassName,从而简化代码,但可读性大大降低。
6.使用static的注意事项
- 静态只能调用静态,而非静态可以调用静态。那是因为静态资源随类的加载而加载,此时还没有创建对象。
- 静态资源占用内存,并且内存一般不会释放
7.static 与 final (重点)
static 与 final 经常连用,用来声明一个对象共享的常量。static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。
- static final修饰的成员变量,必须声明的同时初始化或在静态代码块中初始化,不可被改变。
- static final也可以修饰方法,表示该方法不能重写,可以通过类名直接调用。
- static final常量会在编译时被替换。
public class StaticAndFinalTest {
public static void main(String[] args) {
System.out.println(Foo.num);
}
}
class Foo{
static {
System.out.println("Foo:静态代码块");
}
public static final int num = 100;
}
输出:
100
解析:main方法中直接调用Foo类的 static final 变量,即使静态代码块在静态变量之前,也不会先执行静态代码块,因为 static final常量会被编译器直接替换,所以不需要加载Foo类,也就不会执行静态代码块。