JAVA面向对象之继承与重写

194 阅读8分钟

这是我参与8月更文挑战的第8天,活动详情查看:8月更文挑战

文章目录

继承

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

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

几点说明:
1、子类拥有父类非 private 的属性、方法。而且子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。子类可以用自己的方式实现父类的方法。
2、构造方法不能被继承,可以通过使用super()进行调用。子类会默认调用父类的构造器,但是如果没有默认的父类构造器,子类必须要显示的指定父类的构造器(通过super()),而且必须是在子类构造器中第一行代码调用。
3、Java 的继承是单继承,即一个子类只能有一个父类,但一个父类可以有多个子类.
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关键字的介绍。
在这里插入图片描述

关于继承的几点说明

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;
    }
}

再次运行程序:
在这里插入图片描述

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

public class Employee extends Human {
    String company;

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

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

重载和重写的区别

区别点重载方法重写方法
参数列表必须修改一定不能修改
返回类型可以修改一定不能修改
异常可以修改可以减少或删除,一定不能抛出新的或者更广的异常
访问可以修改一定不能做更严格的限制(可以降低限制)

Super 关键字

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

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

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

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

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

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

首先创建一个 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());
    }
}

运行程序:
在这里插入图片描述

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

我们可以在刚才程序的基础上进行拓展。三维坐标中相比二维坐标多了一个 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");
    }
}

运行程序:
在这里插入图片描述