0


【Java从入门到精通】第八篇:##Java类和对象##一万字带你全面理解类和对象#@@细节&&硬核内容

前言

此文章可能会有点乱,我用我仅有的总结能力总结的,如果有哪些地方总结的不好,请@我进行改正。内容可能会有点多,我就我所学的所写的,可能会有错误的地方,请各位大佬指点,谢谢!!

一,面向对象的初步认知

1,什么是面向对象?

Java是一门纯面向对象的语言(OOP,OOP思想即使继承、封装、多态等思想)(不是说面向对象的语言只有Java),在面向对象的世界里,一切皆为对象。面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。用面向对象的思想来涉及程序,更符合人们对事物的认知,对大型程序设计、扩展以及维护都非常友好。

注意:面向过程和面向对象并不是一门语言,而是解决问题的办法,没有好坏之分,都有专门的应用场景。

2,类定义和使用

2.1,简单认识类

类是用来对一个实体(对象)来进行描述的额,主要描述该实体(对象)具有哪些属性,哪些功能,描述完成后计算机就可以识别了。

2.2,类的定义格式

在Java中定义类时需要用到class关键字,具体语法如下:

//创建类
class ClassName{
     field;  //字段(属性)或者 成员变量
     method;  //行为 或则  成员方法
}

class为定义类的关键字,ClassName为类的名字,{}中为类的主体。

类中包含的内容称为类的成员。属性主要是用来描述类的,称之为类的成员属性或者类成员变量。方法主要说明类具有哪些功能,称为类的成员方法。

例子:

采用Java语言将该类在计算机中定义完成,经过javac编译之后形成.class文件,在JVM的基础上计算机就可以识别了。

注意事项:

(1)类名注意采用大驼峰定义

(2)成员前写法统一为public

(3)细心的小伙伴们会发现此处的方法不带static 关键字。

(4)一般一个文件当中只定义一个类

(5)main方法所在的类一般要使用public修饰(注意:Eclipse默认会在public修饰的类中找main方法)

(6)public修饰的类必须要和文件名相同

(7)不要轻易去修改public修饰的类的名称,如果修改,通过开发工具修改(如下图)

如果是直接修改类名,则会报错,如下:

想要修改类名可以通过开发工具修改,如下:

鼠标右键点击类名:

在这个框里面就可以修改:

3,类的实例化

3.1,什么是实例化?

定义了一个类,就相当于在计算机中定义了一种新的类型,与int,double类似,只不过int和double是Java语言自带的内置类型,而类是用户自定义了一个新的类型。有了这些自定义的类型之后,就可以使用这些类来定义实例(或者称为对象)。

用类类型创建对象的过程,称为类的实例化,在Java中采用new关键字,配合类名实例化对象。

语法格式:

类名   变量   = new  类名()    //实例化一个对象

例子:

我们都知道如果一个变量没有初始值,直接打印该变量编译器则会报错,那么如果对象不初始化又会怎样呢?如下图:

我们可以发现:当成员变量没有赋初始值的时候,每个成员变量都是它所对应的0值。

引用类型对应的是null(如上图)

boolean类型对应的是false

char对应的是'\u0000'--->空格

注意:

(1)new关键字用于创建一个对象的实例。

(2)使用 . 来访问对象中的属性和方法。

(3)同一个类可以创建多个实例。

3.2,类和对象的说明

(1)类只是一个模型一样的东西,用来对一个实体进行描述,限定了类有哪些成员。

(2)类是一种自定义的类型,可以用来定义变量,但是在Java中用类定义出来的变量我们称为对象。

(3)一个类可以实例化出多个对象,实例化出的对象占用实际物理空间,存储类成员变量。

(4)做个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像设计图。只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间。

4,this引用

4.1,为什么要有this引用

我们直接看代码:

以上代码 定义了一个日期类,然后main方法中创建了两个对象,并通过MyDate类中的成员方法对对象进行设置和打印,代码整体逻辑非常简单,没有任何问题。

那么我们来看下面的代码:

形参名和成员变量名相同时:

可以看出运行结果发生了变化。

那函数体中到底是给谁赋值了?成员变量给成员变量?参数给参数?参数给成员变量?成员变量给参数?估计编译器自己也搞不清了吧。所以程序运行的结果不是我们所想要看到的。

那我们该怎么办?看下面代码:

加上this之后运行正常了。

4.2什么是this引用

Java编译器给每一个“成员方法”增加了一个隐藏的引用类型参数,该引用参数指向当前对象(成员方法运行时调用该成员方法对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户都是透明的,即用户不需要来传递,编译器自动完成。

注意:this引用的是调用成员方法的对象。

4.3,this引用的特性

(1)this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型

(2)this只能在“成员方法”中使用。

(3)在“成员方法”中,this只能引用当前对象,不能再引用其他对象,具有final属性。

(4)this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收。

什么时隐式参数呢?我们看下面两张代码:

我们通过对比可以发现,编译器编译之后将“成员方法”的隐藏的this参数还原,在方法中所有“成员变量”都通过this引用来访问。

我们可以从字节码层面来解析:

打开这个Java程序的字节码文件可以看到

以上的是setDate方法的字节码。aload_0就是获取局部变量表0号槽的内容,即this引用。setDate方法的局部边来个表,可以看出该方法内部总共有4个变量:(1):this(这就是隐藏的this参数);(2):year;(3):month;(4):day

我们下来看一下main的字节码:

上面字节码中的8到15行是在调用setDate方法;18到22行是在调用printDate方法。两者可以看出aload_1是在传递this。(额,,我是小白,还不太会用编辑器,实在不会画图。。我尽力了,,如果这部分有什么不太懂的,在私我哈)

5,对象的构造及初始化

5.1,如何初始化对象

通过前面知识点的学习知道,在Java方法内部定义一个局部变量时,必须要初始化,否则会编译失败。

例子:

要使上述代码通过编译,只需要在正式使用a之前,给a设置一个初始化即可。如果是对象:

需要调用之前写的setDate方法才可以将具体的日期设置到对象中。通过上述例子,我们可以发现两个问题:

(1)每次对象创建好之后调用setDate方法 设置具体日期,比较麻烦。

(2)局部变量必须要初始化才能使用,为什么字段声明之后没有给值依然可以使用?

5.2,构造方法

5.2.1,概念

构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象的时候,由编译器自动调用,并且在整个对象的生命周期内只调用一次。

普通的初始化方法:

这种初始化叫做就地初始化,这样就把成员变量给锁死了。但是你也可以在下面的main函数中调用再改,如下:

可以看到改了之后原来的name被覆盖掉了 。(这样子就特别麻烦)

使用构造方法对他进行初始化:

这就是一个构造方法。那么这个构造方法是来干什么的呢?当我们实例化一个对象的时候,必须会有:(1)为对象分配内存;

(2)调用合适的构造方法 这两步,那么看下面代码:

此时,可能很多人会有我这样的疑问: 为什么在实例化一个对象的时候会调用这个构造方法?

原因是:实例化一个对象时,Student 后面的“()”就会帮你调用构造方法。再来看下面的代码:

当我们不写构造方法的时候,程序并没有报错!原因是: 当程序当中没有构造方法的时候,编译器会帮我们默认提供一个不带参数的构造方法,当程序中有构造方法了,编译器就不会再帮你默认生成一个不带参数的构造方法。(如下代码)

此时编译器就会报错,

此时就不会,或者:

编译器也不会报错。 也可以清晰的时候这个也是一个重载。

综上所述,构造方法的用法:对对象中的成员进行初始化,并不负责给对象开辟空间!

例子:

注意:上面的代码中成员方法可以不初始化了(忘记改了,抱歉) 。

(讲了这么多,好像有点零散,但我尽力了)

(额,,我好像在idea发现了新大陆,哈哈哈哈额),这有快捷键能快速创建一个构造方法。。如下:

综上注意:(1)名字必须与类名相同

(2)没有返回值类型,设置为void也不行

(3)创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)

(4)构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)

(5)构造方法中,可以通过this调用其他构造方法来简化代码(一定要在构造方法当中使用),如下例子:

(额,我尽力了,,)无参数构造方法---内部给各个成员赋初始值,该部分功能与2个参数的构造方法重复,所以无参数构造方法中可以通过this调用带有2个参数的构造方法。

注意,此时的this(...)必须放在第一行,否则编译不通过,如下:

也不可以同时有2个或以上的this,只能在当前构造方法里面调用一个,如下:

也不可以形成换,如下:

无参数构造方法调用有2个参数的构造方法,而2个构造方法调用无参数的构造方法,形成了构造方法的递归调用,也就是环,编译器不通过。

(6)总结一下this的用法:

a:this.date//访问属性 b:this.func()//访问方法 c:this();//调用本类中其他的构造方法

5.3 默认初始化

回到上面讲到的一个问题:为什么局部变量在使用时必须要初始化,而成员变量可以不用呢?

Date  d = new Date(2022.3.30);

上面我们看到的在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单说说(嘻嘻):

(1)检测对象对应的类是否加载,如果没有加载则加载

(2)为对象分配内存空间

(3)处理并发安全问题

比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突。

(4)初始化所分配的空间

即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如:

5.4,几个问题

(1)为什么会打印地址?

查看println函数的处理方法(看原码):

调用valueOf,再来看它的原码:

如果不等于“null”则为"obj.toString",我们再来看它的原码:

可以看出这是一个地址,所以打印的是一个地址。

所以我又能有什么坏想法呢?(我又发现了新大陆,哈哈哈哈额)如下:

那这样我们就不用自己写 show方法了,,(这样的编译器真的太可爱了!)

在这里,又可以发现,当我们有自己的toString的时候, 就会使用自己的,如果没有,就会使用编译器自带的。(这样的编译器谁能不爱呢?)

6,封装

6.1,封装概念

面向对象程序三大特性:封装、继承、多态。那为什么要封装呢?简单的说,就是套壳屏蔽细节。

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

6.2,访问限定符

Java中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:

说明:(1)protected主要是用在继承中

(2)default权限指:什么都不写时的默认权限

(3)访问权限除了可以限定类中成员的可见性,也可以控制类的可见性。

(4)public 公共的,大家都可用,也不能轻易改名字

(5)private 私有的 被这个关键字修饰的,只能在当前类中使用

例子:

假如想要给cpu赋值并且打印怎么办?(如下)

(我就不用过多的文字表达了,这个代码和上面的一样的,只是有部分被我删掉了,太长截图不了。额,,就这样吧!) 快捷键的话,和上面的差不多,只是点击“Getter and setter”,嗯,我试过了,是这样的。

注意:一般情况下成员变量设置为private,成员方法设置为public。

6.3,封装扩展包

6.3.1,包的概念

在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包。有点类似于目录,比如:为了更好的管理电脑中的歌曲,一种好的方式就是将相同属性的歌曲放在相同文件下,也可以对某个文件夹下的音乐进行更详细的分类。

Java中也引进了包,包时对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式,比如:一个包中的类不想被其他包中的类使用。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在不同的包中即可。

6.3.2,导入包中的类

Java中已经提供了很多现成的类供我们使用。例如Date类:可以使用java.util.Date导入java.util这个包中的Date类。

例子:

但这种写法比较麻烦,可以使用import语句导入包

例子:

如果需要使用java.util 中的其他类,可以使用 import java.util.*

例子:

建议显示的指定要导入的类名。否则还是容易出现冲突的情况。

例子:

在这种情况下需要使用完整的类名

例子:

可以使用import static 导入包中静态的方法和字段

注意:import 和c++的#include 差别很大的。c++必须 #include 来引入其他文件内容,但是Java不需要。import只是为了写代码的时候更方便。import 更类似于c++的namespace和using。

6.3.3,自定义包

基本规则:

(1)在文件的最上方加上一个package语句指定该代码在哪个包。

(2)包名需要尽量指定成唯一的名字,通常会用公司的域名的颠倒形式(如:com.baidu.www)

(3)包名需要和diamagnetic路径相匹配。例如创建 com.baidu.www1的包.那么会存在一个对应的路径 com/baidu/www1 来存储代码。

(4)如果一个类没有package语句,则该类被放在一个默认的包中。

操作步骤:

1,在IDEA中先建一个包:右键 src ---》新建----》包

在弹出的对话框中输入包名,例如:com.baidu.www1

如果想三者合为一个,变换形式,如下:

3,在包中创建类,右键包名---》新建----》类,然后输入类名即可。

在任何一个中都可以创建。

4,此时可以看到我们的磁盘上的目录结构已经被IDEA自动创建出来了。

5,同时我们也看到了,在创建的One.java 文件的最上方,就出现了一个package语句。

6,我们也可以在任何一个底下创建包,包中也可以创建类。

6.3.4,包的访问权限控制举例

Computer类位于com.baidu.www1包中:

TestComputer位于com.baidu.www2包中:

6.3.5,常见的包

(1)java.lang---->系统常用的基础类(String、Object),此包从JDK1.1后自动导入。

(2)java.lang.reflect----->java反射编程包。

(3)java.net---->进行网络编程开发包。

(4)java.sql------>进行数据库开发支持包。

(5)java.util----->是Java提供的工具程序包。

(6)java.io------>I/O编程开发包。

7,static成员

7.1,什么是static成员?

以经常使用的学生类来做例子,如下学生类实例化2个对象student1、student2,每个对象都有自己特有的名字、年龄、分数、班级等成员信息,这些信息就是对不同学生进行描述的,如下:

我们对他进行调试一下:

student1:

student2:

假设这两个同学是同一个班的,也就是classes是相同的。那么他们上课肯定是在同一个教室的,那既然在同一个教室,那能否给类中再加上一个成员变量,来保存同学上课是的教室呢?答案是不行的!!

之前在Student类中定义的成员变量,每个对象中都会包含一份(称之为实例变量),因为需要使用这些信息来描述具体学生。而现在要表示学生上课的教室,这个教室的属性并不需要每个学生对象都存储一份,而是需要让所有学生来共享。 在Java中,被static修饰的成员,称之为静态成员,也可以称为成员,其不属于某个具体的对象,是所有对象所共享的。

7.2,static修饰成员变量

static修饰的成员变量,称为静态成员变量(类变量);static修饰的方法,称为静态成员方法(类方法),放在方法区,只有一份。静态成员变量最大的特性:不属于某个具体对象,是所有对象所共享的。

静态成员变量的特性:

(1)不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中

(2)既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问

(3)类变量存储在方法区当中

(4)生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)

需要注意的几个点

例子1:

而此时我们可以在调试一下:

在监视窗口中可以i看到,静态成员变量并没有储存在某个具体的对象中。

例子2:

一般情况下,这里会发生空指针异常,为什么这里会直接打印null而不发生空指针异常呢?

而我们可以试一下给它赋值,如下:

这里可以输出赋值的内容。

原因是因为student1不指向任何对象,而classes而不属于任何一个对象的,classes 存在于方法区中,所以编译器支持这样子写。(但不建议这样子写)

所以,静态的成员方法和静态的成员变量都不依赖于对象,因为他们是类变量!!!!

例子3:

我们可以看到doClasses是一个普通的成员方法,classes是一个静态的成员变量,此时编译运行都正常 。所以,我们可以知道:普通的成员当中可以使用静态的成员变量!!!

那么此时我们就会问:在静态的成员方法当中是否能使用普通的成员变量呢?(如下)

此时编译器就会报错!

所以在静态成员方法当中不能访问普通的成员变量!!!原因是非静态的成员变量是依赖于对象的!

7.3,static修饰的成员方法

一般类中的数据成员都设置为private,而成员方法设置为public,那设置之后,Student类中classes属性如何在类外访问呢?

Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的,如下:

静态方法特征:

(1)不属于某个具体的对象,是类方法。

(2)可以通过对象调用,也可以通过类名、静态方法名调用,推荐用静态方法名调用。

(3)静态方法没有隐藏的this引用参数,因此不能再静态方法中访问任何非静态的成员变量,如下:

(4)静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用的时候无法传递this引用,如下:

7.4,static成员变量初始化

注意:静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性。

静态成员变量初始化分为两种:就地初始化和静态代码块初始化

1,就地初始化

就地初始化就是:在定义的时候直接给初始值

例子:

2,静态代码块初始化

那我们首先来了解一下什么是代码块?

8,代码块

8.1,代码块的概念及分类

使用{}定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可以分为以下4种:

(1)普通(本地)代码块

(2)构造块

(3)静态块

(4) 同步代码块

8.2,普通(本地)代码块

定义在方法中的代码块

例子:

8.3,构造代码块

构造代码块: 定义在类中的代码块(不加修饰符)。也叫:实例代码块,构造代码块一般用于初始化实例成员变量。

例子:

无论构造方法在实例代码块前后,实例代码块都优先于构造方法!!!

例子:

我们可以看到结果依旧没变,也就是说实例代码块优先于构造方法执行!

我们可以从字节码角度来看:

这里可以清晰的看到:实例代码块优先于构造方法!!!从字节码角度来看,是把实例代码块当中的内容拷贝到了构造方法之前。

8.4,静态代码块

使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。

例子:

运行后:

我们可以发现:静态代码块比实例代码块要更早的执行!!! (当然你也可以通过字节码文件来看)

注意事项:

(1)静态代码块不管生成多少个对象,其只会执行一次。

(2)静态成员变量是类的属性,因此的是在JVM加载类时开辟空间并初始化的。

Java代码在经过编译器编译之后,如果要运行必须先要经过类加载子系统加载到JVM中才能运行。在加载阶段:

在链接阶段第二步准备中会给静态成员变量开辟空间,并设置为默认值,在初始化阶段,会执行静态代码块中的代码。

(3)如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次合并,最终放在生成的<>方法中,该方法在类加载时调用,并且只调用一次。

例子:

相当于“自动化2班”覆盖了“自动化1班”,所以输出的是“自动化2班”。

我们也可以通过字节码来清晰的看到:

(4)实例化代码块只有在创建对象时才会执行。

9,内部类

当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部类的完整的结构又

只为外部事件提供服务,那么整个内部的完整结构最好使用内部类。在Java中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。

例子:

public class OutClass{
    class InnerClass{

   }
}
//OutClass是外部类
//InnerClass是内部类

注意:

1,定义在class类名{}花括号外部的,即使是在一个文件里,都不能称为内部类。

public class A{

}

class B{

}
//A和B是两个独立的类,彼此之间没有关系

2,内部类和外部类共用同一个Java源文件,但是经过编译后,内部类会形成单独的字节码文件。

9.1,内部类的分类

(1)成员位置定义:未被static修饰-----》实例内部类

Public class OutClass{

    public class InnerClass {

    }
}

(2)成员位置定义:被static修饰------》静态内部类

Public class OutClass {

  static class InnerClass {

  }
}

(3)方法中的内部类-----》局部内部类

Public class OutClass{

     public void method(){
       
         class InnerClass{
 
         }
     }
}

根据内部类定义的位置不同,一般可以分为以下几种形式:

(1)成员内部类(实例内部类:未被static修饰的成员内部类和静态内部类-----》被stati修饰的成员内部类)

(2)局部内部类(不谈修饰符)、匿名内部类。

9.1.1,实例内部类

也就是未被static修饰的成员内部类

(1)内部类如何实例化对象?

实例内部类的定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类

外部类名.内部类   变量  =  外部类对象的引用. new  InnerClass();

例子:

(2)内部类定义静态成员变量

如果按平时来定义,编译器会报错

例子:

那怎么才能定义静态的成员变量呢?

如果一定要在内部类当中定义静态成员变量,必须是编译的时候确定的值,也就是必须是 static final 的。

例子:

此时,date6是一个常量。

(3)实例内部类当中,如果有成员变量和外部类的成员变量重名了,怎么办?

如果重名了,内部类优先使用自己的,如下:

例子:

那如何拿到外部类的呢?

例子:

我们从上面例子可以意识到: 实例内部类当中不仅包含了自己的this,而且还包含了外部类的this。所以能拿到外部类!

(4)当使用了成员内部类(包括这里的实例内部类和下面的静态内部类)的时候,在out中就会生成一个字节码文件,如下:

命名形式:外部类类名 $ 内部类类名.class 。

(5)注意事项:

1,外部类中的任何成员都可以被实例内部类方法中直接访问。

2,实例内部类所处的位置与外部类成员位置是相同的,因此也受public、private等访问限定符的约束。

3,在内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名.this.同名成员 来访问。

4,实例内部类对象必须在先有外部类对象前提下才能创建。

5,实例内部类的非静态方法中包含了一个指向外部类对象的引用。

6,外部类中,不能直接访问内部类中的成员,如果想要访问必须先要创建内部类的对象。

9.1.2,静态内部类

被static修饰的内部成员类称为静态内部类

(1)如何实例化静态内部类?如下:

(2)在静态内部类当中,只能访问外部类的静态类的成员。

所以,如果想要访问外部类的非静态成员变量,那怎么办?-----》提供外部类对象

在静态内部类当中实例化一个对象,如下:

写的方法不唯一,也可以像这样写:

9.2,局部内部类

定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用非常少,此处简单了解下语法格式。

public class OutClass{

   public void method(){
   //局部内部类:定义在方法体内部
   //不能被public、static等访问限定符修饰
      
     class InnerClass{
   
      public void methodInnerClass(){
  
      }

     }
       
    }
      //只能在该方法体内部使用,其他位置不能使用,非常局限!!
}

注意事项:

(1)局部内部类只能在所定义的方法体内部使用!!!

(2)不能被public、static等访问限定符修饰

(3)编译器也有自己独立的字节码文件。

9.3,匿名内部类(在这里不详细讲)

例子:

也可以调用内部的:

里面也可以写方法之类的。

后记:此内容为我自己学习之后的所记的内容,如果有哪些不太好的或者错误的,请各位大神帮我指出,嘻嘻!!谢谢!!!

关注我,带你全面学习Java!!一起加油!

标签: java

本文转载自: https://blog.csdn.net/m0_62262008/article/details/123774581
版权归原作者 肖肖的猪会飞 所有, 如有侵权,请联系我们删除。

“【Java从入门到精通】第八篇:##Java类和对象##一万字带你全面理解类和对象#@@细节&amp;&amp;硬核内容”的评论:

还没有评论