1. 概述
- 每一个应用程序都以类名开头,类名必须与文件名匹配,所以保存文件时,要使用类名保存;
- java中运行的每一行代码都必须在一个
class
中; - 在类名的开头,可以用权限修饰符来解释,如
private
或者public
; main()
方法是必需的,每一个Java程序都有;main()
方法中的任何代码都会被执行;- 对于
xxx
类中的main()
方法中的方法(函数
),相当于嵌套调用; -
可以使用
println()
方法将一行文本打印到屏幕上,如:public static void main(String[] args) { System.out.println("Hello World"); }
- 同样,与c++一致,大括号表示代码块的开始和结束;
- 每个代码语句必须用分号结尾;
2. 注释
- 单行注释以
//
开头,java将默认忽略//
和行尾之间的任何文本; - 多行注释以
/*
开头,*/
结尾,同样两者之间的文本会被Java忽略;
3. 变量类型
- 变量是存储数据的容器;
- Java的变量类型有:
string
:文本,字符串;int
:有符号数;float
:浮点数;char
:存储单个字符,通过"……"
的形式接收,当然'……'
也行;boolean
:存储布尔值,有true
和false
两种;
- 建立变量:
- 指定类型 (+ 赋值);
type variable (= value)
; - 一般情况下,新的赋值会覆盖掉旧的赋值,如果要避免这种情况,可以使用
final
关键字声明在变量定义前面,如:final int num=1;
- 指定类型 (+ 赋值);
- 我们可以使用
System.out.println()
的方式用于显示变量; - 如果要一次性声明多个同样类型的变量,可以用
,
隔开,如int i1=1,i2=2;
- java的变量名不应包含空格;
- 理论上可以使用
$
和_
开头; - java的名称区分大小写;
- system默认的关键字不要用做变量名,不然会覆盖掉原有设置好的;
4. 数据类型
- 基本数据类型
byte
,short
之类的; - 非基本数据类型
String
,Arrays
和Classes
;
- 基本八种:
数据类型 大小 描述 byte 1B 有符号二进制数 short 2B 范围小有符号数 int 4B 范围适中的有符号数 long 8B 范围较大的有符号数 float 4B 范围适中的浮点数 double 8B 范围较大的浮点数 boolean 1b,即一位 存储 true
和false
char 2B 存储单个字符 - 对对应数据类型赋值时,应当在数值末尾加上对应的类型符,如
long---L
,float---f
,double---d
; - boolean型的取值只能是
true
或者false
; - 浮点数用
e
表示10的幂,如12e9=12*10^9
; - 字符的要求,赋值时推荐:字符用单引号,字符串用双引号;
注意,字符串的string要写成大写的String,而且它不是原始数据类型,因为使用它的时候需要引用到一个对象;
- 创建的原始类型始终会有数值,而非原始类型则可以以
null
填充; - 原始类型的大小取决于数据类型,而非原属类型的大小都是相同的(
相对固定
);
5. 数据类型转换
- 隐式类型转换
- 自动完成,思路是:通过以零补位的形式实现;
byte-->short-->char-->int-->long-->float-->double
;
- 强制类型转换
- 手动完成,需要强制转换符
(……)
如:int myInt = (int)myDouble;
- 顺序与上边相反的,将较小的类型转换为较小的类型,需要使用强制转换;
- 手动完成,需要强制转换符
6. 运算符
- 用于对变量和值执行操作;
- 包括:
- 算术运算符;
- 赋值运算符;
- 关系运算符;
- 逻辑运算符;
- 位运算符;
%
取模;++
自加1,--
自减1;+=
自加,-=
自减,/=
自除,|=
位或运算,^=
异或运算,&=
位并运算,>>=无符号部分右移得到移码
,无符号部分左移得到移码
;==
等于,!=
不等于;&&
逻辑与、||
逻辑或、!
逻辑非;&
位交,|
位并,~
位取反,^
位异或,<<
左移码,>>
右移码,>>>
右移码并补零;
7. 字符串
- 用字符串结构
String
来存储文本,用双引号包围的字符来传值; - 关于字符串有以下几种方法:
- 字符串长度:
.length()
返回; - 大小写转换:
toUpperCase()
将字符串转变成大写,toLowerCase()
将字符串转变成小写; - 查找字符串第一次出现的位置:
indexOf()
返回第一次出现的位置(学了数据结构的都知道从零开始
); - 串联:用
+
可以连接前后两个字符串、又或者使用<前者>.concat(<后者>)
的方法;
- 字符串长度:
- 特殊字符必须写在引号内,否则Java会误解此字符串的性质,并生成错误。
- 为了避免’,",\被系统误以为有实际功能,可以使用反斜杠
\
将特殊字符转换为字符串; - 除此以外,还有一些特殊的转义字符:
\n
:换行;\r
:回车;\t
:制表;\b
:空格;\f
:换页;
注意:如果是一个数字和一个字符串执行
+
操作,则数字部分会被强制转化为字符串进行运算。
- 更多关于字符串的方法在Java 字符串方法参考手册
8. 数学方法(内置)
用
Math
类的方法
max(x,y)
:用于查找x
和y
的最大值;min(x,y)
:用于查找x
和y
的最小值;sqrt(x)
:用于返回x的平方根;abs(x)
:用于返回x的绝对值;random()
:返回一个介于[0.0,1.0)的随机数,至于如果你要0到10^k之间的随机数,可以用上述随机数乘以10^k+1
来获得;
- 其他的数学方法可以在Java 数学方法参考手册 中查询获得;
所有的数学方法都是
static
静态的;
9. 条件语句
- 使用方法基本和C++差不多;
- 以下介绍三元运算符:
其中前者的表达式是条件为真使用的,后边是条件为假使用的。variable=(condition)? expressionTrue:expressionFalse;
10. 选择语句
- 即
switch……case
型; - 格式为:
switch(expression){ case x: 代码块1; (break;) case y: 代码块2; (break;) ···· (default: 代码块n;) }
- 意思为:
switch
先计算一次;case
将计算的结果和每个case
后的情况进行条件比较。如果符合的话执行后边相关的代码块;break
、default
是可有可无的;
- 中断关键字
- break:停止在块内执行,并跳出所在的程序块;
- default设置默认值兜底;
- 关键点:
expression
必须是整数、字符、字符串、枚举或者是它们的表达式;break
用于跳出switch
语句,没有它会导致继续执行下一个case
代码块。所以通常在每一个case
语句的末尾添加break
;default
少用,建议在处理不期望的输入时使用它。
11. 循环语句
- 基本和c++的一样;
-
for(代码块第一次执行前执行一次;判断代码块是否执行的条件;代码块执行后执行){ 代码块 }
- 除此以外,还有
for-each
循环,它专门用于循环在数组Array
中的元素。
典型的有:X型数组A=(A1,A2,...); for (X型值i : A){ 代码块 }
String[] cars = {"Volvo", "BMW", "Ford", "Mazda"}; for (String i : cars) { System.out.println(i); }
break
跳出循环,continue
跳出本轮循环。他们俩常配置着循环语句使用。
12. 数组
- 用方括号定义变量类型并声明数组:
数组类型[] 数组名;
- 数组赋的值框在大括号
{···}
里。 - 与C++一样,访问元素用索引,索引从0开始;
- 更改数组元素,可以用索引赋值;
- 数组有方法
.length
可以返回数组有多少个元素; for
循环常用于遍历数组,不过for-each
可读性更强,更推荐使用;- 可以通过将数组当成一个元素嵌套到新的数组的方式实现多维数组,如{{1,2},{3,4}}且索引方式也是一级一级打开; 对于多维数组的遍历,多个for循环是不错的方法;
13. 方法(函数)
- 由于java所有的文件都是类文件(
有大有小
),所以方法必须在类里边申明。 - 方法使用名称来定义的,后面跟上{代码块};
- 方法的调用通过方法名配合必要的参数;
- 一个方法可以被多次调用;
- 可以在方法名后增加
()
的方式来传递参数,申明时也要标准形参的数据类型,不同参数之间用,
隔开。 - 当参数被传递给方法后,就从形参转化为实参
也就是它被实体化了。 - 在申明方法时,我们会顺便申明返回值的情况。此处可以用返回值的数值类型说明。与C++一样,返回值用
return
传递。如果是不需要返回值,用void
申明;
14. 重载
- 使用方法重载,可以使多个方法拥有相同的名称和不同参数,便于理清思路和展示联系;
- 与其定义两个应该做相同事情的方法,不如直接重载一个;
- 这里就体现java的方法都在类里定义的好处了。重载不需要再像C++一样有特殊的
operator
运算符,直接下边重写就可以了。 - 只要参数的数量或者类型不同,多个方法就可以具有相同的名称。
15. 作用域
- 在Java中,变量只能够在创建的区域内访问,区域称之为作用域;
- 方法内申明的,申明语句后的语句都可以使用;
- 代码块内申明的变量,只能够由大括号之间的代码访问,且在申明后方可使用;
16. 递归
- 对于函数来说,是自己传值给自己,以实现数学上的递归或者说回归,最后企图得到(可能存在的)不动点。
- 为了防止递归没完没了,显然递归函数都必须要有合理的停止条件,即必须要有能够跳出循环的判断条件;
17. OOP/面向对象编程
- 即同时创建包含的数据和函数的对象,以对象为操作和访问的基本逻辑单位;
- 有利于多态,封装和模块化,提供了清晰的结构。
- 体现了局部性原理的思想;
- 类是对象的模板,对象是类的实例;
- 对象将继承类中的所有变量和方法;
18. 类
- 在java的语法中,类始终以大写字母开头,类名应该要与文件名相匹配。
- 与C++一样,类的申明由
class
关键字开始,类的权限则出现在一开头class
的左边。 - 对象的创建:
其中等号的后半部分是为了申明该对象需要分配的物理存储空间;类名 对象名 = new 类名(可能的传参);
- 一个类可以创建多个对象。
- 还可以创建一个类的对象并在另一个类中访问它。这有利于更快地组织类(一个类拥有所有的方法和属性(
变量
),另一个类则含有main()
方法;
在命令行中,需要用:
javac 对象名.java//编译文件 java 对象名//执行文件
- 对于类中的变量,与C++一直,可以使用
.
取值符来访问,而且可以在新生成的对象中修改它,但只对对象自己有效。这样看来,类中赋值了的属性,字段或者说变量更像是default默认值一般的存在。 - 如果你不想类中的属性值随意的被对象更改掉,可以在申明的时候添加
final
关键字锁死。
19. 类方法
- 调用格式:
对象名.方法名();
- 经常可以看到具有
static
静态和public
公共属性和方法的Java程序; -
static
:可以在不创建类的对象的情况下访问该方法,例如Math
中的数学计算方法都是static
的就是这个原因;public
:只能用对象来访问方法;
- 为了使用
public
的类方法,我们必须先创建对象再调用。 .
用于访问对象的属性和方法。- 将方法实行和方法建立分散到对应的类和类中的对象中,是很不错的技巧。
20. 构造函数
- 是一种用于初始化对象的特殊方法;
- 在创建类对象的时候会调用构造函数;
- 它常用于设置对象属性的初始值,等于说原来类的属性是没赋值的,现在创建对象的时候再赋值;
- 【注意】:构造函数的名称必须与类名相匹配,且不能有返回值,最好直接是
void
; - 但理论上,所有类在创建对象的时候都是有构建函数,只是类中赋值的对象无法设置对象属性的初始值;
- 典型例子:
public class MyClass { int x; public MyClass(int y) { x = y; } public static void main(String[] args) { MyClass myObj = new MyClass(5); System.out.println(myObj.x); } } // 输出 5
21. 修饰符
- 用于设置类、属性、方法和构造函数的访问级别;
- 一般分为两类:
- 访问修饰符:控制访问级别;
- 非访问修饰符:不控制访问级别,提供其他功能;
- 对于
class
常用的修饰符有:- 访问修饰符
public
:任何类都可以访问;default
:只能由同一包中的类访问(一般出现在不指定修改器
);
- 非访问修饰符
final
:该类不能被其他类继承(和用在属性上一样,表示不改了
);abstract
:该类被抽象了出来,自身不能创建对象,只能通过继承的方式创建;
- 访问修饰符
- 对于
attribute
或者means
和constructor
来说:- 访问修饰符
public
:所有类都可以访问;private
:只能该类内部访问,即通过类的方法,其他类无法直接访问属性(便于实现封装、隐藏内部实现细节,提高代码的安全性和可维护性
);default
:该类只能由同一包中的类进行访问,在不指定修改器时使用;protected
:代码可以在相同包,和子类中访问;
- 非访问修饰符
final
;static
:属性和方法属于类而不是对象;abstract
:只能在抽象类中使用,且只能在方法上使用,即主体部分只能由生成的对象提供。transient
*:序列化包含属性和方法的对象时,将跳过属性和方法- 在Java中,序列化(Serialization)是指将对象的状态转换为字节流的过程,从而可以将对象保存到文件、数据库或通过网络传输给其他Java虚拟机(JVM)。反序列化(Deserialization)是将字节流转换回对象的过程。序列化的主要作用是持久化对象状态和对象之间的远程通信。
- 关键点:
- 实现Serializable接口:要使一个Java对象可以序列化,该类必须实现java.io.Serializable接口。
- 序列化过程:使用ObjectOutputStream类的writeObject()方法将对象转换为字节流。
- 反序列化过程:使用ObjectInputStream类的readObject()方法将字节流转换回对象。
- transient关键字:用transient关键字修饰的字段不会被序列化。
volatile
:属性值不是本地缓存的线程,总是从主存中读取- 用于修饰变量,确保在多个线程间对该变量的读写操作的可见性。它的主要作用是保证变量的可见性和防止指令重排序。
主要用途:- 变量的可见性:
- 当一个变量被声明为volatile时,Java内存模型保证所有线程都能看到该变量的最新值。当一个线程修改了volatile变量的值,新值会立即被刷新到主内存中,其他线程读取时可以立即获得最新值。
- 防止指令重排序:
- volatile变量在读取和写入时都会插入内存屏障(memory barrier),这可以防止编译器和处理器对这些操作进行重排序,从而保证了代码执行的顺序一致性。
- 变量的可见性:
- 用于修饰变量,确保在多个线程间对该变量的读写操作的可见性。它的主要作用是保证变量的可见性和防止指令重排序。
- 访问修饰符
22. 封装
- 步骤:
- 将类变量/属性声明为
private
; - 提供公共
get
和set
方法来访问和更新private
私有变量的值; get
方法返回变量值,set
方法设置值,两者的语法都是以get
或set
开头,后跟变量名,第一个字母大写;
- 将类变量/属性声明为
- 意义:
- 类属性可以设置为只读(
如果只使用get方法
),也可以设置为只写(如果只使用set方法
); - 提高数据的安全性;
- 可以在不影响其他部分的情况下更改代码的一部分;
- 类属性可以设置为只读(
23. 包(package
)
- Java 中的包用于对相关类进行分组。可将其视为文件目录中的文件夹;
- 我们使用包来避免名称冲突,并编写更好的可维护代码。
- 分为两类:
- 内置包(来自Java API的包);
- 用户定义的包(创建自己的包);
API
- Java API 是Java开发环境中包含的一个预编写类库,该库分为包和类,可以免费使用;
- 完整列表可在Oracles网站上找到;
- 该库包含用于管理输入、数据库编程等的组件;
- 要使用库中的类和包,需要使用import关键字;
- 使用
- 导入
- 导入包中的某个类:
import <packagename>.<classname>
,而且要使用导入的类需要创建该类的对象; - 导入整个包:
import <packagename>.*
;
- 导入包中的某个类:
- 创建自己的包
- 使用
package
关键字; - 格式:
package <自定义包名> class <自建类>{……}
- 将上面的文件另存为后编译,这将强制编译器创建"自定义包名"的包
-d 关键字指定保存类文件的目标位置,空格后可以直接跟地址也可以直接跟
.
表示在同一目录下(linux
)。 - 要使用创建包中的类,在命令行中使用
java <自定义包名>.<自建类>
;
- 使用
- 导入
24. 继承
- Java 中可以将属性和方法从一个类继承到另一个类。
- 继承分为两部分:
- 子类 (Subclass) - 子,从另一个类继承的类;
- 超类 (Superclass) - 父,被继承的类;
- 要从类继承,请使用
extends
关键字。
总结:访问权限
private
:仅在同一个类可访问;default
(无修饰符):仅在同一个包中可访问;protected
:在同一个包,以及不同包的子类中可以访问;public
:在任何地方都可以访问;
【注意】:protected
成员在子类中可以重写,但在子类外部(即使是子类的实例)不能直接访问这些成员。
- 子类对超类的继承,可以使用
protected
进行安全性的保障,但要注意子类的实例无法使用带该关键字的属性、方法。 - 如果不想其他类从该类继承,同样使用
final
关键字;
25. 多态
- 在子类继承超类时,除默认的超类中的属性、方法外,在代码块中每个子类可以定义属于自己的属性、方法;这种在属性、方法上的差异被解释成多态。
- 也许不同子类的属性、方法名字也叫一样,但是这本质是功能的不同实现,busuanshi“重写”;
- 继承和多态的意义:对于代码的可重用性很有用,在创建新类时也可以重用现有类的属性和方法。
26. 内部类/嵌套类
- 即类中类;
- 嵌套类的目的是将属于同一类的类分组,这使代码更具可读性和可维护性;
- 要访问内部类,请创建外部类的对象,然后创建内部类的对象(
一层一层实体化
); - 与常规类不同,常规类的访问关键字只能是
public
或者default
两种,内部类新增加了private
和protected
两种。如果你想对类设置一些权限,可以采用内部类的方式,甚至直接private
禁止访问。 - 内部类也可以是
static
的,也就是静态的,属于类但不属于对象,这意味着可以在不创建外部类的对象的情况下访问它。但是这与
static
静态属性、方法一样,static
的内部类无法访问外部类的属性、方法; - 内部类可以访问外部类的属性和方法;
27. 抽象类
- 数据抽象是隐藏某些细节并仅向用户显示基本信息的过程。
- 可以通过
abstract class
抽象类或interfaces
接口来实现; abstract
关键字是非访问修饰符,用于类和方法:- 抽象类:不能直接创建对象,只能继承后创建;
- 抽象方法:只能在抽象类中使用,自身没有主体代码块,主体代码块要继承的子类提供;
- 抽象类中除了抽象方法也存在常规方法;
28. 接口
-
Java 中实现
abstraction
抽象的另一种方法是使用接口,实际上是高度抽象; -
接口,即
interface
关键字定义的部分,是一个完全抽象“类”,它将相关方法和空实体分组,在接口中所有方法都没有主体代码块; -
要访问接口方式,需要创建一个由
implements
关键字引导“继承的类”。一般情况下,接口的方法在implement类中定义; -
Java中的类只能继承一个父类,但可以实现多个接口,这实现了多重继承的效果。
-
为了实现安全性-隐藏某些细节,只给对象(接口)显示重要细节,例如
api
;
-
一些性质的简要说明:
- 接口中的所有方法默认都是抽象的(
即使不加abstract关键字
); - 和抽象类一样,接口不能用于创建对象;
- 接口方法没有主体,主体由
implement
类提供; - 在实现接口时,必须重写其所有方法;
- 接口不能包含构造函数(因为它不能用于创建对象);
- 默认情况下,接口方法是
abstract
抽象的和public
公共的; - I接口属性默认为
public
,static
和final
; - 接口可以包含常量,所有的字段默认是
public static final
的;
- 接口中的所有方法默认都是抽象的(
-
在Java中,接口(interface)是一种引用类型,用于定义一组抽象方法和常量。接口不能包含具体实现,具体的实现由实现接口的类提供。接口在Java中扮演着重要角色,特别是在设计良好的API和实现多重继承时。
28.1 接口的主要特点:
- 抽象方法:接口中的所有方法默认都是抽象的(即使不加
abstract
关键字),在Java 8之后,可以包含默认方法和静态方法。 - 多重继承:Java中的类只能继承一个父类,但可以实现多个接口,这实现了多重继承的效果。
- 常量:接口可以包含常量,所有的字段默认是
public static final
的。 - 隐式
public
:接口中的方法默认是public
的,不能是private
或protected
。
28.2 定义和实现接口:
定义接口:
public interface Animal {
void eat();
void sleep();
}
实现接口:
public class Dog implements Animal {
@Override
public void eat() {
System.out.println("Dog is eating");
}
@Override
public void sleep() {
System.out.println("Dog is sleeping");
}
}
public class Cat implements Animal {
@Override
public void eat() {
System.out.println("Cat is eating");
}
@Override
public void sleep() {
System.out.println("Cat is sleeping");
}
}
28.4 使用接口的优势:
- 多态性:接口可以用于实现多态,一个接口类型的引用可以指向任何实现了该接口的对象。
- 解耦:接口定义了一组方法的集合,具体实现由不同的类提供,这使得代码更加灵活和可维护。
- 代码重用:通过实现接口,不同类可以共享接口的规范,这有助于代码的重用和一致性。
- 分离接口和实现:接口可以定义API规范,而具体实现可以独立开发和变化,这有助于分层设计。
示例:
使用接口实现多态性:
public class AnimalFeeder {
public void feed(Animal animal) {
animal.eat();
}
public static void main(String[] args) {
AnimalFeeder feeder = new AnimalFeeder();
Animal dog = new Dog();
Animal cat = new Cat();
feeder.feed(dog); // 输出: Dog is eating
feeder.feed(cat); // 输出: Cat is eating
}
}
28.5 Java 8及之后的接口特性:
- 默认方法:可以在接口中定义带有默认实现的方法,使用
default
关键字。 - 静态方法:可以在接口中定义静态方法,使用
static
关键字。 - 私有方法(Java 9及之后):可以在接口中定义私有方法,用于辅助默认方法或静态方法。
示例:
public interface Animal {
void eat();
void sleep();
default void breathe() {
System.out.println("Animal is breathing");
}
static void description() {
System.out.println("This is an animal");
}
private void privateMethod() {
// 仅供默认方法或静态方法调用
}
}
28.6 多个接口的实现
- 在
implements
说明之后用,
隔开多个接口名。
28.7 总结
- 接口在Java编程中提供了一种抽象层次,使得代码更加灵活和易于维护。通过理解和应用接口,可以设计出更具扩展性和可维护性的程序。
29. 枚举
-
enum
枚举是一个特殊的"类",它表示一组常量; -
常量指不可更改的变量,如
final
定义下的变量; -
要创建enum,请使用
enum
关键字,并用逗号,
分隔常量; -
【注意】:定义的常量名应该为大写字母;
-
与数组一样,可以使用
.
来访问枚举中的常量; -
可以在类中创建
enum
枚举; -
枚举通常用于
switch
语句中作为case
的比较值检查相应的值;
-
枚举类型有一个
values()
方法,该方法返回所有枚举常量的数组。如果要循环遍历枚举的常量,可以用.values()
先将枚举转化成数组,再使用for-each
方法对其遍历输出/访问即可; -
枚举常量的赋值方式是
常量名{常量的值,……}
,属性的赋值方法是常量名(属性值)
;
29.1 枚举属性和枚举方法
enum
枚举可以像class
一样具有属性和方法。唯一的区别是枚举常量是public static final
;
在Java中,枚举(enum)是一种特殊的类,用于表示一组固定的常量。与普通类不同,枚举的实例是有限且固定的。枚举不仅可以包含常量,还可以包含属性、方法和构造函数,从而使其更加灵活和功能强大。
定义枚举的属性和方法
1. 枚举常量带有属性:
枚举常量可以带有属性,这些属性可以在构造函数中进行初始化。
- 在Java中,枚举常量带有属性时,通常会将这些属性声明为private final并通过构造函数进行初始化,这提供了枚举属性的不可变性和封装的特点。
public enum Day {
MONDAY("Start of the work week"),
TUESDAY("Second day"),
WEDNESDAY("Midweek"),
THURSDAY("Almost there"),
FRIDAY("End of the work week"),
SATURDAY("Weekend"),
SUNDAY("Rest day");
private final String description;
// 构造函数
Day(String description) {
this.description = description;
}
// 获取描述的方法
public String getDescription() {
return description;
}
}
在这个示例中,每个枚举常量都带有一个描述属性,并通过构造函数进行初始化。
2. 枚举方法:
枚举还可以包含方法,可以根据需要定义实例方法和静态方法。
public enum Operation {
ADDITION("+") {
public double apply(double x, double y) {
return x + y;
}
},
SUBTRACTION("-") {
public double apply(double x, double y) {
return x - y;
}
},
MULTIPLICATION("*") {
public double apply(double x, double y) {
return x * y;
}
},
DIVISION("/") {
public double apply(double x, double y) {
return x / y;
}
};
private final String symbol;
Operation(String symbol) {
this.symbol = symbol;
}
@Override
public String toString() {
return symbol;
}
// 抽象方法,每个枚举实例必须实现此方法
public abstract double apply(double x, double y);
}
在这个示例中,每个枚举常量都表示一个数学运算,并实现了apply
方法。
使用示例:
public class EnumTest {
public static void main(String[] args) {
// 使用 Day 枚举
for (Day day : Day.values()) {
System.out.println(day + ": " + day.getDescription());
}
// 使用 Operation 枚举
double x = 10.0;
double y = 5.0;
for (Operation op : Operation.values()) {
System.out.printf("%f %s %f = %f%n", x, op, y, op.apply(x, y));
}
}
}
输出:
MONDAY: Start of the work week
TUESDAY: Second day
WEDNESDAY: Midweek
THURSDAY: Almost there
FRIDAY: End of the work week
SATURDAY: Weekend
SUNDAY: Rest day
10.000000 + 5.000000 = 15.000000
10.000000 - 5.000000 = 5.000000
10.000000 * 5.000000 = 50.000000
10.000000 / 5.000000 = 2.000000
关键点总结:
- 属性:枚举可以包含属性,并通过构造函数初始化。
- 方法:枚举可以包含实例方法和静态方法。
- 抽象方法:枚举可以定义抽象方法,每个枚举常量必须实现该方法。
- 构造函数:枚举的构造函数总是私有的,可以省略
private
修饰符。
通过这些特性,Java的枚举类型不仅仅是简单的常量集合,还能实现复杂的行为和属性,使其在代码设计中更加灵活和强大。
30. 用户输入
- java采用
scanner
类用于获取用户输入,这个类属于java.util
包中; - 同样,为了使用
scanner
类,需要创建该类的对象,从而使用scanner
类文档中任何可用的方法; - 输入类型与之对应的方法,基本名称就是
next<数据类型,记得开头字母要大写>
:nextBoolean()
:从用户处读取boolean布尔值;nextByte()
:从用户处读取byte字节值;nextDouble()
:从用户处读取double双精度值;- ……
31. 日期和时间
-
与C++不同,Java没有内置的Date类,但是我们可以导入
java.time
包来使用Date
类和time API
; -
java.time
包含的时间和日期类有:LocalDate
:表示日期,格式是年–月–日;LocalTime
:表示时间,格式是小时–分钟–秒钟–纳秒;LocalDateTime
:表示日期和时间;DateTimeFormatter
:格式化显示日期时间;
-
now()
方法可以调出当前的时间数据,不同的类都有其自己的now()
方法; -
如果你想让时间按照自己喜欢的格式显示,可以用
DateTimeFormatter
类格式化,并用.offpattern
描述自己需要的格式,并接纳; -
上述描述,小写的
dd
代表日期,大写的若干个M
代表月份,yyyy
象征年份。
32,数组列表
- 在
java.util
包中,包含ArrayList
类,它是一个可以调整大小的数组(array
),或者说C++的向量
、顺序表
; ArrayLis
和Array
最大的区别就是可以改变大小,可以随意增加或者删除元素;
-
生成:
import java.util.ArrayList; // 导入 ArrayList 类 ArrayList<ElemType> <Name> = new ArrayList<ElemType>(); // 创建一个 ArrayList 对象
-
增加:
.add(<值>)
的方法,且增加在末尾; -
查询:
.get位置索引)
的方法; -
修改:
。set(位置索引,值)
的方法; -
删除:
.remove(位置索引)
; -
计算大小:
.size()
的方法; -
遍历:
for
循环 +.size()
方法找到数量大小 /for-each
循环; -
排序:在
java.util
包中,还有一个非常有用的类Collection
。该类中的.sort()
方法可用于按字母或者数字顺序列表的排序,默认是升序。当然除此之外,你还可以传递一个实现了comparator
接口的实例给该方法,以自定义的顺序进行排序,如:import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; public class Main { public static void main(String[] args) { ArrayList<Integer> list = new ArrayList<>(); list.add(3); list.add(1); list.add(2); // 自定义排序:降序 Collections.sort(list, new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o2 - o1; // 降序排序 } }); System.out.println(list); // 输出: [3, 2, 1] }
- 需要注意的是,ArrayList中的元素其实是对象,所以要生成对应数据类型的数组列表,使用的是基本类型,如 Integer(int的包装类)、String、Boolean(boolean的包装类)、Character(char的包装类)、Double(double的包装类);
- 非常别扭的是,在自动装箱功能普及的时候,对ArrayList的生成依旧要指定包装类名,但在ArrayList以外的代码,包装类名和基本类型基本是通用的。
33. 链表
- 与
ArrayList
几乎相同,LinkedList
的差异只是它是链表; LinkedList
也实现了List
接口,所以增删改查是完全一样的。- 考虑到效率,回归到数据结构的问题,以下是使用两者的取舍:
- 随机访问较多使用链表,增删较多用数组列表;
ArrayList
通过数组储存元素,当输入超过容量,它会新建一个更大的数组并迁移其中,删去旧有的;LinkedList
则是以容器为单位构建的链表。不同数据类型对应不同的容器。
- 关于
Linkedlist
有以下方法,如:addFirst()
:将一个项目添加到列表的开头;addLast()
:将项目添加到列表末尾;removeFirst()
:从列表的开头删除一个项目;removeLast()
:从列表末尾删除一个项目;getFirst()
:获取列表开头的项目;getLast()
:获取列表末尾的项目;
34. HashMap
-
在数组列表中,查找元素都需要使用
int
型的索引;而在HashMap
中,使用了键值对(Python中的数据类型是字典
),将元素存在key/value
中,故可能可以使用Char
型或者String
型的索引进行查找; -
同样要从
java.util
的库中调用HashMap
类,并创建对象实体化后使用,具体格式如下:import java.util.HashMap HashMap<key,value> name = new HashMap<key,value>();
-
关于
HashMap
有几种常用的方法:.put()
:添加键值对,参数用,
隔开;.get()
:使用key值查找value;.remove()
:使用key值删除对应的键值对;.size()
:返回这个表的大小;- 可以使用
for-each
循环遍历整个表; - 对于上述的遍历,如果只需要key值,可以使用
.keyset()
框定范围;同理,可以使用.values()
框定返回所有值;
-
值得注意的是,使用
HashMap<……,……>
中创建对象时,数据类型同样填入包含类的而不是基本类型,也就是:- char → \rightarrow →Character;
- boolean → \rightarrow →Boolean;
- double → \rightarrow →Double;
- int → \rightarrow →Integer;
35. HashSet
-
同样在
java.util
的包中,使用时创建对象; -
区别在于它是集合,类似于Python,每个元素都是唯一的;
-
创建:
import java.util.HashSet; Hashset<ElemType> name = new HashSet<Elemtpye>();
-
一些常用的方法:
.add()
:添加元素;.contain()
:查找是否存在该元素;.remove()
:删去对应的元素;.size()
:返回这个集合的大小;.clear()
:一次性删除所有的元素;- 可以使用
for-each
循环遍历整个表;
-
同样,创建对象时声明的数据类型使用的是
对象名
或者说包装类
而不是基本数据类型
;
36. 迭代器/iterator
- 是一个可用于循环遍历集合的对象;
ArrayList
和HashSet
就是两种常用的类容器;- 关于迭代器有几种常用的方法:
- 使用
.iterator()
可以获取任何集合的迭代器,并创建对象://依照一个集合创建一个迭代器对象 Iterator<ElemType> Name = SetName.iterator();
- 使用
.next()
访问这个迭代器的头指针,多次使用可以从头开始遍历整个迭代器(很明显的顺序表思想
); - 循环遍历集合:
使用//.next()方法和.hasNest()方法配合,可以想顺序表一样遍历全部的集合,如head指针和p指针 //例如: while(Name.hasNext()) { System.out.println(Name.next()); }
for
循环或者for-each
循环删除将无法正常工作。 - 可以使用
.remove()
删除迭代器中的元素;
- 使用
37. 包装类
原始数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
-
要创建包装器对象,请使用包装器类而不是原始类型。要获取值,您只需打印对象;
-
以下方法用于获取与对应包装对象关联的值:
intValue(), byteValue(), shortValue(), longValue(), floatValue(), doubleValue(), charValue(), booleanValue()
。等于说你用包装类创建对象并赋值后,需要在执行的时候输出值,可以选择使用这个; -
可以使用
toString()
将包装类中的对象转换为字符串,例如:Integer myInt = 100; String myString = myInt.toString(); System.out.println(myString.length());
38. 异常处理
- 在发生错误的时候,java通常会停止运行并生成错误信息,即抛出异常;
在Java中,异常可以分为两大类:受检异常(Checked Exceptions)和非受检异常(Unchecked Exceptions)。它们在使用和处理上有一些重要的区别。
非受检异常(Unchecked Exceptions)
定义
非受检异常是指那些不需要强制在代码中进行捕获或声明的异常。它们继承自RuntimeException
类及其子类。非受检异常通常用于表示编程错误,例如逻辑错误或使用不当的API。
特点
- 无需显式处理:编译器不会强制要求在方法中捕获或声明非受检异常。
- 发生频率高:非受检异常通常用于程序中的常见错误,如空指针引用或数组下标越界。
- 编译器不检查:在编译期间,编译器不会检查非受检异常是否已经被处理。
常见的非受检异常
NullPointerException
ArrayIndexOutOfBoundsException
ArithmeticException
ClassCastException
IllegalArgumentException
示例
public class Main {
public static void main(String[] args) {
try {
int result = 10 / 0; // 这里会抛出 ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Caught ArithmeticException: " + e.getMessage());
}
}
}
受检异常(Checked Exceptions)
定义
受检异常是指那些需要在代码中显式捕获或声明的异常。它们继承自Exception
类,但不包括RuntimeException
及其子类。受检异常通常用于表示程序的外部条件错误,例如文件未找到或数据库连接失败。
特点
- 需要显式处理:编译器要求在方法中捕获或声明受检异常。
- 通常表示外部错误:受检异常通常用于处理程序无法控制的外部条件错误。
- 编译器检查:在编译期间,编译器会检查受检异常是否已经被处理。
常见的受检异常
IOException
SQLException
ClassNotFoundException
InterruptedException
示例
import java.io.*;
public class Main {
public static void main(String[] args) {
try {
FileInputStream file = new FileInputStream("test.txt"); // 可能抛出 FileNotFoundException
} catch (FileNotFoundException e) {
System.out.println("Caught FileNotFoundException: " + e.getMessage());
}
}
}
主要区别
-
处理要求:
- 受检异常:必须显式捕获(使用
try-catch
)或声明(使用throws
)。 - 非受检异常:无需显式捕获或声明。
- 受检异常:必须显式捕获(使用
-
用途:
- 受检异常:用于表示程序外部的错误条件(如I/O错误、数据库错误)。
- 非受检异常:用于表示编程错误(如逻辑错误、API误用)。
-
编译器行为:
- 受检异常:编译器强制检查是否已经处理。
- 非受检异常:编译器不强制检查。
总结
- 了解受检异常和非受检异常的区别对于编写健壮和可靠的Java程序非常重要。受检异常用于处理外部错误,需要在代码中显式处理,而非受检异常用于处理程序中的常见逻辑错误,可以选择是否处理。掌握这两种异常的使用方法和场景,有助于编写更好的异常处理代码。
try
和catch
try
语句定义一段代码块,以便在执行的时候使用其进行错误测试;如果发现错误,执行catch
语句下处理代码块。- 格式为:
try { // 要尝试的代码块 } catch(Exception e) { // 处理错误的代码块 }
- 实例:
public class MyClass { public static void main(String[ ] args) { try { int[] myNumbers = {1, 2, 3}; System.out.println(myNumbers[10]); } catch (Exception e) { System.out.println("Something went wrong."); } } }
finally
语句允许在try-catch
语句之后照常执行代码,不论后者的执行结果如何。
throw
语句允许您创建自定义错误;throw
语句常与异常类型一起使用;- Java 中有许多异常类型可用:
ArithmeticException(算数异常), FileNotFoundException, ArrayIndexOutOfBoundsException, SecurityException
, etc: - 格式为:
throw new <ExceptionType>(说明异常的字符串/话)
- 注意,
throw
只是自定义异常,而不是异常类型。如果你想自定义异常类型,需要使用类的继承:class <自定义异常> extends Exception/RuntimeException{ 代码块 } …… throw new <自定义异常>(自定义说明语句)
39. 正则表达式
- 是形成搜索模式的字符序列,当您在文本中搜索数据时,您可以使用此搜索模式来描述您要搜索的内容。
- 可用于执行所有类型的文本搜索和文本替换操作;
- Java 没有内置的正则表达式类,但我们可以导入
java.util.regex
包来使用正则表达式。
Java中的正则表达式(Regular Expressions)提供了一种强大且灵活的文本处理工具。正则表达式可以用来搜索、编辑或处理文本数据。Java通过java.util.regex
包提供对正则表达式的支持。这个包中的主要类是Pattern
和Matcher
。 - 对于
complie()
方法,可以在方法中备注 标志(Flag) 改变搜索的执行方式:Pattern.CASE_INSENSITIVE
- 执行搜索时将忽略英文字母的大小写;Pattern.LITERAL
- 模式中的特殊字符没有任何特殊含义,在执行搜索时将被视为普通字符;Pattern.UNICODE_CASE
- 将它与CASE_INSENSITIVE
标志一起使用,也可以忽略英文字母表之外的其他字母的大小写;
基本概念
Pattern类
Pattern
类表示一个编译后的正则表达式。它不能直接实例化,而是通过静态方法compile
创建。
Matcher类
Matcher
类是一个引擎,用于解释和匹配输入字符串的模式。可以通过Pattern
对象的matcher
方法创建Matcher
对象。
基本用法
编写和匹配正则表达式
- 创建Pattern对象
- 创建Matcher对象
- 使用Matcher对象进行匹配操作
import java.util.regex.*;
public class RegexExample {
public static void main(String[] args) {
// 创建Pattern对象
Pattern pattern = Pattern.compile("a*b");
// 创建Matcher对象
Matcher matcher = pattern.matcher("aaaaab");
// 检查是否匹配
boolean matches = matcher.matches();
System.out.println("Matches: " + matches); // 输出:Matches: true
}
}
常用正则表达式语法
当然可以,以下是根据您提供的图片内容编写的Markdown格式文本:
括号和元字符
括号用于查找一系列字符:
- 表达式 | 描述
[abc]
| 从括号内的选项中查找一个字符[^abc]
| 找到一个不在括号内的字符[0-9]
| 从0到9范围内查找一个字符
元字符:
- 元字符 | 描述
.
| 查找由.
分隔的任意一种模式的匹配项,如:cat.dog
或fish
?
| 查找任何字符的一个实例^
| 查找作为字符串开头的匹配项,如:^Hello
$
| 在字符串末尾查找匹配项,如:World$
\\d
| 找一个数字\\s
| 查找空白字符\\b
| 在这样的单词开头查找匹配项:\bWORD
,或在这样的单词结尾处查找匹配项:WORD\b
\xxxxx
| 查找十六进制数xxxx
指定的Unicode字符
量词:
- 量词 | 描述
n+
| 匹配任何至少包含一个n的字符串n*
| 匹配包含零次或多次出现n的任何字符串n?
| 匹配包含零次或一次出现n的任何字符串n(x)
| 匹配任何包含一系列Xn的字符串nx,y)
| 匹配任何包含X到Yn序列的字符串n(x,)
| 匹配任何包含至少Xn的序列的字符串
请注意,Markdown中的反斜杠\
是转义字符,所以在Markdown中使用时需要使用两个反斜杠\\
来表示一个反斜杠。例如,\d
在Markdown中应写为\\d
。
字符匹配
.
:匹配任意单个字符\d
:匹配数字[0-9]
\D
:匹配非数字[^0-9]
(^
表示取反)\w
:匹配字母或数字[a-zA-Z_0-9]
\W
:匹配非字母或数字[^a-zA-Z_0-9]
\s
:匹配空白字符(包括空格、制表符、换页符等)\S
:匹配非空白字符
数量匹配
*
:匹配前面的字符零次或多次+
:匹配前面的字符一次或多次?
:匹配前面的字符零次或一次{n}
:匹配前面的字符恰好n次{n,}
:匹配前面的字符至少n次{n,m}
:匹配前面的字符至少n次但不超过m次
边界匹配
^
:匹配行的开头$
:匹配行的结尾\b
:匹配单词边界\B
:匹配非单词边界
【注意】:对选中区域的匹配,正则表达式字符匹配的先后一般没有影响。每一个匹配符都代表可能存在的一种字符类型。
括号包含
- 先用中括号再用小括号
示例
检查电子邮件地址
import java.util.regex.*;
public class EmailValidator {
public static void main(String[] args) {
//由于在字符串中,所以需要使用转义符号\\。
String emailPattern = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$";
Pattern pattern = Pattern.compile(emailPattern);
String email = "example@example.com";
Matcher matcher = pattern.matcher(email);
if (matcher.matches()) {
System.out.println(email + " is a valid email address.");
} else {
System.out.println(email + " is not a valid email address.");
}
}
}
查找文本中的所有数字
import java.util.regex.*;
public class FindDigits {
public static void main(String[] args) {
String text = "My phone number is 123-456-7890 and my zip code is 98765.";
String digitPattern = "\\d+";
Pattern pattern = Pattern.compile(digitPattern);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
System.out.println("Found number: " + matcher.group());
}
}
}
常用方法
Pattern类
compile(String regex)
:编译正则表达式compile(String regex, int flags)
:编译带有指定标志的正则表达式
Matcher类
matches()
:尝试将整个区域与模式匹配find()
:扫描输入的序列,查找与该模式匹配的下一个子序列group()
:返回由以前的匹配操作所匹配的输入子序列start()
:返回以前匹配的初始索引end()
:返回最后匹配字符之后的偏移量
总结
Java中的正则表达式为文本处理提供了强大的功能,通过Pattern
和Matcher
类,可以灵活地搜索、匹配和操作字符串。掌握正则表达式的基本语法和使用方法,有助于提高字符串处理的效率和灵活性。
40. 线程
-
结合操作系统的知识,线程即专注于实现程序的某一项功能的部分指令流,它是一个程序中独立执行的最小单位;
-
它允许程序通过同时执行多项任务来更有效地运行,可以用来在后台执行复杂的任务而不中断主程序(
在内核态中分配CPU资源
); -
每个Java应用程序至少有一个线程:主线程(
mainstream
)。 -
Java中有两种主要方式创建线程:继承
Thread
类,和实现Runnable
接口(后者继承接口后
),具体格式如下:- 继承
Thread
类
class <线程名> extends Thread{ 代码块; public static void main(String[] args) { <线程名> 对象名 = new <线程名>(); 对象名.start(); // 开启线程 } }
- 实现
Runnable
接口
class <线程名> implements Runnable{ 代码块; public static void main(String[] args) { <线程名> 对象名1 = new <线程名>(); //接口的话需要重新实体化成线程,并创建对象使用(殊途同归) Thread 对象名2 = new Thread(对象名1); thread.start(); // 开启线程 } }
- 继承
-
操作系统中,线程有五个状态:
- New:新建态;
- Runnable:就绪态;
- Running:运行态;
- Blocked:阻塞态;
- Dead:线程执行完毕被注销;
-
当多个线程共享资源时,可能导致资源的不一致性问题。为了解决这个问题,需要进行线程同步。
40.1 线程同步
- 保证多个线程对共享资源的访问是有序的、互不干扰的。同步可以防止数据不一致和竞态条件;
- Java提供了多种方式来实现线程同步,主要包括使用
synchronized
关键字、volatile
关键字、显示锁(如ReentrantLock
),以及更高级的并发工具(如信号量、栅栏和闭锁)。
-
synchronized
关键字- 是Java中最常用的线程同步工具,它可以用来修饰方法或代码块,确保同一时间只有一个线程可以执行被同步的代码。
synchronized
方法是针对整个方法加锁,而synchronized
代码块则可以只对需要同步的部分加锁,从而减少锁的粒度,提高性能。- 实例
//同步方法 public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } //同步代码块 public class Counter { private int count = 0; public void increment() { //用this指明需要同步的代码块 synchronized (this) { count++; } } public int getCount() { synchronized (this) { return count; } } }
-
volatile
关键字- 用于声明变量,使其在多个线程中可见。它确保变量的值在所有线程中是可见的,但它不保证复合操作(如递增操作)的原子性(
回到操作系统的问题,不同的进程在本关键字的加成下可以共享变量的数据,但是不能保证读写操作之间不会冲突。解决方法是使用其他关键字完成同步,例如synchronized和Atomic
); - 实例:
public class VolatileExample { private volatile boolean flag = true; public void stop() { flag = false; } public void run() { while (flag) { // do something } } }
- 用于声明变量,使其在多个线程中可见。它确保变量的值在所有线程中是可见的,但它不保证复合操作(如递增操作)的原子性(
-
显示锁
- Java在
java.util.concurrent.locks
包中的提供了锁,如ReentranLock
,它们提供了比synchronized
更灵活的同步机制。 - 最常用的显示锁是
ReentrantLock
,它是一个可重入锁,类似于synchronized
关键字,但提供了更多的功能和灵活性;
具体的使用写在另一份资料里,此处略。
- Java在
40.2 线程创建
创建线程有几种方法:
- 继承
Thread
类并覆盖其run()
方法来创建://常见的格式 class <线程名> extends Thread{ public void run() { System.out.println("Thread is running..."); } }
- 实现
Runnable
接口://例如以下格式 public class MyRunnable implements Runnable { public void run() { System.out.println("Thread is running..."); } public static void main(String[] args) { //建立接口的对象 MyRunnable myRunnable = new MyRunnable(); //将接口对象转变为线程对象 Thread t1 = new Thread(myRunnable); //启动线程 t1.start(); } }
- 使用匿名类实现
Runnable
接口此处略,在匿名类笔记中有。
- 使用 Lambda 表达式
- 需要Java 8以上;
//一个很好的例子 public class Main { public static void main(String[] args) { Thread t1 = new Thread(() -> System.out.println("Thread is running...")); t1.start(); } }
40.3 线程运行
- 如果该类是继承自
Thread
类,则将其实例化后可以调用.start()
方法来运行线程; - 如果该类使实现了
Runnable
接口,则可以通过将类的实例传递给Thread
(由变成Thread的对象实例化),并调用线程的.start()
运行它。
补充:“继承自类”和“部署接口”的区别:
- 继承自
Thread
类时,除了Thread
之外无法同时继承其他的类;- 部署
Runnable
接口时,也允许从其他的类继承过来(这两个同时发生
);
40.4 并发问题*
- 因为线程与程序的其他部分同时运行,所以无法知道代码将以何种顺序运行。当线程和主程序读取和写入相同的变量时,其值是不可预测的。由此产生的问题称为并发问题(
ReadWriteLock的用武之地
); - 为了避免并发问题,最好在线程之间共享尽可能少的属性。如果需要共享属性,一种可能的解决方案是在使用线程可以更改的任何属性之前,使用线程的
isAlive()
方法检查线程是否已完成运行。
一个实例:
public class MyClass extends Thread {
public static int amount = 0;
public static void main(String[] args) {
MyClass thread = new MyClass();
thread.start();
// 等待线程完成
while(thread.isAlive()) {
System.out.println("Waiting...");
}
//解除循环意味着线程执行完毕
// 更新 amount 并打印其值
System.out.println("Main: " + amount);
amount++;
System.out.println("Main: " + amount);
}
//重写run()方法;
public void run() {
amount++;
}
}
41. Lambda表达式
-
表达式是一小段代码,它接受参数并返回一个值。 Lambda 表达式类似于方法,但它们不需要名称,并且可以直接在方法体中实现;
-
设置格式:
<para1,para2,···> -> expression;
-
由于其过于简练,故在表达方式有限的情况下,它需要立即返回一个值,并且不能包含变量、赋值或语句。由此,为了进行更复杂的操作,可以使用花括号包含代码块。如果 lambda 表达式需要返回一个值,那么代码块应该有一个
return
语句。 -
Lambda 表达式通常作为参数传递给方法。而且如果变量类型是只有一个方法的接口,则
Lambda
表达式可以存储在变量中,如Consumer<Integer> method = (n) -> { System.out.println(n); };
中method接口是Consumer类接口(在java.util.function.Consumer中调用
)
,可以作为存储的容器。 -
要在方法中使用
Lanbda表达式
,该方法应该要有一个参数,其类型为单方法接口。调用这个方法的时候就会运行Lambda
表达式。
实例:
//设置单方法接口
interface StringFunction {
String run(String str);
}
public class MyClass {
public static void main(String[] args) {
//对于main方法,它的参数是StringFunction接口类型的,而这个接口是单方法的,保证了传递Lambda表达式时的唯一性传递。
StringFunction exclaim = (s) -> s + "!";
StringFunction ask = (s) -> s + "?";
printFormatted("Hello", exclaim);
printFormatted("Hello", ask);
}
public static void printFormatted(String str, StringFunction format) {
String result = format.run(str);
System.out.println(result);
}
}
42. 文件
- Java 有多种创建、读取、更新和删除文件的方法;
java.io
包中的File
文件类允许我们处理文件;- 同样,要使用该类的方法,需要先创建该类的对象,制定需要操作的文件名和目录名;
- 以下是类中一些实用的方法:
方法 类型 描述 canRead()
Boolean 测试文件是否可读 canWrite()
Boolean 测试文件是否可写 createNewFile()
Boolean 创建一个空文件 delete()
Boolean 删除文件 exists()
Boolean 测试文件是否存在 getName()
String 返回文件名 getAbsolutePath()
String 返回文件的绝对路径名 length()
Long 返回文件大小(以字节为单位) list()
String[] 返回目录文件的数组 mkdir()
Boolean 创建目录
42.1 创建文件
- 要在Java中创建文件,可以使用
createNewFile()
方法,它将返回一个boolean值以表示文件是否创建成功; - 请注意,该方法包含在
try...catch
块中。 这是必要的,因为如果发生错误(如果由于某种原因无法创建文件),它会抛出IOException
; - 所以,创建步骤如下:
//导入File类和IOException类 import java.io.File; import java.io.IOException; ··· //使用try-catch语块 try { //新建文件类对象,并声明文件名 File myObj = new File("filename.txt"); //根据返回值判断下一步行动 if (myObj.createNewFile()) { System.out.println("File created: " + myObj.getName()); } else { System.out.println("File already exists."); } } catch (IOException e) { System.out.println("An error occurred.");//抛出异常 e.printStackTrace(); }
- 要在特定目录中创建文件(需要权限),请指定文件的路径并使用双反斜杠转义“\ \”字符(对于 Windows)。 在 Mac 和 Linux 上,您可以只写路径,例如:
/Users/name/filename.txt
。 - 对于我们创建的文件,我们可以使用
FileWriter
类和write()
方法将一些文本写入已经创建的文件中去,如:
之后,如果我们需要关闭我们创建的文本文件,直接使用FileWriter myWriter = new FileWriter("filename.txt"); myWriter.write("Files in Java might be tricky, but it is fun enough!");
.close()
方法即可,如:myWriter.close();
42.2 读取文件
- Java API中有许多可用类(
FileReader、BufferedReader、Files、Scanner、FileInputStream、FileWriter、BufferedWriter、FileOutputStream
)可用于在Java中读取和写入文件,具体使用那一版取决于需要读取的字节、字符,以及文件/行的大小。 - 一般情况下,我们使用
Scanner
类来读取我们创建/储存的文本文件的内容:import java.io.File; // 导入 File 文件类 import java.io.FileNotFoundException; // 导入这个类来处理错误 import java.util.Scanner; // 导入 Scanner 类以读取文本文件 ··· File myObj = new File("filename.txt"); Scanner myReader = new Scanner(myObj);//生成Scanner类的对象以开始扫描读取工作 while (myReader.hasNextLine()) { String data = myReader.nextLine(); System.out.println(data); }//以行为单位逐行输出文件内容
除了.nextLine()
方法以外,要获取更多的文件信息,可以使用之前提到的File
方法。
42.3 删除文件
- 使用
.delete()
方法,它的返回值是一个布尔值; - 删除过程中建议加上提示词,如:
if (myObj.delete()) { System.out.println("Deleted the file: " + myObj.getName()); } else { System.out.println("Failed to delete the file."); }
- 由于在创建文件的时候你可以直接填写路径生成文件夹,故你也可以直接删除文件夹,但是它必须为空(
如果不为空,delete()方法会返回false,删除操作将失败
): - 如果我们想删除非空文件夹,可以使用递归的方法:
也可以直接使用import java.io.File; public class DeleteDirectory { public static void main(String[] args) { // 指定要删除的目录 File directory = new File("path/to/directory"); // 调用递归删除方法 boolean result = deleteDirectory(directory); if (result) { System.out.println("目录删除成功"); } else { System.out.println("目录删除失败"); } } // 递归删除目录的方法 public static boolean deleteDirectory(File directory) { // 如果目录不存在,返回 true if (!directory.exists()) { return true; } // 如果目录是文件,直接删除并返回删除结果 if (directory.isFile()) { return directory.delete(); } // 获取目录中的所有文件和子目录,用文件数组的形式存储 File[] files = directory.listFiles(); if (files != null) { // 递归删除目录中的所有内容 for (File file : files) { if (!deleteDirectory(file)) { return false; } } } // 删除目录本身 return directory.delete(); } }
org.apache.commons.io.FileUtils
库中的方法,使用前要先添加到你的项目中。
添加依赖后,import org.apache.commons.io.FileUtils;//调用这个特定的库 import java.io.File; import java.io.IOException; public class DeleteDirectory { public static void main(String[] args) { // 指定要删除的目录 File directory = new File("path/to/directory"); try{ // 调用 FileUtils.deleteDirectory 方法删除目录,一步就搞定了 FileUtils.deleteDirectory(directory); System.out.println("目录删除成功"); }catch (IOException e) { e.printStackTrace(); System.out.println("目录删除失败"); } } }
42.4 接收输入
- 要接收用户的输入并用文件储存,可以使用
Scanner
类从控制台接受用户的输入,并使用PrinterWriter
类将输入的数据写入文件。若之后还要读取文件内容,继续使用Scanner
类对象从文件中读取数据。
实例:
import java.io.File;//文件的类
import java.io.FileNotFoundException;//解决没找到的文件异常的类
import java.io.PrintWriter;//将输入写入文件的类
import java.io.IOException;//解决文件I/O异常的类
import java.util.Scanner;//接受数据并输出的类
public class FileInputExample {
public static void main(String[] args) {
// 创建一个Scanner对象,用于接收用户输入
Scanner scanner = new Scanner(System.in);
System.out.print("请输入一些文本数据:");
//以行为单位接收数据
String userInput = scanner.nextLine();
// 指定文件路径,创建文件
File file = new File("userInput.txt");
// 将用户输入写入文件,创建写入文件类会返回Boolean值
try (PrintWriter writer = new PrintWriter(file)) {
writer.println(userInput);//将输入写入到写入文件中
} catch (IOException e) {
System.out.println("写入文件时出错:" + e.getMessage());
}
// 读取文件中的数据并输出
try (Scanner fileScanner = new Scanner(file)) {
System.out.println("从文件中读取的数据:");
while (fileScanner.hasNextLine()) {
System.out.println(fileScanner.nextLine());
}//用循环遍历的方式输出文件的全部信息
} catch (FileNotFoundException e) {
System.out.println("读取文件时出错:" + e.getMessage());
}
scanner.close();//关闭文件输出
}
}
- 如果要接受多行数据写入文件,可以以行为单位接收字符串
String
并用.append()
写入接收文件中,如:
1.
Scanner scanner = new Scanner(System.in);
//System.in是标准输入流,可以从控制台读取输入数据。由Scanner类对象接收它,可以很方便地读取各种类型的输入类型。
//除此以外,System.in还是一个Inputstream,它抽象了输入源,除了从键盘读取输入,还可以将其重定向到其他输入源,如文件或者网络流,从而使得程序更具灵活性。
2.
StringBuilder userInput = new StringBuilder();
// 使用StringBuilder来存储多行输入,StringBuilder是标准库的一个类,无需导入,是可以直接使用的。具体情况看附录文件。
3.
String line;//用字符串来存储一行的数据
while (!(line = scanner.nextLine()).equalsIgnoreCase("exit")) {
userInput.append(line).append(System.lineSeparator());//在没有接收到exit的情况下循环接收以行为单位的数据,并添加到StringBuilder类对象中,并自动在每行末尾加上自动换行的特殊符
}
4.
// 将用户输入的数据写入输入文件中
try (PrintWriter writer = new PrintWriter(file)) {
writer.print(userInput.toString());//将输入文件中的内容读入要存储的文件中
} catch (IOException e) {
System.out.println("写入文件时出错:" + e.getMessage());
}//异常处理
在上述接受的过程中,数据的存储类型发生了几次变化,即:
S
t
r
i
n
g
字符串
→
S
t
r
i
n
g
B
u
i
l
d
e
r
字符串流
→
P
r
i
n
t
W
r
i
t
e
r
输入文件
→
F
i
l
e
文件
String字符串\rightarrow StringBuilder字符串流\rightarrow PrintWriter输入文件\rightarrow File文件
String字符串→StringBuilder字符串流→PrintWriter输入文件→File文件
43. 关键字
true
,false
,null
虽然不是关键字,但是它们不能用作标识符的文字或者保留字。
44. 默认方法
- 采用
default
关键字定义的方法只能出现在接口Interface
中,它为接口提供了一种默认选项。你可以选择在实现它的时候重写它也可以选择不重写它。
45. Java中的XML应用
-
XML 是一种简单的基于文本的语言,旨在以纯文本格式存储和传输数据。
-
以下是 XML 提供的优势:
-
与技术无关 − 作为纯文本,XML 与技术无关。 它可以被任何技术用于数据存储和数据传输目的。
-
人类可读 − XML 使用简单的文本格式。 它是人类可读且易于理解的。
-
可扩展 − 在 XML 中,可以非常轻松地创建和使用自定义标签。
-
允许验证 − 使用 XSD、DTD 和 XML 结构可以很容易地进行验证。
-
-
XML Parser 提供了一种访问或修改 XML 文档中数据的方法。 Java 提供了多种解析 XML 文档的选项。 以下是通常用于解析 XML 文档的各种类型的解析器:
-
Dom 解析器 − 通过加载文档的完整内容并在内存中创建其完整的层次树来解析 XML 文档。
-
SAX 解析器 − 在基于事件的触发器上解析 XML 文档。 不将完整的文档加载到内存中。
-
JDOM 解析器 − 解析 XML 文档的方式与 DOM 解析器类似,但方式更简单。
-
StAX 解析器 − 以与 SAX 解析器类似的方式解析 XML 文档,但以更有效的方式。
-
XPath 解析器 − 基于表达式解析 XML 文档,并广泛与 XSLT 结合使用。
-
DOM4J 解析器 − 一个使用 Java Collections Framework 解析 XML、XPath 和 XSLT 的 java 库。 它提供对 DOM、SAX 和 JAXP 的支持。
有 JAXB 和 XSLT API 可用于以面向对象的方式处理 XML 解析。
-
-
详细内容参考Java XML 教程,此处略。
46. Java中的包
此处只提供教程链接,在看完教程之后还需要详细信息的可以问ChatGpt: