目录

几年前我姐三十岁生日,我准备了个礼物,功能就是脚本式的对话框交流加上打印机效果的祝福语显示。当时基本一点基础都没有,但我异常自信地在网上下了本安卓第一行代码和Java入门123 ,然后看着教程装好环境和 Android Studio 就开工了。最后花了大约两个星期把成品做了出来——其实能更早,是我的审美洁癖拖了一半的时间。程序源码我上传到仓库里了,链接:a-gift-for-my-sister

当时做完后一不做二不休,趁着课余时间也把那本 Java 入门书籍读完了。最近趁过年简单粗略地回忆一下那时候学过的知识,万一以后能用上呢。

面向过程与面向对象

  1. 面向过程的语言一般会将解决问题的过程拆成一个个方法,通过一个个方法的执行来解决问题。对于更复杂的工程,往往需要通过一个主要的入口方法,自顶向下的设计程序。因此牵一发而动全身,可维护性不够理想。以洗衣机为例,假如要清洗洗衣机里的一堆脏衣服。对面向过程的语言,执行流程大概是这样:
    1. 执行加洗衣粉方法。
    2. 执行加水方法。
    3. 执行洗衣服方法。
    4. 执行清洗方法。
    5. 执行烘干方法。
  2. 解决同一个问题,面向对象的语言往往会先抽象出对象,然后用对象执行方法的方式解决问题,且下次洗衣服我们还可以直接调用已有的洗衣服方法。对面向对象的语言,执行流程大概是这样:
    1. 先弄出两个对象:“洗衣机”对象和“人”对象。
    2. 针对对象“洗衣机”加入一些属性和方法:“洗衣服方法”、“清洗方法”、“烘干方法”。
    3. 针对对象“人”加入属性和方法:“加洗衣粉方法”、“加水方法”。
    4. 执行:人.加洗衣粉 $\longrightarrow$ 人.加水 $\longrightarrow$ 洗衣机.洗衣服 $\longrightarrow$ 洗衣机.清洗 $\longrightarrow$ 洗衣机.烘干。

类与对象

  1. 用public声明的类可以被其他类使用
  2. 类是我们对某事物进行抽象,并把抽象出来的东西封装为一种自定义数据类型,方便以后直接调用。
  3. 创建一个类的实体就是创建一个类的对象,实际上就是对一个类的具体化。
  4. 类文件中只定义属性名称,可以赋值也可以不赋值。但创建对象后可对类中赋予的初始值进行修改。
  5. 引用是一个对象的名字,就像指针一样指向一个对象。一个对象可以由无数个名字,每个名字多可以用来调用该对象。
public class BasicCar {
    int speed = 60;
    String name = "五菱宏光";
    String direction = “长安大学方向";
}
public class Test1 {
    public static void main(String[] args){
        BasicCar car1 = new car();    //声明一个Car类的引用,并创建一个Car类实体
        car1.speed = 60;    //通过ACommonCar给Car对象各属性赋值
        car1.name = "五菱宏光";
        car1.direction = “长安大学方向";
    }
}

方法

  1. 方法是类的功能,必须在类内定义。
  2. 方法可以操作类内的属性。
  3. 方法定义格式为访问控制符+返回值+方法名+(参数){方法内容;}
    1. 访问控制符控制权限。
    2. 返回值定义方法返回的数据类型,见例程方法 isOverspeed()
    3. 方法名……就是方法名。
    4. 参数见第 4 条以及例程方法 raiseSpeed()
  4. 参数注意事项:
    1. 多个参数之间使用逗号隔开。
    2. 形参是类中定义的方法参数。如例程中的 int speedup
    3. 实参是调用时传入的方法参数,如例程中的 5
    4. 实参可以是直接的数值,也可是一个变量,只要其值可赋给形参即可。
  5. main 方法是 java 运行时的程序入口。
  6. static 关键字意为静态。
    1. 修饰变量时,称该变量为类变量或静态变量。创建对象时不会创建静态变量,且可以直接通过类名静态变量。
    2. 修饰方法时,称该变量为类方法或静态方法。与类变量同,可以直接通过类名调用静态方法。
public class BasicCar {
    int speed = 60;
    String name = "五菱宏光";
    String direction = “长安大学方向";
    public void driveCar(){    //访问控制符+返回值+方法名+(参数)
        speed = 30;
        direction = "家";
    }
    public void raiseSpeed(int speedup){    //访问控制符+返回值+方法名+(参数)
        int currentspeed = speed + speedup;    //计算当前速度
        speed = speed + speedup;    //赋给speed属性
    }
    public boolean isOverspeed(){    //访问控制符+返回值+方法名+(参数)
        if(speed > 80){
            return true;
        }
        else{
            return false; }
        }
}
public class Test2 {
    public static void main(String[] args){
        BasicCar car2 = new Car();
        car2.driveCar();    //通过car2调用driveCar()方法
        System.out.print (car2.speed);    //输出速度
        
        car2.raiseSpeed(5);    //通过car2调用raiseSpeed()方法
        System.out.print (car2.speed);    //输出新速度
        
        isOverspeed = myCar.isOverspeed();    //通过car2调用isOverspeed()方法
        if(isOverspeed){System.out.println ("汽车超速行驶中")}
    }
}

继承

  1. 引入继承的原因:
    1. 假如之前的 BasicCar 开腻了想换辆车,新车除了包含老车的所有属性,还能摆脱行星重力井飞向太空。我们应该怎么做?
    2. 方法一:在原 BasicCar 类中增加一个判断语句判断是否是新车,再增加新车的新属性。但万一以后再换能登陆火星的新车、能飞出太阳系的新车就会很麻烦。
    3. 方法二:一个类一个类地编写。但以后要对整个车族某项属性进行统一修改的话会很麻烦。且每个类中有大量重复代码,代码冗余量多,不够简洁。
  2. 因此引入继承,语法是 public class SpaceCar extends Car,则子类可继承父类所有内容,然后再单独添加新的内容。
public class SpaceCar extends BasicCar {
    public int BoosterAmount;    //助推器数量
    public void launchToSpace(int BoosterAmount){
        int OrbitalAltitude = 100*BoosterAmount;   //根据助推器数量计算发射后轨道高度
    }
}
  1. 继承的注意事项:
    1. 继承不是简单的“直接享用”父类中所有的方法和属性。子类中能使用父类中哪些属性和方法主要由访问控制符控制。访问控制符对继承有很大的意义,语法规则比较复杂,暂时略过。
    2. Java 中所有的类只有一个父类。
    3. 所有的类,都直接或间接的继承自 Object 类。
    4. 创建子类对象时会自动创建其父类对象,并内嵌在子类对象中。
    5. 可以用父类引用指向子类对象,将子类对象伪装成父类对象。但需注意伪装之后不可调用子类独有内容,因为是引用类型决定了可被调用的内容。

覆盖

  1. 引入覆盖的原因:
    1. 假设我需要对 SpaceCar 继承的 speedUp 方法进行一些升级。
    2. 方法一:直接修改 speedUp 方法,但显然不行,speedUp 方法是全部车辆通用的。
    3. 方法二:单独给跑车设置一个新的加速方法 newSpeedUp,但这样不合适,因为车应该只有一种加速逻辑。
    4. 方法三:把 BasicCar 类里的 speedUp 方法删了,每个新车类均定义自己的 speedUp 方法。也行,但感觉有点不完美。
  2. 因此引入覆盖,语法没有新内容,直接在 SpaceCar 类里再定义一个空的 speedUp 方法里,把升级内容写进方法即可。
  3. SpaceCar 类继承自 BasicCar 类,BasicCar 类中也有一个speedUp 方法。方法签名一模一样,为什么没有冲突?
    1. 在同一个类中,方法的签名是辨别方法的唯一标识。故一个类中不允许出现两个方法签名相同的方法。
    2. 方法签名不能相同是针对同一个类中而言,而 SpaceCar 类是在子类中添加一个和父类中某方法同名方法,故不会冲突。也即“覆盖”。
  4. 覆盖发生的注意事项:
    1. 子类必须把这个方法从父类中继承过来,这个与方法的访问控制符有关。
    2. 子类中方法的访问控制符赋予方法的访问权限,必须与父类中对应方法的访问权限相同或更宽松。大多数情况下,都应该使用与父类方法相同的访问控制符。
    3. 子类中方法的返回值类型必须能赋给父类方法的返回值类型。举例来说,如果父类中的方法返回值是 int,则子类中对应方法的返回值就不允许是 double
  5. 当覆盖遇到伪装引用时,例如 BasicCar Test3 = new SpaceCar(),则运行伪装成 BasicCarTest3.speedUp 会调用新的 speedUp 方法。有点反直觉,原因如下:
    1. 程序编译时,发现 Test3 是一个 BasicCar 类引用,而 BasicCar 类中正好定义有 speedUp 方法,符合继承中提到的伪装引用执行规则,故编译通过。
    2. 程序运行时,Java 平台运行到 Test3.speedUp 时发现 Test3 引用指向了 SpaceCar 类对象,故会先去 SpaceCar 类中寻找 speedUp 方法,找不到再去父类中继续寻找。

接口

  1. 没有接口的局限性:
    1. 假设现在需要监测那两辆车状态怎么办?首先新写一个 Status 类组合到 BasicCar 类上用于描述状态,然后再新写一个 Recorder 类,分别传入两辆车的引用即可。
    2. 那过几天多了一百来辆车怎么办?貌似只传入所有车的父类引用也行。
    3. 那系统中还有 100 个 Bike 类,200 个 Skate 类等和 BasicCar 类没继承关系的类呢?那就得每多一种类型改一次 Recorder 类代码。
  2. 接口的含义:
    1. 谈到接口,大多数人的印象可能是 PCI 接口之类的插槽。这是种错误的认知,当我们说某型号 PCI 接口时,指的是该型号插槽遵守了 PCI 规范,而具体的 PCI 插槽只是该型号 PCI 接口的实例,下图显示了这种抽象过程。
    2. 在 Java中,接口其实也是一种类型,它与类不同在于,类是一种具体的类型,包含了每个方法的代码;而接口是一种抽象的类型,它只包含方法的定义,没有包含方法的具体代码。因此与现实世界类似,必须要有一个类去实现这个接口。
  3. 接口的特点:
    1. 一个接口可以有无数种实现。
    2. 一个类实现一个接口时,类中必须提供接口中所有抽象方法的具体实现。
    3. 一个类实现一个接口时,其子类也会随之自动实现某接口。
    4. 一个类实现一个接口时,就拥有了接口的类型。
    5. 接口也可以有引用,并可以指向实现接口的类。
  4. 因此对前述问题,我们可以通过接口使 Recorder()xxxCar 完全分离,从而解决问题。
    1. 定义一个 recordAble 接口,令任何一个实现了 recordAble 接口的 xxxCar 都能返回 recordAble 接口要求的标准数据类型。
    2. 由于所有汽车类都实现了 recordAble 接口,故 recordAble 引用可指向所有汽车类,并可通过 recordAble 引用调用 getStatus() 方法(参考覆盖与伪装部分)。
    3. Recorder() 类中的传入形参改为 recordAble
    4. 这样一来,车辆类型对 Recorder 完全透明,完全无需随程序扩展而修改。
//定义接口语法
public abstract interface RecordeAble {
    public abstract Status getStatus();
}

//实现接口语法
public class Bike implements RecordeAble {
    public Status getStatus(){    \\实现接口规定方法
        //具体实现
    }
    public xxx xxx(){    \\其他无关方法
        //具体实现
    }
}