杏仁玫瑰花饼的Java学习日记第十一天(super,方法重写,多态,instanceof关键字,static关键字,抽象类,接口)

一,前言

今天就是第十一天了,继续努力。

二,super关键字详解

在 Java 中,super关键字是一个引用变量,主要用于引用直接父类的对象。它在继承关系中扮演着重要角色,能够帮助子类访问父类的属性、方法和构造函数。

1. 访问父类的成员变量

当子类与父类拥有同名的成员变量时,子类默认访问自己的变量。使用super关键字可以显式访问父类的同名变量。

示例

class Parent {
    int num = 10;
}
​
class Child extends Parent {
    int num = 20;
    
    void display() {
        System.out.println("子类的num: " + num);           // 输出:20
        System.out.println("父类的num: " + super.num);    // 输出:10
    }

2. 调用父类的方法

如果子类重写(Override)了父类的方法,使用super关键字可以调用父类的原始方法。

示例

class Animal {
    void eat() {
        System.out.println("动物在吃东西");
    }
}
​
class Dog extends Animal {
    @Override
    void eat() {
        System.out.println("狗在吃骨头");
        super.eat();  // 调用父类的eat()方法
    }
}

3. 调用父类的构造函数

在子类的构造函数中,可以使用super()调用父类的构造函数。这必须是子类构造函数的第一条语句。如果要使用this也必须是第一条语句。

示例

class Parent {
    Parent() {
        System.out.println("父类的无参构造函数");
    }
    
    Parent(int num) {
        System.out.println("父类的带参构造函数: " + num);
    }
}
​
class Child extends Parent {
    Child() {
        super();  // 调用父类的无参构造函数(默认会添加,可省略)
        System.out.println("子类的构造函数");
    }
    
    Child(int num) {
        super(num);  // 调用父类的带参构造函数
        System.out.println("子类的带参构造函数");
    }
}

super注意点: 1.super调用父类的构造方法,必须在构造方法的第一个

2.super必须只能出现在子类的方法或者构造方法中!

3,super和this不能同时调用构造方法!

Vs this: 代表的对象不同: this:本身调用者这个对象 super:代表父类对象的

应用前提 this:没有继承也可以使用 super:只能在继承条件才可以使用 构造方法 this(); 本类的构造 super(): 父类的构造

通过合理使用super关键字,可以确保子类正确继承和扩展父类的特性,避免代码冗余,并增强类之间的协作性。

三,方法重写

方法重写的概念

方法重写是面向对象编程中的一个重要概念,指的是子类重新实现(覆盖)父类中已有的方法。当子类需要修改或扩展父类的行为时,就可以使用方法重写。

方法重写的条件

重写:需要有继承关系,子类重写父类的方法 1,方法名必频相同 2,参数列表必须相同 3,修饰符:范围可以扩大但不能缩小:public>Protected>Default>private 4,抛出的异常:范围,可以被缩小,但不能扩大:ClassNotFoundException->Exception(大) 重写,子类的方法和父类必须一致:方法体不同 为什么需要重写: 1,父类的功能,子类不一定需要,或者不一定满足!

例如:

class Animal {
    // 父类方法
    public void makeSound() {
        System.out.println("动物发出声音");
    }
    
    // 带参数的方法
    public void move(int distance) {
        System.out.println("动物移动了" + distance + "米");
    }
}
​
class Dog extends Animal {
    // 重写父类的makeSound方法
    @Override
    public void makeSound() {
        System.out.println("汪汪汪!");
    }
    
    // 重写父类的move方法
    @Override
    public void move(int distance) {
        System.out.println("狗狗欢快地跑了" + distance + "米");
    }
}
​
class Cat extends Animal {
    // 重写父类的makeSound方法
    @Override
    public void makeSound() {
        System.out.println("喵喵喵!");
    }
    
    // 重写父类的move方法,同时使用super调用父类方法
    @Override
    public void move(int distance) {
        super.move(distance); // 调用父类的move方法
        System.out.println("猫咪优雅地移动");
    }
}
​
public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.makeSound(); // 输出: 动物发出声音
        
        Dog dog = new Dog();
        dog.makeSound();    // 输出: 汪汪汪!
        dog.move(10);       // 输出: 狗狗欢快地跑了10米
        
        Cat cat = new Cat();
        cat.makeSound();    // 输出: 喵喵喵!
        cat.move(5);        // 输出: 动物移动了5米 猫咪优雅地移动
        
        // 多态性体现
        Animal myDog = new Dog();
        myDog.makeSound();  // 输出: 汪汪汪!
    }
}
​
  1. 方法重写的关键点

    1. @Override 注解

      • 虽然不是必需的,但强烈建议在重写方法时使用@Override注解。

      • 该注解帮助编译器验证方法是否正确重写了父类方法,如果没有正确重写会报错。

    2. super 关键字

      • 在子类的重写方法中,可以使用super.方法名()调用父类的原始方法。

      • 这在需要保留父类功能并添加新功能时非常有用。

    3. final 方法

      • 父类中被声明为final的方法不能被子类重写。

    4. 静态方法

      • 静态方法不能被重写,但可以被隐藏。子类可以定义与父类同名的静态方法,但这不是真正的重写,而是隐藏。

    5. 构造方法

      • 构造方法不能被重写,因为它们不是继承的。

通过合理使用方法重写,可以让代码更加符合开闭原则(对扩展开放,对修改关闭)。

四,多态

多态的概念

多态是面向对象编程的三大核心特性之一(另外两个是封装和继承),它允许不同类的对象通过相同的接口进行调用,从而实现 "一个接口,多种实现" 的效果。

多态的类型

多态主要分为两种类型:

  1. 编译时多态(静态多态):通过方法重载(Overloading)实现。

    • 在编译阶段确定调用哪个方法。

    • 基于参数类型、数量或顺序的不同来区分同名方法。

  2. 运行时多态(动态多态):通过方法重写(Overriding)和向上转型实现。

    • 在运行时根据对象的实际类型确定调用哪个方法。

    • 这是多态的核心表现形式。

运行时多态的实现条件

要实现运行时多态,必须满足以下三个条件:

  1. 继承关系:必须存在子类与父类的继承关系。

  2. 方法重写:子类必须重写父类的方法。

  3. 向上转型:父类引用指向子类对象。

// 父类:Shape
class Shape {
    // 定义一个方法,供子类重写
    public void draw() {
        System.out.println("绘制形状");
    }
    
    // 静态方法,用于演示静态绑定
    public static void staticMethod() {
        System.out.println("Shape的静态方法");
    }
}
​
// 子类:Circle
class Circle extends Shape {
    @Override
    public void draw() {
        System.out.println("绘制圆形");
    }
    
    // 子类的特有方法
    public void radius() {
        System.out.println("计算圆的半径");
    }
    
    // 静态方法,隐藏父类的静态方法
    public static void staticMethod() {
        System.out.println("Circle的静态方法");
    }
}
​
// 子类:Rectangle
class Rectangle extends Shape {
    @Override
    public void draw() {
        System.out.println("绘制矩形");
    }
}
​
// 测试类
public class Main {
    public static void main(String[] args) {
        // 向上转型:父类引用指向子类对象
        Shape s1 = new Circle();
        Shape s2 = new Rectangle();
        
        // 多态的表现:调用相同的方法,执行不同的实现
        s1.draw();  // 输出:绘制圆形
        s2.draw();  // 输出:绘制矩形
        
        // 静态方法不具有多态性,调用的是引用类型的方法
        s1.staticMethod();  // 输出:Shape的静态方法
        
        // 不能通过父类引用调用子类特有的方法
        // s1.radius();  // 编译错误
        //instanceof  类型转换~引用类型
        // 如果需要调用子类特有的方法,需要向下转型
        if (s1 instanceof Circle) {
            Circle c = (Circle) s1;  // 向下转型
            c.radius();  // 调用子类特有的方法
        }
        
        // 使用多态实现方法参数的灵活性
        drawShape(new Circle());    // 输出:绘制圆形
        drawShape(new Rectangle()); // 输出:绘制矩形
    }
    
    // 接受Shape类型参数的方法,可以处理任何Shape的子类,通过父类引用调用子类重写的方法
    public static void drawShape(Shape shape) {
        shape.draw();
    }
}
//与其他代码的关联
//第 63 行:drawShape(new Circle()) 传入 Circle 对象,输出 绘制圆形。
//第 64 行:drawShape(new Rectangle()) 传入 Rectangle 对象,输出 绘制矩形。

向上转型与向下转型的区别

向上转型和向下转型是面向对象编程中处理继承关系的两种重要机制,它们在方向、安全性和应用场景上有明显区别。

核心区别

特性 向上转型(Upcasting) 向下转型(Downcasting)
方向 子类 → 父类 父类 → 子类
语法 父类 引用 = new 子类() 子类 引用 = (子类)父类引用
安全性 隐式转换,安全 显式转换,可能抛出 ClassCastException
访问限制 只能访问父类定义的成员 可以访问子类特有的成员
动态绑定 支持多态 支持多态
常见场景 方法参数、集合存储多类型对象 需要调用子类特有方法时

常见应用场景

向上转型
  • 多态参数:方法接收父类类型,实际传入子类对象。

    void play(Animal a) { a.eat(); }  // 可传入Dog、Cat等

  • 集合泛型:统一存储不同子类对象。

    List zoo = new ArrayList<>();
    zoo.add(new Dog());
    zoo.add(new Cat());

向下转型
  • 需要调用子类特有方法

    void wash(Animal a) {
        if (a instanceof Dog) {
            ((Dog) a).bark();  // 调用Dog特有的方法
        }
    }

注意事项

  1. 避免不必要的转型

    • 优先设计合理的继承结构,减少转型需求。

  2. 向下转型的风险

    • 必须确保父类引用实际指向的是目标子类对象。

  3. 替代方案

    • 若频繁需要向下转型,可能是设计缺陷,考虑使用多态或接口实现。

总结

  • 向上转型是多态的基础,用于统一接口、简化代码。

  • 向下转型是向上转型的补充,用于扩展功能,但需谨慎使用。

合理运用两种转型,能让代码既灵活又安全,符合面向对象的设计原则。

五,instanceof关键字

nstanceof 是 Java 中的一个二元运算符,用于在运行时检查对象是否为特定类或接口的实例。它是处理向下转型时的安全保障,也是实现动态类型检查的核心工具。

1. 基本语法
对象引用 instanceof 类型
  • 返回值:布尔值(truefalse)。

  • 类型:可以是类、接口或数组类型。

2. 作用

  • 避免 ClassCastException:在向下转型前,先检查对象的实际类型。

  • 实现条件逻辑:根据对象类型执行不同的操作。

3. 示例

检查类的实例
class Animal {}
class Dog extends Animal {}
class Cat extends Animal {}
​
Animal animal = new Dog();
​
System.out.println(animal instanceof Animal);  // true:Dog是Animal的子类
System.out.println(animal instanceof Dog);     // true:animal实际是Dog对象
System.out.println(animal instanceof Cat);     // false:animal不是Cat类型

只要比较的双方有关系(父与子)就会返回true。

总结

  • instanceof 是运行时类型检查的关键工具,用于确保向下转型的安全性。

  • 合理使用 instanceof 可避免 ClassCastException,但需警惕过度使用导致的代码复杂度增加。

通过 instanceof,Java 提供了一种灵活且安全的方式来处理类型不确定的场景,是实现动态行为的重要手段。

六,static关键字详解

static 是 Java 中的一个重要关键字,用于修饰类的成员(字段、方法、内部类)和代码块。它将成员与类本身关联,而非与类的实例关联,从而改变成员的生命周期和访问方式。

1. 静态变量(类变量)

定义
  • static 修饰的字段属于类,而非实例。

  • 所有实例共享同一个静态变量,内存中只有一份拷贝。

特点
  • 生命周期:随类加载而创建,随类卸载而销毁。

  • 访问方式:可通过 类名.变量名 直接访问,无需创建实例。

  • 初始化时机:优先于实例变量,在类加载时初始化。

示例
class Student {
    static int totalStudents = 0;  // 静态变量:记录学生总数
    String name;
    
    Student(String name) {
        this.name = name;
        totalStudents++;  // 每创建一个实例,总数加1
    }
}
​
// 使用
System.out.println(Student.totalStudents);  // 输出:0
new Student("Alice");
System.out.println(Student.totalStudents);  // 输出:1

2. 静态方法(类方法)

定义
  • static 修饰的方法属于类,而非实例。

特点
  • 访问限制:

    • 只能直接访问类的静态成员(静态变量、静态方法)。

    • 不能直接访问非静态成员(需通过实例访问)。

  • 调用方式:可通过 类名.方法名 直接调用,无需创建实例。

  • this 引用:静态方法中没有 this 指针,因为它不属于任何实例。

示例
class MathUtil {
    static int add(int a, int b) {  // 静态方法
        return a + b;
    }
    
    int multiply(int a, int b) {  // 非静态方法
        return a * b;
    }
}
​
// 使用
int sum = MathUtil.add(3, 5);  // 直接通过类名调用
// int product = MathUtil.multiply(3, 5);  // 错误:需创建实例
int product = new MathUtil().multiply(3, 5);  // 正确

3. 静态代码块

定义
  • static 修饰的代码块,用于初始化静态变量。

特点
  • 执行时机:类加载时执行,且只执行一次。

  • 执行顺序优先于实例代码块和构造函数。

  • 用途:复杂的静态变量初始化逻辑(如读取配置文件)。

{
//匿名代码块
}
​
static{
//静态代码块
}

4. 静态与非静态的对比

特性 静态成员 非静态成员
归属 实例
访问方式 类名.成员 实例.成员
内存占用 一份拷贝 每个实例一份
生命周期 类加载到卸载 实例创建到销毁
访问限制 只能访问静态成员 可访问静态和非静态成员
使用场景 工具方法、全局配置 实例状态、行为

5.静态导入包

静态导入(Static Import)是 Java 5 引入的语法糖,用于简化对类的静态成员(静态变量、静态方法)的访问。通过静态导入,可以直接使用类的静态成员,而无需通过类名前缀调用。

1. 基本语法

导入特定静态成员

import static 包名.类名.静态成员;

导入类的所有静态成员

import static 包名.类名.*;
2. 为什么需要静态导入?

在没有静态导入的情况下,访问静态成员需要通过类名前缀:

double area = java.lang.Math.PI * radius * radius;
int max = java.util.Collections.max(list);

使用静态导入后,可以直接访问静态成员:

import static java.lang.Math.PI;
import static java.util.Collections.max;
​
double area = PI * radius * radius;  // 直接使用PI
int maxValue = max(list);           // 直接使用max方法
3. 静态导入的优缺点

优点

  • 代码简洁:减少重复的类名前缀,提高可读性。

  • 提升效率:对于频繁使用的静态成员(如 Math.PI),可简化代码。

  • 语法一致性:使静态方法调用与实例方法调用风格统一。

缺点

  • 降低可读性:过度使用会导致代码来源不明确(如 max() 可能来自多个类)。

  • 命名冲突:若导入多个类的同名静态成员,会导致编译错误。

  • 维护成本:团队成员需熟悉静态导入的来源,增加理解成本。

总结

  • 静态导入是简化代码的工具,但需谨慎使用,避免降低可读性。

  • 优先在工具类、常量类和测试框架中使用,保持代码简洁且表意明确。

  • 团队协作时需统一规范,避免因滥用导致代码维护困难。

通过合理运用静态导入,可以在保持代码清晰的同时,减少冗余,提升开发效率。了解一下就可以了

七,抽象类

抽象类是 Java 面向对象编程中的一种特殊类,它不能被实例化,主要用于定义子类的通用结构和行为。抽象类是实现多态和代码复用的重要工具,广泛应用于框架设计和接口规范。

1. 抽象类的定义

语法
abstract class 类名 {
    // 成员变量
    // 构造方法
    // 普通方法
    // 抽象方法(可选)
}
关键特性
  • 不能实例化:无法通过 new 创建抽象类的对象。

  • 可包含抽象方法:声明但不实现的方法,子类必须重写。

  • 可包含普通方法:提供默认实现,子类可继承或重写。

  • 构造方法:可定义构造方法,但仅用于子类初始化。

  • 就是一个约束

2. 抽象方法

定义

abstract 修饰的方法,只有方法签名,没有方法体:

abstract 返回类型 方法名(参数列表);
特点
  • 强制子类实现:子类必须重写所有抽象方法,否则子类也必须声明为抽象类。

  • 不能是私有或静态:抽象方法需要被子类重写,因此不能是 privatestatic

3. 抽象类的示例

// 抽象类:Shape
abstract class Shape {
    String color;
    
    // 构造方法
    public Shape(String color) {
        this.color = color;
    }
    
    // 抽象方法:计算面积
    public abstract double area();
    
    // 普通方法:显示颜色
    public void showColor() {
        System.out.println("颜色:" + color);
    }
}
​
// 子类:Circle
class Circle extends Shape {
    double radius;
    
    public Circle(String color, double radius) {
        super(color);  // 调用父类构造方法
        this.radius = radius;
    }
    
    @Override
    public double area() {  // 实现抽象方法
        return Math.PI * radius * radius;
    }
}
​
// 子类:Rectangle
class Rectangle extends Shape {
    double width, height;
    
    public Rectangle(String color, double width, double height) {
        super(color);
        this.width = width;
        this.height = height;
    }
    
    @Override
    public double area() {  // 实现抽象方法
        return width * height;
    }
}
​
// 使用
Shape circle = new Circle("红色", 5);
System.out.println("圆的面积:" + circle.area());  // 多态调用
circle.showColor();  // 继承的普通方法

4 注意事项

  1. 抽象类的子类

    • 必须实现所有抽象方法,否则子类需声明为抽象类。

  2. 抽象类的构造方法

    • 不能直接调用,但可被子类通过 super() 调用。

  3. 不能修饰局部变量

    • abstract 不能用于修饰方法内的局部变量。

  4. final 的互斥

    • 抽象类不能是 final(因为需被继承),抽象方法不能是 final(需被子类重写)

八,接口

普通类:只有具体实现

抽象类:具体实现和规范(抽象方法)都有

接口:只有规范!自己无法写方法~专业的约束~约束和实现分离:面向接口编程

接口就是规范,接口的本质就是契约、就像是法律,制定好后大家都遵守。

接口是 Java 中实现多重继承的一种机制,它定义了一组方法签名,但不包含实现。接口是 Java 面向对象设计的核心工具,广泛应用于框架设计、组件交互和设计模式中。

1. 接口的定义

语法:用interface声明

interface 接口名 {
    // 常量(默认 public static final)
    // 抽象方法(默认 public abstract)
    // 默认方法
    // 静态方法
    // 私有方法
}
关键特性
  • 不能实例化:接口不能直接创建对象。

  • 实现机制:类通过 implements 关键字实现接口,必须实现所有抽象方法。

  • 多重实现:一个类可同时实现多个接口,解决 Java 单继承的限制。

  • 变量默认修饰符:接口中的变量默认是 public static final(常量)。

  • 方法默认修饰符:接口中的方法默认是 public abstract(抽象方法)。

接口的示例
// 接口定义
interface Animal {
    // 常量(默认 public static final)
    int LEGS = 4;
    
    // 抽象方法(默认 public abstract)
    void eat();
    void sleep();
    
    // 默认方法(Java 8+)
    default void move() {
        System.out.println("动物在移动");
    }
    
    // 静态方法(Java 8+)
    static void info() {
        System.out.println("这是一个动物接口");
    }
}
​
// 实现接口
class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("狗在吃骨头");
    }
    
    @Override
    public void sleep() {
        System.out.println("狗在睡觉");
    }
    
    // 可选:重写默认方法
    @Override
    public void move() {
        System.out.println("狗在奔跑");
    }
}
​
// 使用
Dog dog = new Dog();
dog.eat();        // 输出:狗在吃骨头
dog.move();       // 输出:狗在奔跑
Animal.info();    // 调用静态方法:这是一个动物接口
System.out.println(Animal.LEGS);  // 访问常量:4

3. 接口的进化(Java 8+)

Java 8:默认方法和静态方法

  • 默认方法:用 default 修饰,提供默认实现,子类可选择性重写。

    interface Vehicle {
        default void start() {
            System.out.println("启动车辆");
        }
    }

  • 静态方法:用 static 修饰,属于接口本身,通过 接口名.方法名 调用。

    interface MathUtils {
        static int add(int a, int b) {
            return a + b;
        }
    }

4. 接口与抽象类的对比

特性 接口 抽象类
继承 / 实现 类实现接口(implements 类继承抽象类(extends
多重性 支持多实现 单继承
构造方法 不能有 可以有
成员变量 只能是 public static final 各种类型
方法实现 默认不能有(Java 8+ 支持 defaultstatic 可包含普通方法
设计目的 定义 "能做什么"(行为契约) 定义 "是什么"(类型抽象)

5. 接口的注意事项

接口默认方法冲突

  1. 冲突原因

当一个类同时实现两个接口,且这两个接口包含同名同参数的默认方法时,会发生冲突。Java 编译器无法确定应该调用哪个接口的默认方法,因此要求实现类必须显式重写该方法。

2. 解决方法

实现类必须在重写方法中显式指定要调用哪个接口的默认方法,语法为:

接口名.super.方法名(参数);
  1. 方法冲突

    • 若实现多个接口有同名默认方法,必须在实现类中重写该方法。

    interface A { default void m() { System.out.println("A"); } }
    interface B { default void m() { System.out.println("B"); } }
    ​
    class C implements A, B {
        @Override
        public void m() {
                // 必须显式指定调用哪个接口的默认方法
            A.super.m();  // 调用接口A的默认方法
        }
    }

  2. 接口继承

    • 接口可继承多个接口(interface A extends B, C)。

  3. 与抽象类的选择

    • 若需共享实现逻辑,选择抽象类。

    • 若只需定义行为契约,选择接口。

今天就到这里把,明天估计就可以学完Java基础了,然后就要去学前端三件套了。

你可能感兴趣的:(java,学习)