阶段二JavaSE进阶阶段之设计模式&继承 2.2

文章目录

  • 一、设计模式
    • [1.1 设计模式概述](#1.1 设计模式概述)
    • [1.2 单例设计模式](#1.2 单例设计模式)
    • [1.3 单例设计模式的应用场景](#1.3 单例设计模式的应用场景)
    • [1.4 单例设计模式的实现方式](#1.4 单例设计模式的实现方式)
  • 二、继承
    • [2.1 继承快速入门](#2.1 继承快速入门)
    • [2.2 为什么要使用继承](#2.2 为什么要使用继承)
    • [2.3 继承的注意事项](#2.3 继承的注意事项)
      • [2.3.1 权限修饰符](#2.3.1 权限修饰符)
      • [2.3.2 单继承、Object](#2.3.2 单继承、Object)
      • [2.3.3 方法重写](#2.3.3 方法重写)
      • [2.3.4 子类中访问成员的特点](#2.3.4 子类中访问成员的特点)
      • [2.3.5 子类中访问构造器的特点](#2.3.5 子类中访问构造器的特点)
  • 总结

详细的java学习路径和教程请看我写的另一篇java小白到大牛的快速直通车


一、设计模式

1.1 设计模式概述

设计模式其实就是某个具体问题的最优解法,设计模式有20多种,对应20多种软件开发中会遇到的问题

我们学习设计模式时应该带着问题去学习:

  • 某种设计模式具体时解决什么问题呢
  • 这种设计模式该如何写呢

1.2 单例设计模式

单例设计模式就是确保一个类只有一个对象

写法:

  • 将类的构造器私有化
  • 定义一个类变量记住类的一个对象
  • 定义一个类方法,返回类的对象

代码表示如下

我们在测试类中多次调用getObject方法并输出对象的地址

结果如下:

可以看到,多次调用返回的对象地址都相同,保证了类只有一个对象

1.3 单例设计模式的应用场景

单例设计模式常用语接受当前环境下状态,比如咱们电脑中的任务管理器

无论我们开几次任务管理器,它也只会开一个,在Java中有这么一个API,叫RunTime,我们点开他的源码查看会发现它就是单例设计模式

因为他的对象表示当前的运行环境,所以不需要每次都创建对象

1.4 单例设计模式的实现方式

单例实际模式实现方式有很多种,这里只给大家介绍2种方式

  • 饿汉式单例:拿对象时,对象早就创建好了
  • 懒汉式单例:拿对象时,才开始创建对象

饿汉式单例:就是上面我们介绍的那种

类变量会随着类的加载而分配内存,所以上图当我们加载类时就直接会创建对象,所以为饿汉单例。

懒汉式单例:

只有我们调用方法时才会对变量进行初始化,并判断对象是否初始化,如未初始化则代表第一次调用,则对其进行初始化,如已经初始化则直接返回对象

二、继承

2.1 继承快速入门

面向对象编程之所以能够能够被广大开发者认可,有一个非常重要的原因,是因为它有三大特征,继承、封装和多态。封装我们在前面的文章已经讲过了,接下来我们讲一下继承。

继承

  • java中提供了一个关键字extends,用这个关键字,可以让一个类和另一个类建立起父子关系

继承的特点:

  • 子类能继承父类的非私有变量(成员变量、成员方法)

继承后对象的创建:

  • 子类的对象是由子类、父类共同完成的

接下来,我们演示一下使用继承来编写代码,注意观察继承的特点。

我们先写一个父类A

java 复制代码
public class A{
    //公开的成员
    public int i;
    public void print1(){
        System.out.println("===print1===");
    }
    
    //私有的成员
    private int j;
    private void print2(){
        System.out.println("===print2===");
    }
}

然后,写一个B类,让B类继承A类。在继承A类的同时,B类中新增一个方法print3

java 复制代码
public class B extends A{
    public void print3(){
        //由于i和print1是属于父类A的公有成员,在子类中可以直接被使用
        System.out.println(i); //正确
        print1(); //正确
        
        //由于j和print2是属于父类A的私有成员,在子类中不可以被使用
        System.out.println(j); //错误
        print2();
    }
}

接下来,我们再演示一下,创建B类对象,能否调用父类A的成员。再写一个测试类

java 复制代码
public class Test{
    public static void main(String[] args){
        B b = new B();
        //父类公有成员,子类对象是可以调用的
        System.out.println(i); //正确
        b.print1();
        
        //父类私有成员,子类对象时不可以调用的
        System.out.println(j); //错误
        b.print2(); //错误
    }
}

为了让大家对继承有更深入的认识,我们来看看继承的内存原理。

这里我们只需要关注一点:子类对象实际上是由子、父类两张设计图共同创建出来的。

所以,在子类对象的空间中,既有本类的成员,也有父类的成员。但是子类只能调用父类公有的成员。

2.2 为什么要使用继承

我们通过一个案例来讲一下继承的好处

现在我们有这样一个需求:我们要编写员工管理系统,员工有老师和咨询师,老师的数据有:姓名、技能,咨询师的数据有姓名、解答问题人数

我们会发现,2个类中存在着大量的重复代码,这个时候,我们可以将重复代码都提到父类中,然后让其他类继承父类就可以了,这样可以提高代码的复用性。改造后的代码如下:

接下来使用继承来完成上面的案例,这里只演示People类和Teacher类,然后大家尝试自己完成Consultant类。

  • 先写一个父类 People,用来设计Teacher和Consultant公有的成员。
java 复制代码
public class People{
    private String name;
    
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name=name;
    }
}
  • 再写两个子类Teacher继承People类,同时在子类中加上自己特有的成员。
java 复制代码
public class Teacher extends People{
    private String skill; //技能
    
    public String getSkill(){
        return skill;
    }
    
    public void setSkill(String skill){
        this.skill=skill;
    }
    
    public void printInfo(){
        System.out.println(getName()+"具备的技能:"+skill);
    }
}
  • 最后再写一个测试类,再测试类中创建Teacher、Consultant对象,并调用方法。
java 复制代码
public class Test {
    public static void main(String[] args) {
        Teacher t = new Teacher();
        t.setName("剁椒");
        t.setSkill("Java、Spring");
        System.out.println(t.getName());
        System.out.println(t.getSkill());
        t.printInfo();
    }
}

执行代码,打印结果如下:

2.3 继承的注意事项

2.3.1 权限修饰符

以前文章使用的代码中我们有用到两个权限修饰符,一个是public(公有的)、一个是private(私有的),实际上还有两个权限修饰符,一个是protected(受保护的)、一个是缺省的(不写任何修饰符)。

接下来我们就学习一下这四个权限修饰符的访问范围。

下面我们用代码演示一下,在本类中可以访问到哪些权限修饰的方法。

java 复制代码
public class Fu {
    // 1、私有:只能在本类中访问
    private void privateMethod(){
        System.out.println("==private==");
    }

    // 2、缺省:本类,同一个包下的类
    void method(){
        System.out.println("==缺省==");
    }

    // 3、protected: 本类,同一个包下的类,任意包下的子类
    protected void protectedMethod(){
        System.out.println("==protected==");
    }

    // 4、public: 本类,同一个包下的类,任意包下的子类,任意包下的任意类
    public void publicMethod(){
        System.out.println("==public==");
    }

    public void test(){
        //在本类中,所有权限都可以被访问到
        privateMethod(); //正确
        method(); //正确
        protectedMethod(); //正确
        publicMethod(); //正确
    }
}

接下来,在和Fu类同一个包下,创建一个测试类Demo,演示同一个包下可以访问到哪些权限修饰的方法。

java 复制代码
public class Demo {
    public static void main(String[] args) {
        Fu f = new Fu();
        // f.privateMethod();	//私有方法无法使用
        f.method();
        f.protectedMethod();
        f.publicMethod();
    }
}

接下来,在另一个包下创建一个Fu类的子类,演示不同包下的子类中可以访问哪些权限修饰的方法。

java 复制代码
public class Zi extends Fu {
    //在不同包下的子类中,只能访问到public、protected修饰的方法
    public void test(){
        // privateMethod(); // 报错
        // method(); // 报错
        protectedMethod();	//正确
        publicMethod();	//正确
    }
}

接下来,在和Fu类不同的包下,创建一个测试类Demo2,演示一下不同包的无关类,能访问到哪些权限修饰的方法;

java 复制代码
public class Demo2 {
    public static void main(String[] args) {
        Fu f = new Fu();
        // f.privateMethod(); // 报错
        // f.method();		  //报错
        // f.protecedMethod();//报错
        f.publicMethod();	//正确

        Zi zi = new Zi();
        // zi.protectedMethod();
    }
}

2.3.2 单继承、Object

上面我们写的代码中,都是一个子类继承一个父类,那么一个子类可以继承多个父类吗?

Java语言只支持单继承,不支持多继承,但是可以多层继承 。就像家族里儿子、爸爸和爷爷的关系一样:一个儿子只能有一个爸爸,不能有多个爸爸,但是爸爸也是有爸爸的。

Object类是所有类的祖宗类,所有的类都直接或者间接的继承Object类

java 复制代码
public class Test {
    public static void main(String[] args) {
        // 目标:掌握继承的两个注意事项事项。
        // 1、Java是单继承的:一个类只能继承一个直接父类;
        // 2、Object类是Java中所有类的祖宗类。
        A a = new A();
        B b = new B();

        ArrayList list = new ArrayList();
        list.add("java");
        System.out.println(list.toString());
    }
}

class A {} //默认添加的extends Object{}
class B extends A{}
// class C extends B , A{} // 报错
class D extends B{}

2.3.3 方法重写

学习完继承之后,在继承的基础之上还有一个很重要的现象需要给大家说一下。

叫做方法重写。为了让大家能够掌握方法重写,我们先认识什么是方法重写,再说一下方法的应用场景。

什么是方法重写

当子类觉得父类方法不好用,或者无法满足父类需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。

注意:重写后,方法的访问遵循就近原则。下面我们看一个代码演示

写一个A类作为父类,定义两个方法print1和print2

java 复制代码
public class A {
    public void print1(){
        System.out.println("重写前1");
    }

    public void print2(int a, int b){
        System.out.println("重写前2");
    }
}

再写一个B类作为A类的子类,重写print1和print2方法。

java 复制代码
public class B extends A{
    // 方法重写
    @Override // 安全,可读性好
    public void print1(){
        System.out.println("重写后1");
    }


    // 方法重写
    @Override
    public void print2(int a, int b){
        System.out.println("重写后2");
    }
}

接下来,在测试类中创建B类对象,调用方法

java 复制代码
public class Test {
    public static void main(String[] args) {
        // 目标:认识方法重写,掌握方法重写的常见应用场景。
        B b =  new B();
        b.print1();
        b.print2(2, 3);
    }
}

执行代码,我们发现真正执行的是B类中的print1和print2方法

知道什么是方法重写之后,还有一些注意事项:

  • 重写的方法上面,可以加一个注解@Override,用于标注这个方法是复写的父类方法,增加重写的安全性和可读性
  • 子类复写父类方法时,访问权限必须大于或者等于 父类方法的权限
    public > protected > 缺省
  • 重写的方法返回值类型,必须与被重写的方法返回值类型一样,或者范围更小
  • 私有方法、静态方法不能被重写,如果重写会报错。

方法重写的应用场景

学习完方法重写之后,接下来,我们还需要大家掌握方法重写,在实际中的应用场景。方法重写的应用场景之一就是:子类重写Object的toString()方法,以便返回对象的内容。

比如:有一个Student类,这个类会默认继承Object类。

java 复制代码
public class Student extends Object{
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }
}

在Object类中有一个toString()方法,直接通过Student对象调用Object的toString()方法,会得到对象的地址值

java 复制代码
public class Test {
    public static void main(String[] args) {
        Student s = new Student("剁椒", 18);
        // System.out.println(s.toString());
        System.out.println(s);
    }
}

此时我们不想返回地址值而是内容,那就可以在Student类中重新写一个toSting()方法,用于返回对象的属性值。

java 复制代码
public class Student extends Object{
    private String name;
    private int age;

    public Student() {
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

重新运行测试类,结果如下

2.3.4 子类中访问成员的特点

刚才我们已经学习了继承,我们发现继承至少涉及到两个类,而每一个类中都可能有各自的成员(成员变量、成员方法),就有可能出现子类和父类有相同成员的情况,那么在子类中访问其他成员有什么特点呢?

在子类中访问其他成员(成员变量、成员方法),是依据就近原则的

定义一个父类,代码如下

java 复制代码
public class F {
    String name = "父类名字";

    public void print() {
        System.out.println("==父类中print方法执行==");
    }
}

再定义一个子类,代码如下。有同名的name成员变量和局部变量,有同名的print成员方法;

java 复制代码
public class Z extends F{
    String name = "子类名字";

    public void showName(){
        String name = "子类局部名字";
        System.out.println(name);   //子类局部名字
        System.out.println(this.name);     //子类名字
        System.out.println(super.name);     //父类名字
    }

    @Override
    public void print(){
        System.out.println("==子类中print方法执行==");
    }

    public void showMethod(){
        print();    //就近原则,子类print方法执行
        super.print();      //父类中的print方法执行
    }
}

运行结果如下

可以发现,如果子类和父类出现同名变量或者方法,优先使用子类的;此时如果一定要在子类中使用父类的成员,可以加this或者super进行区分。

this调用本类的成员,super调用父类的成员

2.3.5 子类中访问构造器的特点

子类在运行构造器前都会先调用父类构造器,在执行自己

执行顺序,如下图①②③步骤执行

如果父类中没有无参构造,则子类会报错(因为子类的构造器默认添加super()方法),所以我们需要给父类添加无参构造,如果不想添加无参构造,则可以给父类添加有参构造,子类手动添加super(参数)调用父类的有参构造

子类访问构造器的应用场景

  • 一般用于快速地给子类独有的成员和父类共有的成员快速赋值

    当我们想调用自己本身的构造器时,可以使用this调用
    什么时候会调用本身构造器呢,这里通过一个案例大家就明白了
    我们现在有个学生管理系统
java 复制代码
class Student{
    private String name;	//学生姓名
    private int age;	//学生年龄
    private String schoolName;	//学生学校名称

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSchoolName() {
        return schoolName;
    }

    public void setSchoolName(String schoolName) {
        this.schoolName = schoolName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

此时我们创建学生对象并赋值

java 复制代码
public class Test {
    Student s = new Student("剁椒",18,"清华");
}

现在我们有个需求时如果学生不填学校名称,则默认北大

这时代码可以这样写,在Student中添加name和age的构造器

java 复制代码
public Student(String name, int age) {
        this.name = name;
        this.age = age;
        this.schoolName = "北大";
    }

这样就满足了我们的需求,但是我们发现一个问题,就是重复的代码太多

这个时候我们就可以让2个参数的构造器直接调用3个参数的构造器,以减少代码重复,这里我们使用this关键字

最后我们对this和super的用法在总结一下

访问本类成员:

this.成员变量 //访问本类成员变量

this.成员方法 //调用本类成员方法

this() //调用本类空参数构造器

this(参数) //调用本类有参数构造器

访问父类成员:

super.成员变量 //访问父类成员变量

super.成员方法 //调用父类成员方法

super() //调用父类空参数构造器

super(参数) //调用父类有参数构造器

注意:this和super访问构造方法,不能同时在一个方法中出现,否则会报错


总结

以上就是今天要讲的内容,本文介绍了单例设计模式和继承及相关注意事项,通过本篇文章我们能掌握设计模式的设计思想,继承的优势和使用形式等

相关推荐
胡西风_foxww25 分钟前
Java的extends通配符
java·开发语言·通配符·extends
中国lanwp1 小时前
Spring Boot 中使用 Lombok 进行依赖注入的示例
java·spring boot·后端
凌辰揽月2 小时前
AJAX 学习
java·前端·javascript·学习·ajax·okhttp
永日456702 小时前
学习日记-spring-day45-7.10
java·学习·spring
小屁孩大帅-杨一凡4 小时前
如何解决ThreadLocal内存泄漏问题?
java·开发语言·jvm·算法
学习3人组4 小时前
在 IntelliJ IDEA 系列中phpstorm2025设置中文界面
java·ide·intellij-idea
pe7er5 小时前
nuxtjs+git submodule的微前端有没有搞头
前端·设计模式·前端框架
cainiao0806056 小时前
Java 大视界:基于 Java 的大数据可视化在智慧城市能源消耗动态监测与优化决策中的应用(2025 实战全景)
java
长风破浪会有时呀6 小时前
记一次接口优化历程 CountDownLatch
java
极光雨雨6 小时前
【设计模式】单例模式 饿汉式单例与懒汉式单例
单例模式·设计模式