- 关键字final整理
- 1,final 数据
(1) 编译期常数,它永远不会改变
(2) 在运行期初始化的一个值,我们不希望它发生变化
对于编译期的常数,编译器(程序)可将常数值“封装”到需要的计算过程里。也就是说,计算可在编译期间提前执行,从而节省运行时的一些开销。在 Java 中,这些形式的常数必须属于基本数据类型(Primitives),而且要用 final关键字进行表达。在对这样的一个常数进行定义的时候,必须给出一个值。无论static还是 final字段,都只能存储一个数据,而且不得改变。若随同对象句柄使用final,而不是基本数据类型,它的含义就稍微让人有点儿迷糊了。对于基本数据类型,final 会将值变成一个常数;但对于对象句柄,final 会将句柄变成一个常数。进行声明时,必须将句柄初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。Java对此未提供任何手段,可将一个对象直接变成一个常数(但是,我们可自己编写一个类,使其中的对象具有“常数”效果)。这一限制也适用于数组,它也属于对象。
- 2,final变量
类变量:必须在静态初始化块中指定或者声明该类变量的时候就指定
实例变量:必须在非静态初始化中或者声明该实例变量的时候指定或者构造器中指定。
/** * @author LinkinPark * @date 2015年10月16日 上午10:51:01 * @Description: final用法相关测试,下面所有的赋值仅可赋值一次,如果第二次赋值则报错 */ public class Linkin { //1,定义成员变量,指定默认值,合法 final int a = 6; //2,定义成员变量,在初始化块中初始化值,合法 final String name; { name = "LinkinPark..."; } //3,定义成员变量,在构造器中初始化值,合法 final int age; public Linkin() { // System.out.println(age);报错,age还未赋值,不能访问 age = 25; } //4,定义静态成员变量,指定默认值,合法 final static int b = 8; //5,定义静态成员变量,在静态初始化中初始化值,合法 final static String andress; static { andress = "地球"; } public void test(final int c) { // c = 6;报错 } }
现在强行要求我们对final 进行赋值处理——要么在定义字段时使用一个表达 式,要么在每个构建器中。这样就可以确保final 字段在使用前获得正确的初始化。
关于final修饰变量这里有2点要注意:
1),就是final修饰基本类型变量和引用类型变量的区别。当final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型不能被改变。但是对于引用类型变量而已,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但是这个对象完全是可以改变的。
public class Linkin { private Integer id; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public static void main(String[] args) { final Linkin linkin = new Linkin(); linkin.setId(1); //这个对象里面的内容可以改变 linkin.setId(2); //报错,linkin这个引用只能指向前面第一次那个对象,不可以在重新引用别的对象 // linkin = new Linkin(); } }
2),可执行宏替换的final变量。宏替换的本质就是说编译器会把程序中所有用到该变量的地方直接替换成了这个变量的值了。对于一个final变量来说,不管他是类变量,实例变量,还是局部变量,只要该变量满足三个条件,这个final变量就不是一个变量,而是相当于一个直接量。这3个条件是,使用final修饰符修饰,在定义该变量的时候指定初始值,该变量可以在编译的时候就确定下来了。
public class Linkin { public static void main(String[] args) { //定义4个final“宏变量”. final int a = 1 + 1; final double b = 10.0 / 2; //字符串池,如果编译时期已经可以决定了那么就放在了池里面了呢,下次用的时候就去抓就好了 final String name = "Linkin" + "111"; final String otherName = "Linin" + String.valueOf("111"); System.out.println(name == "Linkin111");//true System.out.println(otherName == "Linkin111");//false } }
- 3,final 方法
- 4,final 类
关于这个final类,这里有一个不可变类的感念。不可变类的意思就说创建了该类的实例后,该类的实例变量是不可以被改变的。java提供的8个包装类和字符串类都是不可变类。实际编码过程中我从来没有写过不可变类,不过我觉得这里还是必要研究下的,以后肯定会用到的。说白了就是不可变类没给我们提供改变它状态和属性的方法,那我们肯定是不能操作了。
如果需要创建自定义的不可变类,要遵守以下的规则:
1),使用private和final修饰该类的成员变量
2),提供带参数的构造器,用于根据传入参数来初始化该类的成员变量
3),仅为该类提供getter方法,不要提供setter方法,因为普通的方法不能改变这个类的属性
4),如果有必要,重写equals和hashcode方法。
/** * @author LinkinPark * @date 2015年10月16日 上午11:14:00 * @Description: 不可变类 */ public class Linkin { private final Integer id; private final String name; public Linkin() { this.id = null; this.name = ""; } public Linkin(Integer id, String name) { super(); this.id = id; this.name = name; } public Integer getId() { return id; } public String getName() { return name; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((name == null) ? 0 : name.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Linkin other = (Linkin) obj; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } }
别小看这个不可变类,还是有点可以琢磨的。我们前面已经说到了,当使用final修饰引用类型变量时,仅表示这个引用类型变量不可被重新赋值,但引用类型变量所指向的对象依然可以改变的。这就产生了一个问题了,我们创建一个不可变类,如果它包含的成员变量是可变的,那么这个对象的状态和属性也就发生改变了,那么这个不可变类就算是失败了。怎么办呢?在构造器初始化这个对象的时候,不要直接用那个引用变量来直接设对象的值,要重新new一次,将传进来的参数的内容搬一遍就OK了。
- 5,final总结
另一个值得注意的是Hashtable(散列表),它是另一个重要的标准类。该类没有采用任何final 方法。正如我们在本书其他地方提到的那样,显然一些类的设计人员与其他设计人员有着全然不同的素质(注意比较Hashtable 极短的方法名与Vecor 的方法名)。对类库的用户来说,这显然是不应该如此轻易就能看出的。一个产品的设计变得不一致后,会加大用户的工作量。这也从另一个侧面强调了代码设计与检查时需要很强的责任心。
关于上面说的JDK中的几个设计好与不好我不发表个人观点,这里我想说下final在实际编码中的使用,只要是我们不想别人乱动我们的代码,这里说的乱动就是连续2次的赋值操作,那么我们就要加上final。在实际的编码过程中,我们往往不注意,也很好加final,其实这个习惯不好的。比如说我们在用持久层框架做CRUD操作的时候,方法入参传入的主键ID就不应该被反复修改,所以这个时候就应该添加final。比如说我们自己写的一些工具类,我们自己在代码中大量使用,万一别人继承后者重写了我们的方法的时候,可能会引发一些问题,所以说我们自己写的工具类也都要添加final。