• Java基础学习笔记(七)—— 面向对象编程(3)


    1 多态

    1.1 多态的概述


    什么是多态?

    • 同一个对象,在不同时刻表现出来的不同形态
    • 同类型的对象,执行同一个行为,会表现出不同的行为特征

    举例:

    • 我们可以说猫是猫:猫 cat = new 猫();
    • 我们也可以说猫是动物:动物 animal = new 猫();
    • 这里说明了猫在不同时刻表现出来了不同的形态,这就是多态

    多态的前提

    • 要有继承或实现关系
    • 要有方法的重写
    • 要有父类引用指向子类对象

    🙋举个栗子:

    public class Test1Polymorphic {
        public static void main(String[] args) {
            // 当前事物, 是一只猫
            Cat c = new Cat();
            // 当前事物, 是一只动物
            Animal a = new Cat();
            a.eat();
    
        }
    }
    
    class Animal {
        public void eat(){
            System.out.println("动物吃饭");
        }
    }
    
    class Cat extends Animal {
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
    }
    
    class Dog extends Animal {
        @Override
        public void eat() {
            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

    1.2 多态中成员访问特点


    • 构造方法:同继承一样,子类会通过super访问父类的构造方法
    • 成员变量:编译看左边(父类),执行看左边(父类)
      • 如果父类当中没有写这个成员变量,编译会报错
    • 成员方法:编译看左边(父类),执行看右边(子类)
      • 如果父类当中没有写这个成员方法,编译会报错,有的话,可以保证编译通过,然后运行子类的方法逻辑

    为什么成员方法和成员变量的访问不一样?

    • 因为成员方法是有重写(子类重写父类的方法),而成员变量没有
    public class Test2Polymorpic {
        public static void main(String[] args) {
            Fu f = new Zi();
            System.out.println(f.num); // 10
            f.method(); // Zi.. method
        }
    }
    
    class Fu {
        int num = 10;
    
        public void method(){
            System.out.println("Fu.. method");
        }
    }
    
    class Zi extends Fu {
        int num = 20;
    	@Override
        public void method(){
            System.out.println("Zi.. method");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    1.3 多态的利与弊


    好处

    • ​在多态形式下,右边对象可以实现解耦合,便于程序的维护和扩展
    • 定义方法时候,使用父类类型作为参数,该方法就可以接收这个父类的任意子类对象,在使用的时候,使用具体的子类型参与操作

    弊端 ​

    • 不能使用子类的特有功能

    🙋举个栗子:

    public class Test3Polymorpic {
        public static void main(String[] args) {
            useAnimal(new Dog());  // 狗吃肉
            useAnimal(new Cat());  // 猫吃鱼
        }
    	
    	// 这个方法就可以接收Animal的一切子类对象
        public static void useAnimal(Animal a){  // Animal a = new Dog();
                                                 // Animal a = new Cat();
            a.eat();
            //a.watchHome(); 编译会报错
    
    		
    //        Dog dog = (Dog) a;
    //        dog.watchHome();  // ClassCastException  类型转换异常
    // 		  调用useAnimal(new Cat());时,执行Animal a = new Cat();将原本是猫的a转成狗了
    //        若执行上述注释的代码,运行结果为:狗吃肉\n看家\n猫吃鱼\n报ClassCastException 类型异常
        }
    }
    
    abstract class Animal {
        public abstract void eat();
    }
    
    class Dog extends Animal {
        public void eat() {
            System.out.println("狗吃肉");
        }
    
        public void watchHome(){ //这是子类Dog类特有的方法
            System.out.println("看家");
        }
    }
    
    class Cat extends Animal {
        public void eat() {
            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

    1.4 多态的转型


    多态中的转型有两种:

    • 向上转型,也叫自动类型转换
      • 从子到父
      • 父类引用指向子类对象
    • 向下转型,也叫强制类型转换
      • 从父到子
      • 父类引用转为子类对象
      • 格式:子类 对象变量=(子类)父类类型变量;
      • 作用:可以解决多态下的劣势,实现调用子类独有的功能

    🙋举个栗子:

    public class Test4Polymorpic {
        public static void main(String[] args) {
            // 1. 向上转型 : 父类引用指向子类对象
            Fu f = new Zi();
            f.show();
            // 多态的弊端: 不能调用子类特有的成员
            // f.method(); 编译看左边(父类)没有这个方法会报错
    
    		// 多态的方式,一般也只会调用共有的方法,如果一定要调用子类中特有的成员,有以下两种方法:
            // A: 直接创建子类对象
            // B: 向下转型
    
            // 2. 向下转型 : 从父类类型, 转换回子类类型
            Zi z = (Zi) f; // 相当于强制类型转换
            z.method();
        }
    }
    
    class Fu {
        public void show(){
            System.out.println("Fu..show...");
        }
    }
    
    class Zi extends Fu {
        @Override
        public void show() {
            System.out.println("Zi..show...");
        }
    
        public void method(){
            System.out.println("我是子类特有的方法, method");
        }
    }
    
    • 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

    ❗️注意:

    • 如果转型后的类型和对象真实类型不是同一种类型,那么在转换的时候就会出现ClassCastException
    • 解决办法:关键字instanceof(建议强制类型转换之前,先判断变量指向对象的真实类型)
      • 使用格式:变量名 instanceof 类型
        • 判断关键字左边的变量是否是右边的类型,返回 boolean 类型结果
    • 另外,有继承或实现关系的2个类型就可以进行强制类型转换,编译无问题,运行时,如果发现强制类型转换后的类型不是对象的真实类型则报错

    接着1.3中的例子,可以在 useAnimal 方法中加下以下代码进行判断

    if(a instanceof Dog){
    	Dog dog = (Dog) a;
    	dog.watchHome();
    }
    
    • 1
    • 2
    • 3
    • 4

    2 内部类

    2.1 内部类概述


    内部类:就是在一个类中定义一个类。举例:在A类的内部定义一个B类,B类就被称为内部类

    格式:

    修饰符 class 外部类名{
    	修饰符 class 内部类名{
    	}
    }
    
    • 1
    • 2
    • 3
    • 4

    内部类的使用场景与作用:

    • 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整结构又只为外部事物提供服务,那么整个内部的完整结构可以选择使用内部类来设计
    • 内部类通常可以方便的访问外部类的成员,包括私有成员
    • 内部类提供了更好的封装性,内部类本身就可以用privateprotected等修饰,封装性可以做更多控制

    按照内部类在类中定义的位置不同,可以分为如下两种形式

    • 成员内部类
      • 在类的成员位置
    • 局部内部类
      • 在类的局部位置

    2.2 成员内部类

    2.2.1 一般成员内部类


    也叫实例内部类,没有static修饰,属于外部类的对象所有。

    在JDK16之前,成员内部类中不能定义静态成员,JDK16开始可以定义静态成员了。

    内部类的访问特点:

    • 内部类可以直接访问外部类的成员,包括私有
    • 外部类要访问内部类的成员,必须创建对象

    创建内部类对象的格式:

    外部类名.内部类名 对象名 = new 外部类对象().new 内部类对象();
    
    • 1

    示例代码

    public class Test1Inner {
        public static void main(String[] args) {
    		// 外部类要访问内部类的成员,必须创建对象
            Outer.Inner i = new Outer().new Inner();
            System.out.println(i.num); 
            i.show(); 
            		  
        }
    }
    class Outer {
        private int a = 10;
        private void aa(){
            System.out.println("我是我外部类中的实例方法");
        }
        class Inner {
            int num = 10;
    
            public void show(){
                System.out.println("Inner..show");
                // 内部类, 访问外部类成员, 可以直接访问, 包括私有
                System.out.println(a);
                aa(); 
            }
        }
    }
    
    • 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

    成员内部类,也属于成员,既然属于成员就可以被一些修饰符所修饰

    • private 私有成员内部类
    • static 静态成员内部类

    ❗️注意:

    • 成员内部类中可以直接访问外部类的静态成员(有static修饰)
      • 外部类的静态成员只有一份,可以被共享
    • 成员内部类中可以直接访问外部类的实例成员(没有static修饰)
      • 因为必须先有外部类对象,才能有成员内部类对象,所以可以直接访问外部类的实例成员

    ✨ 面试题拓展:观察如下代码,写出合适的代码对应其注释要求输出的结果

    在这里插入图片描述

    public class People {
        private int heartBeat=150;
        public class Heart{
            private int heartBeat=110;
            public void show(){
                int heartBeat=78;
                System.out.println(heartBeat); // 78
                System.out.println(this.heartBeat); // 110
                System.out.println(People.this.heartBeat); // 150
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    ❗️注意:

    • 在成员内部类中访问所在外部类的对象,格式:外部类名.this

    2.2.2 私有成员内部类


    public class Test2Innerclass {
        public static void main(String[] args) {
            // Outer.Inner oi = new Outer().new Inner(); 内部类为私有,会报错
    
            Outer o = new Outer();
            o.method();
        }
    }
    
    class Outer {
        private class Inner { //内部类私有了
            public void show(){
                System.out.println("inner..show");
            }
        }
    
        public void method(){
            Inner i = new Inner();
            i.show();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.2.3 静态成员内部类


    静态内部类:有static修饰,属于外部类本身,它的特点和使用与普通类是完全一样的,类有的成分它都有,只是位置在别人里面而已

    要注意对于静态成员内部类,其创建对象的格式发生了变化:

     外部类名.内部类名 对象名 = new 外部类名.内部类名();
    
    • 1

    示例代码

    public class Test3Innerclass {
        public static void main(String[] args) {
            Outer.Inner oi = new Outer.Inner();
            oi.show();
    
            Outer.Inner.method(); // 静态定义的内容可以直接通过类名调用
        }
    }
    
    class Outer {
    	public static int a=100;
    	public String name;
        static class Inner {
            public void show(){
                System.out.println("inner..show");
            }
    		// 在静态成员内部类中再定义一个静态方法,
            public static void method(){
                System.out.println("inner..method");
    			
    			// 静态内部类中可以直接访问外部类的静态成员
    			System.out.println(a);
    			// 外部类的实例成员必须用外部类对象访问 
    			Outer o=new Outer();
    			System.out.println(o.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

    ❗️注意:

    • 静态内部类中可以直接访问外部类的静态成员(有static修饰)
      • 外部类的静态成员只有一份,可以被共享
    • 静态内部类中不可以直接访问外部类的实例成员(没有static修饰)
      • 外部类的实例成员必须用外部类对象访问

    2.3 局部内部类


    局部内部类

    • 概念
      • 是定义在方法中的类
    • 使用方法
      • 所以外界是无法使用的,只能在方法内部创建对象并使用
    • 访问特点
      • 该类可以直接访问外部类的成员,也可以访问方法内的局部变量
    public class Test4Innerclass {
        public static void main(String[] args) {
            Outer o = new Outer();
            o.method();
        }
    }
    
    class Outer {
        int a = 10;
        public void method(){
            int b = 20;
            class Inner {
                public void show(){
                    System.out.println("show...");
                    System.out.println(a);//10
                    System.out.println(b);//20
                }
            }
    
            Inner i = new Inner();
            i.show();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    2.4 匿名内部类(重点)


    概念:匿名内部类本质上是一个特殊的局部内部类(一般定义在方法,代码块中)【匿名内部类的使用更加频繁,更为重要】

    作用:方便创建子类对象,最终目的是为了简化代码的编写

    前提:存在一个接口或者类

    特点:

    • 匿名内部类是一个没有名字的内部类
    • 匿名内部类写出来就会产生一个匿名内部类对象
    • 匿名内部类的对象类型相当于是当前 new 的那个类型的子类类型

    格式:

    new 类名或接口名(){
    	重写方法;
    } 
    
    • 1
    • 2
    • 3

    示例代码

    public class Test5Innerclass {
        /*
            1. 创建实现类, 通过implements关键字去实现接口
            2. 重写方法
            3. 创建实现类对象
            4. 调用重写后的方法
         */
        public static void main(String[] args) {
            InterImpl ii = new InterImpl();
            ii.show(); // InterImpl  重写的show方法
    
            // 匿名内部类的理解: 将继承\实现, 方法重写, 创建对象, 放在了一步进行.
            // 解释: 实现了Inter接口的, 一个实现类【对象】
            new Inter() {
                @Override
                public void show() {
                    System.out.println("我是匿名内部类中的show方法");
                }
            }.show();
    
            // 情况: 接口中存在多个方法,如何调用多个方法?
            // "new Inter2(){……}"是接口的一个实现类对象,可以在前面用父接口的引用承接一下,就可以以多态的形式
            Inter2 i = new Inter2() {
                @Override
                public void show1() {
                    System.out.println("show1...");
                }
                @Override
                public void show2() {
                    System.out.println("show2...");
                }
            };
            i.show1();
            i.show2();
        }
    }
    
    interface Inter {
        void show();
    }
    
    interface Inter2 {
        void show1();
        void show2();
    }
    
    class InterImpl implements Inter {
        @Override
        public void show() {
            System.out.println("InterImpl  重写的show方法");
        }
    }
    
    • 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

    匿名内部类的使用场景

    • 当发现某个方法的形式参数是接口或抽象类时,我们就可以将匿名内部类作为实际参数传过去,来简化传统的代码
    public class TestSwimming {
        public static void main(String[] args) {
            goSwimming(new Swimming() {
                @Override
                public void swim() {
                    System.out.println("铁汁, 我们去游泳吧");
                }
            });
        }
    
        /**
         * 使用接口的方法
         */
        public static void goSwimming(Swimming swimming){
            swimming.swim();
        }
    }
    
    /*
        游泳接口
     */
    interface Swimming {
        void swim();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3 lambda表达式

    3.1 函数式编程思想概述


    在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”。

    面向对象思想

    • 强调“必须通过对象的形式来做事情”

    函数式思想

    • 则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”

    而Lambda表达式就是函数式思想的体现

    3.2 lambda表达式概述


    lambda表达式是 JDK8 开始的一种新语法格式,其主要作用就是 简化匿名内部类 的代码


    格式: (形式参数) -> {代码块}

    (匿名内部类被重写方法的形式参数)>{
    	被重写方法的方法体;
    }
    
    • 1
    • 2
    • 3
    • 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
    • ->:由英文中画线和大于符号组成,固定写法,没有实际含义,仅代表指向动作
    • 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容

    lambda 表达式的使用前提:

    • 有一个 接口
    • 接口中 有且仅有 一个 抽象方法

    3.3 lambda表达式的使用

    3.3.1 无参数无返回值


    需求:

    • 编写一个接口ShowHandler
    • 在该接口中存在一个抽象方法show,该方法是无参数无返回值
    • 在测试类ShowHandlerDemo中存在一个方法useShowHandler
      • 方法的参数是ShowHandler类型的
      • 在方法内部调用了ShowHandlershow方法
    public class TestLambda {
        public static void main(String[] args) {
            useShowHandler(new ShowHandler() {
                @Override
                public void show() {
                    System.out.println("我是匿名内部类中的show方法");
                }
            });
    
            // Lambda实现
            useShowHandler( () -> System.out.println("我是Lambda中的show方法"));
        }
    
        public static void useShowHandler(ShowHandler showHandler){
            showHandler.show();
        }
    }
    
    interface ShowHandler {
        void show();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.3.2 带参数无返回值


    需求:

    • 首先存在一个接口StringHandler
    • 在该接口中存在一个抽象方法printMessage,该方法是有参数无返回值
    • 在测试类StringHandlerDemo中存在一个方法useStringHandler
      • 方法的的参数是StringHandler类型的
      • 在方法内部调用了StringHandlerprintMessage方法
    public class StringHandlerDemo {
        public static void main(String[] args) {
            useStringHandler(new StringHandler() {
                @Override
                public void printMessage(String msg) {
                    System.out.println("我是匿名内部类" + msg);
                }
            });
    
            // Lambda实现
            useStringHandler( (msg) -> {System.out.println("我是Lambda表达式" + msg);});
        }
    
        public static void useStringHandler(StringHandler stringHandler){
            stringHandler.printMessage("hello world");
        }
    }
    
    interface StringHandler {
        void printMessage(String msg);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    3.3.3 无参数有返回值


    需求:

    • 首先存在一个接口RandomNumHandler
    • 在该接口中存在一个抽象方法getNumber,该方法是无参数但是有返回值
    • 在测试类RandomNumHandlerDemo中存在一个方法useRandomNumHandler
      • 方法的的参数是RandomNumHandler类型的
      • 在方法内部调用了RandomNumHandlergetNumber方法
    import java.util.Random;
    
    public class RandomNumHandlerDemo {
        public static void main(String[] args) {
            useRandomNumHandler(new RandomNumHandler() {
                @Override
                public int getNumber() {
                    Random r = new Random();
                    int num = r.nextInt(10) + 1;
                    return num;
                }
            });
    
            useRandomNumHandler( () -> {
                    Random r = new Random();
                    int num = r.nextInt(10) + 1;
                    return num;
                    // 注意: 如果lambda所操作的接口中的方法, 有返回值, 一定要通过return语句, 将结果返回
                    // 否则会出现编译错误
            } );
        }
    
        public static void useRandomNumHandler(RandomNumHandler randomNumHandler){
            int result = randomNumHandler.getNumber();
            System.out.println(result);
        }
    }
    
    interface RandomNumHandler {
        int getNumber();
    }
    
    • 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

    3.3.4 带参数带返回值


    需求:

    • 首先存在一个接口Calculator
    • 在该接口中存在一个抽象方法calc,该方法是有参数也有返回值
    • 在测试类CalculatorDemo中存在一个方法useCalculator
      • 方法的的参数是Calculator类型的
      • 在方法内部调用了Calculatorcalc方法
    public class CalculatorDemo {
        public static void main(String[] args) {
            useCalculator(new Calculator() {
                @Override
                public int calc(int a, int b) {
                    return a + b;
                }
            });
    
            useCalculator( ( a,  b) ->
                 a + b
             );
        }
    
        public static void useCalculator(Calculator calculator){
            int result = calculator.calc(10,20);
            System.out.println(result);
        }
    }
    
    interface Calculator {
        int calc(int a, int b);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    lambda表达式的省略规则:

    • 参数类型可以省略,但是有多个参数的情况下,不能只省略一个
    • 如果参数有且仅有一个,那么小括号可以省略,没有参数时不能省略
    • 如果代码块的语句只有一条,可以省略大括号,同时要省略分号
    • 如果代码块的语句只有一条,而且是return语句,可以省略大括号,同时必须要省略return和分号

    3.4 与匿名内部类的区别


    • 所需类型不同
      • 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
      • Lambda表达式:只能是接口
    • 使用限制不同
      • 如果接口中有且仅有一个抽象方法,可以使用 Lambda 表达式,也可以使用匿名内部类
      • 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用 Lambda 表达式
    • 实现原理不同
      • 匿名内部类:编译之后,产生一个单独的.class字节码文件保存在硬盘当中
      • Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成,不会保留到本地的硬盘当中

    下一篇:Java SE 学习笔记(六)—— 常用API(1)

  • 相关阅读:
    人工智能基础 | 机器学习模型评估与选择(二)
    华为机试:单词搜索(找到它)
    聊聊昨日ChatGPT全球宕机事件,带给我们的警示
    [附源码]java毕业设计图书管理系统
    python-List&tuple&dict
    Python学习:if else对缩进的要求
    uni-app去除页面头部的标题栏
    互联网应用主流框架整合之Spring Boot运维体系
    缓存 (模拟两种:数据库提供数据到缓存、外界提供数据到缓存)
    界面组件DevExpress WPF v22.1 - 全新升级类Office、Excel功能
  • 原文地址:https://blog.csdn.net/hu_wei123/article/details/127821509