06 - 面向对象编程(中级部分二)

一、方法重写/覆盖(override)

基本介绍

方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法。


使用案例

public class Animal {
	public void cry() {
		System.out.println("动物叫唤..");
	}
}

public class Dog extends Animal{

	//1. 因为 Dog 是 Animal 子类
	//2. Dog 的 cry 方法和 Animal 的 cry 定义形式一样(名称、返回类型、参数)
	//3. 这时我们就说 Dog 的 cry 方法,重写了 Animal 的 cry 方法
	public void cry() {
		System.out.println("小狗汪汪叫..");
	}
}

注意事项

1)子类的方法的方法名称,形参列表,要和父类方法的方法名称,形参列表完全一样。

2)子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类
	比如:父类返回类型为Object,子类返回类型是String
	Public Object getInfo() { }
	public String getInfo() { }

3)子类方法不能缩小父类方法的访问权限
	public > protected > 默认 > private

重写(override)和重载(overload)的区别

名称 发生范围 方法名 形参列表 返回类型 修饰符
重载(overload) 本类 必须一样 类型、个数或者顺序至少有一个不同 无要求 无要求
重写(override) 父子类 必须一样 相同 子类方法不能缩小父类方法的访问范围

二、面向对象编程-多态

基本介绍

方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。


多态的具体体现

1)方法的多态
public class PloyMethod {
	public static void main(String[] args) {
	
		//方法重载体现多态
		A a = new A();
		//这里我们传入不同的参数,就会调用不同 sum 方法,就体现多态
		System.out.println(a.sum(10, 20));
		System.out.println(a.sum(10, 20, 30));

		//方法重写体现多态
		B b = new B();
		a.say();
		b.say();
	}
}

class B { //父类
	public void say() {
		System.out.println("B say() 方法被调用...");
	}
}

class A extends B {//子类
	public int sum(int n1, int n2){
		//和下面 sum 构成重载
		return n1 + n2;
	}
	
	public int sum(int n1, int n2, int n3){
		return n1 + n2 + n3;
	}
	
	public void say() {
		System.out.println("Asay() 方法被调用...");
	} 
}

//结论:重写和重载就体现多态
2)对象的多态
	1 - 一个对象的编译类型和运行类型可以不一致
	2 - 编译类型在定义对象时,就确定了,不能被改变
	3 - 运行类型是可以变化的
	4 - 编译类型看定义时 =号的左边,运行类型看 =号的右边
	比如:
	Animal animal = new Dog();
	//animal的编译类型是Animal,运行类型是Dog
	animal = new Cat();
	//animal的运行类型变成了Cat,编译类型仍然是Animal

补充:编译类型和运行类型
	例如:Person person = new Student();
	这行代码将会生成一个person变量,该变量的编译时类型是Person,运行时类型是Student
	
	类在继承时,子类会覆盖与父类相同的属性
	
	总结一点就是:对象访问变量看声明,访问方法看实际对象类型(new出来的类型)
	
	java允许把一个子类对象直接赋值给一个父类引用变量,无须任何类型转换
	或者被称为向上转型,由系统自动完成
//例: 在一个主人类master中的喂食方法

Public void feed(Dog dog, Bone bone) {
	System.out.println(this.name + “给” + dog.getName() + “eat” + bone.getName());
}
Public void feed(Cat cat, Fish fish) {
	System.out.println(this.name + “给” + cat.getName() + “eat” + fish.getName());
}

//为了提高代码的复用性,减少冗余代码量,可使用以下方法代替:

Public void feed(Animal animal, Food food) {
	System.out.println(this.name + “给” + animal.getName() + “eat” + food.getName());
}

//利用多态的原理:
//animal变量的编译类型是Animal,可以指向(接收)Animal子类的对象
//food变量的编译类型是Food,可以指向(接收)Food子类的对象

多态注意事项

多态的前提是:两个对象(类)存在继承关系

  • 多态的向上转型

      	1)本质:父类的引用指向了子类的对象
      	2)语法:
      		父类类型 引用名 = new 子类类型();
      	3)特点:编译类型看左边,运行类型看右边。
    
//向上转型: 父类的引用 指向了 子类的对象
//语法:父类类型 引用名 = new 子类类型();
Animal animal = new Cat();
Object obj = new Cat();// Object 也是 Cat 的父类


//向上转型调用方法的规则如下:

//(1) 可以调用父类中的所有成员(需遵守访问权限)
//(2) 但是不能调用子类的特有的成员
//(3) 因为在编译阶段,能调用哪些成员,是由编译类型来决定的 //animal.catchMouse();错误
//(4) 最终运行效果看子类(运行类型)的具体实现,即调用方法时,按照从子类(运行类型)开始查找方法 //,然后调用,规则与前面讲的方法调用规则一致。

  • 多态的向下转型

      	1)语法:
      		子类类型 引用名 = (子类类型)父类引用;
      	2)只能强转父类的引用,不能强转父类的对象
      	3)要求父类的引用必须指向的是当前目标类型的对象
      	4)当向下转型后,可以调用子类类型中所有的成员
    
//多态的向下转型
//(1) 语法:子类类型 引用名 =(子类类型)父类引用; 
//(2) 要求父类的引用必须指向的是当前目标类型的对象
//cat 的编译类型 Cat,运行类型是 Cat 
Animal animal1 = new Cat();
Cat cat = (Cat) animal1;

//dog 的编译类型 Dog,运行类型是 Dog
Animal animal2 = new Dog();
Dog dog = (Dog) animal2;
  • 补充:

      	属性没有重写之说!属性的值看编译类型
    
      	instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型 或 XX 类型的子类型
    
class AA {} //父类
class BB extends AA {}//子类

BB bb = new BB();
System.out.println(bb instanceof BB);// true 
System.out.println(bb instanceof AA);// true 

//aa 编译类型 AA, 运行类型是 BB 
//BB 是 AA 子类 
AA aa = new BB();
System.out.println(aa instanceof AA); // true
System.out.println(aa instanceof BB); // true

Object obj = new Object(); 
System.out.println(obj instanceof AA);//false 
String str = "hello"; 
//System.out.println(str instanceof AA); 
System.out.println(str instanceof Object);//true


Java 的动态绑定机制

1)调用对象方法,方法会和该对象的 运行类型 / 内存地址 绑定.
2)调用对象属性,没有动态绑定机制,哪里声明,使用哪里的属性。

多态的应用

  1. 多态数组
    数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
// 应用实例:现有一个继承结构如下:要求创建 1 个 Person 对象、
// 让 Student 和 Teacher 都继承 Person。
// 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scott", 30, 20000);
persons[4] = new Teacher("king", 50, 25000);
  1. 多态参数
    方法定义的形参类型为父类类型,实参允许为子类类型。(后面我们使用接口编程,一般都是子类类型)

三、Object 类详解

1. equals 方法

equals 方法

== 和 equals 的对比
	== 是一个比较运算符
		1)既可以判断基本类型,又可以判断引用类型
		2)如果判断基本类型,判断的是 值是否相等
		示例:
		int i = 10; double d = 10.0;
		3)如果判断引用类型,判断的是 地址是否相等
		即判断是不是同一个对象
	
	equals 是 Object类中的方法
		1)equals 只能判断引用类型
		2)默认判断的是地址是否相等,子类中往往重写该方法
		用于判断内容是否相等
		比如Integer,String

String类 重写equals方法源码

/*
	//看看 Jdk 的源码,String 类的 equals 方法
	//把 Object 的 equals 方法重写了,变成了比较两个字符串值是否相同
*/
public boolean equals(Object anObject) {
	if (this == anObject) {		//如果是同一个对象
		return true;//返回 true
	}
	
	if (anObject instanceof String) {	//判断类型
		String anotherString = (String)anObject;	//向下转型
		int n = value.length;
		if (n == anotherString.value.length) {	//如果长度相同
			char v1[] = value;
			char v2[] = anotherString.value;
			int i = 0;
			while (n-- != 0) {	//然后一个一个的比较字符
				if (v1[i] != v2[i])
					return false;
				i++;
			}
			return true;	//如果两个字符串的所有字符都相等,则返回 true
		}
	}
	return false;	//如果比较的不是字符串,则直接返回 false
}

Object类 equals方法源码

//即 Object类的 equals 方法默认就是比较对象地址是否相同
//也就是判断两个对象是不是同一个对象.
public boolean equals(Object obj) {
	return (this == obj);
}

Integer类 重写equals方法源码

//从源码可以看到 Integer类 也重写了 Object类的 equals 方法
//变成了判断两个值是否相同
public boolean equals(Object obj) {
	if (obj instanceof Integer) {
		return value == ((Integer)obj).intValue();
	}
	return false;
}

尝试自己重写equals
假如我们有一个Person对象,有name,age,gender属性,只需要这三个值相同,那么我们就认为这个对象相等。

//重写 Object 的 equals 方法 
public boolean equals(Object obj) {
	//判断如果比较的两个对象是同一个对象,则直接返回 true 
	if(this == obj) {
		return true; 
	}
	
	//类型判断
	if(obj instanceof Person) {//是 Person,我们才比较
	//进行 向下转型, 因为我需要得到 obj 的 各个属性
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age == p.age && this.gender == p.gender;
	}
	//如果不是 Person ,则直接返回 false 
	return false;
}

2. hashCode 方法

基本介绍

1)提高具有哈希结构的容器的效率。
2)两个引用,如果指向的是同一个对象,则哈希值肯定是一样的。
3)两个引用,如果指向的是不同对象,则哈希值是不一样的。
4)哈希值主要根据地址号来的!,但不能完全将哈希值等价于地址。
5)后面在集合中,如果需要 hashCode 的话,也会重写。
public class HashCode_ {
	public static void main(String[] args) {
		AA aa = new AA();
		AA aa2 = new AA();
		AA aa3 = aa;
		
		System.out.println("aa.hashCode()=" + aa.hashCode());
		System.out.println("aa2.hashCode()=" + aa2.hashCode());
		System.out.println("aa3.hashCode()=" + aa3.hashCode());
	} 
}
class AA {}

3. toString 方法

基本介绍

1)默认返回:全类名+@+哈希值的十六进制
【查看 Object 的 toString 方法】
2)子类往往重写 toString 方法,用于返回对象的属性信息。
3)重写 toString 方法,打印对象或拼接对象时,都会自动调用该对象的 toString 形式。
4)当直接输出一个对象时,toString 方法会被默认的调用。
比如System.out.println(monster); 就会默认调用 monster.toString()

Object 的 toString() 源码

//(1)getClass().getName() 类的全类名(包名+类名)
//(2)Integer.toHexString(hashCode()) 将对象的 hashCode 值转成 16 进制字符串
public String toString() {
	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

4. finalize 方法

基本介绍

当对象被回收时,系统自动调用该对象的 finalize 方法。子类可以重写该方法,做一些释放资源的操作。
什么时候被回收:当某个对象没有任何引用时,则 jvm 就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来 销毁该对象,在销毁该对象前,会先调用 finalize 方法。
垃圾回收机制的调用,是由系统来决定(即有自己的 GC 算法), 也可以通过 System.gc() 主动触发垃圾回收机制。

//重写 finalize
@Override
protected void finalize() throws Throwable {
	System.out.println("我们销毁 汽车" + name);
	System.out.println("释放了某些资源...");
}

注:本博客引用韩顺平老师Java课程

上一篇:06-面向对象进级(面向对象)


下一篇:06 - 条件语句