Java之继承和多态

1.继承

继承是面向对象的三大特征之一。

类与类之间的三种关系:
is a :继承关系,例如:公共汽车 is a 汽车
use a:使用关系,例如:人 use a 钳子
has a:包含关系,例如:人has a 胳膊

1.1.为什么要继承

  • 继承的出现提高了代码的复用性,提高软件开发效率。
  • 继承的出现让类与类之间产生了关系,提供了多态的前提。

1.2.继承的定义格式

在程序中,如果想声明一个类继承另一个类,需要使用extends关键字。

[访问修饰符] class 子类 extends 父类 {
    子类成员变量
    子类成员方法
}

●注意:子类无法继承父类的构造方法

1.3 方法重写(方法覆盖|override)

1.3.1什么是方法重写

答:在子类中将父类的方法再重新定义一遍。即方法名,参数列表,返回值类型与父类相同

1.3.2为什么要方法重写

class Pet {//宠物
    public void sound(){
        System.out.println("宠物叫");
    }
}
class Dog extends Pet {//狗

}
class Cat extends Pet{//猫

}

public class PetShop{//宠物店
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound();
        Cat cat = new Cat();
        cat.sound();
    }
}

如果子类从父类继承的方法不能满足子类的需要,或者不适合子类的需要。

此时子类可以将从父类继承的方法重写定义成满足自己需要的方法。

重新定义称为重写。

改进本例,让猫和狗重写父类的sound()方法,实现各自的叫声。

class Pet {//宠物
    public void sound(){
        System.out.println("宠物叫");
    }
}
class Dog extends Pet {//狗

    //方法重写
    @Override
    public void sound() {
        System.out.println("汪汪汪");
    }
}
class Cat extends Pet{//猫
    //方法重写
    @Override
    public void sound(){
        System.out.println("喵喵喵");
    }
}

public class PetShop{//宠物店
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound();//调用狗的sound()
        Cat cat = new Cat();
        cat.sound();//调用猫的sound()
    }
}

1.4.使用继承

案例:公司有2个部门,人事部和研发部,各自属性如下


部门属性

●定义人事部类:

/*
 * 人事部
 */
public class PersonalDepartment {
    private int ID;// 部门编号
    private String name = "待定";// 部门名称
    private int amount = 0;// 部门人数
    private String responsibility = "待定";// 部门职责
    private String manager = "无名氏";// 部门经理
    private int count;//招聘人数

    public PersonalDepartment() {
    }

    public PersonalDepartment(int ID, String name, int amount, String responsibility, String manager, int count) {
        this.ID = ID;
        this.name = name;
        this.amount = amount;
        this.responsibility = responsibility;
        this.manager = manager;
        this.count = count;
    }
    //为了阅读方便,以下省略set//get方法
    ......

●定义研发部类

/**
 * 研发部
 */
public class ResourceDepartment {
    private int ID;// 部门编号
    private String name = "待定";// 部门名称
    private int amount = 0;// 部门人数
    private String responsibility = "待定";// 部门职责
    private String manager = "无名氏";// 部门经理
    private String speciality =null;//研发方向

    public ResourceDepartment() {
    }

    public ResourceDepartment(int ID, String name, int amount, String responsibility, String manager, String speciality) {
        this.ID = ID;
        this.name = name;
        this.amount = amount;
        this.responsibility = responsibility;
        this.manager = manager;
        this.speciality = speciality;
    }
    //为了阅读方便,以下省略set//get方法
    ......

发现问题:两个类中的属性有相同的部分,代码冗余。解决办法是将两个部门共性的属性抽取出来,放在父类中,然后让两个部门继承父类。

●定义父类:两个部门共性的属性抽取到父类中

/**
 * 部门父类:存放所有子类共性的内容
 */
public class Deparment {
    private int ID;// 部门编号
    private String name = "待定";// 部门名称
    private int amount = 0;// 部门人数
    private String responsibility = "待定";// 部门职责
    private String manager = "无名氏";// 部门经理
    
    //为了阅读方便,以下省略set//get方法
    ......

●子类修改如下:

  1. 共性的属性删除
  2. 共性属性的get和set方法删除
  3. 构造方法做调整

●让人事部和研发部继承部门的父类

  • 使用extends Deparment继承部门类
/**
 * 人事部
 */
public class PersonalDepartment extends Deparment{
    private int count;//招聘人数

    public PersonalDepartment() {
    }

    public PersonalDepartment(int count) {

        this.count = count;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}
  • 使用extends Deparment继承部门类
/**
 * 研发部
 */
public class ResourceDepartment extends Deparment{

    private String speciality =null;//研发方向

    public ResourceDepartment() {
    }

    public ResourceDepartment(String speciality) {
        this.speciality = speciality;
    }

    public String getSpeciality() {
        return speciality;
    }

    public void setSpeciality(String speciality) {
        this.speciality = speciality;
    }
}

●验证子类是否真的从父类继承了数据

验证方法:

  • 创建人事部和研发部的对象,如果能够调用出父类的方法,证明继承了

创建一个公司类,测试一下:

/**
 * 公司类
 */
public class Company {
    public static void main(String[] args) {
        //创建人事部对象
        PersonalDepartment personalDepartment = new PersonalDepartment();
        //创建研发部对象
        ResourceDepartment resourceDepartment = new ResourceDepartment();
        //调用父类的方法
        System.out.println(personalDepartment.getName()); //getName()是父类的
        System.out.println(resourceDepartment.getName()); //getName()是父类的
    }
}

继承的好处:

  • 好处1:代码复用
  • 好处2:父类可以控制子类能继承什么,通过封装控制

来一个新的需求:每个部门都自我介绍一下
具体办法:在部门 的父类中添加一个方法,toString(),实现自我介绍

    @Override
    public String toString() {
        return "Deparment{" +
                "ID=" + ID +
                ", name='" + name + '\'' +
                ", amount=" + amount +
                ", responsibility='" + responsibility + '\'' +
                ", manager='" + manager + '\'' +
                '}';
    }

由于toString()方法定义在父类中,所以人事部和研发部都继承了这个toString()方法。

在公司类中让每个部门自我介绍一下,只要调用toString()方法就可以自我介绍了。

public class Company {
    public static void main(String[] args) {
        //创建人事部对象
        PersonalDepartment personalDepartment = new PersonalDepartment();
        //创建研发部对象
        ResourceDepartment resourceDepartment = new ResourceDepartment();
        //调用父类的方法
        System.out.println(personalDepartment.toString());//自我介绍
        System.out.println(resourceDepartment);//自我介绍,如果一个对象放在了sout语句中,那么这个对象默认会调用toString()方法。
    }
}

运行结果:
Deparment{ID=0, name='待定', amount=0, responsibility='待定', manager='无名氏'}
Deparment{ID=0, name='待定', amount=0, responsibility='待定', manager='无名氏'}

来一个新的需求:为部门设置属性值

●为人事部设置属性值

public class Company extends Object{
    public static void main(String[] args) {
        //创建人事部对象
        PersonalDepartment personalDepartment = new PersonalDepartment();
        personalDepartment.setID(110);       //通过父类的set方法为属性赋值
        personalDepartment.setCount(5);      //通过父类的set方法为属性赋值
        personalDepartment.setManager("卡卡");    //通过父类的set方法为属性赋值
        personalDepartment.setResponsibility("招聘");   //通过父类的set方法为属性赋值
        personalDepartment.setName("人事部");    //通过父类的set方法为属性赋值

        //创建研发部对象
        ResourceDepartment resourceDepartment = new ResourceDepartment();
        //调用父类的方法
        System.out.println(personalDepartment.toString());
        System.out.println(resourceDepartment);
    }
}

运行结果:
Deparment{ID=110, name='人事部', amount=0, responsibility='招聘', manager='卡卡'}
Deparment{ID=0, name='待定', amount=0, responsibility='待定', manager='无名氏'}

结论:对象的属性可以通过set方法赋值

来一个新的需求:当创建部门对象时,直接初始化部门的所有信息。不要new完以后再调用set方法赋值。

解释:创建子类对象时的运行规则

  1. 第一个方面:构造函数的调用顺序问题
    1. new子类时,首先调用子类构造函数,但是不执行子类构造函数
    2. 子类构造函数被调用后立即调用父类的构造函数。
  2. 第二个方面:属性初始化顺序的问题
    1. 从Object类开始初始化
    2. 然后依次从父到子的顺序初始化(哪个类定义的属性,就由哪个类负责初始化)


      解释

子类通过关键字super()调用父类构造函数
super()必须放在子类构造函数的第一行代码

代码编写步骤:

●第一步:重构父类构造

  • 首先为父类添加构造,父类的构造为父类中定义的属性初始化
  • 子类定义的特有属性不是父类负责初始化的。
    public Deparment(int ID, String name, int amount, String responsibility, String manager) {
        super();
        this.ID = ID;
        this.name = name;
        this.amount = amount;
        this.responsibility = responsibility;
        this.manager = manager;
    }

●第二步:重构子类构造

  • 子类构造应该获得所有属性的初始值,本例中共6个属性,其中5个是父类的,1个是自己的
  • 子类构造的第一行必须是调用父类构造,
  • 如果不写super(),那么默认再子类第一行添加调用父类无参的构造super()
  • 子类再调用父类构造代码super()的下面负责给自己的属性赋值
    public PersonalDepartment(int ID, String name, int amount, String responsibility, String manager,int count) {
        super(ID,name,amount,responsibility,manager);
        this.count = count;
    }

●第三步:重构实例化子类对象

  • 再测试类中new子类时,要为子类传入属性的参数
PersonalDepartment personalDepartment = new PersonalDepartment(110,"人事部",5,"负责招聘","卡卡",10);

●第四步:测试

研发部按照人事部同样处理。

※本例的结论:

  • new子类时父类new了吗?答new了
  • new子类时构造函数的调用顺序是什么?从子到父
  • new子类时构造函数的执行顺序是什么?从父到子

1.5.继承的注意事项

类只支持单继承,不允许多继承

class A{} 
class B{}
class C extends A,B{}  // C类不可以同时继承A类和B类

多个类可以继承一个父类

class A{}
class B extends A{}
class C extends A{}   // 类B和类C都可以继承类A

允许多层继承

class A{}
class B extends A{}   // 类B继承类A,类B是类A的子类
class C extends B{}   // 类C继承类B,类C是类B的子类,同时也是类A的子类

子类和父类是一种相对概念

也就是说一个类是某个类父类的同时,也可以是另一个类的子类。例如上面的这种情况中,B类是A类的子类,同时又是C类的父类。

1.6.继承的体系 之 Object类

Object是所有类的父类

如果一个类没有显示定义父类,那么默认继承Object类

Object类中没有定义属性,但是定义了12个方法,并且这些方法都是实例方法。因此每个对象都拥有这14个方法。

序号 方法签名 说明
1 String toString() 返回当前对象本身的有关信息,返回字符串对象
2 boolean equals(Object) 比较两个对象是否是同一个对象,若是,返回true
3 Object clone() 生成当前对象的一个副本,并返回
4 int hashCode(); 返回该对象的哈希码值
5 void finalize() 在垃圾收集器将对象从内存中清除出之前做必要的清理工作。
6 void wait() 线程等待
7 void wait(int) 线程等待
8 void wait(long,int) 线程等待
9 void notify() 线程唤醒
10 void notifyall() 线程唤醒
11 Class getClass() 获取类结构信息,返回Class对象

1.7. ※继承过程分析(父子类的实例化顺序)(重要)

当new子类时,父类也new了

那先new谁呢?先new Object,然后依次从上向下new 子类。

执行:从父到子

调用:从子到父

哪个类定义的属性,就由哪个类赋值初始化,对于父类来说,初始化的数据是由子类通过super(数据)传入给父类的

1.7 继承后子类的成员的变化

了解了继承给我们带来的好处,提高了代码的复用性。继承让类与类或者说对象与对象之间产生了关系。那么,当继承出现后,类的成员之间产生了那些变化呢?

子类中的成员包括:

  • 子类自己定义的属性和方法
  • 从父类继承的属性和方法
  • 子类不能使用从父类继承的私有成员 原因:因为被封装了。

1.8. this和super

继承中的this(调用子类成员)

  • this可以调用子类成员
  • this可以调用子类重载的构造函数,必须是子类构造函数的第一行

继承中的super(调用父类成员)

  • super调用父类成员
  • super调用父类构造函数,必须是子类构造函数的第一行

this的冤家和搭档

一对搭档 this和super

this代表自己,this可以调用

  • 自己的属性和方法
  • 在本类的构造函数内调用其他构造函数

super代表父类,super可以调用

  • 父类的属性和方法
  • 在子类构造中调用父类构造

一对冤家 this和static

this表示实例

static表示静态

static不能调实例,static看this就像冤家

对象名可以调用static,但是this确不能调用static,this看static也像冤家

2. 多态

多态是面向对象的三大特征之一

2.1.什么是多态

多态就是多种形态,多种形式

多态的实现方法有两种

  • 方法重写(公认的多态)
  • 方法重载(非公认的多态)

2.2.多态的语法格式

多态的定义格式:就是父类的引用变量指向子类对象

父类类型  变量名 = new 子类类型();
变量名.方法名();
Pet pet = new Cat();
pet.toString();

2.3. 多态的实现(方法重写)

2.3.1普通类实现多态

  • 普通类多态定义的格式
父类 变量名 = new 子类();

例如:

class Fu {}
class Zi extends Fu {}
Fu f = new Zi();//类的多态使用

2.3.2.抽象类实现多态

  • 抽象类多态定义的格式
抽象类 变量名 = new 抽象类子类();

例如:

abstract class Fu {
     public abstract void method();
}
class Zi extends Fu {
    public void method(){
        System.out.println(“重写父类抽象方法”);
    }
}
Fu fu= new Zi();//类的多态使用

2.3.3 接口实现多态

  • 接口多态定义的格式
接口 变量名 = new 接口实现类();
``
例如:
```java
interface Fu {
    public abstract void method();
}
class Zi implements Fu {
    public void method(){
         System.out.println(“重写接口抽象方法”);
    }
}
Fu fu = new Zi();//接口的多态使用
  • 注意事项

同一个父类的方法会被不同的子类重写。父类引用在调用方法时,调用的为各个子类重写后的方法。

Person p1 = new Student();
Person p2 = new Teacher();
p1.work(); //p1会调用Student类中重写的work方法
p2.work(); //p2会调用Teacher类中重写的work方法

当变量名指向不同的子类对象时,由于每个子类重写父类方法的内容不同,所以会调用不同的方法。

2.4.多态的实现(方法重载)

class Student {
    public void doWork(){
        System.out.println(" is working ...");
    }
    public void doWork(Date from ,Date end){
        SimpleDateFormat sdf =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        System.out.println(" from "+ sdf.format(from) +"... end "+ sdf.format(end) +"... working....");
    }
}

public class TestInstanceof {
    public static void main(String[] args) {
        Student student = new Student();
        student.doWork();//第1种重载(第1种多态)
        student.doWork(new Date(1658972077239L),new Date(1658973187239L));//第2种重载(第2种多态)
    }
}

2.5. 多态的转型

多态的转型分为向上转型与向下转型两种:

  • 向上转型就是父类引用指向子类对象
  • 向下转型就是子类引用指向父类对象

2.5.1.向上转型

向上转型:当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向上转型的过程。

使用格式:

父类类型  变量名 = new 子类类型();

例如:

Person p = new Student();

2.5.2向下转型

向下转型:一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。

如果是直接创建父类对象,是无法向下转型的!

使用格式:

子类类型 变量名 = (子类类型) 父类类型的变量;

例如:

Student stu = (Student) p;  //变量p 实际上指向Student对象

2.5.3 多态的好处和弊端

当父类的引用指向子类对象时,就发生了向上转型,即把子类类型对象转成了父类类型。向上转型的好处是隐藏了子类类型,提高了代码的扩展性。

但向上转型也有弊端,只能使用父类共性的内容,而无法使用子类特有功能,功能有限制。看如下代码

//描述动物类,并抽取共性eat方法
abstract class Animal {
    abstract void eat();
}
 
// 描述狗类,继承动物类,重写eat方法,增加lookHome方法
class Dog extends Animal {
    void eat() {
        System.out.println("啃骨头");
    }

    void lookHome() {
        System.out.println("看家");
    }
}

// 描述猫类,继承动物类,重写eat方法,增加catchMouse方法
class Cat extends Animal {
    void eat() {
        System.out.println("吃鱼");
    }

    void catchMouse() {
        System.out.println("抓老鼠");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog(); //多态形式,创建一个狗对象
        a.eat(); // 调用对象中的方法,会执行狗类中的eat方法
        // a.lookHome();//使用Dog类特有的方法,需要向下转型,不能直接使用
        
        // 为了使用狗类的lookHome方法,需要向下转型
        // 向下转型过程中,可能会发生类型转换的错误,即ClassCastException异常
        // 那么,在转之前需要做健壮性判断 
        if( !(a instanceof Dog)){ // 判断当前对象是否是Dog类型
                System.out.println("类型不匹配,不能转换"); 
                return; 
        } 
        Dog d = (Dog) a; //向下转型
        d.lookHome();   //调用狗类的lookHome方法
    }
}

来总结一下

  • 什么时候使用向上转型

    当不需要面对子类类型时,通过提高扩展性,或者使用父类的功能就能完成相应的操作,这时就可以使用向上转型。

    例如:

    Animal a = new Dog();
    a.eat();
    
    
  • 什么时候使用向下转型

    当要使用子类特有功能时,就需要使用向下转型。

    例如:

    Dog d = (Dog) a; //向下转型
    d.lookHome();  //调用狗类的lookHome方法
    
    
  • 向下转型的好处:可以使用子类特有功能。

  • 向下转型的弊端是:需要面对具体的子类对象;在向下转型时容易发生ClassCastException类型转换异常。在转换之前必须做类型判断。例如:

    if(a instanceof Dog){…}
    
    

instanceOf运算符解决子类扩展方法的调用

//描述动物类,并抽取共性eat方法
abstract class Animal {
    abstract void eat();
}

// 描述狗类,继承动物类,重写eat方法,增加lookHome方法
class Dog extends Animal {
    void eat() {
        System.out.println("啃骨头");
    }

    void lookHome() {
        System.out.println("看家");
    }
}

// 描述猫类,继承动物类,重写eat方法,增加catchMouse方法
class Cat extends Animal {
    void eat() {
        System.out.println("吃鱼");
    }

    void catchMouse() {
        System.out.println("抓老鼠");
    }
}

public class TestInstanceof {
    public static void main(String[] args) {
        Animal a = new Dog(); //多态形式,创建一个狗对象
        a.eat(); // 调用对象中的方法,会执行狗类中的eat方法
        //a.lookHome();//使用Dog类特有的方法,需要向下转型,不能直接使用

        // 为了使用狗类的lookHome方法,需要向下转型
        // 向下转型过程中,可能会发生类型转换的错误,即ClassCastException异常
        // 那么,在转之前需要做健壮性判断
        if( !(a instanceof Dog)){ // 判断当前对象是否是Dog类型
            System.out.println("类型不匹配,不能转换");
            return;
        }
        Dog d = (Dog) a; //向下转型
        d.lookHome();   //调用狗类的lookHome方法
    }
}

Java子方法重载(overload)和方法重写(override)的区别

  1. 方法重载:
    1. 在同一个类中(包括从父类继承的) 方法 同名 不同参 与返回值无关
  2. 方法重写:
    1. 方法重写存在于继承关系中
    2. 父子类之间的方法 同名,同参,同返回
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容

  • 1、什么是继承,继承的特点? 子类继承父类的特征和行为,使得子类具有父类的各种属性和方法。或子类从父类继承方法,使...
    1挥改oJo阅读 237评论 0 0
  • 子类与父类、子类的继承性成员变量的隐藏与方法重写super与final关键字对象类型转换继承与多态抽象方法与抽象类...
    David_Rao阅读 218评论 0 0
  • Java继承和多态 继承 定义 继承就是利用现有类创建新类的过程,现有的类称为 父类(基类),新类称为 子类(派生...
    Golden30阅读 471评论 0 1
  • 1,继承 (1), 关键字 extends (2),在父子的继承的关系当中,若果重命则创建子类对象的时候,有两种方...
    最不愿意起名了阅读 178评论 0 1
  • java 继承 多态 继承与多态是面向对象的语言的两个重要的特点,深入的认识对运用好这门编程语言非常重要。 今天的...
    Peanut_Butter阅读 300评论 0 0