软件构造 3-4 Object-Oriented Programming (OOP)

3.4 面向对象的编程

一. 接口

  Java 中的 interface(接口)是一种表示抽象数据类型的好方法。接口中是一连串的方法标识,但是没有方法体(定义)。如果想要写一个类来实现接口,我们必须给类加上 implements 关键字,并且在类内部提供接口中方法的定义。所以接口+实现类也是 Java 中定义抽象数据类型的一种方法。

  我们偏向于使用接口来定义变量。

Set<Criminal> senate = new HashSet<>();

  接口中无构造函数。可以设计静态方法构造。静态方法可以存在于接口中。


  使用接口的优点:

  • 接口只为使用者提供“契约”。实例化的变量不能放在接口中(具体实现被分离在另外的类中)。
  • 允许了一种抽象类型能够有多种实现/表示,即一个接口可以有多个实现类(而一个类也可以同时实现多个接口)。

  然而, Java静态检查会发现没有实现接口的错误,但不会检查是否遵循了接口中的文档注释。

二. 继承与重写

  严格继承:子类只能添加新方法,无法重写超类中的方法。

//Superclass
public class Car {
	public final void drive() {...}
	public final void brake() {...}
	public final void accelerate() {...}
}
//Subclass
public class LuxuryCar extends Car {
	public void playMusic() {...}
	public void ejectCD() {...}
	public void resumeMusic() {...}
	public void pauseMusic() {...}
}

  @Override 的使用:通知编译器这个方法必须和其父类中的某个方法的标识完全一样(覆盖)。

  但是由于实现接口时编译器会自动检查我们的实现方法是否遵循了接口中的方法标识,这里的 @Override 更多是一种文档注释,它告诉读者这里的方法是为了实现某个接口,读者应该去阅读这个接口中的规格说明。

  同时,如果你没有对实现类(子类型)的规格说明进行强化,这里就不需要再写一遍规格说明了。(DRY 原则)

  重写的时候,不要改变原方法的本意。


  为了让接口不必直接知道抽象层次最底层的名称,例如下面的 FastMyString

MyString s = new FastMyString(true);
System.out.println("The first character is: " + s.charAt(0));

  我们可以为接口定义静态方法,即静态的工厂方法来实现创建:

public interface MyString { 

    /** @param b a boolean value
     *  @return string representation of b, either "true" or "false" */
    public static MyString valueOf(boolean b) {
        return new FastMyString(true);
    }

    // ...

  重写之后,可利用 super() 复用了父类型中函数的功能(构造方法中必须放在第一句),并对其进行扩展:

class Thought {
	public void message() {
		System.out.println("Thought.");
	}
}
public class Advice extends Thought {
	@Override // @Override annotation in Java 5 is optional but
	public void message() {
		super.message(); // Invoke parent's version of method.
		System.out.println("Advice.");
	}
}
Thought parking = new Thought();
parking.message(); // Prints "Thought."
Thought dates = new Advice();
dates.message(); // Prints "Advice \n Thought."

三. 泛型(参数型多态)

  例如 Set<E>Set 是一个泛型类型

  这种类型的规格说明中用一个占位符(以后会被作为参数输入)表示具体类型,而不是分开为不同类型例如 Set<String>, Set<Integer> , 进行说明。

  对于泛型接口的实现:

  • 一个非泛型的实现(用一个特定的类型替代 E适用于任意类型的元素)
public interface Set<E> {
	...
}
public class CharSet1 implements Set<Character> {
	...
}
  • 一个泛型实现(保留类型占位符
public interface Set<E> {
	...
}
public class HashSet<E> implements Set<E> {
	...
}

  泛型的通配符 ? ,运行时泛型会进行擦除

四. 枚举

  枚举:当值域很小有限时,将所有的值定义为被命名的常量。这样的类型往往会被用来组成更复杂的类型DateTime或者Latitude),或者作为一个改某个方法的行为的参数使用(例如drawline)。

public enum Month { JANUARY, FEBRUARY, MARCH, ..., DECEMBER };

  这实质上定义了一个被命名的值集合,由于这些值实际上是public static final,所以我们将这个集合中的每个值的每个字母大写

  枚举显式地列出了一个集合中的所有元素,并且JAVA为每个元素都分配了数字作为代表它们的值


  对于枚举类型来说,==equals() 效果一致。这是因为实际上只有一个对象来表示枚举类型的每个取值,且用户不可能创建更多的对象,它没有构造者方法。


  Java 支持在 switch 中使用 enum 类型。Java 对枚举类型有更多的静态检查

  一个enum声明中可以包含所有能在class声明中常用字段和方法。所以你可以为这个ADT定义额外的操作,并且还定义你自己的表示成员变量)。


  所有的enum类型也都有一些内置的操作,这些操作在Enum中定义:

  • ordinal() 是某个值在枚举类型中的索引值
  • compareTo() 基于两个值的索引值来比较两个值
  • name()返回字符串形式表示的当前枚举类型值
  • toString()name() 是一样的。

五. 多态与重载

  多态:让一个对象或一个类在不同的时间产生不同的变化:

  • 特殊多态重载):使用功能重载的许多语言都支持特殊多态。不同方法共用一个方法名
  • 参数化多态泛型编程。
  • 子类型 subtyping 多态、包含多态:当名称表示由某个公共父类关联的许多不同类的实例时。如:
Set<Criminal> senate = new HashSet<>();

1. 特殊多态(重载)

  重载:多个方法具有同样的名字,但有不同参数列表返回值类型

public class OverloadExample {
	public static void main(String args[]) {
		System.out.println(add("C","D"));
		System.out.println(add("C","D","E"));
		System.out.println(add(2, 3));
	}
	public static String add(String c, String d) {
		return c.concat(d);
	}
	public static String add(String c, String d, String e) {
		return c.concat(d).concat(e);
	}
	public static int add(int a, int b) {
		return a+b;
	}
}

  静态多态

  • 根据参数列表进行最佳匹配
  • 静态类型检查
  • 编译阶段时决定要具体执行哪个方法

  与之相反,重写方法是在运行时进行动态检查


  函数重载中的规则:重载的函数必须根据特性数据类型有所不同。

  • 不同的参数列表
  • 相同 / 不同的返回值类型
  • 相同 / 不同的 public / private / protected
  • 相同 / 不同的异常
  • 可以在同一个类内或子类中重载
  • 方法名相同

  OverrideOverload 的比较

OverloadingOverriding
参数列表必须改变禁止改变
返回类型可以改变禁止改变
异常可以改变可以减少或消除,禁止抛出新的或更广泛的 checked 异常
规约可以改变不能有更多的限制(可以少一些限制)
调用引用类型决定选择哪个重载版本(基于声明的参数类型)。在编译时发生。实际调用的方法仍然是在运行时发生的虚拟方法调用,但是编译器始终知道要调用的方法的签名。因此,在运行时参数匹配将已经确定,只是不是方法所在的实际类对象类型(换句话说,上实例的类型)决定了哪个方法被选中。发生在运行时

  调用方法 / 参数看的是本身的类型而不是指向的对象,如下程序第三条对 dostuff() 的调用,调用的是第一个 dostuff() 方法:

class Animal {
	public void eat() {}
}
class Horse extends Animal {
	public void eat(String food) {}
}
public class UseAnimals {
	//第一个
	public void doStuff(Animal a) {
		System.out.println("Animal");
	}
	public void doStuff(Horse h) {
		System.out.println("Horse");
	}
}

public class TestUseAnimals {
	public static void main (String [] args) {
		UseAnimals ua = new UseAnimals();
		Animal animalobj = new Animal();
		Horse horseobj = new Horse();
		Animal animalRefToHorse = new Horse();
		ua.doStuff(animalobj);
		ua.doStuff(horseobj);
		ua.doStuff(animalRefToHorse);//第三句
	}
}

  对于重载与重写,编译时决定是否能够调用,运行时决定调用哪种方法。

interface Animal {
	void vocalize();
}
class Cow implements Animal {
	public void vocalize() { moo(); }
	public void moo() {System.out.println("Moo!"); }
}

  在调用

Animal b = new Cow();
b.moo();

  会发生编译错误

2. 子类型多态

  例如 ArrayListLinkedListList 的子类型。

  BA 的子类型意味着每一个 B都是 A,即每一个 B满足A 的规格说明。这也意味着 B 的规格说明至少强于 A 的规格说明。——任何一个 B 都是 A

  然而编译器只会检查是否实现接口中规定的所有函数、标识是否对得上,但不会检查是否通过其他形式弱化了规格说明。

  子类型多态:不同类型的对象可以统一的处理而无需区分。

  instanceof :获取当前正在运行对象的实际类型:

public void doSomething(Account acct) }
	long adj = 0;
	if (acct instanceof CheckingAccount) {
		checkingAcct = (CheckingAccount) acct;
		adj = checkingAcct.getFee();
	} else if (acct instanceof SavingsAccount) {
		savingsAcct = (SavingsAccount) acct;
		adj = savingsAcct.getInterest();
	}
	...
}

  少用这种方法。


  不要出现向下转换的情况。

六. Java中重要的方法

  • equals() 两个对象相等时返回 true。如果需要值语义,则 @Override
  • hashCode() 用于哈希映射的哈希代码。如果需要值语义,则 @Override
  • toString() 一个可打印的字符串表示。总是 @Override,除非你知道toString()不会被调用。
  • 静态方法:类的所有对象所共享
  • 实例方法:单独所有

七. 抽象类

  类中至少有一个方法是抽象方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值