Java学习——面对对象的思想入门

     

本文是看过《head first Java》之后的一点感悟,写点东西帮忙以后回忆,Java目前在我的工作中用到还不多,而我又对面对对象的编程非常的感兴趣。曾经在MFC平台上写过一个比较大的程序,但是看了本书后,发现之前程序中漏洞百出,而且对面对对象的思想理解不深刻,感觉需要重新学习一遍。C++和JAVA的面对对象还是很有差距的,但是他们的基本思想是相同,抓住思想再学习语言会更高效。http://www.cnblogs.com/jsgnadsj

 

面向过程编程语言的局限:

 

大家最熟悉的应该是C语言编程,它是一个面向过程的编程语言。我曾经做过模型车,用主控芯片去控制四个轮子转动,用C语言的时候,只要控制轮子正反转就能控制小车方向。http://www.cnblogs.com/jsgnadsj

电机正转与反转:

  1. void motorFrontLeft(int sw)
  2. {
  3.    switch{
  4.    case 1:clockwise_rotation();
  5.    case 2:counter_clockwise_rotation();
  6.    }
  7. }

同理其他电机:FrontRight、BackLeft、BackRight。

clockwise_rotation() 函数是低层硬件操作,实质上就是电池正负极连接到电机两端的方式。正接的时候正转,反着接就反着转。

 

这里补充一点:并不是说C语言一定是面向过程而不能编写面向对象的程序,这是误解,C同样也能做面向对象编程。面向过程和面向对象是编程的一种思想,C++是在C的基础上发展而成,为了更好的利用面向对象的思想。

 

通过前进函数调用上面的电机函数就能够控制小车前进、后退、左转、右转动作了。

前进动作:

  1. void RunFront(int sw)
  2. {
  3.    motorFrontLeft(1);
  4.    motorFrontRight(1);
  5.    motorBackLeft(1);
  6.    motorBackRight(1);
  7. }

四个轮子同时正转,小车就前进了。

这就是面对过程思想编程,通过编写过程代码,来控制。

如果需要我们增加一些功能,或者修改一些功能的时候,麻烦来了。比如前进动作,希望能够控制其前进速度,那么我们要在RunFront()的函数参数中增加速度这个参数项,不仅如此,还要将此速度值传递给RunFront中调用的motorXX函数。这还没结束,真正控制轮子转速的是电机,所以还要修改motorXX里面代码,为其增加可以控制转速的功能。

四个轮子需要修改四个函数,如果这是一个蜈蚣车(只是说明需要的轮子多),那得修改多少。

面对对象思想的就是希望能够比较好的解决这类问题。

 

面对对象语言的优势:

 

面对对象思想主要有三个大的核心思想:封装,继承,多态。

我们可以看到,修改工作量大是因为我们对每个轮子控制函数要进行修改,也就是修改重复代码,这样会增加出现的概率。想想,四个电机其实质是什么?就是电机!除了安放在不同的位置,其他什么都一样的。理想状态下,通过同样的设置就能够得到同样的效果。比如接线方式是一样则正转,电压一样则转速一致。对于这样的有着相同属性和行为的物体,我们可以把他们统称为一个类(Class)。http://www.cnblogs.com/jsgnadsj

  1. class Motor{
  2.    int rotate;//方向
  3.    int v; //速度
  4.    void run(int rotate){
  5.       //
  6.       switch (dir){
  7.  
  8.       case 1://正转
  9.  
  10.       case 2://反转
  11.       }
  12.    }
  13.    void stop(){
  14.  
  15.    }
  16. }

Motor就是我们构造出来的类。这就是我们需要的电机的总称。

如何使用类?下面讲对象。

 

对象

 

类是一类事物的抽象描述。比如我们说的电机,它不是虚无飘渺凭空出现的事物。我们首先有它的设计图,然后根据设计图创造出它的实物。设计图就是类,创造出来的实物就是对象。这就是类与对象关系。类是抽象的(设计图),对象是具体的(电机实物)。

电动车的四个电机的构造:

  1. Motor motorFL = new Motor();
  2. Motor motorFR = new Motor();
  3. Motor motorBL = new Motor();
  4. Motor motorBR = new Motor();

这样就创造了四个电机实例对象。具体语法及含义参考java书籍。

 

变量类型:

首先明确一点,Java中只有两类类型,一种是基本数据类型,另一种是引用类型。

数据类型:char、short、int、float、double、boolean,与C中基本类似。

引用类型:作用于对象的变量

Java中对对象的操作都是通过引用变量来操作的,比如上面代码,Motor motorXX = new Motor(),

motorXX就是引用变量,Motor()是对象,这样就通过了motorXX来访问Motor对象,类比C中指针变量。

明确一点 motorXX 与 Motor() 是不同的两个变量,一个是引用类型:通过 Motor motorXX 来声明;一个是实际对象:通过new Motor()来创造。通过 = 使他们两个之间联系起来,这样就能够通过motorXX来访问Motor()对象。http://www.cnblogs.com/jsgnadsj

"="在这里的意思可以理解为将引用变量指向对象。

上面4句代码会产生如下结果:

我们可以修改motorFL来指向motorFR所指向的对象,如下图:

  1. motorFL = motorFR;

这样的操作之后,相当于两个遥控器可以控制同一个对象,那么 那个没有遥控器的对象怎么办呢?

我们无论如何都不会再指向它了,所以它会被Java的回收机制在适当的时候删除。(适当这个词好深奥!!!)

这里将引用对象和实际对象非常简单的说了下,这里只是入门而已,具体还是要看书,比如它们存放的区域就不同等等。

 

 

封装

 

我们有电机的设计图,但是我们不希望别人看到这个设计图,这就是封装。之前电机,我们通过类能够快速创造出4个电机对象,说明该类是能够重复利用的;比如我们家里的电视机,我们只需要怎么用,不必关心它是怎么做出来的;还有将设计图中某些东西给隐藏起来,这样的好处就是安全。这些都是封装的好处。

说道封装的安全性,我们就要提到四个关键字:Public、private、defalut、protected。什么是安全,就是某些事物是否有权限。比如:你爸爸可以进入你家,因为你爸爸有权限,而坏人呢,坏人没有进入你家权限,所以他是危险的。刚才提到的四个关键字就是四个等级的权限(具体参考书籍),这样理解就好了。

修改Motor类,使其添加明确的权限功能:http://www.cnblogs.com/jsgnadsj

  1. class Motor{
  2.    private int rotate;//方向
  3.    private int v; //速度
  4.    public void run(int rotate){
  5.       //
  6.       switch (rotate){
  7.  
  8.       case 1://正转
  9.  
  10.       case 2://反转
  11.       }
  12.    }
  13.    public void stop(){
  14.  
  15.    }
  16. }

属性rotate用private修饰,而方法run用public修饰。这就是刚才我们提到的电视,不关心里面构造(rotate),而只要知道怎么用(run)。

 

Static 关键字:

 

Static的变量:

如果我们希望统计一个类被创造出多少个对象,在C语言中,我们可以这样统计一个方法被调用次数:

  1. void func()
  2. {
  3.    static time;
  4.    time++;
  5. }

所以在Java中也可以用Static来帮助统计对象个数。

在类中定义一个统计个数的变量,然后在构造函数中自增。

  1. class Motor{
  2.    private int time;
  3.    private int rotate;//方向
  4.    private int v; //速度
  5.  
  6.    public Motor()
  7.    {
  8.       time++;
  9.    }
  10.    public void run(int rotate){
  11.       //
  12.       switch (rotate){
  13.  
  14.       case 1://正转
  15.  
  16.       case 2://反转
  17.       }
  18.    }
  19.    public void stop(){
  20.  
  21.    }
  22. }

这样每次创建一个Moter对象的时候,time都会自增,就可以统计对象个数了。

 

Static的方法:

在Java里面最熟悉的应该是Math里面的静态方法,比如Math().abs()…

对于abs()方法,它是一个静态方法,允许不创建对象而调用静态方法,是Java为了减少程序员调用某些常用方法时的麻烦,而允许程序员按照传统的C语言中使用函数的方式来使用方法。可以这样调用:Math().abs(-1);而不需要先创建Math对象,再调用方法。

 

Static类:

一般情况下是不可以用static修饰类的。如果一定要用static修饰类的话,通常static修饰的是匿名内部类。

在一个类中创建另外一个类,叫做成员内部类。这个成员内部类可以静态的(利用static关键字修饰),也可以是非静态的。由于静态的内部类在定义、使用的时候会有种种的限制。所以在实际工作中用到的并不多。

在开发过程中,内部类中使用的最多的还是非静态地成员内部类。不过在特定的情况下,静态内部类也能够发挥其独特的作用。

具体可以查看相关书籍。

 

 

继承

 

为什么需要继承?

我们以电视机为例,电视机发展过程为:黑白电视机->彩色电视机->液晶电视机->智能电视机…一步一步发展过来的。我们在从黑白电视机发展为彩色电视机的时候,不是完全摒弃了黑白电视机的设计图,因为黑白电视机某些属性和性质(比如电源系统)都还能够继续为彩色电视机使用,不需要再设计,所以仅仅需要修改显示结构即可。

继承就起到这样的作用,可以减少代码重复量,只需要修改或者增加新属性和行为就可以得到一个新的类。http://www.cnblogs.com/jsgnadsj

 

 

写一个电视机类:

普通电视机类

  1. class TeleVision
  2. {
  3.    private int voltage;//电压
  4.    private int channel;//频道
  5.  
  6.    public void show(){
  7.       System.out.println("television!");
  8.    }
  9.  
  10.    public int getVoltage() {
  11.       return voltage;
  12.    }
  13.    public void setVoltage(int voltage) {
  14.       this.voltage = voltage;
  15.    }
  16.    public int getChannel() {
  17.       return channel;
  18.    }
  19.    public void setChannel(int channel) {
  20.       this.channel = channel;
  21.    }
  22. }

现在我继承这个类,继承后的类是黑白电视机,修改show方法即可。

黑白电视机类:

  1. class BlackWhiteTV extends TeleVision
  2. {
  3.    public void show()
  4.    {
  5.       System.out.println("Black White TV!");
  6.    }
  7.  
  8. }

BlackWhiteTV 里面包含了 TeleVision 中的方法:

 

对象bwTV继承了他的父类TeleVision的public方法,但是没有直接访问private属性的权利。private修饰的属性和行为是不允许其他类包括它的继承类访问。

 

覆盖 override

为什么需要覆盖?

上例中,电视机类是一个总体概述的,通过一种抽象提取出来的,后面还要对它修改使其更符合面对对象的思想,这里暂且认为TeleVision类是一个蓝图,是我们脑海中的。基于这样的条件下,电视机和黑白电视机show是不同的,TeleVision是我们想想出来的,我们希望有个机器能够显示出图像就行,具体是什么样子我们不关心,而BlackWhiteTV则不一样,我们希望它能够显示出黑白的图像。假设之后有彩色电视机了,那么我们不再需要它show出黑白,那么我们直接可以将它的show覆盖,重新改成彩色就可以了。

上个例子中,BlackWhiteTV中的show方法就是覆盖了它的父类show方法。在执行bwTV.show()的时候,会调用BlackWhiteTV中的show方法。

覆盖注意几个问题:http://www.cnblogs.com/jsgnadsj

  • 覆盖的方法的标志必须要和被覆盖的方法的标志完全匹配,才能达到覆盖的效果;
  • 覆盖的方法的返回值必须和被覆盖的方法的返回一致;
  • 覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
  • 被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。

对于覆盖还有其他许多约束条件。

 

 

重载 overload

这里提及一下,这是多态思想。上例中,BlackWhiteTV中的show方法是没有参数传入的,如果此时我希望写一个有一个参数传递的show方法时候,如下:

  1. class BlackWhiteTV extends TeleVision
  2. {
  3.    public void show()
  4.    {
  5.       System.out.println("Black White TV!");
  6.    }
  7.    public void show(int i)
  8.    {
  9.       System.out.printf("%d Black White TV! ",i);
  10.    }
  11.  
  12. }

这就是重载,为什么会有这样的设计?

比如我们现在的电视机,希望它既可以显示黑白(比较怀旧),又能够显示彩色,那么我们肯定不希望这样写函数:showBlackWhite(),showColorful()。假如还有什么2D,3D,4D,5D等等,那么我们对于一个对象要写很多个不同函数,使用的时候还要记忆具体函数名,虽然函数名上可以取得比较相似,但是还是有点复杂。再比如我们常见的函数abs求绝对值的函数,java中的数据类型有int、float、double等等,我们肯定不希望函数写成这样:absInt(int i)、absFloat(float f)、absDouble(double d)。我们希望用一个函数名能够实现接收多个不同类型参数,简化代码,所以重载就提供了这样的方法。

我们用同样的函数名,接收不同类型的参数就能够实现一个函数名具有不同的功能,重载的意义重大。

重载需要注意:

  1. 在使用重载时只能通过不同的参数样式。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int,float),但是不能为fun(int,int));
  2. 不能通过访问权限、返回类型、抛出的异常进行重载;
  3. 方法的异常类型和数目不会对重载造成影响;
  4. 对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果。

 

继承时候类中方法调用:

如下继承图

  1. SmartTV stv = new SmartTV();
  2. stv.function();

stv.function()调用的是哪个类中的方法?

如果在SmartTelevision中覆盖了function方法,则调用SmartTelevision中的,反之,则调用ColorTelevision中的;若ColorTelevision中也没有覆盖function方法,则调用Television中的方法。

对于ColorTelevision中的function完全按照(返回类型、参数类型、方法名)Television中的function来写,这叫做覆盖

 

 

this 关键字

为什么需要this关键字?

比如一下这种情况:

  1. class BlackWhiteTV extends TeleVision
  2. {
  3.    private int i;
  4.    public void show()
  5.    {
  6.       System.out.println("Black White TV!");
  7.    }
  8.    public void show(int i)
  9.    {
  10.       System.out.printf("%d Black White TV! ",i);
  11.       System.out.printf("%d Black White TV! ",this.i);
  12.    }
  13.  
  14. }

BlackWhiteTV类中有类属性i,同时show方法的参数也是i,此时如何区分这两个i?

当一个对象创建后,Java虚拟机(JVM)就会给这个对象分配一个引用自身的指针,这个指针的名字就是 this。所以我们可以用this.i来指代类中属性i。

 

如下情况:

  1. class BlackWhiteTV extends TeleVision
  2. {
  3.    private int i;
  4.    BlackWhiteTV()
  5.    {
  6.       System.out.println("blackwhiteTV create!");
  7.    }
  8.    BlackWhiteTV(int i)
  9.    {
  10.       this();
  11.       System.out.println("the parament i:"+i);
  12.    }
  13.    public void show()
  14.    {
  15.       System.out.println("Black White TV!");
  16.    }
  17.    public void show(int i)
  18.    {
  19.       System.out.printf("%d Black White TV! ",i);
  20.       System.out.printf("%d Black White TV! ",this.i);
  21.    }
  22.  
  23. }

 

如果想要BlackWhiteTV(int i)首先调用BlackWhiteTV()再调用自己的函数,这个时候用this()就表示该类的无参数构造函数,如果带参数this(x),这样就是调用类的含有参数的构造函数。

 

注意:

  • 做区分:函数参数或者函数中的局部变量和成员变量同名的情况下,成员变量被屏蔽,此时要访问成员变量则需要用"this.成员变量名"的方式来引用成员变量。
  • 调用自身类某些方法:通过this调用另一个构造方法,用发是this(参数列表),这个仅仅在类的构造方法中,别的地方不能这么用。
  • 还有一个地方用到this:在函数中,需要引用该函所属类的当前对象时候,直接用this。

 

super 关键字

 

如果我们希望子类方法不是完全覆盖父类方法,而是希望先调用父类方法,再调用自身的函数,

例子如下:

  1. class TeleVision
  2. {
  3.    public void show(){
  4.       System.out.println("television!");
  5.    }
  6. }
  7.  
  8. class BlackWhiteTV extends TeleVision
  9. {
  10.    public void show()
  11.    {
  12.       super.show();
  13.       System.out.println("Black White TV!");
  14.    }
  15.  
  16. }

这样会显示:

Television!

Black White TV!

这样子类不是完全的覆盖父类方法,而是保留了父类方法基础上,又有自己的方法。

 

同理this,super同样对构造函数一样效用。可以通过super(参数)调用父类构造函数。

 

如果遇到子类中的成员变量或方法与父类中的成员变量或方法同名。因为子类中的成员变量或方法名优先级高,所以子类中的同名成员变量或方法就隐藏了父类的成员变量或方法,但是我们如果想要使用父类中的这个成员变量或方法,就需要用到super。

 

 

Super和This区别

  • super(参数):调用基类中的某一个构造函数(应该为构造函数中的第一条语句)
  • this(参数) :调用本类中另一种形成的构造函数(应该为构造函数中的第一条语句)
  • super         : 它引用当前对象的直接父类中的成员(用来访问直接父类中被隐藏的父类中成员数据或函数,基类与派生类中有相同成员定义时如:super.变量名 super.成员函数据名(实参)
  • this         :它代表当前对象名(在程序中易产生二义性之处,应使用this来指明当前对象;如果函数的形参与类中的成员数据同名,这时需用this来指明成员变量名)

 

  • 调用super()必须写在子类构造方法的第一行,否则编译不通过。每个子类构造方法的第一条语句,都是隐含地调用super(),如果父类没有这种形式的构造函数,那么在编译的时候就会报错。
  • super()和this()类似,区别是,super()从子类中调用父类的构造方法,this()在同一类内调用其它方法。
  • super()和this()均需放在构造方法内第一行。
  • 尽管可以用this调用一个构造器,但却不能调用两个。
  • this和super不能同时出现在一个构造函数里面,因为this必然会调用其它的构造函数,其它的构造函数必然也会有super语句的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也不会通过。
  • this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
  • 从本质上讲,this是一个指向本对象的指针, 然而super是一个Java关键字。http://www.cnblogs.com/jsgnadsj

 

 

final 关键字

继承有一个缺点:打破了封装特性!我们可以通过继承来覆盖或者重载它的方法,那么我们能不能不然我们的类(或者方法或者属性)被继承呢?——Final关键字就派上用场!

类被修饰为final:

 

类方法被修饰为final:

 

类成员被修饰为final:

假设我们知道一个变量不会再被改变时候,我们可以把他定义为常量。用final修饰。比如常数PI,我们就可以用final修饰:

类成员变量被修饰的时候需要注意:

  1. 显示的初始化,
  2. 只能初始化一次,
  3. 在声明或者构造函数中初始化。

 

对于PI,我们希望能够给所有的类使用,怎么办?

在要使用的类中,创造一个含有PI的类,然后用 类.PI 方式调用。这样的方法不好,因为我们只需要PI一个值,但是创造出来的类其他部分确实多余的。

想想有没其他办法?

前面我们说道过static,我们可以直接通过 类 .static修饰的变量来调用,所以我们可以在一个常用类中定义一个 public final static的PI 就可以了。而这个PI就在我们的Math类中。

 

  • 对于类:将某个类的定义为final 时,该类无法被继承。而且由于final类禁止继承,所以final类中所有的方法都隐式指定为final的,因为无法覆盖它们。
  • 对于类的方法:仅将某个类的方法定义为final时,该方法无法继承,其他不受影响。
    • 为了确保某个函数的行为在继承过程中保持不变,并且不能被覆盖(overridding),可以使用final方法。
    • class中所有的private和static方法自然就是final。类中所有的private方法都隐式地指定是final的。由于无法取用private方法,所以也就无法覆盖它。
  • 对于类的属性(成员):由于其不能被改变,所以要显示的初始化,并且之后不能在被修改。

中国地区的电气的电压是220v,所以可以将 voltage定义为final类,但是private是隐式的final,所以直接private即可。

Final 在还有其他注意:比如final 的对象引用等。具体参考书籍。

 

 

如何判断继承:

IS-A 方法:如果 x继承了y(即x extends y),那么有y is – a x成立。这条性质有传递性。

 

 

Abstract 关键字

 

  1. class TeleVision
  2. {
  3.    private int voltage;//电压
  4.    private int channel;//频道
  5.  
  6.    public void show(){
  7.       System.out.println("television!");
  8.    }
  9.  
  10.    public int getVoltage() {
  11.       return voltage;
  12.    }
  13.    public void setVoltage(int voltage) {
  14.       this.voltage = voltage;
  15.    }
  16.    public int getChannel() {
  17.       return channel;
  18.    }
  19.    public void setChannel(int channel) {
  20.       this.channel = channel;
  21.    }
  22. }
  23.  
  24. class BlackWhiteTV extends TeleVision
  25. {
  26.    public void show()
  27.    {
  28.       System.out.println("Black White CRT TV!");
  29.    }
  30. }
  31.  
  32. class ColorfulTV extends BlackWhiteTV
  33. {
  34.    public void show()
  35.    {
  36.       System.out.println("Colorful CRT TV!");
  37.    }
  38. }

我们现在的代码是这样,有没有感觉到TeleVision里面show方法有点多余?因为我们后面的黑白电视,彩色电视都没有用到TeleVision的show()方法,而是直接将其覆盖,实现了它们自己的方法。

然后我们再思考,电压和频道的get和set对TeleVision都没有用,因为我们没有创造出TeleVision的实例,因为它是我们脑子中蓝图,是一个虚拟的,虚拟的电视机我们不需要去获取他的属性数据。

这样我们希望能够有一种方法,只给这个类保留类的方法名称,也就是说继承该类的所以子类都有这样的方法,但是各个子类的实现却不一样,各自为政。这样既可以保证该类确实有这样的方法,同时也能让程序员灵活的实现。

抽象类发挥巨大作用!http://www.cnblogs.com/jsgnadsj

原则:

  • 必须用public abstract修饰抽象方法,private ,static 都不能用于修饰
  • 包含抽象方法的类必须是抽象类,也就是说,只要类中有抽象方法则该类一定是抽象类
  • 抽象类可以不含抽象方法

 

  1. abstract class TeleVision
  2. {
  3.    private int voltage = 220;//电压
  4.    private int channel;//频道
  5.  
  6.    abstract public void show();
  7.  
  8.    public int getVoltage() {
  9.       return voltage;
  10.    }
  11.    public int getChannel() {
  12.       return channel;
  13.    }
  14.    public void setChannel(int channel) {
  15.       this.channel = channel;
  16.    }
  17. }

 

黑白电视机:

  1. class BlackWhiteTV extends TeleVision
  2. {
  3.    public void show()
  4.    {
  5.       System.out.println("Black White CRT TV!");
  6.    }
  7. }

此类中必须实现TeleVision的show(),否则编译器报错。我们越来越能够抽象电视机这样的事物了,电视机不就是能够放映的事物嘛!只要有show()即可,而具体实现看具体继承的类了。比如:黑白电视的show黑白,彩色电视的show彩色。

 

我们重新调整一下类的继承关系,因为彩色电视机不是从黑白电视机继承的,彩色电视机也可以从电视机继承来。

 

对于这张继承图,电器是最顶层的,电视机不就是一个电器设备,这样就将电视机类更加抽象了。

电器类:

  1. abstract class ElecEequitment
  2. {
  3.    private final int voltage = 220; //额定电压
  4.  
  5.    public int getVoltage()
  6.    {
  7.       return this.voltage;
  8.    }
  9.  
  10.    abstract public void function(); //功能
  11.    abstract public int getCstmPower(); //获取消耗电量
  12. }

 

厨房电器类:

  1. abstract class CookElecEquitments extends ElecEequitment
  2. {
  3.    abstract public void ResistWater();
  4. }

在原有的基础上,增加了 防水功能。

 

客房电器类:

  1. abstract class RoomElecEquitments extends ElecEequitment
  2. {
  3.    abstract public void energySavingStandard(int i);
  4. }

在原有的基础上,增加了 "节能标准"。

 

继承抽象类的类不仅可以是普通类,同样也能够时抽象类。

 

 

多态:

 

上面的电器家组图的UML图如下:

抽象类代码在上面!

  1. class MicrowaveOven extends CookElecEquitments
  2. {
  3.    public void function()
  4.    {
  5.       System.out.println("Microwave Oven!");
  6.    }
  7.    public int getCstmPower()
  8.    {
  9.       return 0;
  10.    }
  11.  
  12.    public void ResistWater()
  13.    {
  14.  
  15.    }
  16. }
  17. class ElectricCooker extends CookElecEquitments
  18. {
  19.    public void function()
  20.    {
  21.       System.out.println("Electric Cooker!");
  22.    }
  23.    public int getCstmPower()
  24.    {
  25.       return 0;
  26.    }
  27.  
  28.    public void ResistWater()
  29.    {
  30.  
  31.    }
  32. }
  33. class Light extends RoomElecEquitments
  34. {
  35.    public void function()
  36.    {
  37.       System.out.println("Light!");
  38.    }
  39.    public int getCstmPower()
  40.    {
  41.       return 0;
  42.    }
  43.  
  44.    public void energySavingStandard(int i)
  45.    {
  46.       System.out.println("the standard is"+i);
  47.    }
  48. }
  49. class Television extends RoomElecEquitments
  50. {
  51.    public void function()
  52.    {
  53.       System.out.println("watch TV!");
  54.    }
  55.    public int getCstmPower()
  56.    {
  57.       return 0;
  58.    }
  59.  
  60.    public void energySavingStandard(int i)
  61.    {
  62.       System.out.println("the standard is"+i);
  63.    }
  64. }
  65.  
  66. class PC extends RoomElecEquitments
  67. {
  68.    public void function()
  69.    {
  70.       System.out.println("PC!");
  71.    }
  72.    public int getCstmPower()
  73.    {
  74.       return 0;
  75.    }
  76.  
  77.    public void energySavingStandard(int i)
  78.    {
  79.       System.out.println("the standard is"+i);
  80.    }
  81. }
  82.  
  83. class AirCondition extends RoomElecEquitments
  84. {
  85.    public void function()
  86.    {
  87.       System.out.println("Air Condition!");
  88.    }
  89.    public int getCstmPower()
  90.    {
  91.       return 0;
  92.    }
  93.  
  94.    public void energySavingStandard(int i)
  95.    {
  96.       System.out.println("the standard is"+i);
  97.    }
  98. }

 

 

假设这里我们需要频繁的调用最下面的类,那么我们需要有6个不同类型的引用,比如:

我们要声明六个不同类的引用

  1. MicrowaveOven mo = new MicrowaveOven();
  2. ElectricCooker ec = new ElectricCooker();
  3. Light lt = new Light();
  4. Television tv = new Television();
  5. PC pc = new PC();
  6. AirCondition ac = new AirCondition();

然后调用的时候,分别调用,对PC就要用pc,对Television就要用tv,比如他们都有一个function()功能,那么调用的时候需要这样:

  1. mo.function();
  2. ec.function();
  3. lt.function();
  4. tv.function();
  5. pc.function();
  6. ac.function();

有没有感觉很麻烦?它们都有相同的方法,却要一个一个通过不同引用类型调用。

麻烦的原因是:引用类型和对象类型必须相符才能正确调用。

 

应用1:引用类型是实际类型的父类(或间接父类):

我们看看UML图,最后的6个对象怎么都是有点联系的,既然有联系能不能通过什么方法来解决这样的问题呢?

他们有一个共同的父类(间接),我们能不能利用这个特性?

如下代码:

  1. ElecEequitment[] eq = new ElecEequitment[6];
  2.  
  3. eq[0] = new MicrowaveOven();
  4. eq[1] = new ElectricCooker();
  5. eq[2] = new Light();
  6. eq[3] = new Television();
  7. eq[4] = new PC();
  8. eq[5] = new AirCondition();
  9.  
  10. int i;
  11. for(i = 0 ; i < 6 ; i++)
  12. {
  13.    eq[i].function();
  14. }

第一句代码:创建6个父类引用!注意这里的是 new ElecEequitment[6] 而不是 new ElecEequitment()[6]。并没有创造6个ElecEequitment对象。

下面的for循环代码中,通过他们共同的父类(间接)的引用来调用,这样方便了很多。

同样值得注意,第一句话中的引用变量类不一定是abstract,也可以是普通类。

 

同样,这样的方法也可以用于传递函数的参数或者返回类型。http://www.cnblogs.com/jsgnadsj

 

应用2:参数类型与返回类型的多态

 

比如我增加一个方法,用于打开电器,如果没有多态,那么我需要对每一个电器都需要写一个打开方法的函数。如果增加了新的电器,那么又要改写代码。——多态就能够轻松解决这样的问题

  1. class opt
  2. {
  3.    public void powerOn(XXX xxx)
  4.    {
  5.  
  6.    }
  7. }

上述代码中,XXX是已有的电器类(TV、PC…),xxx则是对应的引用。

应用多态就能够很好的解决,XXX定义为父类,这样就很好的解决了。

  1. class opt
  2. {
  3.    public void powerOn(ElecEequitment eq)
  4.    {
  5.       System.out.println(eq.getClass().getSimpleName()+"power On");
  6.    }
  7. }
  8. public class Elec_equipmentTest {
  9.    public void powerOn(ElecEequitment eq)
  10.    {
  11.       System.out.println(eq.getClass().getSimpleName()+"power On");
  12.    }
  13.    public static void main(String[] args) {
  14.       // TODO Auto-generated method stub
  15.       ElecEequitment[] eq = new ElecEequitment[6];
  16.  
  17.       eq[0] = new MicrowaveOven();
  18.       eq[1] = new ElectricCooker();
  19.       eq[2] = new Light();
  20.       eq[3] = new Television();
  21.       eq[4] = new PC();
  22.       eq[5] = new AirCondition();
  23.  
  24.       opt o = new opt();
  25.       for (int j = 0; j < eq.length; j++) {
  26.          o.powerOn(eq[j]);
  27.       }
  28.    }
  29.  
  30. }

输出:

 

  • 注意一种情况,父类中如果有static 修饰的方法的时候。由于静态方法只能继承,不能重载,那么如果子类中定义了同名同形式的静态方法,它对父类方法只起到隐藏的作用。调用的时候用谁的引用,则调用谁的版本。http://www.cnblogs.com/jsgnadsj

 

重载

多态对于方法的作用,之前讲过的重载!

 

再说关键字 abstract

之前说过,创建引用对象的时候是new ElecEequitment[6] 而不是 new ElecEequitment()[6]。

  1. 假如我们不小心写成了后面那种样子,没有abstract修饰ElecEequitment()会被创造出来,但是电器类是什么样子?可能是电视机,可能是电脑、可能是微波炉,对它应该是一个变形金刚。想要他是什么,它就应该能够变成什么,所以这里添加了abstract修饰,这样电器类就不会被构造出来。
  2. Abstract的方法必须在继承具体类中全部实现。如果某个abstract方法没有实现,那么就会有包含这个方法的类就应该是abstract类。

 

类型转化

  1. class TV
  2. {
  3.    private int voltage;
  4.    public int getVoltage()
  5.    {
  6.       return voltage;
  7.    }
  8.    public void function()
  9.    {
  10.       System.out.println("Watch TV");
  11.    }
  12.    public int getCstmPower()
  13.    {
  14.       return 10;
  15.    }
  16. }
  17.  
  18. class ColorTV extends TV
  19. {
  20.    public void showcolor()
  21.    {
  22.       System.out.println("ColorTV: colorful");
  23.    }
  24.    public void function()
  25.    {
  26.       System.out.println("Watch Color TV!");
  27.    }
  28. }
  29.  
  30. class SmartTV extends ColorTV
  31. {
  32.    public void onInternet()
  33.    {
  34.       System.out.println("SmartTV: surf the Internet");
  35.    }
  36.    public void function()
  37.    {
  38.       System.out.println("Watch Smart TV");
  39.    }
  40. }

 

之前都是用父类做引用类型来使用它的子类的实际类型。即引用类型是TV,用来引用ColorTV、SmartTV。如果我们希望有个集合来存放ColorTV、SmartTV或者其他XXXTV(父类为TV)等等。那么就可以用TV做参数来存放TV及TV的派生类。

我们来设计一个TV类:http://www.cnblogs.com/jsgnadsj

  1. class TVarylist
  2. {
  3.    private static int length;
  4.    private TV[] arytv = new TV[10];
  5.  
  6.    public void add(TV t)
  7.    {
  8.       arytv[length++] = t;
  9.    }
  10.    public TV get(int idx)
  11.    {
  12.       return arytv[idx];
  13.    }
  14. }

通过这样的调用:

  1. public static void main(String[] args) {
  2.       // TODO Auto-generated method stub
  3.       SmartTV stv = new SmartTV();
  4.       ColorTV ctv = new ColorTV();
  5.  
  6.       TVarylist ary = new TVarylist();
  7.       ary.add(ctv);
  8.       ary.add(stv);
  9.       System.out.println(ary.get(0).getClass().getSimpleName());
  10.       System.out.println(ary.get(1).getClass().getSimpleName());
  11.  
  12.    }

最后显示:

TV集合类的设计如下:

  1. class TVarylist
  2. {
  3.    private static int length;
  4.    private TV[] arytv = new TV[10];
  5.  
  6.    public void add(TV t)
  7.    {
  8.       arytv[length++] = t;
  9.    }
  10.    public TV get(int idx)
  11.    {
  12.       return arytv[idx];
  13.    }
  14. }

这是一个简单的集合类设计,最大能够存放10个。

做一个大胆的假设,TV类也是我们自己定义的一个类,要是有个类是所有类(包括我们自己定义出来的类)的父类,那么多态的功能将会大大增强。如果这样,我们可以制作一个集合类,可以将所有元素通过总父类引用进去。

Java还真有这样的一个类:Object

Object是所有类的父类,同样也是你自己定义类的父类。(原因可以查书)

Java类中已经有做好的集合类,ArrayList类。

比如我们可以定义一个TV的集合类:ArrayList<TV>,刚才说过,有一个所有类的父类——Object类,那么一定可以用ArrayList<Object>来存放Java中所有的类。

但是需要注意的是,

明明是按SmartTV类型的stv存入,那么我也应该用SmartTV类型的引用类型来获取呀?

对于这样的问题,我们要知道当我们将集合类<Object>以这样的形式声明的时候,无论什么都将用Object类引用,所以当我们get的时候,获取的引用对象也应该是Object。而要想获得原先的类型SmartTV,就应该用原类型强制转化一下。

  1. public static void main(String[] args) {
  2.       // TODO Auto-generated method stub
  3.       SmartTV stv = new SmartTV();
  4.       ColorTV ctv = new ColorTV();
  5.  
  6.  
  7.       ArrayList<Object> aryobj = new ArrayList<Object>();
  8.       aryobj.add(ctv);
  9.       aryobj.add(stv);
  10.  
  11.       SmartTV rstv = (SmartTV)aryobj.get(1);
  12.       rstv.function();
  13.    }

 

总结

所谓多态,就是父类型的引用可以指向子类型的对象,或者接口类型的引用可以指向实现该接口的类的实例。

 

接口

我希望给 Television 和 PC 增加usb功能,如何实现?

可以在RoomElecEquitments类中添加一个usb的抽象方法,在Television 和PC中实现就行了,但是这样Light和AirCondition不是也得实现这个usb抽象方法了,有人给出方案,可以通过写空代码实现。如果对ElectricCooker也添加usb功能,那么是不是就要在ElecEequipment添加一个抽象的usb方法,那不得每一个电器都要去实现这个usb方法!!!不能忍受。http://www.cnblogs.com/jsgnadsj

    Java中提供了一个非常好用的方法来解决这个问题,为类专门指定想要的功能,你缺什么就给你添加什么功能,这样的方法叫接口。

这样就可以指定的为类添加指定的功能了。

 

Interface 和implements关键字

接口可以理解为抽象的抽象类。也就是说,里面的方法只有抽象方法,需要在继承它的类中实现,相当于只告诉我们,这里有个方法需要你实现,而具体归你管,我只管告诉你。

 

    Interface天生就是abstract,不难理解。用interface声明一个接口。接口类中的方法默认就是abstract,所以可以省略不写。

  1. interface usb
  2. {
  3.    public void read();
  4.    public void write();
  5. }

 

对电器的代码的TV再继承如下:

  1. class SmartTeleVision extends Television implements usb
  2. {
  3.    public void function()
  4.    {
  5.       System.out.println("watch Smart TV!");
  6.    }
  7.  
  8.    @Override
  9.    public void read() {
  10.       // TODO Auto-generated method stub
  11.       System.out.println("usb read");
  12.    }
  13.  
  14.    @Override
  15.    public void write() {
  16.       // TODO Auto-generated method stub
  17.       System.out.println("usb write");
  18.    }
  19.  
  20. }

调用如下:

  1. public static void main(String[] args) {
  2.    // TODO Auto-generated method stub
  3.    SmartTeleVision smarttv = new SmartTeleVision();
  4.  
  5.    smarttv.read();
  6.    smarttv.write();
  7. }

显示如下:

 

以上基本是Java的面对对象思想的一个缩影,写出来是为了能够更好的接触java,本文不能完全覆盖所有内容。文中错误还请各位看管指出,谢谢。http://www.cnblogs.com/jsgnadsj

原文地址:https://www.cnblogs.com/jsgnadsj/p/3493724.html