Java---第七章(抽象类,接口,多态,Object,异常处理)

在这里插入图片描述

一 抽象类

抽象类的介绍

概念:

抽象类是被声明为abstract的类-它可能包含也可能不包含抽象方法,抽象类不能被实例化,但是可以被子类化(也就是可以被继承)

抽象类定义语法:

public abstract class 类名{
    
    //定义一个抽象类

}

抽象方法定义语法:
知道要做一件事情,但是不知道如何去做

访问修饰符 abstract 返回值类型 方法名(参数列表);

抽象类应用场景

一般来说,描述抽象的事物就要用到抽象类。
比如:动物,设备,几何图形等

public abstract class Animal {
    
    
    public abstract void eat();
}

public class Panda extends Animal {
    
    

    @Override
    public void eat() {
    
    
        System.out.println("熊猫吃竹叶");
    }
}
public class PersonTest{
    
    
    public static void main(String[] args) {
    
    
        Panda a = new Panda();
        a.eat();
    }
}

如果一个类继承于一个抽象类,那么该类必须实现这个抽象类中所有的抽象方法。否则,该类必须定义抽象类
抽象类不一定有抽象方法,抽象方法一定是抽象类

二 接口

接口的介绍

在Java编程语言中,接口类似于类的引用类型。它只能包含常量,方法签名,默认方法,静态方法和嵌套类型
方法主体仅适用于默认方法和静态方法。
接口无法实例化–它们只能由类实现或者其他接口拓展

接口声明可以包含方法签名,默认方法,静态方法和常量定义
具有实现的方法是默认方法和常量方法

扫描二维码关注公众号,回复: 16439646 查看本文章

接口中没有构造方法

语法:

[public] interface 接口名{
    
    
	[public static final]数据类型 变量名 = 变量的值;
	//接口中定义变量,该变量是静态变量,在定义时必须赋值

	返回值类型 方法名([参数列表]);//定义接口方法
	
	default 返回值类型 方法名([参数列表]){
    
    
	//接口中定义的默认方法,必须在JDK8及以上版本使用
		[return 返回值;]
	}

	static 返回值类型 方法名([参数列表]){
    
    
	//接口中定义的静态方法,必须在JDK8及以上版本使用
		[return 返回值;]
	}

	private 返回值类型 方法名([参数列表]){
    
    
	//接口中定义的私有方法,必须在JDK9及以上版本使用
		[return 返回值;]
	}
}

示例:

public interface Test {
    
    
    public static final int number = 10;
    public abstract void show();
    public default int plus(int a,int b){
    
    
        return a+b;
    }
    public default int multiply(int x,int y){
    
    
        return x*y;
    }
    private String getName(){
    
    
        return "admin";
    }
}

接口继承

语法:

[public] interface 接口名 extends 接口名1,接口名2,....接口名n{
    
    

}

注意:接口可以多继承,这是Java中唯一可以使用多继承的地方。接口包含的变量都是静态常量,接口中包含的方法签名都是公开的抽象方法,接口中的默认方法和静态方法在JDK8及以上版本才能定义,接口的私有方法必须在JDK9及以上版本才能定义。接口编译完成后也会生成想要的class文件

接口实现

语法:

访问修饰符 class 类名 implements 接口名1,接口名2,...接口名n{
    
    
}

实现接口的类必须实现接口中声明的所有方法

一个类如果实现了一个接口,那么就必须实现这个接口中定义的所有抽象方法(包括接口通过继承关系继承过来的抽象方法),这个类被称为接口的实现类或者说子类,与继承关系一样,实现类与接口的关系也是is-a关系

练习:

打印机对外暴露有墨盒(颜色)和纸张(大小)接口,墨盒生产商按照墨盒接口的约定生产黑白墨盒和彩色墨盒,纸张生产商按纸张接口的约定生产A2和A4纸张

接口:
墨盒:

public interface InkBox {
    
    
    String getcolor();
}

纸张:

public interface Paper {
    
    
    String getsize();
}

类:
黑白打印机:

public class BlackInkBox implements InkBox{
    
    
    @Override
    public String getcolor() {
    
    
        return "黑白";
    }
}

彩色打印机:

public class ColorInkBox implements InkBox{
    
    
    @Override
    public String getcolor() {
    
    
        return "彩色";
    }
}

A2纸张:

public class A2Paper implements Paper{
    
    
    @Override
    public String getsize() {
    
    
        return "A2";
    }
}

A4纸张:

public class A4Paper implements Paper{
    
    
    @Override
    public String getsize() {
    
    
        return "A4";
    }
}

打印机:

public class Printer {
    
    
    private InkBox inkBox;
    private Paper paper;
    public Printer(){
    
    
    }
    public Printer(InkBox inkBox, Paper paper) {
    
    
        this.inkBox = inkBox;
        this.paper = paper;
    }
    public void print(){
    
    
        System.out.printf("打印机使用%s墨盒打印%s纸张上打印\n",inkBox.getcolor(),paper.getsize());
    }
    public InkBox getInkBox() {
    
    
        return inkBox;
    }
    public void setInkBox(InkBox inkBox) {
    
    
        this.inkBox = inkBox;
    }
    public Paper getPaper() {
    
    
        return paper;
    }
    public void setPaper(Paper paper) {
    
    
        this.paper = paper;
    }
}

测试:

public class PrinterTest {
    
    
    public static void main(String[] args) {
    
    
        Printer p1 = new Printer();
        p1.setInkBox(new ColorInkBox());
        p1.setPaper(new A4Paper());
        p1.print();

        Printer p2 = new Printer(new BlackInkBox(),new A2Paper());
        p2.print();
    }
}

三 抽象类和接口的区别

  • 抽象类拥有构造方法,而接口没有构造方法
  • 抽象类可以定义成员变量,静态变量,静态常量,而接口中只能定义公开的静态常量
  • 抽象类中的方法可以有受保护,默认的方法,而接口中的方法都是公开的(JDK9中可以定义的私有方法除外)
  • 抽象类主要应用在对于抽象事物的描述,而接口主要应用在对于约定,规则的描述
  • 抽象类只能够单继承,而接口可以实现多继承

四 多态(Polymorphism)

多态介绍:

概念:

多态性的字典定义是指在生物学原理,其中的生物体或物质可具有许多不同的形式或阶段。
该原理也可以应用于面向对象的编程和Java语言之类的语言。
一个子类可以定义自己的独特行为,但可以共享父类的某些相同特征。

从上面的描述我们可以得到:

  • 继承,接口就是多态的具体体现方式
  • 多态主要体现在类别,做事的方式上面
  • 多态是面向对象的三大特征之一
  • 多态分为编译时多态(方法重载)和运行时多态两大类

编译时多态:
举例:
方法重载

计算随机两个数字的和

public class Printer {
    
    
    public long calculate(long a,long b){
    
    
        return a + b;
    }
    public double calculate(double a,double b){
    
    
        return a + b;
    }
}
public class PrinterTest {
    
    
    public static void main(String[] args) {
    
    
        Printer p = new Printer();
        long result1 = p.calculate(1,2);
        double result2 = p.calculate(1.0,2.0);
    }
}

运行时多态:

Java虚拟机(JVM)为每个变量中引用的对象调用适当的方法。它不会调用由变量类型定义的方法。这种行为称为虚拟调用方法,它说明了Java语言中重要的多态性特征的一个方面

父类定义一个方法

public class Printer {
    
    
    public void show(){
    
    
        System.out.println("Father");
    }
}

子类重写这个方法

public class Person extends Printer{
    
    
    @Override
    public void show() {
    
    
        System.out.println("Child");
    }
}
public class PrinterTest {
    
    
    public static void main(String[] args) {
    
    
        //变量f的类型是Printer
        Printer p = new Person();
        //p调用show()时,不会调用Printer定义的方法
        p.show();
    }
}

五 instanceof运算符

instanceof本身就是表示是什么什么的一个实例。
主要应用在类型的强制转换上面。
在使用类型的强制转换时,如果使用不正确,在运行时会报错。
instanceof运算符对转换的目标类型进行检测,如果是,则进行强制类型转换,这样可以保证程序的正常运行

语法:

对象名 instanceof 类名;//表示检测对象是否是指定类型的一个实例,返回值为boolean型

练习:

某商场有电视机,电风扇,空调等电器设备展示。现有质检人员对这些电器设备——检测,如果是电视机,就能播放视频检测;如果是电风扇,就启动电风扇;如果是空调,就制冷检测。

电器设备抽象类:

public abstract class Device {
    
    
    public abstract void show();
}

电视机:

public class TV extends Device{
    
    
    @Override
    public void show() {
    
    
        System.out.println("这是电视机");
    }
    public void vedio(){
    
    
        System.out.println("播放电视剧");
    }
}

空调:

public class AirCod extends Device{
    
    

    @Override
    public void show() {
    
    
        System.out.println("这是空调");
    }
    public void ice(){
    
    
        System.out.println("制冷");
    }
}

电风扇:

public class ElectronicFan extends Device{
    
    
    @Override
    public void show() {
    
    
        System.out.println("这是电风扇");
    }
    public void start(){
    
    
        System.out.println("启动电风扇");
    }
}

质检:

public class Quality {
    
    
    public void test(Device device){
    
    
        device.show();
        if(device instanceof TV){
    
    
            ((TV)device).vedio();
        } else if (device instanceof  AirCod) {
    
    
            ((AirCod)device).ice();
        } else if (device instanceof  ElectronicFan) {
    
    
            ((ElectronicFan)device).start();
        }
    }
}

测试:

public class DeviceTest {
    
    
    public static void main(String[] args) {
    
    
        Quality q = new Quality();
        q.test(new TV());
        q.test(new AirCod());
        q.test(new ElectronicFan());
    }
}

六 Object类常用方法

Object类中定义的方法大多数都是属于native方法(本地方法),实现方式是在**C++**中

getClass()

public final Class getClass()

getClass() 方法返回一个Class对象,该对象具有可用于获取该类的信息的方法,例如其名称(getSimpleName()),其超类//(getSuperclass())及其实现的接口(getInterfaces())

package org.device.Device;

public class DeviceTest {
    
    
    public static void main(String[] args) {
    
    
        Quality q = new Quality();
        q.test(new TV());
        q.test(new AirCod());
        q.test(new ElectronicFan());

        TV tv = new TV();
        Class clazz  = tv.getClass();
        String name = clazz.getSimpleName();//获取类名
        System.out.println(name);
        String className = clazz.getName();//获取类的全限定名
        System.out.println(className);

        Class superclass = clazz.getSuperclass();//获取父类的定义信息
        String superName = superclass.getSimpleName();//获取父类的名称
        System.out.println(superName);
        String superClassName = superclass.getName();//获取父类的全限定名
        System.out.println(superClassName);

        String s = "admin";
        Class stringClass  = s.getClass();
        Class[] interfaceClasses = stringClass.getInterfaces();
        for(int i=0;i<interfaceClasses.length;i++){
    
    
            Class interfaceClass  = interfaceClasses[i];
            String interfaceName = interfaceClass.getSimpleName();//获取接口的名称
            System.out.println(interfaceName);
            String interfaceClassName = interfaceClass.getName();//获取接口的全限定名
            System.out.println(interfaceClassName);
        }
    }
}

hashCode()

public int hashCode()

返回值是对象的哈希码,即对象的内存地址(十六进制0x…)

根据定义,如果两个对象相等,则他们的哈希码也必须相等。
如果重写equals()方法,则会更改两个对象的相等方式,并且Object的hashCode()实现不再有效。
因此,如果重写equals()方法,则还必须重写hashCode()方法

Object类中的hashCode()方法返回的就是对象的内存地址。一旦重写hashCode()方法,那么Object中的hashCode()方法就失效了,此时hashCode()返回的值就不再是内存地址了

根据定义,如果两个对象相等,则哈希码一定相等,反之则不然
重写了equals方法,就必须重写hashCode方法,才能满足上面的定义结论

重写hashCode()

    @Override
    public int hashCode(){
    
    
        return name.hashCode() + age;
    }

equals(Object obj)

public boolean equals(Object obj)

equals()方法比较两个对象是否相等,如果相等则返回true。
Object类提供的 equals()方法使用身份运算符(==) 来确定两个对象是否相等。
对于原始数据类型,这将得出正确的结果
但是对于对象,Object提供的equals()方法测试对象引用是否相等,即所比较的对象是否完全相等

要测试两个对象在等效性上是否完全相等(包含相同的信息),必须重写equals()方法。

public class Student {
    
    
    private String name;
    private int age;

    public Student(String name, int age) {
    
    
        this.name = name;
        this.age = age;
    }
    @Override
    public int hashCode(){
    
    
        return name.hashCode() + age;
    }
}

以下举例,返回值为false

public class DeviceTest {
    
    
    public static void main(String[] args) {
    
    
        Student s1 = new Student("张三",1);
        Student s2 = new Student("张三",1);
        boolean result = s1.equals(s2);
        System.out.println(result);//false
    }
}

重写equals()方法

    @Override
    public boolean equals(Object obj) {
    
    
        if(this == obj) return true;
        //比较类的定义是否相等
        if(this.getClass() != obj.getClass()) return false;
        //类的定义一致,那么对象obj就可被强制转换为Student
        Student other = (Student) obj;
        return this.name.equals(other.name) && this.age == other.age;
    }
  • 比较内存地址
  • 检测是否是统一类型
  • 检测属性是否相等

描述 == 和 equals 方法的区别:

基本数据类型使用 == 比较的就是两个数据的字面量是否相等。引用数据类型使用 其等号 比较的是内存地址。
equals方法来自Object类,本身实现也是 == ,此时它们没有任何区别。
但是equals方法可能被重写,此时比较就需要看重写逻辑来进行

hashCode()和equals()

如果重写了equals方法,那么一定要重写hashCode方法,因为不重写hashCode就会调用Object类中的hashCode方法,得到的是内存地址,不同对象的内存地址是不一样的。
但是equals方法重写之后,比较的不是内存地址,而是对象的内部信息,不然就会造成多个不同的对象相等却拥有不同的哈希码

toString()

public String toString()

应该始终考虑在类中重写toString()方法

Object中的toString() 方法返回该对象的String表示形式,这对于调试非常有用。
对象的String表示形式完全取决于对象,这就是为什么要在类中重写toString方法

那上面举例:

public class DeviceTest {
    
    
    public static void main(String[] args) {
    
    
        Student s1 = new Student("张三",1);
        System.out.println(s1);
        Student s2 = new Student("张三",1);
        boolean result = s1.equals(s2);
        System.out.println(result);//false
    }
}

打印s1的结果为:

org.device.Device.Student@bd2ea

并非是 张三 1

重写toString方法

    @Override
    public String toString() {
    
    
        return name + "\t" + age;
    }

finalize()

protected void finalize() throws Throwable

Object类提供了一个回调的方法finalize() ,当该对象变为垃圾时可以在该对象上调用该方法。
Object类的finalize()实现不执行任何操作–你可以覆盖finalize()进行清理,例如释放资源

finalize重写

    //当一个Student对象变成垃圾时可能会被调用
    @Override
    protected void finalize() throws Throwable {
    
    
        this.name = null;
        System.out.println("所有资源已释放完毕,可以进行清理了");
    }
public class DeviceTest {
    
    
    public static void main(String[] args) {
    
    
        show();
        //garbage collertor
        System.gc();
    }
    public static void show(){
    
    
        //s对象的作用范围只是在show()方法中
        //一旦方法执行完毕,那么s对象就应该消亡,释放内存
        Student s = new Student("李四",21);
        System.out.println(s);
    }
}

只有堆上的内存才可能产生垃圾(栈上没东西来引用它)

七 异常

异常的介绍

概念:

异常是在程序执行期间发生的事件,该事件中断了程序指令的正常流程

当方法内发生错误时,该方法将创建一个对象并将其交给运行时系统
该对象称为异常对象包含有关错误信息,包括错误的类型和发生错误时程序的状态,创建异常对象并将其交给运行时系统统称为抛出异常

异常是由方法抛出的

异常体系:
在这里插入图片描述
Throwable
是所有异常的父类,其常用方法如下:

public String getMessage();//获取异常发生的原因

public void printStackTrace();//打印异常在栈中的轨迹信息

Error

Error是一种非常严重的错误,程序员不能通过编写解决

Exception

其表示异常的意思,主要是程序员在编写代码时考虑不周导致的问题。异常分为运行时异常,检查时异常两大类,一旦程序出现这些异常,程序员应该去处理

RuntimeException

其表示运行时异常,所有在程序运行时抛出的异常类型都是属于RuntimeException的子类,运行时异常一般来说程序可以自动恢复,不必处理

检查异常

检查异常是指编译器在编译代码的过程中发现不正确的编码所抛出的异常。

如何处理异常

在Java中,异常的种类很多,如果每一种异常类型我们都需要去记住,这无疑是一件很困难的事。如果能够有一套机制来处理异常,那么将减少程序员在开发时的耗时。
Java就提供了一套异常处理机制来出来异常,
Java处理异常用了五个关键字:
throw
throws
try
catch
finally

throw抛出异常:

throw关键字只能在方法内部使用,throw关键字抛出异常表示自身并未对异常进行处理

语法:

throw 异常对象;//通常与if选择结构配合使用

示例:

import java.util.Scanner;

public class DeviceTest {
    
    
    private static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) {
    
    
        calculate();
    }
    public static void calculate(){
    
    
        System.out.println("请输入一个数字");
        int number1 = sc.nextInt();
        System.out.println("请再输入一个数字");
        int number2 = sc.nextInt();
        if(number2 == 0){
    
    
            throw new ArithmeticException("除数不能为0");
        }
        int result = number1 / number2;
        System.out.println(result);
    }
}

请输入一个数字
1
请再输入一个数字
0
Exception in thread “main” java.lang.ArithmeticException: 除数不能为0
at org.device.Device.DeviceTest.calculate(DeviceTest.java:16)
at org.device.Device.DeviceTest.main(DeviceTest.java:8)

throw new ArithmeticException("除数不能为0");

这两种方法都可以

ArithmeticException e = new ArithmeticException("除数不能为0");
throw e;

throws声明可能抛出的异常类型:

throws关键字只能应用在方法或者构造方法的定义上,对可能抛出异常类型进行声明,自身不会对异常做出处理,由方法调用者来处理
如果方法调用者未处理,则异常将持续向上一级调用者抛出,直至main()方法为止,如果mian()方法也未处理,那么程序可能因此终止。

语法:

访问修饰符 返回值类型 方法名(参数列表) throws 异常类型1,异常类型2,....异常类型n{
    
    
}

示例:

import java.util.Scanner;

public class DeviceTest {
    
    
    private static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) {
    
    
        deviced();
    }
    public  static int deviced() throws InterruptedException,ArithmeticException{
    
    
        int number1 = getNumber();
        int number2 = getNumber();
        return number1/number2;
    }
    public static int  getNumber()throws InterruptedException{
    
    
        System.out.println("请输入一个数字");
        int number = sc.nextInt();
        return number;
    }
}

throws可以声明方法执行时可能抛出的异常类型,但需要注意的是:方法执行过程中只能抛出一个异常

try-catch捕获异常:

throw和throws关键字均没有对异常进行处理,这可能会导致程序终止,在这种情况下,可以使用try-catch结构来对抛出异常进行捕获处理,从而保证程序能够正常进行。

语法:

try{
    
    
	//代码块
}catch(异常类型 异常对象名){
    
    
}

其中try表示尝试的意思,尝试执行try结构中的代码块,如果执行过程中抛出了异常,则交给catch语句块进行捕获操作

示例:

import java.util.Scanner;

public class DeviceTest {
    
    
    private static Scanner sc = new Scanner(System.in);
    public static void main(String[] args) {
    
    
        try{
    
    
            int number = deviced();
            System.out.println(number);
        }catch (InterruptedException e){
    
    
            e.printStackTrace();//打印异常轨迹
            System.out.println("请不要瞎搞");
        }
        System.out.println("发生异常也会执行");
    }
    public  static int deviced() throws InterruptedException,ArithmeticException{
    
    
        int number1 = getNumber();
        int number2 = getNumber();
        return number1/number2;
    }
    public static int  getNumber()throws InterruptedException{
    
    
        System.out.println("请输入一个数字");
        int number = sc.nextInt();
        return number;
    }
}

多个异常捕获处理:

    public  static int deviced(){
    
    
        try{
    
    
            int number1 = getNumber();
            int number2 = getNumber();
            return number1/number2;
        }catch (InterruptedException e){
    
    
            System.out.println("请不要瞎搞");
        }catch (ArithmeticException e){
    
    
            System.out.println("除数不能为0");
        }
        return 0;
    }

当使用多个catch子句捕获异常时,如果捕获的多个异常对象的数据类型具有继承关系,那么父类异常不能放在前面
就好比:

    int a = 100;
    if(a>100){
    
    

    }else(a>10){
    
    
        
    }

finally语句:
finally语句不能单独使用,必须与try语句或者try-catch结构配合使用,表示无论程序是否发生异常都会执行,主要用于释放资源。
但是在try语句或者catch语句中存在系统退出的代码,则finally语句将得不到执行

System.exit(0);//系统正常退出
0正常退出,非0异常退出
System.exit(-1);//系统异常退出

语法:

try{
    
    
} finally{
    
    
}

//或者

try{
    
    
} catch(异常类型 异常对象名){
    
    
}finally{
    
    
}

示例:

package org.device.Device;

public class DeviceTest {
    
    
    private static int[] numbers = {
    
    1,2,3,4,5};
    public static void main(String[] args) {
    
    
        try{
    
    
            int number = getNumber(5);
            System.out.println(number);
        }catch (ArrayIndexOutOfBoundsException e){
    
    
            System.out.println("数组下标越界了");
            System.exit(0);
        }finally {
    
    
            System.out.println("需要执行的代码");
        }
    }
    public static int getNumber(int index){
    
    
        return numbers[index];
    }
}

思考题:

public class DeviceTest {
    
    
    public static void main(String[] args) {
    
    
        int result = getNumber();
        System.out.println(result);
    }
    public static int getNumber(){
    
    
        int number = 10;
        try{
    
    
            return number;
        }catch (Exception e){
    
    
            return 1;
        }finally {
    
    
            number++;
        }
    }
}

结果为:

10

try为尝试执行
return—>尝试返回一个结果,但发现后面还有finally模块,而finally模块一定会得到执行,于是这里只能将返回值使用一个临时变量存储起来,然后再执行finally模块,finally模块执行完之后再将这个临时变量返回

自定义异常

为什么要使用自定义异常呢?
在Java中,异常的类型非常多,想要使用这些异常,首先必须要熟悉它们,这无疑是一个巨大的工作量,很耗费时间。
如果我们可以自定义异常,则只需要熟悉RuntimeException,Exception和Throwable即可。
这大大缩小了范围,自定义异常还可以帮助我们快速的定位问题。

自定义运行时异常语法:

public class 类名 extends RuntimeException{
    
    }

自定义检查异常语法:

public class 类名 extends Exception{
    
    }

示例:
账号登录

package org.device.Device;

import javax.management.BadAttributeValueExpException;
import java.util.Scanner;

public class DeviceTest {
    
    
    public static Scanner sc = new Scanner(System.in);
    public static void main(String[] args){
    
    
        System.out.println("请输入账号");
        String  username = sc.next();
        System.out.println("请输入密码");
        String  password = sc.next();
        try {
    
    
            login(username,password);
        } catch (BadAttributeValueExpException e) {
    
    
            throw new RuntimeException(e);
        } catch (UsernameNotFound e) {
    
    
            throw new RuntimeException(e);
        } catch (BadCredentialsException e) {
    
    
            throw new RuntimeException(e);
        }
    }
    public static void login(String username,String password) throws BadAttributeValueExpException, UsernameNotFound, BadCredentialsException {
    
    
        if("admin".equals(username)){
    
    
            if("12345".equals(password)){
    
    
                System.out.println("登录成功");
            }else{
    
    
                throw new BadCredentialsException("账号或者密码错误");
            }
        }else{
    
    
            throw new UsernameNotFound("账号不存在");
        }
    }
}
package org.device.Device;

public  class UsernameNotFound extends Exception {
    
    
    public UsernameNotFound(){
    
    }
    public UsernameNotFound(String msg){
    
    
        super(msg);
    }
}
package org.device.Device;

public class BadCredentialsException extends Exception{
    
    
    public BadCredentialsException(){
    
    }
    public BadCredentialsException(String msg){
    
    
        super(msg);
    }
}

异常使用注意事项:

  • 运行时异常可以不处理
  • 如果父类抛出了多个异常,子类覆盖父类方法时,只能抛出相同的异常或者是该异常的子集(与协变返回类型原理一样)
  • 父类方法没有抛出异常,子类覆盖父类该方法时也不可抛出检查异常,此时子类产生该异常,只能捕获处理,不能声明抛出

猜你喜欢

转载自blog.csdn.net/weixin_72138633/article/details/131807453
今日推荐