天天看点

关于final的一些事情

1.关于final的一些事情

一个Java关键字

1.当其一个类变量没有加该修饰符的时候的一个效果:

private static int i = 847;

public static void main(String[] args) {
    System.out.println(i);
    i = 888;
    System.out.println(i);
}      

执行结果:

关于final的一些事情

这个结果应该都懂得,这里就不解释了。

当加上一个final之后,是一个什么样的效果呢?

private final static int i = 847;

public static void main(String[] args) {
    System.out.println(i);
    i = 888;
    System.out.println(i);
}      

显而易见,连编译都无法通过

关于final的一些事情
public static class Father {
    final static int i;

    static {
        i = 100;
    }
}      

但是此类方法却不会报错,因为虽然被修饰为了final,但并没有被初始化,那么是可以使用静态方法对其进行初始化操作的。

那么同理可得,构造方法,也是可以存在的。

public static class Father {
    final int i;
}      

但是上图这种方式,会编译异常,因为对于jvm来说,你没有设计出构造方法来对于i进行初始化操作,那么将会抛出异常;

并且,当你设计出的构造方法,没有对类成员变量进行初始化操作的代码块的话,那么同样也会引起编译报错(总而言之,要想使用这个类,在类成员还没进行初始化操作的前提下,都是不允许的,那么类静态成员也是一样的)

public static class Father {
    final int i;

    public Father(int i) {
        this.i = i;
    }
}      

那么对于静态成员进行初始操作,会影响类的静态代码块初始化吗?顺序是怎么样呢?咱写个代码分析一下:

第一步,不使用final,也就是不是常量;

public static void main(String[] args) {
    System.out.println(Father.i);
}

public static class Father {
    static int i;

    static {
        System.out.println("我是类静态代码块");
    }

    public Father() {
        System.out.println("我是类无参构造代码块");
    }
}      
关于final的一些事情

先进行clean,清除所有编译文件,保持首次编译,结果如下:

关于final的一些事情

可以看出,在访问类静态成员变量的时候,类的静态代码块也先进行了初始化操作,并且顺序是在我们访问之前的,所以可以猜出类的加载机制,是先执行类的静态成员变量,对于如果访问类中的静态成员方法也是同理哈,也是先初始化类的静态代码块,再执行类的静态成员方法。

那么重点来了,我们将i置为final,看看常量对类的加载机制有什么影响;

public static void main(String[] args) {
    System.out.println(Father.i);
}

public static class Father {
    static final int i = 1;

    static {
        System.out.println("我是类静态代码块");
    }

    public Father() {
        System.out.println("我是类无参构造代码块");
    }
}      

执行结果:

关于final的一些事情

奇了!!进行访问类的静态常量成员的时候,没有加载类的静态方法代码;

那么就可以理解了,对于常量池的访问来说,并不会影响类的加载器,因为在访问i这个指向的内容的时候,并不会执行类加载器的操作。这也是常量池的优势。

那么如果类的静态常量初始化的时候,并不是一个直接的常量值呢,而是new一个对象而进行的初始化呢?,需要执行类加载吗?

public static void main(String[] args) {
    System.out.println(Father.i);
}

public static class Father {
    static final int i = new Integer(1);

    static {
        System.out.println("我是类静态代码块");
    }

    public Father() {
        System.out.println("我是类无参构造代码块");
    }
}      

结果:

关于final的一些事情

结果很意外啊,这个时候,虽然加上了final关键字,类加载器又对类进行了加载操作。所以这种问题,可以这么说,当你不去亲身试验一下,你根本不知道类的加载时机。

关于类的加载时机,还有很多隐式的操作,我们都可以去实践一下,比如子类进行构造加载的时候,其实这个时候也是会对父类进行一个类加载的。(这里就不说结果了,不然就偏离我们这个final的主题了)

public static void main(String[] args) {
    System.out.println(Son.i);
}

public static class Father {
    static final int i = new Integer(1);

    static {
        System.out.println("我是父类静态代码块");
    }

    public Father() {
        System.out.println("我是父类无参构造代码块");
    }
}

public static class Son extends Father {
    static final int i = new Integer(1);

    void method1() {
    }

    static {
        System.out.println("我是子类静态代码块");
    }

    public Son() {
        System.out.println("我是子类无参构造代码块");
    }

}      

2.那么一个局部变量是什么效果呢?

public static void main(String[] args) {
    final int i = 847;
    System.out.println(i);
    i = 888;
    System.out.println(i);
}      

结果:

关于final的一些事情

同样的操作,也就是说,在编译阶段,这样都是不允许的;

3.那么作用在一个方法上是有什么效果呢?

public class Father {
    final int i = 10;

    final void method1() {
    }
}

public class Son extends Father {
    void method1() {
    }
}      

结果:

关于final的一些事情

很明显,编译直接不通过,说明方法被修饰为final,不允许重写

3.那么再引申一下,如果作用在类上是一个什么效果呢?

public final class Father {

}

public class Son extends Father {

}      

那么这种操作是不合法的,直接会编译不通过的。就不执行结果了。

那么作用在类上是有什么特殊含义吗?在下面会总结一下;

4.那是不是加上了final就不可以改变这个对象的内容呢?

不一定,贴代码

public static void main(String[] args) {
    final AtomicInteger i = new AtomicInteger(847);
    System.out.println(i);
    i.compareAndSet(847,1111);
    System.out.println(i);
}      

上述打出的结果

关于final的一些事情

很好理解哈,就是,上图可以说,对象i指向了一个new AtomicInteger对象,那么这个AtomicInteger对象又指向了一个 847 内存地址,那么我们可以改变 这个AtomicInteger所指向的变量值,但是不能改变i对象所指向的对象,因为加了final修饰。

5.总结:

当类被一个final关键字所修饰的时候,标识这个类不被继承,那么所带来的作用是什么用意呢?

可以理解为一种规范,当我们对一个类进行final进行修改的时候,那么这个类就不允许被继承,限制了程序上的操作,那么类中的所有成员方法,也会隐式的设置为final,不允许重写,但是成员变量不会,此处需注意;

那么对于一个方法来说,其实加上了private也可以理解为,也是隐式的添加了final关键字,标识该方法不别重写;

那么对于jvm层面有什么积极意义呢?

对于类加载我们知道,在类加载的时候,需要维护类的上下级继承关系,那么对于如果加入了final的话,会有什么影响呢?

对于final修饰的类是不能被继承的,也就是不可派生。在jdk源码中,有很多相关的例子,例如常用的String、System,整个类都是被修饰为final。这里涉及到一个final的内存分配的机制;

方法内嵌:什么是方法内嵌?抽象点理解,就是当我们确定了一个方法永远不会发生改变的时候,那么我们就可以理解为,他会直接把方法的代码贴到方法调用方中,减少内存传递,减少用户线程中的函数调用;因为我们知道该方法是怎么实现的了,并且知道你不会改方法了。这样有什么好处呢?就是当我们进行循环之类的调用,就会不停的内嵌调用一个方法,那么如果这个方法内容比较少,那么这个时候,编译器就会执行自动优化,发现该方法为final标识,那么就会将该方法的代码块直接内嵌到这个循环体中,那么对于编译成字节码的时候,直接编译即可,就减少了方法间的调用情况。

但是可以这么说,对于直观上的执行效率的提升,并不明显。

对于类来说,也是相似的原理,对于类的加载机制,对于jvm来说,他知道该类为一个不可继承的类,那么对于类之间的派生类,父类相互维持关系,相互初始化,那么就没有必要,jvm就知道进行对应的加载优化了。

这也是我对final自我认识,欢迎指正,批评