面相对象(基础)
一、类与对象
1、类与对象引出
- 一个养猫的问题
现有技术解决:
- 单独的定义变量解决
- 单独用数组解决
public class object01 {
public static void main(String[] args) {
/*
* 张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。
* 请编写一个程序,当用户输入小猫的名字时就显示该猫的名字,年龄,颜色。
* 如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。
* */
// 单独变量来解决 => 不利于数据管理(当猫的数量过多时,当需要新增猫的属性时)
// 第一只猫信息
String cat1Name = "小白";
int cat1Age = 3;
String cat1Color = "白色";
//第2只猫信息
String cat2Name = "小花";
int cat2Age = 100;
String cat2color = "花色";
//数组 ===>(1)数据类型体现不出来(年龄原本是int类型,现在只能用String类型)
// (2)只能通过[下标]获取信息,造成变量名字和内容的对应关系不明确
// (3)不能体现猫的行为
String[] cat1 = {"小白", "3", "白色"};
String[] cat2 = {"小花", "100", "花色"};
}
}
java设计者引入类与对象(OOP),根本原因就是现有的技术,不能完美的解决新的需求。
2、类与对象概述
一个程序就是一个世界,有很多的事物,这个事物就是对象。
对象包含两个关键的东西,属性和行为。
比如说,一只小狗就是一个对象,小狗的年龄就是小狗的属性,吃饭就是小狗的行为。
那么类和对象是什么关系呢?
类就是自定义数据类型,对象就是一个类的具体实例
例如:狗类是一个“类”,具体的某一只小狗就是“对象”
java最大的特点就是面向对象!!!
3、面向对象快速入门
面向对象的方式解决养猫问题
/*
* 体验:面相对象解决养猫问题
* */
public class object01 {
public static void main(String[] args) {
/*
* 张老太养了两只猫猫:一只名字叫小白,今年3岁,白色。还有一只叫小花,今年100岁,花色。
* 请编写一个程序,当用户输入小猫的名字时就显示该猫的名字,年龄,颜色。
* 如果用户输入的小猫名错误,则显示 张老太没有这只猫猫。
* */
// 单独变量来解决 => 不利于数据管理(当猫的数量过多时,当需要新增猫的属性时)
// 第一只猫信息
String cat1Name = "小白";
int cat1Age = 3;
String cat1Color = "白色";
//第2只猫信息
String cat2Name = "小花";
int cat2Age = 100;
String cat2color = "花色";
//数组 ===>(1)数据类型体现不出来(年龄原本是int类型,现在只能用String类型)
// (2)只能通过[下标]获取信息,造成变量名字和内容的对应关系不明确
// (3)不能体现猫的行为
String[] cat1 = {"小白", "3", "白色"};
String[] cat2 = {"小花", "100", "花色"};
// 使用OOP面向对象解决
// 实例化一只猫[创建一只猫对象]
// new Cat() 创建一只猫
// Cat oopCat1 = new Cat(); 把创建的猫赋给 oopCat1
Cat oopCat1 = new Cat();
oopCat1.name = "小白";
oopCat1.age = 1;
oopCat1.color = "白色";
// 创建第二只猫,并赋值给oopCat2
Cat oopCat2 = new Cat();
oopCat2.name = "小黑";
oopCat2.age = 9;
oopCat2.color = "黑色";
// 怎么使用
System.out.println("第一只猫的名字" + oopCat1.name );
}
}
// 使用面向对象的方式来解决养猫问题
// 定义一个猫类 => 自定义的数据类型
class Cat{
// 属性
String name; // 名字
int age; // 年龄
String color; // 颜色
// 行为
}
通过上面的案例,我们可以看出:
- 类是抽象的,概念的,代表一类的事物,例如:人类,猫类……。即它就是数据类型。
- 对象是具体的,实际的,代表一个具体事物,即是实例。
- 类是对象的模板,对象是类的一个个体,对应一个实例。
4、对象内存布局(重要)
对象在内存中的存在形式是什么样的?(重要)

类首先会把类信息加载到方法区(主要是属性和行为),然后开始分配空间。分配空间首先在堆中根据不同的属性分配空间。最后再把地址返回给栈中的cat
5、属性的概念
- 从概念或叫法上看:成员变量 = 属性 = field
- 属性是类的一个组成部分,一般是基本数据类型,也可以是引用数据类型(对象,数组)
注意事项和细节说明
-
属性的定义语法同变量,示例: 访问修饰符 属性类型 属性名
-
属性的定义类型可以为任意类型,包含基本类型或引用类型。
-
属性如果不赋值,有默认值,规则和数组一样,
int 0 short 0 byte 0 long 0 float 0.0 double 0.0 char \u0000 boolean false String null
6、创建对象访问属性
-
如何创建对象
-
先声明再创建
Cat cat; // 在栈中cat指向一个为空值的空间 cat = new Cat(); // new 之后在堆中开辟空间,同时产生一个地址,并将地址填回 cat 指向的空间 -
直接创建
Cat cat = new Cat();
-
7、对象分配机制(重要)
// 我们看看下面一段代码
Person p1 = new Person();
p1.age = 10;
p1.name = "小明";
Person p2 = p1;
System.out.println(p2.age)
// 请问:p2.age究竟是多少?并画出内存图
第一段代码:首先在方法区中加载Person类信息,然后在堆中开辟空间,进行默认初始化,并将地址填写到栈中p1指向的空间地址
第二段代码:将10赋值给p1指向的堆中分配的空间中的age
第三段代码:在方法区的常量池中创建一条“小明”数据,并产生地址,然后将地址填写到堆中的name
第四段代码:将p1指向的地址复制了一份给到p2,即p1,p2指向同一个对象(类似于一个人(堆中分配的空间),有人叫他名字p1,有人叫他名字p2,但实际上不管叫p1还是p2,都是在叫那同一个人)

8、对象创建过程
Java内存的结构分析
- 栈:一般存放基本数据类型(局部变量)
- 堆:存放对象、数组等
- 方法区:常量池(常量,比如字符串),类加载信息
java创建对象的流程简单分析
Person p1 = new Person();
p1.age = 10;
p1.name = "小明";
- 先加载Person类信息(属性和方法信息,只会加载一次)
- 在堆中分配空间,进行默认初始化,然后把地址赋给 p1,p1就指向对象
- 进行指定初始化(即给对象属性赋值)
二、成员方法(重要)
1、成员方法快速入门
基本介绍
在某些情况下,我们需要定义成员方法(简称方法)。比如人类:除了有一些属性外(年龄、姓名……),我们人类还有一些行为比如:说话、吃饭……。这时就要用成员方法来完成。
public class method01 {
public static void main(String[] args) {
// 创建对象
Person p1 = new Person();
// 调用方法
p1.speak();
Integer sum = p1.cal01();
System.out.println("sum="+ sum);
Integer sum02 = p1.cal02(10000);
System.out.println("sum02="+ sum02);
}
}
class Person{
String name;
int age;
// 方法(成员方法)
// 添加 speak 成员方法,输出 “我是一个好人”
// public 表示方法是公开
// void 表示方法没有返回值
// speak 方法名 ()是形参列表 {}方法体,可以写我们需要的代码
public void speak(){
System.out.println("我是一个好人");
}
// 添加 cal01 成员方法,可以计算从 1+..+1000的结果
public Integer cal01(){
int sum = 0;
for (int i = 0; i <= 1000; i++) {
sum += i;
}
return sum;
}
// 添加 cal02 方法,可以接收一个数 n ,计算从1+..+n的计算结果
public Integer cal02(Integer n){
int sum = 0;
for (int i = 0; i <= n; i++) {
sum += i;
}
return sum;
}
}
2、方法调用机制
- 当程序执行到方法时,就会开辟一个独立的空间(栈空间)
- 当方法执行完毕后,或者执行到return语句时,就会返回到调用方法的地方
- 然后销毁该独立空间
- main方法(栈)继续执行后面的代码,main方法执行完毕后,整个程序退出
3、成员方法带来了哪些便利?
看一个需求:
请遍历一个数组,输出数组的各个元素值;
解决思路1:传统的方法,就是使用for循环,将数组输出
public class method02 {
public static void main(String[] args) {
// 遍历一个数组,输出数组的各个元素值
int [][] map = {{0,0,1},{1,1,1},{1,1,3}};
// 遍历 map数组
// 传统的解决方式就是for循环
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.println(map[i][j]);
}
System.out.println();
}
// ....
//
// 要求再次遍历map数组时
// 如何处理?
}
}
解决思路2:定义一个MyTools类,然后写一个成员方法。
public class method02 {
public static void main(String[] args) {
// 遍历一个数组,输出数组的各个元素值
int [][] map = {{0,0,1},{1,1,1},{1,1,3}};
// 遍历 map数组
// 传统的解决方式就是for循环
MyTools myTools = new MyTools();
myTools.printArr(map);
}
}
class MyTools{
public void printArr(int [][] map){
for (int i = 0; i < map.length; i++) {
for (int j = 0; j < map[i].length; j++) {
System.out.print(map[i][j]);
}
System.out.println();
}
}
}
使用方法带来的好处:
- 提高代码的复用性
- 可以将实现的细节封装起来,然后供其他用户来调用即可
4、方法调用的细节说明
- 同一个类中方法调用:直接调用即可。
- 跨类中方法A类调用B类:需要通过对象名调用。比如:对象名.方法名(参数);
- 特别说明:跨类的方法调用和方法的访问修饰符相关。
5、成员方法传参机制(重要)
-
基本数据类型的传参机制
结论:基本数据类型传递的是值(值拷贝),形参的任何改变不影响实参 -
应用数据类型的传参机制
结论:引用数据类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参
三、方法递归
1、什么是方法递归?
- 方法直接调用自己或者间接调用自己的形式称为方法递归(recursion)
- 递归作为一种算法在程序设计语言中广泛应用。
2、递归的形式
- 直接递归:方法自己调用自己
- 间接递归:方法调用其他方法,其它方法又回调方法自己。
3、方法递归存在的问题?
- 递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出现象。
4、递归解决问题的思路
- 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。
5、递归算法三要素大体可以总结为:
- 递归的公式
- 递归的终结点
- 递归的方向必须走向终结点
6、递归的简单例子
public class recursion01 {
// 计算1-n的和
public static void main(String[] args) {
System.out.println(sum(5));
}
private static int sum(int n){
if (n == 1){
return 1;
}else{
return n + sum(n-1);
}
}
}
四、方法重载
在java中,方法重载(Overloading)是一种让类以统一的方式处理不同类型数据或不同数量参数的技术。重载是通过改变方法的参数列表来实现的,包括参数的类型、数量或顺序,而方法的名称和返回类型则保持不变。这使得同一个类中可以存在多个同名方法,只要他们的参数列表不同即可。
1、方法重载的基本规则
- 方法名必须相同:重载的方法必须具有相同的名称。
- 参数列表必须不同:这可以通过改变参数的数量、类型或顺序来实现。
- 返回类型可以不同
- 修饰符可以不同
- 异常列表可以不同
2、方法重载示例
public class overloading01 {
public static void main(String[] args) {
Count count = new Count();
System.out.println(count.add(1,1));
System.out.println(count.add(1.1,1.1));
System.out.println(count.add(1,1,1));
}
}
class Count{
// 第一个加法方法,接受两个int类型的参数
public int add(int num1, int num2) {
return num1 + num2;
}
// 第二个加法方法,接受两个double类型的参数
public double add(double num1, double num2) {
return num1 + num2;
}
// 第三个加法方法,接受三个int类型的参数
public int add(int num1, int num2, int num3) {
return num1 + num2 + num3;
}
}
3、方法重载的优点与注意事项
优点:
- 提高代码可读性:使用不同参数列表的方法,让程序员可以更清楚的表达意图。
- 提高代码灵活性:方法重载使得类可以更灵活的处理不同类型的数据
- 减少代码量:不需要为每种数据类型一个单独的函数或类
- 提高代码的可维护性:更改参数类型或添加新类型的参数通常比修改返回类型或修改方法名称更容易理解和维护。
注意事项:
- 方法重载不应该改变方法可见性(public,private,protected)。如果改变了方法的可见性,那它就不是真正的重载了。
- 如果两个方法有相同的参数类型、数量和顺序,但返回类型不同,那么他们不是重载的;他们会引发编译错误,因为java不支持返回类型的重载(C++支持)
五、可变参数
1、可变参数概述
java中,我们可以将名称相同、功能也相同,但是形参个数不同的多个函数,封装为某个类中的一个函数。比如:要求多个数的和,这时我们并不能确定有多少个数,如果每增加一个参数就对应增加一个函数,那就太影响效率了。我们可以通过可变参数的方式来解决这个问题。
2、格式
访问修饰符 返回值类型 函数名 (数据类型... 变量名) {
// 方法体
}
// 注意:这里的“数据类型...”是固定格式
3、注意事项
- 调用可变参数的方法时,传入实参的个数可以是0~n。(n=0,1,2,3)
- 传入的实参也可以直接是一个数组。
- 可变参数的本质,其实就是一个数组,因此你也可以把它当做数组来使用。
- 当形参中既含有可变参数,也含有普通的参数,必须确保可变参数在形参列表的最后。
- 每个形参列表都只能有一个可变参数。
4、示例
public class variableParameters {
public static void main(String[] args) {
Test1 test1 = new Test1();
// 调用可变参数的方法时,传入实参的个数可以是0~n。(n = 0,1,2......)
int[] array1 = {2, 11, 211, 985, 141};
int[] array2 = {1, 2, 7};
System.out.println(test1.getSum(0,1,2,3,4,5));
System.out.println(test1.getSum(5,5,5));
// 传入的实参也可以直接是一个数组
System.out.println(test1.getSum(array1));
System.out.println(test1.getSum(array2));
}
}
class Test1{
public int getSum(int... paramters){
System.out.println("可变参数中含有"+paramters.length+"个参数");
int sum = 0;
for (int i = 0; i < paramters.length; i++) {
sum += paramters[i];
}
System.out.println("传入总和是:"+sum);
return sum;
}
}
六、作用域
在Java中,我们经常看到的 public、protected、private这些修饰符。在Java中,这些修饰符都可以用来限定作用域。
1、public
- 定义为
public的class、interface可以被其他任何类访问 - 定义为
public的field、method可以被其他类访问,前提是有访问 class 的权限
package abc;
public class Hello {
public void hi() {
}
}
上面的Hello是public,因此,可以被其他包的类访问
package abc;
public class Hello {
public void hi() {
}
}
上面的hi()方法是public,可以被其他类调用,前提是首先要能访问Hello类:
2、private
- 定义为
private的field、method无法被其他类访问
说明:
- 实际上,确切地说,
private访问权限被限定在class的内部,而且与方法声明顺序无关。推荐把private方法放到后面,因为public方法定义了类对外提供的功能,阅读代码的时候,应该先关注public方法:
package abc;
public class Hello {
public void hello() {
this.hi();
}
private void hi() {
}
}
- 由于Java支持嵌套类,如果一个类内部还定义了嵌套类,那么,嵌套类拥有访问
private的权限:
// private
public class Main {
public static void main(String[] args) {
Inner i = new Inner();
i.hi();
}
// private方法:
private static void hello() {
System.out.println("private hello!");
}
// 静态内部类:
static class Inner {
public void hi() {
Main.hello();
}
}
}
3、protected
protected作用于继承关系,定义为protected的字段和方法可以被子类访问,以及子类的子类
package abc;
public class Hello {
// protected方法:
protected void hi() {
}
}
上面的protected方法可以被继承的类访问:
package xyz;
class Main extends Hello {
void foo() {
// 可以访问protected方法:
hi();
}
}
4、package
最后,包作用域是指一个类允许访问同一个package的没有public、private修饰的class,以及没有public、protected、private修饰的字段和方法。
package abc;
// package权限的类:
class Hello {
// package权限的方法:
void hi() {
}
}
只要在同一个包,就可以访问package权限的class、field和method:
package abc;
class Main {
void foo() {
// 可以访问package权限的类:
Hello h = new Hello();
// 可以调用package权限的方法:
h.hi();
}
}
注意,包名必须完全一致,包没有父子关系,com.apache和com.apache.abc是不同的包。
5、局部变量
在方法内部定义的变量称为局部变量,局部变量作用域从变量声明处开始到对应的块结束。方法参数也是局部变量。
package abc;
public class Hello {
void hi(String name) { // 1
String s = name.toLowerCase(); // 2
int len = s.length(); // 3
if (len < 10) { // 4
int p = 10 - len; // 5
for (int i=0; i<10; i++) { // 6
System.out.println(); // 7
} // 8
} // 9
} // 10
}
我们观察上面的hi()方法代码:
- 方法参数name是局部变量,它的作用域是整个方法,即1 ~ 10;
- 变量s的作用域是定义处到方法结束,即2 ~ 10;
- 变量len的作用域是定义处到方法结束,即3 ~ 10;
- 变量p的作用域是定义处到if块结束,即5 ~ 9;
- 变量i的作用域是for循环,即6 ~ 8。
使用局部变量时,应该尽可能把局部变量的作用域缩小,尽可能延后声明局部变量。
6、final
Java还提供了一个final修饰符。final与访问权限不冲突,它有很多作用。
- 用
final修饰class可以阻止被继承:
package abc;
// 无法被继承:
public final class Hello {
private int n = 0;
protected void hi(int t) {
long i = t;
}
}
- 用
final修饰method可以阻止被子类覆写:
package abc;
public class Hello {
// 无法被覆写:
protected final void hi() {
}
}
- 用
final修饰field可以阻止被重新赋值:
package abc;
public class Hello {
private final int n = 0;
protected void hi() {
this.n = 1; // error!
}
}
- 用
final修饰局部变量可以阻止被重新赋值:
package abc;
public class Hello {
protected void hi(final int t) {
t = 1; // error!
}
}
7、最佳实践
-
如果不确定是否需要
public,就不声明为public,即尽可能少地暴露对外的字段和方法。 -
把方法定义为
package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。 -
一个
.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。
8、小结
- Java内建的访问权限包括
public、protected、private和package权限; - Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;
final修饰符不是访问权限,它可以修饰class、field和method;- 一个
.java文件只能包含一个public类,但可以包含多个非public类。
七、递归函数
作者在实际开发经验中很少用到递归,因此此处不做详细说明
1、递归原理
递归是基于函数调用栈的原理实现的。当一个方法被调用时,会在调用栈中创建一个对应的栈帧,包含方法的参数、局部变量和返回地址等信息。在递归中,方法会在自身的定义中调用自身,这会导致多个相同方法的栈帧依次入栈。当满足条件时,递归开始回溯,栈帧依次出栈,方法得以执行完毕。
2、递归的实现方法
- 终止条件:定义递归结束的条件(无适当的终止条件可能会使递归陷入无限循环,,导致栈溢出)
- 递归调用:在方法定义中调用自身。
八、构造器
1、什么是构造器
构造器通常也叫构造方法、构造函数,它在类的实例化过程中被调用,用来初始化新对象(给对象赋初始值),即我们在new一个对象时,JVM就会自动调用构造器,那么为什么在之前我们没有创建构造器时,也可以new一个对象呢?因为java系统会提供一个默认的构造器。
2、构造器的基本语法
构造方法的名字必须和所在类名字一致,没有返回值,但不能声明 void ,访问权限可以为任意,但一般情况下使用public方法权限,构造方法中的参数可以自定义,参数不同的构造方法构成重载。
public class ClassName {
// 构造器
public ClassName() {
// 初始化代码
}
}
- 构造器的名称必须和类名一致
- 一个类中可以定义多个构造器,但是构造器的参数列表必须不同(重载)
- java系统会在没有手动定义构造器时提供一个默认的构造器(默认构造器不做任何操作,只单纯创建一个对象),手动定义后,默认的构造器会被收回
- 代码游离块先执行
3、 构造器的类型
3.1、默认构造器
由java编译器自动提供的一个无参默认构造器,在手动创建构造器后,该默认构造器会被回收
public class Constructortest01 {
public static void main(String[] args) {
test01 test01 = new test01();//使用默认构造器
}
}
class test01 {
// 编译器自动提供一个默认无参的构造器
}
3.2、无参构造器
无参构造器是用户自己定义的一个不传递参数的构造器
public class Constructortest02 {
public static void main(String[] args) {
test02 test02 = new test02();// 调用自定义无参构造器,此时编译器提供的默认构造器被回收
System.out.println(test02.name + ":"+test02.sex);
}
}
class test02{
String name;
String sex;
// 定义无参构造器
public test02(){
name = "小明";
sex = "男";
System.out.println(name + ":"+ sex );
}
}
3.3、有参构造器
有参构造器是用户自己定义的一个传递参数的构造器(可以重载)
public class Constructortest03 {
public static void main(String[] args) {
//调用无参构造器
test03 test03 = new test03();
// 调用有参构造器
test03 小明 = new test03("小明");
test03 test031 = new test03("小明", "男");
test03 test032 = new test03("小明", "男", 12);
}
}
class test03{
String name;
String sex;
int age;
// 无参
public test03(){
System.out.println("无参构造器");
}
// 有参以及重载
public test03(String name){
this.name = name;
System.out.println(this.name +":"+this.sex +":"+this.age);
}
public test03(String n,String s ){
name = n;
sex = s;
System.out.println(name +":"+sex +":"+age);
}
public test03(String n , String s , int a){
name = n;
sex = s;
age = a;
System.out.println(name +":"+sex +":"+age);
}
}
4、构造器的使用
- 使用new时(类初始化时的()中填入不同参数以及类型,调用对应的构造器)
- 使用this(同一类中一个构造器调用另一个构造器,必须在第一行使用this)
- 使用super(子类构造器调用父类构造器)
4.1、使用this调用构造器
当我们需要在同一类中的一个构造器中调用另一个构造器时,就需要使用this关键字。
当使用this关键字调用同一类中的另一个构造器时,this必须是构造器中的第一条语句。
public class Constructortest04 {
public static void main(String[] args) {
Test04 test04 = new Test04("小明", "男", 12);
}
}
class Test04 {
String name;
String sex;
int age;
public Test04(String n){
name = n;
System.out.println("调用一个参数");
System.out.println(name +":"+sex +":"+age);
}
public Test04(String n , String s , int a){
this(n);
System.out.println(name +":"+sex +":"+age);
}
}
4.2、构造器的继承
首先,构造器是不能继承的,但是子类可以调用父类的构造器。子类构造器会默认调用父类的无参构造器,如果父类没有无参构造器,则必须在子类构造器的第一行通过super关键字来指定调用父类中的哪个构造器。
4.3、使用super调用父类构造器
当我们想要调用父类构造器时,就需要使用super关键字。
super关键字可以调用父类关键字,在使用时必须放在子类构造器中的第一行。
public class Constructortest05 {
}
class Test05 extends Test04{
// 子类构造器Test05,调用父类有参构造器 Test04(String n)
public Test05(String n) {
super(n);
}
}
、使用this调用构造器
当我们需要在同一类中的一个构造器中调用另一个构造器时,就需要使用this关键字。
当使用this关键字调用同一类中的另一个构造器时,this必须是构造器中的第一条语句。
public class Constructortest04 {
public static void main(String[] args) {
Test04 test04 = new Test04("小明", "男", 12);
}
}
class Test04 {
String name;
String sex;
int age;
public Test04(String n){
name = n;
System.out.println("调用一个参数");
System.out.println(name +":"+sex +":"+age);
}
public Test04(String n , String s , int a){
this(n);
System.out.println(name +":"+sex +":"+age);
}
}
4.2、构造器的继承
首先,构造器是不能继承的,但是子类可以调用父类的构造器。子类构造器会默认调用父类的无参构造器,如果父类没有无参构造器,则必须在子类构造器的第一行通过super关键字来指定调用父类中的哪个构造器。
4.3、使用super调用父类构造器
当我们想要调用父类构造器时,就需要使用super关键字。
super关键字可以调用父类关键字,在使用时必须放在子类构造器中的第一行。
public class Constructortest05 {
}
class Test05 extends Test04{
// 子类构造器Test05,调用父类有参构造器 Test04(String n)
public Test05(String n) {
super(n);
}
}
&spm=1001.2101.3001.5002&articleId=143925015&d=1&t=3&u=89677b2c74b04e7490581be56fbd6c4d)
828

被折叠的 条评论
为什么被折叠?



