从零学编程--Java 继承

简介: 从零学编程--Java 继承

前言


兄弟们,奥列给就完了!运维需要学,编程也要学,干就完了,查漏补缺啥不会学啥,明天又是新的一天!!!冲着小资奔去~


Java 继承


来来来,他们来了,你将知道什么是继承继承有什么特点

如何实现继承方法重写的概念和实现

方法重写和方法重载是比较容易混淆的概念,我们也会介绍两个概念的区别,这些都是本小节的重点,本小节的最后我们还会介绍 super 关键字以及 final 关键字。


1. 概念和特点


1.1 概念


继承是面向对象软件技术当中的一个概念。如果一个类别 B “继承自” 另一个类别 A,就把这个 B 称为 “A 的子类”

而把 A 称为 “B 的父类别”

也可以称 “A 是 B 的超类”。

继承可以使得子类具有父类别的各种属性和方法,而不需要再次编写相同的代码。

Java 语言提供了类的继承机制。利用继承,新建的类可以在原有类的基础上,使用或者重写原有类的成员方法,访问原有类的成员变量。新建的类成为子类,而原有类为新建类的父类,如果 A 是 B 的父类,B 是 C 的父类,那么 C 也是 A 的子类。


1.2 特点


Java 中的继承为单一继承,也就是说,一个子类只能拥有一个父类,一个父类可以拥有多个子类。

可以类比我们人类,你只能有一个爸爸,而你的爸爸可以有多个儿子

另外,所有的 Java 类都继承自 Java.lang.Object,所以 Object 是所有类的祖先类,除了 Object 外,所有类都必须有一个父类。我们在定义类的时候没有显示指定其父类,它默认就继承自 Object 类。

子类一旦继承父类,就会继承父类所有开放的特征,不能选择性地继承父类特征。

继承体现的是类与类之间的关系,这种关系是 is a 的关系,也就是说满足 A is a B 的关系就可以形成继承关系。 下图展示了 Object 类、父类以及子类的树形关系:

image.png


2. 实现继承


定义父类 FatherClass


// 父类
class FatherClass {
    ...
}


在 Java 语言中,我们通过 extends 关键字声明一个类继承自另一个类:

// 子类
class SonCalss extends FatherClass {
    ...
}

例如,宠物猫和宠物狗都是宠物,都有昵称、年龄等属性,都有吃东西、叫喊等行为。我们可以定义一个父类:宠物类。并且宠物猫和宠物狗类都继承宠物类,继承树形图如下:

image.png

代码实现:

public class Pet {
    private String name;  // 昵称
    private int age;      // 年龄
    // getter 和 setter
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    // 吃东西
    public void eat() {
        System.out.println(this.getName() + "在吃东西");
    }
    // 叫喊
    public void shout() {
        System.out.println("宠物会叫");
    }
}

父类宠物类中 nameage 都是私有属性,而对应的 gettersetter 方法,eatshout 方法都是公有方法。

宠物狗类:

public class Dog extends Pet {
  // 特有属性体重
    private float weight;
    // getter和setter
    public float getWeight() {
        return weight;
    }
    public void setWeight(float weight) {
        this.weight = weight;
    }
    // 特有的方法 run
    public void run() {
        System.out.println("胖成了" + this.getWeight() + "斤的狗子在奔跑");
    }
}

宠物狗类有一个自己特有的属性 weight,还有一个特有的方法 run

宠物猫类:

public class Cat extends Pet {
    public void sleep() {
        System.out.println(this.getName() + "睡大觉zzz");
    }
}

宠物猫类有一个特有的方法 sleep,在方法中可以调用其父类 PetgetName 方法。

调用类的方法:

// 实例化一个宠物狗
Dog dog = new Dog();
dog.setName("张三");
dog.setWeight(30f);
// 调用继承自父类的公有方法
dog.eat();
// 调用其特有方法
dog.run();
// 实例化一个宠物猫
Cat cat = new Cat();
cat.setName("豆豆");
// 调用继承自父类的公有方法
cat.eat();
// 调用其特有方法
cat.sleep();

运行结果:

张三在吃东西
胖成了30.0斤的狗子张三在奔跑
豆豆在吃东西
豆豆睡大觉zzz


3. 方法重写


3.1 概念


如果一个类从它的父类继承了一个方法,如果这个方法没有被标记为 finalstatic,就可以对这个方法进行重写。重写的好处是:能够定义特定于子类类型的行为,这意味着子类能够基于要求来实现父类的方法。


3.2 实例


在上述父类 Pet 中有一个 shout 方法,我们知道小猫和小狗的叫声是不同的,此时可以使用方法重写,在 Dog 类和 Cat 类中重写 shout 方法。

Dog 类:

class Dog extends Pet{
  // 重写 shout 方法
    public void shout() {
        System.out.println(this.getName() + "汪汪汪地叫~");
    }
}

Cat 类:

class Cat extends Pet{
    @Override  // 使用注解
    public void shout() {
        System.out.println(this.getName() + "喵喵喵地叫~");
    }
}

Tips:在要重写的方法上面,可以选择使用 @Override 注解,让编译器帮助检查是否进行了正确的重写。如果重写有误,编译器会提示错误。虽然这个注解不是必须的,但建议日常编码中,在所有要重写的方法上都加上 @Override 注解,这样可以避免我们由于马虎造成的错误。

在IDEA里只要和父类方法名一致,就会自动生成@Overvide修饰符,意思是对父类的方法进行重写

可以使用对象实例调用其重写的方法:

// 实例化一个宠物狗
Dog dog = new Dog();
dog.setName("欢欢");
// 调用重写方法
dog.shout();
// 实例化一个宠物猫
Cat cat = new Cat();
cat.setName("豆豆");
// 调用重写方法
cat.shout();

运行结果:

张三汪汪汪地叫~
豆豆喵喵喵地叫~


3.3 方法重写规则


关于方法重写,有以下规则:

  • 重写方法的参数列表应该与原方法完全相同;
  • 返回值类型应该和原方法的返回值类型一样或者是它在父类定义时的子类型;
  • 重写方法访问级别限制不能比原方法高。例如:如果父类方法声明为公有的,那么子类中的重写方法不能是私有的或是保护的。具体限制级别参考访问修饰符;
  • 只有被子类继承时,方法才能被重写;
  • 方法定义为 final,将不能被重写;
  • 一个方法被定义为 static,将使其不能被重写,但是可以重新声明;
  • 一个方法不能被继承,那么也不能被重写;
  • 和父类在一个包中的子类能够重写任何没有被声明为 private 和 final 的父类方法;
  • 和父类不在同一个包中的子类只能重写 non-final 方法或被声明为 public 或 protected 的方法;
  • 一个重写方法能够抛出任何运行时异常,不管被重写方法是否抛出异常。然而重写方法不应该抛出比被重写方法声明的更新更广泛的已检查异常。重写方法能够抛出比被重写方法更窄或更少的异常;
  • 构造方法不能重写。


3.4 方法重写和方法重载的区别


Java 中的方法重写(Overriding)是说子类重新定义了父类的方法。方法重写必须有相同的方法名,参数列表和返回类型。覆盖者访问修饰符的限定大于等于父类方法。

而方法重载(Overloading)发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。


4. 访问修饰符


4.1 作用


我们使用 privatepublic 两种访问修饰符实现了对类的封装。现在终于到了详细了解访问修饰符的时候了。

为了实现对类的封装和继承,Java 提供了访问控制机制。通过访问控制机制,类的设计者可以掩盖变量和方法来达到维护类自身状态的目的,而且还可以将另外一些需要暴露的变量和方法提供给别的类进行访问和修改。


4.2 种类


Java 一共提供了 4 种访问修饰符:

  1. private:私有的,只允许在本类中访问;
  2. protected:受保护的,允许在同一个类、同一个包以及不同包的子类中访问;
  3. 默认的:允许在同一个类,同一个包中访问;
  4. public:公共的,可以再任何地方访问。

下表按照限定能力从大到小列出了访问修饰符在不同作用域的作用范围:


访问控制修饰符 同一个类 同一个包 不同包的子类 不同包的非子类
private(私有的)
default(默认的)
protected(受保护的)
public(公共的)


5. super 关键字


super 是用在子类中的,目的是访问直接父类的变量或方法。注意:

  • super 关键字只能调用父类的 public 以及 protected 成员;
  • super 关键字可以用在子类构造方法中调用父类构造方法;
  • super 关键字不能用于静态 (static) 方法中。


5.1 调用父类构造方法


父类的构造方法既不能被继承,也不能被重写。

可以使用 super 关键字,在子类构造方法中要调用父类的构造方法,语法为:


super(参数列表)

例如,父类 Pet 中存在构造方法:

package com.caq.oop.demo03;
public class Pet {
    public Pet(String name) {
        System.out.println("名字为"+name);
    }
}

子类 Cat 的构造方法中调用父类构造方法:

package com.caq.oop.demo03;
public class Cat extends Pet {
    //子类 `Cat` 的构造方法中调用父类构造方法
    public Cat(String name) {
        super(name);
        System.out.println("调用父类的构造方法调用成功!");
    }
    public static void main(String[] args) {
        new Cat("旺财");
    }
}

调用 Dog 有参构造方法,进行实例化:

new Dog("花花");

运行结果:

宠物实例被创建了,宠物名字为花花
小狗实例被创建了


5.2 调用父类属性


子类中可以引用父类的成员变量,语法为:

super.成员变量名

例如,在 Dog 类中调用父类的成员变量 birthday

class Pet {
    protected String birthday;
}
class Dog extends Pet {
    public Dog() {
        System.out.println("宠物生日:" + super.birthday);
    }
}

5.3 调用父类方法


有时候我们不想完全重写父类方法,可以使用 super 关键字调用父类方法,调用父类方法的语法为:

super.方法名(参数列表)

例如,Cat 类调用父类 Pet 的 eat 方法:

class Pet {
    public void eat() {
        System.out.println("宠物吃东西");
    }
}
class Cat extends Pet{
    public void eat() {
        // 在 eat 方法中调用父类 eat 方法
        super.eat();
        System.out.println("小猫饭量很小");
    }
}
class Test {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
    }
}

运行结果:

宠物吃东西
小猫饭量很小


5.4 super 与 this 的对比


this 关键字指向当前类对象的引用,它的使用场景为:

  • 访问当前类的成员属性和成员方法;
  • 访问当前类的构造方法;
  • 不能在静态方法中使用。

super 关键字指向父类对象的引用,它的使用场景为:

  • 访问父类的成员属性和成员方法;
  • 访问父类的构造方法;
  • 不能在静态方法中使用。

另外,需要注意的是,在构造方法调用时,super 和 this 关键字不能同时出现。


6. final 关键字


final 关键字可以作用于类、方法或变量,分别具有不同的含义。在使用时,必须将其放在变量类型或者方法返回之前,建议将其放在访问修饰符和 static 关键字之后,例如:


// 定义一个常量
public static final int MAX_NUM = 50;


6.1 final 作用于类


final 关键字用于类上面时,这个类不会被其他类继承:

final class FinalClass {
    public String name;
}
// final类不能被继承,编译会报错
public class SubClass extends FinalClass {  
}

编译执行,将会报错:

SubClass.java:1: 错误: 无法从最终FinalClass进行继承
public class SubClass extends FinalClass {
                              ^
1 个错误

6.2 final 作用于方法


当父类中方法不希望被重写时,可以将该方法标记为 final

class SuperClass {
    public final void finalMethod() {
        System.out.println("我是final方法");
    }
}
class SubClass extneds SuperClass {
    // 被父类标记为final的方法不允许被继承,编译会报错
    @Override
    public void finalMethod() {
    }
}

编辑执行,将会报错:

SubClass.java:4: 错误: SubClass中的finalMethod()无法覆盖SuperClass中的finalMethod()
    public void finalMethod() {
                ^
  被覆盖的方法为final
1 个错误


6.3 final 作用于变量


对于实例变量,可以使用 final 修饰,其修饰的变量在初始化后就不能修改

class Cat {
    public final String name = "小花";
}

实例化 Cat 类,重新对 name 字段赋值:

Cat cat = new Cat();
cat.name = "小白";

编译执行,将会报错:

Cat.java:7: 错误: 无法为最终变量name分配值
        cat.name = "小白";
           ^
1 个错误


7. 小结


通过类的继承,可以大大增加代码的复用性。Java 是单继承的语言,所有类的根类都是 Object,继承通过 extends 关键字实现。要注意方法重写和方法重载的区别,不要混淆。类方法和 final 方法不能被重写。通过 super 关键字可以访问父类对象成员。final 关键字可以作用于类、方法和变量。


相关文章
|
1月前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
1天前
|
Java
Java 面向对象编程的三大法宝:封装、继承与多态
本文介绍了Java面向对象编程中的三大核心概念:封装、继承和多态。
31 15
|
28天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
1月前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
66 12
|
28天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
157 2
|
2月前
|
设计模式 Java 开发者
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####
|
2月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
1月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
1月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
63 3
|
1月前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
67 4