类——类的复用(组合、继承、代理)

来源:互联网 发布:php exec w3c 编辑:程序博客网 时间:2024/05/17 05:59

<开始认真记录笔记,但是csdn排版还是不太会用...搞的太粗糙了>


1、类的简介

      在Java中,类文件是以.java文件后缀的代码文件,每个类文件最多允许出现一个public类,当有public类时,此类名必须与文件名一致。

     在类内部,对于成员变量,如果在定义的时候没有进行显示的赋值初始化,则Java会保证类的每个成员变量都得到恰当的初始化:

       1)对于  char、short、byte、int、long、float、double等基本数据类型的变量来说会默认初始化为0(boolean变量默认会被初始化为false);

  2)对于引用类型的变量,会默认初始化为null。

       如果没有显示地定义构造器,则编译器会自动创建一个无参构造器,但是要记住一点,如果显示地定义了构造器,编译器就不会自动添加构造器。注意,所有的构造器默认为static的。

1.1 类的初始化


        当程序执行时,需要生成某个类的对象,Java执行引擎会先检查是否加载了这个类,如果没有加载,则先执行类的加载再生成对象,如果已经加载,则直接生成对象。

  在类的加载过程中,类的static成员变量会被初始化,另外,如果类中有static语句块,则会执行static语句块。static成员变量和static语句块的执行顺序同代码中的顺序一致。记住,在Java中,类是按需加载,只有当需要用到这个类的时候,才会加载这个类,并且只会加载一次。

<span style="font-size:18px;">public class Test {    public static void main(String[] args) throws ClassNotFoundException {                 Bread bread1 = new Bread();        Bread bread2 = new Bread();    }}  class Bread {    static{        System.out.println("Bread is loaded");    }    public Bread() {        System.out.println("bread");    }}</span>
运行这段代码就会发现"Bread is loaded"只会被打印一次。


  在生成对象的过程中,会先初始化对象的成员变量,然后再执行构造器。也就是说类中的变量会在任何方法(包括构造器)调用之前得到初始化,即使变量散步于方法定义之间。
<span style="font-size:18px;">public class Test {    public static void main(String[] args)  {        new Meal();    }}  class Meal {         public Meal() {        System.out.println("meal");    }         Bread bread = new Bread();} class Bread {         public Bread() {        System.out.println("bread");    }}</span>
输出结果为: bread
meal

2.类的复用


2.1 组合

        在新类中new另外一个类的对象,以添加该对象的特征(has-a);

       使用场合:各部件之间没有什么关系,只需要组合即可,电脑computer类需要new CPU()、new RAM()..
演示代码如下:
<span style="font-size:18px;">public class Computer {    public Computer() {        CPU cpu=new CPU();        RAM ram=new RAM();        Disk disk=new Disk();    }}class CPU{    }class RAM{    }class Disk{    }</span>

2.2继承

        继承是所有OOP语言不可缺少的部分,在java中使用extends关键字来表示继承关系。当创建一个类时,总是在继承,如果没有明确指出要继承的类,就总是隐式地从根类Object进行继承。

class Person {    public Person() {             }} class Man extends Person {    public Man() {             }}
     
   类Man继承于Person类,这样一来的话,Person类称为父类(基类),Man类称为子类(导出类)。如果两个类存在继承关系,则子类会自动继承父类的方法和变量,在子类中可以调用父类的方法和变量。在java中,只允许单继承,也就是说 一个类最多只能显示地继承于一个父类。但是一个类却可以被多个类继承,也就是说一个类可以拥有多个子类。

  1.子类继承父类的成员变量

  当子类继承了某个类之后,便可以使用父类中的成员变量,但是并不是完全继承父类的所有成员变量。具体的原则如下:

    1)能够继承父类的public和protected成员变量;不能够继承父类的private成员变量;

    2)对于父类的包访问权限成员变量,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;

    3)对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,即子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。

  2.子类继承父类的方法

  同样地,子类也并不是完全继承父类的所有方法。

    1)能够继承父类的public和protected成员方法;不能够继承父类的private成员方法;

    2)对于父类的包访问权限成员方法,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;

    3)对于子类可以继承的父类成员方法,如果在子类中出现了同名称的成员方法,则称为覆盖,即子类的成员方法会覆盖掉父类的同名成员方法。如果要在子类中访问父类中同名成员方法,需要使用super关键字来进行引用。

  注意:隐藏和覆盖是不同的。隐藏是针对成员变量和静态方法的,而覆盖是针对普通方法的。(后面会讲到)

   3.构造器  
   子类是不能够继承父类的构造器,但是要注意的是,如果父类的构造器都是带有参数的,则必须在子类的构造器中显示地通过super关键字调用父类的构造器并配以适当的参数列表。如果父类有无参构造器,则在子类的构造器中用super关键字调用父类构造器不是必须的,如果没有使用super关键字,系统会自动调用父类的无参构造器。
 
   继承中执行顺序:先调用基类,初始化,在调用子类,其中在生成对象的过程中,会先初始化对象的成员变量,然后再执行构造器。也就是说类中的变量会在任何方法(包括构造器)调用之前得到初始化,即使变量散步于方法定义之间

<span style="font-size:18px;">public class Test {    public static void main(String[] args)  {        new Circle();    }} class Draw {         public Draw(String type) {        System.out.println(type+" draw constructor");    }} class Shape {    private Draw draw = new Draw("shape");         public Shape(){        System.out.println("shape constructor");    }} class Circle extends Shape {    private Draw draw = new Draw("circle");    public Circle() {        System.out.println("circle constructor");    }}</span>

上述代码输出结果为:

<span style="font-size:18px;">shape draw constructorshape constructorcircle draw constructorcircle constructor</span>

2.3 代理

java中并没有提供对代理的直接支持,代理是继承和组合之间的中庸之道,因为将一个成员对象置于所要构造的类中(像组合),但与此同时在新类中暴露了该对象成员的所有方法(像继承)
<span style="font-size:18px;">public class PlaneDelegation{        private PlaneControl planeControl;    //private外部不可访问    /*    * 飞行员权限代理类,普通飞行员不可以开火    */    PlaneDelegation(){        planeControl=new PlaneControl();    }    public void speed(){        planeControl.speed();    }    public void left(){        planeControl.left();    }    public void right(){        planeControl.right();    }}final class PlaneControl {   //final表示不可继承    protected void speed() {}    protected void fire() {}    protected void left() {}    protected void right() {}} </span>
组合和继承的选择

  组合和继承都允许在新的类中放置子对象,组合是显示的这么做,继承则是隐式的这么做;组合技术通常用于想在新类中使用现有类的功能而非它的接口这种情形,即在新类中嵌入某个对象,让其实现所需要的功能。如果只是想单纯的使用某一个类的功能时,使用组合比较好。组合不会使得你的类成为你要使用的类的类型。也可以看两者是is-a和has-a的关系确定。

3.多用组合,少用继承

        对于类的扩展,在面向对象的编程过程中,我们首先想到的是类的继承,由子类继承父类,从而完成了对子类功能的扩展。但是,面向对象的原则告诉我们,对类的功能的扩展要多用组合,而少用继承。其中的原因有以下几点:

        第一、子类对父类的继承是全部的公有和受保护的继承,这使得子类可能继承了对子类无用甚至有害的父类的方法。换句话说,子类只希望继承父类的一部分方法,怎么办?

        第二、实际的对象千变万化,如果每一类的对象都有他们自己的类,尽管这些类都继承了他们的父类,但有些时候还是会造成类的无限膨胀。

        第三、继承的子类,实际上需要编译期确定下来,这满足不了需要在运行内才能确定对象的情况。而组合却可以比继承灵活得多,可以在运行期才决定某个对象。

嗨!光说这么多一二三有什么用,我们就是想看看实际情况是不是像上面说的那样呢?还是来看看实际的例子吧!


   现在我们需要这样一个HashMap,它除了能按常规的Map那样取值,如get(Objectobj)。还能按位取值,像ArrayList那样,按存入对象对的先后顺序取值

   对于这样一个问题,我们首先想到的是做一个类,它继承了HashMap类,然后用一个ArrayList属性来保存存入的key,我们按key的位来取值,代码如下:


public class ListMap extends HashMap {      private List list;          public ListMap() {      super();      this.list = new ArrayList();      }          public Object put(Object key,Object value) {      if(list.contains(key)) {      list.remove(key);      }      this.list.add(key);      return super.put(key,value);      }          public Object getKey(int i) {           return this.list.get(i);      }          public Object getValue(int i)  {           return this.get(getKey(i));      }          public int size()  {           return this.list.size();      }  }

测试代码

     ListMap map = new ListMap();       map.put("a","111");       map.put("v","190");       map.put("d","132");       for(int i=0;i<map.size();i++)  {               System.out.println(map.getValue(i));       }  

输出为

111    190   132

而如果测试代码改成这样,

    ListMap map = new ListMap();               map.put("a","111");               map.put("v","190");               map.put("d","132");               String[] list = (String[])map.values().toArray(new String[0]);                for(int i=0;i<list.length;i++)                {                       System.out.println(list[i]);                }  

输出为

132      111        190

  

   与上面的顺序不对了。可以看出用values方法和getValue方法运行结果不一致,但是都没有错。

   通过上面的例子,我们看到了继承的第一个危害:继承不分青红皂白的把父类的公有和受保护的方法统统继承下来如果你的子类没有对一些方法重写,就会对你的子类产生危害。上面的ListMap类,你没有重写继承自HashMap类的values方法,而该方法仍然是按HashMap的方式取值,没有先后顺序。这时候,如果在ListMap类的对象里使用该方法取得的值,就没有实现我们上面的要求。


   改用组合后则不存在上述问题,代码如下:


public class MyListMap {  private HashMap map;  private List list;  public MyListMap() {           this.map = new HashMap();           this.list = new ArrayList();  }  public Object put(Object key,Object value) {          if(list.contains(key)) {                   list.remove(key);           }           this.list.add(key);           return this.map.put(key,value);  }  public Object getKey(int i) {           return this.list.get(i);  } public Object getValue(int i) {           return this.map.get(getKey(i));  }  public int size() {           return this.list.size();  }  } 

这样就只有getValue方法,不会产生上面产生的错误了。


总结:当你需要它的实现而非方法的时候,应使用继承;当你需要它的方法而非实现的时候,应使用组合;

无论什么时候,组合应比继承优先考虑,因为继承有损松散耦合度。



参考:

http://www.cnblogs.com/dolphin0520/p/3803432.html

http://www.cnblogs.com/shipengzhi/articles/2086419.html

http://www.importnew.com/12907.html

http://www.linuxidc.com/Linux/2015-04/116277.htm


0 0