注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

饥民2011

一直在搬砖

 
 
 

日志

 
 
 
 

Java 里的 abstract 和 final 关键字  

2014-02-07 20:16:46|  分类: Java |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

一. abstract

abstract 的中文意思就是抽象的, 所谓抽象就是这个东西在现实里不存在, 也就是不能直接实例化的意思.
abstract 可以修饰类, 类的方法


1.1 abstract 修饰 类.

现实中有一些类是不应该实例化的. 例如

                 植物
               /          \
        开花植物  蕨类
       /               \
    裸子植物   被子植物
                            /       \
                       苹果     芒果


上面那个简单的类结构体中,  前面3层都是抽象的, 在自然界没有现实的对象存在, 所以在面向过程中应该把它们设为抽象类.
只有下面的若干层派生类才应该可以被实例化.

也可以看出, 抽象类一般是类族的最上面若干层.


看下面的例子.

  1. abstract class Abs_A1{  
  2.     int id;  
  3.     String name;      
  4.     abstract void print();  
  5. }  
  6.   
  7.   
  8. class A2 extends Abs_A1{  
  9.     void print(){  
  10.         System.out.printf("A2\n");  
  11.     }     
  12. }  
  13.   
  14. abstract class Abs_A3 extends A2{  
  15.     abstract void print();    
  16. }  
  17.   
  18. public class Abstract_1{  
  19.     public static void f(){  
  20.         //Abs_A1 a1 = new Abs_A1();   //error  
  21.         A2 a2 = new A2(); //ok  
  22.         //Abs_A3 a3 = new Abs_A3() //error  
  23.   
  24.         Abs_A1 a1 = new A2(); //ok,    
  25.         a1.print();  
  26.     }  
  27. }  


上面定义了2个抽象类A1,A3,   1个非抽象类A2.   它们的关系是 A1 派生出 A2, A2 派生出 A3.

然后我们来从下面的f() 函数里分析一下抽象类的实现.


1.1.1  抽象类不能直接实例化

这个很明显了, 上面的例子里尝试直接实例化, 都会编译失败.

1.1.2  非抽象类可以派生出抽象类

例如上面A2 派生出Abs_A3

1.1.3  抽象类一般和多态技术相结合

  1. Abs_A1 a1 = new A2(); //ok,    
  2. a1.print();  

看上面两句,  虽然定义了1个Abs_A1 的引用, 但是它指向了它的派生类的实例化对象,  这样虽然a1 是1个抽象类的引用, 但是它仍然可以调用派生类重写后的方法.
这个就是多态的关键点之一.



1.2 abstract 修饰方法.

abstract 修饰一个方法就是表示这个方法是一个抽象方法, 所谓抽象方法就是没有方法体的函数.  也就是必须在派生类中重写后才能调用.

例子: 

  1. abstract class Abs_A4{  
  2.     int id;  
  3.     String name;  
  4.     public Abs_A4(int id, String name){  //abstract class can have non-abstract function.  
  5.         this.id = id;  
  6.         this.name = name;  
  7.     }  
  8.   
  9.     public abstract void print();  //abstract function, with no function body, must have symbol ";"  
  10.   
  11.     public static void print2(){  
  12.         System.out.print("A4:function print2\n");     
  13.     }  
  14.   
  15.     //cannot use "private" & "abstract" togethe  
  16.     //private abstract void print3();  
  17.   
  18.     // cannot use "static" & "abstract" together  
  19.     //public static abstract void print3();  
  20. }  
  21.   
  22. class A5 extends Abs_A4{  
  23.     public A5(int id, String name){  
  24.         super(id, name);  
  25.     }  
  26.   
  27.     //note: must subclass must overwrite all the abstract function of it's superclass  
  28.     public void print(){  //overwrite superclass' abstract function  
  29.         System.out.printf("A5: id is %d, name is %s\n", id, name);  
  30.     }  
  31.   
  32.     //error!  cannot define an abstract function in a non-abstract class  
  33.     //public abstract void print4();  
  34.   
  35.   
  36. }  
  37.   
  38.   
  39. public class Abstract_2{  
  40.     public static void f(){  
  41.         Abs_A4 a4 = new A5(1"Jack");  
  42.         a4.print();  
  43.   
  44.     }  
  45. }  


上面定义了1个抽线类Abs_A4 和非抽象类 A5,  下面就利用上面的例子列出抽象方法的一些特性.

1.2.1  抽象方法不能包含函数体, 而且要用分号结尾

例如上面的类Abs_A4 里的
  1. public abstract void print();   

1.2.2  一个抽象类可以存在非抽象方法

例如上面的类Abs_A4的构造方法就是非抽象的.


1.2.3  抽象方法不能存在与非抽象类中

例如上面例子这例子中:
  1. //error!  cannot define an abstract function in a non-abstract class  
  2. //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个超类存在1个private方法f(),   子类再定义1个同名方法f(),  那么这个两个方法只是简单的同名方法, 并没有继承关系.

所以如果在一个抽象超类定义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 来修饰这个类.

下面例子:

  1. package Object_kng.Final_kng;  
  2.   
  3. final class Fnl_A8{  
  4.     int id;  
  5.     String name;  
  6.   
  7.     public void print(){  
  8.   
  9.     }  
  10. }  
  11.   
  12. //class A9 extends Fnl_A8{   //error. cannot inherit a final class  
  13. class A9{  
  14.     int id;  
  15.     String name;  
  16.     public void print(){  
  17.         System.out.printf("A5: id is %d, name is %s\n", id, name);    
  18.     }  
  19. }  
  20.   
  21.   
  22. public class Final_1{  
  23.     public static void f(){  
  24.   
  25.     }  
  26. }  


上面的Fnl_A8 是1个最终类,   当A9尝试继承Fnl_A8 会编译失败..

没什么特别要注意的地方.


2.2 final 修饰方法

Final 修饰1个方法表示这个方法是无法被派生类重写.   如果你希望1个方法不再被其派生类重写, 可以用final来修饰这个方法.

下面例子:
  1. class A10{  
  2.     int id;  
  3.     String name;  
  4.   
  5.     public final void print(){  
  6.         System.out.printf("A10: id is %d, name is %s\n", id, name);   
  7.     }  
  8.   
  9.     public final static void print2(){  
  10.         System.out.printf("A10:print2\n");  
  11.     }  
  12.   
  13.     private void print3(){  // Java will treat all the private mothods as final methods  
  14.         System.out.printf("A10:print3\n");  
  15.     }  
  16. }  
  17.   
  18. class A11 extends A10{  
  19.     //error ,  final method cannot be overwrited in subclasses   
  20.     //public void print(){  
  21.   
  22.     //}  
  23.   
  24.     public void print3(){ // it's not overwriting superclass's print3, another mother  
  25.                                      //have a same name with superclass's print3  
  26.         System.out.printf("A11:print3\n");  
  27.     }  
  28. }  
  29.   
  30.   
  31. public class Final_2{  
  32.     public static void f(){  
  33.         A11 a11 = new A11();  
  34.         a11.id = 1;  
  35.         a11.name = "jack";    
  36.         a11.print();  
  37.         a11.print2(); //  A10:print2  
  38.         a11.print3(); //  A11:print3  
  39.     }  
  40. }  

上面定义两个类其中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 常量.


final成员必须赋值且只能赋一次值. 但是默认赋值并不是真正的赋值.     

这里所谓的默认赋值是指  int  i;  i的默认值就是0, 但是final成员不允许这样赋值

也就是说 final成员允许两种赋值,  1是定义成语时同时赋值.   2是在构造函数中赋值.

例子:

  1. class A12{  
  2.     final int id;  
  3.     final int id2 = 2;  
  4.     //final int id3; //error  
  5.     public A12(int id){  
  6.         this.id = id;  
  7.         //this.id2 = 2; //error  
  8.     }  
  9.   
  10.     public void f(){  
  11.         //id2 = 2;  //error  
  12.     }  
  13. }  
  14.   
  15. public class Final_3{  
  16.   
  17. }  


  

2.3.1  final变量必须赋值.

例如上面

//final int id3;   没有在定义时赋值, 也没有在构造函数中赋值,则编译失败.

2.3.2  final变量不能赋两次值

例如上面的id2,  在定义时赋值了, 如果尝试在构造函数再次赋值则编译失败


2.3.3  final变量能在非构造方法赋值

因为构造方法只会在实例化时执行一次, 而普通方法的执行次数是不定的.
  评论这张
 
阅读(105)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017