final中文意思:最终的,不可改变的。那么使用final作为修饰符修饰类、方法、变量、局部变量、参数就具有了一些别的意义。
final成员变量
成员变量是随着类初始化或者对象初始化而初始化,当类初始化时,系统会为该类变量分配内存并分配默认值;当创建对象时,系统会为该对象的实例变量分配内存,并分配默认值,也就是说,当执行静态初始化代码块时可以对类变量赋初始值,也可以在初始化代码块、构造器中指定初始值。
对于final修饰的成员变量而言,一旦有了初始值,就不能被重新赋值。
class FinalTest{
static final int i = 1;
public static void main(String[] args){
i =2;//报错,不可以给final修饰变量重复赋值
}
}
如果既没有在定义成员变量时指定初始值,也没有在初始化块、构造器中为成员变量指定初始值,那么这些成员变量的值将一直是系统的默认分配的值0、"\u0000"、false、或者null等。
class FinalTest{
static int i ;
public static void main(String[] args){
System.out.println(i);//输出:0,整数默认0
}
}
这些成员变量如果被final修饰,那么也就完全失去了存在的意义。因此java语法规定:final修饰的成员变量必须有程序员显示的指定初始值。
class FinalTest{
static final int i ; //这样是错误滴,违反语法规定!
public static void main(String[] args){
}
}
归纳起来,final修饰的类变量、实例变量能指定初始值的地方如下。
1、 类变量:必须在静态初始化块中指定初始值或声明该类变量时指定的初始值,而且只能在两个地方的其中之一指定。
//在静态代码块中初始化值
class FinalTest{
static final int i ;
static{
i = 2;
}
public static void main(String[] args){
System.out.println(i);
}
}
在声明final修饰的变量时指定初始值
class FinalTest{
static final int i = 3;
public static void main(String[] args){
System.out.println(i);
}
}
错误的案例:静态代码块中赋值+声明变量时赋值 = 错误
class FinalTest{
static final int i = 3;
static{
//不能再次赋值,final int i声明时已指定初始值
i = 2;
}
}
2、 实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,而且只能在三个地方的其中之一指定。
在构造方法中指定初始值
class Person
{
public Person(){
name ="小明";
}
final String name;
}
在构造代码块中指定初始值:
class Person
{
{
name ="小明";
}
final String name;
}
声明时直接赋值:
class Person
{
final String name = "小明";
}
final修饰的实例变量,要么在定义该实例变量时指定初始值,要么在普通初始化块或构造器中为该实例变量指定初始值。但需要注意的是,如果普通初始化块已经为某个实例变量指定了初始值,则不能再在构造器中为该实例变量指定初始值;final修饰的类变量,要么在定义该类变量时指定初始化值,要么在静态初始化块中位该类变量指定初始值。
实例变量不能在静态初始化块中指定初始值,因为静态初始化块是静态成员,不可访问实例变量-非静态成员;类变量不能在普通初始化块中指定初始值,因为类变量在类初始化阶段已经被初始化了,普通初始化块不能对其重新赋值。
//没有给初始化值,就不要访问.
class Person
{
{
System.out.println(name);
name ="小明";
}
final String name;
}
final局部变量
系统不会对局部变量进行初始化,局部变量必须由程序员显示初始化(直接赋值)。
错误的:
class FinalTest{
public static void main(String[] args){
final String str;
//局部变量是没有初始值的,未赋值使用会报错
System.out.println(str);
}
}
正确的:
class FinalTest{
public static void main(String[] args){
final String str =”abcd”;
System.out.println(str);
}
}
定义时不赋值,使用时,赋值:
class FinalTest{
public static void main(String[] args){
final String str;
str ="abcd";
System.out.println(str);
}
}
因此使用final修饰局部变量时,即可在定义时指定默认值,也可以不指定默认值,什么时候使用再赋值,但是只能赋值1次,不能重复赋值。
final修饰基本类型变量和引用类型变量的区别
当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变,但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。
class FinalTest{
public static void main(String[] args){
/*
person变量记录地址值
不可以改变常量指向的对象。
*/
final Person person = new Person();
//person= new Person(); 不可以重新赋值
//但是不影响他所指向的对象本身数据的更改
System.out.println(person.name);
person.name= "小明";
System.out.println(person.name);
}
}
class Person
{
String name;
}
也就是说,使用final修饰的引用类型变量不能被重新复制,但可以改变引用类型所引用对象的内容。
宏替换:
可执行“宏替换“的final变量(字面量常量)
对一个final变量来说,不管他是类变量、实例变量,还是局部变量,只要该变量满足3个条件,这个final变量就不再是一个变量,而是相当于一个直接量,即字面量常量
1、使用final修饰符修饰
2、在定义final变量时指定了初始值
3、该初始值可以编译时就确定下来
//TODO
class FinalTest{
static final String school = "北工商" ;
public static void main(String[] args){
//在编译时,遇到school直接替换成北工商
System.out.println(school);
System.out.println("北工商");
}
}
通过上面程序,我们可以看出来, 变量school满足3个条件,这个时候我们就说,运行时变量school其实根本不存在,当执行system.out.println(school)代码时,实际转换为执行System.out.println("北工商");
当编译时,遇到了这种情况,java编译器就会将程序中所有用到该变量名的地方直接替换成所对应的值。
除了上面那种为final变量赋值时直接赋值的情况外,如果被赋值的表达式只是基本算数表达式或者字符串拼装运算,没有访问普通变量,调用方法,那么这些也是满足上面3个条件,java编译器同样会将这种final变量当成“宏变量”处理。
//TODO做字符串拼接
class FinalTest{
static final String xuexiao = "北工商" ;
static final String xueyuan = "软工学院" ;
static final String banji = "1607A" ;
static final String info = xuexiao +xueyuan +banji;
public static void main(String[] args){
//在编译时,遇到info直接替换成北工商软工学院1607A
System.out.println(info);
}
}
final方法
final修饰的方法不可以被重写,如果处于某些原因,不希望子类重写父类的某个方法,则可以使用final修饰该方法。
java提供Object类里就有一个final方法:getClass(),因为java不希望任何类重写这个方法,所以使用final吧这个方法密封起来。但对于该类提供的toString()和equals()方法,都允许子类重写,因此没有使用final修饰他们
//翻原码,做案例
final类
final修饰类不可以有子类,不可以被继承。
当子类继承父类,将可以访问到父类内部数据,并可以通过重写父类方法改变实现细节,这可能导致一些不安全的因素。为了保证某个类不可以被继承,则可以使用final修饰这个类,下面代码示范了final修饰的类不可以被继承。
不可变类
不可变类意思就是创建该类的对象后,该对象的实例变量不可以改变。java提供了8个包装类和string类都是不可变类。当创建他们的实例后,其实例的实例变量不可以改变。
例如通过构造器构造Double、String、Integer对象,传入的值在他们类中肯定有变量来记录传入的值,但是Double、String、Integer类并没有提供修改该值的方法。
如果需要创建自定义不可变类,需遵守如下规则:
1、使用private和final修饰符来修饰该类的成员。
2、提供带参构造器,对该变量进行初始化。
3、仅提供getValue方法,不提供setVaule方法,因为该指是常量就无法改变。
4、如果有必要,重写equals和hashCode方法,通过该不可变量来判断该实例对象是否相等。
class BufferName
{
public boolean equals(Object obj){
if(obj== null ){
return false;
}
if(!(obj instanceof BufferName)){
return false;
}
BufferName bn = (BufferName)obj;
if(this.name.equals(bn.name)){
return true;
}
return false;
}
//不可变类实例变量
final private String name;
String age;
String address;
}