JavaSE-06面向对象的三大特征:封装、继承、多态,类就是一种封装:类就
JavaSE-06面向对象的三大特征:封装、继承、多态,类就是一种封装:类就
JavaSE-06面向对象的三大特征:封装、继承、多态
- [ 任务列表 ]
- 1.封装
- 2.继承
- 2.1.权限修饰符
- 2.2.继承的特点
- 2.3.在子类中访问父类成员
- 2.4.方法重写
- 2.5.方法重载
- 2.6.子类构造器
- 2.7.兄弟构造器
- 3.多态
- 3.1.对象多态
- 3.2.行为多态
- 3.3.多态下的类型转换——调用子类的功能
- 3.4.强制类型转换前的判断——instanceof关键字
- 4.其他
- 4.1.祖宗类Object
- 4.2.开发规范
- 4.3.解耦合
- 4.4.lombok注解@Data
- 4.5.方法重载和方法重写的区别
- 5.参考资料
1.封装
-
面向对象的三大特征:封装、继承、多态。
-
类就是一种封装:类就是把要处理的对象的数据,以及对这些数据进行处理的方法封装成一个架子。
例如:日常中,手机、洗衣机、汽车……等都是封装。
-
封装的设计要求:合理隐藏,合理暴漏。
-
哪些需要隐藏,哪些需要暴露,怎么隐藏,怎么暴露?
如何隐藏:使用private关键字修饰成员变量,就只能在本类中访问,其他任何地方不能直接访问。
如何暴露(合理暴露):使用public修饰(公开)的getter和setter方法合理暴露。
-
面向对象的高级语法:继承、多态。
2.继承
-
面向对象的高级语法之一:继承。
-
什么是继承?为什么要有继承?
定义的javabean实体类中会有大量的重复代码,怎么把重复的代码抽出来,让其他实体类共同使用呢?——即所有人都叫他爸爸。(减少一些重复代码的使用)
-
Java中提供了一个关键字extends,用这个关键字,可以让一个类和另一个类建立起父子关系。这就是继承。
public class B extends A{
}
// A类称为父类(基类、超类)
// B类称为子类(派生类)
-
子类能继承啥?
子类能继承父类的非私有成员(成员变量、成员方法)
-
继承后对象的创建:
子类的对象是由子类、父类共同完成的
子类对象其实是由子类和父类多张设计图共同创建出来的对象,所以子类对象是完整的。
2.1.权限修饰符
-
什么是权限修饰符?
就是用来限制类中的成员(成员变量、成员方法、构造器)能够被访问的范围。
-
权限修饰符:
public
:公开访问。
protected
:同一包内或者不同包中的子类可访问。即本类,同一个包中的类,子孙类。
- 默认(无修饰符):仅限于同一个包内的类访问。即本类,同一个包中的其他类。
private
:仅限于本类内部访问。
其中,public > protected > 默认(无修饰符)> private
-
权限修饰符的常用情境:
成员变量大概率会private修饰,
成员方法大概率会public修饰,
而缺省和protected一般不会使用。
2.2.继承的特点
- 继承的特点:
单继承,一个类只能继承一个父类,只能有一个爸爸;
多层继承,Java不支持多继承,但支持多层继承,就是爸爸也会继承;
- Java中的类为什么不支持多继承?
用反证法,假设A、B中都有个method方法,A是复习语文的,B是复习数学的
假如C可以同时继承 A和B,如果用C创建对象,C可以调用父类的方法,这是C就不知道去调用A还是B的方法。
- 继承后子类访问成员的特点:就近原则
① 在子类中访问其他成员,是依照就近原则的,先在子类局部范围找,然后子类成员范围找,然后父类成员范围找,如果父类范围还没有找到则报错。
② 如果子父类中,出现了重名的成员,会优先使用子类的成员。
2.3.在子类中访问父类成员
-
如果此时一定要在子类中使用父类的成员怎么办?
可以通过super关键字,指定访问父类的成员。
super.父类成员变量;
super.父类成员方法;
2.4.方法重写
- 方法重写(Overriding):
当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时(即子类重新定义了父类中的一个已有的方法),子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
- 方法重写规范:
- 方法名称,形参列表必须一样,这个方法就是方法重写。
- @Override,方法重写的校验注解(标志):要求方法名称和形参列表必须与被重写方法一致,否则报错!(更安全,可读性更好,更优雅)
- 子类重写父类的方法时,访问权限必须大于或者等于父类该方法的权限,即不能从public变为protected或private,并且抛出的异常范围也应保持一致或更小。(public > protected > 缺省)
- 重写的方法,返回值类型必须与被重写方法的返回值类型一样,或者范围更小;
- 私有方法(private),静态方法(static修饰)不能被重写,如果重写会报错;
- 通常用于实现多态性,允许使用父类类型的引用指向子类对象,并调用子类中重写的方法;
- 重写的规范:声明不变,重新实现。
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
// 实现多态性 可以这么写:
// Animal cat = new Cat();
cat.cry();
}
}
class Animal{
public void cry(){
System.out.println("动物会叫~~~ ");
}
}
class Cat extends Animal{ // Cat类继承Animal类
// 重写的规范:声明不变,重新实现。
@Override // 方法重写的校验注解(标志)
public void cry(){ // 方法重写:方法名称,形参列表必须一样
System.out.println("猫 喵喵喵的叫~~~");
}
}
- 方法重写的常见应用场景:
子类重写Object类的toString()方法,以便返回对象的内容。
当返回的对象是一个地址时,说明没有重写toString方法;
当返回的对象是一个对象内容时代表重写了toString方法。
public class Test2 {
public static void main(String[] args) {
Student s = new Student("赵敏",'女',25);
System.out.println(s);
System.out.println(s.toString());
}
}
class Student{
private String name;
private char sex;
private int age;
// 重写的toString方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex=" + sex +
", age=" + age +
'}';
}
// 无参构造器
public Student() {
}
// 有参构造器
public Student(String name, char sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
2.5.方法重载
-
在Java中,方法重载(Overloading)和方法重写(Overriding)是面向对象编程中的两个重要概念,它们各自有不同的含义、用途和规则。
-
方法重载(Overloading)
方法重载指的是在一个类中定义多个同名但参数列表不同的方法。这些方法可以有不同的参数类型、不同数量的参数,或者两者兼有。返回值类型也可以不同,但是不能仅凭返回值类型来区分重载的方法。
-
方法重载的特点
- 发生在同一类中。
- 方法名称相同,但参数列表不同(参数的数量或类型不同)。
- 可以有不同的返回类型,但这不是区分重载方法的标准。
- 与访问修饰符无关。
public class Example {
void show(int a) {
System.out.println("Integer: " + a);
}
// 重载方法1
void show(String b) {
System.out.println("String: " + b);
}
// 重载方法2
void show(int a, String b) {
System.out.println("Both: " + a + ", " + b);
}
}
-
方法重载(Overloading)和方法重写(Overriding)的区别:
- 方法重载关注的是“同一个类中”方法的不同版本,主要通过不同的参数列表来区分。
- 方法重写则涉及到“父子类之间”的方法替换,要求方法签名完全一致,主要用于实现多态性和特定行为的定制化。
2.6.子类构造器
-
子类构造器特点:子类的全部构造器,都会先调用父类的构造器,再执行自己。
-
子类构造器是如何实现调用父类构造器的?
- 默认情况下,子类全部构造器的第一行代码都是调用父类的无参构造器。
super()
(写不写都有),它会调用父类的无参构造器;
- 如果父类没有无参构造器,则我们必须在所有子类构造器的第一行手写
super(xxx)
,指定去调用父类的有参构造器。
- 子类构造器可以通过调用父类构造器,把对象中包含父类这部分的数据先初始化赋值,
再回来把对象里包含子类这部分的数据也进行初始化赋值。
- 在Java中,子类的构造器不能同时调用多个父类的构造器。每个子类构造器的第一行要么隐式或显式地调用一个父类构造器(通过
super()
或super(参数列表)
),要么在同一类中调用该类的另一个构造器(通过this()
或this(参数列表)
)即兄弟构造器。这意味着你只能选择其中一个父类构造器进行调用。
public class Test {
public static void main(String[] args) {
Zi zi = new Zi(); // 新建一个子类的对象
}
}
class Zi extends Fu{ // Zi类继承Fu类
public Zi(){ // Zi类无参构造器
// super(); // (如果父类有无参构造器)默认存在的,写不写都有
// super(666); // 指定调用Fu类的有参构造器
// 如果父类没有无参构造器,则必须在子类构造器的第一行写这个super(666),指定去调用父类的有参构造器。
System.out.println("子类无参构造器");
}
}
class Fu{
public Fu(){ // 无参构造器,私有的时,不能被子类继承
System.out.println("父类无参构造器");
}
public Fu(int a){ // 有参构造器
System.out.println("父类有参构造器");
}
}
执行语句:
父类无参构造器
子类无参构造器
2.7.兄弟构造器
- 理解
this(xxx)
关键字,调用兄弟构造器:
注意:super(...)
this(...)
必须写在构造器的第一行,但是两者不能同时出现;
因为this调用兄弟构造器时,兄弟构造器肯定先找爸爸构造器,此时super再执行,就是找第二遍爸爸构造器了。
public class Student {
private String name;
private char sex;
private int age;
private String school;
public Student() {
}
public Student(String name, char sex, int age) {
// this调用兄弟构造器
// 注意:super(...) this(...)必须写在构造器的第一行,而且两者不能同时出现
this(name,sex,age,"家里蹲大学"); // 悄悄使用兄弟构造器
// super(); // this下面第二行不能使用super关键字调用父类的构造器
// 当前构造器是传入三个形参变量,但是要求该类在初始化赋值时,
// 如果没有传入school变量,该类能够自动对school变量进行赋值"XXX大学"
// 所以该构造器要手动调用它的兄弟构造器(传入四个形参变量),其中一个手动赋值。
// this.name = name;
// this.sex = sex;
// this.age = age;
// this.school = "黑马程序员";
}
public Student(String name, char sex, int age, String school) {
this.name = name;
this.sex = sex;
this.age = age;
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex=" + sex +
", age=" + age +
", school='" + school + '\'' +
'}';
}
}
3.多态
- 多态是在继承(类之间)、实现(接口和类)情况下的一种现象,表现为:对象多态、行为多态。成员变量不谈多态。
3.1.对象多态
- 对象多态:父类引用指向子类对象;
Animal a1 = new Wolf(); // 对象多态:父类引用指向子类对象
Animal a2 = new Tortoise();
3.2.行为多态
- 行为多态:定义一个通用的接口或基类,并让其子类完成该类的具体实现。这样,即使使用父类类型的引用,也能调用到子类中重写的方法,从而表现出不同的行为。
- 多态的前提:
有继承、实现关系;
父类引用子类对象;
存在方法重写。(只有方法重写才能出现行为多态)
public class Animal {
String name = "Animal";
public void run(){
System.out.println("Animal is running");
}
}
public class Wolf extends Animal{
String name = "狼";
@Override
public void run() {
System.out.println("狼跑的贼溜~~~");
}
}
public class Tortoise extends Animal{
String name = "乌龟";
@Override
public void run() {
System.out.println("乌龟跑的贼慢!!!");
}
}
public class Test1 {
public static void main(String[] args) {
// 1、对象多态
Animal a1 = new Wolf(); // 父类引用指向子类对象
a1.run(); // 方法:编译看左边,运行看右边
// 当Animal中没有run方法时,编译报错
// 当运行run方法时,会去Wolf中找run方法运行
Animal a2 = new Tortoise();
a2.run(); // 方法:编译看左边,运行看右边
// 成员变量:编译看左边,运行也看左边
System.out.println(a1.name); // Animal
System.out.println(a2.name); // Animal
// 使用父类类型数组管理不同子类对象
Animal[] animals = {a1, a2};
for (Animal animal : animals) {
animal.run(); // 动态绑定,运行时确定调用哪个子类的方法
}
}
}
执行结果:
狼跑的贼溜~~~
乌龟跑的贼慢!!!
Animal
Animal
狼跑的贼溜~~~
乌龟跑的贼慢!!!
- 多态的好处:
在多态的形式下,右边的对象是解耦合的,更便于扩展和维护;
定义方法时,使用父类类型的形参,可以接受一切子类对象,扩展性更强,更便利。
public class Animal {
String name = "Animal";
public void run(){
System.out.println("Animal is running");
}
}
public class Wolf extends Animal {
String name = "狼";
@Override
public void run() {
System.out.println("狼跑的贼溜~~~");
}
// 子类独有的功能:狼吃羊
public void eatSheep() {
System.out.println("狼吃羊");
}
}
public class Tortoise extends Animal {
String name = "乌龟";
@Override
public void run() {
System.out.println("乌龟跑的贼慢!!!");
}
// 子类独有的功能:乌龟缩头
public void shrinkHead() {
System.out.println("乌龟缩头了!!!");
}
}
public class Test2 {
public static void main(String[] args) {
// 1、多态的好处1:右边的对象是解耦合的。
Animal a1 = new Tortoise();
a1.run();
// a1.shrinkHead(); // 报错,多态下不能调用子类独有的功能
// 2、多态的好处2:父类类型的变量作为参数,可以接受一个子类对象(go方法中,父类型Animal作为形参变量,可以接受Wolf和Tortoise类型的变量)
Wolf w = new Wolf();
go(w);
Tortoise t = new Tortoise();
go(t);
}
// 宠物游戏:所有动物都可以送给这个方法开始跑步
public static void go(Animal w) {
System.out.println("开始......");
w.run();
// a1.shrinkHead(); // 报错,多态下不能调用子类独有的功能
}
}
执行结果:
乌龟跑的贼慢!!!
开始......
狼跑的贼溜~~~
开始......
乌龟跑的贼慢!!!
3.3.多态下的类型转换——调用子类的功能
-
多态下会产生一个问题,怎么解决?
多态下不能调子类独有的功能——解决方法:多态下的类型转换(也称为向下转型)
-
多态下的类型转换:
自动类型转换:父类 变量名 = new 子类();
强制类型转换:子类 变量名 = (子类) 父类变量;
-
强制类型转换的注意事项:
- 存在继承、实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错。
- 运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误出来。
因为它涉及到将一个父类类型的引用转换为具体的子类类型。这种转换在编译时不会报错,只要存在继承或实现关系,但在运行时如果对象的实际类型与目标类型不匹配,则会抛出ClassCastException
。
public class Test3 { // 会用到Test2里面的:Animal类,Wolf类,Tortoise类
public static void main(String[] args) {
// 正确的类型转换
Animal a1 = new Tortoise();// 向上转型(隐式)
a1.run();
// 强制类型转换
Tortoise t1 = (Tortoise) a1; // 向下转型
t1.shrinkHead(); // 调用子类特有的方法
System.out.println("============================");
// 有继承关系就可以强制转换,编译阶段不会报错!!
// 运行时可能会出现类型转换异常:ClassCastException
Animal a2 = new Wolf(); // ===> Animal a1 = new Tortoise();
a2.run();
// 强制类型转换
Tortoise t2 = (Tortoise) a2;// 编译阶段不会报错,但是运行时会报错
t2.shrinkHead();
}
}
运行结果:
乌龟跑的贼慢!!!
乌龟缩头了!!!
============================
狼跑的贼溜~~~
Exception in thread "main" java.lang.ClassCastException: class sun.superstring.polymorphise3.Wolf cannot be cast to class sun.superstring.polymorphise3.Tortoise (sun.superstring.polymorphise3.Wolf and sun.superstring.polymorphise3.Tortoise are in unnamed module of loader 'app')
at sun.superstring.polymorphise3.Test1.main(Test1.java:21)
3.4.强制类型转换前的判断——instanceof关键字
-
类型转换异常解决:
强转前:使用 instanceof 关键字,判断当前对象的真实类型,再进行强转。
-
instanceof 使用:object instanceof Type
object
:要检查的对象。
Type
:你想要检查的类型(可以是类、接口或者抽象类)。
- 它返回一个布尔值:如果对象是指定类型的实例(包括该类型的子类),则返回 true;否则返回 false。
-
instanceof 使用注意事项:
-
null 值检查:对于 null
值,instanceof
总是返回 false
,因为 null
不指向任何对象。
String str = null;
System.out.println(str instanceof String); // false
-
编译期检查:如果 Type
不是 object
类型或其超类、接口之一,编译器会在编译阶段报错。例如,如果你尝试检查一个与对象无关的类型,如 Integer instanceof String
,编译器会报错。
-
泛型类型参数:instanceof
不能直接用于泛型类型参数,因为泛型信息在运行时会被擦除(类型擦除)。你可以使用原始类型来进行检查,但要注意这样做可能会失去泛型带来的类型安全性。
public class Test3 { // 会用到Test2里面的:Animal类,Wolf类,Tortoise类
public static void main(String[] args) {
// 正确的类型转换
Animal a1 = new Tortoise();// 向上转型(隐式)
a1.run();
// 强制类型转换
Tortoise t1 = (Tortoise) a1; // 向下转型
t1.shrinkHead(); // 调用子类特有的方法
System.out.println("============================");
// 调用独特功能
Animal w = new Wolf();
special(w);
Animal t = new Tortoise();
special(t);
}
// 宠物游戏:所有动物都可以送给这个方法开始跑步
public static void special(Animal w) {
System.out.println("开始......");
// Java建议:强制类型转换前,应该判断对象的真实类型,再进行强制类型转换
if(w instanceof Wolf){
Wolf w1 = (Wolf) w;
w1.eatSheep();
}else if(w instanceof Tortoise){
Tortoise t11 = (Tortoise) w;
t11.shrinkHead();
}
}
}
运行结果:
乌龟跑的贼慢!!!
乌龟缩头了!!!
============================
开始......
狼吃羊
开始......
乌龟缩头了!!!
4.其他
4.1.祖宗类Object3
- 继承的特点:祖宗类Object,Java中所有的类都是Object的子类。
Java中所有的类,要么直接继承了Object,要么默认继承了Object,要么间接继承了Object;因此Object是所有类的祖宗类,Object 功能任何类都可以用。
例如:Object类中的toString方法,会自动返回类的地址。
就近原则,优先访问自己类中,自己类中没有的才会访问父类。
4.2.开发规范
- 可以把多个类写在一个文件中,但是在开发中不建议这样写;
多个类写在一个文件中,但是只能有一个类用public修饰,就是公开类只能有一个暴露出去
4.3.解耦合
- 解耦合:遥控器没电了,拔出来换个电池就可以继续使用了。遥控器和电池是解耦合了
4.4.lombook注解@Data
- 注解:
- lombook技术可以实现为类自动添加getter 和 setter 方法,无参构造,重写toString 方法;
- 使用@Data注解可以调用lombook技术,自动生成getter 和 setter 方法,无参构造,toString 方法。
其中,@AllArgsConstructor注解提供所有的有参构造器,这个时候无参构造器没了,所以还需要加注解@NoArgsConstructor,提供无参构造器
- 如果不好用,这个类显示报错,那就自己写有参无参构造器还有getter,setter方法。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// lombook 可以实现为类自动添加getter 和 setter 方法,无参构造,toString 方法
// @Data注解可以调用lombook技术,可以自动生成getter 和 setter 方法,无参构造,toString 方法
@Data
@NoArgsConstructor // 提供无参构造器
@AllArgsConstructor // 提供所有的有参构造器
public class Card {
private String carId;
private String name;
private String phoneNumber;
private double money; // 账户余额
}
4.5.方法重载和方法重写的区别
- 重载和重写的区别:
① 重载指的是在一个类中定义多个方法名相同但参数列表不同的方法。编译器根据调用时传递的参数类型和数量来决定具体调用哪个方法。重载与返回类型无关,也就是说,仅改变返回类型而不改变参数列表的方法不会被视为重载。
② 重写是指子类重新定义从父类继承而来的方法。重写的方法必须具有相同的名称、参数列表以及返回类型(或者返回类型的协变)。此外,重写方法的访问级别不能比被覆盖的方法更严格,并且重写方法不能抛出新的检查异常或更广泛的异常。
- 总结区别:
面向对象的三大特征:封装、继承、多态。
类就是一种封装:类就是把要处理的对象的数据,以及对这些数据进行处理的方法封装成一个架子。
例如:日常中,手机、洗衣机、汽车……等都是封装。
封装的设计要求:合理隐藏,合理暴漏。
哪些需要隐藏,哪些需要暴露,怎么隐藏,怎么暴露?
如何隐藏:使用private关键字修饰成员变量,就只能在本类中访问,其他任何地方不能直接访问。
如何暴露(合理暴露):使用public修饰(公开)的getter和setter方法合理暴露。
面向对象的高级语法:继承、多态。
面向对象的高级语法之一:继承。
什么是继承?为什么要有继承?
定义的javabean实体类中会有大量的重复代码,怎么把重复的代码抽出来,让其他实体类共同使用呢?——即所有人都叫他爸爸。(减少一些重复代码的使用)
Java中提供了一个关键字extends,用这个关键字,可以让一个类和另一个类建立起父子关系。这就是继承。
public class B extends A{
}
// A类称为父类(基类、超类)
// B类称为子类(派生类)
子类能继承啥?
子类能继承父类的非私有成员(成员变量、成员方法)
继承后对象的创建:
子类的对象是由子类、父类共同完成的
子类对象其实是由子类和父类多张设计图共同创建出来的对象,所以子类对象是完整的。
什么是权限修饰符?
就是用来限制类中的成员(成员变量、成员方法、构造器)能够被访问的范围。
权限修饰符:
public
:公开访问。protected
:同一包内或者不同包中的子类可访问。即本类,同一个包中的类,子孙类。- 默认(无修饰符):仅限于同一个包内的类访问。即本类,同一个包中的其他类。
private
:仅限于本类内部访问。
其中,public > protected > 默认(无修饰符)> private
权限修饰符的常用情境:
成员变量大概率会private修饰,
成员方法大概率会public修饰,
而缺省和protected一般不会使用。
单继承,一个类只能继承一个父类,只能有一个爸爸;
多层继承,Java不支持多继承,但支持多层继承,就是爸爸也会继承;
用反证法,假设A、B中都有个method方法,A是复习语文的,B是复习数学的
假如C可以同时继承 A和B,如果用C创建对象,C可以调用父类的方法,这是C就不知道去调用A还是B的方法。
① 在子类中访问其他成员,是依照就近原则的,先在子类局部范围找,然后子类成员范围找,然后父类成员范围找,如果父类范围还没有找到则报错。
② 如果子父类中,出现了重名的成员,会优先使用子类的成员。
如果此时一定要在子类中使用父类的成员怎么办?
可以通过super关键字,指定访问父类的成员。
super.父类成员变量;
super.父类成员方法;
当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时(即子类重新定义了父类中的一个已有的方法),子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
- 方法名称,形参列表必须一样,这个方法就是方法重写。
- @Override,方法重写的校验注解(标志):要求方法名称和形参列表必须与被重写方法一致,否则报错!(更安全,可读性更好,更优雅)
- 子类重写父类的方法时,访问权限必须大于或者等于父类该方法的权限,即不能从public变为protected或private,并且抛出的异常范围也应保持一致或更小。(public > protected > 缺省)
- 重写的方法,返回值类型必须与被重写方法的返回值类型一样,或者范围更小;
- 私有方法(private),静态方法(static修饰)不能被重写,如果重写会报错;
- 通常用于实现多态性,允许使用父类类型的引用指向子类对象,并调用子类中重写的方法;
- 重写的规范:声明不变,重新实现。
public class Test {
public static void main(String[] args) {
Cat cat = new Cat();
// 实现多态性 可以这么写:
// Animal cat = new Cat();
cat.cry();
}
}
class Animal{
public void cry(){
System.out.println("动物会叫~~~ ");
}
}
class Cat extends Animal{ // Cat类继承Animal类
// 重写的规范:声明不变,重新实现。
@Override // 方法重写的校验注解(标志)
public void cry(){ // 方法重写:方法名称,形参列表必须一样
System.out.println("猫 喵喵喵的叫~~~");
}
}
子类重写Object类的toString()方法,以便返回对象的内容。
当返回的对象是一个地址时,说明没有重写toString方法;
当返回的对象是一个对象内容时代表重写了toString方法。
public class Test2 {
public static void main(String[] args) {
Student s = new Student("赵敏",'女',25);
System.out.println(s);
System.out.println(s.toString());
}
}
class Student{
private String name;
private char sex;
private int age;
// 重写的toString方法
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex=" + sex +
", age=" + age +
'}';
}
// 无参构造器
public Student() {
}
// 有参构造器
public Student(String name, char sex, int age) {
this.name = name;
this.sex = sex;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在Java中,方法重载(Overloading)和方法重写(Overriding)是面向对象编程中的两个重要概念,它们各自有不同的含义、用途和规则。
方法重载(Overloading)
方法重载指的是在一个类中定义多个同名但参数列表不同的方法。这些方法可以有不同的参数类型、不同数量的参数,或者两者兼有。返回值类型也可以不同,但是不能仅凭返回值类型来区分重载的方法。
方法重载的特点
- 发生在同一类中。
- 方法名称相同,但参数列表不同(参数的数量或类型不同)。
- 可以有不同的返回类型,但这不是区分重载方法的标准。
- 与访问修饰符无关。
public class Example {
void show(int a) {
System.out.println("Integer: " + a);
}
// 重载方法1
void show(String b) {
System.out.println("String: " + b);
}
// 重载方法2
void show(int a, String b) {
System.out.println("Both: " + a + ", " + b);
}
}
方法重载(Overloading)和方法重写(Overriding)的区别:
- 方法重载关注的是“同一个类中”方法的不同版本,主要通过不同的参数列表来区分。
- 方法重写则涉及到“父子类之间”的方法替换,要求方法签名完全一致,主要用于实现多态性和特定行为的定制化。
子类构造器特点:子类的全部构造器,都会先调用父类的构造器,再执行自己。
子类构造器是如何实现调用父类构造器的?
- 默认情况下,子类全部构造器的第一行代码都是调用父类的无参构造器。
super()
(写不写都有),它会调用父类的无参构造器; - 如果父类没有无参构造器,则我们必须在所有子类构造器的第一行手写
super(xxx)
,指定去调用父类的有参构造器。 - 子类构造器可以通过调用父类构造器,把对象中包含父类这部分的数据先初始化赋值,
再回来把对象里包含子类这部分的数据也进行初始化赋值。 - 在Java中,子类的构造器不能同时调用多个父类的构造器。每个子类构造器的第一行要么隐式或显式地调用一个父类构造器(通过
super()
或super(参数列表)
),要么在同一类中调用该类的另一个构造器(通过this()
或this(参数列表)
)即兄弟构造器。这意味着你只能选择其中一个父类构造器进行调用。
public class Test {
public static void main(String[] args) {
Zi zi = new Zi(); // 新建一个子类的对象
}
}
class Zi extends Fu{ // Zi类继承Fu类
public Zi(){ // Zi类无参构造器
// super(); // (如果父类有无参构造器)默认存在的,写不写都有
// super(666); // 指定调用Fu类的有参构造器
// 如果父类没有无参构造器,则必须在子类构造器的第一行写这个super(666),指定去调用父类的有参构造器。
System.out.println("子类无参构造器");
}
}
class Fu{
public Fu(){ // 无参构造器,私有的时,不能被子类继承
System.out.println("父类无参构造器");
}
public Fu(int a){ // 有参构造器
System.out.println("父类有参构造器");
}
}
执行语句:
父类无参构造器
子类无参构造器
this(xxx)
关键字,调用兄弟构造器:注意:
super(...)
this(...)
必须写在构造器的第一行,但是两者不能同时出现;因为this调用兄弟构造器时,兄弟构造器肯定先找爸爸构造器,此时super再执行,就是找第二遍爸爸构造器了。
public class Student {
private String name;
private char sex;
private int age;
private String school;
public Student() {
}
public Student(String name, char sex, int age) {
// this调用兄弟构造器
// 注意:super(...) this(...)必须写在构造器的第一行,而且两者不能同时出现
this(name,sex,age,"家里蹲大学"); // 悄悄使用兄弟构造器
// super(); // this下面第二行不能使用super关键字调用父类的构造器
// 当前构造器是传入三个形参变量,但是要求该类在初始化赋值时,
// 如果没有传入school变量,该类能够自动对school变量进行赋值"XXX大学"
// 所以该构造器要手动调用它的兄弟构造器(传入四个形参变量),其中一个手动赋值。
// this.name = name;
// this.sex = sex;
// this.age = age;
// this.school = "黑马程序员";
}
public Student(String name, char sex, int age, String school) {
this.name = name;
this.sex = sex;
this.age = age;
this.school = school;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", sex=" + sex +
", age=" + age +
", school='" + school + '\'' +
'}';
}
}
Animal a1 = new Wolf(); // 对象多态:父类引用指向子类对象
Animal a2 = new Tortoise();
有继承、实现关系;
父类引用子类对象;
存在方法重写。(只有方法重写才能出现行为多态)
public class Animal {
String name = "Animal";
public void run(){
System.out.println("Animal is running");
}
}
public class Wolf extends Animal{
String name = "狼";
@Override
public void run() {
System.out.println("狼跑的贼溜~~~");
}
}
public class Tortoise extends Animal{
String name = "乌龟";
@Override
public void run() {
System.out.println("乌龟跑的贼慢!!!");
}
}
public class Test1 {
public static void main(String[] args) {
// 1、对象多态
Animal a1 = new Wolf(); // 父类引用指向子类对象
a1.run(); // 方法:编译看左边,运行看右边
// 当Animal中没有run方法时,编译报错
// 当运行run方法时,会去Wolf中找run方法运行
Animal a2 = new Tortoise();
a2.run(); // 方法:编译看左边,运行看右边
// 成员变量:编译看左边,运行也看左边
System.out.println(a1.name); // Animal
System.out.println(a2.name); // Animal
// 使用父类类型数组管理不同子类对象
Animal[] animals = {a1, a2};
for (Animal animal : animals) {
animal.run(); // 动态绑定,运行时确定调用哪个子类的方法
}
}
}
执行结果:
狼跑的贼溜~~~
乌龟跑的贼慢!!!
Animal
Animal
狼跑的贼溜~~~
乌龟跑的贼慢!!!
在多态的形式下,右边的对象是解耦合的,更便于扩展和维护;
定义方法时,使用父类类型的形参,可以接受一切子类对象,扩展性更强,更便利。
public class Animal {
String name = "Animal";
public void run(){
System.out.println("Animal is running");
}
}
public class Wolf extends Animal {
String name = "狼";
@Override
public void run() {
System.out.println("狼跑的贼溜~~~");
}
// 子类独有的功能:狼吃羊
public void eatSheep() {
System.out.println("狼吃羊");
}
}
public class Tortoise extends Animal {
String name = "乌龟";
@Override
public void run() {
System.out.println("乌龟跑的贼慢!!!");
}
// 子类独有的功能:乌龟缩头
public void shrinkHead() {
System.out.println("乌龟缩头了!!!");
}
}
public class Test2 {
public static void main(String[] args) {
// 1、多态的好处1:右边的对象是解耦合的。
Animal a1 = new Tortoise();
a1.run();
// a1.shrinkHead(); // 报错,多态下不能调用子类独有的功能
// 2、多态的好处2:父类类型的变量作为参数,可以接受一个子类对象(go方法中,父类型Animal作为形参变量,可以接受Wolf和Tortoise类型的变量)
Wolf w = new Wolf();
go(w);
Tortoise t = new Tortoise();
go(t);
}
// 宠物游戏:所有动物都可以送给这个方法开始跑步
public static void go(Animal w) {
System.out.println("开始......");
w.run();
// a1.shrinkHead(); // 报错,多态下不能调用子类独有的功能
}
}
执行结果:
乌龟跑的贼慢!!!
开始......
狼跑的贼溜~~~
开始......
乌龟跑的贼慢!!!
多态下会产生一个问题,怎么解决?
多态下不能调子类独有的功能——解决方法:多态下的类型转换(也称为向下转型)
多态下的类型转换:
自动类型转换:父类 变量名 = new 子类();
强制类型转换:子类 变量名 = (子类) 父类变量;
强制类型转换的注意事项:
- 存在继承、实现关系就可以在编译阶段进行强制类型转换,编译阶段不会报错。
- 运行时,如果发现对象的真实类型与强转后的类型不同,就会报类型转换异常(ClassCastException)的错误出来。
因为它涉及到将一个父类类型的引用转换为具体的子类类型。这种转换在编译时不会报错,只要存在继承或实现关系,但在运行时如果对象的实际类型与目标类型不匹配,则会抛出ClassCastException
。
public class Test3 { // 会用到Test2里面的:Animal类,Wolf类,Tortoise类
public static void main(String[] args) {
// 正确的类型转换
Animal a1 = new Tortoise();// 向上转型(隐式)
a1.run();
// 强制类型转换
Tortoise t1 = (Tortoise) a1; // 向下转型
t1.shrinkHead(); // 调用子类特有的方法
System.out.println("============================");
// 有继承关系就可以强制转换,编译阶段不会报错!!
// 运行时可能会出现类型转换异常:ClassCastException
Animal a2 = new Wolf(); // ===> Animal a1 = new Tortoise();
a2.run();
// 强制类型转换
Tortoise t2 = (Tortoise) a2;// 编译阶段不会报错,但是运行时会报错
t2.shrinkHead();
}
}
运行结果:
乌龟跑的贼慢!!!
乌龟缩头了!!!
============================
狼跑的贼溜~~~
Exception in thread "main" java.lang.ClassCastException: class sun.superstring.polymorphise3.Wolf cannot be cast to class sun.superstring.polymorphise3.Tortoise (sun.superstring.polymorphise3.Wolf and sun.superstring.polymorphise3.Tortoise are in unnamed module of loader 'app')
at sun.superstring.polymorphise3.Test1.main(Test1.java:21)
类型转换异常解决:
强转前:使用 instanceof 关键字,判断当前对象的真实类型,再进行强转。
instanceof 使用:object instanceof Type
object
:要检查的对象。Type
:你想要检查的类型(可以是类、接口或者抽象类)。- 它返回一个布尔值:如果对象是指定类型的实例(包括该类型的子类),则返回 true;否则返回 false。
instanceof 使用注意事项:
-
null 值检查:对于
null
值,instanceof
总是返回false
,因为null
不指向任何对象。String str = null; System.out.println(str instanceof String); // false
-
编译期检查:如果
Type
不是object
类型或其超类、接口之一,编译器会在编译阶段报错。例如,如果你尝试检查一个与对象无关的类型,如Integer instanceof String
,编译器会报错。 -
泛型类型参数:
instanceof
不能直接用于泛型类型参数,因为泛型信息在运行时会被擦除(类型擦除)。你可以使用原始类型来进行检查,但要注意这样做可能会失去泛型带来的类型安全性。
public class Test3 { // 会用到Test2里面的:Animal类,Wolf类,Tortoise类
public static void main(String[] args) {
// 正确的类型转换
Animal a1 = new Tortoise();// 向上转型(隐式)
a1.run();
// 强制类型转换
Tortoise t1 = (Tortoise) a1; // 向下转型
t1.shrinkHead(); // 调用子类特有的方法
System.out.println("============================");
// 调用独特功能
Animal w = new Wolf();
special(w);
Animal t = new Tortoise();
special(t);
}
// 宠物游戏:所有动物都可以送给这个方法开始跑步
public static void special(Animal w) {
System.out.println("开始......");
// Java建议:强制类型转换前,应该判断对象的真实类型,再进行强制类型转换
if(w instanceof Wolf){
Wolf w1 = (Wolf) w;
w1.eatSheep();
}else if(w instanceof Tortoise){
Tortoise t11 = (Tortoise) w;
t11.shrinkHead();
}
}
}
运行结果:
乌龟跑的贼慢!!!
乌龟缩头了!!!
============================
开始......
狼吃羊
开始......
乌龟缩头了!!!
Java中所有的类,要么直接继承了Object,要么默认继承了Object,要么间接继承了Object;因此Object是所有类的祖宗类,Object 功能任何类都可以用。
例如:Object类中的toString方法,会自动返回类的地址。
就近原则,优先访问自己类中,自己类中没有的才会访问父类。
多个类写在一个文件中,但是只能有一个类用public修饰,就是公开类只能有一个暴露出去
- lombook技术可以实现为类自动添加getter 和 setter 方法,无参构造,重写toString 方法;
- 使用@Data注解可以调用lombook技术,自动生成getter 和 setter 方法,无参构造,toString 方法。
其中,@AllArgsConstructor注解提供所有的有参构造器,这个时候无参构造器没了,所以还需要加注解@NoArgsConstructor,提供无参构造器 - 如果不好用,这个类显示报错,那就自己写有参无参构造器还有getter,setter方法。
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
// lombook 可以实现为类自动添加getter 和 setter 方法,无参构造,toString 方法
// @Data注解可以调用lombook技术,可以自动生成getter 和 setter 方法,无参构造,toString 方法
@Data
@NoArgsConstructor // 提供无参构造器
@AllArgsConstructor // 提供所有的有参构造器
public class Card {
private String carId;
private String name;
private String phoneNumber;
private double money; // 账户余额
}
① 重载指的是在一个类中定义多个方法名相同但参数列表不同的方法。编译器根据调用时传递的参数类型和数量来决定具体调用哪个方法。重载与返回类型无关,也就是说,仅改变返回类型而不改变参数列表的方法不会被视为重载。
② 重写是指子类重新定义从父类继承而来的方法。重写的方法必须具有相同的名称、参数列表以及返回类型(或者返回类型的协变)。此外,重写方法的访问级别不能比被覆盖的方法更严格,并且重写方法不能抛出新的检查异常或更广泛的异常。
特性 | 重载(Overloading) | 重写(Overriding) |
---|---|---|
发生地点 | 同一个类内 | 父类与子类之间 |
方法签名 | 必须不同(参数列表不同) | 必须相同(包括参数列表和返回类型) |
返回类型 | 不影响是否为重载 | 必须相同或返回类型的协变 |
访问修饰符 | 可以不同 | 子类方法的访问权限不能比父类方法更严格 |
异常处理 | 没有特殊限制 | 子类方法不能抛出新的检查异常或更广泛的异常 |
决定调用时机 | 编译时 | 运行时 |
5. 参考资料
- 黑马程序员:黑马程序员Java+AI智能辅助编程全套视频教程,java零基础入门到大牛一套通关_哔哩哔哩_bilibili
- 通义千问:通义 - 你的实用AI助手
用户点评