0028 Java学习笔记-面向对象-final修饰符、不可变类
final关键字可以用于何处
- 修饰类:该类不可被继承
- 修饰变量:该变量一经初始化就不能被重新赋值,即使该值跟初始化的值相同或者指向同一个对象,也不可以 类变量:
- 实例变量:
- 形参: 注意可以修饰形参
- 局部变量
final修饰成员变量
- final修饰成员变量,必须显式的指定初始值,系统不会为final成员进行隐式初始化,不能在初始化前访问。 因为,不管是类变量还是实例变量,都有个初始化的过程,初始化赋值后便不能再赋值,如果不显式的指定初始值,那么这些变量就没有存在的意义
- 要么声明时指定
- 要么在静态块中指定
- 二者互斥,只能是其一
- 要么声明时指定
- 要么非静态块中指定
- 要么构造方法中指定
- 三者任意两者互斥,只能是这三者之其一
final修饰局部变量
- 局部变量,不管有没有final修饰,都是必须显式初始化的
- final修饰的形参,是不能赋值的
final修饰引用类型变量
- final可以用来修饰一个引用类型变量,但只能保证这个变量始终指向同一地址
- 不能保证指向的对象本身发生改变
final与直接量
- 一个变量,不管是类变量、实例变量、局部变量,满足下面三个条件,便是一个直接量 用final修饰
- 在定义该final变量时指定了初始值。注意是定义时,不能在代码块中或者构造方法中
- 该初始值可以在编译时就被确定下来
final修饰方法
- final修饰的方法不能被重写,比如作为根父类的Object就有一个.getClass()方法是用final修饰的
- 如果父类有一个private final修饰的方法,子类也有这个方法,private final修饰,方法签名也相同,注意这不是重写,这是两个无关的方法,
final修饰类
- final修饰的类不能被继承,比如:
- public final class Math extends Object {...}
不可变(immutable)类
- 不可变类:创建实例后,实例变量不可变,即实例的状态不能发生改变
- 8个包装类和String都是不可变类
- 如何定义一个不可变类: 用private和final修饰成员变量
- 提供带参数的构造器,用来初始化成员变量
- 只提供成员变量的get方法,不提供set方法
- 有必要的话重写equals()和hashCode()方法
- 这种情况要特别注意,上面的写法只能用于基本类型
- 对引用类型不可变的写法,基本原则就是隔绝外部对它的访问,主要要做好两点: 传递进来的引用类型对象不要直接用,而要根据其实例变量重新创建一个
- 外部访问时,根据这个对象的实例变量的值,重新创建一个对象返回
示例一:该不可变类对引用类型变量无效
public class Test{ public static void main(String[] args) { Name n=new Name("师兄","大"); System.out.println(n); //Name@15db9742 Person p=new Person(n,100); System.out.println(p); //输出:[大师兄100岁] System.out.println(p.getName()); //Name@15db9742 n.setFirstName("师兄"); n.setLastName("二"); System.out.println(p); //输出:[二师兄100岁],不可变对象还是变了,再看下面一段代码 System.out.println(p.getName()); //Name@15db9742 } } class Person{ private final Name name; private final int age; public Person(Name name,int age){ this.name=name; this.age=age; } public Name getName(){ return name; } public String toString(){ return "["+name.getLastName()+name.getFirstName()+age+"岁]"; } } class Name{ private String firstName; private String lastName; public Name(){} public Name(String first,String last){ firstName=first; lastName=last; } 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 Test{ public static void main(String[] args) { Name n=new Name("师兄","大"); System.out.println(n); //Name@15db9742 Person p=new Person(n,100); System.out.println(p); //输出:[大师兄100岁] System.out.println(p.getName()); //Name@6d06d69c n.setFirstName("师兄"); n.setLastName("二"); System.out.println(p); //输出:[大师兄100岁]。没发生改变 System.out.println(p.getName()); //Name@7852e922 } } class Person{ private final Name name; private final int age; public Person(Name name,int age){ this.name=new Name(name.getFirstName(),name.getLastName()); //此行改写 this.age=age; } public Name getName(){ return new Name(name.getFirstName(),name.getLastName()); //此行改写 } public String toString(){ return "["+name.getLastName()+name.getFirstName()+age+"岁]"; } } class Name{ private String firstName; private String lastName; public Name(){} public Name(String first,String last){ firstName=first; lastName=last; } 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; } }
缓存实例的不可变类
- 如果一个不可变类的部分实例,需要被频繁的访问到,那么可以考虑把这部分实例缓存起来,以提高性能
- Integer类就把[-128,127]的每个整数的Integer对象缓存了,见 0024 Java学习笔记-面向对象-包装类、对象的比较、String常量池问题
- 示例:
public class T1{ public static void main(String[] args) { CacheImmutable c1=CacheImmutable.valueOf("A"); CacheImmutable c2=CacheImmutable.valueOf("A"); System.out.println(c1==c2); //返回true } } class CacheImmutable{ private static int MAX_SIZE = 10; //缓存的大小 private static CacheImmutable[] cache=new CacheImmutable[MAX_SIZE]; //静态变量 private static int pos=0; //下一个要缓存的对象在cache中的索引号 private final String name; //CacheImmutable就这一个实例变量,用private final修饰,name是String类型,也是不可变的 private CacheImmutable(String name){ //通过带参数的构造器初始化final的实例变量,但不允许外部通过它创建对象 this.name=name; } public String getName(){ //提供了get方法,没提供set方法, return name; } public static CacheImmutable valueOf(String name){ //要获得该类的对象,只能通过这个静态方法 for (int i=0;i<MAX_SIZE;i++){ //先遍历一遍,看缓存中是否已经有了name对应的对象 if (cache[i]!=null && cache[i].getName().equals(name)){ //?为何不直接访问name,有何区别 return cache[i]; //如果已经有了就将其返回,方法调用结束 } } if (pos==MAX_SIZE){ cache[0]=new CacheImmutable(name); //如果已经缓存了10个(cache索引号9),那就从头开始覆盖 pos=1; }else{ cache[pos]=new CacheImmutable(name); //如果还不到10个,那就创建个对象,放在pos位置 pos++; } return cache[pos-1]; //返回新创建并缓存的对象,方法调用结束 } public boolean equals(Object obj){ if (this==obj) { return true; } if (obj!=null && obj.getClass()==CacheImmutable.class) { CacheImmutable ci=(CacheImmutable)obj; return name.equals(ci.getName()); } return false; } public int hashCode(){ return name.hashCode(); } }