Java基础4——深入理解final关键字和static关键字以及初始化顺序

深入理解final关键字和static关键字以及初始化顺序

final关键字(基础1中提到)

final关键字可以修饰类、方法和引用。
修饰类,该类不能被继承。并且这个类的对象在堆中分配内存后地址不可变。
修饰方法,方法不能被子类重写。
修饰引用,引用无法改变,对于基本类型,无法修改值,对于引用,虽然不能修改地址值,但是可以对指向对象的内部进行修改。

final class Fi { 
        int a; 
        final int b = 0; 
        Integer s;

}
class Si{//空白final
    //一般情况下final修饰的变量一定要被初始化。
    //只有下面这种情况例外,要求该变量必须在构造方法中被初始化。
    //并且不能有空参数的构造方法。
    //这样就可以让每个实例都有一个不同的变量,并且这个变量在每个实例中只会被初始化一次
    //于是这个变量在单个实例里就是常量了。
    final int s ;
    Si(int s) {
        this.s = s;
    }
}
class Bi {
    final int a = 1;
    final void go() {
        //final修饰方法无法被继承
    }
}
class Ci extends Bi {
    final int a = 1;
//        void go() {
//            //final修饰方法无法被继承
//        }
}
final char[]a = {'a'};
final int[]b = {1};
--------------------- 
作者:How 2 Play Life 
来源:CSDN 
原文:https://blog.csdn.net/a724888/article/details/80048792 
版权声明:本文为博主原创文章,转载请附上博文链接!

  final修饰方法时,此方法不能被重写,但是注意:因为重写前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同样访问控制权限是private,将会导致子类中不能直接继承此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而在子类中重新定义了新的方法

public class B extends A {

    public static void main(String[] args) {

    }

    public void getName() {
        
    }
}

class A {

    /**
     * 因为private修饰,子类中不能继承到此方法,因此,子类中的getName方法是重新定义的、
     * 属于子类本身的方法,编译正常
     */
    private final void getName() {
        
    }

    /* 因为pblic修饰,子类可以继承到此方法,导致重写了父类的final方法,编译出错
    */
    public final void getName() {
    
    }
    
}

  修饰变量是final用得最多的地方,final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
  当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。
  final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值
  当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。

问题1:类的final变量和普通变量有什么区别?

  当用 final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。

public class Test { 
    public static void main(String[] args)  { 
        String a = "hello2";   
        final String b = "hello"; 
        String d = "hello"; 
        String c = b + 2;   
        String e = d + 2; 
        System.out.println((a == c)); //true 当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用。
        System.out.println((a == e)); //false
    } 
}

问题2:final参数的问题

package leetcode.greedy;

public class TestFinal {
	public static void main(String arg[]) {
		TestFinal testFinal = new TestFinal();
        int i = 0;
        testFinal.changeValue(i);
        System.out.println(i);     
    } 
    public void changeValue(final int i){
    	i++; //The final local variable i cannot be assigned. It must be blank and not using a compound assignment
        System.out.println(i);
    	i++;//去掉final
        System.out.println(i);//1,0
    }
}

  上面这段代码changeValue方法中的参数i用final修饰之后,就不能在方法中更改变量i的值了。值得注意的一点,方法changeValue和main方法中的变量i根本就不是一个变量,因为java参数传递采用的是值传递,对于基本类型的变量,相当于直接将变量进行了拷贝。所以即使没有final修饰的情况下,在方法内部改变了变量i的值也不会影响方法外的i。

package leetcode.greedy;
public class TestFinal {
	public static void main(String arg[]) {
		 TestFinal testFinal = new TestFinal();
	     StringBuffer buffer = new StringBuffer("hello");
	     testFinal.changeValue(buffer);
	     System.out.println("fistbuffer:"+buffer);
	        
	}
	public void changeValue(final StringBuffer buffer){
	     buffer = new StringBuffer("hi");//final修饰引用类型的参数,不能再让其指向其他对象,但是对其所指向的内容是可以更改的。
	     buffer.append("world");
	     System.out.println("lastbuffer:"+buffer);
	}
}

  运行这段代码就会发现输出结果为 helloworld。很显然,用final进行修饰虽不能再让buffer指向其他对象,但对于buffer指向的对象的内容是可以改变的。

public class TestFinal {
    public static void main(String[] args){
        TestFinal testFinal = new TestFinal();
        StringBuffer buffer = new StringBuffer("hello");
        testFinal.changeValue(buffer);
        System.out.println(buffer);      
    }
    public void changeValue(StringBuffer buffer){
        //buffer重新指向另一个对象
        buffer = new StringBuffer("hi");
        buffer.append("world");
        System.out.println(buffer);
    }
}
hiworld
hello

static关键字

  关键字static表示「静态的」,主要思想是保证无论该类是否产生对象或无论产生多少对象的情况下,某些特定的数据在内存空间中只有一份
  在Java类中,可用static修饰属性、方法、代码块和内部类,而不能修饰构造器。
  其中,被修饰后的成员具有如下特点:

  • 随着类的加载而加载,故优先于对象存在;
  • 所修饰的成员,被该类的所有对象所共享;
  • 访问权限允许时,可不创建对象,直接被类调用。

  关键字static大体上有一下五种用法:
1.静态导入
2.静态变量
3.静态方法
4.静态代码块
5.静态内部类

1.静态导入:

// 静态导包,在类中使用Math的静态方法和属性时可以省略「Math.」 
import static java.lang.Math.*; 
public class StaticImport {
public static void main(String[] args) { 
	double a = cos(PI / 2); //已省略「Math.」 
	double b = pow(2.4,1.2); 
	double r = max(a,b); 
	System.out.print(r);
	}
}

  当在程序中多次使用某类型的静态成员(静态属性和静态方法)时,即可使用静 态导入,作用是将该类型的静态成员引入到当前的命名空间,那么在程序中调用 该类型的静态成员时可以像调用本类内定义的成员一样,直接调用,而无需采用 「类名.静态成员名」的方式。

2.静态变量:在Java中切记:static是不允许用来修饰局部变量
  在Java类中,用static修饰的属性为静态变量(类变量或类属性),而非static 修饰的属性为实例变量,区别如下:
在这里插入图片描述
3.静态方法:
  在Java类中,用static修饰的方法为静态方法,也叫类方法,其与非静态方法 的对比如下:
在这里插入图片描述
4.静态代码块:
  静态代码块仅在类加载时运行一次,主要用于对Java类的静态变量(类属性) 进行初始化操作。
  执行顺序:静态代码块 > 构造代码块(非静态代码块) > 构造方法。
5.静态内部类
  内部类的一种,静态内部类不依赖于外部类,即可以不依赖于外部类实例对象而 被实例化,且不能访问外部类的非静态成员(属性和方法)。

***注意点:***
1.Java中的static关键字不会影响到变量或者方法的作用域。
2.static是不允许用来修饰局部变量。

变量初始化顺序

  在有继承关系的情况下,变量初始化顺序如下:

  1. 父类的静态变量和静态代码块
  2. 子类的静态变量和静态代码块
  3. 父类的实例变量和普通代码块
  4. 父类的构造函数
  5. 子类的实例变量和普通代码块
  6. 子类的构造函数
public class Test {
    Person person = new Person("Test");
    static{
        System.out.println("test static");
    }
     
    public Test() {
        System.out.println("test constructor");
    }
     
    public static void main(String[] args) {
        new MyClass();
    }
}
 
class Person{
    static{
        System.out.println("person static");
    }
    public Person(String str) {
        System.out.println("person "+str);
    }
}
 
 
class MyClass extends Test {
    Person person = new Person("MyClass");
    static{
        System.out.println("myclass static");
    }
     
    public MyClass() {
        System.out.println("myclass constructor");
    }
}
输出结果:
test static
myclass static
person static
person Test
test constructor
person MyClass
myclass constructor
class A { 
	static { 
		System.out.print("1"); 
	} 
	public A() { 
		System.out.print("2"); 
	} 
}
class B extends A{ 
	static { 
		System.out.print("a"); 
	} 
	public B() { 
		System.out.print("b"); 
	}
}
public class Hello { 
	public static void main(String[] args) { 
		A ab = new B(); ab = new B(); 
	}
}
输出结果:
1a2b2b //静态代码块只初始化一次

猜你喜欢

转载自blog.csdn.net/weixin_43192732/article/details/85318561