阶段二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访问构造方法,不能同时在一个方法中出现,否则会报错


总结

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

相关推荐
杨充17 分钟前
10.可测试性实战设计
设计模式·开源·代码规范
杨充25 分钟前
9.重构十二式的实战
设计模式·开源·代码规范
糖果店的幽灵30 分钟前
langgraph的四种state解析
java·前端·javascript·langgraph
杨充36 分钟前
6.设计原则的全景图
设计模式·开源·全栈
杨充37 分钟前
4.接口而非实现编程
java·后端·架构
杨充38 分钟前
2.面向对象的特性
设计模式
杨充38 分钟前
7.SOLID原则案例汇
设计模式·开源·全栈
杨充38 分钟前
8.反模式与坏味道
设计模式·开源·代码规范
梦梦代码精43 分钟前
基于ThinkPHP6 + Vue3的家政预约系统全解析:从LBS定位到自动派单的完整实现
java·docker·开源·php·代码规范
杨充1 小时前
3.接口vs抽象类比较
设计模式