HIT软件构造学习笔记6- Object-Oriented Programming (OOP)

Basic concepts: object, class, attribute, and method

1.1Objects

在真实世界中的对象都有两个基本的属性特征,那就是状态行为。那我们可以延伸思维,从定义真实世界中的对象的状态和行为开始,慢慢了解面向对象编程的含义。

举两个例子:

  • 犬类
    我们可以定义小狗的状态,它有属于自己的名字、颜色、品种以及饱食度;以及他们的行为,汪汪叫、抓东西、摇尾巴等等。
  • 自行车类:
    自行车的状态有当前档位、当前踏板节奏、当前速度等等;它的行为有换档、改变踏板节奏、踩刹车等等。

那么我们进行一下归纳总结:一个对象就是一捆状态和行为的集合体。状态,是对象包含数据所在字段;行为,是对象所支持进行的操作。

1.2Classes

每一个对象有一个类,在类中可以定义对象的方法,方法和变量的字段称为类的成员。类中同时定义了数据类型和实现方法,数据类型说明了对象可以怎么被使用,而实现方法表明了对象怎么做事情。不严格的说,一个类的方法就是他的应用接口,就是我们听过的API。类有一种称为类图的图示方法,结构是这样的:
 

 

1.3Static vs. instance variables, methods of a class

  • 类成员变量和类方法
    类成员变量是和一个类相关联而不是和一个类的实例化相关联的。同样,也可以把类方法做类似的类比,指的是和类相关联的方法。
  • 实例成员变量和实例方法
    不是和类相关联的成员变量和方法就称之为实例方法和实例成员变量。

下图中,用红色字标注的就是实例成员变量或者实例方法,蓝色字标注的就是类成员变量和类方法。

 

 

 值得注意的是,静态方法无法直接调用非静态成员.

那么静态类与示例有啥不同的呢?

  1. 分配空间的时间不同
    非静态方法在运行到这个方法的时候才在栈中为它分配空间,而静态方法在运行到相关代码之前就已经分配好空间了。

 

    2.引用可见性不同
    这一点说的是静态方法与非静态方法的区别。非静态方法在使用的时候,需要先创建一个              Object,然后再使用Object中定义的方法;而静态方法可以直接调用使用类中的方法,无需先        进行实例化Object的创建

Interface and Enumerations

2.1Interface

Java 的接口是一种用于设计和表达 ADT 的非常有用语言机制,它的实现可以作为类实现这个接口的一种方法。一个接口类有以下一些特点:

  • 接口中只有方法的定义,没有具体的实现体。
  • 接口之间可以继承与扩展。
  • 一个类可以实现多个接口,从而具备了多个接口中的方法。
  • 一个接口可以有很多中实现方法。

关于接口和类的选择和使用比较自由。可以选择适用接口来确定ADT的规约,再用类去实现它;或者也可以不需要接口直接使用类作为ADT,既有ADT定义也有ADT实现。但是在实际中更倾向于使用接口来定义变量。

/** Represents an immutable set of elements of type E. */
	public interface Set<E> {
			/** make an empty set */
			public Set();
			/** @return true if this set contains e as a member */
			public boolean contains(E e);
			/** @return a set which is the union of this and that */
			public ArraySet<E> union(Set<E> that); 
	}
		
	/** 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) { ... }
}

这段代码中有几个不符合Interface的地方:

  1. public Set();不应该出现在这里,接口中不应该有构造方法
  2. public ArraySet<E>破坏了表示独立性
  3. public void add实现体中也有接口方法的缺失

正确的接口和实现它的例子

接口的定义:

实现接口的类:

 

在使用这个实现方法的时候我们可以使用下面两行代码:

MyString s = new SimpleMyString(true);					//选择实现方法

 接口打破了抽象的边界。但是接口中没有包含构造函数,我们也无法保证所有的接口的实现方法都含有一个同名字的构造函数。因此,这意味着客户端需要知道跟这个接口某一种具体实现类的名字才能使用这个接口。

Using static factory instead of constructor

这里说的是使用静态工厂方法来代替构造函数,它的目的就是为了解决上一小节结尾出现的问题。我们使用一个静态工厂方法,返回一个该接口的实现方式。这样的话,在client看到这个接口的时候他会看到这样的场景:

 这样client在使用的时候,他可以直接调用接口中的工厂方法而获得这个接口的一种实现。

Using default methods in an interface

接口中的每一个方法都要在所有的类中实现,想一想就能知道这样的做法可能会导致部分方法被重复实现。为了解决这个问题,我们可以使用default方法,在接口中统一实现某些功能,就无需在他的每一个实现中都重复这些代码了。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 method3() {…}
		
		public static void main(String[] args) {
		Example.method2(2);
		C c = new C();
		c.method1(1);
		c.method3();
		} 
}

这段示例代码中,实现方式C中重写了method3,这段代码很好地展示了静态与非静态、default方法与其他方法在使用上的区别。.method2()是静态方法,因此可以直接调用,而对.method3()方法的调用因为重写所以实现的是重写之后的功能。

 Inheritance and Overriding(继承和重写)

Inheritance

继承的目的是提高代码的复用性,它能够让重复带代码写且只写一次,而且在子类中可以隐式的使用父类中的方法和功能,继承使用下面的语句:

public void A extends B{					//继承
		......
}

 

Overriding

  • 可重写方法和严格继承

通过继承得到的子类中,方法可以大体分为两类,一类是可以进行重写的方法,另一类就是严格继承下来的方法。区别就在于一个可以进行override,而另一个不可以override。而对于严格继承的方法,在Java中以是那些加了final修饰符的方法。我们看下图中的例子,使用final修饰的方法就不能在子类中进行重写。

 一个final修饰的代码字段能够防止在初始化之后对该字段的重新赋值;一个final修饰的方法能够防止子类对其的override;一个final修饰的类能够防止在程序的别的地方派生这个类的子类。

  • Override的要求

在子类中如果想对父类的方法进行重写,有以下几个要求

  • 重写的函数需要与被重写的函数有着同样的签名。
  • 在执行的时候调用哪个方法,在运行的时候才会被决定。就是说如果调用父类中的方法就是调用父类,子类就是子类。

我们通过观察被冲写的父类型的函数体的特点可以作出以下总结:

  • 如果父类型中的被重写函数体不为空,那就意味着对其大多数子类型来说,该方法是可以被直接复用的。
  • 对某些子类型来说,有自己的特殊性,所以重写父类型中的函数,实现自己的特殊要求
  • 如果父类型中的某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写。

在重写之后,子类型也可以通过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(); // Invoke parent's version of method.
		}
}

Thought parking = new Thought();
parking.message(); 						// Prints "Thought."

Thought dates = new Advice(); 
dates.message(); 						// Prints “Advice. \n Thought."

我们在进行对父类的重写的时候在格式上需要注意几点:

  1. 两个方法的签名要保持完全一致。
    想做到这一点可以在方法前面加上@Override,这样的话编译器会帮你检查被冲写的方法和你的重写体的签名是否保持了一致。
  2. 声明部分可以直接复制粘贴,或者让IDE帮你来完成这一步
  3. 可见性要变得更强或者是相等
    也就是说,方法的可见性要变得更加保密或者至少相等,不然会报错。

Abstract Class

  • 抽象方法
    抽象方法是指一类只有方法签名而没有具体实现的方法,在定义的时候使用关键词abstract
  • 抽象类
    指包含着至少一个抽象方法的那些类。需要注意的是,抽象类不能进行实例化,而且继承某个抽象类的子类在实例化时,所有父类中的所有抽象方法必须已经实现

abstract class GraphicObject {
		int x, y;
		...
		void moveTo(int newX, int newY) {
				...
		}
		abstract void draw();
		abstract void resize();
}

class Circle extends GraphicObject {					//继承父类
		void draw() {
				...
		}
		void resize() {
				...
		} 
}

class Rectangle extends GraphicObject {					//继承父类
		void draw() {
				...
		}
		void resize() {
				...
		}
}

对于那些所有子类型完全相同的操作,放在父类型中实现,子类型中无需重写。如果某些操作是所有子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写。而有些子类型有而其他子类型无的操作,不要在父类型中定义和实现,而应在特定子类型中实现。

Polymorphism, subtyping and overloading(多态、子类型、重载)

Three Types of Polymorphism

  • 特殊多态
    一个方法可以有好几个同名的实现方式,在很多编程语言中,特殊多态被一种叫方法重载的形式支持。
  • 参数化多态
    代码的参数不被限制于特定的数据类型而进行的编程,也就是说一个类型名字可以代表多个类型。参数化多态更为熟知的叫法叫泛型编程
  • 子类型多态
    一个变量的名字可以代表多个类的实例

Ad-hoc polymorphism and Overloading

这种特殊多态会在函数以一些不同的类型工作,并且可能表现出不相关的行为的时候被获得,尽管这些函数可能没有展现出相同的结构。例如下面的代码中的add方法,虽然方法签名相同,但是行为却不一样,这就是特殊多态。

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

方法重载
多个方法具有同样的名字,但是有不同的参数列表或者返回值类型。它的好处就是方便了client的调用,他在调用的时候可以使用同一个函数,尽管参数列表不同。

方法重载这个功能是一种能够实现同名但是不同实现函数的能力。对重载函数的调用结合调用这个函数的上下文,来完成特定的实现。也就是允许一个函数根据上下文来执行不同的任务。

方法的重写是一种静态多态,重载的方法会根据输入的参数列表进行最佳匹配。对于他的检查属于静态类型的检查,也就是说在编译阶段时决定要具体执行哪一个方法。

进行一个和重写的区别:重写的方法是在运行时进行动态检查重载的方法是在编译时进行静态检查

重载的要求

  1. 必须要有不同的参数列表,如果违反了这个规则将会被识别为重复定义
  2. 可以改变返回值类型
  3. 可以有不同的可见性
  4. 可以抛出不同的异常类型
  5. 重载可以在同一个类中进行,也可以在其子类中进行。

public void changeSize(int size, String name) { }

public void changeSize(int length, String pattern, float size){ }					//非法的,和原方法一毛一样

public boolean changeSize(int size, String name, float pattern) { }					//非法的,参数列表没有变化
																					//注意他只是改动了参数的名字,但是参数类型是一摸一样的,所以参数列表是一摸一样的。

对于子类和父类之间的重载,通过下面的示例我相信你就可以很容易的理解了。

class Animal { 								//supercalss
		public void eat() {
				System.out.println("I'm an animal. I like eating everything!");}
		}
		
class Horse extends Animal {				//subclass
		public void eat(String food) {
				System.out.println("I'm a horse. I like eating "+ food);
		}
		public void eat() {
		System.out.println("I'm a horse. I like eating grass!"}
}

 

  • 重写和重载的区别

重写时父类和子类中的方法具有相同的签名,那么当前命不相同的时候就是重载;当子类重载了父类的方法后,子类仍然继承了被重载的方法。

Parametric polymorphism and Generic programming(参数化多态以及泛型编程)

参数多态性是指方法针对多种类型时具有同样的行为(这里的多种类型应具有通用结构),此时可使用统一的类型变量表达多种类型。这就是Java中所说的泛型

  • 泛型编程

泛型编程是一种编程风格,其中数据类型和函数是根据待指定的类型编写的,随后在需要时根据参数提供的特定类型进行实例化。泛型编程围绕“从具体进行抽象”的思想,将采用不同数据表示的算法进行抽象,得到泛型化的算法,可以得到复用性、通用性更强的软件。

  • 使用方法

在Java中,使用尖括号<>来帮助我们标识一个类型变量。使用泛型变量有三种形式:泛型类泛型接口泛型方法。下面一样举一个例子。

public interface List<E>												//泛型接口
public class Entry<Keytype, ValueType>									//泛型类
List<Integer> ints = new ArrayList<Integer>(); 							//泛型方法

泛型类:如果一个类中声明了一个或者多个泛型变量,则成为泛型类,这些类型变量称为类的类型参数。在这个类中可以定义一个或者多个类型变量作为参数来使用,所有已经被参数化的类型在同一个类的运行时都保持同一种类型。在下面的示例中,对于client端在调用这个方法的时候,整个类中的泛型变量<E>都是String类型的。
 

public class Pair<E> {
		private final E first, second;
		public Pair(E first, E second) {
				this.first = first;
				this.second = second;
		}
public E first() { return first; }
public E second() { return second; }
}

//	Client:
Pair<String> p = new Pair<>("Hello", "world");
String result = p.first();

泛型接口
如果一个接口声明了一个或多个类型变量,那么它就是泛型的。

  • 这些类型变量称为接口的类型参数。
  • 它定义了一个或多个作为参数的类型变量。
  • 通用接口声明定义了一组类型,每个类型参数部分的可能调用都有一个类型。
  • 所有参数化类型在运行时共享相同的接口。
public InterfceName <T, E, ...>{
		public T methodName();
		...
}

拿Java的自带的变量类型Set举一个例子。Set就是泛型化编程的一个很好的例子,<E>可以是任何你想要存储的对象的类型,比如可以是Set<String>Set<Integer>等等,我们只需要设计并实现一个Set<E>。

Subtype polymorphism

 继承和子类型的好处是可以提高代码的复用性,也可以是建模更加灵活。一个类只能有一个父类,但是可以实现多个接口。

  • 子类型

如果说B是A的子类型,那么意味着每一个B都是一种A。从规约的角度来讲,每一个B都能符合A的规约。B是A唯一的子类型当且仅当B的规约强度至少和A的一样强的时候才会发生。

当我们声明一个实现接口的类时,Java 编译器会自动强制执行部分要求:它确保 A 中的每个方法都出现在 B 中,并具有兼容的类型签名。如果想在 Java 中声明子类型(例如,实现接口),则必须确保子类型的规约至少与父类型的规约一样强。

  • 子类型多态

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

Some important Object methods in Java

一般需要进行重写的Object方法

在Java中,Object类是所有类的父类,也就是说所有的类都能使用Object类中的方法,但是一般情况下需要按照自己的逻辑进行重写才能使用。

  • .equals()方法
    按照设定的逻辑如果两个对象是相等的就返回true
  • .hashCode()方法
    在哈希图中使用的哈希值
  • .toString()方法
    一个可打印的字符串表示,一般用于格式化输出

Overriding .toString()

从下面的示例可以看到toString()方法对输出进行了格式化,client在使用的时候,不消每一次都手动按照格式进行输入,而是由toString()方法在自动完成这个输出:

 

Overriding .equals()

下面的示例中对.equals()方法进行了重写。其实通过观察和经验可以发现,重写.equals()方法是有一定套路的:首先检查两个对象是否是同一个,如果是,则返回真;再看两个对象是否在同一个继承树上,如果不是则返回假;之后的判断就是自己定下的判断逻辑了。如下:

 

Overriding .hashCode()

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值