一.基本概念
面向对象的方法是一种分析方法、设计方法和思维方法。
面向对象方法学的出发点和所追求的基本目标是使人们分析、设计与实现一个系统的方法尽可能接近人们认识一个系统的方法。
使描述问题的问题空间和解决问题的方法空间在结构上尽可能一致
从现实世界中客观存在的事物出发来建立软件系统
强调直接以问题域(现实世界)中的事物为中心来思考问题、认识问题,并根据这些事物的本质特征,把它们抽象地表示为系统中的对象,作为系统的基本构成单位。这可以使系统直接映射问题域,保持问题域中事物及其相互关系的本来面貌。
强调运用人类在日常的逻辑思维中经常采用的思想方法与原则,例如抽象、分类、继承、聚合、封装、关联等等。这使得软件开发者能更有效地思考问题,并以其他人也能看得懂的方式把自己的认识表达出来。
对象 :state状态 + behavior行为
eg:Dogs :state (name, color, breed, hungry) + behavior (barking, fetching, wagging tail)
state -> field
behavior -> method
class和method
静态方法与实例方法的区别:
静态方法与类本身相关联,不会因实例化而新建,使用这些方法也不用先将类进行实例化;实例方法与对象相关联,随着对象的产生和清除而发生变化。
3.接口和枚举类型
**接口:**确定ADT的规约;类:实现ADT
也可以不需要接口直接使用类作为ADT,既有ADT定义也有ADT的实现
实际中更倾向于使用接口来定义变量。
Set<Criminal> senate = new HashSet<>(); //Do this
HashSet<Criminal> senate = new HashSet<>(); //Not this
1
2
在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. 使用接口类型声明变量
2. 客户端仅使用接口中定义的方法
3. 客户端代码无法直接访问属性
Inheritance and Overriding 继承和重写
Overriding
严格继承:子类只能添加新方法,无法重写超类中的方法
若某个方法不能被其子类重写,则需使用final前缀修饰,否则子类中均可将其重写
final修饰的域:初始化后不能被重新赋值
final修饰的方法:不能被重写
final修饰的类:不能被继承
重写的方法一定与原方法有相同的名字,参数列表,返回类型。
实际执行时调用哪个方法,运行时决定。(父类对象调用父类,子类对象调用子类)
子类中重写的方法可以采用super()复用父类函数中中的功能,并在子类中进行扩展。若调用父类构造器必须写在第一行。
抽象类:至少有一个抽象方法的类
如果某些操作是所有子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写。
所有子类型完全相同的操作,放在父类型中实现,子类型中无需重写。
有些子类型有而其他子类型无的操作,不要在父类型中定义和实现,而应在特定子类型中实现。
多态,子类型,重载(overload)
特殊多态->功能重载
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型
价值:方便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> //父类
子类型多态
一种类型是变量值的集合
子类型的规约不能弱化超类型的规约。
子类型多态:不同类型的对象可以统一的处理而无需区分,从而隔离了“变化”
二.ADT与OOP中的等价性
等价关系:自反、对称、传递
不可变类型的等价性
AF映射到同样的结果,则等价
站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这两个对象是等价的。反之亦然!
== 与 equals()
前者比较引用等价性 适用于基本数据类型 判断两对象的ID是否相等(指向内存同一段空间)
后者比较对象等价性 适用于对象数据类型
在自定义ADT时,需要重写Object的equals()
重写equals()
凡是判断相等的位置都会默认采用重写的equals进行判断,例如list中的contains方法
若不经重写,在Object中实现的缺省equals()是在判断引用等价性
不是override而是overload
上面的判断相等
下面的判断不相等
实际上,Duration类中含有两个equals方法,一个从Object类中继承,一个在类中定义
d1.equals(d2) 调用的是新定义的equals
d1.equals(o2) 调用的父类中的
重写hashcode()
Object中缺省的hashcode()默认为该对象的地址
等价的对象必须有相同的hashCode (除非ADT不被放入hash类型的集合类中)
不相等的对象,也可以映射为同样的hashCode,但性能会变差
可变类型的等价性
观察等价性:在不改变状态的情况下,两个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()
原文:https://www.cnblogs.com/szjk/p/11074554.html