包括:继承、spuer关键字、sealed类、多态、方法覆写、final关键字等
输出结果都写在了注释中
1. 继承(Inheritance)
1.1 基本概念
子类(Subclass) :扩展类(Extended Class)
父类(Superclass) :基类(Base Class)或超类(Super Class)
class Person {
private String name;
private int age;
}
// 继承使用关键字extends
class Student extends Person {
// 不要重复name和age字段/方法,
// 只需要定义新增score字段/方法:
private int score;
// 父类的private字段对子类不可见
public String hello(){
//return "Hello," + this.name; 编译错误:无法访问name字段
}
}
1.2 protected
若希望子类能访问父类字段,可将字段声明为protected
public class demo19 {
public static void main(String[] args){
Student s = new Student();
s.setName("Xiaoming");
System.out.println(s.hello());
// Hello,Xiaoming Xiaoming Xiaoming
}
}
class Person{
protected String name;
protected int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Student extends Person{
protected int score;
public String hello(){
// super用于引用父类的字段、方法或构造器
// this.name 和 super.name 效果一样
// 但在重写方法时,如果想明确表示调用父类方法,就必须使用super
return "Hello," + name +" " + super.name +" " + this.name;
}
}
protected
修饰的成员可以被:本类访问、同包类访问、子类访问(不管是否同包)
1.3 继承树
Java中每个类最终都会继承自Object
类:
Object
↑
Person
↑
Student
如果定义Teacher
也是继承自Person
:
Object
↑
Person
↑ ↑
Student Teacher
1.4 super关键字
public class demo20 {
public static void main(String[] args) {
Student s = new Student("Xiao Ming", 12, 89);
}
}
class Person {
protected String name;
protected int age;
//如果我们自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
protected int score;
// 任何class的构造方法,第一行语句必须是调用父类的构造方法
// 如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();
public Student(String name, int age, int score) {
// 如果父类没有无参构造器,子类必须显式调用一个带参数的父类构造器,否则编译报错
// super(); 自动调用父类的构造方法,编译错误
super(name, age); // 调用父类的有参构造方法
this.score = score;
}
}
1.5 sealed类
sealed
是一种新引入的类修饰符(从 Java 15 开始作为预览功能,Java 17 起正式支持),它可以限制哪些类可以继承某个类,防止类被滥用继承,适用于框架开发和复杂体系结构。
使用规则
-
使用关键字
sealed
修饰父类。 -
使用
permits
指定允许继承的子类。 -
被允许继承的子类,必须用以下之一修饰:
final
:子类不能再被继承。sealed
:子类继续限制继承。non-sealed
:子类继承自由,不再受限制。
如果子类没有指定这些修饰符,将会编译错误。
定义sealed父类
// Shape是一个sealed类,只允许Circle和Rectangle继承它
public sealed class Shape permits Circle, Rectangle {
// 父类代码
}
定义允许的子类
public final class Circle extends Shape {
// Circle被声明为final,不能再被继承
}
public sealed class Rectangle extends Shape permits FilledRectangle, EmptyRectangle {
// Rectangle仍是sealed,继续控制自己的子类
}
public final class FilledRectangle extends Rectangle {
// 终结继承链
}
public non-sealed class EmptyRectangle extends Rectangle {
// EmptyRectangle是non-sealed,任何类可以继承它
}
继承树
Shape (sealed)
├── Circle (final)
└── Rectangle (sealed)
├── FilledRectangle (final)
└── EmptyRectangle (non-sealed)
└── 任何子类...
特性总结
特性 | 说明 |
---|---|
sealed | 限制可继承子类,必须列出允许的子类列表 |
final | 子类不能再被继承 |
non-sealed | 子类可以被自由继承,不再受sealed控制 |
关键字permits | 指定允许继承的类,写在类定义后面 |
sealed接口(Java 17起)
public sealed interface Animal permits Dog, Cat {}
public final class Dog implements Animal {}
public final class Cat implements Animal {}
1.6 向上转型和向下转型
向上转型(Upcasting)
向上转型实际上是把一个子类型安全地变为更加抽象的父类型,所以,可以把Student类型转型为Person,或者更高层次的Object
Student s = new Student();
Person p = s; // 子类对象赋值给父类引用,总是安全的
Object o = p; // 继续向上
向下转型(Downcasting)
Person p = new Student();
Student s = (Student) p; // 父类引用强制转为子类引用
Person p1 = new Person();
// 如果父类引用指向的不是子类对象,强制转型将抛出ClassCastException
Student s1 = (Student) p1; // 编译错误
instanceof
instanceof
用于判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为null
,那么对任何instanceof
的判断都为false
Person p = new Student();
if (p instanceof Student) {
Student s = (Student) p;
}
Java 14起支持简化写法:
Person p = new Student();
if (p instanceof Student s) {
// 直接使用s
}
1.7 继承与组合
继承(is关系) :子类是父类的一种。
示例:Student 是 Person
组合(has关系) :一个类拥有另一个类的实例。
示例:Student 有一本 Book
class Book {
protected String name;
public String getName() {...}
public void setName(String name) {...}
}
// 错误
class Student extends Book {
protected int score;
}
// 正确
class Student extends Person {
protected Book book;
protected int score;
}
1.8 访问修饰符的访问范围总结
修饰符 | 同一类中 | 同一包中 | 不同包的子类中 | 不同包的非子类中 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
default(无修饰符) | √ | √ | × | × |
private | √ | × | × | × |
public
:权限最大,任何地方都可以访问。
protected
:主要用于继承,允许在同一包内和不同包的子类中访问。
(default)
(包私有):只允许在同一个包内访问。
private
:权限最小,只能在声明它的类内部访问。
2. 多态(Polymorphism)
2.1 方法覆写(Override)
子类重新定义一个与父类方法签名完全相同的方法,称为方法覆写。
方法签名包括方法名和参数列表,例如:
public int calculateSum(int a, int b) {
return a + b;
}
calculateSum(int a, int b)
是方法的签名。它包括方法名calculateSum
和参数列表(int a, int b)
class Person {
public void run() {
System.out.println("Person.run");
}
}
class Student extends Person {
// 加上@Override注解,可以让编译器帮助检查是否正确覆写,避免笔误
@Override
// 返回类型也必须一致
public void run() {
System.out.println("Student.run");
}
// 希望进行覆写,但是不小心写错了方法签名,编译器会报错
public void run(String s) { //编译错误
}
}
方法重载(Overload)与方法覆写(Override)的区别:
特性 | 重载 (Overload) | 覆写 (Override) |
---|---|---|
发生位置 | 同一个类或子类中 | 父类与子类之间 |
方法名 | 相同 | 相同 |
参数列表 | 不同(参数数量或类型不同) | 完全相同 |
返回类型 | 可以不同 | 通常必须相同(或子类型) |
关键注解 | 无需 @Override | 推荐加上 @Override |
2.2 多态
多态指:在运行时,方法调用执行的是实际对象的版本,而不是变量声明类型的方法,也可以说是同一个行为在不同对象上有不同的实现方式。
方法调用在运行期动态决定。
引用类型不决定调用的方法,实际对象类型才决定调用哪个方法。
public class demo21 {
public static void main(String[] args){
Person p = new Student();
p.run(); // // 调用的是Student的run(),不是Person的run()
// Student run.
}
}
class Person{
public void run(){
System.out.println("Person.run.");
}
}
class Student extends Person{
@Override
public void run(){
System.out.println("Student run.");
}
}
编译时多态(静态多态)
通过方法重载实现:
public class Demo {
public void print(String s) {
System.out.println("字符串:" + s);
}
public void print(int i) {
System.out.println("整数:" + i);
}
}
特点:方法名相同,参数不同(类型或个数),在编译时确定调用哪个方法。
运行时多态(动态多态)
通过方法覆写 + 父类引用指向子类对象实现:
class Animal {
public void makeSound() {
System.out.println("动物叫");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("汪汪");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵喵");
}
}
public class Test {
public static void main(String[] args) {
Animal a1 = new Dog();
Animal a2 = new Cat();
a1.makeSound(); // 输出:汪汪
a2.makeSound(); // 输出:喵喵
}
}
2.3 super调用父类方法
在子类覆写的方法中,可以通过super
调用父类原有的方法
class Person {
protected String name;
public String hello() {
return "Hello, " + name;
}
}
class Student extends Person {
@Override
public String hello() {
return super.hello() + "!";
}
}
2.4 final关键字
方法中
继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final
。用final修饰的方法不能被Override
。
class Person {
protected String name;
public final String hello() {
return "Hello, " + name;
}
}
class Student extends Person {
// 编译错误: 不允许覆写
@Override
public String hello() {
}
}
类中
如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承。
final class Person {
protected String name;
}
// 编译错误: 不允许继承自Person
class Student extends Person {
}
字段中
对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改。
class Person {
public final String name = "Unamed";
}
Person p = new Person();
p.name = "New Name"; // 编译错误!
可以在构造方法中初始化final字段,可以保证实例一旦创建,其final字段就不可修改。
class Person {
public final String name;
public Person(String name) {
this.name = name;
}
}
2.5 覆写Object
的方法
所有类默认继承自Object
,可以覆写其方法以增强功能:
toString()
:自定义对象字符串输出。
equals(Object)
:自定义对象逻辑比较。
hashCode()
:自定义对象哈希值。
public class demo22 {
public static void main(String[] args){
Person p = new Person("Xiaoming",18);
Person p1 = new Person("Xiaoming",18);
System.out.println(p);
// Exp02.Person@314c508a
// 几乎对阅读没用,所以我们常常覆写它
p.toString();
// Person{name='Xiaoming', age=18}
// 调试和打印对象时更友好
System.out.println(p == p1);
// false
System.out.println(p.equals(p1));
// true
}
}
class Person{
public Person(String name,int age){
this.name = name;
this.age = age;
}
protected int age;
protected String name;
@Override
public String toString(){
return "Person{name='" + name + "', age=" + age + "}";
}
@Override
public boolean equals(Object o){
// 当且仅当o为Person类型
if(o instanceof Person){
Person p = (Person) o;
return age == p.age&&name.equals(p.name);
}
return false;
}
@Override
public int hashCode(){
return this.name.hashCode();
}
}