目录
编程基础
常量
常量:在程序运行期间,固定不变的量。
常量的分类:
- 字符串常量:凡是用双引号引起来的部分,叫做字符串常量。例如:"abc"、"Hello"、"123"
- 整数常量:直接写上的数字,没有小数点。例如:100、200、0、-250
- 浮点数常量:直接写上的数字,有小数点。例如:2.5、-3.14、0.0
- 字符常量:凡是用单引号引起来的单个字符,就做字符常量。例如:'A'、'b'、'9'、'中'、'!','@'
- 布尔常量:只有量中取值。true、false。
- 空常量:null。代表没有任何数据。
举例:
//字符串常量System.out.println(""); // 正确!字符串两个双引号中间的内容为空,像空一行的效果。//字符常量System.out.println(''); // 错误!两个单引号中间必须有且仅有一个字符,没有不行。System.out.println('AB'); // 错误!两个单引号中间必须有且仅有一个字符,有两个不行。//空常量。空常量不能直接用来打印输出。System.out.println(null); // 错误!
System.out.println里 \n: 换行
\t: tab变量
变量:程序运行期间,内容可以发生改变的量。
创建一个变量并且使用的格式:
1、数据类型 变量名称; // 创建了一个变量变量名称 = 数据值; // 赋值,将右边的数据值,赋值交给左边的变量 2、一步到位的格式:数据类型 变量名称 = 数据值; // 在创建一个变量的同时,立刻放入指定的数据值注意:右侧数值的范围不能超过左侧数据类型的取值范围
int num1; num1 = 10; System.out.println(num1); // 10 int num2 = 25; System.out.println(num2); // 25 var1 = false; System.out.println(var1); // false // 将一个变量的数据内容,赋值交给另一个变量 // 右侧的变量名称var1已经存在,里面装的是false布尔值 // 将右侧变量里面的false值,向左交给var2变量进行存储 boolean var2 = var1; System.out.println(var2); // false
使用变量的注意事项
- 如果创建多个变量,那么变量之间的名称不可以重复。
- 对于float和long类型来说,字母后缀F和L不要丢掉。
- 如果使用byte或者short类型的变量,那么右侧的数据值不能超过左侧类型的范围。
- 没有进行赋值的变量,不能直接使用;一定要赋值之后,才能使用。
- 变量使用不能超过作用域的范围。 【作用域】:从定义变量的一行开始,一直到直接所属的大括号结束为止。
- 可以通过一个语句来创建多个变量,但是一般情况不推荐这么写,不方便注释。
// 同时创建了三个全都是int类型的变量 int a, b, c; // 各自分别赋值 a = 10; b = 20; c = 30; // 同时创建三个int变量,并且同时各自赋值 int x = 100, y = 200, z = 300;
数据类型
基本数据类型:包括 整数、浮点数、字符、布尔
引用数据类型:包括 类、数组、接口
基本数据类型
1 自动类型转换
将“数据范围小的类型”自动提升为“数据范围大的类型”
数据范围从小到大,与字节数不一定相关 比较对象: int double 注意!右侧的数值大小不能超过左侧的类型范围 // byte num4 = 40;2 转换规则
byte / short / char --> int --> long --> float --> double
boolean类型不能发生数据类型转换!
byte / short / char 运算时,先提升为int再计算。
对于byte/short/char三种类型来说,如果右侧赋值的数值没有超过范围,那么javac编译器将会自动隐含地为我们补上一个(byte)(short)(char)。
- 如果没有超过左侧的范围,编译器补上强转。
- 如果右侧超过了左侧范围,那么直接编译器报错。
【举例】
int --> byte,不是自动类型转换,没有超过范围 编译器将会自动补上一个隐含的(byte) byte num1 = /*(byte)*/ 30; // 右侧没有超过左侧的范围
//byte num2 = 128; // 报错,右侧超过了左侧的范围
int --> char char zifu = /*(char)*/ 65; // A
在给变量进行赋值的时候,如果右侧的表达式当中全都是常量,没有任何变量,那么编译器javac将会直接将若干个常量表达式计算得到结果。
short result = 5 + 8; // 等号右边全都是常量,没有任何变量参与运算
编译之后,得到的.class字节码文件当中相当于【直接就是】:
short result = 13;
右侧的常量结果数值,没有超过左侧范围,所以正确。
这称为“编译器的常量优化”。但是注意:一旦表达式当中有变量参与,那么就不能进行这种优化了。
short num1 = 10; // 正确写法,右侧没有超过左侧的范围
short + short --> int + int --> int
//short result = a + b; // 错误写法!左侧需要是int类型
正确写法:右侧不用变量,而是采用常量,而且只有两个常量,没有别人。
short result = 5 + 8;
short s = 1;s+=1;s+=1 就是 s = (short)(s+1)
3 四则运算
一旦运算中有不同类型的数据,结果是大范围的类型
被除数/除数 = 商…余数 对于整数表达式来说,整数/整数=商,结果仍为整数,只看商,不看余数 取模(取余数):整数 % 整数 = 余数,只有对整数的除法来说,取模才有意义 % 判断奇偶 如:5%2=14 强制类型转换
将 数据范围大的类型 自动提升为 数据范围小的类型
数据类型 变量名 = (数据类型)(被转数据值)double(浮点)类型转成整数型,直接去掉小数点,可能造成数据损失精度
int a = (int)1.5
int强制转换成short,砍掉2个字节,可能造成数据丢失
5 字符串
String 如:String str = 'Hello';
对于字符串String(首字母大写,并不是关键字)来说,加号代表字符串连接操作。 /String + int --> String任何数据类型 + 字符串 = 字符串优先级问题 :
从左往右
String + int + int --> String + int --> String 破解之法:小括号优于一切引用数据类型
数组、类、接口
关键字
特点:
- 完全小写的字母。
- 在增强版的记事本当中(例如Notepad++)有特殊颜色。
如:public
class
static
void
static关键字
static 关键字,可以修饰变量、方法和代码块。在使用的过程中,其主要目的还是想在不创建对象的情况下,去调用方法。
静态方法/变量占用时间长,不容易被释放
static{} 后面加载数据库jdbc用的广泛
类变量
如果一个成员变量使用了static关键字,那么这个变量不再属于对象自己,而是属于所在的类。多个对象共享同一份数据。
当 static 修饰成员变量时,该变量称为类变量。该类的每个对象都共享同一个类变量的值。任何对象都可以更改该类变量的值,但也可以在不创建该类的对象的情况下对类变量进行操作。
定义格式:
static 数据类型 变量名;
举例:
static int numberID;
类方法(静态方法)
当 static 修饰成员方法时,该方法称为类方法 。静态方法在声明中有 static ,建议使用类名来调用,而不需要创建类的对象。调用方式非常简单。
定义格式:
修饰符 static 返回值类型 方法名(参数列表){ //执行语句}
举例:在Student类中定义静态方法
public static void showNum(){ System.out.println("num:" + numberOfStudent);}
静态方法调用的注意事项:
静态方法可以直接访问类变量和静态方法。
静态方法不能直接访问普通成员变量或成员方法。反之,成员方法可以直接访问类变量或静态方法。
静态方法中,不能使用this关键字。
静态方法只能访问静态成员。
调用格式:
//访问类变量类名.类变量名;//调用静态方法类名.静态方法名(参数)
静态原理
static 修饰的内容:
是随着类的加载而加载的,且只加载一次。 存储于一块固定的内存区域(静态区),所以,可以直接被类名调用。 它优先于对象存在,所以,可以被所有对象共享。静态代码块
定义在成员位置,使用static修饰的代码块{ }。
位置:类中方法外。 执行:随着类的加载而执行且只执行一次,优先于main方法和构造方法的执行。格式:
public class ClassName{ static{ //执行语句 }}
作用:给类变量进行初始化赋值
final
不可改变。可以用于修饰类、方法和变量。
- 类:被修饰的类,不能被继承。
- 方法:被修饰的方法,不能被重写。
- 变量:被修饰的变量,不能被重新赋值。
修饰类
final class 类名{}
修饰方法
修饰符 final 返回值类型 方法名(参数列表){ //方法体}
修饰变量
1、局部变量——基本类型
基本类型的局部变量,被final修饰后,只能赋值1次,不能再更改。
2、局部变量——引用类型
引用类型的局部变量,被final修饰后,只能指向一个对象,地址不得再更改,但是不影响对象内部的成员变量值的变化。
3、成员变量
对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变。
- 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
- 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
- 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
被final修饰的常量名称,一般都有书写规范,所有字母都大写。
标识符
自己定义的内容。比如类名、方法名、变量名等。
硬要求
标识符可以包含 英文字母26个(区分大小写)、数字0-9、$、_
标识符不能以数字开头。
标识符不能是关键字
软建议
类名(接口名)规范:首字母大写,后面每个单词首字母大写(大驼峰)
方法名规范:首字母小写,后面每个单词首字母大写(小驼峰)
变量名规范:全部小写
运算符
复合赋值运算符
赋值运算符分为:
基本赋值运算符:就是一个等号“=”,代表将右侧的数据交给左侧的变量。 int a = 30;复合赋值运算符:符号 | 用法 | 解释 |
---|---|---|
+= | a += 3 | 相当于 a = a + 3 |
-= | b -= 4 | 相当于 b = b - 4 |
*= | c *= 5 | 相当于 c = c * 5 |
/= | d /= 6 | 相当于 d = d / 6 |
%= | e %= 7 | 相当于 e = e % 7 |
注意事项:
- 只有变量才能使用赋值运算符,常量不能进行赋值。
- 复合赋值运算符其中隐含了一个强制类型转换。
自减运算符-- 自增运算符++
基本含义:让一个变量涨一个数字1,或者让一个变量降一个数字1
使用格式:写在变量名称之前,或者写在变量名称之后。例如:++num,也可以num++使用方式:- 单独使用:不和其他任何操作混合,自己独立成为一个步骤。
- 混合使用:和其他操作混合,例如与赋值混合,或者与打印操作混合,等。
使用区别:
- 在单独使用的时候,前++和后++没有任何区别。即:++num;和num++;是完全一样的。
- 在混合的时候,有【重大区别】 A. 如果是【前++】,那么变量【立刻马上+1】,然后拿着结果进行使用【先加后用】 B. 如果是【后++】,那么首先使用变量本来的数值,【然后再让变量+1】【先用后加】
注意事项:
只有变量才能使用自增、自减运算符。常量不可发生改变,所以不能用。比较运算符
大于: >
小于: < 大于等于: >= 小于等于: <= 相等: == 【两个等号连写才是相等,一个等号代表的是赋值】 不相等: !=注意事项:
- 比较运算符的结果一定是一个boolean值,成立就是true,不成立就是false
- 如果进行多次判断,不能连着写。 数学当中的写法,例如:1 < x < 3 程序当中【不允许】这种写法。
逻辑运算符【与 或 非】
与(并且) && 全都是true,才是true;否则就是false
或(或者) || 至少一个是true,就是true;全都是false,才是false非(取反) ! 本来是true,变成false;本来是false,变成true与“&&”,或“||”,具有短路效果:如果根据左边已经可以判断得到最终结果,那么右边的代码将不再执行,从而节省一定的性能。
注意事项:
- 逻辑运算符只能用于boolean值。
- 与、或需要左右各自有一个boolean值,但是取反只要有唯一的一个boolean值即可。
- 与、或两种运算符,如果有多个条件,可以连续写。 两个条件:条件A && 条件B 多个条件:条件A && 条件B && 条件C
TIPS:
对于1 < x < 3的情况,应该拆成两个部分,然后使用与运算符连接起来: int x = 2; 1 < x && x < 3三元运算符(推荐)
一元运算符:只需要一个数据就可以进行操作的运算符。例如:取反!、自增++、自减--
二元运算符:需要两个数据才可以进行操作的运算符。例如:加法+、赋值= 三元运算符:需要三个数据才可以进行操作的运算符。格式:
数据类型 变量名称 = 条件判断 ? 表达式A : 表达式B;流程:
首先判断条件是否成立: 如果成立为true,那么将表达式A的值赋值给左侧的变量; 如果不成立为false,那么将表达式B的值赋值给左侧的变量; 二者选其一。注意事项:
- 必须同时保证表达式A和表达式B都符合左侧数据类型的要求。
- 三元运算符的结果必须被使用。
如:int i = (1==2 ? 100 : 200);
System.out.println(i);//200
又如:System.out.println(3==4 ? 100.12 : 400); //400.0
方法
方法定义注意事项
定义一个方法的格式:
public static void 方法名称() { 方法体}
方法名称的命名规则和变量一样,使用小驼峰。
方法体:也就是大括号当中可以包含任意条语句。注意事项:
- 方法定义的先后顺序无所谓。
- 方法的定义不能产生嵌套包含关系。必须定义在一个类中,其他方法外。
- 方法定义好了之后,不会执行的。如果要想执行,一定要进行方法的【调用】。
如何调用方法,格式:
方法名称();
不能用输出语句(System.out...)调用void方法,因为方法执行后没有结果,就打印不出任何内容。
定义方法的完整格式
修饰符 返回值类型 方法名称(参数类型 参数名称, … ){ 方法体; return 返回值;}
参数类型可以是不同的
方法体里定义的变量或数组,都仅在方法里存在,出了方法就没有了修饰符:现阶段的固定写法:public static
返回值类型:也就是方法最终产生的数据结果是什么类型
方法名称:方法的名字、规则和变量一样,小驼峰
参数类型:进入方法的数据是什么类型
参数名称:进入方法的数据对应的变量名称
PS:参数如果有多个,使用逗号进行分隔
方法体:方法需要做的事,若干行代码
return:两个作用,①停止当前方法,②将后面的返回值还给调用处
返回值:也就是方法执行后最终产生的数据结果
注意:return后面的“返回值”,必须和方法名称前面的“返回值类型”保持对应。
方法的三种调用方式
单独调用:方法名称(参数);
打印调用:System.out.println(方法名称(参数));
赋值调用:数据类型 变量名称 = 方法名称(参数);
写方法时的注释
/*** //方法描述*@param //变量说明*@param //变量说明*@return //返回值说明**/
方法重载和下列因素 有关
参数个数不同
参数类型不同
参数的多类型顺序不同
核心:确保重载的方法的参数列表都是唯一的,不能有重复,确保根据参数个数可以直接找到需要调用的唯一的方法
byte a = 20; byte b = 20; getAdd(a,b); getAdd((short)20,(short)20); getAdd(20L,20L);
方法重载和下列因素 无关
与参数的名称无关
与方法的返回值类型无关
Debug
先断点:(多)选中要单独测试的行(红点)
鼠标定位在打算Debug行,再 在左侧右键Debug || shift + F9 || 上方爬虫图标
按 F8 依次向下查看结果
蓝色的行是定位,需要F8后,才会显示
定位到调用方法的方法所在 :
断点到调用方法所在行,Debug,再鼠标点击所调用方法
Run -> Step into || F7
如果是多行调用方法,只会找到第一行的方法
- control + 单击方法
打开工具栏,会显示定位到哪一个class
View -> Toolbar
流程控制语句
if else判断语句
if(关系表达式){ 语句体;}
if(关系表达式){ 语句体1;}else{ 语句体2;}
if (判断条件1){ 执行语句1;} else if (判断条件2){ 执行语句2;} ... } else if (判断条件n){ 执行语句n; } else { 执行语句n+1; }
switch选择语句
swith(表达式,即被检测量){ case 常量值1: 语句体1; break; case 常量值2: 语句体2; break;… default: 语句体n+1; break;}
注意:
- 小括号里只能是 基本数据类型:byte/short/char/int 或 引用数据类型:String字符串、enum枚举
- 匹配哪一个case就从哪一个位置向下执行,直到遇到了break或者整体结束为止。
- 如果case的后面不写break,将出现穿透现象,程序会一直向后走,不会再判断case,直到遇到break,或者整体switch结束。
- 如果所有的case都和表达式的值不匹配,就会执行default语句体部分,然后程序结束掉
for循环语句
注意:
- 初始化表达式 不要忘记定义变量 如:int x = 1
- 步进表达式可以不写
- for的初始表达式如果想初始化多个变量,应该这样写: int a = 0,b = o,c = 0;
while循环语句
do while循环语句
for 和 while 的小区别
控制条件语句所控制的那个变量,在for循环结束后,就不能再被访问到了,而while循环结束还可以继续使用,如果你想继续使用,就用while,否则推荐使用for。原因是for循环结束,该变量就从内存中消失,能够提高内存的使用效率。
在已知循环次数的时候使用推荐使用for,循环次数未知的时推荐使用while。
跳出语句break
使用场景:终止switch或者循环 打断整个循环(if,但switch或for打断1个)
在选择结构switch语句中
在循环语句中
离开使用场景的存在是没有意义的
跳出语句continue
使用场景:结束本次循环,继续下一次的循环
死循环
死循环:也就是循环中的条件永远为true,死循环的是永不结束的循环。
例如:while(true){ 循环体;}
在后期的开发中,会出现使用死循环的场景,例如:我们需要读取用户输入的输入,但是用户输入多少数据我们并 不清楚,也只能使用死循环,当用户不想输入数据了,就可以结束循环了,如何去结束一个死循环呢,就需要使用 到跳出语句了 if … break
死循环后面除了有break,不然轮不到!就报错!
小黑框里停止刷屏:control + C
循环结构的四个组成部分(面试)
- 初始化语句:在循环开始最初执行,而且只做唯一一次。
- 条件判断:如果成立,则循环继续;如果不成立,则循环退出。
- 循环体:重复要做的事情内容,若干行语句。
- 步进语句:每次循环之后都要进行的扫尾工作,每次循环结束之后都要执行一次。
数组
数组就是存储数据长度固定的容器,保证多个数据的数据类型要一致
特性:
数组是引用数据类型; 数组里面存储的数据类型都是一致的; 数组的长度在运行期间无法改变定义数组
数组存储的数据类型[] 数组名字 = new 数组存储的数据类型[长度];
注意:数组有定长特性,长度一旦指定,不可更改。
int[] arr = new int[3];
动态初始化(指定长度)
// 动态初始化可以拆分成为两个步骤int[] arr;arr = new int[3];
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...};
int[] arr = new int[]{1,2,3,4,5};
静态初始化(指定内容)
// 静态初始化的标准格式,可以拆分成为两个步骤int[] arr;arr = new int[]{1,2,3,4,5};
数据类型[] 数组名 = {元素1,元素2,元素3...};
int[] arr = {1,2,3,4,5};
省略型静态初始化(指定内容)
// 静态初始化的省略格式,不能拆分成为两个步骤。方法中的变量arr保存的是数组的地址哈希值,因此是引用数据类型
数组中元素默认值0索引
每一个存储到数组的元素,都会自动的拥有一个编号,从0开始,这个自动编号称为数组索引 (index),可以通过数组的索引访问到数组中的元素。
数组名[索引]语法 | 功能 |
---|---|
数组名.length | 数组的长度 |
数组名.length - 1 | 数组的最大索引值 |
数组名[索引]=数值 | 为数组中的元素赋值 |
变量=数组名[索引] | 获取出数组中的元素 |
JVM的内存划分
两个变量指向一个数组
//定义数组变量arr2,将arr的地址赋值给arr2int[] arr2 = arr; arr2[1] = 9; //arr[1]也会同时改变为9,因为同一个地址
数组空指针异常
arr = null 这行代码,意味着变量arr将不会在保存数组的内存地址,也就不允许再操作数组了,因此运行的时候 会抛出 NullPointerException 空指针异常。在开发中,数组的越界异常是不能出现的,一旦出现了,就必须要修 改我们编写的代码。
数组越界异常
我们不能访问数组中不存在的索引,程序运行后,将会抛出ArrayIndexOutOfBoundsException 数组越界异常。
数组遍历
public class Demo1 { public static void main(String[] args) { int[] arr = {1,2,3,4,5}; for (int i = 0; i < arr.length; i++) { //arr.fori 倒:arr.forr System.out.println(arr[i]); } }}
数组作为方法的返回值, 返回的是数组的内存地址
public static void main(String[] args){ //调用方法,接收数组的返回值 //接收到的是数组的内存地址 int[] arr = getArray(); for(int i = 0; i < arr.length; i++) { System.out.println(arr[i]); }}public static int[] getArray() { int[] arr = {1,2,3,4,5}; //返回数组的地址,返回到调用者 return arr; }
数组作为方法参数
public static void main(String[] args){ int[] arr = {1,2,3,4,5}; change();}public static void change(int[] arr) { arr[0] = 200; }
方法的参数为基本类型时,传递的是数据值.
方法的参数为引用类型时,传递的是地址值.设置方法的注释
File -> Setting -> Live Templates -> 先 + Templates Group,再 + Live Templates,在Abbreviation中輸入一個(這裏是)(在方法内輸入 ,再按 tab 就會跳出的意思)
在Template text裏輸入以下:
/** * @author: $user$ $params$ * @return $returns$ * @description: * @date: $time$ $date$ **/
在Edit variables裏定義以下:
params - Expression:
groovyScript("def result=''; def params="${_1}".replaceAll('[\\[|\\]|\\s]', '').split(',').toList(); for(i = 0; i < params.size(); i++) {result+=' * @param ' + params[i] + ((i < params.size() - 1) ? '\n\b' : '')}; return result", methodParameters())
returns:methodReturnType()
time:time()
date:date()
Apply -> OK
通过键盘录入数字
Scanner
Scanner sc = new Scanner(System.in);int j = sc.nextInt();
权限修饰符
public:公共的
protected:受保护的 default:默认的 private:私有的不同权限的访问能力
建议
成员变量使用private,隐藏细节
构造方法使用public,方便构建对象 成员方法使用public,方便调用方法内部类
将一个类A定义在另一个类B里面,里面那个类A就称为内部类,B则称为外部类
成员内部类
定义在类中方法外的类
class 外部类{ class 内部类{ }}
访问特点【内用外,随意访问;外用内,需要内部类对象。】
内部类可以直接访问外部类的成员,包括私有成员。
外部类要访问内部类的成员,必须要建立内部类的对象。 创建内部类对象格式:外部类名.内部类名 对象名 = new 外部类型().new 内部类型();
内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名
和$
符号。
调用不同位置的变量
// 如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名public class Outer { int num = 10; // 外部类的成员变量 public class Inner /*extends Object*/ { int num = 20; // 内部类的成员变量 public void methodInner() { int num = 30; // 内部类方法的局部变量 System.out.println(num); // 局部变量,就近原则 System.out.println(this.num); // 内部类的成员变量 System.out.println(Outer.this.num); // 外部类的成员变量 } }}
局部内部类
修饰符 class 外部类名称 { 修饰符 返回值类型 外部类方法名称(参数列表) { class 局部内部类名称 { // ... } }}
局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效final的】。
备注:从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略。
原因:
- new出来的对象在堆内存当中。
- 局部变量是跟着方法走的,在栈内存当中。
- 方法运行结束之后,立刻出栈,局部变量就会立刻消失。
- 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。
public class MyOuter { public void methodOuter() { int num = 10; // 所在方法的局部变量 class MyInner { public void methodInner() { System.out.println(num); } } }}
小节一下类的权限修饰符
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:- 外部类:public / (default)
- 成员内部类:public / protected / (default) / private
- 局部内部类:什么都不能写
匿名内部类【重点】
是内部类的简化写法。它的本质是一个【带具体实现的】【父类或者父接口】【匿名的】子类对象
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,
那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。匿名内部类的定义格式
接口名称 对象名 = new 接口名称() { // 覆盖重写所有抽象方法};
对格式“new 接口名称() {...}”进行解析:
- new代表创建对象的动作
- 接口名称就是匿名内部类需要实现哪个接口
- {...}这才是匿名内部类的内容
引用类型用法总结
场景 | 说明 |
---|---|
class作为成员变量 | 类作为成员变量时,对它进行赋值的操作,实际上,是赋给它该类的一个对象 |
interface作为成员变量 | 接口作为成员变量,对它进行赋值的操作,实际上是赋给它该接口的一个子类对象 |
interface作为方法参数和返回值类型 | 当接口作为方法的返回值类型时,返回的是它的子类对象。 |