Java基础知识(9)- Java 面向对象(一)| 继承(Extends)、重写(Override)/重载(Overload)、多态 (Polymorphism)
1. 继承(Extends)
1) 继承的概念
Java 中的继承就是在已有类的基础上进行扩展,从而产生新的类。已有的类称为父类、基类或超类,而新产生的类称为子类或派生类。
(1) 继承格式:
class Human {
}
class Male extends Human {
}
class Boy extends Male {
}
使用 extends 关键字,Boy 继承了 Male,Male 继承了 Human,Human 是 Boy 的间接父类。
(2) 单继承:
Java 语言摒弃了 C++ 中难以理解的多继承特征,即 Java 不支持多继承,只允许一个类直接继承另一个类,即子类只能有一个直接父类,extends 关键字后面只能有一个类名。
一个类只能有一个直接父类,但是它可以有多个间接的父类。
(3) IS-A 关系:
上文中的 Human -> Male -> Boy
IS-A 关系如下:
Man IS-A Human
Boy IS-A Male
因此: Boy IS-A Human
通过使用 instanceof 关键字,能够确定 Male,Boy 是 Human 类的实例, 也就是 IS-A Human。
public static void main(String args[]) {
Male m = new Male();
Boy b = new Boy();
System.out.println(m instanceof Human); // 输出 true
System.out.println(b instanceof Human); // 输出 true
}
2) 继承的规则
(1) 类的继承不改变成员变量和方法的访问权限;
(2) 子类不能继承父类的构造方法(构造函数),如果要调用父类的构造方法,可以使用 super 关键字 (super 可以用来访问父类的构造方法、成员变量和方法);
(3) 子类拥有父类所有成员变量和方法,但在子类中不能直接访问父类中的 private 成员变量和方法(它们在子类中是不可见的);
(4) 子类可以拥有自己的成员变量和方法,即子类可以对父类进行扩展;
(5) 子类可以重写父类的方法;
(6) final 修饰的类不能被继承,即这样的类是最终类;
3) 继承的优点和缺点
继承的优点:
(1) 实现代码共享,减少创建类的工作量,使子类可以拥有父类的成员变量和方法;
(2) 提高代码维护性和可重用性;
(3) 提高代码的可扩展性,更好的实现父类的方法;
继承的缺点:
(1) 继承是侵入性的。只要继承,就必须拥有父类的成员变量和方法;
(2) 降低代码灵活性。子类拥有父类的成员变量和方法后多了些约束;
(3) 增强代码耦合性(开发项目的原则为高内聚低耦合)。当父类的常量、变量和方法被修改时,需要考虑子类的修改,有可能会导致大段的代码需要重构;
4)this 关键字
当一个对象创建后,Java虚拟机(JVM)就会给这个对象分配一个引用自身的指针,这个指针的名字就是 this。
this 关键字的用法:
(1) 使用 this 来区分当前对象,比如在构造方法(构造函数)和对象实例的方法中,实例变量和局部变量名字冲突时,可以给实例变量加上 this. 前缀;
(2) 在构造方法中使用 this 来调用对象本身的其他构造方法;
(3) 返回类的引用。如在代码中,可以使用return this来返回某个类的引用。此时,这个this关键字就代表类的名称;
不能在 static 方法中使用 this 关键字,this 和 super 都无法出现在 static 修饰的方法中。
Static 方法是类方法,先于任何的实例(对象)存在。即 Static方法在类加载时就已经存在了,但是对象是在创建时才在内存中生成,而 this 指代的是当前对象。
实例:
1 public class App { 2 public static void main( String[] args ) { 3 Boy b = new Boy("Boy name"); 4 System.out.println("Boy is an instance of Human: " + (b instanceof Human)); 5 6 b.test(); 7 } 8 } 9 10 // final 类不能继承, 即最终类 11 final class Group { 12 13 public Group() { 14 System.out.println("Group class initialize"); 15 } 16 17 } 18 19 class Human { 20 private String name; 21 protected String label = "Human label"; 22 public int age = 10; 23 24 // 构造方法(构造函数) 25 public Human(String name) { 26 // this 指向自己的引用 27 this.name = name; 28 29 System.out.println("Human class initialize"); 30 } 31 32 public String getName() { 33 return this.name; 34 } 35 36 protected String getName1() { 37 return this.name; 38 } 39 40 private String getName2() { 41 return this.name; 42 } 43 44 } 45 46 class Male extends Human { 47 public Male(String name) { 48 super(name); 49 50 System.out.println("Male class initialize"); 51 } 52 } 53 54 class Boy extends Male { 55 56 private Group group = new Group(); 57 58 // 子类不能继承父类的构造方法(构造函数) 59 public Boy(String name) { 60 // 使用 super 调用父类构造方法 61 super(name); 62 63 System.out.println("Boy class initialize"); 64 } 65 66 public void test() { 67 System.out.println("Can access parent public variable 'age': " + this.age); 68 System.out.println("Can access parent protected variable 'label': " + this.label); 69 System.out.println("Can NOT access parent private variable 'name'"); 70 System.out.println("Can access parent public method 'getName()': " + this.getName()); 71 System.out.println("Can access parent protected method 'getName1()': " + this.getName1()); 72 System.out.println("Can NOT access parent private method 'getName2()'"); 73 } 74 }
输出:
Human class initialize
Male class initialize
Group class initialize
Boy class initialize
Boy is an instance of Human: true
Can access parent public variable 'age': 10
Can access parent protected variable 'label': Human label
Can NOT access parent private variable 'name'
Can access parent public method 'getName()': Boy name
Can access parent protected method 'getName1()': Boy name
Can NOT access parent private method 'getName2()'
2. 重写(Override)/重载(Overload)
1)重写(Override)
在子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写(Override),又称为方法覆盖。
当父类中的方法无法满足子类需求或子类具有特有功能的时候,需要方法重写。
下面程序演示了重写:
class Male extends Human {
public void display() {
}
public static void test() {
}
}
class Boy extends Male {
@Override
public void display() {
}
// 父类的 static 方法不能被重写,可以被再次声明和定义
//@Override
public static void test() {
}
}
(1) @Override
是伪代码,表示方法重写, 使用 @Override 标签的好处:
1) 作为注释,帮助自己检查是否正确的复写了父类中已有的方法;
2) 便于别人理解代码;
3) 编译器可以给你验证 @Override 下面的方法名是否是你父类中所有的,如果没有则报错;
(2) 重写的规则
a) 参数列表与被重写方法的参数列表必须完全相同;
b) 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同);
c) 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected;
d) 父类的成员方法只能被它的子类重写;
e) 声明为 final 的方法不能被重写;
f) 声明为 static 的方法不能被重写,但是能够被再次声明和定义,父类和子类有各自同名的 static 方法;
g) 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法;
h) 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法;
i) 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以;
j) 构造方法不能被重写
2) 重载(Overload)
重载(Overload) 是在一个类里面,方法名字相同,而参数不同,返回类型可以相同也可以不同。最常用的地方就是构造函数的重载。
下面程序演示了重写:
public class Male extends Human {
@Override
public void display() {
}
// 重载本类的 display()
public void display(String str) {
}
}
(1) 重载的规则
a) 被重载的方法必须改变参数列表(参数个数或类型不一样);
b) 被重载的方法可以改变返回类型;
c) 被重载的方法可以改变访问修饰符;
d) 被重载的方法可以声明新的或更广的检查异常;
e) 方法能够在同一个类中或者在一个子类中被重载;
f) 无法以返回值类型作为重载函数的区分标准;
(2) 重载与重写的区别
项目 重载方法 重写方法
参数列表 必须修改 不能修改
返回类型 可以修改 不能修改
异常 可以修改 可以减少或删除,一定不能抛出新的或者更广的异常
访问 可以修改 不能做更严格的限制(可以降低限制)
实例:
1 public class App { 2 public static void main( String[] args ) { 3 4 Male m = new Male("Male name"); 5 Boy b = new Boy("Boy name"); 6 7 b.display(); 8 b.display("Boy: Overload display(String str)"); 9 b.displayParent(); 10 11 b.show(); // Boy 类的 static show() 12 Male m2 = b; 13 m2.show(); // Male 类的 static show() 14 } 15 } 16 17 class Human { 18 private String name; 19 20 public Human(String name) { 21 this.name = name; 22 } 23 24 public static void show() { 25 // 不能在 static 方法中使用 this 关键字 26 //System.out.println("Human: show() -> name = " + this.name); 27 } 28 } 29 30 class Male extends Human { 31 32 public Male(String name) { 33 super(name); 34 } 35 36 // final 方法不能被子类重写(Override) 37 final public void testFinalFunction() { 38 System.out.println("Male: test final function"); 39 } 40 41 public void display() { 42 System.out.println("Male: display()"); 43 } 44 45 // static show() 方法不能被子类重写(Override) 46 public static void show() { 47 System.out.println("Male: static show()"); 48 } 49 } 50 51 class Boy extends Male { 52 53 public Boy(String name) { 54 super(name); 55 } 56 57 public String getName() { 58 return super.getName(); 59 } 60 61 /* 62 // 不能重写(Override)父类的 final 方法 63 @Override 64 public void testFinalFunction() { 65 System.out.println("Boy: test final function"); 66 } 67 */ 68 69 // 重写(Override) 70 @Override 71 public void display() { 72 System.out.println("Boy: Override display()"); 73 } 74 75 // 重载(Overload) 76 public void display(String str) { 77 System.out.println(str); 78 } 79 80 // 使用 super 关键字可以调用父类的被重写方法 81 public void displayParent() { 82 super.display(); 83 } 84 85 // 父类的 static test() 方法不能被重写(Override),可以被再次声明和定义 86 //@Override 87 public static void show() { 88 System.out.println("Boy: static show()"); 89 } 90 }
输出:
Boy: Override display()
Boy: Overload display(String str)
Male: display()
Boy: static show()
Male: static show()
3. 多态 (Polymorphism)
多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。
通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
多态存在的三个必要条件:
1) 继承:在多态中必须存在有继承关系的子类和父类。
2) 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
3) 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
虚函数:
Java 中没有虚函数的概念,它的普通函数就相当于 C++ 的虚函数,动态绑定是 Java 的默认行为。如果 Java 中不希望某个函数具有虚函数特性,可以加上 final 关键字变成非虚函数。
实例:
1 public class App { 2 public static void main( String[] args ) { 3 4 Human h = new Male(; 5 h.sayHello(); // 没有调用 Human 的 sayHello,而是调用了 Male 的 sayHello 6 7 h = new Female(); 8 h.sayHello(); // 没有调用 Human 的 sayHello,而是调用了 Female 的 sayHello 9 10 h = new Human(); 11 h.sayHello(); 12 } 13 } 14 15 class Human { 16 public Human() { 17 } 18 19 public void sayHello() { 20 System.out.println("Human sayHello()"); 21 } 22 } 23 24 class Male extends Human { 25 public Male() { 26 } 27 28 public void sayHello() { 29 System.out.println("Male sayHello()"); 30 } 31 } 32 33 class Female extends Human { 34 public Female() { 35 } 36 37 public void sayHello() { 38 System.out.println("Female sayHello()"); 39 } 40 }
输出:
Male sayHello()
Female sayHello()
Human sayHello()
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期
· 全程使用 AI 从 0 到 1 写了个小工具
· 从文本到图像:SSE 如何助力 AI 内容实时呈现?(Typescript篇)