文章目录
一、入门程序HelloWord
//public 公开的。class一个类,A类名
public class A{
//类体 (类体中只能声明变量【声明和赋值可以同时进行】变量的操作要放在在方法中)
public static void main(Sring[] arge){//---主方法,程序入口(主方法也是方法)static静态的
//方法体
System.out.println("Holle World").
}
}
/*
一个Java源文件中运行可以有多个class,但最多只能有一个public class且该
该类名称与文件名一样(Java默认类与文件同名,且main方法在public修饰的类中)
*/
二、变量
1. 标识符
Java源程序中,凡是程序员有权自己命名的单词均是标识符。例如: 类名,方法名,变量名,接口名,常量名等。
- 语法格式:
● 只能有“数字,字母,下划线 _,美元符号”组成,不能含有其他符号且不能以数字开头,区分大小写。
● 注意:关键字不能做标字符。- 命名规范(规范不是语法,不会报错,但甚至比语法重要):
● 最好见名知意。
● 遵循驼峰命名方式:类名,接口名,首字母大写,后面每个单词首字母大写。变量名,方法名,首字母小写,后面每个单词首字母大写。常量名全部大写。
2. 关键字
Java语言自己定义的中具有一些特殊含义的字符序列,字符均为小写字面值(就是数据)
3. 字面值
1 整形
"abc" 字符串型
3.14 浮点型
'a' 字符型
null 引用类型
4. 变量
- 内存中的一块空间,变量包含三部分,分别为数据类型,变量名和字面值
● 变量中起决定作用的是数据类型,JVM根据数据的类型来开辟对应大小空间.
● 变量中储存的数据类型必须和变量的数据类型保持一致- 声明/定义变量的语法格式:
● 数据类型 变量名;
● 变量在一行上可以声明多个
○ 格式:数据类型 变量名1p,变量名2,变量名3;(用","隔开。)- 变量的赋值语法格式:
● 变量名=字面值;
● 声明和赋值可以同时进行
○ 格式:数据类型 变量名 = 字面值;
注意: 类体中类的方法外,变量声明和赋值不允许分开进行(变量的操作必须在方法中)
● 变量可以重新赋值,引入变量后内存空间得到了重复使用,注意,在同一个作用域中变量不运行能重名- 变量的作用域:
变量只在对应的代码块中有效出了{}就不认识(形参的作用范围为对应方法体内)- 变量的分类:
● 成员变量:在方法体外(类体之内)声明的变量叫成员变量
● 局部变量:在方法体内声明的变量叫局部变量(形参为局部变量)
● 局部变量必须初始化,mian方法也是方法其中变量为局部变量。成员变量可以不进行初始化,系统会自动初始化默认值(通过构造方法初始化)
5. 数据类型
数据类型指导JVM在运行程序的时候给对应数据分配内存空间的大小
- 基本数据类型
注意: “abc”字符串不属于基本数据类型(引用数据类型)一个中文占用两个字节(16b)- 转义字符(char类型)
- 基本数据类型转换
示例
// 整数相除得到的是整数 现有代码如下, 想要运算结果是2.5, 代码应该怎么改 ?
/*
byte short char类型参与运算自动转换为int类型,其他类型参与运算自动
转换为运算中类型最大的类型
*/
public static void main(String[] args) {
int i = 5 / 2;
System.out.println(i);
}
//double d = 5.0 / 2; 或者 double d = 5 / 2.0;
/*
复杂的赋值运算符和++ --运算符-------隐含了强制类型转换
*/
byte b = 20;
b += 2;//等同b = (byte)(b+20000000);
b++;//等同b = (byte)(b+1);
//逻辑运算符 ^ 相同为false不同为true
- 特例
Java的常量优化机制- 引用数据类型
把类名当作是一种类型来声明变量,这种变量叫引用类型变量。引用类型变量保存对象的“引用”,即对象的地址。
6. 运算符
算术运算符
- 减
* 乘
/ 除
% 求余
++ 自加
- - 自减
++运算符
++运算符既可以出现在变量前也可以出现在变量后,但无论出现在变量前还是变量后,++运算结束后该变量一定自加1
注意:++运算符在变量前,该变量先加1,之后再参与运算,++运算符在变量后,该变量先参与运算再加1(重新从前向后读++在前先加一,++在后先取值)
--运算符
与++一致
int a = 10;
int b = a++ + ++a;
/*
读:
1.a(10)++(先取a为10,之后a在加1为11)
2.++a 先加1在取值经过1的a++后为11,此时在加1为12
3.式子为: int b = 10 + 12
*/
关系运算符
< 小于
<= 小于等于
> 大于
>= 大于等于
== 等于(注意与赋值=区分)
!= 不等于
关系运算符的结果均为boolean类型
逻辑运算符
& 逻辑与 (同真为真,有假即假)
| 逻辑或 (有真即真,同假才假)
! 逻辑非 (取反,!false为true)
^ 逻辑异或 (运算符两边真假不同则为真,相同才为假)
&& 短路与 (同真为真,有假即假)
|| 短路或 (有真即真,同假才假)
"&&/||"与“&/|"的区别
● &&和||有短路的效果
○ &:无论左边式子为真假,右边边式子均执行
○ &&:只有左边式子为真,右边式子才会执行,左边为假短路,不在判断右边
○ | :运算符两边均执行
○ ||:左边为假,右边才执行,左边为真短路,不在判断右边
● 总结:某个角度来看,短路与/或更智能,效率更高,实际开发中更常用,但在一些特殊要求中必须用逻辑与/或(结果为boolean型)
赋值运算符
基本赋值运算符:=
扩展赋值运算符:+=,-=,*=,/=,%=
变量类型a 变量1 算术运算符= 变量2/常量 等效于变量类型a 变量1 = (变量类型a)(变量1算术运算符变量2/常量)
即: byte a = 10;
a += 10;<=>a = (byte)(a+10);
扩展运算符自动强制转换
字符串的连接运算符
"+"运算符,有字符串参与时不再是求和运算,而是连接成字符串
多个+号参与时,(有括号优先计算括号内)由左往右一次计算
三元运算符/条件运算符(具有返回值的表达式)
语法表示:
布尔表达式?表达式1:表达式2
布尔表达式的结果为true则选择表达式1作为整个表达式的执行结果
布尔表达式的结果为false则选择表达式2作为整个表达式的执行结果
三、控制语句
1. if条件/分支语句(选择结构)
语法格式1:
if(布尔表达式){
java语句;
………….
}
//执行原理:布尔表达式为true执行if中语句
语法格式2:
if(布尔表达式){
java语句;
………….
}else{
java语句;
………….
}
//执行原理:布尔表达式为真则执行if中语句,为假则执行else中语句
语法格式3:
if(布尔表达式){
java语句;
………….
}else if(布尔表达式){
java语句;
………….
}else if(布尔表达式){
java语句;
………….
}else if(布尔表达式){
java语句;
………….
}
………………..
else if(布尔表达式){
java语句;
………….
}else{
java语句;
…………….
}
/*
执行原理:由上到下依次判断,直到出现为真,执行该条件所在的分支中的语句,
并结束整个if语句,若无真则执行最后一个else分支的语句
最后一个else可以省略。
*/
2. switch分支语句(选择结构)
switch(字面值或变量){
case 字面值或变量:
java语句;-0
………….
break;
case字面值或变量:
java语句;
………….
break;
case 字面值或变量:
java语句;
………….
break;
……..
default:
java语句;
…………..
}
/*
执行原理:switch后面小括号当中的“数据”和case后面的“数据”按上到下进行 一一匹配,匹配成功的分支执行,
执行的分支当中有“break;”语句,整个switch语句终止;若分支中没有“break;
"语句,则依次执行下面分支执行直到遇到“break;”语句(case穿透现象---因此
case可以合并。)
所有的case都没有匹配到,有default语句的话执行default语句
注意:switch关键字和case关键字后面只能是int/String类型,如是byte,short,
char会自动转换为int类型(当然long,浮点型不会自动转换,可以强制转换)
*/
//新版switch(JDK12)无穿透 不建议用,大部分用的还是JDK8
int week = scanner.nextInt();
switch (week){
case 1->{
System.out.println("周一睡觉");
}
case 2->System.out.println("周二睡觉");//只有一条语句时括号可以省略
case 3->System.out.println("周三睡觉");
case 4->System.out.println("周四睡觉");
case 5->System.out.println("周五睡觉");
case 6->System.out.println("周六睡觉");
case 7->System.out.println("周日睡觉");
default -> System.out.println("45sdaf");
}
注意: switch关键字和case关键字后面只能是int/String类型,如是byte,short, char会自动转换为int类型。(当然long,浮点型不会自动转换,可以强制转换)
3. for循环语句(循环结构)
>for(初始化表达式 ; 布尔表达式 ; 更新表达式){
循环体
}
执行原理:
1>执行过程:先执行初始化表达式,并只执行一次判断布尔表达式结果:
<1> true : 执行循环体
执行更新表达式
判断布尔表达式结果(回到上一大步)
<2> false : 循环结束
2>初始化表达式,布尔表达式,更新表达式都不是必须的,但两个分号是必须的
4. while循环语句(循环语句)
while(布尔表达式){
循环体;
}
执行原理:
1>执行过程:先判断布尔值结果:
<1>true:
执行循环体
判断布尔表达式结果(回到上一大步)
<2>false:
循环结束
2>while循环次数0~N次
5. do…while循环(循环结构)
do{
循环体;
}while(布尔表达式);
执行原理:
1>执行过程:
先执行循环体
判断布尔表达式结果:
<1>true:
执行循环体(回到上一大步)
<2>false:
循环结束
2>do..while循环次数1~N次(至少执行一次)
3>do..while循环后有一个分号“;”不可少
6.跳出语句
break;//跳出当前循环
continue;//跳出当前本次循环
return;//跳出当前方法
四、数组
概念:
Java中的数组是一种引用数据类型,不属于基本数据类型
数组实际上是一个容器,可以容纳多个元素(数组是一个数据的集合)数组:“一组数据”
数组当中可以储存基本数据了的数据,也可以存放引用类型数据类型的数据
数组是引用类型,数组的对象储存在堆内存中(数组储存在堆内存中)
数组中如果储存的是"Java对象"的话,实际上储存的是对象的"引用(内存地址)"
Java中规定数组一旦创建长度不可变
数组的分类:一维数组,二维数组,三维数组,多维数组(一维数组最常用,二维数组偶尔使用)
所有数组对象length属性(Java自带的),用来获取数组中元素的个数
Java要求数组中的元素类型统一 例如:int类型数组只能储存int类型的数据,person类型的数组只能储存person类型的数据。
数组中元素的内存地址是连续的,所以的数组都是拿“第一个小方块(元素)的内存地址”作为这个数字对象的内存地址
数组中每一个元素都是有下标,下标由0开始,以1递增,最后一个元素的下标为length-1,数组对元素进行操作时都需要用过下标来进。
1. 一维数组
/*声明:数组类型[] 数组名;*/
int[] a;//声明了一个int类型的数组a
//静态初始化(指定隐式长度,通过系统计算出数组的长度)
//简化格式
a = {1,2,3};
//完整格式
a = new int[]{1,2,3};
//动态初始化(指定显示长度,自己给出数组长度)
a = new int[3];
//注意
//1.两种初始化方式不能同时使用,即:不能同时指定隐式显示长度(重复了)
a = new int[3]{1,2,3};//报错
//2.完整写法声明和赋值可以分开写,简化写法声明和赋值必须同时进行
//完整写法---允许分开
int[] a;
a =new int[]{1,2,3};
//简略写法---这种写法声明和赋值必须同时写
int[] a= {1,2,3};
2. main中的String数组
- 该数组由程序员写出,JVM负责调用,由用户在控制台上输入参数,JVM调用的时候参数自动转化成字符串String数组。
- eg:java类名 abc def xyz JVM会自动将abc def xyz“通过空格分离并放入String[]args数组中,所以String[] args数组是用于接收用户输入的参数的,未输入时默认长度为0。
- 代码分析
cmd输入方式:
idea使用方式
3. 二维数组
二维数组实际上就是特殊的一维数组,二维数组中的每一个元素都是一个一维数组,在实际开发中一维数组最常用,二维数组使用的很少,三维几乎不会使用
/*声明
数组类型[][] 数组名*/
String[][] strArr;//声明了一个String类型的二维数组strArr
/*
1>静态初始化
指定隐式维度,通过系统去计算得出长度:*/
//完整格式
String[][] strArr = new String[][] {
{"aa", "bb", "cc"},
{"ddd", "eee", "fff"}
})
//简化格式
String[][] a = {{1,2,3},{1,2,8},{1,8,7}};
/*2>动态初始化
指定显式维度,自己手动写出的维度*/:
String[][] strArr = new String[3][4]; //3行4列
String[][] strArr = new String[3][]
//注意:不能即指定显式维度,又指定隐式维度,属于维度重复:显示维度 和 隐式维度 不能同时出现。
String[][] strArr = new String[2][3] { //这里已经指定了显示维度2,3
{"aa", "bb", "cc"},
{"ddd", "eee", "fff"} //这里又被系统算出了隐式维度2,3
});
4. 数组的排序
4.1 选择排序
选择排序:选出最小的一个数与第一个位置的数交换
选择排序原理分析
- 第1趟比较:拿第1个元素依次和它后面的每个元素进行比较,如果第1个元素大于后面某个元素,交换它们,经过第1趟比较,数组中最小的元素被选出,它被排在第一位
- 第2趟比较:拿第2个元素依次和它后面的每个元素进行比较,如果第2个元素大于后面某个元素,交换它们,经过第2趟比较,数组中第2小的元素被选出,它被排在第二位
…- 第n-1趟比较:第n-1个元素和第n个元素作比较,如果第n-1个元素大于第n个元素,交换它们
选择排序代码实现
选择排序的优化
- 使用临时变量存储最小值的角标值,减少交换的次数
选择排序的时间复杂度
- 时间复杂度:O(n²)
- 空间复杂度:O(1),只需要一个附加程序单元用于交换
- 稳定性:选择排序是不稳定的排序算法,因为无法保证值相等的元素的相对位置不变,例如 [3, 4, 3, 1, 5]这个数组,第一次交换,第一个3和1交换位置,此时原来两个3的相对位置发生了变化。
4.2 冒泡排序
冒泡排序是我们得最多的排序方式之一,原因是简单易实现,且原理易懂。顾名思义,冒泡排序,它的排序过程就像水中的气泡一样,一个一个上浮到水面。
冒泡排序原理分析
冒泡排序代码实现
输出结果
冒泡排序的优化
- 观察上述代码和运行结果,我们可以发现,当第一轮结束后,最后一个数字一定是数组中最大的元素,那么我们在进行第二趟的两两比较时,实际上是没有必要再对第5个和第6个进行比较的。那么我们可以修改代码如下
继续运行后,可以发现运行结果是一样的。- 当我们用数组:{1,2,0,3,5,4}来测试上述冒泡排序时,运行结果如下:
- 可以看到,在第2轮排序完成后,其实我们就已经的到了排好序的数组,但是我们的程序并不知道,仍然进行了后续的无用工作。那么,我们如何来让程序知道已经完成好排序了呢?
- 这里可以想到,当某一轮的两两比较中,如果都没有发生数组元素的互换,那么其实排序工作已经完成了,所以我们可以考虑在程序中加入一个flag,默认为false,含义是该轮比较中是否发生了元素互换,当程序中执行到元素互换时,将该flag置为true,当该轮比较结束时,若flag为flase,则说明该轮比较未发生元素互换,那么排序完成,若flag为true,说明本轮比较仍然有元素互换,需要继续进行下轮排序。代码实现如下:
运行结果:
冒泡排序的时间复杂度
- 冒泡排序是一种用时间换空间的排序方法,最坏情况是把顺序的排列变成逆序,或者把逆序的数列变成顺序。在这种情况下,每一次比较都需要进行交换运算。举个例子来说,一个数列 5 4 3 2 1 进行冒泡升序排列
- 第一轮的两两比较,需要比较4次;得到 4 3 2 1 5第二轮的两两比较,需要比较3次;得到 3 2 1 4 5第三轮的两两比较,需要比较2次;得到 2 1 3 4 5第四轮的两两比较,需要比较1次;得到 1 2 3 4 5
- 所以总的比较次数为 4 + 3 + 2 + 1 = 10次对于n位的数列则有比较次数为 (n-1) + (n-2) + … + 1 = n * (n - 1) / 2,这就得到了最大的比较次数。而O(N^2)表示的是复杂度的数量级。举个例子来说,如果n = 10000,那么 n(n-1)/2 = (n^2 - n) / 2 = (100000000 - 10000) / 2,相对10^8来说,10000小的可以忽略不计了,所以总计算次数约为0.5 * N2。用O(N2)就表示了其数量级(忽略前面系数0.5)。
- 综上所述,冒泡排序的时间复杂度为:O(n²)
五、方法
方法就是一段可以完成某个特定功能的代码片段,并且可以被重复使用,定义于类体中,方法可以多定义。
1. 方法的定义:
方法的定义:修饰符列表 返回值类型 方法名(形式参数列表){
方法体;
}
1> 修饰符列表:
权限修饰符:public 默认 protected private
其他修饰符:final static abstract等等
2> 返回值类型 : 方法用于完成某种特定操作,有些将操作完成后需要向调用者返
回操作的结果,而有些操作则不需要,返回值类型是方法返回结果的类型:
<1> 不需要返回值:返回值类型为: "void"(方法体可以出现"return;"用于终止方
法)
<2> 需要返回值:返回值类型视情况而定,可以是基本数据类型,也可以是引用
型数据类型,但必须与返回的值的类型保持一致,且此时方法体中,最后一行必
须用"return 数据;"来返回结果且必须保证有返回结果,这个结果不是必须接收
的,但执行了return 语句方法必然结束。
<3>需要返回值的方法必须保证return 一定会被执行且不会遗漏其他语句
3>形式参数列表:形参为局部变量,参数数量0~N个(参数不是必须的)
格式:
参数类型 参数名,参数类型 参数名……(参数与参数之间用逗号(“,”)隔)
方法在调用的时候实际给这个方法传递的真实数据称为:实际参数(实参)
(形参中取决定作用的是数据类型,形参的名字只是一个标志符)传递的实参必
须与形参列表对应,即:数量相同,数据类型相同(但方法可以进行一些自动的
类型转换)
4>方法的调用:具体调用与后续学习中逐渐了解,本章只需掌握接由static修饰的
方法的调用格式:
静态方法:类名.方法名(参数列表) 在同一个类中可以省略类名
实例方法:对象名.方法名(参数)列表 在同一个类中通过this关键字调用(this可以省略)
注意:同一个类中通过this关键字调用实例方法时,不会为创建新对象
2. 方法的内存空间
● 方法只定义,不调用是不会执行的,并且在JVM中也不会给这个方法分配“运行所属”的内存空间。
● JVM内存划分为三块:
○ 方法区内存
○ 堆内存
○ 栈内存
● 关于栈数据结构:
○ 栈:stack,是一种数据结构(数据结构反应的是数据的存储形式)
- 方法代码片段的内存分配:
- 方法代码片属于.class字节码文件的一部分,字节码文件在类加载的时候将其放到了JVM的方法区中(所以JVM的三个主要内存中模块中,方法区先有数据)
- 代码片段虽然在方法区内存当中只有一份,但可以被重复调用,每次调用方法时都需要在栈内存中给该方法分配独立的活动场所(运行所属的内存空间)。
- 方法在调用的瞬间,会在栈中发生压栈动作,给该方法分配内存空间,方法执行结束之后,发生弹栈动作,将给该方法分配的内存空间全部释放。
- 方法调用时才会分配运行空间,所以局部变量在运行阶段才会与栈中分配内存
重点: 方法在调用的过程中,方法传递的是变量中保存的"值",而不是变量本身。(变量)
3. 方法重载机制/Overload
实现"将功能不同但相似的一类方法看成同一个方法
示例:不同类型数据求和
public class Test9 {
public static void main(String[] args) {
sum(1,2);
sum( 1l, 2l);
sum( 1.0, 2.0);
sum((short) 1,(short) 2);
}
public static int sum(int a,int b){
return a+b;
}
public static double sum(double a,double b){
return a+b;
}
public static long sum(long a,long b){
return a+b;
}
public static short sum(short a,short b){
return (short) (a+b);
}
}
重载条件:
在同一个类中
方法名相同
参数列表不同:
数量不同
顺序不同(不同类型)----实际上还是类型不同
类型不同
方法重载仅和“方法名+参数列表”有关,与“返回值”和“修饰符”无关
4. 方法递归
方法嵌套自己
a(){
a();
}
递归很耗费栈内存,即使有结束条件仍然可能发生栈内存益处错误(递归太深),
除非特殊条件否者不用
六、类与对象
1. 类与对象的概念
● Java编程语言是面向对象的语言
● 面向对象的三大特征:
○ 封装
○ 继承
○ 多态
● 采用面向对象的方式开发一个软件的整个生命周期中贯穿使用OO面向对象的方式:
○ OOA(面向对象的分析)
○ OOD(面向对象的设计)
○ OOP(面向对象的编程)
● Java程序是由若干个类组成,类是Java程序的基本要素
● 类和对象的概念:
○ 类并不是现实存在的而是对相同特征的事物的抽象
○ 对象是实际存在的个体
■ 例:对象猫与对象狗之间具有共同特征,则可对对象猫和对象狗进行抽象总结出一个模板动物,这个模板即为类。
● 类与对象的关系
○ 类--【实例化】-->对象(对象又称为实例/instance)
○ 对象--【抽象】-->类
● 类描述的是对象的共同特征包含属性(成员变量)和行为(成员方法)两部分
2. 类的定义
修饰符列表 class 类名{
属性;//采用变量的形式来定义,用来描述对象的状态信息,也称为成员变量(成员变量未赋值时系统会在new对象时通过构造方法给与默认值)
行为;//描述的为对象的行为信息,也称为成员方法,不同对象的具体行为有所差异
}
堆内存中的数据都有默认值(默认值即创建对象时调用无参构造赋值),栈中的数据系统无法默认赋值值必须手动赋值
3. 对象的创建和使用(体现封装)
对象的创建:
Java通过new运算符和构造方法来实例化对象。new申请空间,构造方法初始化成员变量。
格式:类名引用 = new 类名();
等号左边是变量的声明,右边通过new和构造方法将类实例化为对象,通过赋值运算符将实例/对象赋值给引用,引用也叫对象名。
new Student() new在堆开辟内存空间,Student()在new声明的空间中初始化成员变量。之后new会计算出一个引用值该值包含全部实例成员变量的内存地址。
new的对象中会存储对应class文件的内存地址,用于引用调用方法
注意:一个类可以创建N个对象,每个对象都有各自的内存空间
● 对象的使用: Java(必须通过引用)运算符“.”来实现对实例变量的访问和实例方法的调用
○ 对象变量的访问(体现对象的属性)
■ 格式:引用.变量名 (扯淡,都是私有的访问个屁)
○ 对象方法的访问(体现对象的行为)
■ 格式:引用.方法名
● 注意:属性的操作只能放在方法中
4. 对象的内存分析
java1.7之前静态变量放在jvm方法区中,1.7之后静态成员变量存储在堆中,方法区只有Class加载文件,变量存储空间的开辟在堆或者栈中
5. 构造方法
● 构造方法存在的作用就是初始化成员变量,(配合new创建对象,new运算符才会在堆中开辟空间)
● 构造方法又被称为构造函数/构造器/Constructor
○ 语法结构:
○ 修饰符列表构造方法名(形参列表){
■ 方法体;
○ }
● 对照-相较于普通方法:修饰符列表返回值类型方法名(形参列表){},构造方法无需指定返回值类型且构造方法的方法名必须与类名保持一致。(但实际上构造方法都有返回值,且返回值为构造方法所在类的类型Eg: Student s = new Student();返回值类型为Student,但不需要写return 语句 Java自动返回,因为返回值类型必定为类本身,所以不需要编写返回值类型)
● 构造方法的调用:
○ 格式:new 方法名(形参列表);
● 对照-普通方法的调用:实例方法:(先创建对像)引用.方法名(形参列表); 类方法:类名.方法名(形参列表)
● 注意:
○ 当一个类中没有定义任何构造方法时,系统默认给该类提供一个无参构造方法,这个构造方法称为缺省构造器
○ 构造方法支持重载机制
○ 当一个类定义了任何一种构造方法,系统都将不在提供缺省构造器,建议手动定义无参构造方法,避免出错
○ 对象的创建就是利用其构造方法---new开辟空间,构造方法初始化
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/4c8fdd4a706a49e5a1537a193e2ca640.png)
6. 参数传值
Java中,方法中参数都是"传值"的,也就是方法中参数变量的值都是调用者指定的值的拷贝,改变方法参数的在值不会影响参数“传值”的变量的值,反之亦然(传值之后),类似于原件和复印件的关系。
7.this关键字(this:这个)
8. super关键字
9. static关键字
Java1.7之前静态变量放在Jvm方法区中1.7及之后静态成员变量存储在堆中,1.7之后方法区只有加载文件(Class),变量存储空间的只在堆或者栈中开辟
特点:在类加载的时候初始化,只存在一份,被所有对象共享(类只会被;加载器加载一次)
7后静态成员变量在堆中,方法区只存储Class文件
10. static代码块
● 语法格式:
○ static {
■ java语句;
○ }
● 静态代码块在类加载时执行且只执行一次
● 静态代码块在一个类中可编写多个(完全可以放到一个代码块里面),并且遵守之上而下的顺序依次执行
● 静态代码块的使用:与具体的需求有关,静态代码块是为Java程序员准备的一个特殊时期------类加载时刻
● 使用场景:读取配置文件时可以考虑使用这种方式
实例代码块
● 特点
○ 随着对象的加载而加载,先于构造函数执行
● 作用
○ 可以抽取构造函数中的共性内容
● 使用场景
○ 不使用
11. static方法
static方法与static变量的原理和目的几乎一致,当类的对象拥有相同的行为时,则可将该行为提升为类行为,静态方法在类加载的时候初始化,不需要创建对象,储存在方法区,被所有对象共享。
注意:静态方法无法调用实例方法和实例变量(无当前对象,静态方法加载的时候,对象还未创建,this关键字还不存在)
同一个对象中变量和方法的调用是通过this来调用的额,只是可以省略
static修饰方法多用于工具类中方法的修饰(工具类无成员变量,只有成员方法),以此减少对象的创建,节省空间。
//自定义数组工具类
public class Demo2 {
/**
* 作用:求数组最大值
* @param a
* @return
*/
public static int Max(int[] a){
int max = a[0];
for (int e:
a) {
if (max<= e) {
max = e;
}
}
return max;
}
/**
* 作用:求数组最小值
* @param a
* @return
*/
public static int Min(int[] a){
int min = a[0];
for (int e:
a) {
if (min<= e) {
min = e;
}
}
return min;
}
/**
* 作用:数组求和
* @param a
* @return
*/
public static int Sum(int[] a){
int sum = 0;
for (int e:
a) {
sum += e;
}
return sum;
}
}
//测试类
public class Test {
public static void main(String[] args) {
int[] a = {1,2,3,4,5};
int max = Demo2.Max(a);
int min = Demo2.Min(a);
int sum = Demo2.Sum(a);
System.out.println("最大值:"+max+"最小值:"+min+"和:"+sum);
}
}
七、继承
1. 概念
● 继承是面向对象的三大特征(封装,继承,多态)之一。
● 继承的基本作用:代码的复用,但是继承最重要的作用是---为“方法覆盖”和“多态机制”提供了可实现基础。Java语言只支持单继承
● 继承的语法格式:
○ 修饰符列表 class 类名 extends 父类名{
■ 类体;
○ }
● 继承中的一些术语:
○ B类继承A类,其中A类称为:父类,基类,超类,superclass
○ B类称为:子类,派生类,subclass
● 注意:
○ Java语言中子类继承父类会继承父类的全部特征,子类可以直接访问父类的非私有成员,间接访问父类的私有成员,当子类与父类存在相同的成员变量时,会根据就近原则访问。虽然java语言、只支持单继承法,但是一个类可以间接继承其他类。
○ 子类无法继承父类的构造方法,只能调用其父类的构造方法。
■ A extends B{
■ }
■ B extends C{
■ }
■ C extends D{
■ } ---------A类间接继续B,C,D类
● 多层继承 执行时根据Java的就近原则,多级父类中出现成员冲突的情况,谁离得近用谁(现在本类中找,本类中不存在再去一级父类中找...二级父类.....)
● Java语言中未显示继承任何类的类,默认继承JavaSE库提供的java.lang.object类
2. 方法重写/覆盖-overwrite
● 当父类中的方法已经无法满足当前子类的业务需求,子类有必要将父类中继承的方法进行重写,这个重新编写的过程称为方法重写。
● 重写条件:
○ 方法重写发生在具有继承关系的父子类之间
○ 方法写的时候:方法名相同,形参列表相同
○ 类重写的方法返回值权限要更小(兼容型返回)
○ 子类重写的方法,访问限权不能低,可以更高
○ 子类重新的方法,抛出异常不能更多,可以更少
○ 私有方法所以不能别重写。静态方法不存在重写,覆盖只针对方法
● 多用于迭代更新
八、多态
下面的"看左看右"其实就是Java的绑定机制,见知识深化
在实际开发中,使用多态时只是针对子类重写父类中的功能,不会去访问属性(属性一般private修饰)或子类特有的方法,用不到向下转型。
对象 instanceof 类型 判断指定对象是否为指定的类型
多态的作用: 降低程序耦合度提高程序扩展力,能使用多态就尽量使用多态
九、final关键字,访问控制权限修饰符,包
1. final关键字
● final是关键字,表示最终的,不可变的
● final修饰的类无法被继承
● fianl修饰的方法无法被覆盖
● final修饰的变量(基本数据类型)一旦被赋值后,不可重新赋值,(一般与static同时使用即:public static final 常量名;)(JVM会把final修饰的变量(基本数据类型)看成对应字面值的常量,并存储到常理池中)
○ final修饰的实例变量声明时必须手动赋值,不能采用系统默认值,但可以通过构造方法赋值。(变量被final修饰就变成了常量,常量不给值叫啥常量)
○ fianl修饰的局部变量声明时可以不复制,但一旦赋值不可改变。
■ fianl修饰的成员变量命名规范: 所有单词大写多个单词之间用_隔开
● final修饰的引用一旦指向一个对象后,不能在指向其他对象
● fianl通常和static同时使用来做选择,后面被枚举取代了
package day02.DuoTai;
import java.util.Scanner;
public class Demo4 {
//按住alt键,鼠标左键下拉,会同时出现多个编辑光标
public static final int HUA_FEI_CHONG_ZHI = 1;
public static final int HUA_FEI_CAH_XUN = 2;
public static final int LIU_LIANG_CHA_XUN = 3;
public static final int KUA_NDAI_YE_WU = 4;
public static final int REN_GONG_FU_WU = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("1.话费充值");
System.out.println("2.话费查询");
System.out.println("3.流量查询");
System.out.println("4.宽带业务");
System.out.println("0.人工服务");
int choose = scanner.nextInt();
switch (choose) {
case HUA_FEI_CHONG_ZHI:
System.out.println("1.话费充值");
break;
case HUA_FEI_CAH_XUN:
System.out.println("2.话费查询");
break;
case LIU_LIANG_CHA_XUN:
System.out.println("3.流量查询");
break;
case KUA_NDAI_YE_WU:
System.out.println("4.宽带业务");
break;
case REN_GONG_FU_WU:
System.out.println("0.人工服务");
break;
}
}
}
2. 访问控制权限修饰符
● 访问控制限权修饰符用来控制元素访问限权
● 修饰符包括:
○ public 表示公开的,在任何位置都可以访问
○ protected 表示受保护的,在同一个包中访问但可以被子类继承(注意是继承,不同包的子类也可以继承)
○ 缺省(无修饰符) 只能在同一个包中访问
○ private 表示私有的,只能在本类中访问
● 上述修饰限权及用于变量的修饰也用于方法的修饰
● 注意类只能采用public和缺省修饰符进行修饰(内部类除外)
3. 包
十、抽象类和接口
1.抽象类
● 抽象类是具有共同特征的类之间的抽象,由于类本身是不存在的,所以无法实例化(创建对象)抽象类仍属于引用数据类型。
● 抽象类定义的语法格式:
○ 修饰符列表 abstract class 类名{
■ 类体;
○ }
● 抽象类使用场景:当定义父类的时候,发现有些方法在父类中没有办法描述,此时可以将该方法定义成抽象方法,由于抽象方法必须在抽象类中,所以类也必须抽象。(起到定义规划的作用,强制子类必须重写该抽象方法,否则报错,符合多态的语法)
● 抽象类是用来继承的(因此abstract不能与fianl和private同时使用)
● 抽象类不能实例化但有构造方法,构造方法无法继承但创建子类对象一定会先调用父类的构造方法
● 抽象类中可以没有抽象方法但抽象方法必须出现在抽象类中
● 抽象方法:public abstract void doSome(); 没有方法体有分号";"结尾,前面的修饰符列表中有abstract关键字 抽象方法也是用来继承的所以也不允许与fianl和private共存,而protected修饰符可以允许子类访问,可以由其修饰
● 抽象类的继承问题
○ 抽象类可以被抽象了继承,但任然不能创建对象(抽象类没有具体的行为无法被实例化)
○ 抽象类可以被非抽象类继承,需要注意的是非抽象类在继承抽象类时,若抽象类中有抽象方法,非抽象子类必须重写抽象父类中全部的抽象方法(接口中不在叫“继承”了而是叫“实现”有关键字”implements“修饰,见下节
2. 接口
接口通过 interface 关键字来定义,它可以包含一些常量和方法
public interface Electronic {
// 常量
String LED = "LED";
// 抽象方法
int getElectricityUse();
// 静态方法
static boolean isEnergyEfficient(String electtronicType) {
return electtronicType.equals(LED);
}
// 默认方法
default void printDescription() {
System.out.println("电子");
}
}
段代码反编译后如下:
public interface Electronic{
public abstract int getElectricityUse();
public static boolean isEnergyEfficient(String electtronicType){
return electtronicType.equals("LED");
}
public void printDescription(){
System.out.println("\u7535\u5B50");
}
public static final String LED = "LED";
}
由上可知:
- 接口中定义的变量会在编译的时候自动加上 public static final
- 修饰符所以接口中的变量可以用来作为常量类使用,不过这种选择并不可取。因为接口的本意是对方法进行抽象,而常量接口会对子类中的变量造成命名空间上的“污染”。
- 没有使用 private、default 或者 static 关键字修饰的方法是隐式抽象的,在编译的时候会自动加上public abstract修饰符也就是这些方法是抽象方法,没有方法体。
- 从 Java 8 开始,接口中允许有静态方法,比如说上例中的isEnergyEfficient()方法。静态方法无法由(实现了该接口的)类的对象调用,它只能通过接口名来调用。接口中定义静态方法的目的是为了提供一种简单的机制,使我们不必创建对象就能调用方法,从而提高接口的竞争力。
- 从 Java 8 开始接口中允许义default 方法,比如说上例中的 printDescription()方法,它始终由一个代码块组成,为实现该接口而不覆盖该方法的类提供默认实现。
- 默认方法的好处(可以被实现类继承)
- 一个接口可能有多个实现类,这些类就必须实现接口中定义的抽象方法,否则编译器就会报错。假如需要在所有的实现类中追加某个具体的方法,在没有default 方法的帮助下,就必须挨个对实现类进行修改。
- 由上述分析可以得出下面这些结论:
- 接口中允许定义变量
- 接口中允许定义抽象方法
- 接口中允许定义静态方法(Java 8 之后)
- 接口中允许定义默认方法(Java 8 之后)
- 接口不允许直接实例化,否则编译器会报错。
- 接口中不存在构造方法。
- 需要定义一个类去实现接口,见下例。
public class Computer implements Electronic {
public static void main(String[] args) {
new Computer();
}
@Override
public int getElectricityUse() {
return 0;
}
}
//然后再实例化。
Electronic e = new Computer();
- 接口可以是空的,既可以不定义变量,也可以不定义方法。最典型的例子就是 Serializable 接口,在 java.io 包下。
- public interface Serializable {}
- Serializable 接口用来为序列化的具体实现提供一个标记,也就是说,只要某个类实现了 Serializable 接口,那么它就可以用来序列化了。
- 接口不能被final 关键字修饰,否则会报编译错误,因为接口就是为了让子类实现的,而 final 阻止了这种行为。
- 接口的抽象方法不能是 private、protected 或者 final,(就是接口中的抽象方法只能是public)否则编译器都会报错。
- 接口的变量是隐式 public static final(常量),所以其值无法改变。
3.接口的作用
- 第一,使某些实现类具有我们想要的功能,比如说,实现了 Cloneable 接口的类具有拷贝的功能,实现了 Comparable 或者
Comparator 的类具有比较功能。Cloneable 和 Serializable 一样,都属于标记型接口,它们内部都是空的。实现了
Cloneable 接口的类可以使用 Object.clone() 方法,否则会抛出 CloneNotSupportedException。
public class CloneableTest implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneableTest c1 = new CloneableTest();
CloneableTest c2 = (CloneableTest) c1.clone();
}
}
运行后没有报错。现在把 implements Cloneable 去掉。
public class CloneableTest {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneableTest c1 = new CloneableTest();
CloneableTest c2 = (CloneableTest) c1.clone();
}
}
运行后抛出 CloneNotSupportedException:
Exception in thread "main" java.lang.CloneNotSupportedException: com.cmower.baeldung.interface1.CloneableTest
at java.base/java.lang.Object.clone(Native Method)
at com.cmower.baeldung.interface1.CloneableTest.clone(CloneableTest.java:6)
at com.cmower.baeldung.interface1.CloneableTest.main(CloneableTest.java:11)
- 第二,Java 原则上只支持单一继承,但通过接口可以实现多重继承的目的。
- 如果有两个类共同继承(extends)一个父类,那么父类的方法就会被两个子类重写。然后,如果有一个新类同时继承了这两个子类,那么在调用重写方法的时候,编译器就不能识别要调用哪个类的方法了。这也正是著名的菱形问题,见下图。
接口没有这方面的困扰。来定义两个接口,Fly 接口会飞,Run 接口会跑。
public interface Fly {
void fly();
}
public interface Run {
void run();
}
然后让 Pig 类同时实现这两个接口。
public class Pig implements Fly,Run{
@Override
public void fly() {
System.out.println("会飞的猪");
}
@Override
public void run() {
System.out.println("会跑的猪");
}
}
- 在某种形式上,接口实现了多重继承的目的:现实世界里,猪的确只会跑,但在雷军的眼里,站在风口的猪就会飞,这就需要赋予这只猪更多的能力,通过抽象类是无法实现的,只能通过接口。
- 第三,实现多态。
- 通俗的理解,多态就是同一个事件发生在不同的对象上会产生不同的结果,鼠标左键点击窗口上的 X 号可以关闭窗口,点击超链接却可以打开新的网页。
- 多态可以通过继承(extends)的关系实现,也可以通过接口的形式实现。
//Shape 接口表示一个形状。
public interface Shape {
String name();
}
//Circle 类实现了 Shape 接口,并重写了 name() 方法。
public class Circle implements Shape {
@Override
public String name() {
return "圆";
}
}
// Square 类也实现了 Shape 接口,并重写了 name() 方法。
public class Square implements Shape {
@Override
public String name() {
return "正方形";
}
}
//测试类。
List<Shape> shapes = new ArrayList<>();
Shape circleShape = new Circle();
Shape squareShape = new Square();
shapes.add(circleShape);
shapes.add(squareShape);
for (Shape shape : shapes) {
System.out.println(shape.name());
}
- 测试结果:
- 圆
- 正方形
- 也就意味着,尽管在 for 循环中,shape 的类型都为 Shape,但在调用name() 方法的时候,它知道 Circle 对象应该调用 Circle 类的 name() 方法,Square 对象应该调用Square 类的 name() 方法。
- 这就实现了多态,变量 circleShape、squareShape 的引用类型都是
Shape,但执行 shape.name() 方法的时候,Java 虚拟机知道该去调用 Circle 的 name() 方法还是 Square 的 name() 方法。- 多态存在的 3 个前提:
- 要有继承关系,比如说 Circle 和 Square 都实现了Shape 接口。
- 子类要重写父类的方法,Circle 和 Square 都重写了 name() 方法。
- 父类引用指向子类对象,circleShape 和 squareShape 的类型都为 Shape,但前者指向的是 Circle对象,后者指向的是 Square 对象。
十一、内部类
- 内部类:在类的内部有定义了一个新的类
- 内部类分类:
- 静态内部类 - 类似于静态变量
- 实例内部类 - 类似于实例变量
- 局部内部类 - 类似于局部变量
- 匿名内部类 - 没有名字的类
- 注意:内部类编写的代码,可读性差,能不使用尽量不用
- 匿名内部类(重点)
- 匿名内部类是内部类的一种,因类中没有名字所以叫匿名内部类,匿名内部类是最常用的内部类
- 匿名内部类:复杂,可读性差,没有名字不能重复调用
- 定义格式
new 接口/抽象类名(){
重写全部抽象方法;
}- 案例:
- 实例内部类(重点了解)
- 内部类可以直接使用外部类的成员,省去参数的传递的过程,当一个类仅需要在另一个类中使用并且需要使用另一个类中的数据时,采用内部类中的写法,开发过程中很少见到,JDK源码中多用这种形式。
- 对象创建格式
//创建外部类的对象
外部类名 外部对象名 = new 外部类名();
//创建内部类的对象
内部类名 内部对象名 = 外部对象名.new 内部类名();- 静态内部类(了解)
- 静态内部类只能访问外部类中的静态资源,有对象创建格式可知,创建静态内部类时,并未创建外部类对象
- 内部类成员变量与外部类成员变量冲突情况
如果 Outer 和 Inner 类具有相同的成员变量或方法, Inner 类默认访问自己的成员变量或方法(就近原则),如果要访问 Outer 类的成员变量,可以使用 this 关键字
- 格式:外部类名.this.成员变量名
Java中this关键字的完整写法为 类名.this 不写默认为本类类名.this- 注意:内部类在访问外部类的局部变量的时候,局部变量会被final修饰。
- 原因:
- 外部类的局部变最属于当前方法的,内部类的方法和当前方法,其实在栈中开辟的是独立的栈空间,数据相互看不到的,但是由于内部类的特点,可以访问外的我们确定跨方法访问,原因是因为JVM给这些访问到的局部变量前面加了final关键字,将其移动到常量池中,所以两个不同区间的方法却可以访问数据
十一、Lambda表达式
1. 格式
(参数列表) -> {
被重写方法的方法体代码;
}
lambda表达式用于简化函数式接口匿名内部类的书写,简化规则:不需要new对象,直接重写方法即可
在使用Lambda表达式时,必须先有一个接口,而且接口中只能有一个抽象方法。这样的接口,称为函数式接口,只有基于函数式接口的匿名内部类才能被Lambda表达式简化。
public interface Swimming{
void swim();
}
//
public class LambdaTest1 {
public static void main(String[] args) {
// 目标:认识Lambda表达式.
//1.创建一个Swimming接口的匿名内部类对象
Swimming s = new Swimming(){
@Override
public void swim() {
System.out.println("学生快乐的游泳~~~~");
}
};
s.swim();
//2.使用Lambda表达式对Swimming接口的匿名内部类进行简化
Swimming s1 = () -> {
System.out.println("学生快乐的游泳~~~~");
};
s1.swim();
}
}
Lambda表达式特征
lambda必须面向接口而且要求接口中有且只能有一个抽象方法才行 , lambda生成的字节码直接在内存中, 不会在硬盘上留下物理文件。
有且只能有一个抽象方法的接口又叫函数式接口(对静态方法和默认方法的数量无要求)
lambda重心是方法重写
2. 简化格式
1.Lambda的标准格式
(参数类型1 参数名1, 参数类型2 参数名2)->{
...方法体的代码...
return 返回值;
}
2.在标准格式的基础上()中的参数类型可以直接省略
(参数名1, 参数名2)->{
...方法体的代码...
return 返回值;
}
3.如果{}总的语句只有一条语句,则{}可以省略、return关键字、以及最后的“;”都可以省略
(参数名1, 参数名2)-> 结果
4.如果()里面只有一个参数,则()可以省略
参数名->结果
3. 方法引用
基于lambde,当lambde表达式重写函数式接口的抽象方法时,发现重写的方法体在其他类中存在时,可以使用方法引用简化书写
方法引用只是使用方法的一种方式,并不是必须得用,所以要求能看懂别人代码即可,自己喜欢用就用没有强制要求
/*
格式:
实例方法:
对象名::方法名
静态方法:
类名::方法名
注意:此方法只能用于被调用类和重写方法无参数,或者参数个数与参数类型与重写方法的格式和类型一致
即:参数列表一致(参数类型可通过泛型限定从而实现参数统一)
*/
//函数式接口
public interface Demo1 {
void show();
}
//另一个类
public class BBB {
public void bbb(){
System.out.println("BBB中的实例方法bbb");
}
public static void bbbb(){
System.out.println("BBB中的静态方法bbbb");
}
}
//测试类
public class Test {
public static void main(String[] args) {
/*
Demo1 demo1 = ()->new BBB().bbb();
demo1.show
*/
//Demo1的实现类重写的show方法调用BBB类中的bbb方法
Demo1 demo1 = new BBB()::bbb;
demo1.show();
//Demo1的实现类重写的show方法调用BBB类中的bbbb方法
Demo1 demo2 = BBB::bbbb;
demo2.show();
}
}
特定类型的方法引用
/*
Java约定:
如果某个Lambda表达式里只是调用一个实例方法,并且前面参数列表中的第一个参数作为方法的主调,
后面的所有参数都是作为该实例方法的入参时,则就可以使用特定类型的方法引用。
格式:
类型::方法名
*/
//函数式接口
public interface A {
voidmethod(ErShouCheSeller erShouCheSeller, String brand, int price, String state);
}
//另一个类
public class ErShouCheSeller {
public void introduceCar(String brand, int price, String state){
System.out.println("我的车是:"+brand+"品牌,全国扎实的牌子, 可以传三代的");
System.out.println("目前售价"+price+",全郑州二手车最低价,欢迎比价~~~");
System.out.println("我的车目前状态是:"+state+",支持所有检测, 假一赔十~~");
}
}
//测试类
public class Test1 {
public static void main(String[] args) {
/* A a = new A() {
@Override
public void method(ErShouCheSeller erShouCheSeller, String brand, int price, String state) {
erShouCheSeller.introduceCar(brand,price,state);
}
};*/
// A a = (erShouCheSeller, brand, price, state) -> erShouCheSeller.introduceCar(brand,price,state);
A a = ErShouCheSeller::introduceCar;
a.method(new ErShouCheSeller(), "本田crv",100000,"行驶2W公里,无泡水,无大事故");
}
}
构造方法的引用
/*
引用别人构造:
使用场景:
要求函数式接口的抽象方法的返回值是一个自定义类型, 参数刚好和这个类型的构造的参数吻合, 就可以引入这个类型构造函数
格式:
类型::new
*/
//函数式接口
public interface A {
Student method(String name, int age);
}
//测试类
public class Test {
public static void main(String[] args) {
/*A a = new A() {
@Override
public Student method() {
return new Student();
}
};*/
// A a = () -> new Student();
A a = Student::new;
Student method = a.method("aaa",18);
System.out.println(method);
}
}
//另一个类
public class Student {
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
十二、正则表达式
1. 书写规则
/**
* 目标:掌握正则表达式的书写规则
*/
public class RegexTest2 {
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
}
}
2. Pattern(正则)对象
//爬取数据
package day06;
/*
正则表达式:
检验字符串是否符合正则
字符串.matches(正则);
爬取数据的能力
正则对象.matcher(字符串);
(1) 如何将正则的字符串变成正则的对象
Pattern: 这个类就是正则
第一步:
Pattern静态方法compile(String regExp); 得到正则对象
第二步:
通过正则的对象调用matcher(String 要查询的内容); 返回一个集合Matcher
第三步:
遍历集合:
boolean find(); 判断集合中是否还有内容
String group(); 取出一组内容
*/
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Demo3 {
public static void main(String[] args) {
String content = "来黑马程序员学习Java,\\n” +\u000B “" +
" 电话:1866668888,18699997777\\n” +\u000B “ " +
" 或者联系邮箱:boniu@itcast.cn,\\n” +\u000B “ " +
" 座机电话:01036517895,010-98951256\\n” +\u000B “ " +
" 邮箱:bozai@itcast.cn,\\n” +\u000B “ " +
" 邮箱2:dlei0009@163.com,\\n” +\u000B “ " +
" 热线电话:400-618-9090 ,400-618-4000,4006184000,4006189090”";
//1.定义String类型正则
String reqExp = "1[356789]\\d{9}|\\w{3,}@\\w{3,}\\.\\w{2,}|[\\d]{3}[-]?[\\d]{4}[-]?\\d{4}|[\\d]{3}[-]?[\\d]{3}[-]?\\d{4}";
//2.将String类型的正则封装成正则对象
Pattern pattern = Pattern.compile(reqExp);
Pattern pattern1 = Pattern.compile(reqExp);
// 3.正则对象有matcher方法, 可以去字符串中找满足正则的内容 并放到集合中
Matcher matcher = pattern.matcher(content);
//遍历集合
/*
boolean find() : 帮我们查找集合中是否还有没有取出来的满足条件的内容, true代表还有
String group() : 取出一组满足条件的内容
*/
while (matcher.find()){
String str = matcher.group();
System.out.println(str);
}
}
}
3. 贪婪和非贪婪匹配
package day06;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/*
贪婪匹配和非贪婪匹配原则
最长匹配原则和最短匹配原则
?:除了作为模糊量词,还有和其他量词组成非贪婪情况
贪婪匹配取一行中最长
*/
public class Demo4 {
public static void main(String[] args) {
String str = "欢迎张全蛋光临本系统!他删库并跑路," +
"欢迎李二狗子光临本系统!” +\u000B " +
" “欢迎马六子光临本系统!它浏览了很多好看的照片!" +
"欢迎夏洛光临本系统!他在六点钟购买了一台拖拉机!";
String regx = "欢迎.+?光临"; //非贪婪匹配
Pattern pattern = Pattern.compile(regx);
Matcher matcher = pattern.matcher(str);
while (matcher.find()){
String group = matcher.group();
int i = group.indexOf("光临");
String substring = group.substring(2, i);
System.out.println(substring);//张全蛋 李二狗子 马六子 夏洛
}
String regx = "欢迎.+光临";//贪婪匹配
Pattern pattern = Pattern.compile(regx);
/*匹配到的为:欢迎张全蛋光临本系统!他删库并跑路,欢迎李二狗子光临本系统! " +
欢迎马六子光临本系统!它浏览了很多好看的照片!欢迎夏洛光临
(匹配一行中的最长内容,以换行符\r\n为一行)
*/
}
String str = "欢迎张全蛋光临本系统!他删库并跑路,\r\n" +
"欢迎李二狗子光临本系统!” +\u000B \r\n" +
" “欢迎马六子光临本系统!它浏览了很多好看的照片!\r\n" +
"欢迎夏洛光临本系统!他在六点钟购买了一台拖拉机!\r\n";
String regx = "欢迎.+?光临"; //贪婪匹配
Pattern pattern = Pattern.compile(regx);
Matcher matcher = pattern.matcher(str);
while (matcher.find()){
String group = matcher.group();
int i = group.indexOf("光临");
String substring = group.substring(2, i);
System.out.println(substring);//张全蛋 李二狗子 马六子 夏洛
}
}
4. 字符串中的正则方法
/*
字符串跟正则有关的功能
String[] split(String regex) 这个方法是一个正则方法, 参数要的是正则表达式, 使用他的时候小心.代表匹配所有字符
String replace(CharSequence target, CharSequence replacement) 不是正则方法
String replaceAll(String regex, String replacement)这个方法是一个正则方法, 参数要的是正则表达式, 使用他的时候小心.代表匹配所有字符
*/