目录
1.数据类型分类
一共分为两种,基本数据类型和引用数据类型
1.1 基本数据类型【今天重点】
- 整数型 byte(1个字节) short(2个字节) int(4个字节,整数型默认) long(8个字节,加后缀L)
- 浮点型 float(4个字节,加后缀F) double(8个字节,浮点型默认)
- 字符型 char(2个字节)
- 布尔型 boolean(1个字节)
1.2 引用数据类型(今后学习)
字符串、数组、类、接口、Lambda
1.3 注意事项
-
字符串不是基本类型,而是引用类型。
-
浮点型可能只是一个近似值,并非精确的值。
-
数据范围与字节数不一定相关,例如float数据范围比long更加广泛,但是float是4字节,long是8字节。
-
浮点数当中默认类型是double。如果一定要使用float类型,需要加上一个后缀F。
如果是整数,默认为int类型,如果一定要使用long类型,需要加上一个后缀L。推荐使用大写字母后缀。
2. 数据类型转换
当数据类型不一样的时候,会发生数据类型转换
转换的规则:
范围小的类型向范围大的类型提升, byte、short、char 运算时直接提升为 int
byte、short、char‐‐>int‐‐>long‐‐>float‐‐>double
2.1 自动数据类型转换(隐式)
/*
当数据类型不一样时,将会发生数据类型转换。
自动类型转换(隐式)
1. 特点:代码不需要进行特殊处理,自动完成。
2. 规则:数据范围从小到大。
强制类型转换(显式)
*/
public class Demo01DataType {
public static void main(String[] args) {
System.out.println(1024); // 这就是一个整数,默认就是int类型
System.out.println(3.14); // 这就是一个浮点数,默认就是double类型
// 左边是long类型,右边是默认的int类型,左右不一样
// 一个等号代表赋值,将右侧的int常量,交给左侧的long变量进行存储
// int --> long,符合了数据范围从小到大的要求
// 这一行代码发生了自动类型转换。
long num1 = 100;
System.out.println(num1); // 100
// 左边是double类型,右边是float类型,左右不一样
// float --> double,符合从小到大的规则
// 也发生了自动类型转换
double num2 = 2.5F;
System.out.println(num2); // 2.5
// 左边是float类型,右边是long类型,左右不一样
// long --> float,范围是float更大一些,符合从小到大的规则
// 也发生了自动类型转换
float num3 = 30L;
System.out.println(num3); // 30.0
}
}
2.2 强制数据类型转换(显式)
/*
强制类型转换
1. 特点:代码需要进行特殊的格式处理,不能自动完成。
2. 格式:范围小的类型 范围小的变量名 = (范围小的类型) 原本范围大的数据;
注意事项:
1. 强制类型转换一般不推荐使用,因为有可能发生精度损失、数据溢出。
2. byte/short/char这三种类型都可以发生数学运算,例如加法“+”.
3. byte/short/char这三种类型在运算的时候,都会被首先提升成为int类型,然后再计算。
4. boolean类型不能发生数据类型转换
*/
public class Demo02DataType {
public static void main(String[] args) {
// 左边是int类型,右边是long类型,不一样
// long --> int,不是从小到大
// 不能发生自动类型转换!
// 格式:范围小的类型 范围小的变量名 = (范围小的类型) 原本范围大的数据;
int num = (int) 100L;
System.out.println(num);
// long强制转换成为int类型
int num2 = (int) 6000000000L;
System.out.println(num2); // 1705032704
// double --> int,强制类型转换
int num3 = (int) 3.99;
System.out.println(num3); // 3,这并不是四舍五入,所有的小数位都会被舍弃掉
char zifu1 = 'A'; // 这是一个字符型变量,里面是大写字母A
System.out.println(zifu1 + 1); // 66,也就是大写字母A被当做65进行处理
// 计算机的底层会用一个数字(二进制)来代表字符A,就是65
// 一旦char类型进行了数学运算,那么字符就会按照一定的规则翻译成为一个数字
byte num4 = 40; // 注意!右侧的数值大小不能超过左侧的类型范围,右侧是int类型数据,右侧的int数据类型的大小不能超过byte类型数据的大小
byte num5 = 50;
// byte + byte --> int + int --> int
// num4和num5相加的时候,把byte类型的num4和num5转换为int类型的数据之后,再对num4和num5相加
int result1 = num4 + num5;
System.out.println(result1); // 90
short num6 = 60;
// byte + short --> int + int --> int
// int强制转换为short:注意必须保证逻辑上真实大小本来就没有超过short范围,否则会发生数据溢出
// 先把byte类型的num4和short类型的num6转换为int类型之后再相加
// 然后把相加之后的int类型的结果转换为short类型的数据,然后赋值给result2
short result2 = (short) (num4 + num6);
System.out.println(result2); // 100
}
}
2.3 字母和汉字的转换
/*
数字和字符的对照关系表(编码表):
ASCII码表:American Standard Code for Information Interchange,美国信息交换标准代码。
Unicode码表:万国码。也是数字和符号的对照关系,开头0-127部分和ASCII完全一样,但是从128开始包含有更多字符。
48 - '0'
65 - 'A'
97 - 'a'
*/
public class Demo03DataTypeChar {
public static void main(String[] args) {
// char是字符型数据,''(单引号)里面只能放置一个字符
char zifu1 = '1';
System.out.println(zifu1 + 0); // 49
char zifu2 = 'A'; // 其实底层保存的是65数字
char zifu3 = 'c';
// 左侧是int类型,右边是char类型,
// char --> int,确实是从小到大
// 发生了自动类型转换
int num = zifu3;
System.out.println(num); // 99
char zifu4 = '中'; // 正确写法
System.out.println(zifu4 + 0); // 20013
}
}
3. 运算符
3.1 四则运算符
/*
运算符:进行特定操作的符号。例如:+
表达式:用运算符连起来的式子叫做表达式。例如:20 + 5。又例如:a + b
四则运算:
加:+
减:-
乘:*
除:/
取模(取余数):%
首先计算得到表达式的结果,然后再打印输出这个结果。
复习一下小学一年级的除法公式:
被除数 / 除数 = 商 ... 余数
对于一个整数的表达式来说,除法用的是整除,整数除以整数,结果仍然是整数。只看商,不看余数。
只有对于整数的除法来说,取模运算符才有余数的意义。
注意事项:
1. 一旦运算当中有不同类型的数据,那么结果将会是数据类型范围大的那种。
*/
public class Demo04Operator {
public static void main(String[] args) {
// 两个常量之间可以进行数学运算
System.out.println(20 + 30);
// 两个变量之间也可以进行数学运算
int a = 20;
int b = 30;
System.out.println(a - b); // -10
// 变量和常量之间可以混合使用
System.out.println(a * 10); // 200
int x = 10;
int y = 3;
int result1 = x / y;
System.out.println(result1); // 3
int result2 = x % y;
System.out.println(result2); // 余数,模,1
// int + double --> double + double --> double
// 一旦运算当中有不同类型的数据,那么结果将会是数据类型范围大的那种
// 浮点数默认的是 double 类型的数据
double result3 = x + 2.5;
System.out.println(result3); // 12.5
}
}
3.2 四则运算中的 ‘+’(加号)
/*
四则运算当中的加号“+”有常见的三种用法:
1. 对于数值来说,那就是加法。
2. 对于字符char类型来说,在计算之前,char会被提升成为int,然后再计算。
char类型字符,和int类型数字,之间的对照关系表:ASCII、Unicode
3. 对于字符串String(首字母大写,并不是关键字)来说,加号代表字符串连接操作。
任何数据类型和字符串进行连接的时候,结果都会变成字符串
*/
public class Demo05Plus {
public static void main(String[] args) {
// 字符串类型的变量基本使用
// 数据类型 变量名称 = 数据值;
String str1 = "Hello";
System.out.println(str1); // Hello
System.out.println("Hello" + "World"); // HelloWorld
String str2 = "Java";
// String + int --> String
// 任何数据类型和字符串进行连接的时候,结果都会变成字符串
System.out.println(str2 + 20); // Java20
// 优先级问题
// String + int + int
// String + int
// String
System.out.println(str2 + 20 + 30); // Java2030
System.out.println(str2 + (20 + 30)); // Java50
}
}
public static void main(String[] args){
// 因为遇到了字符串,所以直接拼接
System.out.println("5+5="+5+5);//输出5+5=55
}
3.3 自增加.减少运算符(++,–)
/*
自增运算符:++
自减运算符:--
基本含义:让一个变量涨一个数字1,或者让一个变量降一个数字1
使用格式:写在变量名称之前,或者写在变量名称之后。例如:++num,也可以num++
使用方式:
1. 单独使用:不和其他任何操作混合,自己独立成为一个步骤。
2. 混合使用:和其他操作混合,例如与赋值混合,或者与打印操作混合等。
使用区别:
1. 在单独使用的时候,前++和后++没有任何区别。也就是:++num;和num++;是完全一样的。
2. 在混合的时候,有【重大区别】
A. 如果是【前++】,那么变量【立刻马上+1】,然后拿着结果进行使用。 【先加后用】
B. 如果是【后++】,那么首先使用变量本来的数值,【然后再让变量+1】。 【先用后加】
注意事项:
只有变量才能使用自增、自减运算符。常量不可发生改变,所以不能用。
*/
public class Demo06Operator {
public static void main(String[] args) {
int num1 = 10;
System.out.println(num1); // 10
++num1; // 单独使用,前++
System.out.println(num1); // 11
num1++; // 单独使用,后++
System.out.println(num1); // 12
System.out.println("=================");
// 与打印操作混合的时候
int num2 = 20;
// 混合使用,先++,变量立刻马上变成21,然后打印结果21
System.out.println(++num2); // 21
System.out.println(num2); // 21
System.out.println("=================");
int num3 = 30;
// 混合使用,后++,首先使用变量本来的30,然后再让变量+1得到31
System.out.println(num3++); // 30
System.out.println(num3); // 31
System.out.println("=================");
int num4 = 40;
// 和赋值操作混合
int result1 = --num4; // 混合使用,前--,变量立刻马上-1变成39,然后将结果39交给result1变量
System.out.println(result1); // 39
System.out.println(num4); // 39
System.out.println("=================");
int num5 = 50;
// 混合使用,后--,首先把本来的数字50交给result2,然后我自己再-1变成49
int result2 = num5--;
System.out.println(result2); // 50
System.out.println(num5); // 49
System.out.println("=================");
int x = 10;
int y = 20;
// 11 + 20 = 31
int result3 = ++x + y--;
System.out.println(result3); // 31
System.out.println(x); // 11
System.out.println(y); // 19
// 30++; // 错误写法!常量不可以使用++或者--
}
}
3.4 赋值运算符
/*
赋值运算符分为:
基本赋值运算符:就是一个等号“=”,代表将右侧的数据交给左侧的变量。
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. 只有变量才能使用赋值运算符,常量不能进行赋值。
2. 复合赋值运算符其中隐含了一个强制类型转换。
*/
public class Demo07Operator {
public static void main(String[] args) {
int a = 10;
// 按照公式进行翻译:a = a + 5
// a = 10 + 5;
// a = 15;
// a本来是10,现在重新赋值得到15
a += 5;
System.out.println(a); // 15
int x = 10;
// x = x % 3;
// x = 10 % 3;
// x = 1;
// x本来是10,现在重新赋值得到1
x %= 3;
System.out.println(x); // 1
// 50 = 30; // 常量不能进行赋值,不能写在赋值运算符的左边。错误写法!
byte num = 30;
// 隐含的强制类型转换
// num = num + 5;
// num = byte + int
// num = int + int
// num = int
// num = (byte) int 把int类型的数据转换为byte类型的数据
num += 5;
System.out.println(num); // 35
}
}
3.5 比较运算符
/*
比较运算符:
大于: >
小于: <
大于等于: >=
小于等于: <=
相等: == 【两个等号连写才是相等,一个等号代表的是赋值】
不相等: !=
注意事项:
1. 比较运算符的结果一定是一个boolean值,成立就是true,不成立就是false
2. 如果进行多次判断,不能连着写。
数学当中的写法,例如:1 < x < 3
程序当中【不允许】这种写法。
*/
public class Demo08Operator {
public static void main(String[] args) {
System.out.println(10 > 5); // true
int num1 = 10;
int num2 = 12;
System.out.println(num1 < num2); // true
System.out.println(num2 >= 100); // false
System.out.println(num2 <= 100); // true
System.out.println(num2 <= 12); // true
System.out.println("===============");
System.out.println(10 == 10); // true
System.out.println(20 != 25); // true
System.out.println(20 != 20); // false
int x = 2;
// System.out.println(1 < x < 3); // 错误写法!编译报错!不能连着写。
}
}
3.6 与.或.非
/*
与(并且) && 全都是true,才是true;否则就是false
或(或者) || 至少一个是true,就是true;全都是false,才是false
非(取反) ! 本来是true,变成false;本来是false,变成true
与“&&”,或“||”,具有短路效果:如果根据左边已经可以判断得到最终结果,那么右边的代码将不再执行,从而节省一定的性能。
注意事项:
1. 逻辑运算符只能用于boolean值。
2. 与、或需要左右各自有一个boolean值,但是取反只要有唯一的一个boolean值即可。
3. 与、或两种运算符,如果有多个条件,可以连续写。
两个条件:条件A && 条件B
多个条件:条件A && 条件B && 条件C
TIPS:
对于1 < x < 3的情况,应该拆成两个部分,然后使用与运算符连接起来:
int x = 2;
1 < x && x < 3
*/
public class Demo09Logic {
public static void main(String[] args) {
System.out.println(true && false); // false
// true && true --> true
System.out.println(3 < 4 && 10 > 5); // true
System.out.println("============");
System.out.println(true || false); // true
System.out.println(true || true); // true
System.out.println(false || false); // false
System.out.println("============");
System.out.println(true); // true
System.out.println(!true); // false
System.out.println("============");
int a = 10;
// false && ...
System.out.println(3 > 4 && ++a < 100); // false
System.out.println(a); // 10
System.out.println("============");
int b = 20;
// true || ...
System.out.println(3 < 4 || ++b < 100); // true
System.out.println(b); // 20
}
}
3.7 一二三元运算符
/*
一元运算符:只需要一个数据就可以进行操作的运算符。例如:取反!、自增++、自减--
二元运算符:需要两个数据才可以进行操作的运算符。例如:加法+、赋值=
三元运算符:需要三个数据才可以进行操作的运算符。
格式:
数据类型 变量名称 = 条件判断 ? 表达式A : 表达式B;
流程:
首先判断条件是否成立:
如果成立为true,那么将表达式A的值赋值给左侧的变量;
如果不成立为false,那么将表达式B的值赋值给左侧的变量;
二者选其一。
注意事项:
1. 必须同时保证表达式A和表达式B都符合左侧数据类型的要求。
2. 三元运算符的结果必须被使用。
*/
public class Demo10Operator {
public static void main(String[] args) {
int a = 10;
int b = 20;
// 数据类型 变量名称 = 条件判断 ? 表达式A : 表达式B;
// 判断a > b是否成立,如果成立将a的值赋值给max;如果不成立将b的值赋值给max。二者选其一
int max = a > b ? a : b; // 最大值的变量
System.out.println("最大值:" + max); // 最大值:20
// int result = 3 > 4 ? 2.5 : 10; // 错误写法,因为不能把浮点数类型数据传输给整数类型数据!
System.out.println(a > b ? a : b); // 正确写法!
// a > b ? a : b; // 错误写法!
}
}
4. 方法
带static的方法就是一个普通的方法,不需要对象,就可以直接调用
可以把带static的方法理解为python中的静态方法,静态方法本身其实跟类本身没有什么关系了
public class Demo11Method {
public static void seller() {
System.out.println("运输到农贸市场");
System.out.println("抬高价格");
System.out.println("吆喝");
System.out.println("卖给厨子");
}
}
4.1 方法的基本定义
/*
定义一个方法的格式:
public static void 方法名称() {
方法体
}
方法名称的命名规则和变量一样,使用小驼峰。
方法体:也就是大括号当中可以包含任意条语句。
注意事项:
1. 方法定义的先后顺序无所谓。
2. 方法的定义不能产生嵌套包含关系。
3. 方法定义好了之后,不会执行的。如果要想执行,一定要进行方法的【调用】。
如何调用方法,格式:
方法名称();
*/
public class Demo11Method {
// 调用的函数可以放在构建的函数的上面或者下面,此处放在上面,放在下面也可以
public static void main(String[] args) {
farmer(); // 调用农民的方法
seller(); // 调用小商贩的方法
cook(); // 调用厨子的方法
me(); // 调用我自己的方法
}
// 厨子
public static void cook() {
System.out.println("洗菜");
System.out.println("切菜");
System.out.println("炒菜");
System.out.println("装盘");
}
// 我
public static void me() {
System.out.println("吃");
}
// 小商贩
public static void seller() {
System.out.println("运输到农贸市场");
System.out.println("抬高价格");
System.out.println("吆喝");
System.out.println("卖给厨子");
}
// 农民伯伯
public static void farmer() {
System.out.println("播种");
System.out.println("浇水");
System.out.println("施肥");
System.out.println("除虫");
System.out.println("收割");
System.out.println("卖给小商贩");
}
}
package cn.itcast.day04.demo02;
/*
方法其实就是若干语句的功能集合。
方法好比是一个工厂。
蒙牛工厂 原料:奶牛、饲料、水
产出物:奶制品
钢铁工厂 原料:铁矿石、煤炭
产出物:钢铁建材
参数(原料):就是进入方法的数据。
返回值(产出物):就是从方法中出来的数据。
定义方法的完整格式:
修饰符 返回值类型 方法名称(参数类型 参数名称, ...) {
方法体
return 返回值;
}
修饰符:现阶段的固定写法,public static
返回值类型:也就是方法最终产生的数据结果是什么类型
方法名称:方法的名字,规则和变量一样,小驼峰
参数类型:进入方法的数据是什么类型
参数名称:进入方法的数据对应的变量名称
PS:参数如果有多个,使用逗号进行分隔
方法体:方法需要做的事情,若干行代码
return:两个作用,第一停止当前方法,第二将后面的返回值还给调用处
返回值:也就是方法执行后最终产生的数据结果
注意:return后面的“返回值”,必须和方法名称前面的“返回值类型”,保持对应。
定义一个两个int数字相加的方法。三要素:
返回值类型:int
方法名称:sum
参数列表:int a, int b
方法的三种调用格式。
1. 单独调用:方法名称(参数);
2. 打印调用:System.out.println(方法名称(参数));
3. 赋值调用:数据类型 变量名称 = 方法名称(参数);
注意:此前学习的方法,返回值类型固定写为void,这种方法只能够单独调用,不能进行打印调用或者赋值调用。
*/
public class Demo02MethodDefine {
public static void main(String[] args) {
// 单独调用
sum(10, 20);
System.out.println("===========");
// 打印调用
System.out.println(sum(10, 20)); // 30
System.out.println("===========");
// 赋值调用
int number = sum(15, 25);
number += 100;
System.out.println("变量的值:" + number); // 140
}
public static int sum(int a, int b) {
System.out.println("方法执行啦!");
int result = a + b;
return result;
}
}
4.2 方法的参数
package cn.itcast.day04.demo02;
/*
有参数:小括号当中有内容,当一个方法需要一些数据条件,才能完成任务的时候,就是有参数。
例如两个数字相加,必须知道两个数字是各自多少,才能相加。
无参数:小括号当中留空。一个方法不需要任何数据条件,自己就能独立完成任务,就是无参数。
例如定义一个方法,打印固定10次HelloWorld。
*/
public class Demo03MethodParam {
public static void main(String[] args) {
method1(10, 20);
System.out.println("==============");
method2();
}
// 两个数字相乘,做乘法,必须知道两个数字各自是多少,否则无法进行计算
// 有参数
public static void method1(int a, int b) {
int result = a * b;
System.out.println("结果是:" + result);
}
// 例如打印输出固定10次文本字符串
public static void method2() {
for (int i = 0; i < 10; i++) {
System.out.println("Hello, World!" + i);
}
}
}
4.3 方法的重载
package cn.itcast.day04.demo04;
/*
对于功能类似的方法来说,因为参数列表不一样,却需要记住那么多不同的方法名称,太麻烦。
方法的重载(Overload):多个方法的名称一样,但是参数列表不一样。
好处:只需要记住唯一一个方法名称,就可以实现类似的多个功能。
方法重载与下列因素相关:
1. 参数个数不同
2. 参数类型不同
3. 参数的多类型顺序不同
方法重载与下列因素无关:
1. 与参数的名称无关
2. 与方法的返回值类型无关
*/
public class Demo01MethodOverload {
public static void main(String[] args) {
/*System.out.println(sumTwo(10, 20)); // 30
System.out.println(sumThree(10, 20, 30)); // 60
System.out.println(sumFour(10, 20, 30, 40)); // 100*/
System.out.println(sum(10, 20)); // 两个参数的方法
System.out.println(sum(10, 20, 30)); // 三个参数的方法
System.out.println(sum(10, 20, 30, 40)); // 四个参数的方法
// System.out.println(sum(10, 20, 30, 40, 50)); // 找不到任何方法来匹配,所以错误!
sum(10, 20);
}
public static int sum(int a, double b) {
return (int) (a + b);
}
public static int sum(double a, int b) {
return (int) (a + b);
}
public static int sum(int a, int b) {
System.out.println("有2个参数的方法执行!");
return a + b;
}
// 错误写法!与方法的返回值类型无关
// public static double sum(int a, int b) {
// return a + b + 0.0;
// }
// 错误写法!与参数的名称无关
// public static int sum(int x, int y) {
// return x + y;
// }
public static int sum(double a, double b) {
return (int) (a + b);
}
public static int sum(int a, int b, int c) {
System.out.println("有3个参数的方法执行!");
return a + b + c;
}
public static int sum(int a, int b, int c, int d) {
System.out.println("有4个参数的方法执行!");
return a + b + c + d;
}
}
5. 常量和变量的运算补充
/*
对于byte/short/char三种类型来说,如果右侧赋值的数值没有超过范围,
那么javac编译器将会自动隐含地为我们补上一个(byte)(short)(char)。
1. 如果没有超过左侧的范围,编译器补上强转。
2. 如果右侧超过了左侧范围,那么直接编译器报错。
*/
public class Demo12Notice {
public static void main(String[] args) {
// 右侧确实是一个int数字,但是没有超过左侧的范围,就是正确的。
// int --> byte,不是自动类型转换
byte num1 = /*(byte)*/ 30; // 右侧没有超过左侧的范围
System.out.println(num1); // 30
// byte num2 = 128; // 右侧超过了左侧的范围
// int --> char,没有超过范围
// 编译器将会自动补上一个隐含的(char)
char zifu = /*(char)*/ 65;
// 65是int类型,一旦被转换成字符类型之后,就变成65这个数字对应着A这个字母的情况,
// 所以打印出来的数据是 A.
System.out.println(zifu); // A
}
}
/*
在给变量进行赋值的时候,如果右侧的表达式当中全都是常量,没有任何变量,
那么编译器javac将会直接将若干个常量表达式计算得到结果。
short result = 5 + 8; // 等号右边全都是常量,没有任何变量参与运算
编译之后,得到的.class字节码文件当中相当于【直接就是】:
short result = 13;
右侧的常量结果数值,没有超过左侧范围,所以正确。
这称为“编译器的常量优化”。
但是注意:一旦表达式当中有变量参与,那么就不能进行这种优化了。
*/
public class Demo13Notice {
public static void main(String[] args) {
short num1 = 10; // 正确写法,右侧没有超过左侧的范围,
short a = 5;
short b = 8;
// short + short --> int + int --> int
// short result = a + b; // 错误写法!左侧需要是int类型
// 右侧不用变量,而是采用常量,而且只有两个常量,没有别人
short result = 5 + 8;
System.out.println(result);
// 这种情况下会报错,只要加上(short) 强制转换就没有问题
// 因为在计算的时候,会变成int类型进行计算,int类型>short类型,因此需要强制转换
short result2 = 5 + a + 8; // 18
}
}
+=符号的扩展
public static void main(String[] args){
short s = 1;
s+=1;
System.out.println(s);
}
分析: s += 1 逻辑上看作是 s = s + 1 计算结果被提升为int类型,再向short类型赋值时发生错误,因为不能将取值范围 大的类型赋值到取值范围小的类型。但是, s=s+1进行两次运算 , += 是一个运算符,只运算一次,并带有强制转换的特点, 也就是说 s += 1 就是 s = (short)(s + 1) ,因此程序没有问题编译通过,运行结果是2.
6. 判断和选择语句
6.1 判断语句
// 标准的if-else语句
public class Demo03IfElse {
public static void main(String[] args) {
int num = 666;
if (num % 2 == 0) { // 如果除以2能够余数为0,说明是偶数
System.out.println("偶数");
} else {
System.out.println("奇数");
}
}
}
// x和y的关系满足如下:
// 如果x >= 3,那么y = 2x + 1;
// 如果-1 < x < 3,那么y = 2x;
// 如果x <= -1,那么y = 2x – 1;
public class Demo04IfElseExt {
public static void main(String[] args) {
int x = -10;
int y;
if (x >= 3) {
y = 2 * x + 1;
} else if (-1 < x && x < 3) {
y = 2 * x;
} else {
y = 2 * x - 1;
}
System.out.println("结果是:" + y);
}
}
// 题目:使用三元运算符和标准的if-else语句分别实现:取两个数字当中的最大值
public class Demo06Max {
public static void main(String[] args) {
int a = 105;
int b = 20;
// 首先使用三元运算符
// int max = a > b ? a : b;
// 使用今天的if语句
int max;
if (a > b) {
max = a;
} else {
max = b;
}
System.out.println("最大值:" + max);
}
}
6.2 选择语句 switch
/*
switch语句使用的注意事项:
1. 多个case后面的数值不可以重复。
2. switch后面小括号当中只能是下列数据类型:
基本数据类型:byte/short/char/int
引用数据类型:String字符串、enum枚举
3. switch语句格式可以很灵活:前后顺序可以颠倒,而且break语句还可以省略。
“匹配哪一个case就从哪一个位置向下执行,直到遇到了break或者整体结束为止。”
*/
public class Demo08SwitchNotice {
public static void main(String[] args) {
int num = 2;
switch (num) {
case 1:
// 该处的 你好 不会被打印,因为 case处 根本就没有被匹配到
System.out.println("你好");
// break;
case 2:
System.out.println("我好");
// break; 由于此处没有break,因此程序会继续向下执行,直到执行到有break处为止
case 3:
System.out.println("大家好");
break;
default:
System.out.println("他好,我也好。");
break;
} // switch
}
}
7.循环语句
7.1 for 循环
/*
循环结构的基本组成部分,一般可以分成四部分:
1. 初始化语句:在循环开始最初执行,而且只做唯一一次。
2. 条件判断:如果成立,则循环继续;如果不成立,则循环退出。
3. 循环体:重复要做的事情内容,若干行语句。
4. 步进语句:每次循环之后都要进行的扫尾工作,每次循环结束之后都要执行一次。
*/
public class Demo09For {
public static void main(String[] args) {
for (int i = 1; i <= 100; i++) {
System.out.println("我错啦!原谅我吧!" + i);
}
System.out.println("程序停止");
}
}
7.2 while 循环
/*
while循环有一个标准格式,还有一个扩展格式。
标准格式:
while (条件判断) {
循环体
}
扩展格式:
初始化语句;
while (条件判断) {
循环体;
步进语句;
}
*/
public class Demo10While {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
System.out.println("我错啦!" + i);
}
System.out.println("=================");
int i = 1; // 1. 初始化语句
while (i <= 10) { // 2. 条件判断
System.out.println("我错啦!" + i); // 3. 循环体
i++; // 4. 步进语句
}
}
}
/*
do-while循环的标准格式:
do {
循环体
} while (条件判断);
扩展格式:
初始化语句
do {
循环体
步进语句
} while (条件判断);
*/
public class Demo11DoWhile {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
System.out.println("原谅你啦!起来吧!地上怪凉!" + i);
}
System.out.println("===============");
int i = 1; // 1. 初始化语句
do {
System.out.println("原谅你啦!起来吧!地上怪凉!" + i); // 3. 循环体
i++; // 4. 步进语句
} while (i <= 10); // 2. 条件判断
}
}
7.3 三种循环的区别
/*
三种循环的区别。
1. 如果条件判断从来没有满足过,那么for循环和while循环将会执行0次,但是do-while循环会执行至少一次。
2. for循环的变量在小括号当中定义,只有循环内部才可以使用。while循环和do-while循环初始化语句本来就在外面,所以出来循环之后还可以继续使用。
*/
public class Demo13LoopDifference {
public static void main(String[] args) {
for (int i = 1; i < 0; i++) {
System.out.println("Hello");
}
// System.out.println(i); // 这一行是错误写法!因为变量i定义在for循环小括号内,只有for循环自己才能用。
System.out.println("================");
int i = 1;
do {
System.out.println("World");
i++;
} while (i < 0);
// 现在已经超出了do-while循环的范围,我们仍然可以使用变量i
System.out.println(i); // 2
}
}
7.4 break
/*
break关键字的用法有常见的两种:
1. 可以用在switch语句当中,一旦执行,整个switch语句立刻结束。
2. 还可以用在循环语句当中,一旦执行,整个循环语句立刻结束。打断循环。
关于循环的选择,有一个小建议:
凡是次数确定的场景多用for循环;否则多用while循环。
*/
public class Demo14Break {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
// 如果希望从第4次开始,后续全都不要了,就要打断循环
if (i == 4) { // 如果当前是第4次
break; // 那么就打断整个循环
}
System.out.println("Hello" + i);
}
}
}
7.5 continue
/*
另一种循环控制语句是continue关键字。
一旦执行,立刻跳过当前次循环剩余内容,马上开始下一次循环。
*/
public class Demo15Continue {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
if (i == 4) { // 如果当前是第4层
continue; // 那么跳过当前次循环,马上开始下一次(第5层)
}
System.out.println(i + "层到了。");
}
}
}
7.6 循环的嵌套
// 打印矩形
public class Demo18LoopTest {
public static void main(String[] args) {
for (int i = 0;i <8 ;i++ ) {
System.out.print("*");
for (int j = 0;j < 15 ;j++ ) {
System.out.print("*");
}
System.out.println("*");
}
}
}
8. 数组
8.1 数组的基本概念
package cn.itcast.day05.demo01;
/*
数组的概念:是一种容器,可以同时存放多个数据值。
数组的特点:
1. 数组是一种引用数据类型
2. 数组当中的多个数据,类型必须统一
3. 数组的长度在程序运行期间不可改变
数组的初始化:在内存当中创建一个数组,并且向其中赋予一些默认值。
两种常见的初始化方式:
1. 动态初始化(指定长度)
2. 静态初始化(指定内容)
动态初始化数组的格式:
数据类型[] 数组名称 = new 数据类型[数组长度];
解析含义:
左侧数据类型:也就是数组当中保存的数据,全都是统一的什么类型
左侧的中括号:代表我是一个数组
左侧数组名称:给数组取一个名字
右侧的new:代表创建数组的动作
右侧数据类型:必须和左边的数据类型保持一致
右侧中括号的长度:也就是数组当中,到底可以保存多少个数据,是一个int数字
*/
public class Demo01Array {
public static void main(String[] args) {
// 创建一个数组,里面可以存放300个int数据
// 格式:数据类型[] 数组名称 = new 数据类型[数组长度];
int[] arrayA = new int[300];
// 创建一个数组,能存放10个double类型的数据
double[] arrayB = new double[10];
// 创建一个数组,能存放5个字符串
String[] arrayC = new String[5];
}
}
8.2 动态和静态的数组初始化
package cn.itcast.day05.demo01;
/*
动态初始化(指定长度):在创建数组的时候,直接指定数组当中的数据元素个数。
静态初始化(指定内容):在创建数组的时候,不直接指定数据个数多少,而是直接将具体的数据内容进行指定。
静态初始化基本格式:
数据类型[] 数组名称 = new 数据类型[] { 元素1, 元素2, ... };
注意事项:
虽然静态初始化没有直接告诉长度,但是根据大括号里面的元素具体内容,也可以自动推算出来长度。
*/
public class Demo02Array {
public static void main(String[] args) {
// 直接创建一个数组,里面装的全都是int数字,具体为:5、15、25
int[] arrayA = new int[] { 5, 15, 25, 40 };
// 创建一个数组,用来装字符串:"Hello"、"World"、"Java"
String[] arrayB = new String[] { "Hello", "World", "Java" };
}
}
8.3 数组的格式省略
package cn.itcast.day05.demo01;
/*
使用静态初始化数组的时候,格式还可以省略一下。
标准格式:
数据类型[] 数组名称 = new 数据类型[] { 元素1, 元素2, ... };
省略格式:
数据类型[] 数组名称 = { 元素1, 元素2, ... };
注意事项:
1. 静态初始化没有直接指定长度,但是仍然会自动推算得到长度。
2. 静态初始化标准格式可以拆分成为两个步骤。
3. 动态初始化也可以拆分成为两个步骤。
4. 静态初始化一旦使用省略格式,就不能拆分成为两个步骤了。
使用建议:
如果不确定数组当中的具体内容,用动态初始化;否则,已经确定了具体的内容,用静态初始化。
*/
public class Demo03Array {
public static void main(String[] args) {
// 省略格式的静态初始化
int[] arrayA = { 10, 20, 30 };
// 静态初始化的标准格式,可以拆分成为两个步骤
int[] arrayB;
arrayB = new int[] { 11, 21, 31 };
// 动态初始化也可以拆分成为两个步骤
int[] arrayC;
arrayC = new int[5];
// 静态初始化的省略格式,不能拆分成为两个步骤。
// int[] arrayD;
// arrayD = { 10, 20, 30 };
}
}
8.4 获取数组的元素
直接打印数组名称,得到的是数组对应的:内存地址哈希值
package cn.itcast.day05.demo01;
/*
直接打印数组名称,得到的是数组对应的:内存地址哈希值。
二进制:01
十进制:0123456789
16进制:0123456789abcdef
访问数组元素的格式:数组名称[索引值]
索引值:就是一个int数字,代表数组当中元素的编号。
【注意】索引值从0开始,一直到“数组的长度-1”为止。
*/
public class Demo04ArrayUse {
public static void main(String[] args) {
// 静态初始化的省略格式
int[] array = { 10, 20, 30 };
System.out.println(array); // [I@75412c2f
// 直接打印数组当中的元素
System.out.println(array[0]); // 10
System.out.println(array[1]); // 20
System.out.println(array[2]); // 30
System.out.println("=============");
// 也可以将数组当中的某一个单个元素,赋值交给变量
int num = array[1];
System.out.println(num); // 20
}
}
8.5 动态初始化数组—默认值
/*
使用动态初始化数组的时候,其中的元素将会自动拥有一个默认值。规则如下:
如果是整数类型,那么默认为0;
如果是浮点类型,那么默认为0.0;
如果是字符类型,那么默认为'\u0000';
如果是布尔类型,那么默认为false;
如果是引用类型,那么默认为null。
注意事项:
静态初始化其实也有默认值的过程,只不过系统自动马上将默认值替换成为了大括号当中的具体数值。
*/
public class Demo05ArrayUse {
public static void main(String[] args) {
// 动态初始化一个数组
int[] array = new int[3];
System.out.println(array); // 内存地址值
System.out.println(array[0]); // 0
System.out.println(array[1]); // 0
System.out.println(array[2]); // 0
System.out.println("=================");
// 将数据123赋值交给数组array当中的1号元素
array[1] = 123;
System.out.println(array[0]); // 0
System.out.println(array[1]); // 123
System.out.println(array[2]); // 0
}
}
8.6 内存划分
8.6.1 java的内存划分
8.6.2 只有一个数组的内存图
8.6.3 有两个独立数组的内存图
8.6.4 两个引用指向同一个地址的内存图
8.7 数组的空指针异常
/*
所有的引用类型变量,都可以赋值为一个null值。但是代表其中什么都没有。
数组必须进行new初始化才能使用其中的元素。
如果只是赋值了一个null,没有进行new创建,
那么将会发生:
空指针异常 NullPointerException
原因:忘了new
解决:补上new
*/
public class Demo02ArrayNull {
public static void main(String[] args) {
int[] array = null; // 定义了一个数组,为空
// array = new int[3]; 给数组重新赋值, 若去掉这行注释,程序就是运行正常的
System.out.println(array[0]);
}
}
8.8 获取数组的长度
package cn.itcast.day05.demo03;
/*
如何获取数组的长度,格式:
数组名称.length
这将会得到一个int数字,代表数组的长度。
数组一旦创建,程序运行期间,长度不可改变。
*/
public class Demo03ArrayLength {
public static void main(String[] args) {
int[] arrayA = new int[3];
int[] arrayB = {10, 20, 30, 3, 5, 4, 6, 7, 8, 8, 65, 4, 44, 6, 10, 3, 5, 4, 6, 7, 8, 8, 65, 4};
int len = arrayB.length;
System.out.println("arrayB数组的长度是:" + len);
System.out.println("=============");
int[] arrayC = new int[3];
System.out.println(arrayC.length); // 3
arrayC = new int[5];
System.out.println(arrayC.length); // 5
}
}
8.9 对数组进行遍历
package cn.itcast.day05.demo03;
/*
遍历数组,说的就是对数组当中的每一个元素进行逐一、挨个儿处理。默认的处理方式就是打印输出。
*/
public class Demo04Array {
public static void main(String[] args) {
int[] array = { 15, 25, 30, 40, 50, 60, 75 };
// 首先使用原始方式
System.out.println(array[0]); // 15
System.out.println(array[1]); // 25
System.out.println(array[2]); // 30
System.out.println(array[3]); // 40
System.out.println(array[4]); // 50
System.out.println(array[5]); // 50
System.out.println("=================");
// 使用循环,次数其实就是数组的长度。
for (int i = 0; i < 6; i++) {
System.out.println(array[i]);
}
System.out.println("=================");
// int len = array.length; // 长度
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
8.10 查看数组的最大值和最小值
最大值
public class Demo05ArrayMax {
public static void main(String[] args) {
int[] array = { 5, 15, 30, 20, 10000, 30, 35 };
int max = array[0]; // 比武擂台
for (int i = 1; i < array.length; i++) {
// 如果当前元素,比max更大,则换人
if (array[i] > max) {
max = array[i];
}
}
// 谁最后最厉害,就能在max当中留下谁的战斗力
System.out.println("最大值:" + max);
}
}
最小值
public class Demo06ArrayMin {
public static void main(String[] args) {
int[] array = { 5, 15, 30, 20, 10000, -20, 30, 35 };
int min = array[0]; // 比武擂台
for (int i = 1; i < array.length; i++) {
// 如果当前元素,比min更小,则换人
if (array[i] < min) {
min = array[i];
}
}
System.out.println("最小值:" + min);
}
}
8.11 数组的反转
package cn.itcast.day05.demo03;
/*
数组元素的反转:
本来的样子:[1, 2, 3, 4]
之后的样子:[4, 3, 2, 1]
要求不能使用新数组,就用原来的唯一一个数组。
*/
public class Demo07ArrayReverse {
public static void main(String[] args) {
int[] array = { 10, 20, 30, 40, 50 };
// 遍历打印数组本来的样子
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
System.out.println("============");
/*
初始化语句:int min = 0, max = array.length - 1
条件判断:min < max
步进表达式:min++, max--
循环体:用第三个变量倒手
*/
// 当最小值小于最大值的时候
for (int min = 0, max = array.length - 1; min < max; min++, max--) {
int temp = array[min];
array[min] = array[max];
array[max] = temp;
}
// 再次打印遍历输出数组后来的样子
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
8.12 数组作为方法的参数
package cn.itcast.day05.demo04;
/*
数组可以作为方法的参数。
当调用方法的时候,向方法的小括号进行传参,传递进去的其实是数组的地址值。
*/
public class Demo01ArrayParam {
public static void main(String[] args) {
int[] array = { 10, 20, 30, 40, 50 };
System.out.println(array); // 地址值
printArray(array); // 传递进去的就是array当中保存的地址值
System.out.println("==========AAA==========");
printArray(array);
System.out.println("==========BBB==========");
printArray(array);
}
/*
三要素
返回值类型:只是进行打印而已,不需要进行计算,也没有结果,用void
方法名称:printArray
参数列表:必须给我数组,我才能打印其中的元素。int[] array
*/
public static void printArray(int[] array) {
System.out.println("printArray方法收到的参数是:");
System.out.println(array); // 地址值
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
}
8.12 数组作为方法的返回值
package cn.itcast.day05.demo04;
/*
一个方法可以有0、1、多个参数;但是只能有0或者1个返回值,不能有多个返回值。
如果希望一个方法当中产生了多个结果数据进行返回,怎么办?
解决方案:使用一个数组作为返回值类型即可。
任何数据类型都能作为方法的参数类型,或者返回值类型。
数组作为方法的参数,传递进去的其实是数组的地址值。
数组作为方法的返回值,返回的其实也是数组的地址值。
*/
public class Demo02ArrayReturn {
public static void main(String[] args) {
int[] result = calculate(10, 20, 30);
System.out.println("main方法接收到的返回值数组是:");
System.out.println(result); // 地址值
System.out.println("总和:" + result[0]);
System.out.println("平均数:" + result[1]);
}
public static int[] calculate(int a, int b, int c) {
int sum = a + b + c; // 总和
int avg = sum / 3; // 平均数
// 两个结果都希望进行返回
// 需要一个数组,也就是一个塑料兜,数组可以保存多个结果
/*
int[] array = new int[2];
array[0] = sum; // 总和
array[1] = avg; // 平均数
*/
int[] array = { sum, avg };
System.out.println("calculate方法内部数组是:");
System.out.println(array); // 地址值
return array;
}
}
8.13 数组转字符串
// 导入转换的模块
import java.util.Arrays;
public class Demo01PrintArray {
public static void main(String[] args) {
int[] array = {10,20,30};
// 把数组 array 转换为了字符串
System.out.println(Arrays.toString(array)); // [10, 20, 30] 字符串
}
}
8.14 数组的排序
// 定义一个数组,数组里面存放着 Person对象
Person[] arr = {
new Person("柳岩",38),
new Person("迪丽热巴",18),
new Person("古力娜扎",19)
};
// 对数组里面存放的Person进行排序
Arrays.sort(arr, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
// 年龄又小到大的排序方式
return o1.getAge() - o2.getAge();
}
});
// 打印Person数组
System.out.println(Arrays.toString(arr));
// [Person{name='迪丽热巴', age=18}, Person{name='古力娜扎', age=19}, Person{name='柳岩', age=38}]
9. 自定义Java中的类
定义一个类,用来模拟“学生”事物。其中就有两个组成部分:
- 属性(是什么):
-
姓名
-
年龄
-
- 行为(能做什么):
- 吃饭
- 睡觉
- 学习
对应到Java的类当中:
-
成员变量(属性):
- String name; // 姓名
- int age; // 年龄
-
成员方法(行为):
- public void eat() {} // 吃饭
- public void sleep() {} // 睡觉
注意事项:
-
成员变量是直接定义在类当中的,在方法外边。
-
成员方法不要写static关键字。
9.1 定义类,创建对象,使用
通常情况下,一个类并不能直接使用,需要根据类创建一个对象,才能使用。
1.导包:也就是指出需要使用的类,在什么位置。
import 包名称.类名称;
import cn.itcast.day06.demo01.Student;
对于和当前类属于同一个包的情况,可以省略导包语句不写。
2.创建,格式:
类名称 对象名 = new 类名称();
Student stu = new Student();
3.使用,分为两种情况:
使用成员变量:对象名.成员变量名
使用成员方法:对象名.成员方法名(参数)
(也就是,想用谁,就用对象名点儿谁。)
注意事项:
如果成员变量没有进行赋值,那么将会有一个默认值,规则和数组一样。
public class Student {
// 成员变量
String name; // 姓名
int age; // 姓名
// 成员方法,可以理解为类中的方法 成员方法不需要写 static 关键字
// 因为没有返回值,所以用 void 类型
public void eat() {
System.out.println("吃饭饭!");
}
public void sleep() {
System.out.println("睡觉觉!");
}
public void study() {
System.out.println("学习!");
}
// public static void main 主要是用来调用自己创建的类的
public static void main(String[] args) {
// 1. 导包。
// 我需要使用的Student类,和我自己Demo02Student位于同一个包下,所以省略导包语句不写
// 2. 创建,格式:
// 类名称 对象名 = new 类名称();
// 根据Student类,创建了一个名为stu的对象
Student stu = new Student();
// 3. 使用其中的成员变量,格式:
// 对象名.成员变量名
System.out.println(stu.name); // null
System.out.println(stu.age); // 0
System.out.println("=============");
// 改变对象当中的成员变量数值内容
// 将右侧的字符串,赋值交给stu对象当中的name成员变量
stu.name = "赵丽颖";
stu.age = 18;
System.out.println(stu.name); // 赵丽颖
System.out.println(stu.age); // 18
System.out.println("=============");
// 4. 使用对象的成员方法,格式:
// 对象名.成员方法名()
stu.eat();
stu.sleep();
stu.study();
}
}
9.2 创建的对象的内存图
只有一个对象的情况
有两个对象的情况,且使用同一个方法的情况
两个引用指向同一个对象的内存图
9.3 类对象作为参数进行传递
定义好的类
public class Phone {
// 成员变量
String brand;
double price;
String color;
// 成员方法
public void call(String who) {
System.out.println("给" + who + "打电话");
}
public void sendMessage() {
System.out.println("群发短息");
}
}
和类在同一个包的情况下调用这个类,创建对象
public class Demo03PhoneParam {
public static void main(String[] args) {
Phone one = new Phone();
one.brand = "苹果";
one.price = 8366.0;
one.color = "土豪金";
// 调用定义好的方法
method(one); // 传递进入的实际上是地址值
}
public static void method(Phone a) {
System.out.println(a.color);
System.out.println(a.brand);
System.out.println(a.price);
}
}
类对象作为参数传递的内存图解
9.4 类对象作为返回值进行传递
public class Demo05PhoneReturn {
public static void main(String[] args) {
Phone two = getPhone();
System.out.println(two.brand); // 苹果
System.out.println(two.price); // 8388.0
System.out.println(two.color); // 玫瑰金
}
public static Phone getPhone() {
Phone one = new Phone();
one.brand = "苹果";
one.price = 8388.0;
one.color = "玫瑰金";
return one;
}
}
类对象作为返回值进行传递的内存图解
9.5 类中的局部变量和全局变量
局部变量和成员变量
1. 定义的位置不一样【重点】
- 局部变量:在方法的内部
- 成员变量:在方法的外部,直接写在类当中
2. 作用范围不一样【重点】
- 局部变量:只有方法当中才可以使用,出了方法就不能再用
- 成员变量:整个类全都可以通用。
3. 默认值不一样【重点】
- 局部变量:没有默认值,如果要想使用,必须手动进行赋值
- 成员变量:如果没有赋值,会有默认值,规则和数组一样
4. 内存的位置不一样(了解)
- 局部变量:位于栈内存
- 成员变量:位于堆内存
5. 生命周期不一样(了解)
- 局部变量:随着方法进栈而诞生,随着方法出栈而消失
- 成员变量:随着对象创建而诞生,随着对象被垃圾回收而消失
public class Demo01VariableDifference {
String name; // 成员变量,相当于python中的全局变量
public void methodA() {
int num = 20; // 局部变量
System.out.println(num);
System.out.println(name);
}
public void methodB(int param) { // 方法的参数就是局部变量
// 参数在方法调用的时候,必然会被赋值的。
System.out.println(param);
int age; // 局部变量
// System.out.println(age); // 没赋值不能用
// System.out.println(num); // 错误写法!
System.out.println(name);
}
}
10. java中的面向对象
面向对象三大特征:封装、继承、多态。
10.1 封装
封装性在Java当中的体现:
- 方法就是一种封装
- 关键字private也是一种封装
封装就是将一些细节信息隐藏起来,对于外界不可见。
10.1.1 private关键字保护成员变量
问题描述:定义Person的年龄时,无法阻止不合理的数值被设置进来。
解决方案:用private关键字将需要保护的成员变量进行修饰。
一旦使用了private进行修饰,那么本类当中仍然可以随意访问。
但是!超出了本类范围之外就不能再直接访问了。
间接访问private成员变量,就是定义一对儿Getter/Setter方法
必须叫setXxx或者是getXxx命名规则。
对于基本类型当中的boolean值,Getter方法一定要写成isXxx的形式,而setXxx规则不变。
对于Getter来说,不能有参数,返回值类型和成员变量对应;
对于Setter来说,不能有返回值,参数类型和成员变量对应。
定义一个Person方法
public class Person {
String name; // 姓名
private int age; // 年龄
public void show() {
System.out.println("我叫:" + name + ",年龄:" + age);
}
// 这个成员方法,专门用于向age设置数据
public void setAge(int num) {
if (num < 100 && num >= 9) { // 如果是合理情况
age = num;
} else {
System.out.println("数据不合理!");
}
}
// 这个成员方法,专门私语获取age的数据
public int getAge() {
return age;
}
}
调用封装好的Person方法
public class Demo03Person {
public static void main(String[] args) {
Person person = new Person();
person.show();
person.name = "赵丽颖";
// person.age = -20; // 直接访问private内容,错误写法,需要通过下面的方法进行设置!
person.setAge(20);
person.show();
}
}
10.1.2 this 的用法
当方法的局部变量和类的成员变量重名的时候,根据“就近原则”,优先使用局部变量。
如果需要访问本类当中的成员变量,需要使用格式:
this.成员变量名
“通过谁调用的方法,谁就是this。”
定义一个方法
public class Person {
// 此方法中,局部变量和全局变量的名称相同,如果不加this关键字的话,使用局部变量
String name; // 我自己的名字
// 参数name是对方的名字
// 成员变量name是自己的名字
public void sayHello(String name) {
System.out.println(name + ",你好。我是" + this.name);
System.out.println(this); // 地址值
}
}
调用这个方法
public class Demo01Person {
public static void main(String[] args) {
Person person = new Person();
// 设置我自己的名字
person.name = "王健林";
person.sayHello("王思聪");
System.out.println(person); // 地址值
}
}
10.1.3 构造方法
构造方法是专门用来创建对象的方法,当我们通过关键字new来创建对象时,其实就是在调用构造方法。
格式:
public 类名称(参数类型 参数名称) {
// 方法体
}
注意事项:
**1.**构造方法的名称必须和所在的类名称完全一样,就连大小写也要一样
**2.**构造方法不要写返回值类型,连void都不写
**3.**构造方法不能return一个具体的返回值
**4.**如果没有编写任何构造方法,那么编译器将会默认赠送一个构造方法,没有参数、方法体什么事情都不做。
**5.**一旦编写了至少一个构造方法,那么编译器将不再赠送。
**6.**构造方法也是可以进行重载的。重载:方法名称相同,参数列表不同。
定义构造方法
public class Student {
// 成员变量
private String name;
private int age;
// 无参数的构造方法
public Student() {
System.out.println("无参构造方法执行啦!");
}
// 全参数的构造方法
public Student(String name, int age) {
System.out.println("全参构造方法执行啦!");
this.name = name;
this.age = age;
}
// Getter Setter
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
}
调用构造方法
public class Demo02Student {
public static void main(String[] args) {
Student stu1 = new Student(); // 无参构造
System.out.println("============");
Student stu2 = new Student("赵丽颖", 20); // 全参构造
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
// 如果需要改变对象当中的成员变量数据内容,仍然还需要使用setXxx方法
stu2.setAge(21); // 改变年龄
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
}
}
10.1.4 标准的类
一个标准的类通常要拥有下面四个组成部分:
- 所有的成员变量都要使用private关键字修饰
- 为每一个成员变量编写一对儿Getter/Setter方法
- 编写一个无参数的构造方法
- 编写一个全参数的构造方法
这样标准的类也叫做Java Bean
标准类的快速构建方法
首先先写上基本的成员变量
通过IEDA的快捷方式进行构建( Alt + Insert)
标准类的代码
public class Student {
private String name; // 姓名
private int age; // 年龄
// 无参数的构造方法
public Student() {
}
// 全参数的构造方法
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// Getter和Setter的构造方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
对构造方法进行调用
public class Demo01Student {
public static void main(String[] args) {
Student stu1 = new Student();
stu1.setName("迪丽热巴");
stu1.setAge(20);
System.out.println("姓名:" + stu1.getName() + ",年龄:" + stu1.getAge());
System.out.println("=================");
// 运用了方法的重载
// 使用了构造方法,可以直接把参数传递给全局变量,省时省力
Student stu2 = new Student("古力娜扎", 21);
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
stu2.setAge(22);
System.out.println("姓名:" + stu2.getName() + ",年龄:" + stu2.getAge());
}
10.2 继承
在继承的关系中,“子类就是一个父类”。也就是说,子类可以被当做父类看待。
例如父类是员工,子类是讲师,那么“讲师就是一个员工”。关系:is-a。
定义父类的格式:(一个普通的类定义)
public class 父类名称 {
// ...
}
定义子类的格式:
public class 子类名称 extends 父类名称 {
// ...
}
父类
// 定义一个父类:员工
public class Employee {
public void method() {
System.out.println("方法执行!");
}
}
子类继承父类
// 定义了一个员工的子类:讲师
public class Teacher extends Employee {
}
调用
public class Demo01Extends {
public static void main(String[] args) {
// 创建了一个子类对象
Teacher teacher = new Teacher();
// Teacher类当中虽然什么都没写,但是会继承来自父类的method方法。
teacher.method();
// 创建另一个子类助教的对象
Assistant assistant = new Assistant();
assistant.method();
}
}
10.2.1 继承后的特点–成员变量重名
在父子类的继承关系当中,如果成员变量重名,则创建子类对象时,访问有两种方式:
- 直接通过子类对象访问成员变量:
等号左边是谁,就优先用谁,没有则向上找。 - 间接通过成员方法访问成员变量:
该方法属于谁,就优先用谁,没有则向上找。
子类
public class Zi extends Fu {
int numZi = 20;
int num = 200;
public void methodZi() {
// 因为本类当中有num,所以这里用的是本类的num
System.out.println(num);
}
}
父类
public class Fu {
int numFu = 10;
int num = 100;
public void methodFu() {
// 使用的是本类当中的,不会向下找子类的
System.out.println(num);
}
}
调用
public class Demo01ExtendsField {
public static void main(String[] args) {
Fu fu = new Fu(); // 创建父类对象
System.out.println(fu.numFu); // 只能使用父类的东西,没有任何子类内容
System.out.println("===========");
Zi zi = new Zi();
System.out.println(zi.numFu); // 10
System.out.println(zi.numZi); // 20
System.out.println("===========");
// 等号左边是谁,就优先用谁
System.out.println(zi.num); // 优先子类,200
// System.out.println(zi.abc); // 到处都没有,编译报错!
System.out.println("===========");
// 这个方法是子类的,优先用子类的,没有再向上找
zi.methodZi(); // 200
// 这个方法是在父类当中定义的,在子类里面没有,只能向上找,然后就找到了父类
zi.methodFu(); // 100
}
}
10.2.2 super和this修饰变量
- 局部变量:直接写成员变量名
- 本类的成员变量:this.成员变量名
- 父类的成员变量:super.成员变量名
父类和子类
// 父类
public class Fu {
int num = 10;
}
// 子类
public class Zi extends Fu {
int num = 20;
public void method() {
int num = 30;
System.out.println(num); // 30,局部变量
System.out.println(this.num); // 20,本类的成员变量
System.out.println(super.num); // 10,父类的成员变量
}
}
调用
public class Demo01ExtendsField {
public static void main(String[] args) {
Zi zi = new Zi();
zi.method(); // 会执行子类的方法
}
}
10.2.3 继承后方法的重写
在父子类的继承关系当中,创建子类对象,访问成员方法的规则:
创建的对象是谁,就优先用谁,如果没有则向上找。
注意事项:
无论是成员方法还是成员变量,如果没有都是向上找父类,绝对不会向下找子类的。
重写(Override)
概念:在继承关系当中,方法的名称一样,参数列表也一样。
重写(Override):方法的名称一样,参数列表【也一样】。覆盖、覆写。
重载(Overload):方法的名称一样,参数列表【不一样】。
方法的覆盖重写特点:创建的是子类对象,则优先用子类方法。
// 父类
public class Fu {
public void methodFu() {
System.out.println("父类方法执行!");
}
public void method() {
System.out.println("父类重名方法执行!");
}
}
// 子类
public class Zi extends Fu {
public void methodZi() {
System.out.println("子类方法执行!");
}
public void method() {
System.out.println("子类重名方法执行!");
}
}
调用子类
public class Demo01ExtendsMethod {
public static void main(String[] args) {
Zi zi = new Zi();
zi.methodFu(); // 父类方法执行
zi.methodZi(); // 子类方法执行
// 创建的是new了子类对象,虽然父类和子类的方法名一样,优先用子类的方法
zi.method(); // 子类重名方法执行(相当于是子类重写了父类的method()方法)
}
}
10.2.4 方法覆盖重写的注意事项
方法覆盖重写的注意事项:
**1.**必须保证父子类之间方法的名称相同,参数列表也相同。
@Override:写在方法前面,用来检测是不是有效的正确覆盖重写。
这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。
**2.**子类方法的返回值必须【小于等于】父类方法的返回值范围。
小扩展提示:java.lang.Object类是所有类的公共最高父类(祖宗类),java.lang.String就是Object的子类。
**3.**子类方法的权限必须【大于等于】父类方法的权限修饰符。
小扩展提示:public > protected > (default) > private
备注:(default)不是关键字default,而是什么都不写,留空。
父类方法
子类方法
子类方法权限必须大于等于 父类方法的权限修饰符
10.2.5 重写的应用
// 本来的老款手机
public class Phone {
public void call() {
System.out.println("打电话");
}
public void send() {
System.out.println("发短信");
}
public void show() {
System.out.println("显示号码");
}
}
// 定义一个新手机,使用老手机作为父类
public class NewPhone extends Phone {
@Override
public void show() {
// 把父类的show方法拿过来重复利用
super.show();
// 子类的show()方法和父类同名,相当于在父类的基础上又重新拓展了新的功能
System.out.println("显示姓名");
System.out.println("显示头像");
}
}
10.2.6 继承中的构造方法
继承关系中,父子类构造方法的访问特点:
**1.**子类构造方法当中有一个默认隐含的“super()”调用,所以一定是先调用的父类构造,后执行的子类构造。
**2.**子类构造可以通过super关键字来调用父类重载构造。
**3.**super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造。
总结:
-
子类必须调用父类构造方法,不写则赠送super();
-
写了则用写的指定的super调用,super只能有一个,还必须是第一个。
// 父类方法
public class Fu {
public Fu() {
System.out.println("父类无参构造");
}
public Fu(int num) {
System.out.println("父类有参构造!");
}
}
// 子类方法
public class Zi extends Fu {
public Zi() {
super(); // 在调用父类无参构造方法,就是把这个super注释了,父类的构造方法还是会执行的
// super(20); // 在调用父类重载的构造方法,子类只能用一次super
System.out.println("子类构造方法!");
}
public void method() {
// super(); // 错误写法!只有子类构造方法,才能调用父类构造方法。
}
}
调用
public class Demo01Constructor {
public static void main(String[] args) {
Zi zi = new Zi();
}
}
// 打印的结果
// 父类无参数构造
// 子类构造方法执行
10.2.7 super关键字的用法
super关键字的用法有三种:
- 在子类的成员方法中,访问父类的成员变量。
- 在子类的成员方法中,访问父类的成员方法。
- 在子类的构造方法中,访问父类的构造方法。
父类
public class Fu {
int num = 10;
public Fu() {
System.out.println("父类构造方法执行了");
}
public void method() {
System.out.println("父类的普通方法执行了");
}
}
子类
public class Zi extends Fu {
int num = 20;
public Zi() {
super();
System.out.println("子类无参数构造执行了");
}
public void methodZi() {
System.out.println("子类的methodZi()方法执行了" + this.num); // 子类的成员变量
System.out.println(super.num); // 父类的成员变量
}
public void method() {
super.method(); // 子类方法和父类方法同名,此处继承了父类方法,在此基础上添加功能
System.out.println("子类的普通方法执行了");
}
}
10.2.8 this关键字的用法
super关键字用来访问父类内容,而this关键字用来访问本类内容。用法也有三种:
- 在本类的成员方法中,访问本类的成员变量。
- 在本类的成员方法中,访问本类的另一个成员方法。
- 在本类的构造方法中,访问本类的另一个构造方法。
在第三种用法当中要注意:
A. this(…)调用也必须是构造方法的第一个语句,唯一一个。
B. super和this两种构造调用,不能同时使用。
父类
public class Fu {
int num = 30;
}
子类
public class Zi extends Fu {
int num = 20;
public Zi() {
// super(); // 这一行不再赠送,因为this()和super()都必须放到开头,二者不能共存
this(123); // 本类的无参构造,调用本类的有参构造
// this(1, 2); // 错误写法!一个构造方法里面只能用一次this
}
public Zi(int n) {
this(1, 2);
System.out.println("一个参数的构造方法:" + n);
}
public Zi(int n, int m) {
System.out.println("两个参数的构造方法:" + n + m);
}
public void showNum() {
int num = 10;
System.out.println(num); // 局部变量
System.out.println(this.num); // 本类中的成员变量
System.out.println(super.num); // 父类中的成员变量
}
public void methodA() {
System.out.println("AAA");
}
public void methodB() {
this.methodA();
System.out.println("BBB");
}
}
调用
public class Demo01 {
public static void main(String[] args) {
Zi zi = new Zi(56);
zi.methodB();
}
}
// 执行的结果是:
// 创建函数的对象的时候,函数的一个参数构造方法被调用,然而在一个函数的构造方法里面又通过this()调用了两个参数的构造方法,因此两个函数的构造方法要先执行
两个参数的构造方法:12 // 这段代码先执行的原因是,this()放在上面,因此调用一个构造方法的时候先执行
一个参数的构造方法:56
AAA
BBB
10.2.9 java继承的特点
10.2.10 发红包案例
// 用户的总类,群主和群成员都是用户
public class User {
private String name; // 姓名
private int money; // 余额,也就是当前用户拥有的钱数
public User() {
}
public User(String name, int money) {
this.name = name;
this.money = money;
}
// 展示一下当前用户有多少钱
public void show() {
System.out.println("我叫:" + name + ",我有多少钱:" + money);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
}
import java.util.ArrayList;
// 群主的类
public class Manager extends User {
public Manager() {
}
public Manager(String name, int money) {
// 继承
super(name, money);
}
public ArrayList<Integer> send(int totalMoney, int count) {
// 首先需要一个集合,用来存储若干个红包的金额
ArrayList<Integer> redList = new ArrayList<>();
// 首先看一下群主自己有多少钱
int leftMoney = super.getMoney(); // 群主当前余额
if (totalMoney > leftMoney) {
System.out.println("余额不足");
return redList; // 返回空集合
}
// 扣钱,其实就是重新设置余额
super.setMoney(leftMoney - totalMoney);
// 发红包需要平均拆分成为count份
int avg = totalMoney / count;
int mod = totalMoney % count; // 余数,也就是甩下的零头
// 除不开的零头,包在最后一个红包当中
// 下面把红包一个一个放到集合当中
for (int i = 0; i < count - 1; i++) {
redList.add(avg);
}
// 最后一个红包
int last = avg + mod;
redList.add(last);
return redList;
}
}
import java.util.ArrayList;
import java.util.Random;
// 普通成员
public class Member extends User {
public Member() {
}
public Member(String name, int money) {
super(name, money);
}
public void receive(ArrayList<Integer> list) {
// 从多个红包当中随便抽取一个,给我自己。
// 随机获取一个集合当中的索引编号
int index = new Random().nextInt(list.size());
// 根据索引,从集合当中删除,并且得到被删除的红包,给我自己
int delta = list.remove(index);
// 当前成员自己本来有多少钱:
int money = super.getMoney();
// 加法,并且重新设置回去
super.setMoney(money + delta);
}
}
调用
import java.util.ArrayList;
public class MainRedPacket {
public static void main(String[] args) {
Manager manager = new Manager("群主", 100);
Member one = new Member("成员A", 0);
Member two = new Member("成员B", 0);
Member three = new Member("成员C", 0);
manager.show(); // 100
one.show(); // 0
two.show(); // 0
three.show(); // 0
System.out.println("===============");
// 群主总共发20块钱,分成3个红包
ArrayList<Integer> redList = manager.send(20, 3);
// 三个普通成员收红包
one.receive(redList);
two.receive(redList);
three.receive(redList);
manager.show(); // 100-20=80
// 6、6、8,随机分给三个人
one.show();
two.show();
three.show();
}
}
10.3 多态
10.3.1 多态的体现
代码当中体现多态性,其实就是一句话:父类引用指向子类对象。
左父右子就是多态
格式:
父类名称 对象名 = new 子类名称();
或者:
接口名称 对象名 = new 实现类名称();
// 父类方法
public class Fu {
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类特有方法");
}
}
// 子类方法
public class Zi extends Fu {
@Override // 子类重写了父类的method()方法
public void method() {
System.out.println("子类方法");
}
}
调用—体现出多态
public class Demo01Multi {
public static void main(String[] args) {
// 使用多态的写法
// 左侧父类的引用,指向了右侧子类的对象
Fu obj = new Zi();
obj.method(); // 因为new的是子类的对象,所以调用的也是子类重写父类之后的方法
obj.methodFu(); // 父类的特有方法
}
}
10.3.2 多态中成员变量和成员方法的访问
访问成员变量的两种方式:
- 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。
- 间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。
定义一个父子类继承
// 父类
public class Fu /*extends Object*/ {
int num = 10;
public void showNum() {
System.out.println(num);
}
public void method() {
System.out.println("父类方法");
}
public void methodFu() {
System.out.println("父类特有方法");
}
}
// 子类
public class Zi extends Fu {
int num = 20;
int age = 16;
@Override
public void showNum() {
System.out.println(num);
}
@Override
public void method() {
System.out.println("子类方法");
}
public void methodZi() {
System.out.println("子类特有方法");
}
}
调用–多态的体现
public class Demo01MultiField {
public static void main(String[] args) {
// 使用多态的写法,父类引用指向子类对象
Fu obj = new Zi();
System.out.println(obj.num); // 父:10 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找
// System.out.println(obj.age); // 错误写法!因为不可能向下找
System.out.println("=============");
// 子类没有覆盖重写,就是父:10
// 子类如果覆盖重写,就是子:20
obj.showNum();
}
}
访问成员方法
在多态的代码当中,成员方法的访问规则是:
看new的是谁,就优先用谁,没有则向上找。
口诀:编译看左边,运行看右边。
对比一下:
成员变量:编译看左边,运行还看左边。
成员方法:编译看左边,运行看右边。
public class Demo02MultiMethod {
public static void main(String[] args) {
Fu obj = new Zi(); // 多态
obj.method(); // 父子都有,优先用子
// 编译看左边,左边的父类方法确实有methodFu(),运行看右边,右边的子类方法没有methodFu(), // 找到父类
obj.methodFu(); // 子类没有,父类有,向上找到父类
// 编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错。
// obj.methodZi(); // 错误写法!
}
}
10.3.3 多态的好处
10.3.4 对象的向上向下转型
向上转型一定是安全的,没有问题的,正确的。但是也有一个弊端:
对象一旦向上转型为父类,那么就无法调用子类原本特有的内容。
解决方案:用对象的向下转型【还原】。
// 定义一个动物类
public abstract class Animal {
public abstract void eat(); // 动物都会有吃东西的方法
}
// 定义一个猫类去继承动物类
public class Cat extends Animal {
@Override // 重写了动物类的吃东西的方法
public void eat() {
System.out.println("猫吃鱼");
}
// 子类特有方法
public void catchMouse() {
System.out.println("猫抓老鼠");
}
}
// 定义一个狗类去继承动物类
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃SHIT");
}
// 子类特有的方法
public void watchHouse() {
System.out.println("狗看家");
}
}
调用,体现出了对象的向上向下转型
public class Demo01Main {
public static void main(String[] args) {
// 对象的向上转型,就是:父类引用指向之类对象。
Animal animal = new Cat(); // 本来创建的时候是一只猫
animal.eat(); // 猫吃鱼
// animal.catchMouse(); // 错误写法!因为是向上转型,猫转型成为动物,但并不是所有的动物都 //会抓老鼠,所以是错的
// 如果想要调用子类(猫特有的方法的话,需要向下还原)
// 向下转型,进行“还原”动作
Cat cat = (Cat) animal;
cat.catchMouse(); // 猫抓老鼠
// 下面是错误的向下转型
// 本来new的时候是一只猫,现在非要当做狗
// 错误写法!编译不会报错,但是运行会出现异常:
// java.lang.ClassCastException,类转换异常
Dog dog = (Dog) animal;
}
}
10.3.5 instanceof 判断 父类引用的对象本来的子类
如何才能知道一个父类引用的对象,本来是什么子类?
格式:
对象 instanceof 类名称
这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例。
public class Demo02Instanceof {
public static void main(String[] args) {
Animal animal = new Dog(); // 本来是一只狗
animal.eat(); // 狗吃SHIT
// 如果希望掉用子类特有方法,需要向下转型
// 判断一下父类引用animal本来是不是Dog
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
// 判断一下animal本来是不是Cat
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
// 调用传递动物的方法
giveMeAPet(new Dog());
}
// 这个方法并不知道传进去的到底是什么参数,单单看参数只能看出传的是动物,因此需要判断到底是什么动物
public static void giveMeAPet(Animal animal) {
if (animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.watchHouse();
}
if (animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}
}
}
10.3.6 静态没有多态性
静态的多态性(因为静态属于类,而多态时对象的多态): 成员变量或者成员方法是静态的
-
==所以说 多态调用静态成员或者静态方法的时候,没有多态性==
-
父类类型 变量名= new 子类对象();
变量名.静态成员变量; // 访问的就是父类的
变量名.静态成员方法(); // 访问的就是父类
11.java自带的类
11.1 Scanner类
Scanner类的功能:可以实现键盘输入数据,到程序当中。
引用类型的一般使用步骤:
1.导包
import 包路径.类名称;
如果需要使用的目标类,和当前类位于同一个包下,则可以省略导包语句不写。
只有java.lang包下的内容不需要导包,其他的包都需要import语句。
2.创建
类名称 对象名 = new 类名称();
3.使用
对象名.成员方法名()
获取键盘输入的一个int数字:int num = sc.nextInt();
获取键盘输入的一个字符串:String str = sc.next();
import java.util.Scanner; // 1. 导包
public class Demo01Scanner {
public static void main(String[] args) {
// 2. 创建
// 备注:System.in代表从键盘进行输入
Scanner sc = new Scanner(System.in);
// 3. 获取键盘输入的int数字
int num = sc.nextInt();
System.out.println("输入的int数字是:" + num);
// 4. 获取键盘输入的字符串
String str = sc.next();
System.out.println("输入的字符串是:" + str);
}
}
输出三个数中最大的那个
/*
键盘输入三个int数字,然后求出其中的最大值。
思路:
1. 既然是键盘输入,肯定需要用到Scanner
2. Scanner三个步骤:导包、创建、使用nextInt()方法
3. 既然是三个数字,那么调用三次nextInt()方法,得到三个int变量
4. 无法同时判断三个数字谁最大,应该转换成为两个步骤:
4.1 首先判断前两个当中谁最大,拿到前两个的最大值
4.2 拿着前两个中的最大值,再和第三个数字比较,得到三个数字当中的最大值
5. 打印最终结果
*/
import java.util.Scanner;
public class Demo03ScannerMax {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入第一个数字:");
int a = sc.nextInt();
System.out.println("请输入第二个数字:");
int b = sc.nextInt();
System.out.println("请输入第三个数字:");
int c = sc.nextInt();
// 首先得到前两个数字当中的最大值
int temp = a > b ? a : b;
int max = temp > c ? temp : c;
System.out.println("最大值是:" + max);
}
}
11.2 匿名函数
创建对象的标准格式:
类名称 对象名 = new 类名称();
匿名对象就是只有右边的对象,没有左边的名字和赋值运算符。
new 类名称();
注意事项:匿名对象只能使用
唯一的一次,下次再用不得不再创建一个新对象。
使用建议:如果确定有一个对象只需要使用唯一的一次,就可以用匿名对象。
定义一个人物类
public class Person {
String name;
public void showName() {
System.out.println("我叫:" + name);
}
}
调用这个人物类
public class Demo01Anonymous {
public static void main(String[] args) {
// 左边的one就是对象的名字
Person one = new Person();
one.name = "高圆圆";
one.showName(); // 我叫高圆圆
System.out.println("===============");
// 匿名对象
new Person().name = "赵又廷";
// 匿名对象只能使用唯一的一次,因为重新new了一次,所以相当于重新创建了一个对象,因此显示null
new Person().showName(); // 我叫:null
}
}
匿名函数作为参数
import java.util.Scanner;
public class Test {
public static void main(String[] args) {
input(new Scanner(System.in));
}
public static void input(Scanner sc) {
System.out.println("键盘输入的值是:" + sc.nextInt());
}
}
匿名函数作为返回值
import java.util.Scanner;
public class Test1 {
public static void main(String[] args) {
Scanner sc = methodReturn();
int num = sc.nextInt();
System.out.println("输入的是:" + num);
}
public static Scanner methodReturn() {
return new Scanner(System.in);
}
}
11.3 Random类
Random类用来生成随机数字。使用起来也是三个步骤:
1.导包
import java.util.Random;
2.创建
Random r = new Random(); // 小括号当中留空即可
3.使用
获取一个随机的int数字(范围是int所有范围,有正负两种):int num = r.nextInt()
获取一个随机的int数字(参数代表了范围,左闭右开区间):int num = r.nextInt(3)
实际上代表的含义是:[0,3),也就是0~2
import java.util.Random;
public class Demo02Random {
public static void main(String[] args) {
Random r = new Random();
for (int i = 0; i < 100 ; i++) {
int num = r.nextInt(1000);
System.out.println("随机产生的数是:" + num);
}
}
}
/*
题目:
用代码模拟猜数字的小游戏。
思路:
1. 首先需要产生一个随机数字,并且一旦产生不再变化。用Random的nextInt方法
2. 需要键盘输入,所以用到了Scanner
3. 获取键盘输入的数字,用Scanner当中的nextInt方法
4. 已经得到了两个数字,判断(if)一下:
如果太大了,提示太大,并且重试;
如果太小了,提示太小,并且重试;
如果猜中了,游戏结束。
5. 重试就是再来一次,循环次数不确定,用while(true)。
*/
public class Demo04RandomGame {
public static void main(String[] args) {
Random r = new Random();
int randomNum = r.nextInt(100) + 1; // [1,100]
Scanner sc = new Scanner(System.in);
while (true) {
System.out.println("请输入你猜测的数字:");
int guessNum = sc.nextInt(); // 键盘输入猜测的数字
if (guessNum > randomNum) {
System.out.println("太大了,请重试。");
} else if (guessNum < randomNum) {
System.out.println("太小了,请重试。");
} else {
System.out.println("恭喜你,猜中啦!");
break; // 如果猜中,不再重试
}
}
System.out.println("游戏结束。");
}
}
11.4 ArrayList类 (对象数组)
对象数组的引入
/*
题目:
定义一个数组,用来存储3个Person对象。
数组有一个缺点:一旦创建,程序运行期间长度不可以发生改变。
*/
public class Demo01Array {
public static void main(String[] args) {
// 首先创建一个长度为3的数组,里面用来存放Person类型的对象
Person[] array = new Person[3];
// Person类是在其他地方被创建好的
Person one = new Person("迪丽热巴", 18);
Person two = new Person("古力娜扎", 28);
Person three = new Person("玛尔扎哈", 38);
// 将one当中的地址值赋值到数组的0号元素位置
array[0] = one;
array[1] = two;
array[2] = three;
System.out.println(array[0]); // 地址值
System.out.println(array[1]); // 地址值
System.out.println(array[2]); // 地址值
// 相当于是 地址值.getName(),所以能获取到名字
System.out.println(array[1].getName()); // 古力娜扎
}
}
11.4.1 ArrayList类的基本使用
数组的长度不可以发生改变。
但是ArrayList集合的长度是可以随意变化的。
对于ArrayList来说,有一个尖括号代表泛型。
泛型:也就是装在集合当中的所有元素,全都是统一的什么类型。
注意:泛型只能是引用类型,不能是基本类型。虽然不可以储存基本数据类型,但是可以把基本数据类型加工成包装数据类型.然后放到ArrayList集合当中去储存.
注意事项:
对于ArrayList集合来说,直接打印得到的不是地址值,而是内容。
如果内容是空,得到的是空的中括号:[]
代码实现
import java.util.ArrayList;
public class Demo02ArrayList {
public static void main(String[] args) {
// 创建了一个ArrayList集合,集合的名称是list,里面装的全都是String字符串类型的数据
// 备注:从JDK 1.7+开始,右侧的尖括号内部可以不写内容,但是<>本身还是要写的。
ArrayList<String> list = new ArrayList<>();
System.out.println(list); // []
// 向集合当中添加一些数据,需要用到add方法。
list.add("赵丽颖"); // 在创建集合类的时候就规定好了只能添加字符串
System.out.println(list); // [赵丽颖]
list.add("迪丽热巴");
list.add("古力娜扎");
list.add("玛尔扎哈");
System.out.println(list); // [赵丽颖, 迪丽热巴, 古力娜扎, 玛尔扎哈]
// list.add(100); // 错误写法!因为创建的时候尖括号泛型已经说了是字符串,添加进去的元素就必须都是字符串才行
}
}
11.4.2 ArrayList当中的常用方法
ArrayList当中的常用方法有:
public boolean add(E e):向集合当中添加元素,参数的类型和泛型一致。返回值代表添加是否成功。
备注:对于ArrayList集合来说,add添加动作一定是成功的,所以返回值可用可不用。
但是对于其他集合(今后学习)来说,add添加动作不一定成功。
public E get(int index):从集合当中获取元素,参数是索引编号,返回值就是对应位置的元素。
public E remove(int index):从集合当中删除元素,参数是索引编号,返回值就是被删除掉的元素。
public int size():获取集合的尺寸长度,返回值是集合中包含的元素个数。
public class Demo03ArrayListMethod {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
System.out.println(list); // []
// 向集合中添加元素:add 这个用到了返回值
boolean success = list.add("柳岩");
System.out.println(list); // [柳岩]
System.out.println("添加的动作是否成功:" + success); // true
// 下面的几个例子没有用到返回值
list.add("高圆圆");
list.add("赵又廷");
list.add("李小璐");
list.add("贾乃亮");
System.out.println(list); // [柳岩, 高圆圆, 赵又廷, 李小璐, 贾乃亮]
// 从集合中获取元素:get。索引值从0开始
String name = list.get(2);
System.out.println("第2号索引位置:" + name); // 赵又廷
// 从集合中删除元素:remove。索引值从0开始。
String whoRemoved = list.remove(3);
System.out.println("被删除的人是:" + whoRemoved); // 李小璐
System.out.println(list); // [柳岩, 高圆圆, 赵又廷, 贾乃亮]
// 获取集合的长度尺寸,也就是其中元素的个数
int size = list.size();
System.out.println("集合的长度是:" + size);
}
}
11.4.3 对集合中的内容进行遍历
import java.util.ArrayList;
public class Demo04ArrayListEach {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("迪丽热巴");
list.add("古力娜扎");
list.add("玛尔扎哈");
// 遍历集合
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
11.4.4 ArrayList类储存基本数据类型
ArrayList对象不能存储基本类型,只能存储引用类型的数据。类似 不能写,但是存储基本数据类型对应的 包装类型是可以的。所以,想要存储基本类型数据, <> 中的数据类型,必须转换后才能编写 ,集合里面保存的都是地址值,而基本类型的数据没有地址值,因此必须使用包装类
基本类型 包装类(引用类型,包装类都位于java.lang包下)
byte Byte
short Short
int Integer 【特殊】
long Long
float Float
double Double
char Character 【特殊】
boolean Boolean
从JDK 1.5+开始,支持自动装箱、自动拆箱。
自动装箱:基本类型 --> 包装类型
自动拆箱:包装类型 --> 基本类型
11.4.5 基本练习
import java.util.ArrayList;
/*
题目:
自定义4个学生对象,添加到集合,并遍历。
思路:
1. 自定义Student学生类,四个部分。
2. 创建一个集合,用来存储学生对象。泛型:<Student>
3. 根据类,创建4个学生对象。
4. 将4个学生对象添加到集合中:add
5. 遍历集合:for、size、get
*/
public class Demo02ArrayListStudent {
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList<>();
Student one = new Student("洪七公", 20);
Student two = new Student("欧阳锋", 21);
Student three = new Student("黄药师", 22);
Student four = new Student("段智兴", 23);
list.add(one);
list.add(two);
list.add(three);
list.add(four);
// 遍历集合
for (int i = 0; i < list.size(); i++) {
Student stu = list.get(i);
System.out.println("姓名:" + stu.getName() + ",年龄" + stu.getAge());
}
}
}
import java.util.ArrayList;
/*
题目:
定义以指定格式打印集合的方法(ArrayList类型作为参数),使用{}扩起集合,使用@分隔每个元素。
格式参照 {元素@元素@元素}。
System.out.println(list); [10, 20, 30]
printArrayList(list); {10@20@30}
*/
public class Demo03ArrayListPrint {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("张三丰");
list.add("宋远桥");
list.add("张无忌");
list.add("张翠山");
System.out.println(list); // [张三丰, 宋远桥, 张无忌, 张翠山]
printArrayList(list);
}
/*
定义方法的三要素
返回值类型:只是进行打印而已,没有运算,没有结果;所以用void
方法名称:printArrayList
参数列表:ArrayList
*/
public static void printArrayList(ArrayList<String> list) {
// {10@20@30}
System.out.print("{");
for (int i = 0; i < list.size(); i++) {
String name = list.get(i);
// 当遍历到最后一个元素的时候
if (i == list.size() - 1) {
System.out.println(name + "}");
} else {
System.out.print(name + "@");
}
}
}
}
import java.util.ArrayList;
import java.util.Random;
/*
题目:
用一个大集合存入20个随机数字,然后筛选其中的偶数元素,放到小集合当中。
要求使用自定义的方法来实现筛选。
分析:
1. 需要创建一个大集合,用来存储int数字:<Integer>
2. 随机数字就用Random nextInt
3. 循环20次,把随机数字放入大集合:for循环、add方法
4. 定义一个方法,用来进行筛选。
筛选:根据大集合,筛选符合要求的元素,得到小集合。
三要素
返回值类型:ArrayList小集合(里面元素个数不确定)
方法名称:getSmallList
参数列表:ArrayList大集合(装着20个随机数字)
5. 判断(if)是偶数:num % 2 == 0
6. 如果是偶数,就放到小集合当中,否则不放。
*/
public class Demo04ArrayListReturn {
public static void main(String[] args) {
// 定义一个大集合
ArrayList<Integer> bigList = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 20; i++) {
int num = r.nextInt(100) + 1; // 1~100
// 把产生的随机数放到大集合里面
bigList.add(num);
}
ArrayList<Integer> smallList = getSmallList(bigList);
System.out.println("偶数总共有多少个:" + smallList.size());
for (int i = 0; i < smallList.size(); i++) {
System.out.println(smallList.get(i));
}
}
// 这个方法,接收大集合参数,返回小集合结果
public static ArrayList<Integer> getSmallList(ArrayList<Integer> bigList) {
// 创建一个小集合,用来装偶数结果
ArrayList<Integer> smallList = new ArrayList<>();
for (int i = 0; i < bigList.size(); i++) {
int num = bigList.get(i);
if (num % 2 == 0) {
smallList.add(num);
}
}
return smallList;
}
}
11.5 String类
java.lang.String类代表字符串。
API当中说:Java 程序中的所有字符串字面值(如 “abc” )都作为此类的实例实现。
其实就是说:程序当中所有的双引号字符串,都是String类的对象。(就算没有new,也照样是。)
字符串的特点:
- 字符串的内容永不可变。【重点】
- 正是因为字符串不可改变,所以字符串是可以共享使用的。
- 字符串效果上相当于是char[]字符数组,但是底层原理是byte[]字节数组。
创建字符串的常见3+1种方式。
三种构造方法:
public String():创建一个空白字符串,不含有任何内容。
public String(char[] array):根据字符数组的内容,来创建对应的字符串。
public String(byte[] array):根据字节数组的内容,来创建对应的字符串。
一种直接创建:
String str = “Hello”; // 右边直接用双引号
注意:直接写上双引号,就是字符串对象。
public class Demo01String {
public static void main(String[] args) {
// 使用空参构造
String str1 = new String(); // 小括号留空,说明字符串什么内容都没有。
System.out.println("第1个字符串:" + str1); // 第一个字符串
// 根据字符数组创建字符串
char[] charArray = { 'A', 'B', 'C' };
String str2 = new String(charArray);
System.out.println("第2个字符串:" + str2); // 第二个字符串ABC
// 根据字节数组创建字符串
byte[] byteArray = { 97, 98, 99 };
String str3 = new String(byteArray);
System.out.println("第3个字符串:" + str3); // 第三个字符串abc
// 直接创建
String str4 = "Hello";
System.out.println("第4个字符串:" + str4); // 第四个字符串Hello
}
}
11.5.1 字符串常量池
字符串常量池:程序当中直接写上的双引号字符串,就在字符串常量池中。
例如: String str1 = “abc”; 就是直接用了双引号的字符串,也就是在常量池中进行了创建
对于基本类型来说,==
是进行数值的比较。
对于引用类型来说,==
是进行【地址值】的in
比较。
public class Demo02StringPool {
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
char[] charArray = {'a', 'b', 'c'};
String str3 = new String(charArray);
System.out.println(str1 == str2); // true 内存的地址值一样
System.out.println(str1 == str3); // false 内存的地址值不一样
System.out.println(str2 == str3); // false 内存的地址值不一样
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z9L6Mbyo-1612609897894)(E:\Typora笔记保存图片\Java\字符串的常量池.png)]
11.5.2 对字符串的内容进行比较
==是进行对象的地址值比较,如果确实需要字符串的内容比较,可以使用两个方法:
public boolean equals(Object obj):参数可以是任何对象,只有参数是一个字符串并且内容相同的才会给true;否则返回false。
注意事项:
**1.**任何对象都能用Object进行接收。
**2.**equals方法具有对称性,也就是a.equals(b)和b.equals(a)效果一样。
**3.**如果比较双方一个常量一个变量,推荐把常量字符串写在前面。
推荐:“abc”.equals(str) 不推荐:str.equals(“abc”)
public boolean equalsIgnoreCase(String str):忽略大小写,进行内容比较。
public class Demo01StringEquals {
public static void main(String[] args) {
String str1 = "Hello";
String str2 = "Hello";
char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String str3 = new String(charArray);
// 因为是对字符串的内容而不是对字符串的地址进行比较,所以都是true
System.out.println(str1.equals(str2)); // true
System.out.println(str2.equals(str3)); // true
System.out.println(str3.equals("Hello")); // true
System.out.println("Hello".equals(str1)); // true
String str4 = "hello";
// 因为是小写,所以字符串的值不一样,所以是false
System.out.println(str1.equals(str4)); // false
System.out.println("=================");
String str5 = null;
System.out.println("abc".equals(str5)); // 推荐:不会报异常,会报false
// System.out.println(str5.equals("abc")); // 不推荐:报错,空指针异常 NullPointerException
System.out.println("=================");
String strA = "Java";
String strB = "java";
System.out.println(strA.equals(strB)); // false,严格区分大小写
System.out.println(strA.equalsIgnoreCase(strB)); // true,忽略大小写
// 注意,只有英文字母区分大小写,其他都不区分大小写
System.out.println("abc一123".equalsIgnoreCase("abc壹123")); // false
}
}
11.5.3 与字符串获取相关的常用方法有
public int length():获取字符串当中含有的字符个数,拿到字符串长度。
public String concat(String str):将当前字符串和参数字符串拼接成为返回值新的字符串。
public char charAt(int index):获取指定索引位置的单个字符。(索引从0开始。)
public int indexOf(String str):查找参数字符串在本字符串当中首次出现的索引位置,如果没有返回-1值。
public class Demo02StringGet {
public static void main(String[] args) {
// 获取字符串的长度
int length = "asdasfeutrvauevbueyvb".length();
System.out.println("字符串的长度是:" + length);
// 拼接字符串
String str1 = "Hello";
String str2 = "World";
String str3 = str1.concat(str2);
System.out.println(str1); // Hello,原封不动
System.out.println(str2); // World,原封不动
System.out.println(str3); // HelloWorld,新的字符串
System.out.println("==============");
// 获取指定索引位置的单个字符
char ch = "Hello".charAt(1);
System.out.println("在1号索引位置的字符是:" + ch);
System.out.println("==============");
// 查找参数字符串在本来字符串当中出现的第一次索引位置
// 如果根本没有,返回-1值
String original = "HelloWorldHelloWorld";
int index = original.indexOf("llo");
System.out.println("第一次索引值是:" + index); // 2
System.out.println("HelloWorld".indexOf("abc")); // -1
}
}
11.5.4 字符串的截取方法
就是截取出了新的字符串也是产生了新的字符串,有了新的地址,原字符串不会有变化
public String substring(int index):截取从参数位置一直到字符串末尾,返回新字符串。
public String substring(int begin, int end):截取从begin开始,一直到end结束,中间的字符串。
备注:[begin,end),包含左边,不包含右边。
public class Demo03Substring {
public static void main(String[] args) {
String str1 = "HelloWorld";
String str2 = str1.substring(5);
System.out.println(str1); // HelloWorld,原封不动
System.out.println(str2); // World,此处是产生了一个新的字符串,有了新的地址
System.out.println("================");
String str3 = str1.substring(4, 7);
System.out.println(str3); // oWo
System.out.println("================");
// 下面这种写法,字符串的内容仍然是没有改变的
// 下面有两个字符串:"Hello","Java"
// strA当中保存的是地址值。
// 本来地址值是Hello的0x666,
// 后来地址值变成了Java的0x999
String strA = "Hello";
System.out.println(strA); // Hello
strA = "Java";
System.out.println(strA); // Java
}
}
11.5.5 字符串转换的常用方法
public char[] toCharArray():将当前字符串拆分成为字符数组作为返回值。
public byte[] getBytes():获得当前字符串底层的字节数组。
public String replace(CharSequence oldString, CharSequence newString):
将所有出现的老字符串替换成为新的字符串,返回替换之后的结果新字符串。
备注:CharSequence意思就是说可以接受字符串类型。
public class Demo04StringConvert {
public static void main(String[] args) {
// 转换成为字符数组
char[] chars = "Hello".toCharArray();
System.out.println(chars[0]); // H
System.out.println(chars.length); // 5
System.out.println("==============");
// 转换成为字节数组
byte[] bytes = "abc".getBytes();
for (int i = 0; i < bytes.length; i++) {
System.out.println(bytes[i]);
}
System.out.println("==============");
// 字符串的内容替换
String str1 = "How do you do?";
String str2 = str1.replace("o", "*");
System.out.println(str1); // How do you do?
System.out.println(str2); // H*w d* y*u d*?
System.out.println("==============");
String lang1 = "会不会玩儿呀!你大爷的!你大爷的!你大爷的!!!";
String lang2 = lang1.replace("你大爷的", "****");
System.out.println(lang2); // 会不会玩儿呀!****!****!****!!!
}
}
11.5.6 字符串的分割
分割字符串的方法:
public String[] split(String regex):按照参数的规则,将字符串切分成为若干部分。
注意事项:
split方法的参数其实是一个“正则表达式”,今后学习。
今天要注意:如果按照英文句点“.”进行切分,必须写"\."(两个反斜杠)
public class Demo05StringSplit {
public static void main(String[] args) {
String str1 = "aaa,bbb,ccc";
String[] array1 = str1.split(",");
for (int i = 0; i < array1.length; i++) {
System.out.println(array1[i]);
}
System.out.println("===============");
String str2 = "aaa bbb ccc";
// 按照空格来切割
String[] array2 = str2.split(" ");
for (int i = 0; i < array2.length; i++) {
System.out.println(array2[i]);
}
System.out.println("===============");
String str3 = "XXX.YYY.ZZZ";
// 如果按照英文句点“.”进行切分,必须写"\."(两个反斜杠)
String[] array3 = str3.split("\\.");
System.out.println(array3.length);
for (int i = 0; i < array3.length; i++) {
System.out.println(array3[i]);
}
}
}
11.5.7 相关练习
题目:
定义一个方法,把数组{1,2,3}按照指定格式拼接成一个字符串。格式参照如下:[word1#word2#word3]。
分析:
**1.**首先准备一个int[]数组,内容是:1、2、3
**2.**定义一个方法,用来将数组变成字符串
三要素
返回值类型:String
方法名称:fromArrayToString
参数列表:int[]
**3.**格式:[word1#word2#word3]
用到:for循环、字符串拼接、每个数组元素之前都有一个word字样、分隔使用的是#、区分一下是不是最后一个
**4.**调用方法,得到返回值,并打印结果字符串
public class Demo06StringPractise {
public static void main(String[] args) {
int[] array = {1, 2, 3, 4};
String result = fromArrayToString(array);
System.out.println(result);
}
public static String fromArrayToString(int[] array) {
String str = "[";
for (int i = 0; i < array.length; i++) {
if (i == array.length - 1) {
str += "word" + array[i] + "]";
} else {
str += "word" + array[i] + "#";
}
}
return str;
}
}
题目:
键盘输入一个字符串,并且统计其中各种字符出现的次数。
种类有:大写字母、小写字母、数字、其他
思路:
- 既然用到键盘输入,肯定是Scanner
- 键盘输入的是字符串,那么:String str = sc.next();
- 定义四个变量,分别代表四种字符各自的出现次数。
- 需要对字符串一个字、一个字检查,String–>char[],方法就是toCharArray()
- 遍历char[]字符数组,对当前字符的种类进行判断,并且用四个变量进行++动作。
- 打印输出四个变量,分别代表四种字符出现次数。
public class Demo07StringCount {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串:");
String input = sc.next(); // 获取键盘输入的一个字符串
int countUpper = 0; // 大写字母
int countLower = 0; // 小写字母
int countNumber = 0; // 数字
int countOther = 0; // 其他字符
char[] charArray = input.toCharArray();
for (int i = 0; i < charArray.length; i++) {
char ch = charArray[i]; // 当前单个字符
if ('A' <= ch && ch <= 'Z') {
countUpper++;
} else if ('a' <= ch && ch <= 'z') {
countLower++;
} else if ('0' <= ch && ch <= '9') {
countNumber++;
} else {
countOther++;
}
}
System.out.println("大写字母有:" + countUpper);
System.out.println("小写字母有:" + countLower);
System.out.println("数字有:" + countNumber);
System.out.println("其他字符有:" + countOther);
}
}
11.6 static类
static类的概述
11.6.1 static修饰变量(静态变量)
定义一个学生类
public class Student {
private int id; // 学号
private String name; // 姓名
private int age; // 年龄
static String room; // 所在教室
private static int idCounter = 0; // 学号计数器,每当new了一个新对象的时候,计数器++
public Student() {
this.id = ++idCounter; // 这一行写不写不会影响程序的执行
}
public Student(String name, int age) {
this.name = name;
this.age = age;
// 这个是前++,自己先增长一个数之后,再把值赋给id
// 起到计数作用,用来自动增加学生的学号
this.id = ++idCounter;
}
.........
调用这个学生类
public class Demo01StaticField {
public static void main(String[] args) {
Student two = new Student("黄蓉", 16);
// two对象添加的教室属性
two.room = "101教室";
System.out.println("姓名:" + two.getName()
+ ",年龄:" + two.getAge() + ",教室:" + two.room
+ ",学号:" + two.getId());
Student one = new Student("郭靖", 19);
System.out.println("姓名:" + one.getName()
// 虽然one对象没有添加教室属性,但是room用了static关键字,所以与one对象共享
+ ",年龄:" + one.getAge() + ",教室:" + one.room
+ ",学号:" + one.getId());
}
}
11.6.2 static修饰方法(静态方法)
一旦使用static修饰成员方法,那么这就成为了静态方法。静态方法不属于对象,而是属于类的。
如果没有static关键字,那么必须首先创建对象,然后通过对象才能使用它。
如果有了static关键字,那么不需要创建对象,直接就能通过类名称来使用它。
无论是成员变量,还是成员方法。如果有了static,都推荐使用类名称进行调用。
静态变量:类名称.静态变量
静态方法:类名称.静态方法()
注意事项:
- 静态不能直接访问非静态。
原因:因为在内存当中是【先】有的静态内容,【后】有的非静态内容。
“先人不知道后人,但是后人知道先人。” - 静态方法当中不能用this。
原因:this代表当前对象,通过谁调用的方法,谁就是当前对象。静态方法跟对象没有关系,因此不能用this关键字
定义一个类
public class MyClass {
int num; // 成员变量
static int numStatic; // 静态变量
// 成员方法
public void method() {
System.out.println("这是一个成员方法。");
// 成员方法可以访问成员变量
System.out.println(num);
// 成员方法可以访问静态变量
System.out.println(numStatic);
}
// 静态方法
public static void methodStatic() {
System.out.println("这是一个静态方法。");
// 静态方法可以访问静态变量
System.out.println(numStatic);
// 静态不能直接访问非静态【重点】
// System.out.println(num); // 错误写法!
// 静态方法中不能使用this关键字。
// System.out.println(this); // 错误写法!
}
}
调用这个类
public class Demo02StaticMethod {
public static void main(String[] args) {
MyClass obj = new MyClass(); // 首先创建对象
// 然后才能使用没有static关键字的内容
obj.method();
// 对于静态方法来说,可以通过对象名进行调用,也可以直接通过类名称来调用。
obj.methodStatic(); // 正确,不推荐,这种写法在编译之后也会被javac翻译成为“类名称.静态方法名”
MyClass.methodStatic(); // 正确,推荐
// 对于本类当中的静态方法,可以省略类名称
myMethod();
Demo02StaticMethod.myMethod(); // 完全等效
}
public static void myMethod() {
System.out.println("自己的方法!");
}
}
11.6.3 静态static的内存图
11.6.4 静态代码块
静态代码块的格式是:
public class 类名称 {
static {
// 静态代码块的内容
}
}
特点:当第一次用到本类时,静态代码块执行唯一的一次。
静态内容总是优先于非静态,所以静态代码块比构造方法先执行。
静态代码块的典型用途:
用来一次性地对静态成员变量进行赋值。
1.当我们使用到这个类的时候,JVM自动执行静态代码块
2.只会执行一次,而且在第一次使用到这个类的时候执行,第二次使用不会执行了
3.静态代码块的优先级,很高,比构造方法高,比main方法高
4.静态代码块什么用?
用来初始化类的,mysql数据(静态代码块来加载驱动)
定义一个Person类,类里面含有静态代码块
public class Person {
// 静态代码块优先于构造方法执行
static {
System.out.println("静态代码块执行!");
}
public Person() {
System.out.println("构造方法执行!");
}
}
调用Person类
public class Demo04Static {
public static void main(String[] args) {
Person one = new Person();
Person two = new Person();
// 以下是运行的结果
// 静态代码块先执行(这句话只出现了一次,说明静态代码块只执行了一次)
// 构造方法先执行
// 构造方法先执行
}
}
11.6.5 静态方法实现数组的相关操作
数组相关的工具类,里面提供了大量静态方法,用来实现数组常见的操作。
public static String toString(数组):将参数数组变成字符串(按照默认格式:[元素1, 元素2, 元素3…])
public static void sort(数组):按照默认升序(从小到大)对数组的元素进行排序。
备注:
- 如果是数值,sort默认按照升序从小到大
- 如果是字符串,sort默认按照字母升序
- 如果是自定义的类型,那么这个自定义的类需要有Comparable或者Comparator接口的支持。(今后学习)
public class Demo01Arrays {
public static void main(String[] args) {
int[] intArray = {10, 20, 30};
// 将int[]数组按照默认格式变成字符串
String intStr = Arrays.toString(intArray);
System.out.println(intStr); // [10, 20, 30]
int[] array1 = {2, 1, 3, 10, 6};
Arrays.sort(array1);
System.out.println(Arrays.toString(array1)); // [1, 2, 3, 6, 10]
String[] array2 = {"bbb", "aaa", "ccc"};
Arrays.sort(array2);
System.out.println(Arrays.toString(array2)); // [aaa, bbb, ccc]
}
}
java.util.Math类是数学相关的工具类,里面提供了大量的静态方法,完成与数学运算相关的操作。
public static double abs(double num):获取绝对值。有多种重载。
public static double ceil(double num):向上取整。
public static double floor(double num):向下取整。
public static long round(double num):四舍五入。
Math.PI代表近似的圆周率常量(double)。
public class Demo03Math {
public static void main(String[] args) {
// 获取绝对值
System.out.println(Math.abs(3.14)); // 3.14
System.out.println(Math.abs(0)); // 0
System.out.println(Math.abs(-2.5)); // 2.5
System.out.println("================");
// 向上取整
System.out.println(Math.ceil(3.9)); // 4.0
System.out.println(Math.ceil(3.1)); // 4.0
System.out.println(Math.ceil(3.0)); // 3.0
System.out.println("================");
// 向下取整,抹零
System.out.println(Math.floor(30.1)); // 30.0
System.out.println(Math.floor(30.9)); // 30.0
System.out.println(Math.floor(31.0)); // 31.0
System.out.println("================");
// 四舍五入
System.out.println(Math.round(20.4)); // 20
System.out.println(Math.round(10.5)); // 11
}
}
相关练习
/*
题目:
计算在-10.8到5.9之间,绝对值大于6或者小于2.1的整数有多少个?
分析:
1. 既然已经确定了范围,for循环
2. 起点位置-10.8应该转换成为-10,两种办法:
2.1 可以使用Math.ceil方法,向上(向正方向)取整
2.2 强转成为int,自动舍弃所有小数位
3. 每一个数字都是整数,所以步进表达式应该是num++,这样每次都是+1的。
4. 如何拿到绝对值:Math.abs方法。
5. 一旦发现了一个数字,需要让计数器++进行统计。
备注:如果使用Math.ceil方法,-10.8可以变成-10.0。注意double也是可以进行++的。
*/
public class Demo04MathPractise {
public static void main(String[] args) {
int count = 0; // 符合要求的数量
double min = -10.8;
double max = 5.9;
// 这样处理,变量i就是区间之内所有的整数
for (int i = (int) min; i < max; i++) {
int abs = Math.abs(i); // 绝对值
if (abs > 6 || abs < 2.1) {
System.out.println(i);
count++;
}
}
System.out.println("总共有:" + count); // 9
}
}
12. 抽象方法
计算图形面积的方法就是一个抽象方法,没有一个方法能计算出任意图形的面积
12.1 抽象方法的格式
抽象方法:就是加上abstract关键字,然后去掉大括号,直接分号结束。
抽象类:抽象方法所在的类,必须是抽象类才行。在class之前写上abstract即可。
如何使用抽象类和抽象方法:
- 不能直接创建new抽象类对象。
- 必须用一个子类来继承抽象父类。
- 子类必须覆盖重写抽象父类当中所有的抽象方法。
覆盖重写(实现):子类去掉抽象方法的abstract关键字,然后补上方法体大括号。 - 创建子类对象进行使用。
一个抽象类不一定含有抽象方法,
只要保证抽象方法所在的类是抽象类,即可。
这样没有抽象方法的抽象类,也不能直接创建对象,在一些特殊场景下有用途。
动物的父类
public abstract class Animal {
// 这是一个抽象方法,代表吃东西,但是具体吃什么(大括号的内容)不确定。
public abstract void eat();
// 这是普通的成员方法
public void normalMethod() {
......
}
}
猫的子类
public class Cat extends Animal {
@Override // 重写了抽象类方法
public void eat() {
System.out.println("猫吃鱼");
}
}
调用
public class DemoMain {
public static void main(String[] args) {
// Animal animal = new Animal(); // 错误写法!不能直接创建抽象类对象
Cat cat = new Cat();
cat.eat();
}
}
12.2 抽象方法的使用
// 最高的抽象父类
public abstract class Animal {
// 两个抽象方法
public abstract void eat();
public abstract void sleep();
}
// 因为仅仅重写了Animal的eat()方法,还有一个sleep()方法没有重写,所以子类也要是一个抽象类
public abstract class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
// public abstract void sleep();
}
// 下面的二哈和金毛都继承了狗类
public class Dog2Ha extends Dog {
@Override
public void sleep() {
System.out.println("嘿嘿嘿……");
}
}
public class DogGolden extends Dog {
@Override
public void sleep() {
System.out.println("呼呼呼……");
}
}
调用
public class DemoMain {
public static void main(String[] args) {
// Animal animal = new Animal(); // 错误!
// Dog dog = new Dog(); // 错误,这也是抽象类,因为并没有完全的重写所有的抽象方法
Dog2Ha ha = new Dog2Ha(); // 这是普通类,可以直接new对象。
ha.eat();
ha.sleep();
System.out.println("==========");
DogGolden golden = new DogGolden();
golden.eat();
golden.sleep();
}
}
// 程序运行的结果
狗吃骨头
嘿嘿嘿
==========
狗吃骨头
呼呼大睡
13. 接口
接口就是多个类的公共规范。
接口是一种引用数据类型,最重要的内容就是其中的:抽象方法。
如何定义一个接口的格式:
public interface 接口名称 {
// 接口内容
}
备注:换成了关键字interface之后,编译生成的字节码文件仍然是:.java --> .class。
如果是Java 7,那么接口中可以包含的内容有:
- 常量
- 抽象方法
如果是Java 8,还可以额外包含有:
- 默认方法
- 静态方法
如果是Java 9,还可以额外包含有:
- 私有方法
13.1 接口中的抽象方法
在任何版本的Java中,接口都能定义抽象方法。
格式:
public abstract 返回值类型 方法名称(参数列表);
注意事项:
- 接口当中的抽象方法,修饰符必须是两个固定的关键字:public abstract
- 这两个关键字修饰符,可以选择性地省略。(今天刚学,所以不推荐。)
- 方法的三要素,可以随意定义。
public interface MyInterfaceAbstract {
// 这是一个抽象方法
public abstract void methodAbs1();
// 这也是抽象方法
abstract void methodAbs2();
// 这也是抽象方法
public void methodAbs3();
// 这也是抽象方法
void methodAbs4();
}
使用
接口使用步骤:
-
接口不能直接使用,必须有一个“实现类”来“实现”该接口。
格式:public class 实现类名称 implements 接口名称 { // ... }
-
接口的实现类必须覆盖重写(实现)接口中所有的抽象方法。
实现:去掉abstract关键字,加上方法体大括号。 -
创建实现类的对象,进行使用。
注意事项:
如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自己就必须是抽象类。
实现类必须覆盖重写(实现)接口中所有的抽象方法
快速覆盖重写接口中抽象方法的小技巧
// 小建议:在给实现类起名字的时候,建议在实现类名字后面加上Impl,便于一眼就能看出来这是一个实现类
public class MyInterfaceAbstractImpl implements MyInterfaceAbstract {
@Override
public void methodAbs1() {
System.out.println("这是第一个方法!");
}
@Override
public void methodAbs2() {
System.out.println("这是第二个方法!");
}
@Override
public void methodAbs3() {
System.out.println("这是第三个方法!");
}
@Override
public void methodAbs4() {
System.out.println("这是第四个方法!");
}
}
创建实现类的对象,使用接口
public class Demo01Interface {
public static void main(String[] args) {
// 错误写法!不能直接new接口对象使用,应该new实现类,然后创建对象
// MyInterfaceAbstract inter = new MyInterfaceAbstract();
// 创建实现类的对象使用
MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl();
impl.methodAbs1(); // 这是第一个方法!
impl.methodAbs2(); // 这是第二个方法!
}
}
13.2 接口中的默认方法
默认方法产生的原因
实现类继承了接口,并重写了接口的抽象方法,并创建了实现类的对象,程序已经正常运行.如果这个时候,接口重新升级,又添加了好几个抽象方法,由于投入使用的实现类没有重写接口新添加的抽象方法,程序就会报错.
注意:默认方法会被实现类继承下去
从Java 8开始,接口里允许定义默认方法。
格式:
public default 返回值类型 方法名称(参数列表) {
// 方法体
}
备注:接口当中的默认方法,可以解决接口升级的问题。
定义一个接口,里面含有默认方法
public interface MyInterfaceDefault {
// 抽象方法
public abstract void methodAbs();
// 新添加了一个抽象方法
// public abstract void methodAbs2();
// 新添加的方法,改成默认方法,可以解决接口升级添加新功能的问题
public default void methodDefault() {
System.out.println("这是新添加的默认方法");
}
}
定义两个实现类,继承了接口
public class MyInterfaceDefaultA implements MyInterfaceDefault {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,AAA");
}
}
public class MyInterfaceDefaultB implements MyInterfaceDefault {
@Override
public void methodAbs() {
System.out.println("实现了抽象方法,BBB");
}
// 覆盖重写了接口的默认方法
@Override
public void methodDefault() {
System.out.println("实现类B覆盖重写了接口的默认方法");
}
}
- 接口的默认方法,可以通过接口实现类对象,直接调用。
- 接口的默认方法,也可以被接口实现类进行覆盖重写。
创建实现类对象,进行调用
public class Demo02Interface {
public static void main(String[] args) {
// 创建了实现类对象
MyInterfaceDefaultA a = new MyInterfaceDefaultA();
a.methodAbs(); // 调用抽象方法,实际运行的是右侧实现类。
// 调用默认方法,如果实现类当中没有,会向上找接口,找到了接口中的默认方法
a.methodDefault(); // 这是新添加的默认方法
System.out.println("==========");
MyInterfaceDefaultB b = new MyInterfaceDefaultB();
b.methodAbs();
b.methodDefault(); // 实现类B覆盖重写了接口的默认方法
}
}
13.3 接口中的静态方法
从Java 8开始,接口当中允许定义静态方法。
格式:
public static 返回值类型 方法名称(参数列表) {
方法体
}
提示:就是将abstract或者default换成static即可,带上方法体。
定义一个接口中的静态方法
public interface MyInterfaceStatic {
public static void methodStatic() {
System.out.println("这是接口的静态方法!");
}
}
静态方法直接由类名来调用,接口中的静态方法则由接口名调用,所以不用实现类去继承接口了
public class Demo03Interface {
public static void main(String[] args) {
// 创建了实现类对象
// MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
// 错误写法!因为直接用接口名去调用静态方法就可以
// impl.methodStatic();
// 直接通过接口名称调用静态方法
MyInterfaceStatic.methodStatic();
}
}
13.4 接口中的私有方法
问题描述:
我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。
但是这个共有方法不应该让实现类使用,只能让这两个默认方法只用,所以该是私有化的。
解决方案:
从Java 9开始,接口当中允许定义私有方法。
-
普通私有方法,解决多个默认方法之间重复代码问题
格式:private 返回值类型 方法名称(参数列表) { // 方法体 }
-
静态私有方法,解决多个静态方法之间重复代码问题
格式:private static 返回值类型 方法名称(参数列表) { // 方法体 }
定义一个接口
public interface MyInterfacePrivateA {
public default void methodDefault1() {
System.out.println("默认方法1");
methodCommon();
}
// 定义两个默认方法
public default void methodDefault2() {
System.out.println("默认方法2");
methodCommon();
}
// 上面两个默认方法都用到了下面的打印内容,因此通过一个方法把二者共有的方法抽取出来
// 抽取出来的下面的这个方法不能被实现类访问,因此加上了 private关键字
private void methodCommon() {
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
}
}
实现类去继承接口
public class MyInterfacePrivateAImpl implements MyInterfacePrivateA {
// 就算继承之后什么操作也没有也必须要继承,因为要通过实现类去创建对象调用接口中的方法
public void methodAnother() {
// 直接访问到了接口中的默认方法,这样是错误的!
// methodCommon();
}
}
调用
public class Demo04Interface {
public static void main(String[] args) {
MyInterfacePrivateAImpl a = new MyInterfacePrivateAImpl();
a.methodDefault1();
a.methodDefault2();
}
}
接口中的静态方法抽取,如果接口中的静态方法们有部分代码重复,需要用到下面的方法去抽取
public interface MyInterfacePrivateB {
public static void methodStatic1() {
System.out.println("静态方法1");
methodStaticCommon();
}
public static void methodStatic2() {
System.out.println("静态方法2");
methodStaticCommon();
}
// 抽取了静态方法的共有方法
private static void methodStaticCommon() {
System.out.println("AAA");
System.out.println("BBB");
System.out.println("CCC");
}
}
调用
public class Demo04Interface {
public static void main(String[] args) {
MyInterfacePrivateB.methodStatic1();
MyInterfacePrivateB.methodStatic2();
// 错误写法!因为抽取出来的公有部分已经被私有化了
// MyInterfacePrivateB.methodStaticCommon();
}
}
13.5 接口中的常量
接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。
从效果上看,这其实就是接口的【常量】。
格式:
public static final 数据类型 常量名称 = 数据值;
备注:
一旦使用final关键字进行修饰,说明不可改变。
注意事项:
- 接口当中的常量,可以省略public static final,注意:不写也照样是这样。
- 接口当中的常量,必须进行赋值;不能不赋值。
- 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
public interface MyInterfaceConst {
// 这其实就是一个常量,一旦赋值,不可以修改
// 成员变量定义好,不赋值的话,会有一个默认值存在的
public static final int NUM_OF_MY_CLASS = 12;
}
访问接口中的常量
public class Demo05Interface {
public static void main(String[] args) {
// 访问接口当中的常量(接口名称.常量名称)
System.out.println(MyInterfaceConst.NUM_OF_MY_CLASS);
}
}
##13.6 接口内容小结
在Java 9+版本中,接口的内容可以有:
1. 成员变量其实是常量,格式:
[public] [static] [final] 数据类型 常量名称 = 数据值;
注意:
常量必须进行赋值,而且一旦赋值不能改变。
常量名称完全大写,用下划线进行分隔。
2. 接口中最重要的就是抽象方法,格式:
[public] [abstract] 返回值类型 方法名称(参数列表);
注意:实现类必须覆盖重写接口所有的抽象方法,除非实现类是抽象类,也就是说实现类并没有完全的覆盖重写所有的抽象方法。
3. 从Java 8开始,接口里允许定义默认方法,格式:
[public] default 返回值类型 方法名称(参数列表) { 方法体 }
注意:默认方法也可以被覆盖重写
4. 从Java 8开始,接口里允许定义静态方法,格式:
[public] static 返回值类型 方法名称(参数列表) { 方法体 }
注意:应该通过接口名称进行调用,不能通过实现类对象调用接口静态方法
5. 从Java 9开始,接口里允许定义私有方法,格式:
普通私有方法:private 返回值类型 方法名称(参数列表) { 方法体 }
静态私有方法:private static 返回值类型 方法名称(参数列表) { 方法体 }
注意:private的方法只有接口自己才能调用,不能被实现类或别人使用。
13.7 接口使用的注意事项
使用接口的时候,需要注意:
**1.**接口是没有静态代码块或者构造方法的。
**2.**一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
格式:
public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
// 覆盖重写所有抽象方法
}
**3.**如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
**4.**如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类,实现类变成了抽象类。
**5.**如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写。
**6.**一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法。
在java中,继承优先于接口实现.
代码表示一
定义两个接口
public interface MyInterfaceA {
// 错误写法!接口不能有静态代码块
// static {
//
// }
// 错误写法!接口不能有构造方法
// public MyInterfaceA() {
//
// }
public abstract void methodA();
public abstract void methodAbs();
public default void methodDefault() {
System.out.println("默认方法AAA");
}
}
public interface MyInterfaceB {
// 定义两个抽象方法
public abstract void methodB();
public abstract void methodAbs();
// 定义一个默认方法
public default void methodDefault() {
System.out.println("默认方法BBB");
}
}
实现类同时继承两个接口并且对其中的抽象方法进行重写
public class MyInterfaceImpl /*extends Object*/ implements MyInterfaceA, MyInterfaceB {
@Override
public void methodA() {
System.out.println("覆盖重写了A方法"); // 接口A特有的抽象方法
}
@Override
public void methodB() {
System.out.println("覆盖重写了B方法"); // 接口B特有的抽象方法
}
@Override
public void methodAbs() {
// 存在的重复的抽象方法
System.out.println("覆盖重写了AB接口都有的抽象方法"); // 接口A和B都有的抽象方法
}
@Override
public void methodDefault() {
// 对接口A和B都有的同名默认方法进行了重写
System.out.println("对多个接口当中冲突的默认方法进行了覆盖重写");
}
}
如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类
public abstract class MyInterfaceAbstract implements MyInterfaceA, MyInterfaceB {
@Override
public void methodA() {
}
// 由于没有重写methodB(),所以该实现类必须要是一个抽象类
@Override
public void methodAbs() {
}
@Override
public void methodDefault() {
}
}
代码表示二
// 定义一个接口
public interface MyInterface {
public default void method() {
System.out.println("接口的默认方法");
}
}
// 父类方法
public class Fu {
public void method() {
System.out.println("父类方法");
}
}
// 子类方法,子类方法同时继承了接口和父类 (父类方法和接口中的默认方法重名)
public class Zi extends Fu implements MyInterface {
}
调用
public class Demo01Interface {
public static void main(String[] args) {
Zi zi = new Zi();
// 在重名的情况下,会优先使用父类的方法,而不是接口的方法
zi.method();
}
}
13.8 接口的继承
- 类与类之间是单继承的。直接父类只有一个。
- 类与接口之间是多实现的。一个类可以实现多个接口。
- 接口与接口之间是多继承的。
注意事项:
-
多个父接口当中的抽象方法如果重复,没关系。
-
多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】。
-
类与类之间是单继承的。直接父类只有一个。
-
类与接口之间是多实现的。一个类可以实现多个接口。
-
接口与接口之间是多继承的。
注意事项:
- 多个父接口当中的抽象方法如果重复,没关系。
- 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】。
定义两个子类接口
public interface MyInterfaceA {
public abstract void methodA();
public abstract void methodCommon();
public default void methodDefault() {
System.out.println("AAA");
}
}
// 两个子类都有着同名的抽象方法,有着同名的默认方法,抽象方法同名没关系,默认方法同名就要覆盖重写
public interface MyInterfaceB {
public abstract void methodB();
public abstract void methodCommon();
public default void methodDefault() {
System.out.println("BBB");
}
}
一个接口继承上面的两个接口
/*
这个子接口当中有几个方法?答:5个。
methodA 来源于接口A
methodB 来源于接口B
methodCommon 同时来源于接口A和B
method 来源于我自己
methodDefault()来源于对接口A,B的重写
*/
public interface MyInterface extends MyInterfaceA, MyInterfaceB {
public abstract void method();
@Override
// 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写
public default void methodDefault() {
}
}
实现类执行接口
public class MyInterfaceImpl implements MyInterface {
@Override
public void method() {
}
@Override
public void methodA() {
}
@Override
public void methodB() {
}
@Override
public void methodCommon() {
}
}
13.9 综合案例-笔记本使用USB设备
实现思路: 1.先定义一个USB接口,接口里面放上USB接口都有的抽象类 2.然后定义鼠标和键盘的实现类去实现USB接口中的抽象方法,然后再另外实现鼠标和键盘所特有的点击和输入功能 3. 然后定义一个电脑类,电脑类有自己的开关机方法,还有调用USB设备的功能.
定义一个USB接口
public interface USB {
// 设备的打开和关闭是USB设备的基本功能
public abstract void open(); // 打开设备
public abstract void close(); // 关闭设备
}
定义一个鼠标和键盘的实现类去实现USB接口
// 鼠标就是一个USB设备
public class Mouse implements USB {
@Override
public void open() {
System.out.println("打开鼠标");
}
@Override
public void close() {
System.out.println("关闭鼠标");
}
// 鼠标所特有的点击方法
public void click() {
System.out.println("鼠标点击");
}
}
// 键盘就是一个USB设备
public class Keyboard implements USB {
@Override
public void open() {
System.out.println("打开键盘");
}
@Override
public void close() {
System.out.println("关闭键盘");
}
// 键盘所特有的输入方法
public void type() {
System.out.println("键盘输入");
}
}
定义一个电脑类,电脑类有自己的开关机方法,还有使用USB设备的方法
public class Computer {
public void powerOn() {
System.out.println("笔记本电脑开机");
}
public void powerOff() {
System.out.println("笔记本电脑关机");
}
// 使用USB设备的方法,使用接口作为方法的参数
public void useDevice(USB usb) {
usb.open(); // 打开设备
// 因为要实现鼠标和键盘所特有的方法,所以要将USB向下转型
if (usb instanceof Mouse) { // 一定要先判断
Mouse mouse = (Mouse) usb; // 向下转型
mouse.click();
} else if (usb instanceof Keyboard) { // 先判断
Keyboard keyboard = (Keyboard) usb; // 向下转型
keyboard.type();
}
usb.close(); // 关闭设备
}
}
调用电脑类
public class DemoMain {
public static void main(String[] args) {
// 首先创建一个笔记本电脑
Computer computer = new Computer();
computer.powerOn();
// 准备一个鼠标,供电脑使用
// Mouse mouse = new Mouse();
// 首先进行向上转型
USB usbMouse = new Mouse(); // 多态写法
// 参数是USB类型,我正好传递进去的就是USB鼠标
computer.useDevice(usbMouse);
// 创建一个USB键盘
Keyboard keyboard = new Keyboard(); // 没有使用多态写法
// 方法参数是USB类型,传递进去的是实现类对象
computer.useDevice(keyboard); // 正确写法!也发生了向上转型
// 使用子类对象,匿名对象,也可以
// computer.useDevice(new Keyboard()); // 也是正确写法
computer.powerOff();
System.out.println("==================");
// 此注释以下的代码都是为了演示转型而存在的
method(10.0); // 正确写法,double --> double
method(20); // 正确写法,int --> double
int a = 30;
method(a); // 正确写法,int --> double
}
public static void method(double num) {
System.out.println(num);
}
}
14. final关键字的用法
final关键字代表最终、不可改变的。
常见四种用法:
- 可以用来修饰一个类
- 可以用来修饰一个方法
- 还可以用来修饰一个局部变量
- 还可以用来修饰一个成员变量
14.1 修饰一个类
当final关键字用来修饰一个类的时候,格式:
public final class 类名称 {
// ...
}
含义:当前这个类不能有任何的子类。(太监类)
注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写(因为没儿子。)
public final class MyClass /*extends Object*/ {
public void method() {
System.out.println("方法执行!");
}
}
// 不能使用一个final类来作为父类,由于MyClass加上了final关键字,因此下面的类无法作为子类去继承MyClass
public class MySubClass /*extends MyClass*/ {
}
14.2 修饰一个方法
当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。
格式:
修饰符 final 返回值类型 方法名称(参数列表) {
// 方法体
}
注意事项:
对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾。
// 定义一个父类(抽象的)
public abstract class Fu {
// 父类中的method()方法被final修饰,父类可以被继承,但是method()方法没法被重写
public final void method() {
System.out.println("父类方法执行!");
}
// final要求不能重写,而abstract要求必须重写,因此二者矛盾,所以不能同时使用
public abstract /*final*/ void methodAbs() ;
}
// 定义一个子类去继承父类
public class Zi extends Fu {
@Override
public void methodAbs() {
}
// 错误写法!不能覆盖重写父类当中final的方法
// @Override
// public void method() {
// System.out.println("子类覆盖重写父类的方法!");
// }
}
14.3 修饰局部变量
先定义一个学生的标准类
public class Student {
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Demo01Final {
public static void main(String[] args) {
int num1 = 10;
System.out.println(num1); // 10
num1 = 20;
System.out.println(num1); // 20
// 一旦使用final用来修饰局部变量,那么这个变量就不能进行更改。
// “一次赋值,终生不变”
final int num2 = 200;
System.out.println(num2); // 200
// num2 = 250; // 错误写法!不能改变!
// num2 = 200; // 错误写法!
// 正确写法!只要保证有唯一一次赋值即可
final int num3;
num3 = 30;
// 对于基本类型来说,不可变说的是变量当中的数据不可改变
// 对于引用类型来说,不可变说的是变量当中的地址值不可改变
Student stu1 = new Student("赵丽颖");
System.out.println(stu1);
System.out.println(stu1.getName()); // 赵丽颖
stu1 = new Student("霍建华");
System.out.println(stu1);
System.out.println(stu1.getName()); // 霍建华
System.out.println("===============");
final Student stu2 = new Student("高圆圆");
// 错误写法!final的引用类型变量,其中的地址不可改变
// stu2 = new Student("赵又廷"); 这种做法相当于给stu2添加了新的地址
System.out.println(stu2.getName()); // 高圆圆
stu2.setName("高圆圆圆圆圆圆"); // stu2的地址还是原来的地址,但是内容变了,相当于房子还是在原来的地址上,但是房子里住的人已经变了
System.out.println(stu2.getName()); // 高圆圆圆圆圆圆
}
}
14.4 修饰成员变量
对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变。
- 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
- 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
- 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
public class Person {
// 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了
private final String name/* = "鹿晗"*/;
// 当用不带参数的构造方法的时候,被final修饰的成员变量的值是: name = '关晓彤'
public Person() {
name = "关晓彤";
}
// 当用带参数的构造方法的时候,被final修饰的成员变量的值是: name = 传入的值
public Person(String name) {
this.name = name;
}
// 可以随意的获取成员变量的值
public String getName() {
return name;
}
// 由于被final修饰,所以无法进行setName的设置了
// public void setName(String name) {
// this.name = name;
// }
}
15.java中的权限修饰符
Java中有四种权限修饰符:
public > protected > (default) > private
// 同一个类中自己访问自己用什么修饰符都可以访问
同一个类(我自己) YES YES YES YES
// 同一个包中,我用 private 关键字的时候,邻居访问不到
同一个包(我邻居) YES YES YES NO
// 不是同一个包,我用 (default) 和 private 关键字的时候,我的子类访问不到
不同包子类(我儿子) YES YES NO NO
// 不是同一个包,而且两个类之间没有任何关系的时候,只有用 public 关键字,陌生人才能访问到
不同包非子类(陌生人) YES NO NO NO
注意事项:(default)并不是关键字“default”,而是根本不写。
16.内部类
如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。
例如:身体和心脏的关系。又如:汽车和发动机的关系。
分类:
- 成员内部类
- 局部内部类(包含匿名内部类)
成员内部类的定义格式:
修饰符 class 外部类名称 {
修饰符 class 内部类名称 {
// ...
}
// ...
}
注意:内用外,随意访问;外用内,需要内部类对象。
16.1 成员内部类的使用
如何使用成员内部类?有两种方式:
- 间接方式:在外部类的方法当中,使用内部类;然后main只是调用外部类的方法。
- 直接方式,公式:
类名称 对象名 = new 类名称();
【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】
public class Body { // 外部类
// 成员内部类
public class Heart {
// 内部类的方法
public void beat() {
System.out.println("心脏跳动:蹦蹦蹦!");
System.out.println("我叫:" + name); // 正确写法!
}
}
// 外部类的成员变量
private String name;
// 外部类的方法
public void methodBody() {
System.out.println("外部类的方法");
// 外部类通过匿名函数的方式间接的调用了内部类
new Heart().beat();
}
// 外部类的get和set
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
调用
public class Demo01InnerClass {
public static void main(String[] args) {
Body body = new Body(); // 外部类的对象
// 通过外部类的对象,调用外部类的方法,里面间接在使用内部类Heart
body.methodBody();
System.out.println("=====================");
// 按照公式写,直接调用内部类
Body.Heart heart = new Body().new Heart();
heart.beat();
}
}
16.1.1 成员内部类的同名变量访问
// 如果出现了重名现象,那么格式是:外部类名称.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); // 外部类的成员变量
}
}
}
调用
public class Demo02InnerClass {
public static void main(String[] args) {
// 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
Outer.Inner obj = new Outer().new Inner();
obj.methodInner();
}
}
// 打印结果
30
20
10
16.2 局部内部类
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。
定义格式:
修饰符 class 外部类名称 {
修饰符 返回值类型 外部类方法名称(参数列表) {
class 局部内部类名称 {
// ...
}
}
}
小节一下类的权限修饰符:
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
- 外部类:public / (default)
- 成员内部类:public / protected / (default) / private
- 局部内部类:什么都不能写
class Outer {
// 类里面定义一个方法,方法里面再包含一个类,这个类就是局部内部类
public void methodOuter() {
class Inner { // 局部内部类
int num = 10;
public void methodInner() {
System.out.println(num); // 10
}
}
// 在方法里面把局部内部类的对象给创建好,就可以在外边访问到局部内部类里面的内容了
Inner inner = new Inner();
inner.methodInner();
}
}
调用
public class DemoMain {
public static void main(String[] args) {
Outer obj = new Outer();
// 通过调用类的方法就可以访问到局部内部类
obj.methodOuter();
}
}
16.2.1 局部内部类访问所在方法的局部变量
public class MyOuter {
public void methodOuter() {
int num = 10; // 所在方法的局部变量
// 方法中的局部内部类
class MyInner {
public void methodInner() {
// 局部内部类访问所在方法的局部变量
System.out.println(num);
}
}
}
}
16.3 匿名内部类(局部内部类中的)(重点)
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,
那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。
匿名内部类的定义格式:
接口名称 对象名 = new 接口名称() {
// 覆盖重写所有抽象方法
};
对格式“new 接口名称() {…}”进行解析:
- new代表创建对象的动作
- 接口名称就是匿名内部类需要实现哪个接口
- {…}这才是匿名内部类的内容
另外还要注意几点问题:
- 匿名内部类,在【创建对象】的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。 - 匿名对象,在【调用方法】的时候,只能调用唯一一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。 - 匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
强调:匿名内部类和匿名对象不是一回事!!!
// 定义一个接口
public interface MyInterface {
void method1(); // 抽象方法
void method2();
}
public class DemoMain {
public static void main(String[] args) {
// 使用匿名内部类,但不是匿名对象,对象名称就叫objA
// 就是直接在接口后面加上{},{}里面写上覆盖重写抽象方法的内容
MyInterface objA = new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-A");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-A");
}
};
objA.method1();
objA.method2();
System.out.println("=================");
// 使用了匿名内部类,而且省略了对象名称,也是匿名对象
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method1();
// 因为匿名对象无法调用第二次方法,所以需要再创建一个匿名内部类的匿名对象
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method2();
}
}
17. 引用类型用法总结
17.1 类作为成员变量
定义一个武器类
public class Weapon {
private String code; // 武器的代号
public Weapon() {
}
public Weapon(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
定义一个英雄类
// 游戏当中的英雄角色类
public class Hero {
private String name; // 英雄的名字
private int age; // 英雄的年龄
private Weapon weapon; // 英雄的武器(使用类作为成员变量的名字)
public Hero() {
}
public Hero(String name, int age, Weapon weapon) {
this.name = name;
this.age = age;
this.weapon = weapon;
}
// 定义一个英雄攻击敌人的方法
public void attack() {
System.out.println("年龄为" + age + "的" + name + "用" + weapon.getCode() + "攻击敌方。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
调用这个类
public class DemoMain {
public static void main(String[] args) {
// 创建一个英雄角色
Hero hero = new Hero();
// 为英雄起一个名字,并且设置年龄
hero.setName("盖伦");
hero.setAge(20);
// 创建一个武器对象
Weapon weapon = new Weapon("AK-47");
// 为英雄配备武器
hero.setWeapon(weapon);
// 年龄为20的盖伦用多兰剑攻击敌方。
hero.attack();
}
}
17.2 接口作为成员变量
// 定义一个接口
public interface Skill {
void use(); // 释放技能的抽象方法
}
// 实现类重写接口
public class SkillImpl implements Skill {
@Override
public void use() {
System.out.println("Biu~biu~biu~");
}
}
// 定义一个英雄类
public class Hero {
private String name; // 英雄的名称
private Skill skill; // 英雄的技能
public Hero() {
}
public Hero(String name, Skill skill) {
this.name = name;
this.skill = skill;
}
public void attack() {
System.out.println("我叫" + name + ",开始施放技能:");
skill.use(); // 调用接口中的抽象方法
System.out.println("施放技能完成。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Skill getSkill() {
return skill;
}
public void setSkill(Skill skill) {
this.skill = skill;
}
}
// 调用类
public class DemoGame {
public static void main(String[] args) {
Hero hero = new Hero();
hero.setName("艾希"); // 设置英雄的名称
// 设置英雄技能
// hero.setSkill(new SkillImpl()); // 使用单独定义的实现类
// 还可以改成使用匿名内部类
// Skill skill = new Skill() {
// @Override
// public void use() {
// System.out.println("Pia~pia~pia~");
// }
// };
// hero.setSkill(skill);
// 进一步简化,同时使用匿名内部类和匿名对象
hero.setSkill(new Skill() {
@Override
public void use() {
System.out.println("Biu~Pia~Biu~Pia~");
}
});
// 调用英雄的攻击方法
hero.attack();
}
}
17.3 接口作为参数和返回值
import java.util.ArrayList;
import java.util.List;
/*
java.util.List正是ArrayList所实现的接口。
*/
public class DemoInterface {
public static void main(String[] args) {
// 左边是接口名称,右边是实现类名称,这就是多态写法
List<String> list = new ArrayList<>();
List<String> result = addNames(list);
for (int i = 0; i < result.size(); i++) {
System.out.println(result.get(i));
}
}
public static List<String> addNames(List<String> list) {
list.add("迪丽热巴");
list.add("古力娜扎");
list.add("玛尔扎哈");
list.add("沙扬娜拉");
return list;
}
}
18. 部分知识总结
1. static;***
什么时候用static来修饰成员
当这个成员不属于某一个对象,而是属于所有对象,或者属于这个类,那么需要用static来修饰它
被static修饰的成员,有什么特点?
i.存在方法区中的静态区,只有一个空间
ii.静态是优先于对象存在
iii.如何访问静态成员
对象名.静态成员; // 这是可以的,但是不建议
类名.静态成员; // 建议用的
*2.final:
final修饰类(太监类): 不能被继承(没有子类),但是可以是其他类的子类
final修饰方法(最终方法,牛逼方法):子类不能重写
final修饰成员变量:
a.这个成员变量在创建对象之前必须初始化(直接赋值,在构造方法中赋值)
b.只能赋值一次
final修饰局部变量:
a.基本类型:
该变量只能赋值一次(实际上就是常量)
b.引用类型
该引用类型的变量保存的地址不能改变
但是地址指向空间中的内存可以改变
final Person p = new Person();
p = new Person();//错误的
p.name = "";//正确的
p.age = 18;//正确的
3.包:
导包的关键字: import
全限定类名: 包名.类名 (可以不用导包) 后期说反射,用到全限定类名
4.权限修饰符
public protected 不写(default) private
如果一个成员只想在本类中使用 用private修饰
如果一个成员只想在本类中和本包的其他类中使用 不写修饰
如果一个成员只想在本类中使用和本包中使用或者其他包的子类中使用 用protected修饰
如果一个成员想在所有类中都可以使用 用public修饰
5.内部类:
根据内部类定义位置不同的分类:
a.成员内部类:定义类中方法外
在其他类中如何创建该类的对象
固定格式:
外部类名.内部类名 变量名 = new 外部类().new 内部类();
b.局部内部类:定义方法中
在其他类中,是无法使用
只能局部位置使用
6.静态代码块:
代码块,在成员位置 使用大括号括起来的一堆代码
public class Person {
//静态代码块
static{
代码块
}
public void show(){
}
}
特点:
1.当我们使用到一个类时,那么这类中的静态代码块自动执行
2.只会执行一次,第一次使用该类,就立刻执行
3.在同一个类中,静态代码块的优先级很高,比构造方法优先,比main方法优先
4.作用: 用来初始化类中某些功能(mysql的数据库驱动的加载)
7.匿名内部类******
a.作用:
快速创建 抽象类的子类对象,接口的实现类对象.
b.格式:
第一种:
new 抽象类(){
//重写抽象类中所有的方法
};
第二种
new 抽象类(){
//重写抽象类中所有的方法
}.重写后的方法();
第三种
接口类型 变量名 = new 接口(){
//重写接口中所有的方法
};