• final修饰符


    final关键字可以用来修饰类、变量、方法。final修饰的变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值。子类不能覆盖父类的final方法,final类不能有子类。

    1.final修饰的成员变量

    (1)final修饰的成员变量一旦赋值后,不能被重新赋值。
    (2)final修饰的实例Field,要么在定义该Field的时候指定初始值,要么在普通初始化块或构造器中指定初始值。但是如果在普通初始化块中为某个实例Field指定了初始值,则不能再在构造器中指定初始值。
    (3)final修饰的类Field,要么在定义该Field的时候指定初始值,要么在静态代码块中定义初始值。类Field不能在普通初始化块中指定初始值,因为类Field在类初始化阶段已经被初始化了,普通初始化块不能对其重新赋值。
    (4)如果在构造器或初始化块中对final成员变量进行初始化,则不要在初始化之前就访问该成员的值。

    (5)与普通成员变量不同的是,final成员变量(包括实例Field和类Field)必须显示初始化,系统不会对final成员进行隐式初始化。

    package cn.lsl;
    
    public class FinalTest {
        final int a = 5;    //直接赋值
        
        final String str;        //普通代码块中赋值
        {
            str = "zhangsan";
        }
        
        final int b;            //构造器中赋值
        public FinalTest(){
            b = 7;
        }
        
        final static int c = 8;        //直接赋值
        
        final static int d;            //静态代码块中赋值
        static{
            d = 9;
        }
        
        
        
        //如果在构造器或初始化块中对final成员变量进行初始化,则不要在初始化之前就访问该成员的值。
        final int age;
        {
            //System.out.println(age);
            age = 22;
            System.out.println(22);
        }
    }

    2.final修饰的局部变量

    (1)系统不会对局部变量进行初始化,局部变量必须要显示的初始化。所以使用final修饰的局部变量,既可以在定义的时候指定默认值,也可以不指定默认值。如果final修饰的局部变量在定义时没有指定默认值,则可以在后面的代码中对该final变量赋初始值,但只能一次,不能重复赋值;如果final修饰的局部变量在定义时已经指定默认值,则后面的代码中不能再对该变量赋值。
    (2)final修饰形参的时候,不能为该形参赋值。

    3.final修饰基本数据类型变量和修饰引用类型变量的区别

    使用final修饰基本类型的变量,一旦对该变量赋值之后,就不能重新赋值了。但是对于引用类型变量,他保存的只是引用,final只能保证引用类型变量所引用的地址不改变,但不保证这个对象不改变,这个对象完全可以发生改变。

    eg:

    final Person p = new Person();
    p.setAge(23);        //改变了Person对象的age Field
    //p=null            //编译出错

    final修饰的引用类型变量不能被重新赋值,但是可以改变引用变量所引用对象的内容。

    4.final的“宏变量”

    (1)final修饰符的一个重要用途就是“宏变量”。当定义final变量时就为该变量指定了初始值,而且该初始值可以在编译时就确定下来,那么这个final变量本质上就是一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。

    package cn.lsl;
    
    public class FinalTest {
        public static void main(String[] args){
            final String name = "小明" + 22.0;
            final String name1 = "小明" + String.valueOf(22.0);
            System.out.println(name == "小明22.0");//true
            System.out.println(name1 == "小明22.0");//false
        }
    }

    final String name1 = "小明" + String.valueOf(22.0);中调用了String类的方法,因此编译器无法再编译的时候确定name1的值,所以name1不会被当成“宏变量”。

    package cn.lsl;
    
    public class FinalTest {
        public static void main(String[] args){
            String s1 = "小明";
            String s2 = "小" + "明";
            System.out.println(s1 == s2);    //true
            
            String str1 = "小";
            String str2 = "明";
            String s3 = str1 + str2;
            System.out.println(s1 == s3);        //false
            
            //宏替换
            final String str3 = "小";
            final String str4 = "明";
            String s4 = str3 + str4;
            System.out.println(s1 == s4);        //true
        }
    }

    分析:

    1.java会使用常量池管理曾经使用过的字符串直接量。String a = "hello";  那么字符串池中会缓存一个字符串"hello",当执行String b = "hello";会让b直接指向字符串池中的"hello"字符串。所以a==b返回true。
    2.String s3 = str1 + str2;由于str1,str2只是两个普通变量,编译器不会执行“宏替换”,因此编译器无法在编译时确定s3的值。
    3.String s4 = str3 + str4;因为执行了宏替换,所以在编译的时候就已经确定了s4的值。

    5.用final修饰的方法不能被重写。用final修饰的类不能有子类

    即使使用final修饰一个private访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。

    final修饰的方法仅仅是不能被重写,并不是不能被重载。

    6.不可变类

    不可变类是指创建该类的实例后,该实例的Field是不可改变的。
    如果创建自定义的不可变类,应该遵循如下规则
    (1)使用private和final修饰符来修饰该类的Field。
    (2)提供带参数的构造器,用于根据传入参数来初始化类里的Field。
    (3)仅为该类的Field提供getter方法,不要为该类的Field提供setter方法。
    (4)如果有必要,重写Object类的hashCode和equals方法。

    package cn.lsl;
    
    public class Address {
        private final String detail;
        private final String postCode;
        
        public Address() {
            this.detail = "";
            this.postCode = "";
        }
        public Address(String detail, String postCode) {
            this.detail = detail;
            this.postCode = postCode;
        }
        public String getDetail() {
            return detail;
        }
        public String getPostCode() {
            return postCode;
        }
        
        public boolean equals(Object obj){
            if(this == obj){
                return true;
            }
            if(obj !=null && obj.getClass() == Address.class){
                Address ad = (Address)obj;
                if(this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode())){
                    return true;
                }
            }
            return false;
        }
        
        public int hashCode(){
            return detail.hashCode() + postCode.hashCode() * 31;
        }
        
    }

    因为final修饰引用类型变量时,表示这个引用变量不可重新被赋值,但引用类型变量所指向的对象依然可被改变。所以在创建不可变类的时候,如果包含的Field类型是可变的,那么这个不可变类就创建失败了。

    如下:

    package cn.lsl;
    
    class Name{
        private String firstName;
        private String lastName;
        
        public Name() {
            super();
            // TODO Auto-generated constructor stub
        }
        public Name(String firstName, String lastName) {
            super();
            this.firstName = firstName;
            this.lastName = lastName;
        }
        public String getFirstName() {
            return firstName;
        }
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
        public String getLastName() {
            return lastName;
        }
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    }
    
    public class Person {
        private final Name name;
        public Person(Name name){
            this.name = name;
        }
        public Name getName(){
            return name;
        }
        public static void main(String[] args) {
            Name n = new Name("明","小");
            Person p = new Person(n);
            System.out.println(p.getName().getFirstName());
            n.setFirstName("君");
            System.out.println(p.getName().getFirstName());
        }
    }

    通过n.setFirstName("君");改变了firstName。
    为了保证对象的不可变性
    可以修改为如下代码

    package cn.lsl;
    
    class Name{
        private String firstName;
        private String lastName;
        
        public Name() {
            super();
            // TODO Auto-generated constructor stub
        }
        public Name(String firstName, String lastName) {
            super();
            this.firstName = firstName;
            this.lastName = lastName;
        }
        public String getFirstName() {
            return firstName;
        }
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
        public String getLastName() {
            return lastName;
        }
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    }
    
    public class Person {
        private final Name name;
        public Person(Name name){
            //this.name = name;
            this.name = new Name(name.getFirstName(), name.getLastName());
        }
        public Name getName(){
            //return name;
            return new Name(name.getFirstName(), name.getLastName());
        }
        public static void main(String[] args) {
            Name n = new Name("明","小");
            Person p = new Person(n);
            System.out.println(p.getName().getFirstName());
            n.setFirstName("君");
            System.out.println(p.getName().getFirstName());
        }
    }

    7.实例缓存的不可变类

    如果程序需要经常使用想用的不可变类实例,则应该考虑缓存这种不可变类的实例,因为重复创建相同的对象没有太大的意义,而且加大系统的开销。
    可以使用一个数组来作为缓存池,实现不可变类。

    package cn.lsl;
    
    class CacheImmutale {
        private static int MAX_SIZE = 10;
        //使用数组来缓存实例
        private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE];
        private static int pos = 0;
        private final String name;
        private CacheImmutale(String name){
            this.name = name;
        }
        public String getName(){
            return name;
        }
        
        public static CacheImmutale valueOf(String name){
            for(int i=0; i < MAX_SIZE; i++){
                if(cache[i] != null && cache[i].getName().equals(name)){
                    return cache[i];
                }
            }
            if(pos == MAX_SIZE){
                cache[0] = new CacheImmutale(name);
                pos = 1;
            }else{
                cache[pos++] = new CacheImmutale(name);
            }
            return cache[pos-1];
        }
        
        public boolean equals(Object obj){
            if(this == obj){
                return true;
            }
            if(obj != null && obj.getClass() == CacheImmutale.class){
                CacheImmutale ci = (CacheImmutale)obj;
                return name.equals(ci.getName());
            }
            return false;
        }
        public int hashCode(){
            return name.hashCode();
        }
    }
    
    
    public class CacheImmutaleTest{
        public static void main(String[] args) {
            CacheImmutale c1 = CacheImmutale.valueOf("hello");
            CacheImmutale c2 = CacheImmutale.valueOf("hello");
            System.out.println(c1 == c2);
        }
    }

    分析:以上程序缓存池采用“先进先出”规则来决定哪个对象被移除缓存池。
    程序中还使用了private修饰来隐藏该类的构造器,通过提供该类的valueOf方法来获取实例。

    Java提供的java.lang.Integer类也是采用类似的策略来处理的,但是只能Integer缓存-128~127之间的Integer对象。如果采用new构造器来创建Integer对象,则每次返回全新的Integer对象;如果采用valueOf方法来创建Integer对象,则会缓存该方法创建的对象。

    package cn.lsl;
    
    public class IntegerTest {
        public static void main(String[] args) {
            Integer a = new Integer(23);
            Integer b = Integer.valueOf(23);
            Integer c = Integer.valueOf(23);
            System.out.println(a == b);//false
            System.out.println(b == c);//true
            
            //Integer只能缓存-128~127之间的Integer对象
            Integer d = Integer.valueOf(230);
            Integer e = Integer.valueOf(230);
            System.out.println(d == e);//false
        }
    }

    8. static和final的区别和用途

    static:

    • 修饰变量:静态变量随着类的加载就自动完成初始化,内存中只有一个,且虚拟机只会为它分配一次内存,所有类共享静态变量。

    • 修饰方法:静态方法不依赖于于任何实例对象,被类的所有实例共享。静态方法可以直接通过类名调用,静态方法必须实现,不能用abstract修饰。

    • 修饰代码块:在类加载完之后会自动执行的代码块。

    • 代码执行顺序:父类静态代码块--->子类静态代码块--->父类非静态代码块--->父类构造方法--->子类非静态代码块--->子类构造方法 

    final:

    • 修饰变量:

      • 编译期常量:编译期间就将常量值带入到如何计算式汇总,只能是基本类型。

      • 运行时常量:可以是基本类型或引用类型,引用不可以改变,引用指向的对象内容可以改变。

      • 修饰方法:不能被子类继承,且不能被子类修改。

      • 修饰类:不能被继承。

      • 修饰形参:形参不能改变。 

    参考资料

    Java笔记:final修饰符:http://www.cnblogs.com/EvanLiu/archive/2013/06/13/3134776.html

  • 相关阅读:
    nginx原理及常用配置
    课程作业03-1
    Java动手动脑02
    Java课程作业02
    java课堂测试2
    Java验证码程序
    课程作业02-2
    课程作业02-1
    课程作业01
    《大道至简》第一章伪代码
  • 原文地址:https://www.cnblogs.com/junzi2099/p/8654840.html
Copyright © 2020-2023  润新知