java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java继承、封装与多态

新手初学Java继承、封装与多态

作者:有一个大佬梦

封装、继承、多态三大特征是java中比较常用的,务必要掌握,下面给大家介绍Java封装、继承、多态三大特征的理解,有不清楚的朋友可以一起学习下

面向对象的三大核心特性

面向对象开发模式更有利于人们开拓思维,在具体的开发过程中便于程序的划分,方便程序员分工合作,提高开发效率。面向对象程序设计有以下优点。

该开发模式之所以使程序设计更加完善和强大,主要是因为面向对象具有继承、封装和多态 3 个核心特性。

封装

封装将类的某些信息隐藏在类内部,不允许外部程序直接访问,只能通过该类提供的方法来实现对隐藏信息的操作和访问。例如:一台计算机内部极其复杂,有主板、CPU、硬盘和内存, 而一般用户不需要了解它的内部细节,不需要知道主板的型号、CPU 主频、硬盘和内存的大小,于是计算机制造商将用机箱把计算机封装起来,对外提供了一些接口,如鼠标、键盘和显示器等,这样当用户使用计算机就非常方便。

封装的特点:

实现封装的具体步骤如下:

下面以一个员工类的封装为例介绍封装过程。一个员工的主要属性有姓名、年龄、联系电话和家庭住址。假设员工类为 Employee,示例如下:

public class Employee {
    private String name; // 姓名
    private int age; // 年龄
    private String phone; // 联系电话
    private String address; // 家庭住址
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        // 对年龄进行限制
        if (age < 18 || age > 40) {
            System.out.println("年龄必须在18到40之间!");
            this.age = 20; // 默认年龄
        } else {
            this.age = age;
        }
    }
    public String getPhone() {
        return phone;
    }
    public void setPhone(String phone) {
        this.phone = phone;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

继承

继承是面向对象的三大特征之一。继承和现实生活中的“继承”的相似之处是保留一些父辈的特性,从而减少代码冗余,提高程序运行效率。

Java 中的继承就是在已经存在类的基础上进行扩展,从而产生新的类。已经存在的类称为父类、基类或超类,而新产生的类称为子类或派生类。在子类中,不仅包含父类的属性和方法,还可以增加新的属性和方法。

创建人类 People,并定义 name、age、sex、sn 属性,代码如下:

public class People {
    public String name; // 姓名
    public int age; // 年龄
    public String sex; // 性别
    public String sn; // 身份证号
    public People(String name, int age, String sex, String sn) {
        this.name = name;
        this.age = age;
        this.sex = sex;
        this.sn = sn;
    }
    public String toString() {
        return "姓名:" + name + "\n年龄:" + age + "\n性别:" + sex + "\n身份证号:" + sn;
    }
}

创建 People 类的子类 Student 类,并定义 stuNo 和 department 属性,代码如下:

public class Student extends People {
    private String stuNo; // 学号
    private String department; // 所学专业
    public Student(String name, int age, String sex, String sn, String stuno, String department) {
        super(name, age, sex, sn); // 调用父类中的构造方法
        this.stuNo = stuno;
        this.department = department;
    }
    public String toString() {
        return "姓名:" + name + "\n年龄:" + age + "\n性别:" + sex + "\n身份证号:" + sn + "\n学号:" + stuNo + "\n所学专业:" + department;
    }
}

由于 Student 类继承自 People 类,因此,在 Student 类中同样具有 People 类的属性和方法,这里重写了父类中的 toString() 方法。

注意:如果在父类中存在有参的构造方法而并没有重载无参的构造方法,那么在子类中必须含有有参的构造方法,因为如果在子类中不含有构造方法,默认会调用父类中无参的构造方法,而在父类中并没有无参的构造方法,因此会出错。

单继承

Java 语言摒弃了 C++ 中难以理解的多继承特征,即 Java 不支持多继承,只允许一个类直接继承另一个类,即子类只能有一个直接父类,extends 关键字后面只能有一个类名。

很多地方在介绍 Java 的单继承时,可能会说 Java 类只能有一个父类,严格来讲,这种说法是错误的,应该是一个类只能有一个直接父类,但是它可以有多个间接的父类。

继承的优缺点

在面向对象语言中,继承是必不可少的、非常优秀的语言机制,它有如下优点:

自然界的所有事物都是优点和缺点并存的,继承的缺点如下:

super关键字

由于子类不能继承父类的构造方法,因此,如果要调用父类的构造方法,可以使用 super 关键字。super 可以用来访问父类的构造方法、普通方法和属性。

super 关键字的功能:

super调用父类构造方法

super 关键字可以在子类的构造方法中显式地调用父类的构造方法,基本格式如下:

super(parameter-list);

其中,parameter-list 指定了父类构造方法中的所有参数。super( ) 必须是在子类构造方法的方法体的第一行。

声明父类 Person,类中定义两个构造方法。示例代码如下:

public class Person {
    public Person(String name, int age) {
    }
    public Person(String name, int age, String sex) {
    }
}

子类 Student 继承了 Person 类,使用 super 语句来定义 Student 类的构造方法。示例代码如下:

public class Student extends Person {
    public Student(String name, int age, String birth) {
        super(name, age); // 调用父类中含有2个参数的构造方法
    }
    public Student(String name, int age, String sex, String birth) {
        super(name, age, sex); // 调用父类中含有3个参数的构造方法
    }
}

从上述 Student 类构造方法代码可以看出,super 可以用来直接调用父类中的构造方法,使编写代码也更加简洁方便。

编译器会自动在子类构造方法的第一句加上super();来调用父类的无参构造方法,必须写在子类构造方法的第一句,也可以省略不写。通过 super 来调用父类其它构造方法时,只需要把相应的参数传过去。

super访问父类成员

当子类的成员变量或方法与父类同名时,可以使用 super 关键字来访问。如果子类重写了父类的某一个方法,即子类和父类有相同的方法定义,但是有不同的方法体,此时,我们可以通过 super 来调用父类里面的这个方法。

使用 super 访问父类中的成员与 this 关键字的使用相似,只不过它引用的是子类的父类,语法格式如下:

super.member

其中,member 是父类中的属性或方法。使用 super 访问父类的属性和方法时不用位于第一行。

当父类和子类具有相同的数据成员时,JVM 可能会模糊不清。我们可以使用以下代码片段更清楚地理解它。

class Person {
    int age = 12;
}
class Student extends Person {
    int age = 18;
    void display() {
        System.out.println("学生年龄:" + super.age);
    }
}
class Test {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.display();
    }
}

输出结果为:

学生年龄:12

在上面的例子中,父类和子类都有一个成员变量 age。我们可以使用 super 关键字访问 Person 类中的 age 变量。

当父类和子类都具有相同的方法名时,可以使用 super 关键字访问父类的方法。具体如下代码所示。

class Person {
    void message() {
        System.out.println("This is person class");
    }
}
class Student extends Person {
    void message() {
        System.out.println("This is student class");
    }
    void display() {
        message();
        super.message();
    }
}
class Test {
    public static void main(String args[]) {
        Student s = new Student();
        s.display();
    }
}

输出结果为:

This is student class

This is person class

在上面的例子中,可以看到如果只调用方法 message( ),是当前的类 message( ) 被调用,使用 super 关键字时,是父类的 message( ) 被调用

super和this的区别

this 指的是当前对象的引用,super 是当前对象的父对象的引用。下面先简单介绍一下 super 和 this 关键字的用法。

super 关键字的用法:

如果构造方法的第一行代码不是 this() 和 super(),则系统会默认添加 super()。

this 关键字的用法:

当局部变量和成员变量发生冲突时,使用this.进行区分。

关于 Java super 和 this 关键字的异同,可简单总结为以下几条。

多态

多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。

对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。

Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。

创建 Figure 类,在该类中首先定义存储二维对象的尺寸,然后定义有两个参数的构造方法,最后添加 area() 方法,该方法计算对象的面积。代码如下:

public class Figure {
    double dim1;
    double dim2;
    Figure(double d1, double d2) {
        // 有参的构造方法
        this.dim1 = d1;
        this.dim2 = d2;
    }
    double area() {
        // 用于计算对象的面积
        System.out.println("父类中计算对象面积的方法,没有实际意义,需要在子类中重写。");
        return 0;
    }
}

创建继承自 Figure 类的 Rectangle 子类,该类调用父类的构造方法,并且重写父类中的 area() 方法。代码如下:

public class Rectangle extends Figure {
    Rectangle(double d1, double d2) {
        super(d1, d2);
    }
    double area() {
        System.out.println("长方形的面积:");
        return super.dim1 * super.dim2;
    }
}

创建继承自 Figure 类的 Triangle 子类,该类与 Rectangle 相似。代码如下:

public class Triangle extends Figure {
    Triangle(double d1, double d2) {
        super(d1, d2);
    }
    double area() {
        System.out.println("三角形的面积:");
        return super.dim1 * super.dim2 / 2;
    }
}

创建 Test 测试类,在该类的 main() 方法中首先声明 Figure 类的变量 figure,然后分别为 figure 变量指定不同的对象,并调用这些对象的 area() 方法。代码如下:

public class Test {
    public static void main(String[] args) {
        Figure figure; // 声明Figure类的变量
        figure = new Rectangle(9, 9);
        System.out.println(figure.area());
        System.out.println("===============================");
        figure = new Triangle(6, 8);
        System.out.println(figure.area());
        System.out.println("===============================");
        figure = new Figure(10, 10);
        System.out.println(figure.area());
    }
}

从上述代码可以发现,无论 figure 变量的对象是 Rectangle 还是 Triangle,它们都是 Figure 类的子类,因此可以向上转型为该类,从而实现多态。

执行上述代码,输出结果如下:

长方形的面积:

81.0 ===============================

三角形的面积:

24.0 ===============================

父类中计算对象面积的方法,没有实际意义,需要在子类中重写。

0.0

instanceof关键字

严格来说 instanceof 是 Java 中的一个双目运算符,由于它是由字母组成的,所以也是 Java 的保留关键字。在 Java 中可以使用 instanceof 关键字判断一个对象是否为一个类(或接口、抽象类、父类)的实例,语法格式如下所示。

boolean result = obj instanceof Class

其中,obj 是一个对象,Class 表示一个类或接口。obj 是 class 类(或接口)的实例或者子类实例时,结果 result 返回 true,否则返回 false。

下面介绍 Java instanceof 关键字的几种用法。

1)声明一个 class 类的对象,判断 obj 是否为 class 类的实例对象(很普遍的一种用法),如以下代码:

Integer integer = new Integer(1);
System.out.println(integer instanceof Integer);  // true

2)声明一个 class 接口实现类的对象 obj,判断 obj 是否为 class 接口实现类的实例对象,如以下代码:

Java 集合中的 List 接口有个典型实现类 ArrayList。

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable

所以我们可以用 instanceof 运算符判断 ArrayList 类的对象是否属于 List 接口的实例,如果是返回 true,否则返回 false。

ArrayList arrayList = new ArrayList();
System.out.println(arrayList instanceof List);  // true

或者反过来也是返回 true

List list = new ArrayList();
System.out.println(list instanceof ArrayList);  // true

3)obj 是 class 类的直接或间接子类

我们新建一个父类 Person.class,代码如下:

public class Person {
}

创建 Person 的子类 Man,代码如下:

public class Man extends Person {
}

测试代码如下:

Person p1 = new Person();
Person p2 = new Man();
Man m1 = new Man();
System.out.println(p1 instanceof Man);    // false
System.out.println(p2 instanceof Man);    // true
System.out.println(m1 instanceof Man);    // true

第 4 行代码中,Man 是 Person 的子类,Person 不是 Man 的子类,所以返回结果为 false。

值得注意的是 obj 必须为引用类型,不能是基本类型。例如以下代码:

int i = 0;
System.out.println(i instanceof Integer);  // 编译不通过
System.out.println(i instanceof Object);  // 编译不通过

所以,instanceof 运算符只能用作对象的判断。

当 obj 为 null 时,直接返回 false,因为 null 没有引用任何对象。

Integer i = 1;
System.out.println(i instanceof null);  // false

所以,obj 的类型必须是引用类型或空类型,否则会编译错误。

当 class 为 null 时,会发生编译错误,错误信息如下:

Syntax error on token "null", invalid ReferenceType

所以 class 只能是类或者接口。

方法重载

Java 允许同一个类中定义多个同名方法,只要它们的形参列表不同即可。如果同一个类中包含了两个或两个以上方法名相同的方法,但形参列表不同,这种情况被称为方法重载(overload)。

例如,在 JDK 的 java.io.PrintStream 中定义了十多个同名的 println() 方法。

public void println(int i){…}
public void println(double d){…}
public void println(String s){…}

这些方法完成的功能类似,都是格式化输出。根据参数的不同来区分它们,以进行不同的格式化处理和输出。它们之间就构成了方法的重载。实际调用时,根据实参的类型来决定调用哪一个方法。例如:

System.out.println(102);    // 调用println(int i)方法
System.out.println(102.25);    // 调用println(double d)方法
System.out.println("价格为 102.25");    // 调用println(String s)方法

方法重载的要求是两同一不同:同一个类中方法名相同,参数列表不同。至于方法的其他部分,如方法返回值类型、修饰符等,与方法重载没有任何关系。

使用方法重载其实就是避免出现繁多的方法名,有些方法的功能是相似的,如果重新建立一个方法,重新取个方法名称,会降低程序可读性。

为什么方法重载不能用方法的返回值类型区分呢?

对于int f() { }void f() { }两个方法,如果这样调用int result = f();,系统可以识别是调用返回值类型为 int 的方法,但 Java 调用方法时可以忽略方法返回值,如果采用如下方法来调用f();,你能判断是调用哪个方法吗?如果你尚且不能判断,那么 Java 系统也会糊涂。在编程过程中有一条重要规则就是不要让系统糊涂,系统一糊涂,肯定就是你错了。因此,Java 里不能用方法返回值类型作为区分方法重载的依据。

方法重写

在子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写(override),又称为方法覆盖。当父类中的方法无法满足子类需求或子类具有特有功能的时候,需要方法重写。

子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。

在重写方法时,需要遵循下面的规则:

另外还要注意以下几条:

抽象类

Java 语言提供了两种类,分别为具体类和抽象类。

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,那么这样的类称为抽象类。

在 Java 中抽象类的语法格式如下:

<abstract>class<class_name> {
    <abstract><type><method_name>(parameter-iist);
}

其中,abstract 表示该类或该方法是抽象的;class_name 表示抽象类的名称;method_name 表示抽象方法名称,parameter-list 表示方法参数列表。

如果一个方法使用 abstract 来修饰,则说明该方法是抽象方法,抽象方法只有声明没有实现。需要注意的是 abstract 关键字只能用于普通方法,不能用于 static 方法或者构造方法中。

抽象方法的 3 个特征如下:

注意:在使用 abstract 关键字修饰抽象方法时不能使用 private 修饰,因为抽象方法必须被子类重写,而如果使用了 private 声明,则子类是无法重写的。

抽象类的定义和使用规则如下:

接口

抽象类是从多个类中抽象出来的模板,如果将这种抽象进行的更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(Interface)。接口是 Java 中最重要的概念之一,它可以被理解为一种特殊的类,不同的是接口的成员没有执行体,是由全局常量和公共的抽象方法所组成。

注意:一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。

定义接口

接口对于其声明、变量和方法都做了许多限制,这些限制作为接口的特征归纳如下:

例如:

public interface A {
    publicA(){…}    // 编译出错,接口不允许定义构造方法
}

一个接口不能够实现另一个接口,但它可以继承多个其他接口。子接口可以对父接口的方法和常量进行重写。例如:

public interface StudentInterface extends PeopleInterface {
    // 接口 StudentInterface 继承 PeopleInterface
    int age = 25;    // 常量age重写父接口中的age常量
    void getInfo();    // 方法getInfo()重写父接口中的getInfo()方法
}

例如,定义一个接口 MyInterface,并在该接口中声明常量和方法,如下:

public interface MyInterface {    // 接口myInterface
    String name;    // 不合法,变量name必须初始化
    int age = 20;    // 合法,等同于 public static final int age = 20;
    void getInfo();    // 方法声明,等同于 public abstract void getInfo();
}

实现接口

接口的主要用途就是被实现类实现,一个类可以实现一个或多个接口,继承使用 extends 关键字,实现则使用 implements 关键字。因为一个类可以实现多个接口,这也是 Java 为单继承灵活性不足所作的补充。类实现接口的语法格式如下:

<public> class <class_name> [extends superclass_name] [implements interface1_name[, interface2_name…]] {
    // 主体
}

对以上语法的说明如下:

实现接口需要注意以下几点:

总结

本篇文章就到这里了,希望可以帮助到你,也希望您能够多多关注脚本之家的更多内容!

您可能感兴趣的文章:
阅读全文