面向对象-继承
学习内容
1. 继承有什么作用
基本作用:子类继承父类,代码得到复用
主要作用:继承是方法覆盖的重写和多态机制的前提(有了继承关系才有后来的方法覆盖重写和多态机制)
2. 继承的特点
- 若A继承B,我们称B为父类(基类、超类),称A为子类(派生类)。
- 在Java中只支持单继承,这也是体现java简单性的一点。
- 虽然Java不支持多继承,但可以实现间接继承(A–>B–>C)。
- 子类可以拥有父类“特征”,子类也可以拥有自己的“特征”。
- 如果一个类没有编写继承关系,那么这个类默认继承Object(是祖宗类,也就是说所有的类的实例都有Objec类t的特征)。
- 继承也有缺点:在两个类实现继承关系后,会导致代码耦合度提高(若父类修改内容,会导致子类收到影响)
3. 什么条件下可以使用继承?
凡是能用“is a“(类与类之间的关系)能描述的类,都可以使用继承
Cat is Animal -----> class Cat extends Animal
Dog is Animal -----> class Dog extends Animal
Monkey is Animal -----> class Monkey extends Animal
4. 方法的覆盖(重写)
方法重写与方法重载区别
方法重载
-
什么时候会考虑使用方法重载?
在一个类中,如果方法的功能相似,建议将取相同的名字,这样代码美观、便于编程
-
什么条件满足后会构成方法重载?
- 前提:在同一个类中
- 方法名相同
- 参数列表不同(参数的个数,参数的类型,参数类型的顺序)
- 方法重载于修饰符列表和返回值无关
方法重写
-
什么时候会考虑使用方法重写?
当子类从父类继承过来的方法无法满足当前子类的业务需求时,子类有权利对这个方法进行重新编写 -
什么条件满足后会构成方法重写?
- 前提:在继承关系中
- 方法名和参数列表都相同
- 访问权限不能更低,可以更高
- 子类返回值类型必须小于等于父类返回值类型
- 子类不能比父类抛出更宽泛的异常
方法重写经典案例
public class Test2 {
public static void main(String[] args){
EnglishPerson enPer = new EnglishPerson();
ChinaPerson cPer = new ChinaPerson();
enPer.speak();
cPer.speak();
}
}
class Person {
String name; //姓名
String skinColor; //肤色
public void speak() {
System.out.println("人在讲话");
}
}
class EnglishPerson extends Person{
public void speak() {
System.out.println("英国人讲话说英语");
}
}
class ChinaPerson extends Person {
public void speak() {
System.out.print("中国人讲话说汉语");
}
}
上面的代码子类重写了父类的方法
引子——println()输出对象为什么得到的是内存地址的哈希值
package com.jsoft.test;
public class UserTest {
public static void main(String[] args){
User user = new User("admin","123");
System.out.println(user);
}
}
class User {
String name; //用户名
String password; //密码
public User(){}
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
对于上面的程序运行结果是这样的:完整类名@地址哈希值
我们去println()方法中看看这个方法具体是怎么实现的(我们以Java8为例)
可以看到println()调用了String.valueOf()这个静态方法,继续进入到String.valueOf()看一下
我们可以看到当String.valueOf()方法中的参数是一个Object时,会调用这个Object的toString()方法,也就是说当println()中的参数是Object时底层会调用这个Object的toString()方法,那么我们会在下一个内容中,尝试重写一下这个User类的toString()看看
重写从Object类中继承过来的方法
重写toString()
package com.jsoft.test;
public class UserTest {
public static void main(String[] args){
User user = new User("admin","123");
System.out.println(user);
}
}
class User {
String name; //用户名
String password; //密码
public User(){}
public User(String name, String password) {
this.name = name;
this.password = password;
}
public String toString() {
return "用户名: " + name + "\t密码:" + password;
}
}
运行结果如下,可以看出通过重写toString()后使用println(),可以完成对对象打印的操作(toString()即返回对象的字符串表示形式)
重写equals()
为什么要重写equals(),首先我们来看一个案例
public class Test2 {
public static void main(String[] args){
User user1 = new User("user1","123");
User user2 = new User("user1","123");
//判断user1、user2是否相等
System.out.print("是否相等:" + (user1 == user2));
}
}
class User {
String name;
String password;
public User(){}
public User(String name, String password) {
this.name = name;
this.password = password;
}
}
对于这段代码的两个User对象用"=="进行比较是否相等?
从结果来看是不相等的,因为对于引用数据类型来说"=="比较的是两个对象的内存地址但是我们认为这两个User对象的所有属性的值都相等,可以认定这两个对象是相同的,那么该怎么做呢?
Object类中有一个用于比较两个对象是否相同的方法equals,可以通过重写这个equals方法来比较两个对象。如果不重写,默认使用Object类的equls方法,该方法对于非String类型的对象只会使用"=="运算符比较两个对象的内存地址,下图是Object类equals方法的源码(Java8)
对于String类中也重写了equals方法
所以我们需要重写equals方法,设置对象比较条件
public class Test2 {
public static void main(String[] args){
User user1 = new User("user1","123");
User user2 = new User("user1","123");
//判断user1、user2是否相等
System.out.print("是否相等:" + (user1.equals(user2)));
}
}
class User {
String name;
String password;
public User(){}
public User(String name, String password) {
this.name = name;
this.password = password;
}
//重写equals
public boolean equals(Object obj) {
if(this == obj) return true;
if(obj == null) return false;
User user = null;
if(obj instanceof User) {
user = (User)obj;
}
return this.name.equals(user.name) && this.password.equals(user.password);
}
}
代码运行结果如下
通过上述代码得出结论:我们可以通过重写equals()来界定User对象的比较内容
5. this与super
this
- this是关键字,是一个引用保存了对象自身的地址,存储在对象内部,代表对象本身。
- this可以用在实例方法和构造方法中。
- this不能用在静态方法中
- this在大多数情况下是可以省略的——当局部变量和实例变量同名时不能省略
- 语法格式:this.属性名/方法名 | this()
super
- super是关键字,代表的是当前对象(this)的“父类型特征1”
- super可以用在实例方法和构造方法中(子类构造方法第的第一行有一个隐含的“super()”)
- super不能用在静态方法中
- super在大多数情况下是可以省略的——当子类对象的属性或实例方法和“父类型特征”相同时,需要使用super区分两者。
- 语法格式:super.属性名/方法名 | super() —> 初始化对象父类型特征
this和super内存分析
public class Test1 {
public static void main(String[] args){
Student stu1 = new Student();
Student stu2 = new Student("张三",18,1);
}
}
class Person {
String name; //姓名
int age; //年龄
public Person() {
//隐含的 super()
//隐含的 this.name = null;
//隐含的 this.age = 0;
}
public Person(String name, int age) {
//隐含的 super() ----> 调用Object的无参构造,创建对象时初始化Person对象的"父类特征(Object)"
this.name = name;
this.age = age;
}
}
class Student extends Person {
int no; //学号
public Student() {
//隐含的 super() ----> 调用Person的无参构造,创建对象时初始化Student对象的"父类特征(Person)"
//隐含的 this.no = 0;
}
public Student(String name, int age, int no) {
//此处调用父类有参构造,则此处将不再有super()
super(name,age);
this.no = no;
}
}
对于上面的代码的内存图
6. 构造方法中存在的隐含的操作
1.所有子类的构造方法的第一行都会有隐含的super(),用于调用父类缺省构造器,如果在子类中编写super()或this(),那么第一行隐含的super()将不复存在。
2.对于任何构造方法都会有隐含的赋值操作(给实例变量赋默认值),如果给实例变量赋值此处隐含的赋默认值操作将取消。
知识点:了解
掌握情况:了解
思维导图
子类对象的父类型特征是属于对象本身的 ↩︎