软件构造(6)- 面向对象及等价性判断

一.基本概念
  1. 对象 :state状态 + behavior行为
    eg:Dogs :state (name, color, breed, hungry) + behavior (barking, fetching, wagging tail)
    state -> field
    behavior -> method
  2. class和method
    静态方法与实例方法的区别:
    静态方法与类本身相关联,不会因实例化而新建,使用这些方法也不用先将类进行实例化;实例方法与对象相关联,随着对象的产生和清除而发生变化。
    在这里插入图片描述
  3. 接口和枚举类型
    **接口:**确定ADT的规约;类:实现ADT
    也可以不需要接口直接使用类作为ADT,既有ADT定义也有ADT的实现
    实际中更倾向于使用接口来定义变量。
Set<Criminal> senate = new HashSet<>();  //Do this
HashSet<Criminal> senate = new HashSet<>(); //Not this

在Java中使用接口定义ADT
接口中不能定义构造器(接口本身不能被实例化),且接口中的所有方法都必须在子类中被重写,但子类中的方法不局限于接口中已有的方法。

常规的接口实现方法:

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

存在的问题:打破了抽象的边界
在client中若想要实现某个类必须知道该类的构造器具体名称
解决方法:
使用静态工厂代替使用构造器

//接口代码
public interface MyString{
	public static MyString valueof(boolean b) {
		return new FastMyString(true);
	}
}
//客户端代码
MyString s = MyString.valueof(true);
System.out.println("The first character is: " + s.charAt(0));

此时客户端无需已知要实现类的构造器具体名称

枚举类型:
枚举类型的构造方法:

//package enum
public enum Month { 
	JANUARY, FEBRUARY, MARCH, ..., 
	OCTOBER, NOVEMBER, DECEMBER;
}
public enum PenColor { 
	BLACK, GRAY, RED, ..., BLUE;
}
//三种枚举类型的使用方法
PenColor drawingColor = PenColor.RED;
Month month = Month.MARCH;

if(month.equals(Month.MARCH)) {...} //枚举类型判断相等

for(Month m : Month.values())  //枚举类型的遍历方式
	m.name();
	m.ordinal();
	m.comparedTo();
	m.toString();
...

也可在枚举类型中添加数据及行为:

public enum Planet {
	MERCURY(3.302e+23, 2.439e6), VENUS (4.869e+24, 6.052e6),
	EARTH(5.975e+24, 6.378e6), MARS(6.419e+23, 3.393e6);
	private final double mass; // In kg.
	private final double radius; // In m.
	private static final double G = 6.67300E-11;
	Planet(double mass, double radius) {
		this.mass = mass;
		this.radius = radius;
	}
	public double mass() { return mass; }
	public double radius() { return radius; }
	public double surfaceGravity() {
	return G * mass / (radius * radius);
	}
}
  1. 封装和信息隐藏

原理:
1. 使用接口类型声明变量
2. 客户端仅使用接口中定义的方法
3. 客户端代码无法直接访问属性

  1. Inheritance and Overriding 继承和重写

    1. Overriding
      严格继承:子类只能添加新方法,无法重写超类中的方法
      若某个方法不能被其子类重写,则需使用final前缀修饰,否则子类中均可将其重写
      final修饰的域:初始化后不能被重新赋值
      final修饰的方法:不能被重写
      final修饰的类:不能被继承

    重写的方法一定与原方法有相同的名字,参数列表,返回类型。
    实际执行时调用哪个方法,运行时决定。(父类对象调用父类,子类对象调用子类)
    子类中重写的方法可以采用super()复用父类函数中中的功能,并在子类中进行扩展。若调用父类构造器必须写在第一行。

    1. 抽象类:至少有一个抽象方法的类
      如果某些操作是所有子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写。
      所有子类型完全相同的操作,放在父类型中实现,子类型中无需重写。
      有些子类型有而其他子类型无的操作,不要在父类型中定义和实现,而应在特定子类型中实现。
  2. 多态,子类型,重载(overload)

    1. 特殊多态->功能重载
      重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型
      价值:方便client调用,client可用不同的参数列表,调用同样的函数
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;
}

overloading是静态多态,根据参数列表进行最佳匹配,在编译阶段时决定要具体执行哪个方法,使用静态类型检查。
与之相反,overridden methods则是在run-time进行dynamic checking!

重载的原则:
(1) 一定有不同的参数列表(指参数的类型不是名称!)
(2) 相同/不同的返回值类型
(3) 相同/不同的public/private/protected
(4) 可以抛出新的异常
(5) 可以在同一个类中进行重载,也可在子类中进行。

重载与重写的区别:

class B { 
	public void p(int i) { 
 	} 
} 
class A extends B { 
 	// This method overloads the method in B 
 	public void p(double i) { 
 		System.out.println(i); 
 	} 
}
class C extends B { 
 // This method overrides the method in B 
 public void p(int i) { 
 System.out.println(i); 
 } 
}

在这里插入图片描述
2. 参数化多态和泛型编程
使用<>来定义变量类型
通配符,只在使用泛型的时候出现,不能在定义中出现

List<?> list = new ArrayList<String>(); 
List<? extends Animal> //子类
List<? super Animal> //父类
  1. 子类型多态
    一种类型是变量值的集合
    子类型的规约不能弱化超类型的规约。
    子类型多态:不同类型的对象可以统一的处理而无需区分,从而隔离了“变化”
二.ADT与OOP中的等价性

等价关系:自反、对称、传递

  1. 不可变类型的等价性
    AF映射到同样的结果,则等价
    站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这两个对象是等价的。反之亦然!

  2. == 与 equals()
    前者比较引用等价性 适用于基本数据类型 判断两对象的ID是否相等(指向内存同一段空间)
    后者比较对象等价性 适用于对象数据类型

在自定义ADT时,需要重写Object的equals()

  1. 重写equals()
    凡是判断相等的位置都会默认采用重写的equals进行判断,例如list中的contains方法
    若不经重写,在Object中实现的缺省equals()是在判断引用等价性

不是override而是overload
在这里插入图片描述
在这里插入图片描述

上面的判断相等
下面的判断不相等
在这里插入图片描述
实际上,Duration类中含有两个equals方法,一个从Object类中继承,一个在类中定义
d1.equals(d2) 调用的是新定义的equals
d1.equals(o2) 调用的父类中的

  1. 重写hashcode()
    Object中缺省的hashcode()默认为该对象的地址

等价的对象必须有相同的hashCode (除非ADT不被放入hash类型的集合类中)
不相等的对象,也可以映射为同样的hashCode,但性能会变差

  1. 可变类型的等价性
    观察等价性:在不改变状态的情况下,两个mutable对象是否看起来一致
    行为等价性:调用对象的任何方法都展示出一致的结果

对可变类型来说,往往倾向于实现严格的观察等价性
但可变类型发生改变时,其内部的hashcode()也会发生改变
如果某个mutable的对象包含在Set集合类中,当其发生改变后,集合类的行为不确定(可能不再能查找到该元素)

在JDK中,不同的mutable类使用不同的等价性标准
eg:
观察等价性:
Date类的equals()的spec:"Two Date objects are equal if and only if the getTime method returns the same long value for both.
List类的equals()的spec:"Returns true if and only if the specified object is also a list, both lists have the same size, and all corresponding pairs of elements in the two lists are equal. “
只有当所有元素的顺序相同并相等,两个list才相同

行为等价性:
StringBuilder类的equals继承自Object类

对可变类型,实现行为等价性即可
也就是说,只有指向同样内存空间的objects,才是相等的。
所以对可变类型来说,无需重写这两个函数,直接继承Object的两个方法即可。
如果一定要判断两个可变对象看起来是否一致,最好定义一个新的方法。

对于不可变类型:一定要重写equals() hashcode()
对于可变类型:不需重写equals() hashcode()

  1. 自动装箱机制与等价性
Integer x = new Integer(2);
Integer y = new Integer(2);
x.equals(y)  ----true
x == y -----------false
(int) x == (int) y --true
//同理....放入Map的时候,自动将int 转为了Integer
Map<String, Integer> a = new HashMap<>();
Map<String, Integer> b = new HashMap<>();
a.put("c", 1);
b.put("c", 1);
//取出来的时候,得到的也是Integer类型
a.get("a")== b.get("a") -----false
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值