一、⭐多态⭐
1.1 概述
1.多态 是在继承/实现情况下的一种现象, 表现为对象多态和行为多态 2.⭐对象多态写法: 继承: 父类 变量 = new 子类1(); 父类 变量 = new 子类2(); 实现: 接口 变量 = new 实现类(); 3.多态的前提(对象多态的前提为1,2条;行为多态的前提为这3条) 1. 有继承/实现关系 2. 存在父类引用指向子类对象 3. 有方法的重写 4.多态的注意事项 多态是对象、行为的多态; 变量则不涉及多态 5.代码执行: 编译看左,运行看右
//父类
public class People {
private int age;
private String name;
//定义父类People, 内含跑步run方法
public void run(){
System.out.println("父类People, 跑步run方法");
}
}
//子类
public class Student extends People{
@Override
public void run() {
System.out.println("学生跑");
}
}
public class Teacher extends People{
@Override
public void run() {
System.out.println("老师跑");
}
}
//测试类
public class Demo {
public static void main(String[] args) {
//使用多态的形式创建Student对象
People p1 = new Student();
p1.run();
//使用多态的形式创建Teacher对象
People p2 = new Teacher();
p2.run();
}
}
1.2 多态的好处和弊端
多态的好处 1. 在多态形式下,等号左右两边松耦合,更便于修改和维护 2. 在多态下, 定义方法时, 可以使用父类类型作为形参, 那么该方法可以接收该父类下所有子类的对象
public class Demo {
public static void main(String[] args) {
//好处1: 在多态形式下,等号左右两边松耦合,更便于修改和维护
//创建一个老师对象,调用5次跑步的方法=======有一天需求改了========>创建一个学生对象,调用5次跑步的方法
//使用普通写法
Teacher teacher = new Teacher();//改成学生对象的话,需要整体修改
teacher.run();
teacher.run();
teacher.run();
teacher.run();
teacher.run();
//*使用多态的写法
Person person = new Teacher();//改成学生对象的话,直接就将Teacher 改成 Student就完成了
person.run();
person.run();
person.run();
person.run();
person.run();
------------------------------------------------------------------
//调用login方法,可以使用父类类型作为形参, 那么该方法可以接收该父类下所有子类的对象
Person person1 = new Student();
person1.login();//学生登录
Person person2 = new Teacher();
person2.login();//老师登录
}
//好处2: 在多态下, 定义方法时, 可以使用父类类型作为形参, 那么该方法可以接收该父类下所有子类的对象
//创建一个login方法,接收一个老师对象,然后调用对象的login方法=======有一天需求改了========>接收一个学生对象,然后调用对象的login方法
//使用普通写法
public static void login(Teacher teacher){//改成学生对象的话,需要整体修改
teacher.login();
}
//*使用多态的写法
public static void login(Person person){//直接让父类类型作为形参
person.login();
}
}
//父类
class Person{
public void run(){}
public void login(){}
}
//学生子类
class Student extends Person{
@Override
public void run() {
System.out.println("学生跑的快");
}
@Override
public void login() {
System.out.println("学生登录");
}
}
//老师子类
class Teacher extends Person{
@Override
public void run() {
System.out.println("老师跑的慢");
}
@Override
public void login() {
System.out.println("老师登录");
}
}
多态的弊端 不能直接使用子类特有的功能 解决方案: 强制类型转换 多态中的转型 子-->父 (小到大 自动转换): 也称为向上转型, 父类引用指向子类对象 Person p = new Student(); 父-->子 (大到小 强制转换): 也称为向下转型, 父类引用转为子类对象 Student s = (Student)p; 强转风险 强转是存在风险的, 如果转为父类引用记录的真实子类对象,那么不会报错(否则会报ClassCastException) 如果想规避这个风险,可以在强转前,使用instanceof关键字, 判断变量对应的类型
public class Demo {
public static void main(String[] args) {
//1.多态创建一个学生对象
Person p1 = new Student();
//Person p1 = new Teacher();//强转时报错ClassCastException:真实类型与强转类型不一致
System.out.println(p1);
//2.调用方法
p1.run();
//3.调用学生独有功能
//4.判断真实类型与强转类型不一致 语法:变量 instanceof 类型
//报错ClassCastException:真实类型与强转类型不一致
if (p1 instanceof Student){
//将p1转化为子类的对象(父类转化为子类,用强转)
Student s1 = (Student) p1;
s1.study();
}else {
System.out.println("真实类型与强转类型不一致");
}
System.out.println("------------------------------------------------");
Person p2 = new Teacher();
method(p2);
}
public static void method(Person person){
//调用老师的run()方法
person.run();
//需求: 想再调用一下老师的teach()方法
if(person instanceof Teacher){
Teacher t1 = (Teacher) person;
t1.teach();
}else {
System.out.println("真实类型与强转类型不一致");
}
}
}
class Person{
public void run(){}
}
class Student extends Person {
@Override
public void run() {
System.out.println("学生跑的快");
}
//独有方法
public void study(){
System.out.println("学生在学习~~~~");
}
}
class Teacher extends Person {
@Override
public void run() {
System.out.println("老师跑的慢");
}
//独有方法
public void teach(){
System.out.println("老师在上课~~~~");
}
}
运行结果:
二、final
final关键字是最终的意思,可以修饰(类、方法、变量) ⭐ 修饰类:该类被称为最终类, 类不能再被继承 ⭐ 修饰方法:该方法被称为最终方法, 方法不能被重写 ⭐ 修饰变量:该变量只能被赋值一次, 赋值完毕之后不能再修改 成员变量: 声明时赋完值,或者在构造方法结束之前完成赋值 局部变量: 变量只能被赋值一次
⭐final修饰变量的注意事项 基本类型变量: 变量记录的数据不能再被改变 引用类型变量: 变量记录的地址不能再被改变, 但是地址对应的堆内存中的内容可以改变
public class Demo {
public static void main(String[] args) {
//基本类型变量: 变量记录的数据不能再被改变
final int age = 10;
//age = 20;//编译报错, 因为age被final修饰了, 不能再被赋值
System.out.println(age);
final Son son = new Son();
//son = new Son();//编译报错, 因为son被final修饰了, 不能再被赋值
son.run();
//引用类型变量: 变量记录的地址不能再被改变, 但是地址对应的堆内存中的内容可以改变
final int[] arr = {1,2,3};
arr[0] = 5;//内容可变
int[] arr1 = {1,2,4};
//arr = arr1;//编译报错, 因为arr被final修饰了, 地址不能改变
}
}
class Father {
public void run() {
System.out.println("爸爸在跑");
}
}
class Son extends Father {
final String name = "lisi";
String school;
@Override
public void run() {
int age = 10;
//name = "zhangsan"; //编译报错, 因为name被final修饰了, 不能再被赋值
System.out.println(age + "岁的儿子在跑");
}
}
三、抽象类
3.1 抽象类概述
1.抽象类和抽象方法 在Java中有一个关键字叫:abstract,它就是抽象的意思,可以用它修饰类、成员方法。 abstract修饰类,这个类就是抽象类 abstract修饰方法,这个方法就是抽象方法,抽象方法没有方法体 2.语法: 修饰符 abstract class 类名{ 修饰符 abstract 返回值类型 方法名称(形参列表); } 3.抽象类的特点 1. 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类。 2. 类该有的成员(成员变量、方法、构造器)抽象类都可以有。 3. 抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。 4. 一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
public class Demo {
public static void main(String[] args) {
Person p1 = new Student();
p1.run();
Person p2 = new Teacher();
p2.run();
//Person p3 = new Person();//抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现。
}
}
//需求1: 将Person类声明为抽象类
abstract class Person {
//成员变量
private String name;
//成员方法
public void eat(){
System.out.println("吃饭");
}
//需求2: 将run方法修改为抽象方法
public abstract void run();
}
class Student extends Person{
@Override
public void run() {
System.out.println("学生跑步");
}
}
class Teacher extends Person{//鼠标放在这里看提示,Alt+Enter
@Override
public void run() {
System.out.println("老师跑步");
}
}
//一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类。
abstract class Employee extends Person{
}
3.2 抽象类的场景和好处
抽象类的应用场景和好处 1、将所有子类中重复的代码,抽取到抽象的父类中,提高了代码的复用性(先编写子类,再编写抽象类) 2、我们不知道系统未来具体的业务时,可以先定义抽象类,将来让子类去继承实现,提高了代码的扩展性 (先编抽象类,再编写子类)
/*
需求
某宠物游戏,需要管理猫、狗的数据。
猫的数据有:名字;行为是:喵喵喵的叫~
狗的数据有:名字;行为是:汪汪汪的叫~
*/
public class Demo {
public static void main(String[] args) {
Animal cat = new Cat();
cat.setName("小花");//小花喵喵喵的叫~
cat.cry();
System.out.println("-------------------");
Animal dog = new Dog();
dog.setName("小黑");//小黑汪汪汪的叫~
dog.cry();
}
}
//动物的父类,公共的属性和方法
abstract class Animal {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract void cry();
}
//猫
class Cat extends Animal {
@Override
public void cry() {
System.out.println(getName() + "喵喵喵的叫~");
}
}
//狗
class Dog extends Animal {
@Override
public void cry() {
System.out.println(getName() + "汪汪汪的叫~");
}
}
3.3 模板方法设计模式(为了解决某些特定问题的编码套路)
设计模式 对于某类问题,前人总结出类的解决问题的套路 模板方法设计模式用来解决什么问题? 1.一个功能的完成需要经过一系列步骤,这些步骤是固定的 2.但是中间某些步骤具体行为是待定的,在不同的场景中行为不同 使用思路 1、定义一个抽象类(Person作为父类),提供模板方法 2、模板方法中,需要让子类自己实现的地方,定义为抽象方法 3、子类(Teacher Student)只需要继承该抽象类,重写抽象方法即可完成对应的功能 多学一招: 建议使用final关健字修饰模板方法 模板方法是给对象直接使用的,不能被子类重写 一旦子类重写了模板方法,模板方法就失效了
public class Demo {
public static void main(String[] args) {
//创建对象
Person p1 = new Student();
//执行模板方法
p1.work();
System.out.println("-------------------");
Person p2 = new Teacher();
p2.work();
}
}
/**
* 抽象类模板
*/
abstract class Person {
/**
* 1.定义模板(必须具有执行的步骤和顺序)
* 建议使用final关健字修饰模板方法
* 模板方法是给对象直接使用的,不能被子类重写
* 一旦子类重写了模板方法,模板方法就失效了
*/
final public void work() {
System.out.println("1.吃饭");
dowork();
System.out.println("3.睡觉");
}
//2.定义抽象方法,让子类实现
public abstract void dowork();
}
/**
* 子类
*/
class Teacher extends Person {
@Override
public void dowork() {
System.out.println("2.教课");
}
}
class Student extends Person {
@Override
public void dowork() {
System.out.println("2.学习");
}
}
执行结果:
四、⭐接口⭐
4.1 概述
⭐接口 Java提供了一个关键字interface,用这个关键字我们可以定义出一个特殊的结构:接口 ⭐定义格式 public interface 接口名 { 成员变量(接口中的成员变量都是常量, 默认是被public static final修饰的) 成员方法(接口中的成员方法都是抽象方法, 默认是被public abstract修饰的) 注意: 接口中不能有构造方法和代码块 } ⭐注意事项 1. 接口不能直接创建对象 2. 接口是用来被类实现(implements)的,实现接口的类称为⭐实现类⭐。 3. 一个类可以实现多个接口,实现类实现多个接口,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类。 修饰符 class 实现类 implements 接口1, 接口2, 接口3 , ... { } ⭐步骤: 1.定义接口 : interface 2.定义接口实现类,实现上面接口中的所有抽象方法 : implements 3.测试类:创建 接口 变量 = new 实现类()
//定义接口
public interface UserInterface {
//定义成员变量,默认是public static final
String name = "张三";
//定义成员方法,默认是public abstract
void save();//抽象方法
}
/**
* 1. 定义一个实现类, 实现UserInterface接口
* 2.实现接口中的抽象方法
*/
public class UserInterfaceImpl implements UserInterface {
@Override
public void save() {
System.out.println("保存用户" + name);
}
}
// 测试类
public class Demo {
public static void main(String[] args) {
//面向接口编程,创建对象,调用方法
//接口 变量名 = new 实现类()
UserInterface ui = new UserInterfaceImpl();
ui.save();//保存用户张三
}
}
4.2 接口特点、好处
⭐接口特点、好处 让程序可以面向接口编程,这样程序员就可以灵活方便的切换各种业务实现。(解耦合) ⭐接口的作用:解耦 1.接口是用来定义规范 2.定义好xxx功能的方法名,参数,返回值类型(规范定义好,方法名不能随便改)
4.3 接口新特性(了解)
JDK8开始,接口中新增的三种方法 1、默认方法(jdk8开始支持):对接口中的方法提供默认实现 使用default修饰,有方法体,可以但是不强制要求实现类重写, 只能通过实现类的对象调用 2、静态方法(jdk8开始支持):方便调用 使用static修饰,有方法体,只能通过接口名调用 3、私有方法(jdk9开始支持):提高代码复用性 使用private修饰,服务于接口内部,用于抽取相同的功能代码
public class Demo {
public static void main(String[] args) {
Animal a = new Dog();
a.eat();
a.print1();// dog实现了default方法
System.out.println("-----------------");
Animal b = new Cat();
b.eat();
b.print1();// cat未实现default方法,默认调用接口的默认方法
System.out.println("-----------------");
Animal.print2();//dog实现了static方法
}
}
interface Animal{
void eat();
// 默认方法:有方法体,有默认的功能实现,实现类可以选择实现此方法,也可以不实现
default void print1(){
System.out.println("defaultPrint");
print3();//调用私有方法
}
// 静态方法:有方法体,只能通过接口名调用,不能通过实现类的对象调用
static void print2(){
System.out.println("staticPrint");
}
// 私有方法:有方法体,只能用于接口内部,用于抽取相同的功能代码
private void print3(){
System.out.println("privatePrint");
}
}
class Dog implements Animal{
public void eat(){
System.out.println("动物苏醒了");
System.out.println("动物开始用膳");
}
@Override
public void print1() {
System.out.println("dog实现了default方法");
}
}
class Cat implements Animal{
public void eat(){
System.out.println("动物苏醒了");
System.out.println("动物开始用膳");
}
}
4.4 注意事项(了解)
接口使用注感事项 1、一个接口继承多个接口,如果多个接口中存在方法签名冲突,则此时不支持多继承。 2、一个类实现多个接口,如果多个接口中存在方法签名冲突,则此时不支持多实现。 3、一个类实现了多个接口,多个接口中存在同名的默认方法,可以不冲突,这个类重写该方法即可。 4、一个类继承了父类,又同时实现了接口,父类中和接口中有同名的默认方法,实现类会优先用父类的
五、总结
类和接口的关系总结 1、类和类: 继承(extends)关系,对于类只支持单继承,不支持多继承,但是可以多层继承 2、接口和接口: 继承(extends)关系,对于接口,支持多继承 3、类和接口:实现(implements)关系,支持多实现,一个类同时实现多个接口
1. 抽象类 (Abstract Class)
定义: 抽象类是一个不能实例化的类,旨在作为其他类的基类。它可以包含抽象方法(没有实现)和具体方法(有实现)。
字段和构造函数: 抽象类可以有字段(成员变量)和构造函数,并且这些字段可以有初始化值。构造函数可以用来初始化对象的状态。
访问修饰符: 抽象类中的方法可以有不同的访问修饰符(如
private
、protected
、public
)。继承: 一个类只能继承一个抽象类,但可以实现多个接口。抽象类适用于需要共享代码和状态的情况。
用途: 当多个子类共享一些方法和字段时,使用抽象类是合适的。例如,抽象类可以定义一些通用的方法实现,并允许子类扩展或重写这些方法。
2. 接口 (Interface)
定义: 接口是一种纯粹的抽象类型,用于定义一组方法的签名(即方法名和参数),并可以提供一些方法的默认实现(从 Java 8 开始)。接口不能包含字段(除了
public static final
的常量)和构造函数。实现: 接口中的方法默认是
public
和abstract
(除非它们是default
或static
方法)。实现接口的类必须提供这些方法的具体实现。多重实现: 一个类可以实现多个接口,这样可以弥补 Java 单继承的局限性,允许类从多个来源获取功能。
用途: 接口适用于定义某些行为的契约,而这些行为可以由不同的类实现。这对于需要不同类之间共享某种行为而又不需要继承特定类时非常有用。例如,
Runnable
接口允许不同的类实现并定义线程的行为。总结
抽象类 适用于有一些共用实现和状态的场景,子类可以继承这些共用的实现。
接口 适用于定义行为契约,类可以实现多个接口,从而在不同的类中实现这些行为。
在实际使用中,选择使用抽象类还是接口取决于具体的设计需求和架构要求。
3.多态
Java中的多态主要通过以下两个方面实现:
(1)方法重写
方法重写是指子类提供对父类方法的具体实现,实现行动多态。在Java中,当子类重写父类的方法时,子类方法的签名必须与父类中的方法完全一致。
(2)接口实现 (Interface Implementation)
接口定义了一组方法,任何实现这个接口的类都必须提供这些方法的具体实现。接口使得不同的类可以实现相同的行为,但它们不需要有共同的祖先类。
多态的优势
灵活性: 多态允许程序在运行时决定调用哪个方法,提供了灵活的调用方式。
可扩展性: 通过接口和方法重写,新的类可以轻松地加入到现有系统中,而无需修改现有代码。
代码重用: 通过父类和接口定义通用的方法,子类可以重用这些方法的签名和行为,而只需实现具体的逻辑。
关键点
动态绑定: 多态是动态绑定的基础。Java 在运行时根据对象的实际类型决定调用哪个方法,而不是编译时。
继承和接口: 方法重写依赖于继承,而接口允许不同类实现相同的方法,都是实现多态的方式。
通过这两个机制,Java 提供了一种强大的方式来实现灵活的代码设计和运行时行为的变化。