static和final以及初始化

static

最近看的ThinkingInJava中又这样一句话:static方法就是没有this的方法。在static方法内部不能调用非静态方法,反过来是可以的。而且可以在没有创建任何对象的前提下,仅仅通过类本身来调用static方法。这实际上正是static方法的主要用途。

说道用途就首先介绍static的特点:

  • 当声明一个事物是static是,就意味着这个域或这个方法不会与包含它的那个类的任何对象实例关联在一起。
  • 所以即使从未创建过某个类的任何对象,也可以访问其static方法或static域。
  • 禁止在static内部调用非静态方法,这样会报错, 但是却可以在非静态方法里面调用static方法和变量。
  • static作为静态变量可以被所有对象共享,也能作为静态代码块优化性能。
  • 是因为static只会在类加载的时候执行一次。
  • static成员变量的初始化顺序按照定义的顺序进行初始化。

static方法

static方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都是必须依赖具体的对象才能够被调用。

但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的
在这里插入图片描述
在上面的代码中,由于print2方法是独立于对象存在的,可以直接用过类名调用。假如说可以在静态方法中访问非静态方法/变量的话,那么如果在main方法中有下面一条语句:MyObject.print2();
此时对象都没有,str2根本就不存在,所以就会产生矛盾了。同样对于方法也是一样,由于你无法预知在print1方法中是否访问了非静态成员变量,所以也禁止在静态成员方法中访问非静态成员方法。

而对于非静态成员方法,它访问静态成员方法/变量显然是毫无限制的。

因此,如果说想在不创建对象的情况下调用某个方法,就可以将这个方法设置为static。我们最常见的static方法就是main方法,至于为什么main方法必须是static的,现在就很清楚了。因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。

static变量

static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。

static代码块

static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。


final

final变量

final在数据方面的应用:一个永不改变的编译时常量;一个在运行时被初始化的值而你不希望改变它

  • 对于基本数据类型,final使数值恒定不变
  • 当对对象引用而不是基本类型运用final时,可能会有一点迷惑。对于对象引用,final使引用恒定不变。一旦引用被初始化向 一个对象,就无法再把它改为指向另一个对象1。然后对象自身确实可以被修改的,Java并未提供使任何对象恒定不变的途径。
  • final对对象的限制同样也适用于数组,因为数组也是对象。
  • 切记,static不能用来修饰局部变量。
public class ThinkingInJava {
	
	private static Random random=new Random(47);
	private String id;
	public ThinkingInJava(String id) {
		this.id=id;
	}
	
	private final int valueOne=9;
	private static final int VALUE_TWO=99;
	public static final int VALUE_THREE=39;
	
	private final int i4=random.nextInt(20);
	static final int INT_5=random.nextInt(20);
	
	private Value v1=new Value(9);
	private final Value v2=new Value(22);
	private static final Value VAL_3=new Value(33);
	
	private final int[] a= {1,2,3,4,5,6};
	@Override
	public String toString() {
		return id+": "+"i4 = "+i4+", INT_5 = "+INT_5;
	}

	public static void main(String[] args) {
		ThinkingInJava fd1=new ThinkingInJava("fd1");
		//valueOne 和VALUE_TWO是编译时常量  private修饰 只能本类访问而且不能被修改;
		//VALUE_THREE也是编译时常量 public修饰 可以被访问不能被修改;
		
		fd1.v1=new Value(9);
		//v1不是final所以可以指向其他引用  
		//但是v2,v3是final修饰,不能指向其他引用,但是可以改变自身的值
		fd1.v2.i++;
		VAL_3.i++;
		
		//a数组也是对象,同样的不能指向其他引用,却可以改变自身的值
		//fd1.a=new int[6]; X
		fd1.a[0]++;
		
		//由于 i4 INT_5都是final修饰的 
		//i4的值不会改变 但是在新对象创建时 它被重新初始化 所以会改变
		//INT_5永不会改变是因为是static类型的
		System.out.println(fd1);
		System.out.println("新的FinalData对象");
		ThinkingInJava fd2=new ThinkingInJava("fd2");
		System.out.println(fd1);
		System.out.println(fd2);
	}

	
}

final参数

不同于final变量,final参数是在方法之间传递的参数。
Java允许在参数列表中以声明的方式将参数指明为final这意味你无法更改参数引用所指向的对象 ,同时也不能修改参数,你只能够对参数做读操作。

class FinalArguments{
	void with(final Gizmo g){
		//final参数不能指向新的引用
		//g=new Gizmo();
	}
	void without(Gizmo g){
		g=new Gizmo();
		g.spin();
	}
	void f(final int i){
		//不能修改final参数
		//i++;
	}
	int g(final int i){
		//只能读取参数
		return i+1;
	}
}

final类

final修饰的类不能被继承

空白final

空白final是指被声明为final但又未给定初始值的域无论什么情况编译器都确保空白final在使用前会被初始化。
空白final作用:一个类中的final域可以做到根据对象而有所不同却又保持了其恒定不变的特性。


初始化顺序

成员初始化:

对于类的成员变量,JVM会自动初始化。但是对于方法里面定义的局部变量,如果不强制初始化,便会报错。在类里定义一个对象的引用时,如果不将其初始化,此引用就会获得一个特殊的null值。

  • 无法阻止自动初始化的进行,自动初始化会在构造器被调用之前发生。
  • 即使对象和变量的定义散布于方法定义之间,他仍会在任何方法(包括构造器)被调用之前得到初始化。
  • 只有在首次生成这个类的一个对象时,或者首次访问属于哪个类的静态数据成员时,静态对象才会被初始化,此后静态对象不会再次被初始化。
  • 静态对象的初始化优先级大于非静态对象的初始化。
  • 实例初始化子句(也就是非静态对象)是在构造器调用之前执行的。并且每次调用它所属的类的对象的引用的时候都会执行。

初始化顺序

class Insect{
	private int i=printInt("lalalalala");
	protected int j;
	public Insect() {
		System.out.println("Insect constrcutor");
		System.out.println("i="+i+",j="+j);
		j=39;
	}
	private static int x1=printInt("static Insect.x1 initilized");
	static int printInt(String s){
		System.out.println(s);
		return 47;
	}
}


public class Beetle extends Insect{
	private int k=printInt("Beetle.l initialized");
	public Beetle() {
		System.out.println("k="+k);
		System.out.println("j="+j);
	}
	private static int x2=printInt("static Beetle.x2 initilized");
	
	public static void main(String[] args) {
		System.out.println("Bettle constructor");
		Beetle beetle=new Beetle();
	}
}

在这个代码块中,首先我们如果要执行就要找到main方法,然后就要加载Bettle类,但是Bettle继承自Insect所以跳转到Insect。在Insect类中发现了static静态语句,众所周知,static的优先级大于一切,所以先执行Insect的static。加载完Insect以后,回到Bettle开始加载Bettle,同样的执行了Betlle的static命令行。加载完后然后找到了main方法,执行里面的代码块。并准备初始化Bettle的构造器,但是在初始化子类构造器之前会先去初始化父类的构造器,但是在初始化Insect的构造器之前,会先检查有没有待初始化的对象和成员变量,有的话就先初始化这些,所说先初始化了i和j,再初始化构造器。然后回到子类Bettle后也是按照这样的顺序执行。
在这里插入图片描述
所以得出初始化顺序应该是:

  1. static变量,方法,方法块,如果有多个static成员,初始化顺序按照代码书写顺序。静态成员只在类第一次加载的时候初始化一次。
  2. 成员变量,以及调用的对象,如果有多个,也是按照代码书写顺序。并且成员变量和组合的对象在每次这个类的对象被创建时都会被初始化;
  3. 最后才是构造器。

当然父类的这几个操作都优先于子类。因为如要执行子类的main方法,就要先初始化父类,再初始化子类。

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值