Day 07
面向对象特征之二:继承
概念
- 为什么要有继承?
多个类中存在相同属性和行为时,将这些内容抽象到单独一个类中,多个类无需再定义这些属性和行为,只要继承那个类即可。
此处的多个类称为子类,单独的这个类称为父类(基类或超类)。
继承
-
类继承语法规则:
class Subclass extends Superclass{ } -
子类继承了父类,就继承了父类的方法和属性(分情况)。
在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展”。 -
继承的规则:
子类不能直接访问父类中私有的(private)的成员变量和方法。
-
Java中,子类继承父类什么?
- 继承 public 和 protected 修饰的属性和方法,不论子类和父类是否在同一个包;
- 继承缺省的属性和方法,前提是子类和父类必须在同一个包。
- 注意:
- Java只支持单继承,不允许多重继承
- 一个子类只能有一个父类
- 一个父类可以派生出多个子类
- class SubDemo extends Demo{ } //ok
class SubDemo extends Demo1,Demo2…//error
- 单继承举例:
练习
- 定义一个ManKind类,包括成员变量int sex和int salary;
- 方法void manOrWorman():根据sex的值显示“man”(sex== 1)或者“women”(sex==0);
- 方法void employeed():根据salary的值显示“no job”(salary==0)或者“ job”(salary!=0);
- 定义类Kids继承ManKind,并包括成员变量int yearsOld;方法printAge()打印yearsOld的值;
- 在Kids类的main方法中实例化Kids的对象someKid,用该对象访问其父类的成员变量及方法。
package practice2;
public class ManKind {
int sex;
double salary;
public ManKind(int sex, double salary) {
this.sex = sex;
this.salary = salary;
}
public void manOrWoman() {
if(this.sex == 0) {
System.out.println("man");
}else {
System.out.println("woman");
}
}
public void employeed() {
if(this.salary == 0) {
System.out.println("no job");
}else {
System.out.println("job");
}
}
}
package practice2;
public class Kids extends ManKind{
int yearsold;
public Kids(int sex, double salary, int yearsold) {
super(sex, salary);
this.yearsold = yearsold;
}
public void printAge() {
System.out.println(this.yearsold);
}
public static void main(String[] args) {
Kids somekid = new Kids(0, 150000, 27);
somekid.manOrWoman();
somekid.employeed();
somekid.printAge();
}
}
方法的重写
-
概念
在子类中可以根据需要对从父类中继承来的方法进行改造,也称方法的重置、覆盖。在程序执行时,子类的方法将覆盖父类的方法。 -
要求:
(1)重写方法必须和被重写方法具有相同的方法名称、参数列表和返回值类型,即只改变方法体内容。
(2)重写方法不能使用比被重写方法更严格的访问权限。
(3)重写和被重写的方法须同时为static的,或同时为非static的
(4)子类方法抛出的异常不能大于父类被重写方法的异常 -
与重载的区别:
(1)重载时,不关注返回值类型,参数列表必须不同(参数个数、参数顺序、参数类型(构造方法重载除外));
(2)重写时,除了方法体中的内容不同以外,其他部分都相同;
(3)方法重载,指一个类可以有多个同名方法,发生在同一个类内部;
(4)方法重写,指子类可以重写父类的方法,覆盖父类的方法,发生的父子类中。 -
注意:
重写不代表父类被重写的方法不可使用,例如父类Person的showInfo()方法被子类Student重写,此时p.showInfo();调用的是父类中的showInfo()方法,s.showInfo();调用的是子类重写的showInfo()方法。 -
举例1:
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
public class Student extends Person {
public String school;
public String getInfo() {//重写方法
return "Name: "+ name + "\nage: "+ age + "\nschool: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
System.out.println(s1.getInfo()); //Name:Bob age:20 school:school2
}
}
- 举例2:
class Parent {
public void method1() {}
}
class Child extends Parent {
private void method1() {}
//非法,子类中的method1()的访问权限private比被覆盖方法的访问权限public弱
}
- 四种访问权限修饰符:
修饰符 | 类内部 | 同一个包 | 子类 | 其他 |
---|---|---|---|---|
private | √ | |||
(缺省) | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
结论:
- 如果子类和父类在同一个包下,则子类可以访问和调用父类中除了 private 以外的任何属性和方法;
- 如果子类和父类不在同一个包下,则子类只可以访问和调用父类中的 public 和 protected 修饰的属性和方法。
super关键字
-
作用:
(1)访问父类中的属性;
(2)调用父类中的方法:
(3)在子类构造方法中调用父类的构造方法。 -
注意:
(1)当子父类出现同名成员时,可以用super进行区分;
(2)super的追溯不仅限于直接父类(C继承B,B继承A,在C中使用super可以引用A的属性和方法);
(3)super和this的用法相似,this代表本类对象的引用,super代表父类对象的引用。 -
举例:
package test;
public class TestStudent {
public static void main(String[] args){
Student st = new Student();
System.out.println(st.getInfo());
}
}
class Person {
protected String name="张三";
protected int age;
public String getInfo() {
return "Name: " + name + "\nage: " + age;
}
}
class Student extends Person {
protected String name = "李四";
private String school = "New Oriental";
public String getSchool(){
return school;
}
public String getInfo() {
return super.getInfo() +"\nschool: " +school;//调用的是父类方法, 父类中的Name是张三
}
}
/*Name: 张三
age: 0
school: New Oriental*/
- 调用父类构造器
(1)子类中所有的构造器默认都会访问父类中的无参构造器; 即所有子类构造器的第一行包含一条隐式语句super();。
(2)当父类中没有无参构造器时,子类的所有构造器都必须通过 super() 或 this() 语句指定调用父类或子类中的构造器,且必须放在构造器的第一行,super() 或 this() 只能包含其中一个; 注意,使用 this() 调用本类构造器时,要保证至少有一个子类构造器调用了父类构造器。
(3)若使用 super() 或 this() 显示的调用了父类或子类中的构造器,则该构造器内不会再添加默认 super()。
(4)如果子类构造器中未显式调用父类或本类的构造器,且父类中又没有无参构造器,则编译出错。
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String name, int age, Date d) {
this.name = name;
this.age = age;
this.birthDate = d;
}
public Person(String name, int age) {
this(name, age, null);
}
public Person(String name, Date d) {
this(name, 30, d);
}
public Person(String name) {
this(name, 30);
}
}
public class Student extends Person {
private String school;
public Student(String name, int age, String s) {
super(name, age);
school = s;
}
public Student(String name, String s) {
super(name);
school = s;
}
public Student(String s) { // 编译出错: no super(),
school = s; //系统将调用父类的无参构造器,但父类中并无无参构造器
}
}
this 和 super的区别
\ | 区别 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
4 | 特殊 | 表示当前对象 | \ |
一般对象的实例化过程
子类对象的实例化过程
面向对象特征之三:多态
Java中的体现
- 方法的重载(overload)和重写(overwrite)。
- 对象的多态性 ——可以直接应用在抽象类和接口上。
对象的多态性
.在Java中,父类引用的实例可以是它的子类(形参是Person类,传入的实参可以是Student类)。
1. 普通成员方法的调用:
- Person p = new Student(); // Person类型的变量p,指向Student类型的对象。 可以理解为所有的学生都是人,因此可以用Person类接收Student对象,但是人不一定都是学生,因此不能用Student类接收Person对象。
- 使用这种声明方式后,由于引用变量是Person类型,因此p只能调用父类中的方法(若已被子类重写,则调用子类中重写的方法),而不能调用子类特有的方法,如需调用子类特有的方法,要进行强制类型转换。
- 原因:编译时检查父类中是否有被调用的方法,若没有则编译失败;运行时检查子类是否重写方法,若重写则调用重写方法。简单一句话,编译时看左边,运行时看右边。
2. 静态方法的调用:
- Person p = new Student(); 若子类重写了父类的静态方法,则使用p调用该静态方法时,执行的依然是父类中的静态方法,即编译和运行都看左边。
3. 成员属性的访问:
- Person p = new Student(); 无论要访问的属性是否为静态,p只能访问父类中的成员属性,即编译和运行都看左边。
举例
package test;
public class TestAnimal {
public static void main(String[] args) {
Animal a = new Cat();
Function.eatFunc(a);
Function.speakFunc(a);
}
}
abstract class Animal{
//动物叫声
public abstract void speak();
//动物食物
public abstract void eat();
//动物进食
}
class Cat extends Animal{
@Override
public void speak() {
System.out.println("cat miaomiaomiao");
}
@Override
public void eat() {
System.out.println("cat eat fish");
}
}
class Dog extends Animal{
@Override
public void speak() {
System.out.println("dog wangwangwang");
}
@Override
public void eat() {
System.out.println("dog eat rice");
}
}
class Function{
public static void speakFunc(Animal a) {
a.speak();
}
public static void eatFunc(Animal a) {
a.eat();
}
}
instanceof 操作符
-
语法格式:
x instanceof A:检验x是否为类A(或A的子类B)的对象,返回值为boolean型。 -
注意:
(1)要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
(2)如果x属于类A的子类B,x instanceof A值也为true。
(3)判断的是引用x所指代的堆内存是否是类A的对象(关注new后面的内容)。 -
举例:
public class Person {…}
public class Student extends Person {…}
public class Graduate extends Person {…}
public void method1(Person e) {
if (e instanceof Person) {
// 处理Person类及其子类对象(Person,Student,Graduate)
}
if (e instanceof Student) {
//处理Student类及其子类对象(Student,Graduate)
}
if (e instanceof Graduate){
//处理Graduate类及其子类对象(Graduate)
}
}
- 判断:
Person p = new Student();
System.out.println(p instanceof Person);
System.out.println(p instanceof Student);
结果:true true
原因:p中存放的堆内存是Student类的,故结果都为true。
Object类
概念
- Object类是所有Java类的根父类
- 如果在类的声明中未使用extends关键字指明其父类,则默认父类为Object类
public class Person {
…
}
等价于:
public class Person extends Object {
…
} - 例:
method(Object obj){…}//可以接收任何类作为其参数
Person o=new Person();
method(o);
Object类中的主要方法
\ | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Object() | 构造 | 构造方法 |
2 | public boolean equals(Object obj) | 普通 | 对象比较 |
3 | public int hashCode() | 普通 | 获取Hash码 |
4 | public String toString() | 普通 | 打印对象的堆内存地址 |
注意:关于equals()方法,后面有详细介绍。
Person p = new Person();
Person e = new Person();
System.out.println(p.equals(e));
结果:false,p与e的堆内存地址不同,不管p与e存放的内容是否相同,只比较堆内存地址,故返回false。