java中静态变量,静态代码块,静态方法,实例变量,匿名代码块的加载顺序

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lilong117194/article/details/83690580

1. java中静态变量,静态代码块,静态方法,实例变量,匿名代码块

在Java中,使用{}括起来的代码称为代码块,代码块可以分为以下四种:

(1)普通代码块:就是类中方法的方法体

  public void xxx(){ 
      //code 
  } 

(2)构造块:用{}裹起来的代码片段,构造块在创建对象时会被调用,每次创建对象时都会被调用,并且优先于类构造函数执行。 构造块中定义的变量是局部变量。

  { 
     //code 
  } 

(3)静态块:用static{}裹起来的代码片段,只会被执行一次(第一次加载此类时执行,比如说用Class.forName("")加载类时就会执行static block),静态块优先于构造块执行。

  static{  
     //code 
  } 

(4)同步代码块:使用synchronized(obj){}裹起来的代码块,在多线程环境下,对共享数据进行读写操作是需要互斥进行的,否则会导致数据的不一致性。常见的是synchronized用来修饰方法,其语义是任何线程进入synchronized需要先取得对象锁如果被占用了,则阻塞,实现了互斥访问共享资源。而synchronized也是有代价的。一个常见的场景是,一个冗长的方法中,其实只有一小段代码需要访问共享资源,这时使用同步块,就只将这小段代码裹在synchronized block,既能够实现同步访问,也能够减少同步引入的开销。 同步代码块须写在方法中。

    synchronized(obj){ 
        //code 
    } 

下面是一个实例:

public class Test {                             //1.第一步,准备加载类
		       
	    public static void main(String[] args) 
	    {
	        new Test();                         //4.第四步,new一个类,但在new之前要处理匿名代码块
	        // 这里必须等待类加载完
	        System.out.println("done..");       
	        Test.run();
	    }

	    
	    static int num = 4;                     //2.第二步,静态变量和静态代码块的加载顺序由编写先后决定 
	    
	    static 
	    {   
	    	System.out.println("num:"+num);     // 3.第三步,静态块,然后执行静态代码块,因为有输出,故打印a
	        System.out.println("a");
	    }

	    
	    {
	        num += 3;
	        System.out.println("b:"+num);        //5.第五步,按照顺序加载匿名代码块,代码块中有打印
	    }

	    int a = 5;                               //6.第六步,按照顺序加载变量

	    { // 成员变量第三个
	        System.out.println("c:"+a);          //7.第七步,按照顺序打印c
	    }

	    Test() { // 类的构造函数,第四个加载
	        System.out.println("d");             //8.第八步,最后加载构造函数,完成对象的建立
	    }

	    
	    static void run()                       // 静态方法,调用的时候才加载 
	    {
	    	
	        System.out.println("e");
	    }
	
	    	
}

运行:

num:4
a
b:7
c:5
d
done..
e

一般顺序:静态块(静态变量)——>成员变量——>构造方法——>静态方法
1、静态代码块(只加载一次)
2、构造方法(创建一个实例就加载一次)
3、静态方法需要调用才会执行

在这里插入图片描述

在这里插入图片描述

继承类的静态变量,静态代码块,静态方法,实例变量之间的执行顺序:

例子1:

class Print {

    public Print(String s){
        System.out.print(s + " ");
    }
}


class Parent{

    public static Print obj1 = new Print("1");

    public Print obj2 = new Print("2");

    public static Print obj3 = new Print("3");

    static{
        new Print("4");
    }

    public static Print obj4 = new Print("5");

    public Print obj5 = new Print("6");

    public Parent(){
        new Print("7");
    }
  
}
  
class Child extends Parent{

    static
    {
    	//System.out.println(" problem...");
        new Print("a");
    }

    public static Print obj1 = new Print("b");

    public Print obj2 = new Print("c");

    public Child (){
        new Print("d");
    }

    public static Print obj3 = new Print("e");

    public Print obj4 = new Print("f");

    
}


public class Test1 {
   
	public static void main(String [] args){
        Parent obj1 = new Child ();
        Parent obj2 = new Child ();
	 }

}
	

运行:

1 3 4 5 a b e 2 6 7 c f d 2 6 7 c f d 

输出结果表明,程序的执行顺序为:
如果类还没有被加载:
1、先执行父类的静态代码块和静态变量初始化,并且静态代码块和静态变量的执行顺序只跟代码中出现的顺序有关。
2、执行子类的静态代码块和静态变量初始化。
3、执行父类的实例变量初始化
4、执行父类的构造函数
5、执行子类的实例变量初始化
6、执行子类的构造函数

如果类已经被加载:
则静态代码块和静态变量就不用重复执行,再创建类对象时,只执行与实例相关的变量初始化和构造方法。

例子2:

class H1{
    {
        System.out.println("父类代码块");
    }
    public H1(){
        System.out.println("父类构造");
    }
    static{
        System.out.println("父类静态代码块");
    }
}


class H2 extends H1{
    static{
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类代码块");
    }
    public H2(){
        System.out.println("子类构造");
    }
}


public class Test1 {
   
	public static void main(String [] args){
        new H2();
        }
}
	

运行:

父类静态代码块
子类静态代码块
父类代码块
父类构造
子类代码块
子类构造

执行流程分析:

1.java程序中静态内容是随着类的加载而加载的,由于存在继承关系,因此先加载父类而后加载子类,相应的就是先执行父类静态代码块,再执行子类静态代码块

2.类加载完成后程序就开始执行main方法中,紧接着进行初始化工作,由于代码块执行优于构造方法,因此出现先执行父类代码块,再执行父类构造方法,紧接着子类代码块,子类构造方法。

3.类的初始化是分层初始化的,先对父类进行初始化,再对子类进行初始化。在目标类中执行顺序为:1.成员变量初始化:默认初始化----》显示初始化----》构造方法初始化

2. 普通内部类和静态内部类总结

  1. 普通内部类可以访问其外部类的各种类型成员,但是静态内部类只能访问静态成员
  2. 普通内部类里面不能定义各种静态的成员(包括静态变量、静态方法、静态代码块和静态内部类),而静态内部类中则可以;
    (1)静态变量和静态方法会出现这个语法错误(static methods can only be declared in a static or top level type)意思就是static方法只能在静态或者顶级类型(顶级类型应该就是外部类中)中声明,当然static变量和static内部类也是一样的道理。原因在静态变量和静态方法都只需要通过类名就能访问,不必通过任何实例化对象;而普通内部类的初始化要利用外部类的实例化对象,这明显违背了static的设计初衷。
    (2)静态代码块会出现这个语法错误(Cannot define static initializer in inner type Outer.Inner)意思是不能在内部类中定义静态的初始化程序。
    原因跟以上的差不多,static声明的成员只能为类所共有,而不能仅属于一个实例化对象,通俗点来说就是不管有多少层的引用,都只能是类来引用而不能是对象。

3. 理解向上转型:父类引用指向子类对象A a = New B()

向上转型是JAVA中的一种调用方式,是多态的一种表现。向上转型并非是将B自动向上转型为A的对象,相反它是从另一种角度去理解向上两字的:它是对A的对象的方法的扩充,即A的对象可访问B从A中继承来的和B复写A的方法,其它的方法都不能访问,包括A中的私有成员方法。

例子:

class Animal{
	
    public void sleep(){
        System.out.println("Animal sleep");
    }
 
    public void eat() {
        System.out.println("Animal eat");
    }
}
 
class Dog extends Animal {
	
    public void eat() {
        System.out.println("dog eat meat");//重写父类方法
    }
    
    //子类定义了自己的新方法
    public void methods() {
        System.out.println("dog method");
    }
}
 
public class Demo {
 
    public static void main(String[] args) {
    	
        Animal a = new Dog();
        a.sleep();
        a.eat();
        
        //a.methods();	/*报错:The method methods() is undefined for the type Animal*/
    }
}

运行:

Animal sleep

dog eat meat


可以看出:
向上转型后的a对象只能访问从Animal中继承来的和Dog复写Animal的方法,其它的方法都不能访问,包括Animal中的私有成员方法。但如果要访问Dog类自己的方法,必须强制向下转型 Dog c = (Dog)a;

另一个例子:

class Fu {
	public int num = 100;
 
	public void show() {
		System.out.println("show Fu");
	}
 
	public static void function() {
		System.out.println("function Fu");
	}
}
 
class Zi extends Fu {
	public int num = 1000;
	public int num2 = 200;
 
	public void show() {
		System.out.println("show Zi");
	}
 
	public void method() {
		System.out.println("method zi");
	}
 
	public static void function() {
		System.out.println("function Zi");
	}
}
 
public class DuoTaiDemo {
	public static void main(String[] args) {
		// 要有父类引用指向子类对象。
		// 父 f = new 子();
		Fu f = new Zi();
		System.out.println(f.num);
		// 找不到符号
		// System.out.println(f.num2);
 
		f.show();
		// 找不到符号
		// f.method();
		f.function();
	}
}

运行:

100

show Zi

function Fu

我们可以看到多态中的成员访问特点:

  • 成员变量
    编译看左边,运行看左边
  • 构造方法
    子类的构造都会默认访问父类构造
  • 成员方法
    编译看左边,运行看右边
  • 静态方法
    编译看左边,运行看左边
    所以静态方法不能算方法的重写

参考:
https://blog.csdn.net/mrzhoug/article/details/51581994
https://blog.csdn.net/gh2391292/article/details/74421308

猜你喜欢

转载自blog.csdn.net/lilong117194/article/details/83690580
今日推荐