个人学习笔记记录 | 课程参考:b站黑马程序员
了解Java
- 特性:可移植性 | 企业级开发
- 技术体系:JaveSE 标准版:核心和基础 | JaveEE 企业版:互联网企业级解决方案,充分被市场认可 | JavaME 小型版:移动端
DOS
Disk Operating System 磁盘操作系统 通过win+R
输入cmd
进入
- 常用命令
md
:创建文件夹cd
:进入文件夹cd..
:回上一级目录cd\
:回根目录
rd
:删除文件夹。 ps:文件夹中不能有内容,只能递归删除del
:删除文件。ps:模糊匹配可删除该格式的所有文件,比如del *.txt
删除了所有txt格式的文件。dir
:查询当前文件夹下有哪些文件夹cls
:清屏exit
:关闭cmd界面D:
:切盘
开发程序 Hello World
流程:编写 --> 编译 -->运行
要求:后缀为java | 类名与代码文件名称相同
public class HelloWorld{ //产生class 文件
public static void main(String[] args){
System.out.println("Hello World");
}
}
最重要的程序:Javac
- 编译程序 | Java
- 执行程序
执行原理
编程语言发展历史:机器–>汇编–>高级
原理:翻译成计算机底层可识别的机器语言(0和1)
JDK的组成(产品)、跨平台原理
-
Java Development Kit:Java程序开发工具包:必须安装它才可以使用Java
JDK 8 11 17 - LTS版本
-
Java Runtime Environment:Java程序运行时的环境
-
Java Virtual Machine:Java虚拟机,真正运行Java程序的地方
-
关系:
- JDK = JRE + 开发工具集(如Javac编译工具Javac和执行工具Java)
- JRE = JVM + Java SE标准类库(核心类库)(API)
什么是JVM,JRE,JDK?
JVM是Java虚拟机,是真正运行Java程序的地方;JVM加上Java SE标准类库或者也叫核心类库就组成了JRE,这是Java程序运行时的环境;JRE加上开发工具,比如编译工具Javac和执行工具Java就组成了我们常用的JDK,Javac程序开发工具包
跨平台:一次编译、处处可用,我们的程序只需要开发一次,就可以在各种安装了JVM的系统平台上。
Path环境变量和Java_HOME设置
Path的作用:记住程序的路径,方便在命令行窗口的任意目录驱动程序
新版本JDK安装时会自动配置javac和java的路径直接到Path环境变量中去,但老版本时需要自动配置(届时自己上网搜一下手动配置的教程)
字面量
告诉程序员,数据在程序中的书写格式
package com.yjy.literal;
public class LiteralDemo {
public static void main(String[] args) {
// 整数
System.out.println("6");
// 小数
System.out.println("12.04");
// 字符:必须用单引号,有且只能有一个字符
System.out.println('你');
System.out.println(' '); //空字符
//特殊的字符: \n 换行 \t 一个tab
System.out.println('\n');
System.out.println('好');
//字符串:必须是双引号
System.out.println("你好");
//布尔值 true false
System.out.println(true);
}
}
标识符与关键字
-
类名 、变量名都是标识符。要求:数字、字母、下划线_、美元符$ 不能以数字开头、不能以关键字作为名字、区分大小写
建议:类:大驼峰;变量:小驼峰 -
有一些标识符,Java 语言已经赋予了其特殊的含义,只能用于特定的地方,这些特殊的标识符就是关键字 。简单来说,关键字是被赋予特殊含义的标识符 。比如,在我们的日常生活中,如果我们想要开一家店,则要给这个店起一个名字,起的这个“名字”就叫标识符。但是我们店的名字不能叫“医院”,因为“医院”这个名字已经被赋予了特殊的含义,而“医院”就是我们日常生活中的关键字。
-
Tips:所有的关键字都是小写的,在 IDE 中会以特殊颜色显示。default这个关键字很特殊,既属于程序控制,也属于类,方法和变量修饰符,还属于访问控制。在程序控制中,当在 switch 中匹配不到任何情况时,可以使用 default 来编写默认匹配的情况。在访问控制中,如果一个方法前没有任何修饰符,则默认会有一个修饰符 default,但是这个修饰符加上了就会报错。⚠️ true, false, 和 null 不是关键字,是字面值,也不可以作为标识符来使用。
变量
存储原理
二进制:0和1 8421
数据的最小单元:字节,byte,简称B,8位(bit,简称b)一组,不满8位的前面补零
字符:ASCII编码表
图片:无数个像素点,用0~255X255X255表示其颜色,存储的是每个像素点的RGB三个值的二进制
声音:脉冲调制PWM波,最终还是映射到二进制
八大数据类型
byte b = 127; //在内存中占8位 (1个字节)
short s = 13244;
int i = 23; // 在内存中占32位 (4个字节)
long l = 1234567894561234569L; // 整形字面量过大(超过int范围)时,需要在末尾加上 L 或 l,定义为long型
float f = 3.14F; // 小数字面量默认是double类型,若需要成为float类型则需要在末尾加上 F 或 f
double d = 3.1415;
char c = '耶'; // 只能存一个字符哦~ 2个字节,C语言里面占1个
boolean flag = true;
String name = "开心"; //字符串类型,定义的变量用于记住一个字符串数据,这是一种引用数据类型
类型转换
-
自动:类型范围小的变量,可以直接赋值给类型范围大的。
byte -> short -> int -> long -> float -> double
char -> int -> long -> float -> double -
表达式的自动类型转换:
表达式的最终结果由表达式的最高类型决定;
在表达式中,
byte
、short
、char
是直接转换成int
类型参与计算的。int a = 10; long b = 30; double c = a + b + 1.0; //最高类型是1.0 double型 byte i = 10; short j = 20; int k = i + j; //最高类型是int型 byte x = 10; byte y = 80; int b1 + b2; //转换成int进行运算了!
-
强制类型转换:大范围怎么转小范围?
数据类型 变量2 = (数据类型)变量1
// 在IDEA中的快捷键:ALT + ENTERint a = 20; byte b = (byte)a; // 把a的后8位数据直接给b
tips: 可能出现数据丢失。比如小数->整数:直接将小数部分丢掉
运算符
基本运算符
+
:加法 | 连接符 ; 能算就算,不能算就连在一起int a = 9; int b = 2; System.out.println("abc" + a);//"abc9" ,不能算 System.out.println(a + b);//10,能算 System.out.println("y" + a + 'a');//y9a,不能算 System.out.println(a + 'a' + "y");//106y,前面能做ASCII码运算,后面不能算
-
*
/
:两个整数相除结果还是整数int a = 9; int b = 2; System.out.println(a/b); //输出:4, 而不是 4.5 System.out.println(1.0 * a/b); //如果一定要得到小数,在前面乘1.0
%
:取余,获取的是两个数据做除法的余数
自增自减
++
--
:对变量加1或者减1,只能操作变量,不能操作字面量 |++i
先加后用,i++
先用后加int m = 2; int n = 9; int result = ++m - --m + m++ - ++n - n-- + 1; System.out.println(result); // -16
赋值运算符
=
+=
:数据累加,把别人的数据加给自己:a += b
等价于a = a + b
,且包含强制类型转换:a = (a的数据类型) a + b
-=
*=
/=
%=
:同上
关系运算符
>
>=
<
<=
==
!=
:判断某个数据是否符合条件,返回Boolean类型的值
逻辑运算符
&
:与,都真才真|
:或,一真就真!
:非,取反,你真我假
!(2>1)
结果是false^
:异或,前后条件相同为假,不同为真
1>2^3>1
结果是true
开发中更常用的,效率更高的方法:
&&
:短路与(双与),与&
的结果相同,但是执行速度较快,因为它的底层逻辑是判断左边的表达式如果为false
就直接返回false
了,不再判断右边的语句||
:短路或(双或),与|
的结果相同,但是过程也不同,左边为true
就直接返回true
了
int m = 2;
int n = 9;
//双与
System.out.println(m > 9 && ++n > 2); //false
System.out.println(n); //9,因为右边表达式没执行
//单与
System.out.println(m > 9 & ++n > 2);//false
System.out.println(n);//10,因为右边表达式执行了
三元运算符
-
条件表达式 ? 值1:值2
:判断条件表达式的真假,如果为true
返回值1
,false
返回值2
//应用1 寻找2个数中的最大值 int a = 2; int b = 1; System.out.println( a > b ? a : b); //2 //应用2 寻找3个数中的最大值 int c = 9; int temp = a > b ? a : b; System.out.println(c > temp ? c : temp); //9
优先级
在表达式中,运算符存在优先级,要是自己开发,不确定优先级的话就加个()
,括号内的内容肯定是优先执行的
注意:&&
的高于||
流程控制
顺序结构,一步一步
分支结构,有选择地
if
根据条件来选择执行某段程序,有三种写法
// 需求1:测量用户体温,发现体温高于37度就报警。
double t = 36.9;
if(t > 37){
System.out.println("这个人的温度异常,把他赶紧带走~~");
}
// 需求2:发红包,你的钱包余额是99元,现在要发出90元, 如果钱够触发发红包的动作,如果钱不够,则提示:余额不足。
double money = 19;
if(money >= 90){
System.out.println("发红包成功了~");
}else {
System.out.println("余额不足~~");
}
// 需求3:某个公司有一个绩效系统,根据员工的打分输出对应的绩效级别。[0,60) D [60,80) C [80,90) B [90,100] A
int score = 298;
if(score >= 0 && score < 60) {
System.out.println("您的绩效级别是: D");
}else if(score >= 60 && score < 80){
System.out.println("您的绩效级别是: C");
}else if(score >= 80 && score < 90){
System.out.println("您的绩效级别是: B");
}else if(score >= 90 && score <= 100){
System.out.println("您的绩效级别是: A");
}else {
System.out.println("您录入的分数有毛病~~");
}
switch
比较表达式与case
中的哪个值一样,就执行哪段程序
// 周一:埋头苦干,解决bug 周五:今晚吃鸡
// 周二: 请求大牛程序员帮忙 周六:与王婆介绍的小芳相亲
// 周三:今晚啤酒、龙虾、小烧烤 周日:郁郁寡欢、准备上班。
// 周四:主动帮助新来的女程序解决bug
String week = "周三";
switch (week){
case "周一":
System.out.println("埋头苦干,解决bug");
break;
case "周二":
System.out.println("请求大牛程序员帮忙");
break;
case "周三":
System.out.println("今晚啤酒、龙虾、小烧烤");
break;
case "周四":
System.out.println("主动帮助新来的女程序解决bug");
break;
case "周五":
System.out.println("今晚吃鸡");
break;
default: //其他情况
System.out.println("您输入的星期信息不存在~~~");
}
//输出:今晚啤酒、龙虾、小烧烤
- 注意事项:
-
表达式类型只能是
byte
short
int
char
,JDK5后开始支持枚举
,JDK7后支持String
,不支持double
(运算时不精确)float
long
-
case
给出的值不允许重复,且只能是字面量,不能是变量。 -
正常使用switch的时候,不要忘记写break,否则会出现穿透现象。但有时穿透性可以简化代码
String week = "周三"; switch (week){ case "周一": System.out.println("埋头苦干,解决bug"); break; case "周二": System.out.println("请求大牛程序员帮忙"); break; case "周三": System.out.println("今晚啤酒、龙虾、小烧烤"); case "周四": System.out.println("主动帮助新来的女程序解决bug"); break; default: //其他情况 System.out.println("您输入的星期信息不存在~~~"); } /*输出:今晚啤酒、龙虾、小烧烤 主动帮助新来的女程序解决bug 原因:周三语句没有写break,所以穿透到下一条语句了 */
但有时穿透性可以简化代码:当存在多个
case
分支的代码是一样时,可以把代码写到一个case块String week = "周三"; switch (week){ case "周一": System.out.println("埋头苦干,解决bug"); break; case "周二": case "周三": case "周四": System.out.println("请求大牛程序员帮忙"); break; default: System.out.println("您输入的星期信息不存在~~~"); } 输出:今晚啤酒、龙虾、小烧烤
比较与开发建议
if
在功能上比switch
强大:switch
能做的if
一定能做
区间使用if
,一个一个值比较使用switch
(格式良好,性能优雅)
循环结构,不断重复
for
for(int i = 0; i < 3; i++) { // i = 0 1 2
System.out.println("Hello World");
}
while
int i = 0;
while (i < 5) { // i = 0 1 2 3 4
System.out.println("Hello World");
i++;
}
example - 需求:世界最高山峰珠穆朗玛峰高度是: 8848.86米=8848860毫米,假如我有一张足够大的纸,它的厚度是0.1毫米。请问:该纸张折叠多少次,可以折成珠穆朗玛峰的高度?
double paper = 0.1; // 纸张的厚度
int count = 0; // 折叠的次数
while (paper < 8848860){
paper = paper * 2;
count ++;
}
do-while
特点:先执行后判断,比如抢票
int i = 0;
do{
System.out.println("Hello World");
i++;
}while (i < 3);
比较与开发建议
for
while
功能是完全一样的,是先判断后执行;而do while
是先执行后判断- 知道循环几次:使用
for
;不知道循环几次:使用while
- 在
for
循环中,控制循环的变量只在for
内部使用,在while
循环中,控制循环的变量在循环后还可以使用
死循环
应用场景:服务器程序,比如打开一个百度服务器,它会一直运行等待用户操作
for ( ; ; ){
System.out.println("Hello World1");
}
while (true) {
System.out.println("Hello World2");
}
do {
System.out.println("Hello World3");
}while (true);
循环嵌套
// 打印星星4行40列星星
for (int i = 1; i <= 4; i++) {
// i = 1 2 3
// 定义一个循环控制每行打印多少列星星。
for (int j = 1; j <= 40; j++) {
System.out.print("*"); // 不换行
}
System.out.println(); // 换行
}
跳转关键字
break
跳出并结束当前所在循环的执行;只能用于结束所在循环,或者结束所在switch
分支的执行
// 场景:假如你有老婆了,你犯错了,你老婆罚你说:5句我爱你
// 说到第三句的时候心软了,让你别再说了。
for (int i = 1; i <= 5; i++) {
System.out.println("我爱你:" + i);
if(i == 3){
// 说明已经说完了第三句了,心软了。
break; // 跳出并结束当前所在循环的执行。
}
}
continue
跳出当前循环的当次执行,直接进入循环的下一次执行;只能在循环中使用
// 场景: 假如你有老婆,你犯错了,你老婆罚你洗碗5天。
// 第三天的时候,你表现很好,第三天不用洗碗,但是不解恨,第四天还是要继续的。
for (int i = 1; i <= 5; i++) {
if(i == 3) {
// 已经到了第三天,第三天不用洗的。
continue;
}
System.out.println("洗碗:" + i);
}
数组
静态数组
完整格式:数据类型[] 数组名 = new 数据类型[]{元素1,元素2 ,元素3… };
简化格式:数据类型[] 数组名 = { 元素1,元素2 ,元素3,… }
也可写成 数据类型 数组名[] = { 元素1,元素2 ,元素3,… }
int[] arr = new int[]{2,1,9}; // 完整
int[] arr = {2,1,9};// 简化
String[] names = {"victoria","kristal","amber"};
-
访问数组的下标是从0开始的,比如
names[0] = victoria
-
什么类型的数组只能存放什么类型的数据。
-
数组变量名中存储的是数组在内存中的地址,数组是一种引用数据类型
-
数组的长度属性length:获取length:
System.out.println(数组名.length);
,那么数组的最大索引表示为**length -1 **(前提是元素个数大于0)如果超出数组索引,执行程序出bug:Exception in thread “main” java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
-
遍历:一个一个数据的访问;在IDEA中的快捷键
ages.fori
回车补全int[] ages = {12, 24, 36}; // 0 1 2 for (int i = 0; i < ages.length; i++) { System.out.println(ages[i]); }
应用:求和
int[] arr = {2,1,9}; int sum = 0; for (int i = 0; i < arr.length; i++){ sum += arr[i]; } System.out.println(sum); }
动态数组
定义数组时先不存入具体的元素值,只确定数组存储的数据类型和数组的长度。
格式:数据类型[] 数组名 = new 数据类型[长度];
注意:一定别跟静态数组混合了,比如int[] arr = new int [3]{3,1,7};
是不对的
int[] dongtaiArr = new int[3]; // 先定义
dongtaiArr[0] = 2; // 后附值
- 元素默认值规则
数据类型 | 默认值 |
---|---|
byte\short\char\int\long | 0 |
float\double | 0.0 |
boolean | false |
类\接口\数组\String | null |
char[] arr = new char[3];
System.out.println(arr[0]);//一个奇怪的0
System.out.println((int)arr[0]);//0
double[] scores = new double[6]; //应用:求录入评委评分的平均数
Scanner score = new Scanner(System.in);
double sum = 0.0;
for(int i = 0; i < scores.length; i++){
System.out.println("请输入第" + (i+1) + "位评委的成绩");
scores[i] = score.nextDouble();
sum += scores[i];
}
System.out.println("你的平均得分是:" + sum/6);
执行原理
运行一个Java程序,主要是将.class
放在内存中的JVM
中运行,主要需要关注JVM
的三个区域:方法区、栈内存、堆内存
-
方法区
-
放程序编译后的
.class
文件 -
main
-
-
栈内存
方法运行时所进入的内存(把
main
放在这里),变量也存储在这里。普通变量直接存储变量值,引用类型的变量存储地址 -
堆内存
new
出来的东西会在这块内存中开辟空间并产生地址
简单说说int a = 20; int[] arr = new int[3]
这两行代码的原理
a
是普通变量,其值为20直接存储在栈内存中new int[3]
是创建一个数组对象,在堆内存中开辟三个连续空间存储3个整数arr
是变量,在栈内存中存储了arr
的数组对象在堆内存中的地址
多个变量指向同一个数组
当多个变量指向同一个数组时(多个变量存储的是同一个数组对象的地址),如果其中一个变量修改了数组对象里的内容,那么其他的变量指向的值也更改了
int[] arr1 = {11, 22, 33};
int[] arr2 = arr1; // 把int类型的数组变量arr1赋值给int类型的数组变量arr2
System.out.println(arr1); //[I@4eec7777
System.out.println(arr2); //[I@4eec7777 , 同一个地址
arr2[1] = 99; // 修改其中一个变量的数组对象内容
System.out.println(arr1[1]); // 99 ; 对应的其他的变量指向的值也更改了
arr2 = null; // 拿到的数组变量中存储的值是null
System.out.println(arr2); // arr2的地址变成null,不指向任何数组对象;
System.out.println(arr2[0]); //此时再访问arr2中的元素报错:Exception in thread "main" java.lang.NullPointerException: Cannot read the array length because "arr2" is null
方法
定义
方法是一种语法结构,它可以把一段代码封装成一个功能,以便重复调用
格式:
修饰符(public static)
返回值类型
方法名
(形参列表
){
方法体代码(需要执行的功能代码)
return 返回值
}
public static void main(String[] args) {
int x = sum(2,9);
System.out.println(x);
}
public static int sum(int a, int b){ //定义求和的方法
int c = a + b;
return c;
}
Tips:
-
一个方法不能定义在另一个方法的里面,比如必须写在
main
函数的外面,放前放后无所谓。 -
方法不调用就不会执行
-
return
后面的代码无效,写了也没用 -
方法如果申明了具体的返回值类型,内部必须用
return
返回对应类型的数据;如果类型的void
则无需有返回值,且一定不能写返回值 -
形参列表可以有多个,也可以没有。多个形参必须用逗号隔开;
-
按照方法解决的实际业务需求不同,设计出合理的方法形式来解决问题:考虑两个问题
-
是否需要接收数据处理(即是否需要有形参)?
-
是否需要返回数据(即是否需要
return
一个具体返回值类型)?- 无–> 使用
void
,不写return
;只能直接调用 - 有–> 使用其他数据类型,必须写
return
;可以直接调用、定义变量接收结果、直接输出调用
sum(2,9); //直接调用 int x = sum(2,9); //定义变量接收结果 System.out.println(sum(2,9)) //直接输出调用
- 无–> 使用
-
方法要处理的业务(编程能力)
-
执行原理
方法被调用的时候是进入到栈内存(先进后出)中运行的,保证一个方法调用完另一个方法后可以回来
参数传递机制
基本类型的参数传递
值传递:在传输实参给方法形参的时候,传输的是实参变量中存储的值的副本。
public static void main(String[] args) {
int a = 10; // 实参,在方法内部定义的变量
change(a); // change(10);
System.out.println("main:" + a); // 10
}
public static void change(int a){ //这里的a是形参,是指方法定义时的变量
System.out.println("change1:" + a); // 10
a = 20; // 实参,在方法内部定义的变量
System.out.println("change2:" + a); // 20
}
引用类型的参数传递
值传递:但是注意传进去的是地址的副本,如果改变会有影响
public static void main(String[] args) {
int[] arrs = new int[]{10, 20, 30};
change(arrs); //传进去的是地址 [I@58372a00
System.out.println("main:" + arrs[1]); //222
}
public static void change(int[] arrs){
System.out.println("方法内1:" + arrs[1]); //20
arrs[1] = 222; //改变了[I@58372a00地址值指向的堆内存中的内容
System.out.println("方法内2:" + arrs[1]); //222
}
补充知识
方法重载(Overload)
一个类中,出现多个方法的名称
相同,但它们的形参列表
是不同的(个数、类型、顺序有一个不同就算不同,形参的名称不同,但是这三个都相同也不行),就称为方法重载。只要名称相同
就是方法重载,其他都不管(比如修饰符
返回值类型
是否一样都无所谓)
单独使用return关键字
在无返回值的方法中,使用return
可以立即跳出并结束当前方法方法的执行。
面向对象编程
面向对象编程(Object-Oriented Programming,简称 OOP)是一种编程范式,它使用 “对象” 来设计软件和创建可重用的代码,每个对象都是一个特定类的实例。
定义类:定义变量 | 定义方法
public class Student {
// 成员变量(对象的属性)
String name;
double chinese;
double math;
// 成员方法(对象的行为)
public void printTotalScore(){
System.out.println(name + "的总成绩是:" + (chinese + math));
}
public void printAverageScore(){
System.out.println(name + "的平均成绩是:" + (chinese + math) / 2.0);
}
}
创建类:输入变量 | 调用方法
public static void main(String[] args) {
Student s1 = new Student();// 1、创建一个学生对象,封装播妞的数据
s1.name = "播妞";
s1.chinese = 100;
s1.math = 100;
s1.printTotalScore();
s1.printAverageScore();
}
-
为什么要用面向对象编程:符合人类思维习惯,编程更简单
-
对象到底是什么:特殊的数据结构,就是一张表。
-
class
也就是类,是对象的设计图(或者对象的模板):1. 变量-说明对象可以处理哪些数据,即表中有哪些字段 | 2.方法-描述对象有说明功能,即可以对这些数据进行什么样的处理
执行原理
注意事项
- 类名建议英文首字母大写
- 成员变量存在默认值,定义时一般不需要赋初始值
- 一个代码文件中,可以写多个
class
类,但只有一个能用public
修饰,且用public
修饰的类名必须成为代码文件名 - 对象1和对象2之间的数据不会互相影响,但多个变量指向同一个对象时就会互相影响,比如
Student s2 = s1
后 修改s2.name = 李四
,再打印s1.name
的结果会是:李四 - 如果某个对象(在堆内存中)没有一个变量(在栈内存中)引用它,则该对象就是垃圾对象,无法操作。Java存在自动垃圾回收机制,会自动清除掉垃圾对象
this 关键字
-
this
是一个引用变量,可以用在方法中,来拿到当前对象。谁调用它,里面存的就是谁的地址。当类的成员变量与局部变量重名时,我们可以使用 this 来区分它们。此外,this 还可以用于在一个构造函数中调用另一个构造函数。 -
用途:解决对象的成员变量与方法内部变量名称冲突问题
public class Student { String name; double score; public void test(Student this){ System.out.println(this); } public void printPass(double score){ if(this.score >= score){ // 这里解决了score变量名称冲突的问题,this.score就是对象 不然的话score >= score就不对了 System.out.println("恭喜您,您成功考入了哈佛大学~~"); }else { System.out.println("很遗憾,您没有考过~~"); } } }
Student s3 = new Student(); s3.name = "播仔"; s3.score = 2; s3.printPass(256);
构造器
-
方法
的名称和对象
的名称是一样的,这时候的方法
就不叫方法
了,而是叫构造器
。 -
特点:创建对象的时候,对象就会调用构造器
-
应用场景:创建对象时,同时完成对对象成员变量(属性)的初始化赋值
public class Student { String name; double score; // 无参数构造器 public Student(){ System.out.println("无参数构造器被触发执行了~"); //在创建对象时如果是 Student s1 = new Student();就会执行这个方法 } // 有参数构造器 public Student(String name, double score){ System.out.println("有参数构造器被触发执行了~~"); //在创建对象时如果是 Student s1 = new Student(小美,10.0);就会执行这个方法,类似于方法重构 this.name = name; //完成初始化赋值 this.score = score; } }
注意事项:
- 设计类时,不写构造器,Java会自动生成一个无参构造器
- 如果定义了有参数构造器,Java就不会生成无参构造器了,若此时还需要就手写一个无参构造器 (最好是每次都写上!)
封装
- 用类设计对象处理某一个事物的数据时,应该把要处理的数据,和处理这些数据的方法,设计到一个对象中去
- 设计规范:合理隐藏(
private
) | 合理暴露(public
)
public class Student { // 让外部能够合理访问score并合理赋值,而不会赋一些奇怪的值,安全可靠
private double score; //私有变量,外部无法访问
public void setScore(double score){ //但可以通过这个公有方法间接赋值
if(score >= 0 && score <= 100){
this.score = score;
}
else {
System.out.println("数据非法!你输入的成绩应该在0~100区间");
}
}
public double getScore(){ //也可以通过这个公有方法间接取值
return this.score;
}
}
实体JavaBean(实体类)
- 一种特殊的类
- 成员变量都要私有,且向外提供相应的
getXxx
方法和setXxx
方法 - 类中必须要有一个公共的无参的构造器
- 成员变量都要私有,且向外提供相应的
public class Student {
private String name;// 1、必须私有成员变量,并为每个成员变量都提供get set方法
private double score;
public Student() { // 2、必须为类提供一个公开的无参数构造器
}
public Student(String name, double score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
}
- IDEA快捷键:
右键
-->生成
-->构造函数
orgetter and setter
- 特点:仅仅是一个用来保存数据的Java类,不能用它去干其他事情了
- why do this? 将保存数据和处理数据分割成两个Java类来保存,比如一个是
Student
,一个是StudentOperator
public class StudentOperator {
private Student student;
public StudentOperator(Student student){
this.student = student;
}
public void printPass(){
if(student.getScore() >= 60){
System.out.println(student.getName() + "学生成绩及格");
}else {
System.out.println(student.getName() + "学生成绩不及格");
}
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student();//保存数据
s1.setName("播妞");
s1.setScore(99);
System.out.println(s1.getName());
System.out.println(s1.getScore());
StudentOperator operator = new StudentOperator(s1);//处理数据
operator.printPass();
}
}
对象数组
对象也可以用数组的方式操作,比如定义了一个Movie类
private int id;
private String name;
private double price;
若想要打印所有Movie
对象的中的某个值,它的操作类应该这样写:
public class MovieOperator {
private Movie[] movies;
public MovieOperator(Movie[] movies){ //这里用到了[]
this.movies = movies;
}
public void printAllMovies(){
System.out.println("-----系统全部电影信息如下:-------");
for (int i = 0; i < movies.length; i++) {
Movie m = movies[i];
System.out.println("编号:" + m.getId());
System.out.println("名称:" + m.getName());
System.out.println("价格:" + m.getPrice());
System.out.println("------------------------");
}
}
而主程序中录入Movie
类的方式也可以用[]
形式
public static void main(String[] args) {
Movie[] movies = new Movie[4];//这里用到了[]
movies[0] = new Movie(1,"水门桥", 38.9);
movies[1] = new Movie(2, "出拳吧", 39);
movies[2] = new Movie(3,"月球陨落", 42);
movies[3] = new Movie(4,"一点就到家", 35);
MovieOperator operator = new MovieOperator(movies);
operator.printAllMovies();
}
成员变量和局部变量的区别
成员变量 | 局部变量 | |
---|---|---|
类中的位置不同 | 类中,方法外 | 方法中 |
初始值不同 | 有默认值,无需初始化 | 无默认值,使用之前必须赋值 |
内存位置不同 | 堆内存 | 栈内存 |
作用域不同 | 整个对象 | 在所归属的大括号中 |
生命周期不同 | 与对象同生共死 | 方法调用而生,方法结束而死 |
继承
👉extends
关键字:子类能够继承父类的非私有成员(成员变量、成员方法)
👉public class B extends A
:A
是父类(基类、超类),B
是子类(派生类)
继承后对象的创建:子类的对象是由子类、父类两张设计图共同完成的(创建一个对象后,对象里面包含子类和父类的所有属性),但是子类只能调用公有的成员。
权限修饰符
public
(公有的)、private
(私有的),protected
(受保护的)、缺省
(不写任何修饰符)。
是用来限制类中的成员(成员变量、成员方法、构造器、代码块…)能够被访问的范围。
修饰符 | 本类里 | 同一个包中的类 | 子孙类 | 任意类 |
---|---|---|---|---|
private | √ | |||
缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
单继承
一个子类可以继承多个父类吗? 答案是 :dengdengdeng~NO!
- Java语言只支持单继承,不支持多继承,但是可以多层继承。就像家族里儿子、爸爸和爷爷的关系一样:一个儿子只能有一个爸爸,不能有多个爸爸,但是爸爸也是有爸爸的。
Object
类是Java中所有类的祖宗。
方法重写
当子类
觉得父类
中的某个方法
不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。一句话总结:声明不变,重新实现
-
注意:重写后,方法的访问,Java会遵循就近原则
-
重写小tips:使用
Override
注解,他可以指定java编译器,检查我们方法重写的格式是否正确(更安全),代码可读性也会更好。比如说如果想重写父类的方法print1
,但是写成了pritn1
,此时重写是不会成功的,会报错:Method does not override method from its superclass -
子类
重写父类
方法时,访问权限必须大于或者等于父类该方法的权限( public > protected > 缺省 )public class A { protected void print1(){ //这里的权限修饰符是protected System.out.println("111"); } }
public class B extends A{ //A的子类B @Override public void print1(){ //那么这里的修饰符只能是public或者protected 不能是缺省 System.out.println("666"); } }
-
重写的方法
返回值类型
,必须与被重写方法的返回值类型一样,或者范围更小(后面解释) -
私有方法
、静态方法
不能被重写,如果重写会报错的。
就近原则
public class F { //定义一个父类
String name = "父类名字";
public void print1(){
System.out.println("==父类的print1方法执行==");
}
}
public class Z extends F { //子类有一个同名的name成员变量,有一个同名的print1成员方法;
String name = "子类名称";
public void showName(){
String name = "局部名称";
System.out.println(name); // 局部名称
}
@Override
public void print1(){
System.out.println("==子类的print1方法执行了=");
}
public void showMethod(){
print1(); // 子类的
}
}
public class Test {
public static void main(String[] args) {
Z z = new Z();
z.showName(); // 局部名称
z.showMethod(); // "==子类的print1方法执行了="
}
}
- 如果子类和父类出现同名变量或者方法,优先使用子类的;此时如果一定要在子类中使用父类的成员,可以加
this
或者super
进行区分
public class Z extends F {
String name = "子类名称";
public void showName(){
String name = "局部名称";
System.out.println(name); // 局部名称
System.out.println(this.name); // 子类成员变量
System.out.println(super.name); // 父类的成员变量
}
@Override
public void print1(){
System.out.println("==子类的print1方法执行了=");
}
public void showMethod(){
print1(); // 子类的
super.print1(); // 父类的
}
}
顺序:先子类局部返回找(函数内部) --> 再子类成员范围找 --> 父类成员范围找
子类中访问构造器的特点
- 子类全部构造器,都会先调用父类构造器,再执行自己。
class F {
public F() {
System.out.println("父类F | 无参构造器");
}
}
class Z extends F {
public Z() {
// super();
System.out.println("子类Z | 无参构造器");
}
public Z(int id){
System.out.println("子类Z | 有参构造器");
}
}
public class Test {
public static void main(String[] args) {
Z z1 = new Z();
System.out.println("------------------");
Z z2 = new Z(20);
}
}
结果:
为什么会这样?在子类的构造器函数中,默认第一行有个super();
代码
-
如果想调用父类的有参构造器怎么办:在子类的
super()
中手写出父类有参构造器的参数class F { public F(String name) { System.out.println("父类F | 有参构造器");}} class Z extends F { public Z() { super("yjy"); System.out.println("子类Z | 无参构造器"); }} public class Test { public static void main(String[] args) { Z z1 = new Z();}} //父类F | 有参构造器 (换行) 子类Z | 无参构造器
👉应用场景
-
完成两张设计图有参构造器的融合;具体来说,子类构造器可以通过调用父类构造器,把对象中包含父类这部分数据先初始化赋值,再回来把对象里包含子类这部分的数据也进行初始化赋值
public class Test { public static void main(String[] args) { Teacher t = new Teacher("张艳",33,"语文");//三个参数的构造器 System.out.println(t.getName()); //张艳 System.out.println(t.getAge()); //33 System.out.println(t.getSkill()); //语文 } } class Teacher extends People{ private String skill; public Teacher(String name, int age, String skill){ super(name, age); //用这一行,调用了People类中2个参数的构造器 this.skill = skill; //构造自己的skill参数 } //getter & setter } class People { private String name; private int age; public People() { } public People(String name, int age) { this.name = name; this.age = age; } //getter & setter }
-
通过
this()
调用兄弟构造器
👉小tips:在一个构造函数里,不能既有this()
又有super()
,因为都需要放在构造器的第一行
👉总结
访问本类成员:
this.成员变量 //访问本类成员变量
this.成员方法 //调用本类成员方法
this() //调用本类空参数构造器
this(参数) //调用本类有参数构造器
访问父类成员:
super.成员变量 //访问父类成员变量
super.成员方法 //调用父类成员方法
super() //调用父类空参数构造器
super(参数) //调用父类有参数构造器
final 关键字
定义:最终的意思,可以修饰(类、方法、变量)
-
修饰类:该类被称为最终类,特点是不能被继承了。
final class A{} class B extends A{} //报错 无法从final A继承
-
修饰方法:该方法被称为最终方法,特点是不能被重写了。
class A{ public final void test(){ System.out.println("a");} } class B extends A{ @Override public void test(){// 报错 无法重写A中的test(),因为该方法为final System.out.println("b"); }}
-
修饰变量:该变量只能被赋值一次。
- final修饰基本类型的变量,变量存储的数据不能被改变。
- final修饰引用类型的变量,变量存储的地址不能被改变,但地址所指向对象的内容是可以被改变的。
final int id = 5; id = 4; //报错 无法将值赋给final变量id final int[] arr = {2,1,9}; arr = null; //这里会报错,因为算是第二次给arr赋值 arr[1] = 200; //这里不会报错,可以修改数组元素
常量
定义:使用了 static final
修饰的成员变量就被称为常量
-
作用:通常用于记录系统的配置信息。
-
命名规范:大写英文单词,多个单词使用下划线连接起来
public class Constant { public static final String SCHOOL_NAME = "中南大学"; }
-
好处:代码可读性,可维护性更好
-
原理:程序编译后,常量会被“宏替换”:出现常量的地方全部会被替换成其记住的字面量,这样可以保证使用常量和直接用字面量的性能是一样的。
多态
定义:多态是在继承、实现情况下的一种现象,表现为:对象多态、行为多态
- Tip:成员变量没有“多态”的说法:
- 变量:编译看左边, 运行看左边
- 对象和方法:编译看左边, 运行看右边
public class People {
String name = "父类"; // 成员变量
public void run(){
System.out.println("人会跑"); // 行为
}}
public class Teacher extends People{ //对象
String name = "老师";// 成员变量
@Override
public void run() {
System.out.println("老师跑的比较慢~~~"); // 行为
}}
public class Student extends People{
String name = "学生";
@Override
public void run() {
System.out.println("学生跑的飞快~~~");
}}
public class Test {
public static void main(String[] args) {
People p1 = new Teacher(); //父类变量接收了子类对象 -【对象多态】
System.out.println(p1.name);
p1.run();// p1 p2都可以调用run方法,但是方法表现的行为不一样 - 【行为多态】
People p2 = new Student();
System.out.println(p2.name);
p2.run();
}}
多态的好处
-
在多态形式下,右边对象是解耦合的,更便于扩展和维护(紧耦合,牵一发而动全身)
- 比如
People p1 = new Student();
右边的Student
对象可以随时任意切换
- 比如
-
可以使用父类类型的变量作为形参,可以接收一切子类对象
public class Test2 { public static void main(String[] args) { Teacher t = new Teacher(); go(t); Student s = new Student(); go(s); } //参数People p既可以接收Student对象,也能接收Teacher对象。 public static void go(People p){ System.out.println("开始------------------------"); p.run(); System.out.println("结束------------------------"); }}
产生的问题:
-
多态下不能使用子类的独有功能
比如在Teacher类中多了一个teach方法,在Student类中多了一个study方法,这两个方法在多态形式下是不能直接调用的。
-
解决方法:类型转换 -> 把父类变量转换为子类类型。
类型转换
-
自动类型转换:
父类 变量名 = new 子类();
i.e.People p = new Teacher();
,这就是上面用的方法 -
强制类型转换:
子类 变量名 = (子类) 父类变量;
i.e.Teacher p = (Teacher) People
注意:原来是什么类型,才能强制还原成什么类型
小tips:在转换之前写一个
if
语句,加上一个instanceof
关键字进行判断if(父类变量 instanceof 子类){ 子类 变量名 = (子类)父类变量; } // i.e. People p = new Student(); p.study();//会报错 // but if(p instanceof Student){ Student s = (Student)p; s.study();//不会报错 }
用这个关键字还可以很好的防止类型转换错误的异常,比如
People p = new Teacher(); Student s = (Student)p; s.study();// 会报错,ClassCastException。因为p原来的子类是teacher子类,将它强转为student子类会报错 //但此时加上if 和 instanceof判断,不会报错 if(p instanceof Student){ Student s = (Student)p; s.study();//不会报错 }else { Teacher t = (Student)p; t.teach(); }
抽象类
abstract 关键字
可以用abstract
修饰类或方法,格式:
修饰符 abstract class 类名{
修饰符 abstract 返回值类型 方法名称(形参列表);
}
public abstract class A {
public abstract void test();// 抽象方法:必须abstract修饰,只有方法签名,不能有方法体。
}
-
tips:
-
抽象类
中不一定有抽象方法
,有抽象方法
的类一定是抽象类
-
类该有的成员(成员变量、方法、构造器)抽象类都可以有
public abstract class A { // 抽象类 //成员变量 private String name; static String schoolName; //构造方法 public A(){ } //抽象方法 public abstract void test(); //实例方法 public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
抽象类最主要的特点:抽象类不能创建对象,仅作为一种特殊的父类,让子类继承并实现
A a = new A(); //会报错
-
一个类继承抽象类,必须重写完抽象类的全部抽象方法,否则这个类也必须定义成抽象类
public class B extends A { @Override public void test() { //B类继承A类,必须复写test方法 } } // 子类继承父类如果不复写父类的抽象方法,要想不出错,这个子类也必须是抽象类 // 当B类也是抽象类继承A时,就可以不重写A类的抽象方法 public abstract class B extends A { }
-
好处
1.用抽象类可以把父类中相同的代码,包括方法声明都抽取到父类,这样能更好的支持多态,一提高代码的灵活性。
2.反过来用,我们不知道系统未来具体的业务实现时,我们可以先定义抽象类,将来让子类去实现,以方便系统的扩展。
案例:某宠物游戏,需要管理猫、狗的数据。猫的数据有:名字;行为是:喵喵喵的叫~ | 狗的数据有:名字;行为是:汪汪汪的叫~
分析:多个类中只要有重复代码(包括相同的方法签名),我们都应该抽取到父类中去,此时,父类中就有可能存在只有方法签名的方法,这时,父类必定是一个抽象类了,我们抽出这样的抽象类,就是为了更好的支持多态。
public abstract class Animal {
private String name;
public abstract void cry();//动物叫的行为:不具体,是抽象的
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
接着写一个Animal的子类,Dog类。代码如下
public class Dog extends Animal{
public void cry(){
System.out.println(getName() + "汪汪汪的叫~~");
}
}
然后,再写一个Animal的子类,Cat类。代码如下
public class Cat extends Animal{
public void cry(){
System.out.println(getName() + "喵喵喵的叫~~");
}
}
最后,再写一个测试类,Test类。
public class Test2 {
public static void main(String[] args) {
Animal a = new Dog();
a.cry(); //这时执行的是Dog类的cry方法
}
}
再学一招,假设现在系统有需要加一个Pig类,也有叫的行为,这时候也很容易原有功能扩展。只需要让Pig类继承Animal,复写cry方法就行。
public class Pig extends Animal{
@Override
public void cry() {
System.out.println(getName() + "嚯嚯嚯~~~");
}
}
此时,创建对象时,让Animal接收Pig,就可以执行Pig的cry方法
public class Test2 {
public static void main(String[] args) {
Animal a = new Pig();
a.cry(); //这时执行的是Pig类的cry方法
}
}
包
包是分门别类管理程序的,类似于文件夹,建包有利于程序的管理和维护,语法格式如下:
package com.vk.javapackage; //建包
public class Hello{
}
在自己程序中调用其他包的程序的注意事项:
- 同一个包下的程序,可以直接访问
- 访问其他包下的程序,必须导包才可以使用:
package com.vk.Test;
import com.vk.javapackage //导包
public static void main(String[], args){
Hello h1 = new Hello();
}
IDEA自动导包过程:
- 自己的程序中调用Java提供的程序,也需要导包才可以使用,不过
Java.lang
包下的程序是不需要我们导包的,可以直接使用,比如String
或System
就不用,但是Random
要 - 如果有两个路径(
A
和B
)下,包名一样(比如都叫Pkg
),程序名也一样(比如都叫print
),在调用print
程序的时候,只能在头部导入其中一个路径的Pkg
包(比如说此时导入了A
路径),那么如果我还想用B
路径中的print
程序的话,必须要写全名B.Pkg.print
来使用。import A.Pkg; psvm{ Pkg demo = new Pkg(); demo.print(); //这里会用A路径下的Pkg包中的print方法 B.Pkg demo2 = new B.Pkg(); demo2. print();//这里会用B路径下的Pkg包中的print方法 }
String
代表字符串,可以创建对象封装字符串数据,并对字符串的进行处理
应用场景:用户登录 | 屏蔽脏话 | 百度文字匹配搜索
创建对象封装字符串数据
* 直接双引号得到字符串对象,封装字符串数据
* `new String`创建字符串对象,并调用构造器初始化字符串
String name = "victoria"; // 1、直接双引号得到字符串对象,封装字符串数据
System.out.println(name); // 这里会直接打印出来name的字符串内容"victoria",而不是name的地址
// 2、new String创建字符串对象,并调用构造器初始化字符串
String rs1 = new String();
System.out.println(rs1); // 创建一个空白字符串对象,不含有任何内容
String rs2 = new String("hello"); // 不推荐
System.out.println(rs2);//根据传入的字符串内容,创建字符串对象
char[] chars = {'a', '宋', '茜'};
String rs3 = new String(chars);
System.out.println(rs3);//根据字符数组的内容,创建字符串对象
byte[] bytes = {97, 98, 99}; // 对应ASCII码是 a b c
String rs4 = new String(bytes);
System.out.println(rs4);// 根据字节数组的内容,创建字符串对象
常用方法
方法名 | 说明 |
---|---|
public int length() | 获取字符串的长度返回(就是字符个数) |
public char charAt(int index) | 获取某个索引位置处的字符返回 |
public char[] toCharArray(): | 将当前字符串转换成字符数组返回 |
public boolean equals(Object anObject) | 判断当前字符串与另一个字符串的内容一样,一样返回true |
public boolean equalsIgnoreCase(String anotherString) | 判断当前字符串与另一个字符串的内容是否一样(忽略大小写) |
public String substring(int beginIndex, int endIndex) | 根据开始和结束索引进行截取,得到新的字符串(包前不包后) |
public String substring(int beginIndex) | 从传入的索引处截取,截取到末尾,得到新的字符串返回 |
public String replace(CharSequence target, CharSequence replacement) | 使用新值,将字符串中的旧值替换,得到新的字符串 |
public boolean contains(CharSequence s) | 判断字符串中是否包含了某个字符串 |
public boolean startsWith(String prefix) | 判断字符串是否以某个字符串内容开头,开头返回true,反之 |
public String[] split(String regex) | 把字符串按照某个字符串内容分割,并返回字符串数组回来 |
注意事项(Tips)
String
的对象是不可变字符串对象,每次试图改变字符串对象实际上是新产生了新的字符串对象了,变量每次都是指向了新的字符串对象,之前字符串对象的内容确实是没有改变的,因此说String的对象是不可变的。
-
只要是以直接双引号方式写出的字符串对象,会存储得到字符串
常量池
,且相同内容的字符串只存储一份 -
但是通过new方式创建字符串对象,每new一次都会产生一个新的对象放在
堆内存
中String s2 = new String("hhh");//这里创建了两个对象,一个hhh放在堆内存中,一个hhh放在堆内存的字符串常量池中 String s1 = "hhh"; //这里创建了0个对象,s1直接指向放在堆内存的字符串常量池中的hhh System.out.println(s1 == s2); //false: s2的地址是堆内存中hhh的地址
String s1 = "abc"; // 常量池 String s2 = "ab"; // 常量池 String s3 = s2 + "c"; //不是由双引号创建的,而是通过运算得到的,也是放在堆内存里,而没有放在常量池中 System.out.println(s1 == s3); //false
String s1 = "abc"; // 常量池 String s2 = "a" + "b" + "c"; //这样的运算是有编译优化机制的,会直接转成"a + b + c" System.out.println(s1 == s2); // true
-
案例:随机产生n位验证码,每位可能是数字、大写字母、小写字母
public static void main(String[] args) { System.out.println(verifyCode(5)); } public static String verifyCode(int n){ String code = ""; String allWord = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890"; Random rd = new Random(); for (int i = 0; i < n; i++) { int index = rd.nextInt(allWord.length()); code += allWord.charAt(index); } return code; }
ArrayList
创建
- 集合:大小可变的容器,关键:
创建
+增
+删
+改
+查
,它存储的是每个对象在堆内存中的地址 - 泛型编程:
public class ArrayList<E>
,其中<E>
代表可以指定存储哪种类型的,如果不指定就是什么类型都有,推荐这样写:ArrayList<Object> list = new ArrayList<>();
。但是注意不支持基本数据类型,只能支持引用数据类型,比如ArrayList<int> list = new ArrayList<>();
的int
不行,得替换成Interger
- 构造器:1. 无参:初始容量为10(基本上是用这个) | 2.
ArrayList(int n)
:具有指定初始容量的空列表
// 无约束
ArrayList list = new ArrayList(); //无参构造
list.add(219);
System.out.println(list); //数组打印的是地址值,但是集合打印的是内容(因为重写了 toString)
//有约束 泛型形式
ArrayList<String> list2 = new ArrayList<>();// 约束为String了,其他类型不能放进去
list2.add("219");
System.out.println(list2);
常用方法
常用方法名 | 说明 |
---|---|
public boolean add(E e) | 将指定的元素添加到此集合的末尾 |
public void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
public E get(int index) | 返回指定索引处的元素 |
public int size() | 返回集合中的元素的个数 |
public E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
public boolean remove(Object o) | 删除指定的元素,返回删除是否成功 |
public E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
ArrayList<String> list = new ArrayList<>();
list.add("吸引力");
list.add("情绪稳定");
list.add(1,"and");//向指定索引位置添加数据
list.add("精神独立");
System.out.println(list); //[吸引力, and, 情绪稳定, 精神独立]
System.out.println(list.get(2));//获取索引位置的数据:情绪稳定
System.out.println(list.size());//获取集合中元素的个数:4
list.remove(1);//删除集合中的元素
System.out.println(list);//[吸引力, 情绪稳定, 精神独立]
System.out.println(list.remove("吸引力"));//删除集合中指定的元素,返回是否删除成功
System.out.println(list);//[情绪稳定, 精神独立]
list.set(1,"坚持");
System.out.println(list);//[情绪稳定, 坚持]
案例
-
从容器中找出某些数据并成功删除:现在假如购物车中存储了如下这些商品: 宁夏枸杞,黑枸杞,人字拖,特级枸杞,枸杞子。现在用户不想买枸杞了,选择了批量删除
注意集合中边遍历边删除的一些坑public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("宁夏枸杞"); list.add("黑枸杞"); list.add("人字拖"); list.add("特级枸杞"); list.add("枸杞子"); System.out.println(list); batchDelete2(list, "枸杞"); System.out.println(list); } public static void batchDelete1(ArrayList<String> list, String product) { for (int i = 0; i < list.size(); i++) { String ele = list.get(i); if (ele.contains(product)) { list.remove(i); i--; //方法一: i 回退 } } } public static void batchDelete2(ArrayList<String> list, String product) { for (int i = list.size() - 1; i >= 0; i--) { //方法二:倒序遍历,可以避免漏掉元素 String ele = list.get(i); if (ele.contains(product)) { list.remove(i); } } }
static关键字
修饰成员变量
public class Student {
static String name; //类变量
int age; //实例变量
}
类变量
有static
修饰的变量,属于类,在计算机内存里只有一份,会被类的全部对象共享
- 通过
类.静态变量
的方式调用。ps:也可以通过对象.静态变量
的方式访问,但是不推荐 - 与类一起执行,只会加载一次
- 应用场景:如果某个数据只需要一份,且希望能够被共享(访问、修改),则该数据可以定义成类变量来记住。
- 案例:系统启动后,要求用于类可以记住自己创建了多少个用户对象
public class User{ public static int number;//一般用public修饰 public User(){ User.number++;//使用构造器每次创建对象时,number自增一下 //也可以直接写`number++;` ,在同一个类中访问自己类的类变量可以省略类名 } }
public class Test{ public static void main(String[] args){ //创建4个对象 new User(); new User(); new User(); new User(); System.out.println("创建User对象个数:"+User.number);//查看系统创建了多少个User对象 } }
- 案例:系统启动后,要求用于类可以记住自己创建了多少个用户对象
实例变量(对象的变量)
无static
修饰的变量,属于每个对象各自的变量
- 只能通过
对象.实例变量
的方式调用 - 应用场景:每个对象都要有一份,且数据各不同(如:name\score\age)
修饰成员方法
实例方法
无static修饰的方法,是属于对象的;
- 调用时,需要创建对象后,使用对象调用(ps:不能用类访问。因为实例方法中可能会访问实例变量,而实例变量需要创建对象后才存在。
类方法
有static修饰的方法,是属于类的;
- 它是随着类的加载而加载的;调用时直接用类名调用即可(ps:也可以使用对象名访问类方法,但不推荐。
public class Student{
double score;
//类方法:
public static void printHelloWorld{
System.out.println("Hello World!");
}
//实例方法(对象的方法)
public void printPass(){
System.out.println(score>=60?"成绩合格":"成绩不合格");
}
}
public class Test{
public static void main(String[] args){
Student.printHelloWorld();//1.调用Student类中的类方法
Student s = new Student();
s.printPass();//2.调用Student类中的实例方法
s.printHelloWorld(); //使用对象也能调用类方法【不推荐,IDEA连提示都不给你,你就别这么用了】
}
}
main方法
public static void main(String[] args)
-
本质上就是类方法,通过
类.main
直接跑起来 -
如何将数据传给
main
方法的参数args
:执行的时候在后面填参数,但在实际开发中没啥用
工具类
如果一个类中的方法全都是静态
的,那么这个类中的方法就全都可以被类名直接调用,由于调用起来非常方便,就像一个工具一下,所以把这样的类就叫做工具类。(典型工具类:math
)
- 一般命名:
xxUtils
- 工具类中的方法都是类方法,每个类方法都是用来完成一个功能的
案例:生成n
位随机验证码
public class MyUtils{
public static String createCode(int n){
String code = "";
String data = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKMNOPQRSTUVWXYZ";
Random r = new Random();
for(int i=0; i<n; i++){
int index = r.nextInt(data.length());
char ch = data.charAt(index);
code+=ch;
}
return code;
}
}
- 接着可以在任何位置调用
MyUtils
的createCode()方法
产生任意个数的验证码
public class LoginDemo{//比如这是一个登录界面
public static void main(String[] args){
System.out.println(MyUtils.createCode());
}
}
public class registerDemo{//比如这是一个注册界面
public static void main(String[] args){
System.out.println(MyUtils.createCode());
}
}
小tips:工具类里的方法全都是静态的,推荐用类名调用为了防止使用者用对象调用。我们可以把工具类的构造方法私有化。
public class MyUtils{
private MyUtils(){//私有化构造方法:这样别人就不能使用构造方法new对象了
}
public static String createCode(int n){ //公有化类方法
...
}}
- 为什么不直接使用实例方法:实例方法需要创建对象来调用,如果只是为了调用方法,没必要创一个会浪费内存的变量出来,而类方法直接使用类名调用,方便省内存
注意事项
-
①类方法中可以直接访问类的成员(类变量或类方法),不可以直接访问实例成员(实例变量和实例方法)
static String schoolName; // 类变量 double score; // 实例变量 public static void printHelloWorld(){ // 注意:同一个类中,访问类成员,可以省略类名不写。 schoolName = "黑马"; //可以直接访问类变量 printHelloWorld2(); // 可以直接访问类方法 System.out.println(score); // 报错,不可以直接访问实例变量 printPass(); // 报错,不可以直接访问实例方法 system.out.println(this); // 报错:见第三条注意事项 } public static void printHelloWorld2(){ // 类方法 } public void printPass2(){ // 实例方法 }
-
②实例方法中可以直接访问类成员和实例成员
public void printPass(){ schoolName = "黑马2"; //对的 printHelloWorld2(); //对的 System.out.println(score); //对的 printPass2(); //对的 system.out.println(this); // 对的:见第三条注意事项 }
-
③实例方法中可以出现
this
关键字,但是类方法中不可以出现this
关键字
代码块
是类的5大成分之一(成员变量、构造器、方法、代码块、内部类)
静态代码块
- 格式:
static{这里面放类初始化要执行的语句}
- 特点:
- 类加载时自动执行(不需要创建对象就能够执行)
- 由于类只会加载一次,所以静态代码块也只会执行一次
- 作用:完成类的初始化。比如:对类变量进行初始化赋值
public class Student {
static int number = 80;
static String schoolName = "一中";
// 静态代码块
static {
System.out.println("静态代码块执行了~~");
schoolName = "一中"; //对类变量进行初始化赋值
}
}
public class Test {
public static void main(String[] args) {
System.out.println(Student.number);// 先输出"静态代码块执行了~~",才输出80
System.out.println(Student.number);//80
System.out.println(Student.number);//80
System.out.println(Student.schoolName); // 一中
}
}
实例代码块
- 格式:
{}
- 特点
- 每次创建对象时,执行实例代码块,并且在构造器前执行
- 实例代码块每次创建对象之前都会执行一次
- 作用:跟构造器一样,都是用来完成对象的初始化的。比如:对实例变量进行初始化赋值(但不建议);还可以减少有参构造器和无参构造器中出现的重复代码率(就是如果这两个构造器有重复代码就移到动态代码块中去)
public class Student{
int age;//实例变量
//实例代码块:实例代码块会执行在每一个构造方法之前
{
System.out.println("实例代码块执行了~~");
age = 18;
System.out.println("有人创建了对象:" + this);
}
public Student(){
System.out.println("无参数构造器执行了~~");
}
public Student(String name){
System.out.println("有参数构造器执行了~~");
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student("张三");
System.out.println(s1.age); // 18
System.out.println(s2.age); // 18 这样的话所有对象的age都只会是18
}
}
实例代码块
- 格式:
{}
- 特点
- 每次创建对象时,执行实例代码块,并且在构造器前执行
- 实例代码块每次创建对象之前都会执行一次
- 作用:跟构造器一样,都是用来完成对象的初始化的。比如:对实例变量进行初始化赋值(但不建议);还可以减少有参构造器和无参构造器中出现的重复代码率(就是如果这两个构造器有重复代码就移到动态代码块中去)
public class Student{
int age;//实例变量
//实例代码块:实例代码块会执行在每一个构造方法之前
{
System.out.println("实例代码块执行了~~");
age = 18;
System.out.println("有人创建了对象:" + this);
}
public Student(){
System.out.println("无参数构造器执行了~~");
}
public Student(String name){
System.out.println("有参数构造器执行了~~");
}
}
public class Test {
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student("张三");
System.out.println(s1.age); // 18
System.out.println(s2.age); // 18 这样的话所有对象的age都只会是18
}
}
接口
interface关键字
- 格式:
public interface 接口名 {
// 成员变量(常量)
// 成员方法(抽象方法)
}
案例:
public interface A{
//这里public static final可以加,可以不加。
public static final String SCHOOL_NAME = "黑马程序员";
//这里的public abstract可以加,可以不加。
public abstract void test();
}
public class Test{
public static void main(String[] args){
System.out.println(A.SCHOOL_NAME);//打印A接口中的常量
A a = new A();//报错,接口是不能创建对象的
}
}
implements关键字
- 接口不能创建对象;接口是用来被类实现(implements)的,实现接口的类称为实现类。
修饰符 class 实现类 implements 接口1, 接口2, 接口3 , ... {
}
- 一个
类
可以实现多个接口
(接口可以理解成干爹),实现类
实现多个接口
,必须重写完全部接口的全部抽象方法,否则实现类需要定义成抽象类。
比如,再定义一个B接口,里面有两个方法testb1()
,testb2()
public interface B {
void testb1();
void testb2();
}
接着,再定义一个C接口,里面有两个方法testc1()
, testc2()
public interface C {
void testc1();
void testc2();
}
然后,再写一个实现类D,同时实现B接口和C接口,此时就需要复写四个方法,如下代码
public class D implements B, C{// 实现类
@Override
public void testb1() {
}
@Override
public void testb2() {
}
@Override
public void testc1() {
}
@Override
public void testc2() {
}
}
最后,定义一个测试类Test
public class Test {
public static void main(String[] args) {
System.out.println(A.SCHOOL_NAME);
D d = new D();
}
}
接口的好处
- 弥补了类单继承的不足,一个类同时可以实现多个接口。通过接口,我们可以让一个类有一个亲爹的同时,还可以找多个干爹去扩展自己的功能。通过接口去找干爹,别人通过implements的接口,就可以显性的知道你是谁,从而也就可以放心的把你当作谁来用了。
- 一个类我们说可以实现多个接口,同样,一个接口也可以被多个类实现的。让程序可以面向接口编程,这样程序员可以灵活方便的切换各种业务实现。
案例:假设有一个Student
学生类,还有一个Driver
司机的接口,还有一个Singer
歌手的接口。
现在要写一个A类,想让他既是学生,偶然也是司机能够开车,偶尔也是歌手能够唱歌。那我们代码就可以这样设计,如下:
class Student{ //亲爹
}
interface Driver{ //干爹
void drive();
}
interface Singer{ //干爹
void sing();
}
//A类是Student的子类,同时也实现了Dirver接口和Singer接口
class A extends Student implements Driver, Singer{
@Override
public void drive() {
}
@Override
public void sing() {
}
}
public class Test {
public static void main(String[] args) {
Singer s = new A();//想唱歌的时候,A类对象就表现为Singer类型
s.sing();
Driver d = new A();//想开车的时候,A类对象就表现为Driver类型
d.drive();
}
}
接口中新增的方法(JDK8+)
好处:增强了接口的能力(原来都是一些抽象方法,但是新增的这些方法可以写方法体了),更便于项目的扩展和维护。
-
默认方法:使用
default
修饰,使用实现类的对象调用。public interface A{ //接口 default void test1(){ //default修饰实例方法test1,必须调用实现类的对象来访问 System.out.println("默认方法"); }}
public class B implements A{ //实现类 }//由于没有A里抽象方法所以不需要重写
psvm{ B b = new B(); b.test1();// 输出:默认方法 ; 通过创建对象来调用默认方法 }
-
私有方法:
private
修饰,jdk9开始才有的,只能在接口内部被调用。public interface A{ //接口 private void test2(){ //private修饰实例方法test2,在接口外面是访问不了的 System.out.println("私有方法"); } default void test1(){ //在接口内部可以访问 test2(); }}
-
静态方法:
static
修饰,必须用当前接口名调用public interface A{ //接口 static void test3(){ //static修饰实例方法test3,在接口外面用接口名调用 System.out.println("静态方法"); }}
psvm{ A.test3();// 直接使用接口名A来调用 }
-
他们都会默认被
public
修饰(接口就是供别人调用的,是要暴露出去的),所以可以不用带public
内部类
定义:如果一个类定义在另一个类的内部,这个类就是内部类,是类的五大成分之一(成员变量、方法、构造器、内部类、代码块)
场景:当一个类的内部,包含了一个完整的事物,且这个事物没有必要单独设计时,就可以把这个事物设计成内部类
成员内部类
类中的普通成员,类似于普通的成员变量、成员方法
public class Outer {
private int age = 99;
public static String a="lim";
// 成员内部类
public class Inner{
private String name;
private int age = 88;
//在内部类中既可以访问自己类的成员,也可以访问外部类的成员
public void test(){
System.out.println(age); //88
System.out.println(a); //lim
int age = 77;
System.out.println(age); //77
System.out.println(this.age); //88
System.out.println(Outer.this.age); //99 拿到当前外部类的对象
}
// getter&setter
}}
- 成员内部类创建对象的格式:
//外部类.内部类 变量名 = new 外部类().new 内部类();
Outer.Inner in = new Outer().new Inner();
//调用内部类的方法
in.test();
- 既可以访问内部类成员、也可以访问外部类成员(拿到当前外部类的对象:
外部类名.this
) - 如果内部类成员和外部类成员同名,可以使用**
类名.this.成员
**区分
静态内部类
在成员内部类的前面加了一个static
关键字。静态内部类属于外部类自己持有。
静态内部类可以看作外部类的一个静态方法,因此是不能访问外部类的实例变量的。
public class Outer {
private int age = 99;
public static String schoolName="lim";
// 静态内部类
public static class Inner{
//静态内部类访问外部类的静态变量,是可以的;
//静态内部类访问外部类的实例变量,是不行的
public void test(){
System.out.println(schoolName); //lim
//System.out.println(age); //报错
}}}
- 静态内部类创建对象的格式:
//格式:外部类.内部类 变量名 = new 外部类.内部类();
Outer.Inner in = new Outer.Inner();
in.test();
匿名内部类
-
匿名内部类是一种特殊的局部内部类;所谓匿名,指的是程序员不需要为这个类声明名字。
-
本质上是一个没有名字的子类对象、或者接口的实现类对象。
-
作用:简化了创建子类对象、实现类对象的书写格式。核心就是四个字:简化代码
-
格式:
new 父类/接口(参数值){
@Override
重写父类/接口的方法;
}
public abstract class Animal{ //定义一个Animal抽象类
public abstract void cry();
}
public class Test{
public static void main(String[] args){
//这里隐含的有多态的特性: Animal a = Animal子类对象;
Animal a = new Animal(){ //new Animal()就创建了一个Animal的子类
@Override //子类进行方法重写
public void cry(){
System.out.println("猫喵喵喵的叫~~~");
}
}
a.cry(); //猫喵喵喵的叫~~~
}
}
需要注意的是,匿名内部类在编写代码时没有名字,编译后系统会为自动为匿名内部类生产字节码,字节码的名称会以外部类$1.class
的方法命名
- 使用场景:作为一个参数直接传给方法 简化了代码的书写
public interface Swimming{ //有一个游泳的接口
public void swim();
}
public class Test{
public static void main(String[] args){
Swimming s1 = new Swimming(){ //创建了一个匿名类的对象s1
public void swim(){
System.out.println("狗刨飞快");
}
};
go(s1); //将s1传递到go方法中完成游泳行为
Swimming s1 = new Swimming(){//创建了一个匿名类的对象s1
public void swim(){
System.out.println("猴子游泳也还行");
}
};
go(s1);//将s1传递到go方法中完成游泳行为
}
//形参是Swimming接口,实参可以接收任意Swimming接口的实现类对象
public static void go(Swimming s){
System.out.println("开始~~~~~~~~");
s.swim();
System.out.println("结束~~~~~~~~");
}
}
上述代码也可以不创建s1
对象,而是直接把匿名类传递到go
方法中,如下所示:
public class Test{
public static void main(String[] args){
go(new Swimming(){
@Override
public void swim() {
System.out.println("狗🏊飞快~~~~");
}
}); //直接把匿名类传到go中去了
public static go (Swimming s){
System.out.println("开始~~~~~~~~");
s.swim();
System.out.println("结束~~~~~~~~");
}
}
}
枚举
-
定义:是一种特殊类,用
enum
关键字修饰 -
第一行只能写一些名称,这些名称都是常量,且每个常量记住的都是枚举类的一个对象
-
格式:
public enum 枚举类名{ 枚举项1,枚举项2,枚举项3; //枚举项就表示枚举类的对象,这些对象在定义枚举类时就预先写好了,以后就只能用这几个固定的对象。 }
public enum A{ X,Y,Z; }
想要获取枚举类中的枚举项,只需要用类名调用就可以了
public class Test{ public static void main(String[] args){ //获取枚举A类的,枚举项 A a1 = A.X; sout(a1); //输出:X A a2 = A.Y; A a3 = A.Z; }}
反编译后发现,枚举类
A
是用class
定义的,说明枚举确实是一个类,而且X,Y,Z都是A类的对象;而且每一个枚举项都是被public static final
修饰,所以被可以类名调用,而且不能更改
正则表达式
正则表达式
作用:校验 | 查找 | 替换 | 分割
- 校验字符串数据是否合法:
matches(String regex)
匹配一个字符串是否匹配正则表达式的规则 - 可以从一段文本中查找满足要求的内容
案例:检查qq号是否合法
public static boolean checkQQ(String qq){
return qq != null && qq.matches("[1-9]\\d{5,19}");
}
规则:
public class Regex {
public static void main(String[] args) {
// 1、字符类(只能匹配单个字符)
System.out.println("a".matches("[abc]")); // [abc]只能匹配a、b、c
System.out.println("e".matches("[abcd]")); // false
System.out.println("d".matches("[^abc]")); // [^abc] 不能是abc
System.out.println("a".matches("[^abc]")); // false
System.out.println("b".matches("[a-zA-Z]")); // [a-zA-Z] 只能是a-z A-Z的字符
System.out.println("2".matches("[a-zA-Z]")); // false
System.out.println("k".matches("[a-z&&[^bc]]")); // : a到z,除了b和c
System.out.println("b".matches("[a-z&&[^bc]]")); // false
System.out.println("ab".matches("[a-zA-Z0-9]")); // false 注意:以上带 [内容] 的规则都只能用于匹配单个字符
// 2、预定义字符(只能匹配单个字符) . \d \D \s \S \w \W
System.out.println("徐".matches(".")); // .可以匹配任意字符
System.out.println("徐徐".matches(".")); // false
// \转义
System.out.println("\"");
// \n \t
System.out.println("3".matches("\\d")); // \d: 0-9
System.out.println("a".matches("\\d")); //false
System.out.println(" ".matches("\\s")); // \s: 代表一个空白字符
System.out.println("a".matches("\\s")); // false
System.out.println("a".matches("\\S")); // \S: 代表一个非空白字符
System.out.println(" ".matches("\\S")); // false
System.out.println("a".matches("\\w")); // \w: [a-zA-Z_0-9]
System.out.println("_".matches("\\w")); // true
System.out.println("徐".matches("\\w")); // false
System.out.println("徐".matches("\\W")); // [^\w]不能是a-zA-Z_0-9
System.out.println("a".matches("\\W")); // false
System.out.println("23232".matches("\\d")); // false 注意:以上预定义字符都只能匹配单个字符。
// 3、数量词: ? * + {n} {n, } {n, m}
System.out.println("a".matches("\\w?")); // ? 代表0次或1次
System.out.println("".matches("\\w?")); // true
System.out.println("abc".matches("\\w?")); // false
System.out.println("abc12".matches("\\w*")); // * 代表0次或多次
System.out.println("".matches("\\w*")); // true
System.out.println("abc12张".matches("\\w*")); // false
System.out.println("abc12".matches("\\w+")); // + 代表1次或多次
System.out.println("".matches("\\w+")); // false
System.out.println("abc12张".matches("\\w+")); // false
System.out.println("a3c".matches("\\w{3}")); // {3} 代表要正好是n次
System.out.println("abcd".matches("\\w{3}")); // false
System.out.println("abcd".matches("\\w{3,}")); // {3,} 代表是>=3次
System.out.println("ab".matches("\\w{3,}")); // false
System.out.println("abcde徐".matches("\\w{3,}")); // false
System.out.println("abc232d".matches("\\w{3,9}")); // {3, 9} 代表是 大于等于3次,小于等于9次
// 4、其他几个常用的符号:(?i)忽略大小写 、 或:| 、 分组:()
System.out.println("abc".matches("(?i)abc")); // true
System.out.println("ABC".matches("(?i)abc")); // true
System.out.println("aBc".matches("a((?i)b)c")); // true
System.out.println("ABc".matches("a((?i)b)c")); // false
// 需求1:要求要么是3个小写字母,要么是3个数字。
System.out.println("abc".matches("[a-z]{3}|\\d{3}")); // true
System.out.println("ABC".matches("[a-z]{3}|\\d{3}")); // false
System.out.println("123".matches("[a-z]{3}|\\d{3}")); // true
System.out.println("A12".matches("[a-z]{3}|\\d{3}")); // false
// 需求2:必须是”我爱“开头,中间可以是至少一个”编程“,最后至少是1个”666“
System.out.println("我爱编程编程666666".matches("我爱(编程)+(666)+"));
System.out.println("我爱编程编程66666".matches("我爱(编程)+(666)+"));
}}
校验
- 校验手机号码:18676769999、010-3424242424、0104644535,
phone.matches("(1[3-9]\\d{9})|(0\\d{2,7}-?[1-9]\\d{4,19})"
public class RegexTest3 {
public static void main(String[] args) {
checkPhone();
}
public static void checkPhone(){
while (true) {
System.out.println("请您输入您的电话号码(手机|座机): ");
Scanner sc = new Scanner(System.in);
String phone = sc.nextLine();
if(phone.matches("(1[3-9]\\d{9})|(0\\d{2,7}-?[1-9]\\d{4,19})")){
System.out.println("您输入的号码格式正确~~~");
break;
}else {
System.out.println("您输入的号码格式不正确~~~");
}}}}
-
校验邮箱:
email.matches("\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2}")
/**正确案例 * dlei0009@163.com * 25143242@qq.com * itheima@itcast.com.cn */
public class RegexTest3 {
public static void main(String[] args) {
checkEmail();
}
public static void checkEmail(){
while (true) {
System.out.println("请您输入您的邮箱: ");
Scanner sc = new Scanner(System.in);
String email = sc.nextLine();
if(email.matches("\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2}")){
System.out.println("您输入的邮箱格式正确~~~");
break;
}else {
System.out.println("您输入的邮箱格式不正确~~~");
}}}}
查找
- 需求1:从以下内容中爬取出,手机,邮箱,座机、400电话等信息。
public class RegexTest4 {
public static void main(String[] args) {
method1();
}
public static void method1(){
String data = " 来黑马程序员学习Java,\n" +
" 电话:1866668888,18699997777\n" +
" 或者联系邮箱:boniu@itcast.cn,\n" +
" 座机电话:01036517895,010-98951256\n" +
" 邮箱:bozai@itcast.cn,\n" +
" 邮箱:dlei0009@163.com,\n" +
" 热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090";
// 1、定义爬取规则
String regex = "(1[3-9]\\d{9})|(0\\d{2,7}-?[1-9]\\d{4,19})|(\\w{2,}@\\w{2,20}(\\.\\w{2,10}){1,2})"
+ "|(400-?\\d{3,7}-?\\d{3,7})";
// 2、把正则表达式封装成一个Pattern对象
Pattern pattern = Pattern.compile(regex);
// 3、通过pattern对象去获取查找内容的匹配器对象。
Matcher matcher = pattern.matcher(data);
// 4、定义一个循环开始爬取信息
while (matcher.find()){
String rs = matcher.group(); // 获取到了找到的内容了。
System.out.println(rs);
}
}
}