面向对象(二)

继承

在前面的学习我们可以知道,类是对某一批对象的抽象,而继承的本质是对某一批类的抽象,类比于现实世界中对动植物进行界门纲目科属种的逐级分类。

子类(派生类)是父类(基类)的拓展,子类继承父类,我们用extends这个关键词表示继承的关系。(extends v.拓展)子类与父类之间具有一个is的关系,比如学生(子类)是人(父类)。

子类可以继承父类所有public的属性与方法,而父类private的属性与方法,子类是无法继承的。当我们需要让子类继承父类的属性与方法,且又不被继承关系以外的访问到的时候,我们就需要用到protected关键词,让子类能够继承父类使用protected的属性与方法。

在Java里,所有的类,都默认直接或者间接继承了Object类。

注意,在Java里,只有单继承,无多继承,一个子类只能有一个父类。

tips:在IDEA里,ctrl+h快捷键可以打开继承树,可以很直观看到继承关系。

例:

main:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class test {
public static void main(String[] args) {
Student student1 = new Student();

student1.setName("XiaoMing");
student1.setAge(17);
student1.setID(810);
student1.setGrade(99);//无参构造

Student student2 = new Student("XiaoHong", 16, 114, 100);//有参构造

System.out.println(student1.getName()+" "+student1.getAge()+" "+student1.getID()+" "+student1.getGrade());//out:XiaoMing 17 810 99
System.out.println(student2.getName()+" "+student2.getAge()+" "+student2.getID()+" "+student2.getGrade());//out:XiaoHong 16 114 100
}
}

Person:(父类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Person {
protected String name;
protected int age;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}

Student:(子类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Student extends Person{
private int grade;
private int ID;
public Student(){}
public Student(String aName, int aAge, int aID, int aGrade){
name = aName;
age = aAge;//子类继承了父类的属性
ID = aID;
grade = aGrade;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public int getID() {
return ID;
}
public void setID(int ID) {
this.ID = ID;
}
}

super

super关键词可以调用父类的属性与方法,用来引用当前对象的父类。

直接进入例子:

main:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class test {
public static void main(String[] args) {
Student student = new Student();
/*out:
父类无参构造被执行
子类无参构造被执行
*/
student.output();
/*out:
Student
Student
Person
*/
}
}

Person(父类):

1
2
3
4
5
6
7
8
public class Person {
public Person(){
System.out.println("父类无参构造被执行");
}
public void print(){
System.out.println("Person");
}
}

Student(子类):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Student extends Person{
public Student(){
//由于子类是不继承父类的构造器,所以这里隐藏着调用父类构造器:super();
//注意:调用父类构造器时,必须写在子类构造器的第一行
System.out.println("子类无参构造被执行");
}
public void print(){
System.out.println("Student");
}
public void output(){
print();
this.print();
super.print();
}
}

注意点

  1. super只能出现在子类的方法或者构造器中
  2. 当父类没有无参构造时,子类就无法调用父类无参构造,也就是说必须要在子类构造器内写上父类的有参构造。

例:

1
2
3
4
5
6
7
8
9
10
//Person:
public class Person {
public Person(String name){}
}
//Student:
public class Student extends Person{
public Student(){
super("name");
}
}

方法重写

子类可以重写父类的方法。

方法重写的特点:

  1. 方法名必须相同
  2. 参数列表必须相同(与方法重载不同)
  3. 修饰符:范围可以扩大,不可以缩小。

tips:在IDEA里,ctrl+inside快捷键可以快速调用方法重写功能。

不能重写的方法:

  1. static 方法;
  2. private 方法;
  3. final 常量;

main:

1
2
3
4
5
6
public class test {
public static void main(String[] args) {
B b = new B();
b.output(); //out:B-out
}
}

A(父类):

1
2
3
4
5
public class A {
protected void output(){
System.out.println("A-out");
}
}

B(子类):

1
2
3
4
5
6
public class B extends A{
@Override
public void output() {//修饰符范围可以扩大
System.out.println("B-out");
}
}

多态

多态的定义

同一个行为具有多个不同表现形式或形态的能力。

比如在现实世界中,猫和狗(子类)都是动物(父类),他们都会吃(父类继承的方法),但是猫吃鱼,狗吃骨头(方法重写)。

多态存在的三个必要条件

  1. 继承
  2. 重写
  3. 父类引用指向子类对象Person s2 = new Student();

Demo1:

1
2
3
4
5
6
7
8
public class Demo {
public static void main(String[] args) {
Pet pet1 = new Dog();
Pet pet2 = new Cat();
pet1.eat();//Eat:bone
pet2.eat();//out:Eat fish
}
}

Pet(父类):

1
2
3
4
5
public class Pet {
public void eat(){
System.out.println("Eat All");
}
}

Dog(子类1):

1
2
3
4
5
6
public class Dog extends Pet {
@Override
public void eat() {
System.out.println("Eat bone");;
}
}

Cat(子类2):

1
2
3
4
5
6
public class Cat extends Pet {
@Override
public void eat() {
System.out.println("Eat fish");
}
}

多态的作用

提高了代码的拓展性,使用父类类型作为方法形式参数,传递子类对象给方法,进行方法的调用。

听起来很难理解,我们来看例子就很容易理解这个意思的。

依然使用上面例子的三个类PetDogCat

Demo2:

1
2
3
4
5
6
7
8
9
10
11
public class Demo {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
petEat(dog);//out:Eat bone
petEat(cat);//out:Eat fish
}
public static void petEat(Pet pet){
pet.eat();
}
}

多态的"局限"

当某个子类拥有独有的方法的时候,我们多态的写法就无法访问子类独有的方法了。

我们重写一下Cat类:

1
2
3
4
5
6
7
8
9
public class Cat extends Pet {
@Override
public void eat() {
System.out.println("Eat fish");
}
public void playBall(){
System.out.println("Cat is playing Ball");
}
}

Demo:

1
2
3
4
5
6
7
8
9
public class Demo {
public static void main(String[] args) {
Pet cat = new Cat();
cat.eat();//out:Eat fish
cat.playBall();//error!这个编译是没法通过的
//Pet类本身是没有playBall()这个方法的,因此是没法通过编译的。
//在使用多态时,编译看左边,运行看右边。
}
}

类型转换

instanceof关键词可以用来测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。

这种比较需要两边存在关系,否则连编译都没法通过。

1
2
3
4
5
6
7
8
9
public class Demo {
public static void main(String[] args) {
Object object = new Cat();
System.out.println(object instanceof Cat);//out:true
System.out.println(object instanceof Pet);//out:true
System.out.println(object instanceof Object);//out:true
System.out.println(object instanceof Dog);//out:false
}
}

我们在之前的学习中,我们知道,基本数据类型之间的转换,低转高可以自动转化,高转低需要强制转化,在多态的类型转化也是如此。

多态本身是子类向父类向上转换(自动转换)的过程,这个过程是默认的,因此上文提到的“局限”就出现了,所以我们需要一种强制转化的方法来是对象可以调用子类独有的方法。

1
2
3
4
5
6
7
public class Demo {
public static void main(String[] args) {
Pet cat = new Cat();
//将cat对象转化为Cat类型,就可以使用playBall()这个属于Cat类的方法了。
((Cat) cat).playBall();
}
}

这种强制转化的方法,也可以使得子转父,但是在子转父的时候,我们就会损失子类的方法。