软件构造笔记第七章面向对象的编程

第七讲 面向对象的编程

用OOP/接口/类实现ADT

提纲

OOP的基本概念:对象、类、属性、方法和接口
OOP明显的特征

  • 封装与信息隐藏
  • 继承与重写
  • 多态、子类型、重载
  • 静态与动态分派

Java中一些重要的Object methods
设计好的类

  • 设计immutable类型的class
  • 设计mutabe类型的class

OOP的历史

1.基础概念

class variable(类成员变量)
与类相关的变量

instance methods(实例方法)和instance variables(实例成员变量)
在类的每个实例中发生一次

class methods(类方法)和class variables(类成员变量)
和类相关,在每个类中发生一次,使用他们不需要创造对象。

方法都存在方法栈中
实例方法每一个对象对应不同的方法栈,类方法每一个对象共享一个方法栈。
类中基本属性在堆中,类中方法属性在栈中。

2.Interface接口

method signatures(方法签名)
方法声明的两个组件构成了方法签名——方法的名称和参数类型

public double calculateAnswer(double A,int B,double C,double D){....}
//方法签名
calculateAnswer(double,int,double,double)

java中的接口
是方法签名的列表,但没有方法体,不能被实例化。
如果类在其实现子句中声明接口,并为接口的所有方法提供方法体,则类将实现接口。
接口之间可以继承和扩展。
一个类可以实现多个接口(从而具备了多个接口中的方法)。
一个接口可以有多种实现类。
类不允许多重继承。

接口:确定ADT规约
类:实现ADT
也可以不需要接口,直接使用类作为ADT,既有ADT定义也有ADT实现。

实际中更倾向于使用接口来定义变量。

  • 除非知道一个实现足够了,否则对变量和参数使用接口类型。
  • 支持实现的改变
  • 防止依赖实现的细节

例如

Set<Criminal>senate=new HashSet<>();//do this...
HashSet<Criminal>senate=new HashSet<>();//Not this...

错误示例

/** Represents an immutable set of elememts of type E. */
public interface Set<E>{
public Set();//java接口不能有构造器
/** @return true if this set contains e as a member */
public boolean contains contains(E e);
/** @return a set which is the union of this and that */
public ArraySet<E> union(Set<E> that);//ArraySet没有表示独立性
}

/** Implementation of Set<E>.*/
public class ArraySet<E> implements Set<E>{
/** make an empty set */
public ArraySet(){...}
/** @return a set which is the union of this and that */
public ArraySet<E> union(Set<E> that){...}
/** add e to this set */
public void add(E e){...}
}
//Java允许类比接口的方法多,但是不能缺少接口中的方法。
//add(E e)违背了Set的规约——它的不变性,所有ArraySet不是Set的合法实现
MyString s=new FastMyString(true);

问题:打破了抽象边界
接口定义中不包含constructors,也无法保证所有实现类中都包含同样名字的constructors。因此,客户端需要知道该接口的某个具体实现类的名字。

从Java 8开始,允许接口中包含静态方法

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);
 }

MyString s=MyString.valueOf(true);
.....

接口中的每个方法在所有类中都要实现;
通过default方法,在接口中统一实现某些功能,无需在各个类中重复实现它;
以增量式的为借口增加额外的功能而不是破坏已实现的类。

public interface Example{
default int method1(int a){...}//有方法体
static int method2(int b){...}//有方法体
public int method3();//在实现中定义方法体
}

public class C implements Example{
@Override
public int methods(){...}//需要实现method3

public static void main(String[] args){
Example.method2(2);
C c=new C();
c.method1(1);
c.method3();
}
}

3.继承和重写

(1)重写

重写方法
允许重新实现的方法。
Java中默认方法是可以重写的,在没有特殊的关键字情况下。

严格继承——用final实现
子类只能添加新方法,无法重写超类中的方法
如果一个方法不能被重写,它必须被关键字final修饰。

final

  • +类:类不能被继承
  • +方法:子类不能重写该方法
  • +属性:该属性不能改变
  • +immutable类型:值不能被改变
  • +mutable类型:值可变,引用不可变

父类不能调用子类增加的方法。

模板
在父类中实现所有的方法,子类新增的方法需要在父类中声明。客户端只能通过调用父类的方法,间接调用子类的方法。

重写

父类或者超类中已经实现的方法,在子类中重新实现,重写的函数在父类和子类中有完全相同的签名
编译时根据对象所属类判断是否可以调用该函数,实际执行时,根据对象具体实现再决定调用哪一个。

public class Animal{
data1;
data2;
i=1move(){
i=i+1;
}
eat(){...}public class Dog extends Animal{
data3;
data4;
i=1;
move(){
i=i+2;
}
move(int m){.....}
bark(){....}
}

main(){
Aniaml A =new Dog();
A.i;//属性不继承,是Animal中的i=1;
A.move();//Animal中有move,可以调用,编译器不报错
//具体运行时,A是指向Dog类的,所以调用Dog中的move(),值为3
//若Dog中没有重写move(),则调用Animal中的move(),值为2
int c=3;
A.move(c);//Animal中没有move(int),编译器报错
A.move(3);//Animal中没有move(int),编译器报错

如果父类中的某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写

class Device{
int serialnr;
public void setSerialNr(int n){}
}

class Value extends Device{
Position s;
public void on(){
.......
}
public void setSerialNr(int n){
seriennr=n+s.serialnr;
}
}

重写之后,利用super()复用了父类型中函数的功能,并对其进行了扩展。

class Thought{
public void message(){
System.out.println("Thought.");
}
}

public class Advice extends Thought{
@override   //强调要进行重写
public void message(){
System.out.println("Advice");
super.message();//声明父类中的方法版本。
}
}
Thought parking=new Thought();
parking.message();//prints"Thought."

Thought dates=new Advice();
dates.massage();//prints
                           //Advice 
                           //THought.

用this和super进行构造
在子类中创建构造器,其中第一行必须是超类的构造器声明。
在子类中还可以声明其他的构造器。(类似于重载)
重写的时候不要改变原方法的本意,符合可替换原则。静态编译检测不出,只能由程序员来保证。

(2)Abstract Class 抽象类

抽象方法
有签名没有实现体的方法,也称为抽象操作。
由关键字abstract定义。
抽象类至少包含一个抽象方法的类。
接口只有抽象方法的类。

抽象类
介于接口和具体类之间,不能进行实例化,可以包含一些具体方法和方法,一般不用抽象类。
如果某些操作是所有子类型共有的,但彼此有差别,可以在父类中设计抽象方法,在各子类中重写。

abstract class G{
int x,y;
abstract void draw();
abstract void resize();
}
class Circle extends G{
void draw(){....}
void resize(){.....}
}
class Re extends G{
void draw(){....}
void resize(){....}
}

4.Polymorphism,subtyping and overloading 多态、子类型、重载

(1)多态的三种类型

Ad hoc polymorphism特殊多态:功能重载
parametric polymorphism参数化多态:generics或generics programming
subtyping子类型多态、包含多态

(2)特殊多态和重载

方法名相同,但参数列表不同,返回值类型也可以不同,实质上是不同的方法,在编译阶段能确定调用哪一个方法。
价值:方便client调用
重载是一种静态多态,根据参数列表进行最佳匹配(参数类型相同优先,若是不同,则找参数类型的父类进行匹配)。

重载能够进行静态类型检查。
区别:

方式确定时间
重写执行
重载编译

重载规则

  • 必须有不同的参数列表
  • 可以有相同的/不同的返回值
  • 可以有相同的/不同的public/private/protected
  • 可以声明异常
  • 可以在同类中重载,也可以在子类中重载。

重载可以发生在父类和子类之间

class Animal{
public void eat(){..}class Horse extends Animal{
public void eat (String food){...}
}
public class UseAnimals{
public void duStuff(Animal a){
System.out.pirntn("Animal");
}
public void doStuff(Horse h){
System.out.println("Horse");
}
}
mian(String [] args)
{
UseAnimal ua =new UseAnimals();
Animal animalobj =new Animal();
Horse horseobj=new Horse();
Animal animalRefToHorse=new Horse();

ua.doStuff(animalobj)//Animal
ua.doStuff(horseobj);//Horse
ua.doStuff(animalRefToHorse);//Animal
													//原因:animalRefToHorse的类型是Animal,静态类型检查时确定好要调用的函数是doStuff(Animal)
重载重写
参数列表必须改变必须不变
返回值类型可以改变必须不变
异常可以改变异常更少或者更具体
通道可以改变不能设置更严格(可以减少限制)
调用编译阶段对象类型决定选择哪种方式(运行阶段)
本质不同方法同一方法

(3)Parametric polymorphism and Generic programming参数化多态和泛型

参数化多态:

  • 一个函数在一系列类型上运行良好时,得到参数化多态;这些类型通常具有出一些共同结构。

  • 它能够以通用方式定义函数和类型,以便根据运行时传递的参数工作,即允许静态类型检查,而无需完全指定类型。

泛型:

  • 一种编程风格,其中的参数类型和函数按照稍后指定的类型编写,然后在需要时对作为参数提供的特定类型进行实例化。

类型变量:
使用<>来帮助声明类型变量。

List<Integer> ints =new ArrayList<integer>();
public interface List<E>
public class Entry<KeyType,ValueType> 
public class PaperJar<T>{
		private List<T> itemList =new ArrayList<>();
		public void add(T item){
					itemList.add(item);
		}
		public T get(int index){
					return (T) itemList.get(index);
		}
		public static void main (String args[]){
					PapersJar<String> papersStr=new PapersJar<>();
					papersStr.add("Lion");
					String str=(String) papersStr.get(0);
					System.out.println(str);
		}
}

泛型接口:
way1:泛型街口,非泛型的实现类。

public interface Set<E>{
//....
/**
	*Test for membership.
	*@param e an element
	*@return true iff this set contains e
	*/
	public boolean contains(E e);

/**
	*Modifies this set by adding e to the set.
	*@param e element to add
	*/
	public void add(E e);
	//....
}

public class CharSet1 implements Set<Character>{
private String s="";
//...
@Override
public boolean contains(Character e){
		checkRep();
		return s.indexOf(e)!=-1;
}

@Override
public void add(Character e){
		if (!contains(e) )s+=e;
		checkRep;
		}
		//...
}

way2:泛型接口,泛型的实现类
HashSet对于Set就是这样的实现

public interface Set<E>{
//...
}
public class HashSet<E>implements Set<E>{
//...
}

泛型的一些细节:

  • 可以有mutable类型的参数
  • 通配符?,只在使用泛型的时候出现,不能在定义中出现
    • List<?> List =new ArrayList();
    • List<? extends Animal>
    • List<? super Animal>
  • 运行时泛型消失,不能用instanceof()来检查泛型
  • 不能有泛型数组
  • 不能直接赋值
    List<Object> O;
    List<String> p;
    O=p;//错误
    

(4)Subtyping Polymorphism 子类型多态

每个类只能直接继承一个父类,可以实现多个接口。
B是A的子类:意味着每一个B都是一个A。B满足A 的规约,B的规约至少和A的规约一样强

子类型多态 不同类型的对象可以统一的处理而无需区分,从而隔离了“变化”。

Liskov替换原则(LSP)
如果S是T的子类型,那么T类型的对象可以替换为S类型的对象(即T类型的对象可以替换为任何子类型的对象),而不改变任何T的属性。——可替换性

instanceof
判断对象是否为所给类型的操作
尽可能避免使用instanceof(),不要在超类中使用instanceof来检查子类类型。

<检查对象类型>

  • instanceof 是否为该类或其子类
  • getclass 只检查是否为该类

5.java中一些重要的对象方法

重写对象方法

  • equals()——如果两个对象“相等”,返回true
  • hashcode()——用于哈希映射的哈希代码
    如果你想要value,必须重写这两个函数,否则不需要重写
  • toString()——可打印的字符串表示形式
    知道你的对象是什么,会做的更好
    除非你知道in不会被调用,否则始终要重写
    示例:
public class Name{
		private final String first,last;
		public Name(String first,String last){
				if (first==null || last==null)
					throw new NullPointerException();
				this.first=first;
				this.last=last;
		}
		public boolean equals(Name o){
				return first.equals(o.first)&&last.equals(o.last);
		}
		public int hashCode(){
				return 31*first.hashCode()+last.hashCode();
		}
		public static void main(String [] args){
				Set<Name> s=new HashSet<>();
				s.add(new Name("Mickey","Mouse"));
				System.out.println(new Name("Mickey","Mouse")));
		}
}
	//Name 重写了hashCode但是没有重写equals.这两个Name实例不相等。
	//修改:重写equals方法
	//@overrride 强制编译器检测是否为合理的重写
	@override public boolean equals(Object o){
			if (!(o instanceof Name)
					return false;
			Name n=(Name) o;
			return n.first.equals(first)&&n.last.equals(last);
		}

6.设计好的类

immutable类的优势

  • Simplicity
  • Excellent building blocks
    功能简单便于修改
  • Can be shared freely
  • No need for defensive copies
    允许多个引用指向同一个对象
  • Inherently Thread-Safe
    固有线程安全

如何写一个immutable类

  • 不提供变值器
  • 没有方法被重写
  • 所有属性用final修饰
  • 所有属性private
  • 确保任何mutable类型的成分安全(避免表示泄露)
  • 实现toString(),hashCode(),clone(),equals()等

什么时候将类设为immutable

  • 总是,除非有好的原因
  • 总是让小的“value class”是immutable类型
    • 例如:Color,PhoneNumber,Unit
    • Date和Point是错误的
    • 通常使用long代替Date

什么时候让类是mutable类型

  • 类表示的是状态需要改变的实体
    • 真实世界中:BankAccount,TrafficLight
    • 抽象:Iterator迭代器,Matcher匹配器,Collection集合
    • 进程类:Thread线程,Timer计时器
  • 如何类必须是可变的,则最小化可变性
    • 构造函数应完全初始化实例
    • 避免重新初始化方法

7.面向对象编程(OOP)的历史

省略

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wind~飘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值