• 01-Java的static关键字、单例、代码块、继承


    一、面向对象知识的学习建议

    • 我们现在所学的面向对象,相当于是一个一个的小螺丝钉

    • 学了这些小螺丝钉,肯定还不知道具体能解决什么样的问题
      在这里插入图片描述

    • 所以,必须在学习完这些小螺钉之后,将这些综合起来,去造火箭、航母、飞机 …
      在这里插入图片描述

    • 结论:

      多关注语法点的基本作用 ——> 多进行思考和记忆、练习 ——> 要自信,不要在短期想能做什么?

    二、面向对象进阶知识:第一天需要学什么?

    • static关键字:

      • 之前定义的成员变量:name, age属于每个对象的。

      • 如何表示共享的信息?如在线人数等。

    • 设计模式:单例

      • 有些类只需要一个对象就可以了,如任务管理器对象,如何实现一个类只能对外产生一个对象?
    • 面向对象三大特征之二:继承

      • 系统中很多实体类的属性和行为方法存在重复代码,如何把这些类信息进行优化,降低代码冗余,提升复用性呢?

    三、static关键字

    1、static是什么?

    • static是 静态 的意思,可以修饰成员变量和成员方法。

    • static修饰成员变量表示该成员变量 只在内存中存储一份 ,可以被 共享访问、修改

    • 举例:

      • 比如,bilibili的在线观看人数这个成员变量,是不是每个用户对象进入bilibili都可以看到这个在线观看人数;

      • 那么在程序中,这个成员变量就可以用static来修饰,只需要存储一份,可以被共享访问、修改。
        在这里插入图片描述

    2、成员变量的分类

    • 静态成员变量(有static修饰,属于类,内存中只加载一次):

      • 常表示在:如bilibili的在线观看人数、已装填弹幕数等被共享的信息,可以被共享访问。

      • 静态成员变量的调用:

        类名.静态成员变量;	(推荐)
        对象.静态成员变量;	(不推荐)
        
        • 1
        • 2
        • 用户类

          package com.app.d1_static;
          
          // 用户类
          public class User {
              /**
                  静态成员变量:在线人数、弹幕数
                  注意:有static修饰的成员变量:静态成员变量,只在内存中存储一份,可以被共享
               */
              public static int onlineUsers = 22;
              public static int barrageNumber = 342;
          
              
              // main主方法
              public static void main(String[] args) {
                  // 目标:理解static修饰成员变量的作用和访问特点
          //         静态成员变量的调用:
                  System.out.println("静态成员变量的调用: ");
                  // 1.调用:类名.静态成员变量; (推荐)
                  System.out.println(User.onlineUsers + " 人正在观看,已装填 " + User.barrageNumber + " 条弹幕");
          
                  System.out.println("----------------------------");
                  // 2.调用:对象.静态成员变量; (不推荐)
                  User user = new User();
          
                  // 不推荐的原因是因为在用 对象. 的时候不会显示静态成员变量,但是也不影响使用
                  user.onlineUsers++;     // 又有一个人来观看视频
                  user.barrageNumber++;   // 这个人发了一条弹幕
                  System.out.println(user.onlineUsers + " 人正在观看,已装填 " + user.barrageNumber + " 条弹幕");
              }
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
        • 控制台:

          静态成员变量的调用: 
          22 人正在观看,已装填 342 条弹幕
          ----------------------------
          23 人正在观看,已装填 343 条弹幕
          
          Process finished with exit code 0
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
    • 实例成员变量(无static修饰,存在于每个对象中):

      • 常表示姓名name、年龄age、等属于每个对象的信息。

      • 实例成员变量的调用:

        对象.实例成员变量;
        
        • 1
        • 用户类

          package com.app.d1_static;
          
          // 用户类
          public class User {
              /**
                  静态成员变量:在线人数、弹幕数
                  注意:有static修饰的成员变量:静态成员变量,只在内存中存储一份,可以被共享
               */
              public static int onlineUsers = 22;
              public static int barrageNumber = 342;
          
          
              /**
                  实例成员变量:姓名、年龄
                  注意:无static修饰的成员变量:实例成员变量,属于每个对象的,必须用对象名.访问
               */
              public String name;
              public int age;
          
          
              public static void main(String[] args) {
                  // 目标:理解static修饰成员变量的作用和访问特点
          //         静态成员变量的调用:
                  System.out.println("静态成员变量的调用: ");
                  // 1.调用:类名.静态成员变量; (推荐)
                  System.out.println(User.onlineUsers + " 人正在观看,已装填 " + User.barrageNumber + " 条弹幕");
          
                  System.out.println("----------------------------");
                  // 2.调用:对象.静态成员变量; (不推荐)
                  User user = new User();
          
                  // 不推荐的原因是因为在用 对象. 的时候不会显示静态成员变量,但是也不影响使用
                  user.onlineUsers++;     // 又有一个人来观看视频
                  user.barrageNumber++;   // 这个人发了一条弹幕
                  System.out.println(user.onlineUsers + " 人正在观看,已装填 " + user.barrageNumber + " 条弹幕");
          
                  System.out.println();
          
          //        实例成员变量的调用:
                  System.out.println("实例成员变量的调用:");
          //         新增一个用户
                  User user1 = new User();
                  // 3.调用:对象.实例成员变量
                  // User.name = "张飞";   // 报错!因为实例成员变量属于每个对象
                  user1.name = "关羽";
                  user1.age = 25;
                  User.onlineUsers++;     // 关羽来观看视频啦!
                  User.barrageNumber++;   // 关羽发了一条弹幕
                  System.out.println(user1.name + "今年" + user1.age + "岁了!");
          
                  System.out.println("----------------------------");
          //         新增一个用户
                  User user2 = new User();
                  // 3.调用:对象.实例成员变量
                  user2.name = "刘备";
                  user2.age = 39;
                  User.onlineUsers++;     // 关羽来观看视频啦!
                  User.barrageNumber++;   // 关羽发了一条弹幕
                  System.out.println(user2.name + "今年" + user2.age + "岁了!");
          
                  System.out.println();
                  // 静态成员变量的调用
                  System.out.println(User.onlineUsers + " 人正在观看,已装填 " + User.barrageNumber + " 条弹幕");
          
                  System.out.println();
                  // 静态成员变量的访问方式1:对象名访问(不推荐)
                  System.out.println("静态成员变量的访问方式1: " + user1.onlineUsers);
          
                  // 静态成员变量的访问方式2:类名访问(推荐方式)
                  System.out.println("静态成员变量的访问方式2: " + User.onlineUsers);
          
                  // 注意:静态成员变量的访问方式3 ——>同一类中,访问静态成员变量,类名可以省略不写。但只局限于此类中访问。
                  System.out.println("静态成员变量的访问方式3: " + onlineUsers);
              }
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
          • 18
          • 19
          • 20
          • 21
          • 22
          • 23
          • 24
          • 25
          • 26
          • 27
          • 28
          • 29
          • 30
          • 31
          • 32
          • 33
          • 34
          • 35
          • 36
          • 37
          • 38
          • 39
          • 40
          • 41
          • 42
          • 43
          • 44
          • 45
          • 46
          • 47
          • 48
          • 49
          • 50
          • 51
          • 52
          • 53
          • 54
          • 55
          • 56
          • 57
          • 58
          • 59
          • 60
          • 61
          • 62
          • 63
          • 64
          • 65
          • 66
          • 67
          • 68
          • 69
          • 70
          • 71
          • 72
          • 73
          • 74
          • 75
        • 控制台

          静态成员变量的调用: 
          22 人正在观看,已装填 342 条弹幕
          ----------------------------
          23 人正在观看,已装填 343 条弹幕
          
          实例成员变量的调用:
          关羽今年25岁了!
          ----------------------------
          刘备今年39岁了!
          
          25 人正在观看,已装填 345 条弹幕
          
          静态成员变量的访问方式1: 25
          静态成员变量的访问方式2: 25
          静态成员变量的访问方式3: 25
          
          Process finished with exit code 0
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
          • 10
          • 11
          • 12
          • 13
          • 14
          • 15
          • 16
          • 17
    成员变量重点总结

    1、成员变量的分类和访问分别是如何的?

    • 静态成员变量:

      • 有static修饰,属于类,在内存中只存储一份,可以被共享访问

      • 访问格式

        类名.静态成员变量;		// 推荐(方便)
        对象名.静态成员变量;	   // 不推荐(不方便)
        
        • 1
        • 2
    • 实例成员变量:

      • 无static修饰,属于每个对象的

      • 访问格式

        对象名.实例成员变量;
        
        • 1

    2、两种成员变量各自在什么情况下定义?

    • 静态成员变量:表示在在线人数、弹幕数等需要被共享的信息
    • 实例成员变量:属于每个对象,且每个对象的该信息不同时(如:name, age, money …)

    static修饰成员变量的内存机制
    • 第一,将User.java文件 编译成 字节码文件User.class ,编译后的User.class文件和main方法 加载到方法区;

      第二,将main方法加载到栈内存中执行,然后将 static修饰的静态成员变量onLineNumber加载到堆内存中的静态变量区,此时onLineNumber属于被共享了;

      第三,执行main方法里的第一句:System.out.println(User.onLineNumber); ——> 直接输出 访问到的静态成员变量的值到控制台:161;

      第四,执行main方法里的第二句:System.out.println(onLineNumber); ——>直接输出 访问到的静态成员变量的值到控制台:161;

      第五,执行main方法里的第三句:User.onLineNumber++; ——>对静态成员变量onLineNumber进行自增,此时值变成了:162;

      第六,执行main方法里的第四句:System.out.println(name); ——>因为这个 name无static 修饰的,属于实例成员变量,因此它属于每个对象,但是由于目前没有将对象创建出来,就直接访问实例成员变量:name,所以这句代码执行失败!报错!!所以将这句代码注释起来了,为了不影响后面的代码的执行;
      在这里插入图片描述


    • 第七,执行main方法里的第五句:User u1 = new User(); ——>先执行 “=” 右边的代码:在堆内存中创建一个用户对象并产生一个内存地址,然后将这个用户对象的内存地址 赋值给 “=” 左边的对象名:u1

      第八,执行main方法里的第六句:u1.name = "孙悟空"; ——>将 孙悟空 赋值给 对象名: u1u1 再根据地址找到用户对象的成员变量name,进行赋值;

      第九,执行main方法里的第七句:u1.age = 35; ——> 将 35 赋值给 对象名: u1u1 再根据地址找到用户对象的成员变量age,进行赋值;

      第十,执行main方法里的第八句:System.out.println(u1.name); ——> u1 根据地址找到用户对象的成员变量name,将值输出到控制台;

      第十一,执行main方法里的第九句:System.out.println(u1.age); ——> u1 根据地址找到用户对象的成员变量age,将值输出到控制台;

      第十二,执行main方法里的第十句:u1.onLineNumber++; ——> u1 根据地址找到对象,由于静态变量区在堆内存中是被共享的,所以找到静态成员变量onLineNumber 后进行自增的操作,值162+1后得:163;
      在这里插入图片描述


    • 第十三,执行main方法里的第十一句:User u2 = new User(); ——>先执行 “=” 右边的代码:在堆内存中创建一个用户对象并产生一个内存地址,然后将这个用户对象的内存地址 赋值给 “=” 左边的对象名:u2

      第十四,执行main方法里的第十二句:u2.name = "猪八戒"; ——>将 猪八戒 赋值给 对象名: u2u2 再根据地址找到用户对象的成员变量name,进行赋值;

      第十五,执行main方法里的第十三句:u2.age = 29; ——> 将 29 赋值给 对象名: u2u2 再根据地址找到用户对象的成员变量age,进行赋值;

      第十六,执行main方法里的第十四句:System.out.println(u2.name); ——> u2 根据地址找到用户对象的成员变量name,将值输出到控制台;

      第十七,执行main方法里的第十五句:System.out.println(u2.age); ——> u2 根据地址找到用户对象的成员变量age,将值输出到控制台;

      第十八,执行main方法里的第十六句:u2.onLineNumber++; ——> u2 根据地址找到对象,由于静态变量区在堆内存中是被共享的,所以找到静态成员变量onLineNumber 后进行自增的操作,值163+1后得:164;
      在这里插入图片描述

    • 第十九,执行main方法里的第十七句:System.out.println(User.onLineNumber); ——> 直接输出 访问到的静态成员变量的值到控制台:164。

    3、提出问题?

    • 之前我们定义的方法有的有static修饰,有的是没有的,有什么不同?

    在这里插入图片描述

    4、成员方法的分类

    • 静态成员方法(有static修饰,属于类),建议用类名访问,也可以用对象名访问。

    • 实例成员方法(无static修饰,属于对象),只能用对象出发访问。

    • 学生类

    package com.app.d1_static;
    
    /**
        学生类
        目标:理解static修饰成员方法的作用和访问特点。
     */
    public class Student {
        /**
            实例成员变量:无static修饰,属于对象,必须使用对象名触发访问。
         */
        private String name;
    
        /**
            静态成员方法:有static修饰,属于类,可以被共享访问,用类名或者对象名都可以触发访问。
         */
        public static int getMax(int age1, int age2) {
            return age1 > age2 ? age1 : age2;
        }
    
        /**
            实例成员方法:无static修饰,属于对象,只能用对象名触发访问。
         */
        public void study() {
            // 注意:因为name是实例成员变量,所以也属于对象,可以直接访问使用!!
            System.out.println(name + "照镜子,里外不是人!");
        }
    
    
        // main主方法
        public static void main(String[] args) {
    //        静态成员方法的调用
            System.out.println("静态成员方法的调用:");
            // 1.类名.静态成员方法
            System.out.println(Student.getMax(20, 19));     // 20
    
            // 注意:同一个类中,访问静态成员方法,类名可以省略不写。但只局限于此类中访问。
            System.out.println(getMax(19, 23));     // 23
            System.out.println();
    
    //        实例成员方法的调用
            System.out.println("实例成员方法的调用:");
            // 2.对象名.实例成员方法
            Student s1 = new Student();
            s1.name = "猪八戒";
            s1.study();
    
    //        报错!!因为study()方法无static修饰,是实例成员方法,属于对象,不能用类名触发访问调用。
            // Student.study();
            // study();
    
            // 3.对象名.静态成员方法(语法是可行的,但是不推荐)
            System.out.println(s1.getMax(14, 55));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 控制台
    静态成员方法的调用:
    20
    23
    
    实例成员方法的调用:
    猪八戒照镜子,里外不是人!
    55
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 使用场景:
      • 如果表示对象自己的行为的,且方法中需要被访问实例成员的,则该方法必须申明为实例成员方法。
      • 如果该方法是以执行一个共用功能为目的,则可以申明为静态成员方法。
    成员方法重点总结

    1、成员方法的分类和访问分别是什么样的?

    • 静态成员方法:

      • 有static修饰,属于类和对象共享,类名和对象名都可以触发访问;

      • 访问格式:

        类名.静态成员方法;		// 推荐
        对象名.静态成员方法;	   // 不推荐
        
        • 1
        • 2
    • 实例成员方法:

      • 无static修饰,属于对象,必须使用对象名触发访问;

      • 访问格式:

        对象名.实例成员方法;
        
        • 1

    2、每种成员方法的适用场景是什么样的?

    • 表示对象自己的行为的,且方法中需要直接访问实例成员的,该方法必须申明为实例成员方法
    • 如果该方法是以执行一个共用功能为目的,或者需要方便访问,则可以申明为静态成员方法
    static修饰成员方法的内存机制
    • 第一,将Student.java文件 编译成 Student.class字节码文件,编译后的Student.class字节码文件 和 被static修饰的main()、getMax()方法 加载到方法区;

      第二,执行main方法里的第一句:System.out.println(Student.getMax(10, 2)); ——> 由于getMax() 方法是被 static 修饰的,所以是共享的,可以直接通过 类名触发调用该方法,然后将 10和2 这两个实参 传送给 getMax() 方法的两个形参,接收到实参后,判断 10是否大于2 ,为true返回10;为false返回2, 判断结果为true,所以返回10;此时控制台显示10;

      第三,执行main方法里的第二句:System.out.println(getMax(2, 10)); ——> 由于getMax() 方法是被 static 修饰的,所以是共享的,而且又在同一个类中,所以可以直接调用该方法,然后将 2和10 这两个实参 传送给 getMax() 方法的两个形参,接收到实参后,执行getMax()方法的第一句:return a > b ? a : b; ——>判断 2是否大于10 ,为true返回2;为false返回10, 判断结果为false,所以返回10;此时控制台显示10;

      第四,执行main方法里的第三句:study(); ——> 因为这个 study() 方法是 无static 修饰的,属于实例成员方法,因此它属于每个对象,但是由于目前没有将对象创建出来,就直接调用实例成员方法:study() ,所以这句代码执行失败!报错!!所以将这句代码注释起来了,为了不影响后面的代码的执行;
      在这里插入图片描述


    • 第五,执行main方法里的第四句:Student s = new Student(); ——> 先执行 “=” 右边的代码:在堆内存中创建一个学生对象并产生一个内存地址,然后将这个学生对象的内存地址 赋值给 “=” 左边的对象名:s

      第六,执行main方法里的第五句:s.name = "猪八戒"; ——> 将 猪八戒 赋值给 对象名: ss 再根据地址找到学生对象的成员变量:name,进行赋值;

      第七,执行main方法里的第六句:s.study(); ——> s 根据地址找到学生对象,然后根据里面存的实例成员方法的地址找到 study() 方法,进行调用,将study() 方法加载到栈内存中执行的第一句:System.out.println(name + "在好好学习~~~"); ——> 由于 name无static 修饰的,属于实例成员变量,所以可以在实例成员方法study() 中直接调用,在控制台输出:猪八戒在好好学习~~~;

      第八,执行main方法里的第七句:System.out.println(s.getMax(20, 10)); ——> 由于getMax()方法是有static修饰的,属于静态成员方法,是被共享的;因此,可以使用对象名s触发调用getMax()方法,将20和10这两个实参 传送给 getMax() 方法的两个形参,接收到实参后,执行getMax()方法的第一句:return a > b ? a : b; ——>判断 20是否大于10,为true返回20;为false返回10,判断结果为true,返回20,所以控制台显示:20;
      在这里插入图片描述


    四、static的注意事项[拓展]

    • 静态成员方法只能访问静态的成员,不能 “直接” 访问实例的成员

      • 因为静态成员都归属类,并有static修饰,是共享的;实例成员都归属对象,无static修饰,是不共享的,所以静态的成员不可以 “直接” 访问实例的成员,但是可以 “间接” 访问实例的成员。
    • 实例成员方法可以访问静态的成员,也可以访问实例的成员

      • 因为静态成员都归属类,有static修饰的,是共享的;实例成员都归属对象,无static修饰,是不共享的,所以实例的成员既可以访问自己的成员,也可以访问属于共享的静态的成员。
    • 静态方法中是不可以出现this关键字的

      • 因为静态成员都归属类,有static修饰的,是共享的;this关键字代表的就是当前对象,因此属于对象,无static修饰,是不共享的,所以静态成员方法中不可以出现this关键字,因此this可以出现在实例成员方法中。
    package com.app.d1_static;
    
    // 目标:理解static访问相关的语法:面试笔试题,或者以后理解程序很重要的知识[拓展]
    public class Test3 {
        /**
            静态成员:有static修饰的变量或方法
         */
        public static int onlineUsers = 11;
    
        public static void test() {
            System.out.println("静态成员方法被调用啦!!");
        }
    
        
        /**
         实例成员:无static修饰的变量或方法
         */
        private String name;
    
        private void sports() {
            System.out.println(name + "正在跑步~~~");
        }
    
    
        
        // 1. 静态成员方法只能访问静态的成员,不能 “直接” 访问实例的成员
        public static void showMovie() {
    //         (1) 访问静态的成员
            // 同一个类中,访问时可以省略类名不写
            test();
            System.out.println(onlineUsers);
            Test3.test();
            System.out.println(Test3.onlineUsers);
    
    //        (2) 直接访问实例的成员
    //         报错!!不能 “直接” 访问实例的成员
            // System.out.println(name + "正在打篮球~~");
            // sports();
    
    //        (3) 可以间接访问实例的成员
            Test3 t = new Test3();
            System.out.println(t.name);
            t.sports();
        }
    
        
    
        // 2.实例成员方法可以访问静态的成员,也可以访问实例的成员
        public void eat() {
    //        1.访问静态成员
            test();
            System.out.println(onlineUsers);
            Test3.test();
            System.out.println(Test3.onlineUsers);
    
    //        2.访问实例成员
            sports();
            System.out.println(name);
            System.out.println(this);
            System.out.println(this.name);
        }
    
        
    
        // 3.静态方法中是不可以出现this关键字的
        public static void test2() {
            // 报错!!因为this代表的是当前对象,因此属于对象,是不共享的,所以不能出现this。
            // System.out.println(this);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    五、static应用知识:工具类

    1、工具类是什么?

    • 类中都是一些静态成员方法,每个方法都是以完成一个共用的功能为目的,这个类是用来给系统开发人员共同使用的。
    (1) 案例导学
    • 在企业的管理系统中,通常需要在一个系统的很多业务处使用验证码进行防刷新等安全控制。

    • 所以,当用户登录时,需要一个验证码的功能;然后为了防止刷新等恶意操作,又需要一个验证码的功能:

      package com.app.d2_static_util;
      
      import java.util.Random;
      
      public class Login {
          public static void main(String[] args) {
              // 开发一个验证码
              // 1.定义一个变量,用于记住验证码。
              String code = "";
      
              // 2.定义一个变量,用于记住全部字符。
              String data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      
              // 3.创建一个随机数对象,用于生成一个随机索引
              Random rd = new Random();
      
              // 4.定义一个for循环,用于生成几个随机索引,去得到几个字符
              for (int i = 0; i < 5; i++) {
                  // 5.获取随机索引对应的字符,连接给code
                  int index = rd.nextInt(data.length());
                  code += data.charAt(index);
              }
              System.out.println("验证码:" + code);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      package com.app.d2_static_util;
      
      import java.util.Random;
      
      public class Check {
          public static void main(String[] args) {
              // 开发一个验证码
              // 1.定义一个变量,用于记住验证码。
              String code = "";
      
              // 2.定义一个变量,用于记住全部字符
              String data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      
              // 3.创建一个随机数对象,用于生成一个随机索引
              Random rd = new Random();
      
              // 4.定义一个for循环,用于生成几个索引,去得到几个字符
              for (int i = 0; i < 5; i++) {
                  // 5.获取随机索引对应的字符,连接给code
                  int index = rd.nextInt(data.length());
                  code += data.charAt(index);
              }
      
              System.out.println("验证码:" + code);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
    • 问题就来了:

      • 同一个功能多处开发,会出现代码重复度过高,大大降低了开发效率。
    (2) 工具类的好处
    • 一是调用方便,二是提高了代码的复用性(一次编写,处处可用)

      • 工具类

        package com.app.d2_static_util;
        
        import java.util.Random;
        
        /**
            工具类
         */
        public class AppUtil {
            /**
                静态方法
             */
            public static String createVerifyCode(int n) {
                // 开发一个验证码
                // 1、定义一个变量,记住验证码
                String code = "";
        
                // 2、定义一个变量,记住全部可能出现的字符
                String data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        
                // 3、创建一个随机数对象,用于生成一个随机索引
                Random rd = new Random();
        
                // 4、定义for循环,生成n个索引,去得到n个字符
                for (int i = 0; i < n; i++) {
                    // 5.获取索引对应的字符,连接起来
                    int index = rd.nextInt(data.length());
                    code += data.charAt(index);
                }
        
                // 6、将生成好的验证码返回
                return code;
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        package com.app.d2_static_util;
        
        public class Login {
            public static void main(String[] args) {
                // 开发一个验证码
                // 1.调用工具类,得到一个5位的验证码
                System.out.println("5位验证码:" + AppUtil.createVerifyCode(5));
        
                // 2.调用工具类,得到一个6位的验证码
                System.out.println("6位验证码:" + AppUtil.createVerifyCode(6));
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        package com.app.d2_static_util;
        
        public class Check {
            public static void main(String[] args) {
                // 开发一个验证码
                // 1、调用工具类,得到一个5位验证码
                System.out.println(AppUtil.createVerifyCode(5));
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
      • 控制台

        Login类
        ——————————————————————————
        5位验证码:nrhYR
        6位验证码:yjvX5y
            
        ——————————————————————————
        
        Check类
        ——————————————————————————
        7QNjT
        
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
      • 可以看到,我们只需要写一个工具类,其他类要是想用,直接调用就行,想随机几个验证码就随机几个验证码

    (3) 为什么工具类中的方法不用实例方法做?

    在这里插入图片描述

    • 实例方法需要创建对象才能调用
    • 此时每个程序员都用对象,就是为了调用方法的话,这样只会浪费内存

    2、工具类定义时的其他要求

    • 由于工具类都是静态方法,直接使用类名触发访问调用,因此,工具类无需创建对象,建议将工具类的构造器使用private修饰:进行私有化

    • 这样做,是为了防止程序员创建对象,没必要浪费内存;这样做也显得你更专业,严谨。

      package com.app.d2_static_util;
      
      import java.util.Random;
      
      /**
          工具类
       */
      public class AppUtil {
      
          /**
              由于工具类都是静态方法,因此需要创建对象,所以把其构造器进行私有化;
              显得很专业,严谨。
           */
          private AppUtil() {
              
          }
      
          /**
              静态方法
           */
          public static String createVerifyCode(int n) {
              // 开发一个验证码
              // 1、定义一个变量,记住验证码
              String code = "";
      
              // 2、定义一个变量,记住全部可能出现的字符
              String data = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
      
              // 3、创建一个随机数对象,用于生成一个随机索引
              Random rd = new Random();
      
              // 4、定义for循环,生成n个索引,去得到n个字符
              for (int i = 0; i < n; i++) {
                  // 5.获取索引对应的字符,连接起来
                  int index = rd.nextInt(data.length());
                  code += data.charAt(index);
              }
      
              // 6、将生成好的验证码返回
              return code;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
    工具类重点总结

    1、工具类是什么?有什么好处?

    • 内部都是一些静态方法,每个方法都是一个共用功能
    • 一次编写,处处可用,提高代码的复用性、开发效率、节省内存

    2、工具类有什么要求?

    • 建议工具类的构造器进行私有化处理,因为工具类不需要创建对象

    3、练习:定义数组工具类

    • 需求:在实际开发中,经常会遇到一些数组使用的工具类。

    • 请按照如下要求编写一个数组的工具类:ArraysUtils

      • 1、我们知道数组对象直接输出的时候是输出对象的地址的,而项目中很多地方需要返回数组的内容,请在ArraysUtils中提供一个工具类方法toString(),用于返回整数数组的内容,返回的字符串格式如:[10, 20, 50, 34, 100](只考虑整数数组,且只考虑一维数组);
      • 2、经常需要统计平均值,平均值为去掉最低分和最高分后的分值,请提供这样一个工具方法getAerage(),用于返回平均分。(只考虑浮点型数组,且只考虑一维数组);
      • 3、定义一个测试类:TestDemo,调用这些工具类的工具方法,并返回结果。
      package com.app.d2_static_util.demo;
      
      /**
          练习:设计一个数组的工具类
          需求:
              1、提供一个工具类方法toString,用于返回整数数组的内容,返回的字符串如:[10, 20, 50, 34, 100]
              2、提供一个工具类方法getAerage,用于返回平均值
       */
      public class ArraysUtils {
          /**
              1.私有化构造器,防止创建对象,浪费内存
           */
          private ArraysUtils() {}
      
      
      
          /**
           * 静态方法: toString——用于返回整数数组的内容
           * @param arr       用于接收一个数组
           * @return          返回整数数组的内容
           */
          public static String toString(int[] arr) {
              // 1.当传入的数组地址为null时
              if (arr == null) {
                  // 地址为空,返回null
                  return null;
              }
      
              // 2.定义左括号,用于拼接返回结果
              String result = "[";
      
              // 3.定义for循环,遍历接收到的数组,取出元素
              for (int i = 0; i < arr.length; i++) {
                  /*
                      4.拼接遍历到的元素,并且判断是否为最后一个元素:
                        为true,就不需要加逗号隔开,为false,就需要加逗号隔开
                   */
                  result += (i == arr.length - 1 ? arr[i] : arr[i] + ", ");
              }
      
              // 4.表示遍历完了,拼接一个右括号
              result += "]";
      
              // 5.拼接完成,返回结果
              return result;
          }
      
      
      
          /**
           * 静态方法: getAverage——用于返回平均值
           * @param arr       用于接收一个数组
           * @return          返回平均值
           */
          public static double getAverage(double[] arr) {
              // 1.当传入的数组地址为null 或者 数组长度为0时
              if (arr == null || arr.length == 0) {
                  // 地址为null 或者 数组长度为0,返回0
                  return 0;
              }
      
              /*
                  2.定义变量,分别用于记录: 总分、最大值、最小值
                    总分: 默认值为0
                    最大值、最小值: 默认值为数组中的第一个元素
               */
              double sum = 0;
              double max = arr[0];
              double min = arr[0];
      
              // 3.定义for循环,遍历数组,取出所有元素
              for (int i = 0; i < arr.length; i++) {
                  // 4.得到最大值:判断遍历到的元素 是否大于 数组的第一个元素
                  if (arr[i] > max) {
                      // 是,则替换最大值
                      max = arr[i];
                  }
      
                  // 5.得到最小值:判断遍历到的元素 是否小于 数组的第一个元素
                  if (arr[i] < min) {
                      // 是,则替换最小值
                      min = arr[i];
                  }
      
                  // 6.统计总分
                  sum += arr[i];
              }
      
              // 4.循环结束,计算平均分 = (总值 - 最大值 - 最小值) / 个数
              double averageValue = (sum - max - min) / (arr.length-2);
      
              // 5.计算完成,返回平均值
              return averageValue;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      • 55
      • 56
      • 57
      • 58
      • 59
      • 60
      • 61
      • 62
      • 63
      • 64
      • 65
      • 66
      • 67
      • 68
      • 69
      • 70
      • 71
      • 72
      • 73
      • 74
      • 75
      • 76
      • 77
      • 78
      • 79
      • 80
      • 81
      • 82
      • 83
      • 84
      • 85
      • 86
      • 87
      • 88
      • 89
      • 90
      • 91
      • 92
      • 93
      • 94
      • 95
      package com.app.d2_static_util.demo;
      
      /**
          测试类
          需求:
              3、在测试类,调用这些工具类的工具方法,并返回结果
       */
      public class TestDemo {
          public static void main(String[] args) {
      //         1.定义一个数组,传入一组数据,调用工具类方法toString,得到数组内容并输出
              int[] arr1 = {10, 20, 50, 34, 100};
              System.out.println(ArraysUtils.toString(arr1));
      
              int[] arr2 = null;
              System.out.println(ArraysUtils.toString(arr2));
      
              int[] arr3 = {};
              System.out.println(ArraysUtils.toString(arr3));
      
              System.out.println("---------------------------------------");
      
      //        2.定义一个数组,传入一组数据,调用工具类方法getAverage,得到平均值并输出
              double[] scores = {99.5, 69, 88, 79.5, 78, 55};
              System.out.println(ArraysUtils.getAverage(scores));
      
              double[] arr4 = {};
              System.out.println(ArraysUtils.getAverage(arr4));
      
              double[] arr5 = null;
              System.out.println(ArraysUtils.getAverage(arr5));
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      [10, 20, 50, 34, 100]
      null
      []
      ---------------------------------------
      78.625
      0.0
      0.0
      
      Process finished with exit code 0
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    六、static应用知识:代码块

    1、代码块是什么?

    • 代码块是类的5大成分之一(成员变量、构造器、方法、代码块、内部类),定义在类中方法外。
    • 在Java类下,使用 {} 括起来的代码被称为代码块。

    2、代码块的分类

    (1) 静态代码块:
    • 格式:static{}
    • 特点:需要通过static关键字修饰,随着类的加载而加载,并且自动触发,只执行一次。
    • 适用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。
    package com.app.d3_static_code;
    
    /**
        目标:先理解静态代码块
     */
    public class Demo1 {
    
        public static String schoolName;
    
        public static void main(String[] args) {
            System.out.println("main方法执行");
            System.out.println(schoolName);
        }
    
        /**
            静态代码块:有static修饰,属于类,与类一起优先加载,自动触发执行;
            作用:可以用于初始化资源。
         */
        static {
            System.out.println("静态代码块被触发执行了");
            schoolName = "应用大学";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    静态代码块被触发执行了
    main方法执行
    应用大学
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    (2) 构造代码块(了解一下,见得少):
    • 格式:{}
    • 特点:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行。
    • 使用场景:初始化实例资源。
    package com.app.d3_static_code;
    
    /**
        目标:理解实例代码块(构造代码块)
     */
    public class Demo2 {
    
        private String name;
    
        public Demo2() {
            System.out.println("无参构造器被触发执行");
        }
    
    
        /**
            实例代码块:无static修饰,属于对象,每次构建对象时,都会先构造器一步执行
            作用:初始化实例资源
         */
        {
            System.out.println("实例代码块被触发执行了");
            name = "孙悟空";
        }
    
    
        public static void main(String[] args) {
            // 构建对象
            Demo2 d1 = new Demo2();
            System.out.println(d1.name);
    
            System.out.println("-------------------------------");
    
            Demo2 d2 = new Demo2();
            System.out.println(d2.name);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    实例代码块被触发执行了
    无参构造器被触发执行
    孙悟空
    -------------------------------
    实例代码块被触发执行了
    无参构造器被触发执行
    孙悟空
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    (3) 静态代码块案例:斗地主游戏
    • 案例:斗地主游戏
      在这里插入图片描述

    • 需求:在进入游戏房间的时候,应该提前准备好54张牌,后续才可以直接使用这些牌数据。

    • 分析:

      • 1、游戏房间只需要一副牌;
      • 2、定义一个静态的ArrayList集合存储54张牌对象,静态的集合只会加载一份
      • 3、在进入游戏房间前,应该将54张牌初始化好;
      • 4、当房间进入的同时需要准备好54张牌数据,此时可以用静态代码块完成
      package com.app.d3_static_code;
      
      import java.util.ArrayList;
      
      /**
          目标:通过模拟斗地主游戏,更深入的理解静态代码块的作用
          需求:
              在进入游戏房间的时候,应该提前准备好54张牌,后续才能直接使用这些牌数据
       */
      public class StaticTest3 {
          /**
              1.定义一个静态集合,这样这个集合只会加载一次。因为斗地主房间只需要一副牌。
           */
          public static ArrayList<String> cards = new ArrayList<>();
      
      
          /**
              2.在程序真正执行main方法前,把54张牌提前放进集合中,后续游戏房间就可以直接使用了。
           */
          static {
      //        3.正式做牌,将做好的54张牌放到集合中:
              // a、定义一个数组存储牌的全部点数:因为类型确定了,个数确定了,所以用数组会比较专业、优雅。
              String[] sizes = {"3", "4", "5", "6", "7", "8", "9", "J", "Q", "K", "A", "2"};
      
              // b、定义一个数组存储牌的全部花色:因为类型确定了,个数确定了,所以用数组会比较专业、优雅。
              String[] colors = {"♠", "♥", "♦", "♣"};
      
              // c、定义外部for循环:遍历sizes数组,依次得到牌的点数
              for (int i = 0; i < sizes.length; i++) {
                  // sizes[i]
      
                  // d、得到一个牌的点数后,定义内部for循环,遍历colors数组,依次得到牌的花色
                  // 进行配色:如: ♠3, ♥3, ♦3, ♣3
                  for (int j = 0; j < colors.length; j++) {
                      // colors[j]
      
                      // 牌的点数 和 牌的花色 进行拼接,得到一张牌后,放进集合中
                      String card = sizes[i] + colors[j];
                      cards.add(card);
                  }
              }
      
              // 循环全部结束,表示52张牌的点数已经全部配色完毕,并已放进集合中
              // d、将大小王单独放进集合中
              cards.add("大🃏");
              cards.add("小🃏");
          }
      
      
          public static void main(String[] args) {
              // 4、输出新牌
              System.out.println("新牌:" + cards);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      • 43
      • 44
      • 45
      • 46
      • 47
      • 48
      • 49
      • 50
      • 51
      • 52
      • 53
      • 54
      新牌:[3♠, 3♥, 3♦, 3♣, 4♠, 4♥, 4♦, 4♣, 5♠, 5♥, 5♦, 5♣, 6♠, 6♥, 6♦, 6♣, 7♠, 7♥, 7♦, 7♣, 8♠, 8♥, 8♦, 8♣, 9♠, 9♥, 9♦, 9♣, J♠, J♥, J♦, J♣, Q♠, Q♥, Q♦, Q♣, K♠, K♥, K♦, K♣, A♠, A♥, A♦, A♣, 2♠, 2♥, 2♦, 2♣, 大🃏, 小🃏]
      
      Process finished with exit code 0
      
      • 1
      • 2
      • 3
    静态代码块重点总结

    1、静态代码块的作用是什么?

    • 如果要在启动系统时对静态资源进行初始化,则建议使用静态代码块完成数据的初始化操作

    七、static应用知识:单例设计模式

    1、什么是设计模式(Design pattern)

    • 开发中经常遇到一些问题,一个问题通常有n种解法的,但其中肯定有一种解法是最优的,这个最优的解法被人总结出来了,称之为设计模式。
    • 设计模式有20多种,对应20多种软件开发中会遇到的问题。
    • 学设计模式主要学2点:
      • 第一:这种模式用来解决什么问题;
      • 第二:遇到这种问题了,该模式是怎么写的,他是如何解决这个问题的。

    2、单例模式是什么?

    • 可以保证系统中,应用该模式的这个类永远只有一个实例,即一个类永远只能创建一个对象。
    • 使用场景:例如电脑的任务管理器对象,我们只需要一个就可以解决问题了,这样可以节省内存空间。

    3、单例的实现方式

    • 饿汉单例模式;
    • 懒汉单例模式;
    (1) 饿汉单例设计模式
    • 在使用类获取对象的时候,对象已经提前为你创建好了。

    • 设计步骤:

      • 第一,定义一个单例类;
      • 第二,单例必须将构造器私有化,将构造器藏起来,防止对外产生对象;
      • 第三,定义一个静态变量存储一个对象即可:属于类,与类一起加载一次。
      package com.app.d4_static_singleinstance;
      
      /**
          1.定义一个单例类
       */
      public class SingleInstance {
          /**
              2.单例必须将构造器进行私有化,防止对外产生对象
           */
          private SingleInstance() {
      
          }
      
          /**
              3.饿汉单例是在获取对象前,已经提前准备好一个对象;
              这个对象只能是一个,所以定义静态变量存储
           */
          public static SingleInstance instance = new SingleInstance();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      package com.app.d4_static_singleinstance;
      
      /**
          单例测试类
          目标:理解饿汉单例的设计步骤
       */
      public class SingleInstanceTest1 {
          public static void main(String[] args) {
              // 获取对象
              SingleInstance s1 = SingleInstance.instance;
              SingleInstance s2 = SingleInstance.instance;
      
              System.out.println(s1 == s2);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      true
      
      Process finished with exit code 0
      
      • 1
      • 2
      • 3
    饿汉单例重点总结

    1、饿汉单例的实现步骤?

    • 定义一个类,把构造器私有化(防止对外产生对象)
    • 定义一个变量存储一个对象(调用类时,对象已经准备好了,与类一起加载一次)
    (2) 懒汉单例设计模式
    • 在真正需要该对象的时候,才去创建一个对象(延迟加载对象)。

    • 设计步骤:

      • 第一,定义一个类,把构造器私有化:防止对外产生对象;
      • 第二,定义一个静态变量存储一个对象即可:属于类,与类一起加载一次;
      • 第三,必须提供一个方法返回一个单例对象。
      package com.app.d4_static_singleinstance;
      
      /**
          1.定义一个懒汉单例类
       */
      public class SingleInstance2 {
          /**
              2.必须将构造器私有化:防止对外产生对象。
           */
          private SingleInstance2() {
      
          }
      
          /**
              3.定义静态变量存储一个对象;
              与类一起加载,只加载一次。
              注意:最好私有化,这样可以避免给别人挖坑!!因为公开化,别人会拿到一个null。
           */
          private static SingleInstance2 instance;    // null
      
          /**
           * 4.提供一个方法,获取一个单例对象
           * @return      返回单例对象
           */
          public static SingleInstance2 getInstance(){
              /*
                  如果instance变量没有存储对象,就创建一个对象赋值给instance;
                  如果instance变量存储有对象,说明不是第一次来拿对象,就不进入if判断,直接将原本的返回。
               */
              // a、判断instance变量是否存储有对象
              if (instance == null) {
                  // b、没有存储对象,说明是第一次来拿对象,此时需要创建对象给别人拿
                  instance = new SingleInstance2();
              }
      
              // c、将单例对象返回
              return instance;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      package com.app.d4_static_singleinstance;
      
      /**
          懒汉单例测试类
          目标:掌握懒汉单例的设计;理解其思想
       */
      public class SingleInstanceTest2 {
          public static void main(String[] args) {
              // 获取对象
              SingleInstance2 s1 = SingleInstance2.getInstance();
              SingleInstance2 s2 = SingleInstance2.getInstance();
              System.out.println(s1 == s2);
      
              // instance已经私有化,所以会报错!!
              // SingleInstance2 s3 = SingleInstance2.instance;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      true
      
      Process finished with exit code 0
      
      • 1
      • 2
      • 3
    懒汉单例重点总结

    1、懒汉单例的实现步骤?

    • 定义一个单例类,把构造器私有化(防止对外产生对象)
    • 定义一个私有化的静态变量,用于存储一个对象,不设初始值
    • 提供一个返回单例对象的方法




    八、面向对象三大特征之二:继承

    1、继承是什么?

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

      public class Student extends People {
          
      }
      
      • 1
      • 2
      • 3
    • 以上代码中,Student称为子类(派生类),People称为父类(基类或超类)

      在这里插入图片描述

    • 作用: 当子类继承父类后,就可以直接使用父类公共的属性和方法了

      package com.app.d5_extends;
      
      /**
          人类:父类
       */
      public class People {
      
          public void ports() {
              System.out.println("父类公用方法被调用了!!");
          }
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      package com.app.d5_extends;
      
      /**
          学生类:子类
          使用extends继承了People类————父类
       */
      public class Student extends People {
      
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      package com.app.d5_extends;
      
      /**
          继承测试类:
          目标:认识继承这种关系;搞清楚使用继承的好处!
       */
      public class ExtendsTest1 {
          public static void main(String[] args) {
              // 创建Student对象(子类),
              Student s = new Student();
      
              // 由于Student类 继承了 People类,所以儿子可以调用父亲的公用方法
              s.ports();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      控制台:
      
      父类公用方法被调用了!!
      
      Process finished with exit code 0
      
      • 1
      • 2
      • 3
      • 4
      • 5

    2、继承的好处

    • 案例导学:请阅读以下代码存在的问题,并使用继承这个技术进行优化
      在这里插入图片描述

      • 可以看到,一个是学生类,一个是老师类;他们之间都有着相同的特征:成员变量、方法

      • 这样的话,重复代码又多了,是一种很不好的现象

    • 解决:继承(extends)关系;好处:提高代码的复用性

      在这里插入图片描述

    继承概述总结

    1、什么是继承?继承有啥好处?

    • 继承就是Java允许我们用extends关键字,让一个类与另一个类建立起一种父子关系

    • 好处就是减少代码冗余,提高了代码的复用性、增强类的功能扩展性

    2、继承的格式是什么样的?

    • 子类 extends 父类

    3、继承后子类的特点是什么?

    • 子类 继承父类后,子类可以得到父类的属性和方法,子类可以使用
    • Java中子类更强大:因为子类不仅得到父类的属性和方法,还能不断扩展

    3、继承的设计规范

    • 子类们相同的特征(共性属性、方法)放在父类中定义,子类独有的属性和方法应该定义在子类自己里面。

    为什么?

    • 如果子类的独有属性、方法定义在父类中,会导致其他子类也会得到这些属性和方法;这不符合面向对象逻辑。

    案例:继承的设计规范

    • 需求:

      • 在bilibili教育的tlias教学资源管理系统中,存在学生、老师角色会进入系统。
    • 分析:

      • 学生信息和行为(姓名年龄,所在班级,查看课表,填写听课反馈);

      • 老师信息和行为(姓名年龄,部门名称,查看课表,发布问题);

      • 定义角色类作为父类包含学生和老师相同的属性(姓名,年龄),行为(查看课表);

      • 定义学生类(子类):包含属性(所在班级),行为(填写听课反馈);

      • 定义老师类(子类):包含属性(部门名称),行为(发布问题)。

      • 定义角色类,作为父类

        package com.app.d6_extends_test;
        
        /**
            人类:父类
            包含子类相同的属性和行为
         */
        public class People {
            /**
                1、定义子类们相同的属性
             */
            private String name;    // 姓名
            private int age;        // 年龄
        
        
            /**
                2、提供属性对应的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;
            }
        
        
            /**
                3、定义子类们相同的行为方法:查看课表
             */
            public void queryCourse() {
                System.out.println(name + "查看了课表~~");
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
      • 定义学生类,作为子类 继承 父类

        package com.app.d6_extends_test;
        
        /**
            学生类:子类
            继承父类,得到属性和行为
         */
        public class Student extends People {
            /**
                1、定义子类自己独有的属性
             */
            private String className;   // 所在班级
        
        
            /**
                2、提供属性对应的getter、setter方法,暴露其取值和赋值
             */
            public String getClassName() {
                return className;
            }
        
            public void setClassName(String className) {
                this.className = className;
            }
        
        
            /**
                3、定义子类自己独有的行为方法:填写听课反馈
             */
            public void writeInfo() {
                System.out.println(className + "的" + getName() + "说: 老师666!!");
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
      • 定义老师类,作为子类 继承 父类

        package com.app.d6_extends_test;
        
        /**
            老师类:子类
            继承父类,得到属性和行为
         */
        public class Teacher extends People {
            /**
                1、定义子类自己独有的属性
             */
            private String departmentName;  // 部门名称
        
        
            /**
                2、提供属性对应的getter、setter方法,暴露其取值和赋值
             */
            public String getDepartmentName() {
                return departmentName;
            }
        
            public void setDepartmentName(String departmentName) {
                this.departmentName = departmentName;
            }
        
        
            /**
                3、定义子类自己独有的行为方法:发布问题
             */
            public void problem() {
                System.out.println(departmentName + "的" + getName() + "老师问: 这节课老师讲得好不好呀?");
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
      • 定义测试类

        package com.app.d6_extends_test;
        
        /**
            继承测试类
            目标:掌握继承的设计规范,理解继承的设计思想。
         */
        public class ExtendsTest1 {
            public static void main(String[] args) {
                // 1、创建学生对象
                Student s1 = new Student();
        
                // a、调用父类的行为方法:写入学生信息
                s1.setName("孙悟空");
                s1.setAge(799);
        
                // b、调用子类自己独有的行为方法:写入所在班级
                s1.setClassName("2022级天宫云雀9班");
        
                // c、调用父类的行为方法:查看课表
                s1.queryCourse();
        
                System.out.println("-----------------------------");
        
                
                // 2、创建老师对象
                Teacher t1 = new Teacher();
        
                // a、调用父类的行为方法:写入老师信息
                t1.setName("唐僧");
                t1.setAge(600);
        
                // b、调用子类自己独有的行为方法:写入部门名称
                t1.setDepartmentName("佛山老僧部");
        
                // c、调用父类的行为方法:查看课表
                t1.queryCourse();
        
                System.out.println("-----------------------------");
        
                
                // 3、老师提问
                // a、调用子类自己独有的行为方法:发布问题
                t1.problem();
        
                // 4、学生回答问题
                // a、调用子类自己独有的行为方法:填写反馈
                s1.writeInfo();
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
        • 19
        • 20
        • 21
        • 22
        • 23
        • 24
        • 25
        • 26
        • 27
        • 28
        • 29
        • 30
        • 31
        • 32
        • 33
        • 34
        • 35
        • 36
        • 37
        • 38
        • 39
        • 40
        • 41
        • 42
        • 43
        • 44
        • 45
        • 46
        • 47
        • 48
        • 49
      • 控制台

        孙悟空查看了课表~~
        -----------------------------
        唐僧查看了课表~~
        -----------------------------
        佛山老僧部的唐僧老师问: 这节课老师讲得好不好呀?
        2022级天宫云雀9班的孙悟空说: 老师666!!
        
        Process finished with exit code 0
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
    继承设计规范总结

    1、继承需要满足什么样的设计规范?

    • 子类们相同特征:共性属性,共性方法;都放在父类中定义
    • 子类独有的属性,方法;应该定义在子类自己里面

    4、继承的特点

    • 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器;

    • Java是单继承模式:一个类 只能继承一个直接父类;

      • 就是你 只能有一个亲爸,不可能隔壁的老王叔、老杨叔 … 都是你的亲爸吧?

      • 那从伦理角度看,肯定是不支持,但是从Java的角度呢?
        在这里插入图片描述

      • 为何不支持多继承,请看如下反证法:

        在这里插入图片描述

    • Java不支持多继承,但是支持多层继承;

      • 就是你 不可以继承多个爸爸;但是你可以继承你爸的,你爸可以继承你爷爷的,多层继承;

      • 比如:子类A 继承父类B;父类B 可以继承父类 C

    • Java中所有的类都是Object类的子类。

      • Java中所有的类,要么是直接继承了Object类,要么默认继承了Object类,要么间接继承了Object类;
        在这里插入图片描述

      • 所以说,Object是祖宗类

    仔细探索

    1、子类是否可以继承父类的构造器?

    • 不可以的,子类有自己的构造器,父类构造器用于初始化父类对象。

    2、子类是否可以继承父类的私有成员?

    • 这是个有争议的知识点!!
    • 我个人认为是可以的,只是不能直接访问:
      • 就好比,你爸给了你一个保险柜,你是不是已经继承了这个保险柜?
      • 然后你忘记保险柜密码了,难道你说你没有继承你爸的保险柜?
      • 那忘记密码了,当然也有暴力手段来打开这个保险柜;
      • 所以呢,后续我也会带着大家去打开这个保险柜的。

    3、子类是否可以继承父类的静态成员?

    • 这是个有争议的知识点!!
    • 子类可以访问到父类的静态成员,但是我个人认为这不算继承,只是属于共享:
      • 就好比,你爸有辆车,他给你开了一下,但是你能说继承你爸的车了吗?
      • 所以,子类不能继承父类的静态成员,共享并非继承。
    package com.app.d7_extends_feature;
    
    /**
        继承测试类
        目标:理解继承的特点
     */
    public class ExtendsTest1 {
        public static void main(String[] args) {
            // 1、子类可以继承父类的属性和行为,但是子类不能继承父类的构造器;
            // 2、子类是否可以继承父类的私有成员? 我认为是可以继承父类私有成员的,只是不能直接访问。
            Tiger t1 = new Tiger();
            // t1.eat();
    
            // 3、子类是否可以继承父类的静态成员? 子类可以访问到父类的静态成员,但我认为这是共享,不算继承,共享并非继承。
            System.out.println(Tiger.location);
        }
    }
    
    /**
        动物类:作为父类
     */
    class Animal {
        private void eat() {
            System.out.println("动物要吃东西~~");
        }
    
        public static String location = "多多动物园";
    }
    
    /**
        老虎类:作为子类继承父类
     */
    class Tiger extends Animal {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    继承的特点总结

    1、继承有哪些特点?

    • 子类可以继承父类的属性和方法,但是子类不能继承父类的构造器
    • Java是单继承模式:一个类只能继承一个直接父类
    • Java不支持多继承,但是支持多层继承
    • Java中所有的类都是Object类的子类,Object是祖宗类

    5、继承后:成员访问的特点

    就近原则
    • 在子类方法中访问成员(成员变量、方法)满足:就近原则
      • 先在子类局部范围找;
      • 然后在子类成员范围找;
      • 然后在父类成员范围找,如果父类范围还没有找到则报错。

    如果子父类中,出现重名的成员,会优先使用子类的,此时如果一定要在子类中使用父类的成员怎么办?

    • 可以通过 super 关键字,指定访问父类的成员。

    • 格式:

      super.父类成员变量/方法
      
      • 1
    package com.app.d8_extends_filed_method;
    
    /**
        继承测试类
        目标:理解继承后成员的访问特点:就近原则
     */
    public class ExtendsTest {
        public static void main(String[] args) {
            // 创建狗对象
            Dog dog = new Dog();
    
            dog.eat();          // 在子类中找不到此方法,在父类中找到了,所以是父类的
            dog.lookDoor();     // 在子类中找到了,就近原则,所以是子类的
            dog.showName();     // 在子类中找到了,就近原则,所以是子类的
            //dog.go();         // 在子类中找不到,在父类中依旧找不到,无此成员,报错!!
        }
    }
    
    
    /**
        定义父类:动物类
     */
    class Animal{
        public String name = "动物名";
    
        public void eat() {
            System.out.println("动物要吃东西!!!");
        }
    
        public void run() {
            System.out.println("动物可以跑~~~");
        }
    }
    
    
    /**
        定义子类:狗类 继承父类Animal
     */
    class Dog extends Animal{
        public String name = "狗名";
    
    
        public void run() {
            System.out.println("狗跑得贼快~~~");
        }
    
        public void lookDoor() {
            System.out.println("狗可以看门~~~");
        }
    
    
        public void showName() {
            String name = "局部名";
    
            // 在子类局部范围中找到了name,就近原则,所以不会去子类成员范围和父类成员范围找
            System.out.println(name);
    
            // this.name: 代表找当前子类成员范围的name
            System.out.println(this.name);
    
            // super.name: 代表找父类成员范围的name
            System.out.println(super.name);
    
            run();          // 子类成员范围的run()
            super.run();    // 父类成员范围的run()
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    控制台:
    
    动物要吃东西!!!
    狗可以看门~~~
    局部名
    狗名
    动物名
    狗跑得贼快~~~
    动物可以跑~~~
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    成员访问特点总结

    1、在子类方法中访问成员(成员变量、方法)需要满足什么?

    • 就近原则,子类局部没有找子类,子类没有找父类,父类没有就报错!

    2、如果子类和父类中出现了重复的成员,此时如果一定要在子类中使用父类的成员该如何?

    • 使用:super.父类的成员变量/方法

    6、继承后:子类重写方法

    方法重写是什么?
    • 在继承体系中,子类出现了和父类中一模一样的方法声明,我们就称子类的这个方法是重写的方法。

    方法重写的应用场景:

    • 当子类需要父类的方法,但父类的该方法功能不能满足自己的需求时,需要重写方法来改变成能满足自己需求的功能。
    • 子类可以重写父类中的方法。

    案例演示:

    • 旧手机的功能只能是基本的打电话,发信息;
    • 新手机的功能需要能够:基本的打电话下支持视频通话。基本的发信息下支持发送语音和图片。
    package com.app.d9_extends_override_method;
    
    /**
        继承测试类
        目标:理解重写方法
     */
    public class ExtendsTest {
        public static void main(String[] args) {
            // 创建手机对象
            NewPhone hw = new NewPhone();
    
            // 新手机功能
            hw.callUp();
            System.out.println();
            hw.sendMessage();
        }
    }
    
    
    /**
        旧手机:父类
     */
    class Phone{
    
        public void callUp() {
            System.out.println("打电话~~");
        }
    
        public void sendMessage() {
            System.out.println("发短信~~");
        }
    }
    
    
    /**
    
     */
    class NewPhone extends Phone{
    
        // 重写的方法
        public void callUp() {
            // 先用子类爸爸的功能
            super.callUp();
    
            // 在子类爸爸原本功能的基础上开发新功能
            System.out.println("开始视频通话~~~");
        }
    
    
        // 重写的方法
        public void sendMessage() {
            // 先用子类爸爸的功能
            super.sendMessage();
    
            // 在子类爸爸原本功能的基础上开发新功能
            System.out.println("发送语音~~~");
            System.out.println("发送图片~~~");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    打电话~~
    开始视频通话~~~
    
    发短信~~
    发送语音~~~
    发送图片~~~
    
    Process finished with exit code 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    @Override重写注解
    • @Override是放在重写后的方法上,作为重写是否正确的校验注解。

    • 加上该注解后如果重写错误,编译阶段会出现错误提示。

    • 以后重写父类的方法都建议加上 @Override注解,代码可读性好、更安全、优雅
      在这里插入图片描述


    方法重写的注意事项和要求
    • 重写方法的名称、形参列表必须与被重写方法的名称和形参列表一致
      在这里插入图片描述

    • 私有方法不能被重写;
      在这里插入图片描述

    • 子类重写父类方法时,访问权限必须大于或者等于父类(暂时了解:缺省 < protected < public

      • 语法了解一下,认识一下。

      • 因为在实际开发中,子类重写的方法和父类方法是一模一样的,所以权限是等于父类的,不用乱改。

    • 子类不能重写父类的静态方法,如果重写会报错的。
      在这里插入图片描述


    方法重写总结

    1、方法重写是什么样的?

    • 子类中写一个与父类申明一样的方法覆盖父类的方法

    2、方法重写建议加上哪个注解,有什么好处?

    • 建议加上:@Override 重写方法检验注解;
    • 可以校验重写方法是否正确,好处:可读性好、更安全、优雅!

    3、重写方法有哪些基本要求?

    • 重写方法的名称和形参列表必须与被重写方法一致
    • 私有方法不能被重写
    • 子类重写父类方法时,访问权限必须大于或者等于父类被重写方法的访问权限
    • 子类不能重写父类的静态方法,否则会报错

    7、继承后:子类构造器的特点

    • 子类中所有的构造器默认都会访问父类中的无参数构造器,再执行自己。

      package com.app.d10_extends_constructor;
      
      /**
          动物类:父类
       */
      public class Animal {
          public Animal() {
              System.out.println("父类Animal无参数构造器被执行~~");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      package com.app.d10_extends_constructor;
      
      /**
          狗类:子类
       */
      public class Dog extends Animal{
          public Dog() {
              System.out.println("子类Dog无参数构造器被执行~~");
          }
      
          public Dog(String name) {
              System.out.println("子类Dog有参数构造器被执行~~");
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      package com.app.d10_extends_constructor;
      
      /**
          继承测试类
          目标:理解继承后,子类构造器的特点
          特点:子类中所有的构造器默认都会先访问父类的无参数构造器后,再执行自己
       */
      public class ExtendsTest1 {
          public static void main(String[] args) {
              Dog dog1 = new Dog();
              System.out.println(dog1);
      
              System.out.println("-----------");
      
              Dog dog2 = new Dog("哈士奇");
              System.out.println(dog2);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      父类Animal无参数构造器被执行~~
      子类Dog无参数构造器被执行~~
      com.app.d10_extends_constructor.Dog@4eec7777
      -----------
      父类Animal无参数构造器被执行~~
      子类Dog有参数构造器被执行~~
      com.app.d10_extends_constructor.Dog@3b07d329
      
      Process finished with exit code 0
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

    为什么?

    • 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据;
    • 子类在初始化之前,一定要调用父类构造器先完成父类数据的初始化
    • 通俗点讲:儿子想出生,是不是得先要有个爸爸。
    子类是如何调用父类构造器的?
    • 子类构造器的第一行语句默认是:super(),不写也默认存在
      在这里插入图片描述
    总结

    1、子类继承父类后构造器的特点是什么样的?

    • 子类中所有的构造器默认都会先访问父类的无参数构造器,再执行自己

    8、继承后:子类构造器访问父类有参数构造器

    super调用父类有参数构造器的作用:
    • 初始化继承自父类的数据。

      package com.app.d11_extends_constructor;
      
      /**
          人类:父类
       */
      public class People {
          private String name;
          private int age;
      
          public People() {
          }
      
          public People(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;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      package com.app.d11_extends_constructor;
      
      /**
          老师类:子类
       */
      public class Teacher extends People {
          public Teacher(String name, int age) {
              // 调用父类的有参数构造器:初始化继承自父类的数据
              super(name, age);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      package com.app.d11_extends_constructor;
      
      /**
          继承测试类
          目标:学习子类构造器如何去访问父类有参数构造器,还要清楚其作用!
          作用:提高代码的复用性、编程更简洁
       */
      public class ExtendsTest1 {
          public static void main(String[] args) {
              Teacher t1 = new Teacher("猪八戒", 34);
      
              System.out.println(t1.getName());
              System.out.println(t1.getAge());
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      猪八戒
      34
      
      Process finished with exit code 0
      
      • 1
      • 2
      • 3
      • 4
    • 执行流程:
      在这里插入图片描述

    如果父类中没有无参构造器,只有有参构造器,会出现什么现象?

    • 会报错。因为子类默认是调用父类中的无参数构造器的。

    如何解决?

    • 子类构造器中可以通过书写super(…),手动调用父类的有参构造器;
    • 也可以手动将父类的无参数构造器写出来。
    总结

    1、super调用父类构造器的作用是什么?

    • 通过调用父类中的有参数构造器来初始化继承自父类的数据

    9、this、super使用总结

    • this代表本类对象的引用;super代表父类存储空间的标识。
    关键字访问成员变量访问成员方法访问构造方法
    thisthis.成员变量
    访问本类成员变量
    this.成员方法(…)
    访问本类成员方法
    this(…)
    访问本类构造器
    supersuper.成员变量
    访问父类成员变量
    super.成员方法(…)
    访问父类成员方法
    super(…)
    访问父类构造器
    • 实际上,唯独只有this调用本类其他构造器我们是没有接触过的

    案例需求:

    • 在学员信息登记系统中,后台创建对象封装数据的时候如果用户没有输入学校,则默认使用 “bilibili培训中心”。

    • 如果用户输入了学校则使用输入的学校信息。

      package com.app.d12_this;
      
      /**
          学生类
       */
      public class Student {
          private String name;        // 学生姓名
          private String schoolName;  // 学校名称
      
          public Student() {
      
          }
      
      
          /**
              业务需求:当学生只输入姓名,不输入学校名称的时候,默认选择 "bilibili培训中心"
           */
          public Student(String name) {
              this(name, "bilibili培训中心");
          }
      
          public Student(String name, String schoolName) {
              this.name = name;
              this.schoolName = schoolName;
          }
      
          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;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24
      • 25
      • 26
      • 27
      • 28
      • 29
      • 30
      • 31
      • 32
      • 33
      • 34
      • 35
      • 36
      • 37
      • 38
      • 39
      • 40
      • 41
      • 42
      package com.app.d12_this;
      
      /**
          测试类
          目标:理解this(...)的作用:本类构造器中访问其他构造器
       */
      public class ThisTest1 {
          public static void main(String[] args) {
              Student s1 = new Student("殷素素", "冰火岛自学中心");
              System.out.println(s1.getName());
              System.out.println(s1.getSchoolName());
      
              System.out.println("---------");
      
              /**
                  如果学生不填写学校名称,默认这个学生填写的学校是:
                  bilibili培训中心。
               */
              Student s2 = new Student("张无忌");
              System.out.println(s2.getName());
              System.out.println(s2.getSchoolName());
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      殷素素
      冰火岛自学中心
      ---------
      张无忌
      bilibili培训中心
      
      Process finished with exit code 0
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
    this(…) 和 super(…) 使用的注意点
    • 子类通过 this(…) 去调用本类的其他构造器,本类其他构造器会通过 super 去手动调用父类的构造器,最终还是会调用父类的构造器的。
    • 注意:this(…) 和 super(…) 都只能放在构造器的第一行,所以二者不能存在同一构造器中。
  • 相关阅读:
    深入浅出PyTorc——进阶训练技巧
    Linux发展历程
    IDEA中maven jar下载失败问题处理
    新建anaconda使用jupyter出现的一系列问题
    控制台程序
    Linux之简介、shell命令、用户、用户组、环境变量
    Android开发:(AndroidStudio模拟器)如何将模拟器语言设置为中文 && 模拟器输入法更改为中文输入 && 键盘输入中文
    字节跳动测试岗面试挂在2面,复盘后,我总结了失败原因,决定再战一次...
    【华为OD机试真题 JS】英文输入法
    前后端解决跨域问题
  • 原文地址:https://blog.csdn.net/yelitoudu/article/details/126023469