abstract - 抽象类
凡是用修饰符 abstract修饰的类,被称为抽象类。
抽象类与抽象方法
在了解抽象类之前,先来了解一下抽象方法。抽象方法是一种特殊的方法:它只有声明,而没有具体的实现。抽象方法的声明格式为:
abstract void fun();
抽象方法必须用abstract关键字进行修饰。
如果一个类含有抽象方法,则称这个类为抽象类,抽象类必须在类前用abstract关键字修饰。
因为抽象类中含有无具体实现的方法,所以不能将抽象类创建对象实例。
下面要注意一个问题:在《Java编程思想》一书中,将抽象类定义为“包含抽象方法的类”,但是后面发现如果一个类不包含抽象方法,只是用abstract修饰的话也是抽象类。也就是说抽象类不一定必须含有抽象方法。
个人觉得这个属于钻牛角尖的问题吧,因为如果一个抽象类不包含任何抽象方法,为何还要设计为抽象类?所以暂且记住这个概念吧,不必去深究为什么。
abstract class ClassName {
abstract void fun();
}
从这里可以看出,抽象类就是为了继承而存在的。
如果你定义了一个抽象类,却不去继承它,那么等于白白创建了这个抽象类,因为你不能用它来做任何事情。
对于一个父类,如果它的某个方法在父类中实现出来没有任何意义,必须根据子类的实际需求来进行不同的实现,那么就可以将这个方法声明为abstract方法,此时这个类也就成为abstract类了。
包含抽象方法的类称为抽象类,但并不意味着抽象类中只能有抽象方法,它和普通类一样,同样可以拥有成员变量和普通的成员方法。
注意,抽象类和普通类的主要有三点区别:
- 抽象方法必须为public或者protected,缺省情况下默认为public(因为如果为private,则不能被子类继承,子类便无法实现该方法)。
- 不能创建抽象类的实例对象;
- 如果一个类继承于一个抽象类,则子类必须实现父类的所有抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为为abstract类。
final - 最终类
当一个类被final修饰时,该类不能被继承。
子类继承往往可以重写父类的方法和改变父类属性,会带来一定的安全隐患。因此,当一个类不希望被继承时就,可以使用final修饰。
被定义为 final 类通常是一些有固定作用、用来完成某种标准功能的类。如Java系统定义好的用来实现网络功能的InetAddress、Socket等类都是 final类。
注意:修饰符 abstract 和修饰符 final 不能同时修饰同一个类,因为abstract类是没有具体对象的类,它必须有子类,即就是是用来被继承的;而 final类是不可能有子类的类,所以用abstract和final修饰同一个类是无意义的。
最终类
最终类的意思是创建该类的实例后,该实例变量是不可改变的。
最终类有很多好处,譬如它们的对象是只读的,可以在多线程环境下安全的共享,不用额外的同步开销等等。
条件
声明一个最终类,需要满足以下条件:
- 使用private和final修饰符来修饰该类的所有成员变量
- 提供带参的构造器用于初始化类的成员变量;
- 仅为该类的成员变量提供getter方法,不提供setter方法,因为普通方法无法修改fina修饰的成员变量;
- 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝;
- 通过构造器初始化所有成员时,进行深拷贝(deep copy);
- 如果有必要就重写Object类的hashCode()和equals()方法,应该保证用equals()判断相同的两个对象其Hashcode值也是相等的。
例子
public final class FinalClassExample {
private final int id;
private final String name;
private final HashMap testMap;
public int getId() {
return id;
}
public String getName() {
return name;
}
/**
* 可变对象的访问方法
*/
public HashMap getTestMap() {
//return testMap;
return (HashMap) testMap.clone();
}
/**
* 实现深拷贝(deep copy)的构造器
*/
public FinalClassExample(int i, String n, HashMap hm){
System.out.println("Performing Deep Copy for Object initialization");
this.id=i;
this.name=n;
HashMap tempMap=new HashMap();
String key;
Iterator it = hm.keySet().iterator();
while(it.hasNext()){
key=it.next();
tempMap.put(key, hm.get(key));
}
this.testMap=tempMap;
}
/**
* 实现浅拷贝(shallow copy)的构造器
*/
/**
public FinalClassExample(int i, String n, HashMap hm){
System.out.println("Performing Shallow Copy for Object initialization");
this.id=i;
this.name=n;
this.testMap=hm;
}
*/
/**
* 测试浅拷贝的结果
* 为了创建最终类,要使用深拷贝
* @param args
*/
public static void main(String[] args) {
HashMap h1 = new HashMap();
h1.put("1", "first");
h1.put("2", "second");
String s = "original";
int i=10;
FinalClassExample ce = new FinalClassExample(i,s,h1);
//Lets see whether its copy by field or reference
System.out.println(s==ce.getName());
System.out.println(h1 == ce.getTestMap());
//print the ce values
System.out.println("ce id:"+ce.getId());
System.out.println("ce name:"+ce.getName());
System.out.println("ce testMap:"+ce.getTestMap());
//change the local variable values
i=20;
s="modified";
h1.put("3", "third");
//print the values again
System.out.println("ce id after local variable change:"+ce.getId());
System.out.println("ce name after local variable change:"+ce.getName());
System.out.println("ce testMap after local variable change:"+ce.getTestMap());
HashMap hmTest = ce.getTestMap();
hmTest.put("4", "new");
System.out.println("ce testMap after changing variable from accessor methods:"+ce.getTestMap());
}
}
输出
Performing Deep Copy for Object initialization
true
false
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{2=second, 1=first}
ce testMap after changing variable from accessor methods:{2=second, 1=first}
现在我们注释掉深拷贝的构造器,取消对浅拷贝构造器的注释。也对getTestMap()方法中的返回语句取消注释,返回实际的对象引用。然后再一次执行代码。
Performing Shallow Copy for Object initialization
true
true
ce id:10
ce name:original
ce testMap:{2=second, 1=first}
ce id after local variable change:10
ce name after local variable change:original
ce testMap after local variable change:{3=third, 2=second, 1=first}
ce testMap after changing variable from accessor methods:{3=third, 2=second, 1=first, 4=new}
从输出可以看出,HashMap的值被更改了,因为构造器实现的是浅拷贝,而且在getter方法中返回的是原本对象的引用。
JDK中提供的八个包装类和String类都是最终类,我们来看看String的实现。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
...
}
可以看出String的value就是final修饰的,上述其他几条性质也是吻合的。
Reference
- Java中的类修饰符 - https://www.cnblogs.com/Goden/p/3811001.ht
- 接口和抽象类 - https://www.cnblogs.com/dolphin0520/p/3811437.html
- 《The Art of Java Concurrency Programming》
- 你以为你真的了解final吗? - https://juejin.im/post/5ae9b82c6fb9a07ac3634941