精华内容
下载资源
问答
  • 抽象类和接口的区别简洁
    千次阅读
    2016-09-29 20:39:26

    抽象类和接口又什么好处

    从这个问题我们可以衍生出两个问题:

    1.     为什么要用抽象类?

    2.      为什么要使用接口?

    企业单位在面试的时候经常会问到!

    答案综合考虑以下方面:

    1:强调不但应该具有什么能力,而且应该具有什么特性,并且一些能力有共同的处理细节的时候,可以用抽象类;
    2:只强调应该具有什么能力的时候,并且处理细节可能完全不同,就考虑用接口;
    3:抽象类考虑从上到下的继承和拥有,接口考虑从左到右的规范;

    ==============================================================

     

    使用接口定义行为,由于接口是特殊的抽象类,也就可以使用抽象类定义行为,抽象类同时能够实现行为;但仅是定义行为时,就可使用接口,因为接口能够更有效地分离定义与实现,为代码的维护和修改带来方便。

    ==============================================================

     

    1,  抽象类提供了一部分功能的实现,如果子类的功能实现和父类的功能实现是相同的,可以使用抽象

    2,  接口只规定了类的函数规范,没有具体实现。

    ==============================================================

     

    面向对象的经典是面向抽象编程。 

    所以,用抽象的型别统一类型,来进行操作,有利于以后的扩展,移植,复用!!

    ==============================================================

     

    其实抽象类的一个好处是类不能被实例化,最大的好处就是通过方法的覆盖来实现多态的属性。也就是运行期绑定

    ==============================================================

    我想用比较通俗的方法告诉你: 
      假如有两个程序员,两个在两个程序里都要用到一种功能,比如要取一个方法名。 
      甲自己做了一个方法叫getname,乙也作了一个方法叫qumingzi。如果两个人要去看对方的程序,那么

     这个方法要读懂是不是要有一个过程? 
      如果在公司里,有个抽象类,里面有个抽象方法getName,公司规定,凡遇到这样的问题就实现这个方法。  

     那么这两个人要读对方的代码是不是就容易了?? 

    ==============================================================

    假如很多人要买水果吃,吃的动作只有一个,但是有的人要用现金买,有的人用信用卡,有的人赊帐。要为每个人定义一个类,就要定义多个相同的吃的方法。如果定义一个抽象类,在里面实现吃的方法,再做一个买的抽象方法。那每个人的类都从这个抽象类派生下来,只要实现买的方法即可,吃的方法就可以直接用父类的方法了。如果要改吃的方法就不用改多个,只要改这个抽象类里的就行了。

    ==============================================================

    抽象类将事物的共性的东西提取出来,抽象成一个高层的类。子类由其继承时,也拥有了这个超类的属性和方法。---也就实现了代码的复用了。 
    子类中也可加上自己所特有的属性和方法。----也就实现了多态

    ==============================================================

    以学生为列,博士和硕士都是学生,那么学生就成为了一个父类了,他们都具有学生的特征,那些共同的特点就可以继承,实现代码重用 
      但是如果还有一种类型的学生,它既具有博士,又具有硕士的特点,那我岂不是要继承两个类,但是这两个类里又有很多相同的特征啊,那就现的代码冗余了,是吗? 
      那么我现在的目的就是要使它简洁,但是要达到那个效果,我就会用到接口,所谓的接口有两个含义 
      1。方法的集合 
      2。概念 
      第二中就是我们JAVA中用的,它把类中某些独有的方法用接口的形式表现出来,然后你就直接implement接口就是拉,再继承学生那个类,就可以拉  

    ==============================================================

    接口的好处之一: 
      就是可以多重继承, 而类不可以~ 

       
      子类,可以同是继承至一个接口和一个抽象基类 

    ==============================================================

    例子: 
           
        有以下类: 
        类(动物) 类(狗) 类(大象) 
              类(狗)和类(大象)是类(动物)的子类。 
              如果我希望别人只能创建类(狗)和类(大象)的对象,绝不希望别人能直接创建类(动物)的对象(直接创建类(动物)是无意义的,因为每一种动物都有它的真正类),于是我就可以把类(动物)做成抽象类

    ==============================================================

    就象建立房子的框架 
       
      里面的房间可以分别设计,但是框架不变 

    ==============================================================

    面向对象的经典是面向抽象编程 
       
      抽象的意思,就是你不必为每个插销定义一个插座。 
      你不用为每个CPU定义一个主板  
       
      抽象类和接口的意思,就是规范,大家都遵守。这样符合规范的东西,大家都不用了解各自的细节。插座不会了解插销怎么做的。插销也不必了解插座怎么做的。

    抽象类和接口的意思,就是规范,大家都遵守。这样符合规范的东西,大家都不用了解各自的细节。插座不会了解插销怎么做的。插销也不必了解插座怎么做的。

    ==============================================================

    JAVA是基于C/C++开发出来的,也可以称作是二者综合体的简化版,去处其中的一些过于复杂的控制功能或改由计算机自动处理。其中就包括去除了C++中的多重继承功能,而改由不计数量的接口来实现。  
      举个简单的例子:古希腊神话中的飞马。它既能奔跑(马的方法),又可以飞(鸟的方法)。在C++中可以通过多重继承来实现,但在JAVA中则必须通过接口来实现。或者继承马的方法,并实现一个鸟飞的接口。或者同时实现一个跑的接口和飞的接口。 
      接口具有良好可扩展性。有了跑和飞的两个借口,你可以轻松的做出飞猪,飞驴等具有类似功能的对象。 
      接口中不得实现任何方法。一个类一旦要实现一个接口,则必须实现改接口的所有方法,即使在该方法中不执行任何操作(即方法体内为空)。 
      接口与抽象类具有类似的功能。在使用上,一般接口用于定义比较普遍的方法,而抽象类主要用于比较具体的方法。

     

    面向对象的本质:万物皆对象!  
       
      封装:封装就是指利用抽象数据类型将数据和基于数据的操作封装在一起,数据被保护在抽象类型的内部,系统的其他部分只有通过包裹在数据外面的被授权的操作,才能够与这个抽象数据类型交流与交互! 
       
      继承:继承实际上是存在于面向对象程序中的两个类之间的关系。当一个类拥有另一个类的所有数据和操作时,就称这两个类之间具有继承关系! 
       
      多态:多态是指一个程序中同名的不同方法共存的情况。面向对象的程序中多态的情况有多种,可以通过子类对父类方法的覆盖实现多态,也可以利用重载在同一个类中定义多个同名的不同方法! 
       
      OO=Objects+Classes+Inheritance   +   Communication   With  messages  
      那还是教科书上说的, 
      多态,解释就是一个方法被多次构造,,经常出现在,继承关系类(抽象类,接口) 
      回忆一下接口使用   
      interface   I_Test{  
            public   void   Print   (){}  
      }  
      class   C_A   implment   I_Test{  
            public   void   Print(){  System.out.println("C_A")}  
      }  
      class   C_B   implment   I_Test{  
            public   void  Print(){System.out.println("C_B")}    
      }  
       
      再来看看class  Test{  
          public   void   main(){  
          I_Test   i=   new   C_A();  
                    i.Print();   //打印出C_A  
            i=   new   C_B();  
                    i.Print();//打印出C_B  
      }      
      //,"多态性是站在类的角度完成了接口与实现的分离",多态调用Print()方法,不知道能不能解释这句话,个人是这样一来理解的。 
       
      我个人认为“多态”不单是对父子类,接口,抽象类的理解。。关键还是要对  protect,private正确使用中去体味。 
      这样才能更好隔离,综合使用多态才会清晰。
       
       
      接口(Interface)是用来定义行为的!  
      抽象类(Abstract  Class)是用来实现行为的! 
      具体类()是用来执行行为的! 
       
       

       
                           支持多重继承,  支持抽象函数,  允许实现    ,允许创建实体,允许部分实现 
      interface           支持                        支持                  不允许              不允许                  不允许  
      abstract   class不支持                      支持                  允许                不允许                    允许 
      class                   不支持                不支持                    允许                  允许                    不允许  
       
       
      所以打算多继承,又并不想马上实现它的时候,就用接口。需要实现它的类需要。比如我上述例子中的Print(),,通常对应多个实现类的。(记得所有的方法都必须是public的,也可以用有static   final)  
       
      如果只需要实现部分的部分方法,又不需要全部实现,那就可以用抽象类。对于抽象方法,需要overrider  就是要重写一个方法来实现  所定义好的abstract  方法。(这个和接口一样) 
       
      如果全部实现,又全部要执行的。。那么就用直接类吧,父子关系一带被调用它的时候,比如子类继承了父类的方法时候,一调用就执行了。 
       

       
      其实,要从宏观上讲,那就牵涉到软件工程理论了。 
      个人看法: 
      事物的出现,总是有它出现的理由(需求)! 
      但是,事物的生存,需要有适应性(灵活)! 
      比如:长颈鹿,脖子长,这是个抽象的概念; 
      能吃草(是非肉食性动物的特征,接口)。 
       
      所以,当需要描述长颈鹿的时候,我们就可以用接口(非肉食性动物)和抽象(脖子长)的方法来定义它, 
      然后,具体是什么种类/或者颜色的长颈鹿,怎么个吃草法,这就是具体类了。  
       =============================================================

    经典解释是:使用了对象就叫基于对象,基于对象的基础上增加了继承从而变成了面向对象。 
       
      基于对象的最大特点就是封装,至于private,protected,public,并不是基于对象语言的必须要求。同时应该注意到的是,封装对于应用本身并没有提供太大的帮助;换句话说封装仅仅改变了方法的表达形式,除了析构函数能够提供一些资源安全上的保证,当然java没有析构函数。比如某些网络应用,包装了socket,然而必须的accept,listen,connect,read,write还是需要你自己去调用,只不过调用方式有所变化而已;更麻烦的是处理错误,可能的错误你必须完全接管,那就需要对问题本身有很深入的了解。基于对象让你不容易犯错误,但是不能改变对问题本身的理解。 
      于是出现了面向对象。继承,多态只是它的特点而已,一种表象。而关键优越性在于它可以改变对问题的认识。比如处处可见的画图的例子,p.Draw(),就能画出一个形状,都是Draw()方法,但是可以根据p的不同,动态选择。无可否认,这个是多态性的优越性的例子。然而继承又如何呢,可以这样说,如果不使用多态,继承就没有意义了。因为没有多态,继承事实上导致了较高层次的包装而已,与基于对象相同,它不能改变对问题本身的理解。很多时候继承被包裹所代替,COM的对象重用,就是典型的包裹,不过它叫聚集而已。类层次越深管理越困难。 
      面向对象提供了极大的灵活性,然而作为一个概念,外延太丰富,反而不容易把握。看看JFC的文档,实在太灵活了,感觉上这样那样几个东西拼合起来,窗口就可以做成了。事实却不是如此,你必须去了解layer,了解listener,了解很多东西,然后按照固定的规则进行拼装才能达到效果。简单说,看看javadoc的JFC文档,很难学会如何去实现一个可用的窗口应用。这一点明显比不上VB,其实这就和使用面向对象方法是为了接口易用性这一个基本原则背离了。外延被扩大化到难以接受的规模,这是使用面向对象方法很容易犯的毛病。而设计模式提供了一些方法缓和这些矛盾,比如用静态函数创建对象,私有构造,使得对象不能被任意创建,这就大大压缩了这些对象的外延。简单看看MessageDigest,可能在内部有md2,md4,md5,sha1的类供我创建对象,但是这些我都不让你看到,你只要知道一个MessageDigest就足够了,免得产生误会。  
       
      先说那么多了。 
       
       
      回复人:  Leemaasn(我给大家拜早年啦!新春快乐!!!)  (   )   信誉:101    2004-1-12   18:47:34    得分:0   
       
      zengpan_panpan()    
      牛,,,很彻底地分析了“何谓对象”!!! 
       
      回复人:  Leemaasn(我给大家拜早年啦!新春快乐!!!)  (   )   信誉:101    2004-1-12   20:40:23    得分:0   
       
      对于  zengpan_panpan()所说的“而关键优越性在于它可以改变对问题的认识。”!  
      个人看法如下: 
      通常,我们要使用一个对象的paint方法时,如果我们很具体的考虑这个Paint问题时,我们也许会从想要画什么开始。然后,考虑从该画什么东西,又考虑对于这个东西我们应该怎么画。。。。 
      一层层深入时,,,就会陷入到Windows底层API函数(我以Windows平台为例。),楼主是搞Delphi的。这点应该会明白。。。  
      如果只是简单几个类,那也无所谓,当大规模生产时,还是这么做,后果可想而知的(比如:Windows的MFC、或者VCL)。  
       
       
      如果我们以对象和接口的角度来考虑这个Paint问题。  
      那么,我们可以先定义好一些接口,比如paint这个接口。  
      然后再来设计对象,如果某个对象,需要paint这个方法,那么,我们可以让它实现paint这个接口。  
      具体怎么实现,这就是抽象类或者具体类的事情了。 
      然后,我们在其他地方调用这个对象的paint方法时,  
      我们就只需要按照这个paint这个接口的规定来调用就行了。  
       
       
      在这里,“改变对问题的认识”改变在什么地方呢?我们从对象的认识从一个具体到抽象、封装的方向变了。 
      就像人,又分为黄色人种、白色人种、黑色人种(抽象类)等。。。 
      中国人、印度人。。。(接口)等。。。 
       
      如果再抽象,可以看作:脊椎动物。。。。 
       
       
      哈,扯远了。。。 
       
       
      其实,真正的解答,应该请zengpan_panpan()来说明!!!  
      有请我们真正的主角。。。zengpan_panpan()隆重登场。。  
       
       
      回复人:  iamwls(-----魔高一尺,道高一丈-----)  (   )   信誉:96    2004-1-12   20:43:27    得分:0   
       
      to:   zengpan_panpan()    
       
      你所说的:使用了对象就叫基于对象,基于对象的基础上增加了继承从而变成了面向对象。 
       
      好象并不太准确 
       
      记得(好象是,呵呵)有人问JAVA的创始人:如果你想重来一次JAVA,你改变的是什么?  
      答:去掉继承 
       
      也许应该是增加了抽象多态,  从而变成了面向对象可能更准确点 
       
      回复人:  Leemaasn(我给大家拜早年啦!新春快乐!!!)  (   )   信誉:101    2004-1-12   21:06:27    得分:0   
       
      哈,好象这句话,我也有看过。。。 
      记得(好象是,呵呵)有人问JAVA的创始人:如果你想重来一次JAVA,你改变的是什么?  
      答:去掉继承 
       
       
      但没有继承,总觉得。。。似乎世界不太完美。。。。 
       
      回复人:  zengpan_panpan()   (   )   信誉:100    2004-1-12   21:45:13    得分:0   
       
      基于对象和面向对象的定义也不是我说的,以前是这么定义的。 
      至于JAVA要去掉继承,从方法角度讲是可行,不过至少还得保留接口,否则多态无从谈起。不过这需要推翻整个jdk,肯定就不现实了。另外,看看Object类的实现可以发现,里面的notify,   wait系列方法,有多少时候用到了,要知道任何一个对象都有这样的方法,而这两个方法明显是线程相关的;然而线程类仅仅是它的一个子孙而已。这会让人觉得别扭。其实这个问题的关键在于java不仅仅是一门语言,还是一个运行环境(jvm),它们是紧紧捆绑在一起的,Object如此定义是一个妥协的结果。  
       
      呵呵,扯远了,回到面向对象问题上来: 
      其实,面向对象也好,面向过程也好,现在喊得火热的GP也好,争来争去,没有太大意义。不管什么应用,都是面向需求的,这样那样的方法都是手段而已,目的才是最重要的。理论说多了乏味得很。 
       
      关于面向对象方法,有一个比较经典的例子。我回了最后一篇,不过后来沉下去了。 
       
      http://expert.csdn.net/Expert/topic/2625/2625842.xml?temp=.9095117 
       
      欢迎提出不同意见。 
       
      另外,我来出个题目: 
       
      需求很简单:设计个线程池。 
       
      于是现在假设出两种方案,互相对比一下。从需求入手: 
       
      1.   方案一:假设有一线程池对象  tpool;  
          Thread   t   =   tpool.get();  
          t.start(myrunner);  
          tpool.put(t);  
       
      2.   方案二:假设有一线程池类  TPool;  
          TPool.start(myrunner);  
       
      你觉得哪个好,好在哪里,哪个不好,不好又在哪里? 

    zengpan_panpan()   (   )    太精彩了,你的解释,我也随便拜读你的连接文章。:)  果然精彩,请您继续为我们讲解啊。 
       
      我觉得是第二种好。 
      虽然两个都是基于对象的。 
      但是,线程中,反复的创建销毁对象是非常耗资源的。 
      我具体也不怎么会解释,这些池化类方法是比较好的,, 
      让我想到了数据库的连接池。。。。 
       
      请您给我们答案吧。。。期待中。 
       
       
      灯光,,掌声。。。。大家把手机关了:) 
       
      回复人:  zengpan_panpan()   (   )   信誉:100    2004-1-15   14:56:07    得分:0   
       
      其实第一种方法不好。 
      虽然符合一般池子的概念,比如,向池子申请资源,归还资源。 
       
      但是就因为这样将造成线程使用者和线程池之间的强耦合。 
       
      比如向线程池请求一个线程,就不得不考虑,它还有没有线程可以提供给我用?如果没有到底是返回错误,还是还是把我挂起?另外线程运行了我的runner,我什么时候知道知道它结束,如果它还没有结束,我又怎么能把线程归还给池子。考虑这些问题自然就很麻烦了,不但设计应用麻烦,设计线程池也麻烦。 
       
      而第二种就很简单了。 
      把我的runner直接提供给池子,如果它暂时没有线程可用,那么排队就是了。如果不允许排队可以考虑增加一个addTask,提供个参数说明我不排队,要是没有直接报错。更多的通过这个参数甚至可以控制线程优先级一系列的问题,而不需要对池子有多大改动。 
       
      线程池本身,涉及到很多方面的问题,比如操作系统调度之类的问题,里面也会包含一些对象化的思考,这些思考并不在于用什么具体语言去实现。现在给出第二种的最简单实现,可以去分析一下里面到底有多少对象化概念,有什么模式,也可能像这种模式,不过似是而非,但是这些都不重要,并不见得一定要符合什么模式才是对的。前面已经说过,不管面向什么,不管什么方法,最终都是面向需求的。最后应该把你的程序看成一块胶泥,想捏成圆的就圆的,想捏成方的就方的。 
       
      //   TPool.java  
      import   java.lang.*;  
      import   java.util.*;  
      import   java.io.*;  
       
      class   MyRunner   implements   Runnable  
      {  
                      String  name;  
       
                      public   MyRunner(String  x)  
                      {  
                                     name   =   x;  
                      }  
       
                      public  void   run()  
                      {  
                                     System.out.println("My  name   is   "   +   name);  
                                     try   {   Thread.sleep(50);  }   catch   (Exception   e)   {}  
       
                      }  
      }  
       
      public   class   TPool   implements   Runnable 
      {  
                      private  int   id;  
                      private  static   LinkedList   pool   =   new   LinkedList(); 
                      private  TPool   (int   i)   {   id   =   i;   } 
       
                      public  void   run()  
                      {  
                                     for   (;;)  
                                     {  
                                                    Runnable   r;  
                                                    synchronized(pool)  
                                                    {  
                                                                   System.out.println("[Thread   "   +   id   +  "]   Enter   Sleep");  
                                                                   while   (pool.size()   ==   0)  
                                                                    { 
                                                                                   try  
                                                                                   {  
                                                                                                  pool.wait();  
                                                                                   }  catch(InterruptedException   e)  
                                                                                   {  
                                                                                   }  
                                                                    } 
                                                                    r  =   (Runnable)pool.getFirst();  
                                                                   pool.removeFirst();  
                                                                   System.out.println("[Thread   "   +   id   +  "]   Exec   Runner");  
                                                    }  
                                                    r.run();  
                                     }  
                      }  
       
                      static  void   addTask(Runnable   r)  
                      {  
                                     synchronized(pool)  
                                     {  
                                                    pool.add(r);  
                                                    pool.notifyAll();  
                                     }  
                      }  
       
                      static  void   init   (int   size)  
                      {  
                                     for   (int   i   =  0;   i   <   size;   i++)  
                                                    (new   Thread(new   TPool(i))).start();  
                      }  
       
                      static  void   main(String   args[])  
                      {  
                                     TPool.addTask(new  MyRunner("A"));  
                                     TPool.addTask(new  MyRunner("B"));  
                                     TPool.addTask(new  MyRunner("C"));  
                                     TPool.init(3);  
                                     for(;;)  
                                                    try  
                                                    {  
                                                                   Thread.sleep(1000);  
                                                                    int  n   =   (int)(Math.random()   *   5);  
                                                                   System.out.println("\nGenerate   "   +   n   +  "   Tasks\n");  
                                                                   for   (int   i   =   0;   i   <   n;  i++)  
                                                                                   TPool.addTask(new  MyRunner(   (new   Double(Math.random())).toString()  ));  
                                                    }   catch(Exception   e)  
                                                    {  
                                                    }  
                      }  
      }  

    接口(Interface)是用来定义行为的! 
      抽象类(Abstract  Class)是用来实现行为的! 
      具体类()是用来执行行为的! 
       
      这个不错 
      抽象类用来实现公用行为更好一点 
       
      其实除了interface外,还有adapter模式可以参照

    对于接口、抽象类,我的理解是:从类图出发,根据实际情况定义接口或抽象类,接口是定义行为的,任何一个类都可以看作有一个接口,如果需要可以定义接口,否则不需要定义,而抽象类是可以定义和实现行为,但不能执行行为,因为它不能实例化,但可以定义对象,这个对象的行为是抽象类的实现类的实例的行为。

    从设计上说抽象类的好处有二: 
      一、抽象类提供了部分实现,使子类可以重用部分抽象类的代码 
      二、抽象类为其子类提供了共同的接口(注意:这里说我接口与  java   中的  interface   是不同的) 
              也就是别人可以将各个子类的对象当成抽象类的对象使用。 
               
      我觉得“为了方便扩展”的说法是荒谬的,如果可以实现,为什么要代码写到别的类中去呢? 
      抽象类的关键是抽象方法,而设计抽象方法的原因是在于抽象类自己没有办法实现这个方法, 
      或者说在抽象类这一层,我们不知道如何实现这个方法,只有到了具体类才有办法知道实现。 
      如“动物”这个抽象类,有一个抽象方法“吃”,但是动物这个类是不能实现“吃这个方法的,  
      只有到了“人”这个类,才能有“用手将东西放进嘴里然后吞下去”这样的实现。  
      “动物”这个类的“吃”方法也不可能有默认的实现,因为现实中没有一个纯“动物”的实例, 
      它要么是“人”类的实例,要么是其它“动物”类子类的实例! 
      但我们一旦碰到一个“动物”对象,就可以调用“吃”这个方法, 
      当然我们也要预期不同对象的实现不是一样的 

    interface表述“has   a”关系,用于描述功能  
      是用来说明它的是实现类是“做什么?”的 
      至于“怎么作?”,interface并不进行约束  
       
      而abstract  class表述“is  a”关系,它在描述功能的同时 
      也通过具体实现部分功能的方式,来约束“怎么作?”  
      只有接受了这个约束,才应该去继承它 
      从而可以将“其中被具体实现的部分功能”,在它的子类中得到重用 
       
      实际使用当中,interface与而abstract   class各有优势,  
      需要权衡。 
       
      不过,有时候,两者也可以兼用 
      《effective  java》中提出, 
      可以声明一个interface描述功能,  
      再提供一个该接口的骨架(skeleton)实现(实现该接口的abstract   class)  
      固化对功能实现方式的约束,从而尽可能地实现重用 
       
      于是就有了另一个问题: 
      在使用当中,可能发现这个skeleton实现不能满足现有需求  
      这时可以在原有的interface基础上,派生出另一个skeleton实现  

    抽象类作为超类,可以将子类的公用逻辑用方法实现,而接口只是定义了实现类的规范(其实就是定义了实现类该做什么) 
      因此子类有公用的逻辑部分就在抽象类里实现该方法的细节,子类中有共性但实现细节不同则定义为抽象方法。抽象类只能被一个子类继承,而接口可以被多个实现类实现,所以同意楼上的接口的多态性更好

    接口:  只定义方法名称,没有方法的具体实现。 
       
      抽象类:  只定义方法名称,没有方法的具体实现。    (与   接口   相同) 
                        定义方法名称,并实现这些方法。                (与  类      相同) 
                        以上两者都有。(真正的抽象类大多是这种方式.  可参看API源代码)  
       
      类:定义的方法必须都有实现。

     

    抽象类与接口在设计时各有什么好处?2007-04-13 13:37接口不能执行任何的方法,而抽象类可以。

    类可以执行许多接口,但只有一个父类。 这个也有人说用接口可以实现多重继承。

    接口不是类分级结构的一部分。而没有联系的类可以执行相同的接口。

     

    具体说说:

    接口是对象属性和方法的描述(但不包括他们具体实现),比如Int32类型实现了IConvertible接口,就说明Int32具有 IConvertible接口所描述的ToDouble()方法。但IConvertible并没有给出实现ToDouble()的内容,需要由 Int32类型自己实现。

    接口用于实现多态。比如Int32,Int64和Single类型都实现了IConvertible借口,那么就说明他们一定都具有ToDouble()方法。所以,定义一个变量是IConvertible类型:

    IConvertible c;

    然后,无论是给c赋任何实现IConvertible类型的变量,我都能够保证,c至少有一个ToDouble()方法可用,而不用关心c具体是什么类型的变量。如

    int i = 3; //Int32

    long j = 6L; //Int64

    float k = 4F; //Single

     

    c = i;

    c.ToDouble();

     

    c = j;

    c.ToDouble();

     

    c = k;

    c.ToDouble();

    都不会发生错误。

     

    实现接口的类可以显式实现该接口的成员。当显式实现某成员时,不能通过类实例访问该成员,而只能通过该接口的实例访问该成员。

    接口的应用大多数是在DesignPattern时才用到。

     

     

    抽象类,从多个对象中抽出来的“共性”,而他的后代,既有共性、又有特性。例如:“图形”是抽象的,没有形状,由点线组成;正方形、圆形是他的派生,可以是对象。 

     

    更多相关内容
  • 面向对象编程之继承、多态、封装、抽象类接口、包继承类的继承格式为什么要继承继承的特点继承的种类及关键字访问权限多态向上转型动态绑定...抽象类和接口区别接口与类的区别包包的语法格式导入包的作用欢迎指正...

    下篇👇
    面向对象编程之继承、多态、封装、抽象类、接口、包-下(实战图书管理系统)

    继承

    继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

    类的继承格式

    在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:

    class 父类 {
    }
     
    class 子类 extends 父类 {
    }
    

    为什么要继承

    从这下面两段代码可以看出来,代码存在重复了,导致后果就是代码量大且臃肿,而且维护性不高(维护性主要是后期需要修改的时候,就需要修改很多的代码,容易出错)

    /**
     1. user:ypc;
     2. date:2021-04-20;
     3. time: 17:13;
     */
    public class Dog {
        private String name;
        private int id;
        public Dog(String myName, int  myid) {
            name = myName;
            id = myid;
        }
        public void eat(){
            System.out.println(name+"正在吃");
        }
        public void sleep(){
            System.out.println(name+"正在睡");
        }
        public void introduction() {
            System.out.println("大家好!我是"         + id + "号" + name + ".");
        }
    }
    
    /**
     4. user:ypc;
     5. date:2021-04-20;
     6. time: 17:14;
     */
    public class Cat {
        private String name;
        private int id;
        public Cat(String myName, int  myid) {
            name = myName;
            id = myid;
        }
        public void eat(){
            System.out.println(name+"正在吃");
        }
        public void sleep(){
            System.out.println(name+"正在睡");
        }
        public void introduction() {
            System.out.println("大家好!我是"         + id + "号" + name + ".");
        }
    }
    

    所以要从根本上解决这两段代码的问题,就需要继承,将两段代码中相同的部分提取出来组成 一个父类:

    public class Animal { 
        private String name;  
        private int id; 
        public Animal(String myName, int myid) { 
            name = myName; 
            id = myid;
        } 
        public void eat(){ 
            System.out.println(name+"正在吃"); 
        }
        public void sleep(){
            System.out.println(name+"正在睡");
        }
        public void introduction() { 
            System.out.println("大家好!我是"         + id + "号" + name + "."); 
        } 
    }
    

    这个Animal类就可以作为一个父类,然后狗类和猫类继承这个类之后,就具有父类当中的属性和方法,子类就不会存在重复的代码,维护性也提高,代码也更加简洁,提高代码的复用性(复用性主要是可以多次使用,不用再多次写同样的代码) 继承之后的代码:

    public class Dog extends Animal { 
        public Dog (String myName, int myid) { 
            super(myName, myid); 
        } 
    }
    public class Cat extends Animal { 
        public Cat (String myName, int myid) { 
            super(myName, myid); 
        } 
    }
    

    继承的特点

    🍟使用 extends 指定父类.
    🍟Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性
    🍟子类会继承父类的所有 public 的字段和方法.
    🍟对于父类的 private 的字段和方法, 子类中是无法访问的.
    🍟子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用
    🍟提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
    🍟子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

    继承的种类及关键字

    🥚种类
    在这里插入图片描述
    🥚关键字
    final 关键字
    final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.
    父类没有final修饰子类可以正常继承
    在这里插入图片描述
    有final关键字修饰父类子类不可以继承,编译会报错
    在这里插入图片描述

    super 关键字
    super关键字:
    1.super()调用父类的构造方法,必需放在第一行
    在这里插入图片描述
    2.super.父类的成员变量,用super来调用父类的成员变量
    在这里插入图片描述
    3.调用父类的成员函数 super.func()
    在这里插入图片描述

    this关键字
    指向自己的引用。
    在这里插入图片描述

    extends关键字
    在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
    多继承编译会报错👇
    在这里插入图片描述

    访问权限

    🍿private 修饰的方法和变量只可以在自己的类中访问,子类也不可以访问。
    🍿default 就是不加private,protected,public修饰限定符修饰的方法或变量,在同一个包的同一类和不同类可以访问default方法和变量
    🍿protected相比于default,在不同包的子类可以访问protected方法或变量,但这个类必需是它的父类。
    🍿public可以在任一个类中访问变量和方法。
    🍿可以在任何一个类中使用public类
    🍿不能用private和protected来修饰类
    🍿访问权限从高到低排列顺序是:
    public protected default private
    在这里插入图片描述

    多态

    向上转型

    向上转型,JAVA中的一种调用方式。向上转型是对A的对象的方法的扩充,即A的对象可访问B从A中继承来的和B“重写”A的方法。
    例如:

    public  class Animal {
        public  String name;
        public  int age;
        public Animal(String name, int  age) {
            this.age = age;
            this.name = name;
        }
        public void eat() {
            System.out.println(this.name+"在吃东西");
        }
        public void sleep() {
            System.out.println(this.name+"在睡觉");
            }
    }
    class Pig extends Animal{
        public Pig(String name, int  age){
            super( name, age);
        }
        void funcPig(){
        }
       public  void eat(){
           System.out.println(this.name+"吃桃子");
        }
         void func(){
             System.out.println(super.age);
             this.eat();//调用自己的eat()fangfa
             super.eat();//调用父类的eat()方法
             this.funcPig();
         }
    
    }
    
    class Test{
        public static void main(String[] args) {
            /*
            正常使用
             */
            Animal animal1 = new Animal("gougou",12);
            animal1.eat();
            Pig pig  = new Pig("zhuzhu",8);
            pig.eat();
            /*
            向上转型
             */
            Animal animal =new Pig("bajie",78);
            animal.eat();
        }
    
    
    }
    

    运行结果为:
    在这里插入图片描述

    动态绑定

    在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引
    用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定.
    上面的Animal animal =new Pig(“bajie”,78);
    animal.eat();就发生了动态绑定
    构造方法也可以发生动态绑定(也叫运行时绑定)
    动态绑定的条件:
    🎊一定有向上转型
    🎊子类重写了父类的方法,发生了方法的重写
    🎊通过父类的引用来调用被重写的方法

    方法重写

    子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override)
    比如刚才的eat()方法,在Pig子类中重写了父类Animal的eat()方法

    public  class Animal {
        public  String name;
        public  int age;
        public Animal(String name, int  age) {
            this.age = age;
            this.name = name;
        }
        public void eat() {
            System.out.println(this.name+"在吃东西");
        }
        public void sleep() {
            System.out.println(this.name+"在睡觉");
            }
    }
    class Pig extends Animal{
        public Pig(String name, int  age){
            super( name, age);
        }
        void funcPig(){
        }
       public  void eat(){//重写父类的eat()方法
           System.out.println(this.name+"吃桃子");
        }
         void func(){
             System.out.println(super.age);
             this.eat();//调用自己的eat()fangfa
             super.eat();//调用父类的eat()方法
             this.funcPig();
         }
    
    }
    
    class Test{
        public static void main(String[] args) {
            /*
            正常使用
             */
            Animal animal1 = new Animal("gougou",12);
            animal1.eat();
            Pig pig  = new Pig("zhuzhu",8);
            pig.eat();
            /*
            向上转型
             */
            Animal animal =new Pig("bajie",78);
            animal.eat();
        }
    
    
    }
    

    针对重写的方法, 可以使用 @Override 注解来显式指定.
    在这里插入图片描述
    如果不重写eat()方法编译就会报错
    在这里插入图片描述

    重写和重载的区别比较

    重写
    重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

    重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
    重写规则:

    🎈参数列表与被重写方法的参数列表必须完全相同。

    🎈返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

    🎈访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

    🎈父类的成员方法只能被它的子类重写。

    🎈声明为 final 的方法不能被重写。

    🎈声明为 static 的方法不能被重写,但是能够被再次声明。

    🎈子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

    🎈子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

    🎈重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

    🎈构造方法不能被重写。

    🎈如果不能继承一个类,则不能重写该类的方法
    重载
    重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
    每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
    重载规则:

    🎄被重载的方法必须改变参数列表(参数个数或类型不一样);
    🎄被重载的方法可以改变返回类型;
    🎄被重载的方法可以改变访问修饰符;
    🎄被重载的方法可以声明新的或更广的检查异常;
    🎄方法能够在同一个类中或者在一个子类中被重载。
    🎄无法以返回值类型作为重载函数的区分标准。
    在这里插入图片描述

    多态的优点

    🎁 消除类型之间的耦合关系
    🎁可替换性
    🎁可扩充性
    🎁接口性
    🎁灵活性
    🎁简化性
    🎁多态存在的三个必要条件
    1.继承
    2.重写
    3.父类引用指向子类对象:Parent p = new Child();
    多态的实现方式
    🚗方式一:重写:
    参见上文
    🚗方式二:接口
    java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。
    参见下文
    🚗方式三:抽象类和抽象方法
    参见下文

    封装

    在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
    封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
    要访问该类的代码和数据,必须通过严格的接口控制。
    封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
    适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

    实现Java的封装

    1. 修改属性的可见性来限制对属性的访问(一般限制为private)如:
    /**
     * user:ypc;
     * date:2021-04-20;
     * time: 19:15;
     */
    public class Person {
        private String name;
        private int age;
    }
    

    这段代码中,将 name 和 age 属性设置为私有的,只能本类才能访问.如此就对信息进行了隐藏。
    2. 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问如:get\set方法(idea快捷键alt+Ins插入)

    /**
     * user:ypc;
     * date:2021-04-20;
     * time: 19:15;
     */
    public class Person {
        private String name;
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    在这里插入图片描述
    3.实例化

    /**
     * user:ypc;
     * date:2021-04-20;
     * time: 19:15;
     */
    public class Person {
        private String name;
        private int age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }
    class TestDemo{
        public static void main(String[] args) {
            Person person = new Person();
            person.setAge(20);
            person.setName("jack");
            System.out.println(person.getName());
            System.out.println(person.getAge());
    
        }
    }
    

    结果
    在这里插入图片描述

    封装的优点

    🎨良好的封装能够减少耦合。

    🎨类内部的结构可以自由修改。

    🎨可以对成员变量进行更精确的控制。

    🎨隐藏信息,实现细节

    抽象类

    抽象类及其实现

    在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
    由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
    父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
    例如:Person就是一个抽象类

    public abstract class Person {
        private String name;
        private int age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
        public void Work(){}
    }
    class Teacher extends Person{
        public void Work(){
            System.out.println(this.getName()+"的工作是教书");
        }
    }
    class Student extends  Person{
        public void Work(){
            System.out.println(this.getName()+"的工作学习");
        }
    }
    class TestDemo{
        public static void main(String[] args) {
         //   Person person = new Person();不能实例化否则编译就会报错
            person.setAge(20);
            person.setName("jack");
            System.out.println(person.getName());
            System.out.println(person.getAge());
    
        }
    }
    

    在这里插入图片描述

    抽象方法

    如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
    Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
    抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
    如:Pereon类中的Work()方法
    在这里插入图片描述
    抽象方法必需被重写否则就会报错

    /**
     * user:ypc;
     * date:2021-04-20;
     * time: 19:15;
     */
    public abstract class Person {
        private String name;
        private int age;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAge() {
            return age;
        }
        public void setAge(int age) {
            this.age = age;
        }
       // public void Work(){}
        public abstract  void Work();
    }
    class Teacher extends Person{//抽象方法必需被重写否则就会报错
        public void Work(){
            System.out.println(this.getName()+"的工作是教书");
        }
    }
    class Student extends  Person{
        public void Work(){
            System.out.println(this.getName()+"的工作是学习");
        }
    }
    class TestDemo{
        public static void main(String[] args) {
            Person person = new Student();
            person.setName("Steven");
            person.Work();
    //        Person person = new Person();
    //        person.setAge(20);
    //        person.setName("jack");
    //        System.out.println(person.getName());
    //        System.out.println(person.getAge());
    
        }
    }
    

    在这里插入图片描述

    抽象类使用的注意事项

    👧如果一个类包含抽象方法,那么该类必须是抽象类。
    任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
    👧继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。
    👧抽象类可以发生向上转型,可以被继承,可以动态绑定
    👧抽象类不能被实例化
    👧抽象类被继承时方法一定要重写
    👧抽象类存在的意义就是为了被继承
    👧抽象方法不能是private
    👧抽象类可以有非抽象方法

    接口

    接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含
    静态常量.
    接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

    接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

    除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

    接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象

    接口的声明

    [可见度] interface 接口名称 [extends 其他的接口名] {
    // 声明变量
    // 抽象方法
    }
    例如:IShape就是一个接口

    interface IShape {
        void draw();
    }
    class Rect implements  IShape{
        public void draw(){
            System.out.println("⬜");
        }
    }
    class Cycle implements IShape {
        public void draw() {
            System.out.println("○");
        }
    }
     class TestDemo2 {
        public static void main(String[] args) {
            IShape shape = new Rect();
            shape.draw();
        }
    }
    

    接口的特性

    🎦接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
    🎦接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
    🎦接口中的方法都是公有的。
    也就是说interface IShape{}中的方法默认都是从public abstrat的,并且不能有具体实现
    变量默认都是public abstrat final的
    🎦接口可以向上转型,可以发生运行时绑定即动态绑定
    🎦接口中的方法可以具体实现但必须是这样的:default void function(){…}
    🎦一个类可以实现多个接口
    🎦接口的实现就是为了满足继承
    🎦接口可以拓展
    🎦一个类可以实现一个接口也可以继承一个类
    如下:
    在这里插入图片描述

    class A{
    
    }
    interface IShape {
        void draw();
    }
    /*
    🎦一个类可以实现多个接口
    🎦接口的实现就是为了满足继承
    🎦接口可以拓展
    🎦一个类可以实现一个接口也可以继承一个类
     */
    interface IShape2 extends IShape {
        void draw();
    }
    class Rect extends A implements  IShape,IShape2{
        public void draw(){
            System.out.println("⬜");
        }
    }
    class Cycle implements IShape {
        public void draw() {
            System.out.println("○");
        }
    }
     class TestDemo2 {
        public static void main(String[] args) {
            IShape shape = new Rect();
            shape.draw();
        }
    }
    

    抽象类和接口的区别

    🎅抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
    🎅 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
    🎅接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
    🎅 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

    接口与类的区别

    🤶接口不能用于实例化对象。
    🤶接口没有构造方法。
    🤶接口中所有的方法必须是抽象方法。
    🤶接口不能包含成员变量,除了 static 和 final 变量。
    🤶接口不是被类继承了,而是要被类实现。
    🤶接口支持多继承

    为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。

    包的语法格式和导入

    包的语法格式
    package pkg1[.pkg2[.pkg3…]];
    包的导入
    import package1[.package2…].(classname|*);
    例如:Demo6导入了B包中的Demo5类并在主函数中实列化了Demo5类,使用了Demo5类的func函数
    同时也继承了A接口(不能实现A接口(不能使用implements接口),只能继承)
    在这里插入图片描述

    在这里插入图片描述
    包B中的Demo5类的代码

    package B;
    
    /**
     * user:ypc;
     * date:2021-04-20;
     * time: 20:46;
     */
    interface A{
        public void test();
    }
    public class Demo5 {
        public void func(){
            System.out.println("导入了B包中的Demo5类的func()方法");
        }
    }
    
    

    包外类Demo6的代码

    import B.Demo5;
    /**
     * user:ypc;
     * date:2021-04-20;
     * time: 20:46;
     */
    class B extends A {
        public void test(){
            System.out.println("继承了B包中的A接口的test方法");
        }
    }
    public class Demo6 {
        public static void main(String[] args) {
            B b = new B();
            b.test();
            Demo5 demo5 = new Demo5();
            demo5.func();
    
        }
    
    }
    
    

    包的作用

    📆把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。

    📆如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。

    📆包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

    Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

    欢迎指正😄

    展开全文
  • 抽象类 接口 匿名类的区别

    千次阅读 2015-07-29 15:28:11
    1 抽象类  当编写一个类时,常常会为该类定义一些方法,这些方法用以描述该类的行为方式,那么这些方法都有具体的方法体。但在某些情况下,某个父类并不需要实现,因为它只需要当做一个模板,而具体的实现,...

    1 抽象类                                                                 

      当编写一个类时,常常会为该类定义一些方法,这些方法用以描述该类的行为方式,那么这些方法都有具体的方法体。但在某些情况下,某个父类并不需要实现,因为它只需要当做一个模板,而具体的实现,可以由它的子类来实现。比如说一个长方体的表面积和一个立方体的表面积计算方式是有区别的,长方体表面积需要有三个参数,而立方体需要一个参数。
      抽象方法可以只有方法签名,而没有方法实现。

    1.1 抽象方法和抽象类的定义  

      抽象方法和抽象类必须使用abstract修饰符来定义,有抽象方法的类只能被定义成抽象类,抽象类里可以没有抽象方法。
    抽象方法和抽象类的规则如下:
      1.抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。连大括号都不能用
      2.抽象类不能被实例化(实例化是为了调用属性和方法,抽象类本身没有方法实现的),无法使用new关键字来调用抽象类的构造器来初始化抽象类的实例。即使抽象类里不包含抽象方法,这个抽象类也不能创建实例。
      3.抽象类可以包含属性、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类、枚举类六种成分。抽象类的构造器不能用于创建实例,主要用于被其子类调用
      4.含义抽象方法的类(包括直接定义了一个抽象方法:继承了一个抽象父类,但没有完全实现父类包含的抽象方法;以及实现了一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义成抽象类。
    根据定义规则:普通类不能包含抽象方法,而抽象类不仅可以包含普通方法,也可以包含抽象方法。

    复制代码
    abstract class 类名称{
    
      属性;
    
        权限类型  返回值类型 方法名称(参数类别 参数列表){
    
    }
    
        权限类型 abstract  返回值类型 抽象方法名称(参数类型 参数列表)
    
    }

      注意些抽象方法时候:只能是声明,后面连大括号{}都不能跟。大括号里面哪怕什么都没有,也表示是空语句,不是抽象方法。

      一个抽象类不能用final来声明,因为抽象类是需要继承来覆写抽象类的方法,但是final关键字意味着抽象类不能有子类,显然矛盾。

      抽象类的抽象方法也不能用private声明,因为抽象类的抽象方法需要子类覆写,使用private修饰的话,子类无法覆写抽象方法。

      抽象方法和空方法体的方法不是同一个概念。例如public abstract void test();是一个抽象方法,它根本没有方法体,即方法定义后面没有一对花括号;但public void test(){}方法是一个普通方法,它已经定义了一个方法体,只是方法体为空,方法体上面也不做,这个方法不能用abstract来修饰。
    abstract不能用于修饰属性,不能用于修饰局部变量,即没有抽象属性、抽象变量的说法,abstract也不能用于修饰构造器,抽象类里定义的构造器只能是普通构造器。
      除此之外,当使用static来修饰一个方法时,表面这个方法属于当前类,即该方法可以通过类来调用,如果该方法被定义成抽象方法,则将导致通过该类来调用该方法时出现错误(调用了一个没有方法体的方法肯定会引起错误),因此static和abstract不能同时修饰某个方法,即没有所谓的类抽象方法。
      abstract关键字修饰的方法必须被其子类重写才有意义,否则这个方法将永远不会有方法体,因此abstract方法不能定义为private访问权限,即private和abstract不能同时使用。

    package chapter6;
    
    import java.util.Scanner;
    
    public class Circle extends Shape{
        private double radius;
        public Circle(String color,double radius){
            super(color);
            if(radius > 0){
                this.radius = radius;
            }
        }
        public void setRadius(double radius){
            if(radius > 0){
                this.radius = radius;
            }
        }
        //重写计算园周长的方法
        public double calPerimeter(){
            return 2 * Math.PI * radius;
        }
        public String getType(){
            return getColor() + "圆";
        }
        
        public static void main(String[] args){
            Triangle s1 = new Triangle("红色",3,4,5);
            Circle s2 = new Circle("紫色",5);
            System.out.println(s1.getType() + " 周长是" + s1.calPerimeter());
            System.out.println(s2.getType() + " 周长是" + s2.calPerimeter());
            
        }
    }

    1.2 抽象类的作用                  

      从前面的实例可以看出,抽象类不能创建实例,它只能当成父类被继承。从语义角度来看,抽象类是从多个具体类抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类设计的随意性。
      抽象类提醒的就是一种模板模式的设计,抽象类作为子类的模板,子类在抽象类的继承上进行扩展和改造。
      如果编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给子类实现,这就是一种模板模式,模板模式也是最常见、最简单的设计模式之一。如前面的Shape、Circle和Triangle就是使用这种模式。

    2 接口:更彻底的抽象                                             

      抽象类是从多个类抽象出来的模板,如果将这种抽象进行得更彻底,则可以提炼出一种更加特殊的"抽象类"——接口(interface),接口里不能包含普通方法,接口里所有方法都是抽象方法。

    2.1 接口的概念                   

      经常听说接口,比如PCI接口、AGP接口,因此很多人认为接口等同于主机板上的插槽,这是一种错误的认识。当我们说PCI接口时,指的是主机板上那条插槽遵守的了PCI规范,而具体的PCI插槽只是PCI接口的实例。
      对于不同型号的主机板而言,它们各自的PCI插槽都需要遵守一个规范,遵守这个规范就可以保证插入该插槽里的板卡能与主机板正常通信。对于同一个型号的主机板而言,它们的PCI插槽都需要相同的数据交换方式、相同的实现细节,它们都是同一个类的不同实例。
      下图展示了三种层次的关系:接口层次、类层次和对象层次


      从上图可以看出,同一个类的内部状态数据,各种方法的实现细节完全相同,类是一种具体实现体。而接口定义了一种规范,接口定义某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不需要关系这些类里方法的实现细节。它只规定这批类里必须提供某些方法,提供这些方法的类就可满足实际需要。
    可见,接口是从多个相似类中抽象出来的规范,接口不提供任何实现。接口体现的是规范和实现分离的设计这些。

      让规范和实现分离是接口的好处,让软件系统的各组件之间面向接口耦合,是一种松耦合的设计。例如主机板上提供了PCI插槽,只要一块显卡遵守PCI接口规范,就可以插入PCI插槽内,与该主板正常通信。至于这块显卡是哪个厂家制造的,内部如果实现,主机板无需关心。
      类似的,软件系统的各模块之间也应该采用面向接口的耦合,尽量降低各模块之间的耦合,为系统提供更好的可扩展性和可维护性。
      因此接口定义的是多个类共同的公共行为规范,这些行为是与外部交流的通道,这就意味着接口里通常是定义一组共用方法。

    2.2 接口的定义                        

      和类定义不同,定义接口不再使用class关键字,而是使用interface关键字,接口定义的基本语法是:

    修饰符 interface 接口名 extends 父接口1,父接口2...{
            零到多个常量定义...
            领个到多个抽象方法定义...
        }

    语法的详细说明如下:
      1.修饰符可以是public或者省略,如果省略了public访问控制符,则默认采用包权限访问控制符,即只有在相同包结构下才可以访问该接口。
      2.接口名应与类名采用相同的命名规则,即如果仅从语法角度来看,接口名只要合法即可,但实际是需要是有意义。
      3.一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
      接口是一种规范,因此接口里不能包含构造器和初始化块定义,接口里只能包含常量属性、抽象实例方法(不能有static)、内部类(包括内部接口)和枚举类定义。
      由于接口是一种公共规范,因此接口里面的所有成员包括常量、抽象方法、内部类和枚举类都是public访问权限。定义接口成员时候,可以省略控制修饰符,但如果指定,则只能是public。对于接口里定义的常量属性而言,它们是接口相关的,它们只能是常量,因此系统会自动为这些属性增加static和final两个修饰符。也就是说,在接口定义属性时,不管是否使用publicstatic final修饰符,接口里的属性总将使用这三个修饰符来修饰。而且由于接口里没有构造器和初始化块,因此接口里定义的属性只能定义时指定默认值。
      接口里定义属性采用如下两行代码的结果完全一样:

    int MAX_SIZE = 50;
    public static final int MAX_SIZE = 50;
    

      接口定义的抽象方法修饰符是public abstract,不能有static修饰,内部类和枚举类都默认采用public static两个修饰符,不管定义时是否只能这两个修饰符,系统自动使用public static 对它们进行修饰。

    package chapter6;
    
    public abstract interface Output {
        //接口定义的属性只能是常量
        int MAX_CACHE_LINE = 50;
        //接口里定义只能是抽象方法
        void out();
        void getData(String name);
    }
    package chapter6;
    
    public class TestOutputProperty {
        public static void main(String[] args){
            //访问另一个包中的属性        System.out.println(chapter6.Output.MAX_CACHE_LINE);
            //不能重新赋值,因为接口的属性都是final的
            //Output.MAX_CACHE_LINE = 3;    }
    }

    2.3接口的继承                      

      接口的继承和类继承不同,接口完全支持多继承,即一个接口可以有多个直接父接口。和类继承相似,子接口扩展某个父接口,将获得父接口的所有抽象方法、常量属性、内部类和枚举类定义。 
      一个接口继承多个父接口时,多个父接口排在extends关键字之后,多个父接口之间以英文逗号隔开。

    package chapter6;
    
    interface interfaceA{
        int PROP_A = 5;
        void test_A();
    }
    
    interface interfaceB{
        int PROP_B = 6;
        void test_B();
    }
    
    interface interfaceC extends interfaceA,interfaceB{
        int PROP_C = 6;
    }
    public class TestInterfaceExtends {
        public static void main(String[] args){
            System.out.println(interfaceA.PROP_A);
            System.out.println(interfaceB.PROP_B);
            System.out.println(interfaceC.PROP_C);
        }
    }

    2.4 使用接口                      

      接口不能用于创建实例,但接口可以用于声明引用类型的变量。当使用接口来声明引用类型的变量时,这个引用类型的变量必须引用到其实现类的对象。除此之外,接口主要用途是被实现类实现。
      一个类可以实现一个或多个接口,继承使用extends关键字,实现则使用implements关键字。因为一个类可以实现多个接口,这也是Java为单继承灵活性不足所做的补充。类实现接口的语法格式:

    修饰符 class 类名 extends 父类 implements 接口1,接口2...{
        类体部分
    }

      实现接口与继承父类相似,一样可以获得所实现接口里定义常量属性、抽象方法、内部类和枚举类定义。
      让类实现接口需要类定义后增加implements部分,当需要实现多个接口时,多个接口之间以英文逗号(,)隔开。一个类可以继承一个父类,并同时实现多个接口,implements部分必须放在extends部分之后。
      一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义抽象类。下面看一个实现接口的类。

    import lee.Output;
    
    //定义一个Product接口interface Product
    {
        int getProduceTime();
    }
    //让Printer类实现Output和Product接口public class Printer implements Output , Product
    {
        private String[] printData 
            = new String[MAX_CACHE_LINE];
        //用以记录当前需打印的作业数
        private int dataNum = 0;
        public void out()
        {
            //只要还有作业,继续打印
            while(dataNum > 0)
            {
                System.out.println("打印机打印:" + printData[0]);
                //把作业队列整体前移一位,并将剩下的作业数减1
                System.arraycopy(printData , 1
                    , printData, 0, --dataNum);
            }
        }
        public void getData(String msg)
        {
            if (dataNum >= MAX_CACHE_LINE)
            {
                System.out.println("输出队列已满,添加失败");
            }
            else
            {
                //把打印数据添加到队列里,已保存数据的数量加1。
                printData[dataNum++] = msg;
            }
        }
        public int getProduceTime()
        {
            return 45;
        }
        public static void main(String[] args)
        {
            //创建一个Printer对象,当成Output使用
            Output o = new Printer();
            o.getData("轻量级Java EE企业应用实战");
            o.getData("疯狂Java讲义");
            o.out();
            o.getData("疯狂Android讲义");
            o.getData("疯狂Ajax讲义");
            o.out();
            //创建一个Printer对象,当成Product使用
            Product p = new Printer();
            System.out.println(p.getProduceTime());
            //所有接口类型的引用变量都可直接赋给Object类型的变量
            Object obj = p;
        }
    }

      实现接口方法时,必须使用public访问控制修饰符,因为接口里的方法都是public的,而子类(相当于实现类)重写父类方法时访问权限只能更大或相等,所以实现类实现接口里的方法时只能使用public访问权限。
      接口不能显式继承任何类,但所有接口类型的引用变量都可以直接赋给Object类型的引用变量。所以上面的程序可以把Product类型变量直接赋给Object类型的变量,这是利用向上转型实现,因为编译器知道任何Java对象都必须是Object或其子类的实例。

    2.5 接口和抽象类对比            

      接口和抽象都很像,它们都具有如下特征:
      1. 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
      2. 接口和抽象类可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

      但接口和抽象类之间的差别很大,这种差别主要体现在二者设计目的上,下面具体分析二者的差别。
      接口作为系统与外界交互的窗口,接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式来提供);对于接口的调用者而言,接口规定了调用者可以调用哪些服务,当在一个程序中使用接口时,接口是多个模块间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。
    但接口和抽象类之间的差别非常大,这种差别主要体现在二者的设计目的上,下面具体分析两者的差别。
      接口作为系统与外界交互的窗口,接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式来提供);对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务。当在一个程序中使用接口时,接口是多个模块间的耦合标准;当在多个应用程序之间使用接口时,接口是多个程序之间的通信标准。
      从某种程度上来看,接口类似于整个系统的总纲,制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口被改变,对整个系统甚至其他系统的影响将是辐射式的,导致系统中大部分类都需要改写。
      抽象类则不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模板式设计。抽象父类作为多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(那些已经提供实现的方法),但这个产品依然不能当成最终产品,必须有跟进一步的完善,这种完善可能有几种不同方式。
    除此之外,接口和抽象类在用法上也存在如下区别。
      1. 接口里只能包含抽象方法,不包含已经提供实现的方法;抽象类则完全可以包含普通方法。
      2. 接口里不能定义静态方法;抽象类里可以定义静态方法。
      3. 接口里只能定义静态常量属性,不能定义普通属性;抽象类里则既可以定义普通属性,也可以定义静态常量属性。
      4. 接口不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而让其子类调用这些构造器来完成属于抽象类的初始化操作。
      5. 接口里不能包含初始化块,但抽象类则完全可以包含初始化块。
      6. 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足。

    2.6 面向接口编程

      接口体现的是一种规范和设计分离的设计哲学,充分利用接口可以极好地降低程序各模块之间的耦合,从而提高系统的可扩展性和可维护性。
    基于这种原则,很多软件架构设计理论都倡导面向接口编程,而不是面向实现类编程,希望通过面向接口编程来降低程序的耦合。下面将介绍两种常用场景来示范面向接口编程的优势。

    2.6.1 工场模式                       

       有这样的场景,假设程序中有个Computer类需要组合一个输出设备,现在又两个选择:直接让Computer该类组合衣柜Printer属性,或者让Computer组合一个Output属性,采用哪种方式更好呢?

      假设让Computer组合一个Printer属性,如果有一天系统需要重构,需要使用BetterPrinter来代替Printer,于是需要打开Computer类源代码进行修改。如果系统中只有一个Computer类组合了Printer属性,那么我们只需要修改这一个Computer类就可以了,但是如果有1000个、10000个类甚至更多的类,那么就需要一个个打开这么多文件进行修改(并不是这么多类都放在一个word文档里面进行批量替换这么简单的)。这种工作量非常之大。为了避免这个问题,我们让Computer组合一个Output属性,将Computer类与Printer类完全分离。Computer对象实际组合的是Printer对象,还是BetterPrinter对象,对Computer而言是屏蔽的,由Output来进行耦合Computer和Output属性
    理清一下思路:
      假设现在一个应用程序要打印一份文件,那么这个应用程序可以调用windows平台开放的打印程序,而一台计算机上可以连接多个不同类型的打印机,这样打印程序就可以接口的形式来调用具体实现打印功能的类
    示例:
    下面这个程序可以当做windows平台的打印程序,用于开放给应用程序

    package chapter6;
    
    public class Computer {
        private Output out;
        public Computer(Output out){
            this.out = out;
        }
        //定义一个模拟字符串输入的方法
        public void keyIn(String msg){
            out.getData(msg);
        }
        public void print(){
            out.out();
        }
    }

      上面的程序已经将Computer类和Printer类相分离,用Output接口来耦合,也就是使用Output来生产(创建)Printer类对象来供Computer类来使用。不再用Computer来生产。

    package chapter6;
    
    public class OutputFactory {
        //制造Printer对象
        public Output getOutput(){
            return new Printer();
        }
        public static void main(String[] args){
            OutputFactory of = new OutputFactory();
            Computer c = new Computer(of.getOutput());
            c.keyIn("疯狂Java讲义");
            c.keyIn("简明德语语法");
            c.print();
        }
    }
    
    
    public class Printer implements Output{
        private String[] printData = new String[MAX_CACHE_LINE];
        //记录当前需要打印的页数
        private int dataNum = 0;
        public void out(){
            //只要页数大于0继续打印
            while(dataNum > 0){
                System.out.println("打印机打印:" + printData[0]);
                //作业队列整体前移一位,并将剩下的作业减1
                System.arraycopy(printData, 1, printData, 0, --dataNum);
            }
        }
        public void getData(String msg){
            if(dataNum >= MAX_CACHE_LINE){
                System.out.println("输出队列已满,添加失败");
            }else{
                printData[dataNum++] = msg;
            }
        }
    }

      我们可以把上面的Printer代码修改成BetterPrinter,并且在OutputFactory程序中修改制造对象。

    2.6.2 命令模式                       

      有这样的场景:某个方法需要完成某个行为,但这个行为的具体实现无法确定,必须等到执行该方法时才可以确定。具体一点:假设有个方法需要遍历某个数组的数组元素,但无法确定在遍历数组时候还需要做什么操作,需要在调用该方法时指定具体的处理行为。这个看起来很奇怪,难道调用函数时候,能够把行为作为一个参数?在某些编程语言,如Ruby,这种特性是支持的,在Java里面不支持,但是可以间接做到。
      Java是不允许代码块单独存在的,因此可以使用一个Command接口来定义一个方法来封装处理行为。

    package chapter6;
    
    public interface Command {
        //定义封装抽象的行为
        void process(int[] target);
        
    }
    package chapter6;
    
    public class ProcessArray {
        public void process(int[] target,Command cmd){
            cmd.process(target);
        }
    }

      通过一个Command类,就实现了让ProcessArray类和具体处理行为相分离,程序使用Command接口处理代表处理数组的处理行为。Command接口也没有提供真正的处理,只有等到需要调用ProcessArray对象的process方法时,才真正传入一个Comman对象,才确定对数组的处理行为。

    package chapter6;
    
    public class PrintCommand implements Command{
        public void process(int[] target){
            for(int temp:target){
                System.out.println("迭代输出的数组是:" + temp);
            }
        }
    }
    package chapter6;
    
    public class AddCommand implements Command{
        public void process(int[] target){
            int sum = 0;
            for(int temp:target){
                sum += temp;
            }
            System.out.println("数组元素总和是:" + sum);
        }
    
    }
    package chapter6;
    
    public class TestCommand {
        public static void main(String[] args){
            ProcessArray pa = new ProcessArray();
            int[] target = {3,4,9};
            //第一次处理数组
            pa.process(target, new PrintCommand());
            System.out.println("---------------");
            //第二次处理数组
            pa.process(target, new AddCommand());
            
        }
    }

    3 内部类                                                              

      大部分时候,我们把类定义成一个独立的程序单元。在某些情况下,我们把一个类放在另一个类的内部定义,这个定义在其他类内部的类被称为内部类(也被称为嵌套类),包含内部类的类也被称为外部类(有的地方也叫宿主类)。内部类有如下作用。
      1.内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类。假设需要创建一个Cow类,Cow类需要组合一个CowLeg属性,CowLeg类只有在Cow类里才有效,离开Cow类之后没有任何意义。这种情况下就可以把CowLeg类定义成Cow的内部类,不允许其他类访问CowLeg
      2.内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问,但外部类不能访问内部类的实现细节,例如内部类的属性。
      3.匿名内部类适合用于创建那些仅需要一次使用的类。对于前面介绍的命令模式,当需要传入一个Command对象时,重新专门定义PrintCommand和AddCommand两个实现类可能没有太大的意义,因为这两个实现类可能仅需要使用一次。这种情况下,匿名内部类使用更方便。

    3.1 非静态内部类                 

    1 非静态内部类
    2     public class OuterClass{
    3     //此处定义内部类4     }
    

      大部分时候,内部类被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与属性、方法、构造器和初始化块相似的类成员;局部内部类和匿名内部类则不是类成员。
     
      成员内部类分为两种:静态内部类和非静态内部类,使用static修饰的成员内部类是静态内部类,没有使用static修饰的成员内部类是非静态内部类。

     1 package chapter6;
     2 
     3 public class Cow {
     4     private double weight;
     5     //外部类的两个重载构造器 6     public Cow(){
     7         
     8     }
     9     public Cow(double weight){
    10         this.weight = weight;
    11     }
    12     //定义一个非静态内部类13     private class CowLeg{
    14         //非静态内部类的两个属性15         private double length;
    16         private String color;
    17         //非静态内部类的两个构造器18         public CowLeg(){
    19             
    20         }
    21         public CowLeg(double length,String color){
    22             this.length = length;
    23             this.color = color;
    24         }
    25         public void setLength(double length){
    26             this.length = length;
    27         }
    28         public double getLength(){
    29             return length;
    30         }
    31         public void setColor(String color){
    32             
    33         }
    34         public String getColor(){
    35             return color;
    36         }
    37         //非静态内部类的实例方法38         public void info(){
    39             System.out.println("当前牛腿的颜色是: " + color + " 高:" + length);
    40             //直接访问外部类属性41             System.out.println("本牛腿所在的奶牛体重:" + weight);
    42         }
    43     }
    44     //用内部类创建对象45     public void test(){
    46         CowLeg cl = new CowLeg(1.1,"黑白相间");
    47         cl.info();
    48     }
    49     public static void main(String[] args){
    50         Cow c = new Cow(380);
    51         c.test();
    52     }
    53 }
    54 输出结果:
    55 当前牛腿的颜色是: 黑白相间 高:1.1
    56 本牛腿所在的奶牛体重:380.0
    

    从上面的结果可以看出:
      1.内部类和类的属性一样,有多种作用域,当定义成private时,访问级别是类内部,内部类可以访问外部类的属性、方法,外部类里的方法也可以访问内部类的属性、方法
      2.内部类的使用和普通类使用没有区别。
      3.编译程序可以看出生成两个class文件,一个是Cow.class一个是Cow$CowLeg.class,前者是外部类的Cow的class文件,后者是内部类的class文件。
      当在非静态内部类的方法内访问某个变量时,系统优先在该方法内查找是否存在该名字的局部变量,如果存在该名字的局部变量,就使用该变量;如果不存在,则到该方法所在的内部类中查找是否存在该名字的属性,如果存在则使用该属性;如果不存在,则到该内部类所在的外部类中查找是否存在该名字的属性,如果存在则使用该属性。如果依然不存在,系统将出现编译错误:提示找不到该变量。
    因此,如果外部类属性、内部类属性与内部类里方法的局部变量同名,则可通过使用this、外部类类名.this作为限定来区分。

     1 package chapter6;
     2 
     3 public class DiscernVariable {
     4     private String prop = "外部类属性";
     5     private class InnerClass{
     6         private String prop = "内部类属性";
     7         public void info(){
     8             String prop = "局部变量属性";
     9             //通过外部类.this.外部类属性名访问外部类属性10             System.out.println("外部类属性值:" + DiscernVariable.this.prop);
    11             //通过this.内部属性名访问内部属性12             System.out.println("内部类属性值:" + this.prop);
    13             //访问方法内的局部变量值14             System.out.println("局部属性值:" + prop);
    15         }
    16     }
    17     public void test(){
    18         InnerClass in = new InnerClass();
    19         in.info();
    20     }
    21     public static void main(String[] args){
    22         new DiscernVariable().test();
    23     }
    24 }
    

      非静态内部类的成员可以访问外部类的private成员,但反过来就不成立了。非静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类成员,则必须显式创建非静态内部类对象来调用访问其实例成员。

     1 public class Outer
     2 {
     3     private int outProp = 9;
     4     class Inner
     5     {
     6         private int inProp = 5;
     7         public void acessOuterProp()
     8         {
     9             //非静态内部类可以直接访问外部类的成员10             System.out.println("外部类的outProp值:"
    11                 + outProp);
    12         }
    13     }
    14     public void accessInnerProp()
    15     {
    16         //外部类不能直接访问非静态内部类的实例Field,
    17         //下面代码出现编译错误
    18         //System.out.println("内部类的inProp值:" + inProp);
    19         //如需访问内部类的实例Field,必须显式创建内部类对象20         System.out.println("内部类的inProp值:" 
    21             + new Inner().inProp);
    22     }
    23     public static void main(String[] args)
    24     {
    25         //执行下面代码,只创建了外部类对象,还未创建内部类对象26         Outer out = new Outer();      //27         out.accessInnerProp();
    28     }
    29 }
    

    3.1.1 非静态内部类对象和外部类对象的关系

      非静态内部类对象必须寄存在外部类对象里,而外部类对象则不必一定有非静态内部类对象寄存其中。简单的说,如果存在一个非静态内部类对象,则一定存在一个被它寄存的外部类对象。但外部类对象存在时,外部类对象里不一定寄存了非静态内部类对象。因此外部类对象访问非静态内部类成员时,可能非静态普通内部类对象根本不存在。而非静态内部类对象访问外部类成员时,外部类对象一定是存在的。
      根据静态成员不能访问非静态成员的规则,外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量,创建实例等。总之,不能在外部类的静态成员中直接使用非静态内部类。

     

     1 public class InnerNoStatic
     2 {
     3     private class InnerClass
     4     {
     5         /* 6         下面三个静态声明都将引发如下编译错误:
     7         非静态内部类不能有静态声明
     8         */ 9         static10         {
    11             System.out.println("==========");
    12         }
    13         private static int inProp;
    14         private static void test(){}
    15     }
    16 }
    

    3.2 静态内部类                      

      如果使用static来修饰一个内部类,则这个内部类变成是外部类相关的,属于整个外部类,而不是单独属于外部类的某个对象。因此使用static修饰的内部类被称为类内部类,有的地方称为静态内部类。 
     
      static关键字的作用是把类的成员变成类相关,而不是实例相关,即static修饰成员是属于整个类,而不是属于单个对象。外部类的上一级程序单元是包,所以不可使用static修饰;而内部类的上一级程序单元是外部类,使用static修饰可以讲内部类变成外部类相关,而不是外部类实例相关,因此static关键字不可修饰外部类,可以修饰内部类。
      静态内部类可以包含静态成员,也可以包含非静态成员。静态内部类不能访问外部类实例成员,只能访问外部类成员。即使静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。 
     
      外部类不能直接访问静态内部类成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类的对象作为调用者来访问静态内部类的实例成员。

     1 package chapter6;
     2 
     3 public class AccessStaticInnerClass {
     4     static class InnerClass{
     5         private static String prop1 = "Hello";
     6         private int prop2 = 3;
     7     }
     8     public void AccessStaticInnerClass(){
     9         //直接访问报错误
    10         //System.out.println(prop1);
    11         //通过内部静态类名访问12         System.out.println(InnerClass.prop1);
    13         //通过创建实例来访问内部类属性14         System.out.println(new InnerClass().prop2);
    15     }
    16 
    17 }
    

      除此之外,Java还允许在接口里定义内部类,接口里定义的内部类默认使用public static修饰,也就是说接口内部类只能是静态内部类。
    如果为接口内部类指定访问控制符,则只能指定public访问控制符。如果定义接口内部类时省略访问控制符,则该内部类默认是public访问控制权限。
    接口里也可以定义内部接口,但这种做法用处不大,接口里的内部接口是接口的成员,接口的作用是定义一个公共规范,如果定义成一个内部接口,那么意义不大。

    3.3 使用内部类                   

      定义类的主要作用就是定义变量、创建实例和作为父类被继承。定义内部类的主要作用也如此。但使用内部类定义变量和创建实例则与外部类存在一些小小的差异。下面分三种情况讨论内部类的用法。

    3.3.1在外部类内部使用内部类

      在前面程序中可以看出,在外部类的内部使用类时,与平常使用普通类没有太大区别。一样可以直接通过内部类类名来定义变量,通过new 调用内部类构造器来创建实例。
      唯一存在的一个区别是:不要在外部类的静态成员(静态方法和静态初始化块)中使用非静态内部类,因为静态成员不能访问非静态成员。
      在外部类内部定义内部类的子类与平常定义子类也没有太大的区别。

    3.3.2 在外部类以外使用非静态内部类

      如果希望在外部类以外的地方访问内部类(包括静态和非静态两种),则内部类不能使用private访问控制权限,private修饰的内部类只能在外部类内部使用。对于使用其他访问控制符修饰的内部类,则能在访问控制符对应访问权限内使用。
      1.省略访问控制符的内部类,只能被与外部类处于同一个包中其他类所访问。
      2.使用protected修饰的内部类:可被与外部类处于同一个包中其他类和外部类的子类访问
      3.使用public修饰的内部类:可在任何地方被访问。
    在外部类以外地方来使用内部类的语法是:
      OuterClass.InnerClass varName
      也就是要用相对完整的类名,如果在别的包外,还需要包名。

      因为非静态内部类的对象必须寄存在外部类的对象里,因此创建非静态内部类对象之前,必须先创建其外部类对象。在外部类以外的地方创建非静态内部类实例的语法如下:
    OuterInstance.new InnerConstructor()
      也就是说,在外部类以外的地方创建非静态内部类实例必须使用外部类实例和new来调用非静态内部类构造器。

     1 class Out
     2 {
     3     //定义一个内部类,不使用访问控制符,
     4     //即只有同一个包中其他类可访问该内部类 5     class In
     6     {
     7         public In(String msg)
     8         {
     9             System.out.println(msg);
    10         }
    11     }
    12 }
    13 public class CreateInnerInstance
    14 {
    15     public static void main(String[] args) 
    16     {
    17         Out.In in = new Out().new In("测试信息");
    18         /*19         上面代码可改为如下三行代码:
    20         使用OutterClass.InnerClass的形式定义内部类变量
    21         Out.In in;
    22         创建外部类实例,非静态内部类实例将寄存在该实例中
    23         Out out = new Out();
    24         通过外部类实例和new来调用内部类构造器创建非静态内部类实例
    25         in = out.new In("测试信息");
    26         */27     }
    28 }
    

       如果需要在外部类以外的地方创建非静态内部类的子类,尤其需要注意上面的规则:非静态内部类的构造器必须通过其外部类对象来调用。

    我们知道:当创建一个子类时,子类构造器总会调用父类的构造器,因此在创建非静态内部类的子类时,必须保证让子类的构造器可以调用非静态内部类的构造器,调用非静态内部类的构造器时,必须存在一个外部类的对象。下面程序定义了一个子类继承了Out类的非静态内部类In

    1 public class SubClass extends Out.In{
    2     //显式定义SubClass的构造器3     public SubClass(Out out){
    4     //通过传入Out对象显式调用In的构造器5     out.super("hello");
    6     }
    7 }
    

      上面的代码可以看出如果要创建一个SubClass对象,必须先创建一个Out对象。因为SubClass是非静态内部类In的子类,非静态内部类In对象里必须有一个对Out对象的引用。其子类SubClass对象里也应该有一个Out对象的引用。
      非静态内部类In对象和SubClass对象都必须保留有指向Outer对象的引用,区别是创建两种对象时传入Out对象方式不同:当创建非静态内部类In类的对象时,必须通过Outer对象来调用new 关键字;当创建SubClass类的对象时,必须将Outer对象作为参数传给SubClass的构造器。
      非静态内部类的子类不一定是内部类,也可以是顶层类。但非静态内部类的子类实例一样需要保留一个引用,该引用就是指向子类的父类所在的外部类的对象,也就是说,如果有一个内部类的子类对象存在,一定存在与之对应外部类的对象。

    3.3.3 在外部类以外使用静态内部类

      静态内部类是外部类相关的,因此创建内部类对象时无需创建外部类的对象。
    语法是:

     1 new OuterClass.InnerConstructor()
     2 class StaticOut{
     3     //定义一个静态内部类,不使用访问控制符,
     4     //即同一个包中其他类可访问该内部类 5     static class StaticIn{
     6         public StaticIn(){
     7             System.out.println("静态内部类的构造器");
     8         }
     9     }
    10 }
    11 public class CreateStaticInnerInstance{
    12     public static void main(String[] args){
    13         StaticOut.StaticIn in = new StaticOut.StaticIn();
    14         /*15         上面代码可改为如下两行代码:
    16         使用OutterClass.InnerClass的形式定义内部类变量
    17         StaticOut.StaticIn in;
    18         通过new来调用内部类构造器创建静态内部类实例
    19         in = new StaticOut.StaticIn();
    20         */21     }
    22 }
    

      因为调用静态内部类的构造器无需使用外部类对象,所以创建静态内部类的子类比较简单,下面定义静态内部类StaticIn定义了一个空的子类
    public class StaticSubClass extends StaticOut.StaticIn{}

    3.4 局部内部类                   

      如果把一个内部类放在方法里定义,则这个内部类就是一个局部内部类,局部内部类仅在该方法里有效。因此,局部内部类不能在外部类以外的地方使用,那么局部内部类也无需使用访问控制符和static修饰符修饰。
      对局部成员而言,不管是局部变量还是局部内部类,它们的上一级程序单元是方法,而不是类,使用static修饰它们没有任何意义。因此,所有局部成员都不能使用static修饰。不仅如此,因为局部成员的作用域是所在方法,其他程序单元永远也不可能访问另一个方法中的局部成员,所以局部成员都不能使用访问控制符修饰。

     1 package chapter6;
     2 
     3 public class LocalInnerClass {
     4     public static void main(String[] args){
     5         //定义局部内部类 6         class InnerBase{
     7             int a;
     8         }
     9         //定义局部内部类的子类10         class SubInnerBaseClass extends InnerBase{
    11             int b;
    12         }
    13         //局部类对象14         SubInnerBaseClass sb = new SubInnerBaseClass();
    15         sb.a = 3;
    16         sb.b = 4;
    17         System.out.println(sb.a);
    18         System.out.println(sb.b);
    19     }
    20 }
    

      编译上面程序,看到生成三个class文件LocalInnerClass$1SubInnerBaseClass.class,LocalInnerClass$1InnerBase.class,LocalInnerClass.class,局部内部类的class文件综述遵循如下命名格式:OuterClass$NInnerClass.class,注意到局部内部类的class文件的文件名比成员内部类的class文件的文件名多了一个数字,这是因为同一个类里不可能有两个同名的成员内部类,而同一个类里面可能有两个以上同名的局部内部类。所以Java为局部内部类的class文件名增加一个数字用于区分。

    3.5 匿名内部类                     

      匿名内部类适合一次性创建使用的类,例如命令模式的对象,创建匿名内部类时会立即创建该类的一个实例,这个类的定义立即消失,匿名内部类不能重复使用。
    语法格式:

    1 new 父类名称|接口{
    2 
    3 
    4 }
    

      从上面的定义可以看出匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类或实现一个接口

    关于匿名内部类还有如下两条规则:
      1.匿名内部类不能是抽象类,因为系统在创建匿名内部类的时候,会立即创建匿名内部类的对象。因此不允许将匿名内部类定义成抽象类。
      2.匿名内部类不能定义构造器,因为匿名内部类没有类名,所以无法定义构造器,但匿名内部类可以定义实例初始化块,通过实例初始化块来完成构造器需要完成的事情。
      最常用的创建匿名内部类的方式是需要创建某个接口类型的对象,如下所示:

    package chapter6;
    
    interface Product{
        public double getPrice();
        public String getName();
    }
    public class TestAnonymous {
        public void test(Product p){
            System.out.println("购买了一个名为" + p.getName() + " 价格为:" + p.getPrice());
        }
        public static void main(String[] args){
            TestAnonymous ta = new TestAnonymous();
            //调用test方法时候,需要传递一个Product类对象作为参数
            ta.test(new Product(){
                public double getPrice(){
                    return 6.3;
                }
                public String getName(){
                    return "南瓜";
                }
            });
        }
    }

      上面程序中的TestAnonymous类定义了一个test方法,该方法需要一个Product对象作为参数,但Product只是一个接口,无法直接创建对象,因此此处考虑创建一个Product接口实现类的对象传入该方法——如果这个Product接口实现类需要重复使用,则应该讲该实现类定义成一个独立类;如果这个Product接口实现类只需一次使用,则可以采用上面程序中的方式,定义一个匿名内部类。
      匿名内部类无须class关键字,而是在定义匿名内部类时直接生成该匿名内部类的对象。
      由于匿名内部类不能是抽象类,所以匿名内部类必须实现它的抽象父类或者接口里面包含的所有抽象方法。
    上面创建Product实现类对象的代码,可以拆分成如下代码:

    1 class AnonymousProduct implements Product{
    2 public double getPrice(){
    3                 return 6.3;
    4             }
    5             public String getName(){
    6                 return "南瓜";
    7 }
    8 ta.test(new AnonymousProduct());
    

      显然使用内部类是更加简洁一点。
      当通过实现接口来创建匿名内部类时,匿名内部类也不能显式创建构造器,因此匿名内部类只有一个隐式的无参数构造器,故new接口名后的括号里不能传入参数值。
      但如果通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,此处的相似指的是拥有相同的形参列表。

     1 abstract class Device{
     2     private String name;
     3     public abstract double getPrice();
     4     public Device(){}
     5     public Device(String name){
     6         this.name = name;
     7     }
     8     public void setName(String name){
     9         this.name = name;
    10     }
    11     public String getName(){
    12          return this.name;
    13     }
    14 }
    15 public class AnonymousInner{
    16     public void test(Device d){
    17         System.out.println("购买了一个" + d.getName() + ",花掉了" + d.getPrice());
    18     }
    19     public static void main(String[] args){
    20         AnonymousInner ai = new AnonymousInner();
    21         //调用有参数的构造器创建Device匿名实现类的对象22         ai.test(new Device("电子示波器"){
    23             public double getPrice(){
    24                 return 67.8;
    25             }
    26         });
    27         //调用无参数的构造器创建Device匿名实现类的对象28         Device d = new Device(){
    29             //初始化块{30                 System.out.println("匿名内部类的初始化块...");
    31             }
    32             //实现抽象方法33             public double getPrice(){
    34                 return 56.2;
    35             }
    36             //重写父类的实例方法37             public String getName(){
    38                 return "键盘";
    39             }
    40         };
    41         ai.test(d);
    42     }
    43 }
    

      上面程序创建了一个抽象父类Device,这个抽象父类里包含两个构造器:一个无参数一个有参数。当创建以Device为父类的匿名内部类时,既可以传入参数,也可以不传入参数。
      当创建匿名内部类时,必须实现接口或抽象方法里的所有抽象方法。如果有需要,也可以重写父类中的普通方法。
      如果匿名内部类需要访问外部类的局部变量,则必须使用final修饰符来修饰外部类的局部变量,否则系统将报错。

     1 interface A{
     2     void test();
     3 }
     4 public class TestA{
     5     public static void main(String[] args){
     6         int age = 0;
     7         A a = new A(){
     8             public void test(){
     9                 //下面语句将提示错误:匿名内部类内访问局部变量必须使用final修饰10                 System.out.println(age);
    11             }
    12         };
    13     }
    14 }
    

    注意:如果没有A a = new A()后面的大括号进行初始化,只有A a = new A();是不对的,因为接口没有构造器,无法进行实例化

    3.6 闭包和回调                    

      闭包就是一种内部类,用内部类来实现外部接口,并且可以直接调用外部类的private成员(也就是回调)。
      Java并不能显式支持闭包,但对于非晶态内部类而言,它不仅记录了其外部类的详细信息,还保留了一个创建非静态内部类对象的引用,并且可以直接调用外部类的private成员,因此可以把非静态内部类当成面向对象领域的闭包。
      通过这种仿闭包的非静态内部类,可以很方便地实现回调功能,回调就是某个方法一旦获得了内部类对象引用后,就可以在合适时候反过来调用外部类实例的方法。
    下面的Teachable和Programmer基类都提供了work方法,这两个方法的签名一样,但是方法功能不同。

     1 package chapter6;
     2 
     3 interface Teachable {
     4     public void work();
     5 }
     6 package chapter6;
     7 
     8 public class Programmer {
     9     protected String name;
    10     //无参构造器11     public Programmer(){
    12         
    13     }
    14     //有参数构造器15     public Programmer(String name){
    16         this.name = name;
    17     }
    18     //省略了getter和setter方法19     public void work(){
    20         System.out.println(name + "在灯下认真敲键盘");
    21     }
    22 }
    

      假设现在有一个人,既是程序员,也是一个教师,也就是需要定义一个特殊的类,既需要实现Teachable接口,也需要继承Programmer父类,表面上看起来没有任何问题,问题是Teachable接口和Programmer父类里包含了相同的work方法,如果按照下面代码来定义一个特殊的TeachableProgrammer类,是有问题的

    1 package chapter6;
    2 
    3 public class TeachableProgrammer extends Programmer implements Teachable{
    4     public void work(){
    5         System.out.println(super.name + "教师在课堂上讲解");
    6     }
    7 }
    

      显然上面的TeachableProgrammer类只有一个work方法,这个work方法只能进行教学,不再可以进行编程,但实际需要两者技能都要具备。
    可以用一个内部类来实现这个功能

     1 package chapter6;
     2 
     3 public class TeachableProgrammer extends Programmer{
     4     public TeachableProgrammer(){
     5         
     6     }
     7     public TeachableProgrammer(String name){
     8         super.name = name;
     9     }
    10     public void teach(){
    11         System.out.println(getName() + "教师在课堂上讲解");
    12     }
    13     private class Closure implements Teachable{
    14         //非静态内部类回调外部类的work方法15         public void work(){
    16             teach();
    17         }
    18     }
    19     //返回一个非静态内部类引用,使得外部类允许非静态内部类引用回调外部类的方法20     public Teachable getCallBackReference(){
    21         return new Closure();
    22     }
    23 }
    

      上面的TeachableProgrammer至少Programmer类的子类,它可以直接调用Programmer基类的work方法,该类也包含教学teach方法,单子合格方法与Teachable接口没有任何关系,TeachableProgrammer也不能当场Teachable使用,此时创建了一个内部类,实现了Teachable接口,并实现了教学的work方法。但这种实现是通过回调TeachableProgrammer类的teach方法实现的。如果需要让TeachableProgrammer对象进行教学,只需要调用Closure内部类(它是Teachable接口的实现类)对象的work方法即可。
      TeachableProgrammer类提供了一个获取内部类对象的方法:该方法无需返回Closure类型,只需要返回所实现接口Teachable类型即可,因为它只需要当初一个Teachable对象使用即可。

     1 public class TestTeachableProgrammer
     2 {
     3     public static void main(String[] args) 
     4     {
     5         TeachableProgrammer tp = new TeachableProgrammer("李刚");
     6         //直接调用TeachableProgrammer类从Programmer类继承到的work方法 7         tp.work();
     8         //表面上调用的是Closure的work方法,实际上是回调TeachableProgrammer的teach方法 9         tp.getCallbackReference().work();
    10     }
    11 }
    

    4 枚举类                                                             

      在某些情况下,一个类的对象是有限而且固定的,例如季节类,它只有四个对象。这种实例有限而且固定的类,在Java里被称为枚举类。
    手动实现枚举类
    如果需要手动实现枚举类,可以采用如下设计方式:
      1.通过private将构造器隐藏起来
      2.把这个类的所有可能实例都使用public static final属性来保存
      3.如果有必要,可以提供一些静态方法,允许其他程序根据特定参数来获取与之匹配的实例。

     1 public class Season{
     2     //把Season类定义成不可变的,将其属性也定义成final 3     private final String name;
     4     private final String desc;
     5     public static final Season SPRING = new Season("春天" , "趁春踏青");
     6     public static final Season SUMMER = new Season("夏天" , "夏日炎炎");
     7     public static final Season FALL = new Season("秋天" , "秋高气爽");
     8     public static final Season WINTER = new Season("冬天" , "围炉赏雪");
     9 
    10     public static Season getSeaon(int seasonNum){
    11         switch(seasonNum){
    12             case 1 :
    13                 return SPRING;
    14             case 2 :
    15                 return SUMMER;
    16             case 3 :
    17                 return FALL;
    18             case 4 :
    19                 return WINTER;
    20             default :
    21                 return null;
    22         }
    23     }
    24 
    25     //将构造器定义成private访问权限26     private Season(String name , String desc){
    27         this.name = name;
    28         this.desc = desc;
    29     }
    30     //只为name和desc属性提供getter方法31     public String getName(){
    32          return this.name;
    33     }
    34     public String getDesc(){
    35          return this.desc;
    36     }
    37 }
    

      上面的方式有些麻烦,J2SE1.5新增了一个enum关键字,用以定义枚举类。枚举类是特俗的类。它可以有自己的方法和属性,可以实现一个或者多个接口,也可以定义自己的构造器。一个Java源文件最多只能定义一个public访问权限的枚举类,且该Java源文件必须和该枚举类的类名相同。
    但枚举类终究不是普通类,有自己的如下特点:
      1.枚举类可以实现一个或多个接口,使用enum定义的枚举类默认继承了java.lang.Enum类,而不是继承Object类,其中java.lang.Enum类实现了java.lang.Serializable和java.lang.Comparable两个接口
      2.枚举类的构造器只能使用private访问控制符,如果省略了其构造器的访问控制符,则默认使用private修饰,如果强制指定,则只能使用private修饰符
      3.枚举类的所有实例都必须在枚举类中显式列出,否则这个枚举类将永远都不能产生实例。列出这些实例时,系统会自动添加public static final修饰,无须程序员显式添加。
      4.所有枚举类都提供了一个values方法,该方法可以很方便的遍历所有的枚举值。

    1 package chapter6;
    2 
    3 public enum SeasonEnum {
    4     SPRING,SUMMER,FALL,WINTER;
    5     public static void main(String[] args){
    6         System.out.println(SeasonEnum.SPRING);
    7         
    8     }
    9 }
    

      编译上面的程序可以生成一个class文件,enum关键字和class、interface关键字的作用大致类似。
    定义枚举类时,需要显式列出所有枚举值,如上面的 SPRING等,枚举值之间用逗号,隔开,枚举值列举结束后以英文分号作为结束。这些枚举值是枚举类的实例。

     1 package chapter6;
     2 
     3 public class TestEnum {
     4     public void judge(SeasonEnum s){
     5         //swich分支语句 6         switch(s){
     7         case SPRING:
     8             System.out.println("面朝大海,春暖花开~");
     9             break;
    10         case SUMMER:
    11             System.out.println("像夏花一样绚烂");
    12             break;
    13         case FALL:
    14             System.out.println("自古逢秋悲寂寥,我言秋日胜春朝");
    15             break;
    16         case WINTER:
    17             System.out.println("你就像那冬天里的一把火");
    18             break;
    19         }
    20     }
    21     public static void main(String[] args){
    22         //列出枚举类的所有实例23         for(SeasonEnum s:SeasonEnum.values()){
    24             System.out.println(s);
    25         }
    26         //直接访问单个实例27         new TestEnum().judge(SeasonEnum.SPRING);
    28     }
    29 }
    30 输出:
    31 SPRING
    32 SUMMER
    33 FALL
    34 WINTER
    35 面朝大海,春暖花开~
    

    4.1 枚举类的属性、方法和构造器

    枚举类也是一种类,只是它是一种比较特殊的类,因此它一样可以使用属性和方法。

     1 public enum Gender{
     2     MALE,FEMALE;
     3     public String name;
     4 
     5 }
     6     上面的Gender枚举类里定义了一个name属性,并且将它定义成一个public访问权限的属性,下面使用该枚举类
     7 public class TestGender{
     8     public static void main(String[] args){
     9         Gender g = Enum.valueOf(Gender.class , "FEMALE");
    10         g.name = "女";
    11         System.out.println(g + "代表:" + g.name);
    12     }
    13 }
    

    注意:Enum类的实例生成不是通过new的,而是通过方法valueOf来解决。
    正如前面提到的,Java应该把所有类设计成良好封装的类,所以不应该允许直接访问Gender类的name属性,而应该通过方法来控制访问。

    4.2 实现接口的枚举类             

    枚举类可以实现一个或多个接口。与普通类实现一个或多个接口完全一样,枚举类实现一个或多个接口,也需要实现该接口所包含的方法。

    1 public interface GenderDesc{
    2     void info();
    3 }
    

    下面的类实现了这个接口

    1 public String getName(){
    2          return this.name;
    3     }
    4     /*5     public void info(){
    6         System.out.println("这是一个用于用于定义性别属性的枚举类");
    7     }

      如果由枚举类来实现接口里的方法,则每个枚举值在调用该方法时,都有相同的行为方式(因为方法体完全一样)。如果需要每个枚举值在调用该方法时呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法,每个枚举值提供不同的实现方式,从而让不同枚举值调用该方法时具有不同的行为方式,下面的Gender枚举类中,不同枚举值对info方法的实现则各不相同。

     1 public enum Gender implements GenderDesc{
     2     //此处的枚举值必须调用对应构造器来创建 3     MALE("男"){
     4         public void info(){
     5             System.out.println("这个枚举值代表男性");
     6         }
     7     },
     8     FEMALE("女"){    
     9         public void info(){
    10             System.out.println("这个枚举值代表女性");
    11         }
    12     };
    13     private String name;
    14     //枚举类的构造器只能使用private修饰15     private Gender(String name){
    16         this.name = name;
    17     }
    18 }
    

      上面的MALE和FEMALE后面跟了一个花括号,花括号部分实际上是一个类体部分,这种情况下,当创建枚举值时,并不是直接创建了Gender枚举类的实例,而是相当于创建Gender的匿名子类的实例。也就是一个匿名内部类的类体部分,所以这个部分的代码语法与前面介绍的匿名内部类语法大致相似,依然是枚举类的匿名内部子类。
      编译上面的程序,可以看到生成了Gender.class、Gender$1.class和Gender$2.class三个文件。这也就是说MALE和FEMALE实际上是Gender匿名子类的实例,而不是Gender类的实例。

    4.3 包含抽象方法的枚举类

    枚举类的枚举值就是实例值
    假设有一个Operation枚举类,它的四个枚举值PLUS,MINUS,TIMES,DIVIDE分别代表加减乘除,为此定义枚举类如下

     1 package chapter6;
     2 
     3 public enum Operation {
     4     PLUS,MINUS,TIMES,DIVIDE;
     5     double eval (double x,double y){
     6         switch(this){
     7         case PLUS:
     8             return x + y;
     9         case MINUS:
    10             return x - y;
    11         case TIMES:
    12             return x*y;
    13         case DIVIDE:
    14             return x/y;
    15         default:
    16             return 0;
    17         }
    18     }
    19     public static void main(String[] args){
    20         System.out.println(Operation.PLUS.eval(2, 3));
    21         System.out.println(Operation.MINUS.eval(2, 3));
    22         System.out.println(Operation.TIMES.eval(2, 3));
    23         System.out.println(Operation.DIVIDE.eval(2, 3));
    24     }
    25 }
    

    上面的枚举类可以实现四个方法,this代表四个枚举类的实例值。这四个值是确定的,不能有其他值。实际上Operation类的四个值对eval方法各有不同的实现。为此可以采用前面MALE/FEMALE的方法,让它们分别为四个枚举值提供eval实现,然后在Operation类中定义一个eval的抽象方法。

    package chapter6;
    
    public enum Operation2 {
    
        PLUS{
            public double eval(double x,double y){
                return x + y;
            }
        },
        MINUS{
            public double eval(double x,double y){
                return x - y;
            }
        },
        TIMES{
            public double eval(double x,double y){
                return x*y;
            }
        },
        DIVIDE{
            public double eval(double x,double y){
                return x/y;
            }
        };
        //提供抽象方法,但是是放在下面的
        public abstract double  eval(double x,double y);
        public static void main(String[] args){
            System.out.println(Operation2.PLUS.eval(2, 3));
            System.out.println(Operation2.MINUS.eval(2, 3));
            System.out.println(Operation2.TIMES.eval(2, 3));
            System.out.println(Operation2.DIVIDE.eval(2, 3));
        }
        
    }
    输出:
    5.0
    -1.0
    6.0
    0.6666666666666666
    

      编译上面的程序会生成5个class文件,其实Operation2对应一个class文件,它的四个匿名内部子类分别各对应一个class文件。
    枚举类里定义抽象方法时无需显式使用abstract关键字将枚举类定义成抽象类,但因为枚举类需要显式创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。

    5 对象与垃圾回收

      垃圾回收是Java语言的重要功能,当程序创建对象、数组等引用类型实体时,系统都会在堆内存为之分配一块内存区,对象就保存在这块内存区中,当这块内存不再被引用变量引用时,这块内存就变成垃圾,等待垃圾回收机制进行回收。垃圾回收机制具有如下特征:
      1.垃圾回收机制只负责回收堆内存中对象,不会回收任何物理资源(例如数据库连接、网络IO等资源)
      2.程序无法精确控制垃圾回收的运行,垃圾回收会在合适时候运行。当对象永久性地失去引用后,系统就会在合适时候回收它所占的内存。
      3.垃圾回收机制回收任何对象之前,总会先调用它的finalize方法,该方法可能使该对象重新复活(让一个引用该变量重新引用该对象),从而导致垃圾回收机制取消回收。

    5.1 对象在内存中的状态      

      当一个对象在堆内存中运行时,根据它被引用变量所引用的状态,可以把它所处的状态分成如下三种:
      1.激活状态:当一个对象被创建后,有一个以上的引用变量引用它,则这个对象在程序中处于激活状态,程序可通过引用变量来调用该对象的属性和方法。
      2.去活状态:如果程序中某个对象不再有任何引用变量引用它,它就进入了去活状态。在这个状态下,系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有去活状态对象的finalize方法进行资源清理,如果系统在调用finalize方法重写让一个引用变量引用该对象,则这个对象会再次变为激活状态;否则该对象将进入死亡状态
      3.死亡状态:当对象与所有变量的关联都被切断,且系统已经调用所有对象的finalize方法,依然没有使该对象变成激活状态,那这个对象将永久性地失去引用,最后变成死亡状态。只有当一个对象处于死亡状态时,系统才会真正回收该对象所占有的资源。

     

     1 下面的程序说明了上面的原理:
     2 package chapter6;
     3 
     4 public class StatusTransfer {
     5     public static void test(){
     6         String a = new String("Englis");①
     7         a = new String("Deutsch");②
     8     }
     9     public static void main(String[] args){
    10         test();③
    11     }
    12     
    13 }
    

       当程序执行test方法①代码时,代码定义了一个a变量,并让该变量指向字符串English,代码执行结束后,字符串对象English处于激活状态。当程序执行了test方法的②代码后,代码再次定义了Deutsch对象,并让a变量指向这个对象,此时English处于去活状态,而Deutsch处于激活状态。

      一个对象可以被一个方法局部变量所引用,也可以被其他类的类属性引用,或被其他对象的实例属性引用。当被类属性引用时,只有类被销毁,对象才会进入去活状态,当被其他对象的实例属性引用时,只有该对象被销毁,该对象才会进入去活状态。

    5.2 强制垃圾回收               

      程序无法精确控制Java垃圾回收的时机,但我们依然可以强制系统进行垃圾回收——只是这种机制是通知系统进行垃圾回收,但系统是否进行垃圾回收依然不确定。大部分时候,强制垃圾回收会有效果,强制垃圾回收有如下两个方法。
      1.调用System类的gc()静态方法:System.gc();
      2.调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()

     1 public class TestGc{
     2     private double height;
     3     public static void main(String[] args){
     4         for (int i = 0 ; i < 4; i++){
     5             new TestGc();
     6             //System.gc(); 7             Runtime.getRuntime().gc();
     8         }
     9     }
    10     public void finalize(){
    11         System.out.println("系统正在清理TestGc对象的资源...");
    12     }
    13 }
    

    5.1 finalize方法                   

      在垃圾回收机制回收某个对象所占用的内存之前,通常要求程序调用适当的方法来清理资源,在没有明确指定资源清理的情况下,Java提供了默认机制来清理该对象的资源,这个方法是finalize,它是Object类的实例方法,方法原先为:
    protected void finalize() throws Throwable
      当finalize()方法返回之后,对象消失,垃圾回收机制开始执行。方法原型中的throws Throwable表示可以抛出任何类型的异常。
      任何Java类都可以覆盖Object类的finalize方法,在该方法中清理该对象占用的资源。如果程序终止前始终没有进行垃圾回收,则不会调用失去引用对象的finalize方法来清理资源。垃圾回收机制何时调用对象的finalize方法是完全透明的,只有当程序认为需要更多额外内存时,垃圾回收机制才会进行垃圾回收。

    finalize方法有如下四个特点:

    1.永远不要主动调用某个对象的finalize方法,该方法应交给垃圾回收机制调用。
    2.finalize方法何时被调用,是否被调用具有不确定性。不要把finalize方法当成一定会被执行的方法
    3.当JVM执行去活对象的finalize方法时,可能使该对象或系统中其他对象重新变成激活状态
    4.当JVM执行finalize方法时出现了异常,垃圾回收机制不会报告异常,程序继续执行。

     1 public class TestFinalize{
     2     private static TestFinalize tf = null;
     3     public void info(){
     4         System.out.println("测试资源清理的finalize方法");
     5     }
     6     public static void main(String[] args) throws Exception{
     7         //创建TestFinalize对象立即进入去活状态 8         new TestFinalize();
     9         //通知系统进行资源回收10         System.gc();
    11         System.runFinalization();
    12         //Thread.sleep(2000);13         tf.info();
    14     }
    15     public void finalize(){
    16         //让tf引用到试图回收的去活对象,即去活对象重新变成激活17         tf = this;
    18     }
    19 }
    

    上面程序中定义了一个TestFinalize类,重写了finalize方法,该方法使一个tf引用变量引用的对象从去活对象重新变成激活状态。
    除此之外,System和Runtime类里都提供了一个runFinalization方法,可以强制垃圾回收机制调用系统去活对象的finalize方法。

    5.2 对象的软、弱和虚引用     

      对大部分对象而言,程序里会有一个引用变量引用该对象,这种引用方式是最常见的引用方式。除此之外,java.lang.ref包下提供了三个类:SoftReference、PhantomReference和WeakReference,它们分别代表了系统对对象的三种引用方式:软引用、虚引用和弱引用。因此Java语言对对象的引用有如下四种。

    强引用

    这是Java程序中最常见的引用方式,程序创建一个对象,并把这个对象赋给一个引用变量。程序通过该引用变量来操作实际的对象,前面介绍的对象和数组都是采用了这种强引用的方式。当一个对象被一个或一个以上的引用变量所引用时,它处于激活状态,不可能被系统垃圾回收机制回收。

    软引用

    软引用需要通过SoftReference类来实现,当一个对象只具有软引用时,它有可能被垃圾回收机制回收。对于只有软引用的对象而言,当系统内存空间足够时,它不会被系统回收,程序也可使用该对象;当系统内存空间不足时,系统将会回收它。软引用通常用于对内存敏感的程序中。

    弱引用

    弱引用通过WeakReference类实现,弱引用和软引用很像,但弱引用级别更低。对于只有弱引用的对象而言,当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存。

    虚引用

    虚引用通过PhantomReference类实现,虚引用完全类似于没有引用。虚引用对对象本身没有太大影响,对象甚至感觉不到虚引用的存在。如果一个对象只有一个虚引用时,那它和没有引用的效果大致相同。虚引用主要用于跟踪对象呗垃圾回收的状态,虚引用不能单独使用,必须和引用队列ReferenceQueue联合使用。

    软、弱、虚引用三种都有一个get方法

     1 public class TestReference{
     2     public static void main(String[] args) throws Exception{
     3         //创建一个字符串对象 4         String str = new String("Struts2权威指南");
     5         //创建一个弱引用,让此弱引用引用到"Struts2权威指南"字符串 6         WeakReference wr = new WeakReference(str);
     7         //切断str引用和"Struts2权威指南"字符串之间的引用 8         str = null;
     9         //取出弱引用所引用的对象10         System.out.println(wr.get());
    11         //强制垃圾回收12         System.gc();
    13         System.runFinalization();
    14         //再次取出弱引用所引用的对象15         System.out.println(wr.get());
    16     }
    17 }
    

    6 jar命令                                                           

      为了用 JAR 文件执行基本的任务,要使用作为Java Development Kit 的一部分提供的 Java Archive Tool ( jar 工具)。用 jar 命令调用 jar 工具。表 1 显示了一些常见的应用:
    常见的 jar 工具用法
    功能
    命令
    用一个单独的文件创建一个 JAR 文件      jar cf jar-file input-file...
    用一个目录创建一个 JAR 文件         jar cf jar-file dir-name
    创建一个未压缩的 JAR 文件          jar cf0 jar-file dir-name
    更新一个 JAR 文件              jar uf jar-file input-file...
    查看一个 JAR 文件的内容           jar tf jar-file
    提取一个 JAR 文件的内容           jar xf jar-file
    从一个 JAR 文件中提取特定的文件       jar xf jar-file archived-file...
    运行一个打包为可执行 JAR 文件的应用程序   java -jar app.jar


      jar包就是zip包,可以用一些windows自带的工具如winrar等解压缩文件进行处理。使用WinRAR工具创建JAR包时候,因为工具本身不会自动添加清单文件,所以需要手动添加清单文件,即需要手动建立META-INF路径,并在该路径下建立MANIFEST.MF文件,该文件至少需要如下两行:

    Manifest-Version:1.0
    Created-By:1.6.0_03(Sun Microsystem Inc.)
    

      除此之外,Java还能生成两种压缩包:WAR包和EAR,WAR文件是Web Archive File,对应一个Web应用文档,EAR是Enterprise Archive File对应企业应用文档,有Web和EJB两个部分组成。WAR、EAR和JAR包完全一样,至少改变了文件后缀而已。

    6.1 创建可执行的JAR包     

    当一个应用程序开发成功后,大致有三种发布方式:
      1.使用平台相关的编译器将整个应用编译成平台相关的可执行性文件。这种方式常常需要第三方编译器支持,而且编译生成的可执行性文件丧失了跨平台特性,甚至可能有一定的性能下降。
      2.为整个应用编辑一个批处理文件。使用如下命令:

    java package.MainClass
    

      当客户点击上面的批处理文件时候,系统执行批处理文件的java命令,从而允许程序的主类。

      3.将一个应用程序制作成可执行的JAR包,通过JAR包来发布应用程序。
      创建可执行JAR包的关键在于:让javaw命令知道JAR包中哪个类是主类,javaw命令可以通过运行该主类来运行程序,这就需要借助于清单文件,需要在清单文件中增加如下一行:

    Main-Class:test.Test
    

      也就是test包下的Test类作为主类。这样javaw就知道从JAR包中的test.Test开始运行。

    在清单文件中增加这一行的方法如下:
    1.创建一个文本文件,里面包含如下内容
    Main-Class:<空格>test.Test<回车>
    注意:上面的属性文件要求很严格,冒号前面是key(即Main-Class),冒号后面的空格的后面是value,也就是test.Test。文件格式要求如下:
    1.每行只能写一个key-value对,key-value必须顶格写。
    2.key-value之间的冒号后紧跟一个空格
    3.文件开头没有空行
    4.文件必须以一行空行结束,也就是末尾以回车结束。
    上面的文件可以保存在任意位置,以任意文件名存放。
    使用如下命令进行添加:

    1 jar cvfm test.jar a.txt test
    

    运行上面的命令后,在当前路径下生产一个test.jar文件,查看文件可以看到Main-Class为:test.Test。表名该JAR包的主类

    6.2 运行JAR包有如下方式  

    1.使用java命令,使用java运行时的语法是:java -jar test.jar
    2.使用javaw命令,使用javaw运行的语法是:javaw test.jar。

    展开全文
  • 抽象类和接口

    千次阅读 2021-01-19 18:32:00
    抽象类 (一)语法规则 在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计...

    抽象类

    (一)语法规则

    在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class).

    abstract class Shape {
       abstract public void draw();
    }
    
    • 在 draw 方法前加上 abstract 关键字, 表示这是一个抽象方法. 同时抽象方法没有方法体(没有 { }, 不能执行具体代码).
    • 对于包含抽象方法的类, 必须加上 abstract 关键字表示这是一个抽象类.
    (二)抽象类的作用

    (1)抽象类存在的最大意义就是为了被继承。
    (2)抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类。 然后让子类重写抽象类中的抽象方法。

    有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?

    确实如此。但是使用抽象类相当于多了一重编译器的校验。使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成。那么此时如果不小心误用成父类了, 使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题。

    很多语法存在的意义都是为了 “预防出错”, 例如我们曾经用过的 final 也是类似。 创建的变量用户不去修改, 不就相当于常量嘛?
    但是加上final 能够在不小心误修改的时候, 让编译器及时提醒我们。 充分利用编译器的校验, 在实际开发中是非常有意义的。

    接口

    概念:接口是抽象类的更进一步。 抽象类中还可以包含非抽象方法, 和字段。 而接口中包含的方法都是抽象方法, 字段只能包含静态常量。

    (一)语法规则

    在打印图形的示例中, 我们的父类 Shape 并没有包含别的非抽象方法, 也可以设计成一个接口。

    interface IShape { 
      void draw(); 
    } 
    class Cycle implements IShape { 
      @Override 
      public void draw() { 
      System.out.println("○"); 
      } 
    } 
    public class Test { 
      public static void main(String[] args) { 
      IShape shape = new Rect(); 
      shape.draw(); 
      } 
    }
    
    • 使用 interface 定义一个接口
    • 接口中的方法一定是抽象方法, 因此可以省略 abstract
    • 接口中的方法一定是 public, 因此可以省略 public
    • Cycle 使用 implements 继承接口. 此时表达的含义不再是 “扩展”, 而是 "实现"
    • 在调用的时候同样可以创建一个接口的引用, 对应到一个子类的实例.
    • 接口不能单独被实例化.
    • 扩展(extends) vs 实现(implements)
    • 扩展指的是当前已经有一定的功能了, 进一步扩充功能.
    • 实现指的是当前啥都没有,需要从头构造出来.

    接口中只能包含抽象方法. 对于字段来说, 接口中只能包含静态常量(final static).

    interface IShape { 
      void draw(); 
      public static final int num = 10; 
    }
    

    其中的 public, static, final 的关键字都可以省略. 省略后的 num 仍然表示 public 的静态常量.

    提示:

    1. 我们创建接口的时候, 接口的命名一般以大写字母 I 开头.
    2. 接口的命名一般使用 “形容词” 词性的单词.
    3. 阿里编码规范中约定, 接口中的方法和属性不要加任何修饰符号, 保持代码的简洁性.
    (二) 实现多个接口

    有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承 的方式来实现的.
    然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.
    现在我们通过类来表示一组动物.

    class Animal { 
      protected String name;  
      public Animal(String name) { 
      this.name = name; 
      } 
    }
    

    另外我们再提供一组接口, 分别表示 “会飞的”, “会跑的”, “会游泳的”.

    interface IFlying { 
      void fly(); 
    } 
    interface IRunning { 
      void run(); 
    } 
    interface ISwimming { 
      void swim(); 
    }
    

    接下来我们创建几个具体的动物
    猫, 是会跑的.

    class Cat extends Animal implements IRunning { 
       public Cat(String name) { 
       super(name); 
     } 
      @Override 
      public void run() { 
      System.out.println(this.name + "正在用四条腿跑"); 
      } 
    }
    

    鱼, 是会游的.

    class Fish extends Animal implements ISwimming { 
      public Fish(String name) { 
      super(name); 
     } 
     @Override 
      public void swim() { 
      System.out.println(this.name + "正在用尾巴游泳"); 
      } 
    } 
    

    青蛙, 既能跑, 又能游(两栖动物)

    class Frog extends Animal implements IRunning, ISwimming { 
      public Frog(String name) { 
      super(name); 
    } 
      @Override 
      public void run() { 
      System.out.println(this.name + "正在往前跳"); 
     } 
      @Override 
      public void swim() { 
      System.out.println(this.name + "正在蹬腿游泳"); 
      } 
    }
    

    还有一种神奇的动物, 水陆空三栖, 叫做 “鸭子”

    class Duck extends Animal implements IRunning, ISwimming, IFlying { 
      public Duck(String name) { 
      super(name); 
    } 
      @Override 
      public void fly() { 
      System.out.println(this.name + "正在用翅膀飞"); 
     } 
      @Override 
      public void run() { 
      System.out.println(this.name + "正在用两条腿跑"); 
     } 
      @Override 
      public void swim() { 
      System.out.println(this.name + "正在漂在水上"); 
      } 
    }
    

    上面的代码展示了 Java 面向对象编程中最常见的用法: 一个类继承一个父类, 同时实现多种接口. 继承表达的含义是 is - a 语义,而接口表达的含义是 具有 xxx 特性 .

    (三)接口使用实例

    刚才的例子比较抽象, 我们再来一个更能实际的例子.

    给对象数组排序

    给定一个学生类

    class Student { 
     private String name; 
     private int score; 
     public Student(String name, int score) { 
     this.name = name; 
     this.score = score; 
     } 
     
     @Override 
     public String toString() { 
     return "[" + this.name + ":" + this.score + "]"; 
     } 
    }
    

    再给定一个学生对象数组, 对这个对象数组中的元素进行排序(按分数降序).

    Student[] students = new Student[] { 
     new Student("张三", 95), 
     new Student("李四", 96), 
     new Student("王五", 97), 
     new Student("赵六", 92), 
    };
    

    按照我们之前的理解, 数组我们有一个现成的 sort 方法, 能否直接使用这个方法呢?

    Arrays.sort(students); 
    System.out.println(Arrays.toString(students)); 
    // 运行出错, 抛出异常. 
    Exception in thread "main" java.lang.ClassCastException: Student cannot be cast to 
    java.lang.Comparable
    

    让我们的 Student 类实现 Comparable 接口, 并实现其中的 compareTo 方法

    class Student implements Comparable { 
     private String name; 
     private int score; 
     public Student(String name, int score) { 
     this.name = name; 
     this.score = score; 
     } 
     @Override 
     public String toString() { 
     return "[" + this.name + ":" + this.score + "]"; 
     } 
     @Override 
     public int compareTo(Object o) { 
     Student s = (Student)o; 
     if (this.score > s.score) { 
     return -1; 
     } else if (this.score < s.score) { 
     return 1; 
     } else { 
     return 0; 
     } 
     } 
    }
    

    在 sort 方法中会自动调用 compareTo 方法. compareTo 的参数是 Object , 其实传入的就是 Student 类型的对象.然后比较当前对象和参数对象的大小关系(按分数来算).

    • 如果当前对象应排在参数对象之前, 返回小于 0 的数字;
    • 如果当前对象应排在参数对象之后, 返回大于 0 的数字;
    • 如果当前对象和参数对象不分先后, 返回 0;

    再次执行程序, 结果就符合预期了.

    // 执行结果
    [[王五:97], [李四:96], [张三:95], [赵六:92]]

    注意事项: 对于 sort 方法来说, 需要传入的数组的每个对象都是 “可比较” 的, 需要具备 compareTo 这样的能力. 通过重写 compareTo 方法的方式, 就可以定义比较规则.
    为了进一步加深对接口的理解, 我们可以尝试自己实现一个 sort 方法来完成刚才的排序过程(使用冒泡排序)

    public static void sort(Comparable[] array) { 
     for (int bound = 0; bound < array.length; bound++) { 
     for (int cur = array.length - 1; cur > bound; cur--) { 
     if (array[cur - 1].compareTo(array[cur]) > 0) { 
     // 说明顺序不符合要求, 交换两个变量的位置
     Comparable tmp = array[cur - 1]; 
     array[cur - 1] = array[cur]; 
     array[cur] = tmp; 
     } 
     } 
     } 
    }
    

    再次执行代码

    sort(students); 
    System.out.println(Arrays.toString(students)); 
    

    // 执行结果
    [[王五:97], [李四:96], [张三:95], [赵六:92]]

    (四)接口间的继承

    接口可以继承一个接口, 达到复用的效果. 使用 extends 关键字.

    interface IRunning { 
     void run(); 
    } 
    interface ISwimming { 
     void swim(); 
    } 
    // 两栖的动物, 既能跑, 也能游
    interface IAmphibious extends IRunning, ISwimming { 
    } 
    class Frog implements IAmphibious { 
     ... 
    } 
    

    通过接口继承创建一个新的接口 IAmphibious 表示 “两栖的”. 此时实现接口创建的 Frog 类, 就继续要实现 run 方法,也需要实现 swim 方法.

    接口间的继承相当于把多个接口合并在一起

    (五)总结

    抽象类和接口都是 Java 中多态的常见使用方式.
    核心区别: 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法.
    之前写的 Animal 例子. 此处的 Animal 中包含一个 name 这样的属性, 这个属性在任何子类中都是存在的. 因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口。
    在这里插入图片描述

    展开全文
  • 利用“族模式”隐藏“抽象基类”背后实现的细节,以保持接口简洁。主要是提供一种思考方式,实现输入框的纯数字或者带小数点数字金额的输入验证(也可以采用扩展等其他的手段去实现,或许其他的方法更为简单)...
  • 抽象类接口-JAVA

    2022-03-04 20:13:49
    1. 抽象类1.1 抽象类概念 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一 个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类1.2 抽象...
  • 面向对象编程:包,继承,多态,抽象类接口

    千次阅读 多人点赞 2021-11-16 21:33:39
    1、导入包中的 import java.util.Date; // import java.util; 导入一个具体的 不能导入一个具体的包 import java.util.*; // 通配符 // util下有很多 Java处理的时候 需要哪个才会拿哪个 // C语言 通过...
  • Junit实现抽象类和接口类测试

    千次阅读 2018-03-22 20:48:39
    Junit实现抽象类和接口类测试 简单的目录: Junit实现抽象类和接口类测试 Junit实现接口类测试 Junit实现抽象类测试(一) Junit实现抽象类测试(二) JUnit使用abstract类来进行测试 参考: Junit...
  •  抽象类可以重用你代码使你的代码更加简洁;2.从行为来看: 接口可以多继承,multi-implement 抽象类不能实例化,必须子类化才能实例化;3.从属性来看: 接口的属性必须是常量;即public static final; 抽象类...
  • Java---抽象类接口

    千次阅读 多人点赞 2021-10-12 19:42:49
    抽象类 1.1 抽象类的概念 在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。例如: ...
  • JAVA中的抽象类和接口的一个区别,就是抽象类允许有公共的状态行为。也就是说,如果所有派生类具有一些公共的状态行为,那么可以放在抽象类中实现。在JAVAC++中很重要的一点是所有类只能继承一个类,换言之,...
  • 其次接口和抽象类是实现多态的简洁方式, 普通类也可以实现多态,但方法定义更复杂,代码更多还可能产生无用代码 再其次,本质上说接口是一种特殊的抽象类接口抽象类更纯粹 抽象类:abstract关键字 用abstrsct...
  • 策略模式接口和抽象类的简单实现

    千次阅读 2017-11-05 23:02:55
    策略模式下用接口和抽象方法简单实现自导自演的scenario
  • Java 接口和抽象类

    千次阅读 2016-05-15 00:36:20
    介绍Java中的接口和抽象类 Java 接口和抽象类 版本号: 2018/9/29-1(22:40) 文章目录 Java 接口和抽象类 接口(27) Marker Interface functional interface default method 抽象类(18) 接口抽象类区别(10)...
  • 接口和抽象类区别,实不相瞒,我忘记了? 在Java基础部分,接口和抽象类是相当重要的知识点,尤其是接口,在后面的开发中,经常会见到这么四个字“面向接口编程”,而且对于接口和抽象类的知识也是面试中经常会...
  • //如果抽象类的类体里的方法全部都是抽象方法,可以把把这个类定义为接口//换言之,类体里全部都是抽象方法,可以把这个类定义为接口.interface Runer//接口的定义,接口里只包含常量抽象方法.{String texing="动时要...
  • 接口抽象类、多态这些JAVA基础中的基础,在多少初级中级攻城狮的眼中如同鸡肋,去掉接口其它两项工作两年没真正去使用过的人不在少数。 随着敏捷模式在国内大肆推广,底层技术人员疲于应付不断变动需求,总在高呼...
  • 4.1 抽象类 在面向对象领域由于抽象的概念在问题领域没有对应的具体概念,所以用以表征抽象概念的抽象类是不能实例化的。 同时,抽象类体现了数据抽象的思想,是实现多态的一种机制。它定义了一组抽象的方法,至于...
  • 本节目标 抽象类 接口 一、什么是抽象类 没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method), 包含抽象方法的类我们称为 抽象类(abstract class)。...抽象类和普通类的区别 1、抽象类是不
  • 接口抽象类,内部类
  • 如何简洁明了的描述Android中活动、实例、对象、类、抽象类接口之间的关系?
  • Java中抽象类接口、包的文档练习

    千次阅读 2014-07-28 22:22:29
    1. 实现接口修饰不可以是( ) A. Public B、abstract C、final D、void 2. 下面程序定义了一个,关于该说法正确的是( ) abstract classabstractClass{ …… } A. 该能调用new abstractClass...
  • 经典回答 接口和抽象是Java 面向对象设计的两个基础机制。 接口是对行为的抽象,它是抽象...抽象类是不能实例化的类,用关键字abstract 修饰,其主要目的是代码重用。除了不能实例化,形式上一般的 Java 类并没有...
  • 接口和抽象类的使用

    2014-05-19 23:20:31
    接口和抽象类表明上提供了相同的功能,那么我们如何使用它们呢?何时使用接口接口允许使用者从零开始实现接口,或者在其他与改接口目的完全不同的代码中实现该接口,这种情况下,该接口对实现者而言显得次要,因为...
  • Java接口回调一般用法

    2020-12-22 15:58:49
    Java接口回调一般用法:实现接口实际上继承抽象类类似,只不过继承是在类的层面上操作,接口是在方法常量集合的层面上操作,接口抽象类更抽象、更简洁。可以把实现接口看成继承特定的一个或多个方法以及一些...
  • C#(一)-抽象类接口

    千次阅读 热门讨论 2014-03-16 15:05:07
    从C#开始,才真正的比较清晰地了解一些OO的特性,抽象、封装、...结合前边所讲的类图、包图,我们可以得知,水果可以称之为苹果、橘子的,食物可以称之为苹果、橘子、香肠的包。  那么到底什么是抽象?? 抽象是从
  • 假设某动物园管理员每天需要给他所负责饲养的狮子、猴子鸽子喂食。我们用一个程序来模拟他喂食的过程。 先用常规编程方式 饲养员用一个 Feeder() 来表示,三个动物分别是:class Monkey() ,class Pigeon() ...
  • 有时候,可能会遇到带有两种甚至多种风格的实例的,并包含表示实例风格的标签(tag)域,例如,考虑下面这个,它能够表示圆形或者矩形: // Tagged class - vastly inferior to a class hierarchy! class ...
  • 抽象角色:通过接口抽象类声明真实角色实现的业务方法。 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。 真实角色:实现抽象角色,定义真实角色...
  • Java 泛型(Generic)1.为什么要有泛型(Generic)1.没有使用功能泛型时2.使用泛型时3....泛型接口4.泛型通配符 1.为什么要有泛型(Generic) 1.没有使用功能泛型时 解决元素存储的安全性问题 在集合...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 71,349
精华内容 28,539
关键字:

抽象类和接口的区别简洁