Java继承与多态
一、Java中的继承
1、继承的作用
Java是一个面向对象的编程语言,因此我们将很多事物用其相关特征描述出来,这就是类。一个类就相当于一个对象的模板,需要用到具体的对象时我们通过类来实例化(new)出对象,然而对于很多对象他们存在一定的共性,例如苹果和香蕉这两个对象都属于水果,他们具有水果的相同特性而又有着属于本身的特性,如果对每一个对象都写一个对应的类,那么我们无疑会写很多重复的代码,比如苹果和香蕉的类,我们会重复写水果的属性和方法,对此我们有了继承 (IS-A的关系)。
2、继承的实现
继承可以使得你可以定义一个通用的类(即父类),之后扩充该类为一个更加特定的类(即子类)。在Java术语中,如果类A1扩展自另一个类A2,那么就将A1称为次类(子类),将A2称为超类(父类/基类)。 子类从它的父类中继承可访问的数据域和方法,还可以添加新数据域和新方法。
实现:public class A1 extends A2{ //A1继承A2 }
例如:我们对于苹果和香蕉这两个对象的描述可以写三个类,水果类(父类)、苹果类(子类)、香蕉类(子类)。苹果类和香蕉类共同继承其父类水果,因此他们不必重新写水果的共同特性,只需要写自己本身的私有特性。
3、继承的数据访问规则
-
父类的私有数据域不能被子类直接访问,唯一方式是子类通过父类私有数据的get和set等公有方法访问数据。
-
Java中子类对父类是单一继承,也就是说子类只能够继承一个父类,如果想要实现多继承,可以采用实现接口的方式,即implements后跟接口名,多个接口间实现使用逗号分离。
-
关键字super指代父类,可以用于调用父类中的普通方法和构造方法。父类的构造方法不会被子类继承。它们只能使用关键字super从子类的构造方法中调用。调用父类构造方法的语法是:super(),或者super(parameters);super()调用父类的无参构造方法,而super(arguments)调用与参数匹配的父类的构造方法。语句super()和super(arguments)必须出现在子类构造方法的第一行,这是显示调用父类构造方法的唯一方式。
-
成员变量隐藏:是指在子类中定义了一个与直接超类的某个成员变量同名的成员变量,从而使超类中的那个成员变量不能直接被子类继承。这时候子类使用简单的变量名访问只会访问到本身的成员变量,在超类中访问也只会访问到超类本身的成员变量,那么如何在子类中访问到超类中和本身已有的同名成员变量呢,我们有如下方法:
super.<变量名> // 在子类类体里,访问直接超类中被隐藏的成员变量
((<超类名>)<子类实例引用>).变量名 //访问指定超类中被隐藏的成员变量
<超类名>.<变量名> //访问指定超类中被隐藏的类变量
-
子类对父类方法的重写(Override)
a、方法名称要一样
b、返回值数据类型要一样
c、所使用的参数列表要一样,参数列表包括参数个数及每个参数的数据类型,不包括参数的变量名
d、访问修饰符只能越来越开放,不能越来越封闭
对于方法的重写,我们通常使用@Override关键字标注。 -
泛化:抽取调整代码到父类的过程,称为泛化,即子类可以选择完全重写或者在父类方法中补充。
public void test() {
super.test(); //调用父类的实现
System.out.println(" 增加部分 " );
}
- 子类和父类的代码执行顺序:
父类静态代码块和静态变量(代码块:最先执行的代码)
子类静态代码块和静态变量
父类代码块和普通变量
父类构造函数
子类代码块和普通变量
子类构造函数
二、Java的多态
1、什么是多态
程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
2、多态的特性
- 使用父类类型的引用实例出子类的对象
- 该引用只能调用父类中定义的方法和变量
- 如果子类中重写(覆盖)了父类中的一个方法,那么在调用这个方法的时候,将会调用子类中的这个方法
- 变量不能被重写(覆盖),重写只针对方法,如果在子类中重写了父类的变量,编译时会报错
3、多态的实现(继承、重写、向上转型)
继承:在多态中必须存在有继承关系的子类和父类。
重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。
只有满足了上述三个条件,我们才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而达到执行不同的行为。
例如:
public classFruit { //父类-水果
public void fun1(){
System.out.println("这是水果的fun1");
fun2();
}
public void fun2(){
System.out.println("这是水果的fun2");
}
}
public class Apple extends Fruit{
/**
* @desc 子类重载父类方法
* 父类中不存在该方法,向上转型后,父类是不能引用该方法的
*/
public void fun1(String a){
System.out.println("苹果 的 Fun1...");
fun2();
}
/**
* 子类重写父类方法
* 指向子类的父类引用调用fun2时,必定是调用该方法
*/
public void fun2(){
System.out.println("苹果 的Fun2...");
}
}
public class Test {
public static void main(String[] args) {
Fruit a = new Apple(); //多态
a.fun1();
}
}
--------------------------------------------------------------------------------------------
Output:
这是水果的fun1
苹果 的Fun2...
在多态中,当子类重写父类的方法被调用时,只有对象继承链中的最末端的方法才会被调用
三、Java的封装
封装是把过程和数据隐藏起来,对数据的访问只能通过已定义的接口。面向对象始于封装这个概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。封装是一种信息隐藏技术,在java中通过关键字private,protected和public实现封装。什么是封装?封装把对象的所有组成部分组合在一起,封装定义程序如何引用对象的数据,封装实际上使用方法将类的数据隐藏起来,控制用户对类的修改和访问数据的程度。 适当的封装可以让程式码更容易理解和维护,也加强了程式码的安全性。
在平时写一个类时,我们可以将相应的属性用private关键字修饰,然后给这些属性写上对应的get和set方法,这样,我们外部不能够直接访问该对象的相关属性,但是我们可以通过它的get和set等公有的方法访问这些属性,这就是封装。