文章目录

继承

面向对象的三个基本特征是:封装、继承、多态。接下来的几篇文章会分别介绍这三个基本特征。今天首先学习 继承。继承使用关键字​​extends​​(extends 可以理解为扩展)

被继承的类称为父类/超类,即 Superclass,继承类称为子类。

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。通过继承可以实现代码重用。

继承是一种类与类之间的关系,利用一个已经存在的类去快速创建新的类的机制。

几点说明:
1、子类拥有父类非 private 的属性、方法。而且子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。子类可以用自己的方式实现父类的方法。(父类的 private 的属性能继承,但是子类继承过来之后,也不能对它进行直接引用访问,那是父亲的私有部分,只能对父类中的成员开放)
2、构造方法不能被继承,可以通过使用​​​super()​​​进行调用。子类会默认调用父类的构造器,但是如果没有默认的父类构造器,子类必须要显示的指定父类的构造器(通过​​super()​​​),而且必须是在子类构造器中第一行代码调用。
3、Java 的继承是单继承,即一个子类只能有一个父类,但一个父类可以有多个子类。如果没有显式的继承,则默认继承 Object类。

4、protected(受保护的访问权限修饰符),修饰的属性或方法可以被子类继承。

一堆理论可能看不懂,我们结合实际例子来学习

举例说明继承

人类范围包含学生和员工:
人类
    |——学生
    |——员工

我们创建一个人类 Human 类。人类的的属性有 姓名(name)、性别(gender)、年龄(age)。同时我们添加无参和有参构造方法,并分别打印信息。

public class Human {
String name;
String gender;
int age;

public Human() {
Log.d("继承", "Human()");
}

public Human(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
Log.d("继承", "Human(1,2,3)");
}

public String tostring() {
return "\n姓名:" + name +
"\n性别:" + gender +
"\n年龄:" + age;
}
}

我们创建一个学生类 Student 类。Student 类除了拥有人类的属性,还特有属性 学校(school)

public class Student extends Human {
String school;
}

我们创建一个员工类 Employee 类。Employee 除了拥有人类属性,还拥有特有属性 工作单位(company)

public class Employee extends Human {
String company;
}

xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="human" />

<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="student" />

<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="imployee" />

<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#222222"
android:textSize="18sp" />

</LinearLayout>

MainActivity

public class MainActivity extends AppCompatActivity {
Button button1;
Button button2;
Button button3;
TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

button1 = findViewById(R.id.button1);
button2 = findViewById(R.id.button2);
button3 = findViewById(R.id.button3);
textView = findViewById(R.id.textView);
}

public void doClick(View view) {
switch (view.getId()) {
case R.id.button1:
f1();
break;
case R.id.button2:
f2();
break;
case R.id.button3:
f3();
break;
}
}

private void f1() {
Human human = new Human("美伢", "女", 18);
textView.setText(human.tostring());
}

private void f2() {
Student student = new Student();
student.name = "小A";
student.gender = "男";
student.age = 10;
student.school = "北大";
textView.setText(student.tostring());
}

private void f3() {
Employee employee = new Employee();
employee.name = "小B";
employee.gender = "女";
employee.age = 22;
employee.company = "冀春";
textView.setText(employee.tostring());
}
}

运行程序:
【达内课程】面向对象之继承与重写_继承
同时我们可以看到 Log 的输出。说明创建子类对象时默认调用的父类的无参构造方法。更具体的说明可以查看下边关于 ​​​super​​​关键字的介绍。
【达内课程】面向对象之继承与重写_父类_02

关于继承的几点说明

1、继承的初始化顺序
①、先初始化父类再初始化子类
②、先执行初始化对象中属性,再执行构造方法中的初始化。

基于上面两点,我们就知道实例化一个子类,java程序的执行顺序是:
父类对象属性初始化---->父类对象构造方法---->子类对象属性初始化—>子类对象构造方法

用我们开始的例子来说,当创建 Student 类时,实际上是先创建了 Human 类(先创建父类然后创建了子类)。Student 类和 Human 类组成了完整的Student 类。
2、子类调用成员变量,先找子类,再找父类

例如,当我们调用 ​​student.name​​会先查找子类,看子类中是否有 name 属性,如果没有,则查找父类。

3、子类默认调用父类无参构造方法

我们可以给 Student 类中的增加构造方法,不管是无参构造方法还是有参构造方法,都默认调用了父类的无参构造方法:

public Student(String name,String gender,int age,String school){
//super();//默认是有这句的
this.name = name;
this.gender = gender;
this.age = age;
this.school = school;
}

public Student(){
//super();//默认是有这句的,默认调用的父类无参构造方法
}

4、子类也可以手动调用父类有参构造方法

例如 Student 构造方法中手动调用父类有参构造方法:

public Student(String name,String gender,int age,String school){
super(name,gender,age);
this.school = school;
}

public Student(){

}

方法重写Override

在实现继承关系后,在子类中重写父类中已经定义的方法,或在类实现了接口,实现接口中的抽象方法,即为重写。

当继承的方法,不满足子类需要,可以在子类中重新编写这个方法。就像上边例子中,Human 中有 ​​tostring()​​​ 方法来打印姓名、性别、年龄。但学生类中的特殊属性 学校(school)和员工类中的特殊属性 工作单位(company)并没有打印出来,所以我们需要重写 ​​tostring()​​ 方法。

如果再子类中重新编写​​tostring()​​​方法,那么子类再调用​​tostring()​​​方法,就直接执行子类中的​​tostirng()​​方法

Student类

public class Student extends Human {
String school;

public String tostring() {
return "\n姓名:" + name +
"\n性别:" + gender +
"\n年龄:" + age +
"\n学校:" + school;
}
}

Employee 类

public class Employee extends Human {
String company;

public String tostring() {
return "\n姓名:" + name +
"\n性别:" + gender +
"\n年龄:" + age +
"\n公司:" + company;
}
}

再次运行程序:
【达内课程】面向对象之继承与重写_继承_03

重写父类方法时,可以使用​​super.xx();​​调用父类同一个方法的代码,这样就避免了代码重复,例如:

public class Employee extends Human {
String company;

public String tostring() {
return super.tostring() +
"\n公司:" + company;
}
}

几点说明:
1、返回值类型、方法名参数类型及个数都要与父类继承的方法相同,才叫方法的重写。
2、父类中的方法若使用private、static、final任意修饰符修饰,那么,不能被子类重写。这几个关键字在后边的文章中会介绍到
3、重写的方法不可以使用更严格的访问权限
4、重写的方法不可以抛出更多的异常

重载和重写的区别

区别点

重载方法

重写方法

参数列表

必须修改

一定不能修改

返回类型

可以修改

一定不能修改

异常

可以修改

可以减少或删除,一定不能抛出新的或者更广的异常

访问

可以修改

一定不能做更严格的限制(可以降低限制)

Super 关键字

可以理解为对父类的引用,使用 super 来调用父类的属性、方法和构造方法。

关于构造方法

我们知道子类的构造的过程当中必须调用父类的构造方法。其实这个过程已经隐式地使用了我们的​​super​​关键字。这是因为如果子类的构造方法中没有显示调用父类的构造方法,则系统默认调用父类无参的构造方法。

如果自己用 super 关键字在子类里调用父类的构造方法,则必须在子类的构造方法中的第一行。

要注意的是:如果子类构造方法中既没有显示调用父类的构造方法,而父类没有无参的构造方法,则编译出错。这时有两种方法处理
1、给父类添加无参构造方法
2、在子类中手动调用父类的有参构造方法,例如:​​​super(参数);​

(特别说明:如果你声明了一个有参的构造方法,而没有声明无参的构造方法,这时系统不会动默认生成一个无参构造方法,此时称为父类有没有无参的构造方法)

关于属性

如果子类中声明了和父类中相同的属性,则父类中的属性会被隐藏。不能直接访问父类的属性,想要访问必须使用 super。

public class Animal{
String name = "动物";
}

public class Dog extends Animal {
String name = "修勾";

public String getName() {
return "父类:" + super.name + "\n" + "子类:" + name;
}
}

Dog dog = new Dog();
dog.getName()

运行结果:

父类:动物
子类:修勾

练习:二维坐标中,求点到原点的距离

首先创建一个 Point 类,用 x 和 y 表示坐标点

public class Point {
int x;
int y;

public Point(int x, int y) {
this.x = x;
this.y = y;
}

public String tostring() {
return "(" + x + "," + y + ")";
}

public double distance() {
return Math.sqrt(x * x + y * y);
}

public double distance(Point p) {
x = p.x;
y = p.y;
return distance();
}
}

xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<EditText
android:id="@+id/xEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="x" />

<EditText
android:id="@+id/yEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="y" />

<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="到原点的距离" />

<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#222222"
android:textSize="18sp" />

</LinearLayout>

MainActivity

public class MainActivity extends AppCompatActivity {
EditText xEditText;
EditText yEditText;
Button button1;
TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

xEditText = findViewById(R.id.xEditText);
yEditText = findViewById(R.id.yEditText);
button1 = findViewById(R.id.button1);
textView = findViewById(R.id.textView);
}

public void doClick(View view) {
switch (view.getId()) {
case R.id.button1:
f1();
break;
}
}

private void f1() {
String x = xEditText.getText().toString();
String y = yEditText.getText().toString();
Point point = new Point(Integer.parseInt(x), Integer.parseInt(y));
textView.setText(point.tostring() + "\n" + point.distance());
}
}

运行程序:
【达内课程】面向对象之继承与重写_android_04

练习:三维坐标中,点到原点的距离

我们可以在刚才程序的基础上进行拓展。三维坐标中相比二维坐标多了一个 z 轴,所以我们可以创建一个 Point3D 类来继承 Point 类

Point3D类

public class Point3D extends Point {
int z;

public Point3D(int x, int y) {
super(x, y);
}

public Point3D(int x, int y, int z) {
super(x, y);
this.z = z;
}

@Override
public String tostring() {
return "(" + x + "," + y + "," + z + ")";
}

@Override
public double distance() {
return Math.sqrt(x * x + y * y + z * z);
}

@Override
public double distance(Point p) {
return super.distance(p);
}
}

xml 在原来按钮下面增加输入 z 轴坐标的 Edittext 和 求到原点距离的Button

<EditText
android:id="@+id/zEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="z" />

<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="doClick"
android:text="3d" />

MainActivity 中我们修改原来的代码,第一个按钮除了求坐标点到原点的距离,还可以求到 随机坐标的距离。同时增加了三维坐标中的点到原点距离的方法:

public class MainActivity extends AppCompatActivity {
EditText editText1;
EditText editText2;
EditText editText3;
Button button1;
Button button2;
TextView textView;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

editText1 = (EditText) findViewById(R.id.xEditText);
editText2 = (EditText) findViewById(R.id.yEditText);
editText3 = (EditText) findViewById(R.id.zEditText);
button1 = (Button) findViewById(R.id.button1);
button2 = (Button) findViewById(R.id.button2);
textView = (TextView) findViewById(R.id.textView);
}

public void doClick(View view) {
switch (view.getId()) {
case R.id.button1:
f1();
break;
case R.id.button2:
f2();
break;
}
}

private void f2() {
int x = Integer.parseInt(editText1.getText().toString());
int y = Integer.parseInt(editText2.getText().toString());
int z = Integer.parseInt(editText3.getText().toString());
Point3D point3D = new Point3D(x, y, z);
textView.append(point3D.tostring() + "距原点距离" + point3D.distance() + "\n");
}

private void f1() {
int x = Integer.parseInt(editText1.getText().toString());
int y = Integer.parseInt(editText2.getText().toString());
Point point1 = new Point(x, y);
textView.setText(point1.tostring() + "距原点距离" + point1.distance() + "\n");

Point point2 = new Point(new Random().nextInt(10), new Random().nextInt(10));
textView.append(point1.tostring() + "距离" + point2.tostring() + "距离为:" + point1.distance(point2) + "\n");
}
}

运行程序:
【达内课程】面向对象之继承与重写_父类_05