一. abstract
abstract 的中文意思就是抽象的, 所谓抽象就是这个东西在现实里不存在, 也就是不能直接实例化的意思.
abstract 可以修饰类, 类的方法
1.1 abstract 修饰 类.
现实中有一些类是不应该实例化的. 例如
植物
/ \
开花植物 蕨类
/ \
裸子植物 被子植物
/ \
苹果 芒果
上面那个简单的类结构体中, 前面3层都是抽象的, 在自然界没有现实的对象存在, 所以在面向过程中应该把它们设为抽象类.
只有下面的若干层派生类才应该可以被实例化.
也可以看出, 抽象类一般是类族的最上面若干层.
看下面的例子.
abstract class Abs_A1{
int id;
String name;
abstract void print();
}
class A2 extends Abs_A1{
void print(){
System.out.printf("A2\n");
}
}
abstract class Abs_A3 extends A2{
abstract void print();
}
public class Abstract_1{
public static void f(){
//Abs_A1 a1 = new Abs_A1(); //error
A2 a2 = new A2(); //ok
//Abs_A3 a3 = new Abs_A3() //error
Abs_A1 a1 = new A2(); //ok,
a1.print();
}
}
然后我们来从下面的f() 函数里分析一下抽象类的实现.
1.1.1 抽象类不能直接实例化
这个很明显了, 上面的例子里尝试直接实例化, 都会编译失败.
1.1.2 非抽象类可以派生出抽象类
例如上面A2 派生出Abs_A3
1.1.3 抽象类一般和多态技术相结合
Abs_A1 a1 = new A2(); //ok,
a1.print();
看上面两句, 虽然定义了1个Abs_A1 的引用, 但是它指向了它的派生类的实例化对象, 这样虽然a1 是1个抽象类的引用, 但是它仍然可以调用派生类重写后的方法.
这个就是多态的关键点之一.
1.2 abstract 修饰方法.
abstract 修饰一个方法就是表示这个方法是一个抽象方法, 所谓抽象方法就是没有方法体的函数. 也就是必须在派生类中重写后才能调用.
例子:
abstract class Abs_A4{
int id;
String name;
public Abs_A4(int id, String name){ //abstract class can have non-abstract function.
this.id = id;
this.name = name;
}
public abstract void print(); //abstract function, with no function body, must have symbol ";"
public static void print2(){
System.out.print("A4:function print2\n");
}
//cannot use "private" & "abstract" togethe
//private abstract void print3();
// cannot use "static" & "abstract" together
//public static abstract void print3();
}
class A5 extends Abs_A4{
public A5(int id, String name){
super(id, name);
}
//note: must subclass must overwrite all the abstract function of it's superclass
public void print(){ //overwrite superclass' abstract function
System.out.printf("A5: id is %d, name is %s\n", id, name);
}
//error! cannot define an abstract function in a non-abstract class
//public abstract void print4();
}
public class Abstract_2{
public static void f(){
Abs_A4 a4 = new A5(1, "Jack");
a4.print();
}
}
上面定义了1个抽线类Abs_A4 和非抽象类 A5, 下面就利用上面的例子列出抽象方法的一些特性.
1.2.1 抽象方法不能包含函数体, 而且要用分号结尾
例如上面的类Abs_A4 里的
public abstract void print();
1.2.2 一个抽象类可以存在非抽象方法
例如上面的类Abs_A4的构造方法就是非抽象的.
1.2.3 抽象方法不能存在与非抽象类中
例如上面例子这例子中:
//error! cannot define an abstract function in a non-abstract class
//public abstract void print4();
这句是错误的, 因为尝试在非抽象类A5中定义1个抽象方法, 必须注释掉
也就说, 如果1个类只要存在1个抽象方法, 那么它必须是抽象类.
1.2.4 如果1个非抽象的派生类继承1个抽象类, 那么这个非抽象类必须重写所有它的抽象超类的所有抽象方法
例如上面的例子中, Abs_A4里存在1个抽象方法print(), 如何非抽象类A5 继承 Abs_A4, 则A5 必须重写print(), 否则编译失败.
其实这跟上面的1.2.3 特征是同一道理, 也就是: 非抽象类不能存在抽象方法!
1.2.5 不能用private 修饰抽象方法.
Java里, private修饰的成员是能被子类隐藏继承的. 但是private修饰的方法不能被继承.
所以如果在一个抽象超类定义1个private abstract 方法, 那么非抽象子类则无法继承这个方法, 也就是说无法重写了.
所以Java里是不允许private 和 abstract 同时使用修饰1个方法的. 编译会失败.
实际上private 方法都是final 方法, 下面会提到.
1.2.6 不能用static 修饰抽象方法.
static 方法是属于类本身的方法. 而且static 方法不需要实例化就可以调用, 如果定义1个static抽象方法, 那么直接用利用类名来调用这个方法就很奇怪了, 因为抽象方法没有函数体啊.
现实中理解:
假如1个植物类族, 那么植物就是抽象类.
重量, 体积 这些可以定作为成员, 可以被所有子类继承.
繁殖可以定为抽象方法, 因为各种植物的繁殖方法很多种, 由各个子类重写.
求重量, 求体积的方法可以定义成公共可继承方法. 因为这些方法的函数体基本不变.
一些公式转换, 例如光合作用的公式可以定义成静态方法, 这些方法不必实例化就可以使用.
至于抽象静态方法, 本人觉得现实中还是存在的, 例如高等植物和低等植物(蓝藻等)的RNA/DNA 合成公式可能不同, 有必要重写. 但是JAVA中不允许抽象静态方法的存在, 原因上面已经提到.
1.3 abstract 不允许修饰成员.
原因也很简单啦, 因为成员是没有重写这个概念的.
也即系讲, java中冇抽象成员这个概念.
二. final
2.1 final 修饰 类.
final 修饰类则表示这个类是1个无法被继承的类, 也就是在类族树中最底层的类, 如果你不想1个类被派生出子类, 那么可以用final 来修饰这个类.
下面例子:
package Object_kng.Final_kng;
final class Fnl_A8{
int id;
String name;
public void print(){
}
}
//class A9 extends Fnl_A8{ //error. cannot inherit a final class
class A9{
int id;
String name;
public void print(){
System.out.printf("A5: id is %d, name is %s\n", id, name);
}
}
public class Final_1{
public static void f(){
}
}
上面的Fnl_A8 是1个最终类, 当A9尝试继承Fnl_A8 会编译失败..
没什么特别要注意的地方.
2.2 final 修饰方法
Final 修饰1个方法表示这个方法是无法被派生类重写. 如果你希望1个方法不再被其派生类重写, 可以用final来修饰这个方法.
下面例子:
class A10{
int id;
String name;
public final void print(){
System.out.printf("A10: id is %d, name is %s\n", id, name);
}
public final static void print2(){
System.out.printf("A10:print2\n");
}
private void print3(){ // Java will treat all the private mothods as final methods
System.out.printf("A10:print3\n");
}
}
class A11 extends A10{
//error , final method cannot be overwrited in subclasses
//public void print(){
//}
public void print3(){ // it's not overwriting superclass's print3, another mother
//have a same name with superclass's print3
System.out.printf("A11:print3\n");
}
}
public class Final_2{
public static void f(){
A11 a11 = new A11();
a11.id = 1;
a11.name = "jack";
a11.print();
a11.print2(); // A10:print2
a11.print3(); // A11:print3
}
}
上面定义两个类其中A11 继承 A10
下面分析下fina方法的一些特征
2.2.1 final方法可以被子类继承, 但是不能重写.
这个是最基本的了, 如上面A10 的方法 print 和 print2.
2.2.2 final类里可以定义final方法.
编译会通过, 但是final类不能被继承, 所以里面定义final无业务上的意义. 但是貌似会轻微加快编译和运行速度.
2.2.3 final 和 abstract 关键字不能一齐使用
也就是无论类或方法, 都不能同时是抽象的也是final的.
2.2.4 private方法会被认为是final方法.
也就是说private 方法不能被继承和重写.
但是还是有区别的, 如上面的例子, A10 定义了1个public final 方法print(). 当类A11尝试定义1个名字为print() (同名同参数)的方法是, 编译会失败.
但是A10还定义了1个private方法print3().
而A11 还是允许定义1个名字叫print3()的方法的, 这不过这两个print3()方法不是重写关系, 只是简单的重名.
2.3 final 修饰成员
final修饰成员or变量就表示这是1个常变量, 相当c/c++ 的const 常量.
默认赋值并不是真正的赋值.
这里所谓的默认赋值是指 int i; i的默认值就是0, 但是final成员不允许这样赋值
也就是说 final成员允许两种赋值, 1是定义成语时同时赋值. 2是在构造函数中赋值.
例子:
class A12{
final int id;
final int id2 = 2;
//final int id3; //error
public A12(int id){
this.id = id;
//this.id2 = 2; //error
}
public void f(){
//id2 = 2; //error
}
}
public class Final_3{
}
2.3.1 final变量必须赋值.
例如上面
//final int id3; 没有在定义时赋值, 也没有在构造函数中赋值,则编译失败.
2.3.2 final变量不能赋两次值
例如上面的id2, 在定义时赋值了, 如果尝试在构造函数再次赋值则编译失败
2.3.3 final变量能在非构造方法赋值
因为构造方法只会在实例化时执行一次, 而普通方法的执行次数是不定的.