JavaSE
This article mainly focuses on the knowledge of JavaSE.
引入
首先了解一些基本的doc命令
| 命令 | 意思 |
|---|---|
| dir | 查看当前路径 |
| cd | 进入目录 |
| cd.. | 退回上一级 |
| cls | 清屏 |
| javac | 编译Java文件 |
| java | 运行class文件 |
| exit | 退出DOS窗口 |
| c:/d: | 切换盘符 |
第一个Java文件
在test.java中编写以下代码
public class test{
public static void main(String [] args){
System.out.println("Hello World!");
}
}
进入控制台分别输入以下内容
javac test.java
java test
看到有以下内容输出:
Hello World!
到此成功执行第一个Java文件
前面是在文本编辑器中进行操作
在以后我们都会使用IDEA进行代码的编写和运行
Java的加载与执行
Java程序的运行阶段包括两个非常重要的阶段
- 编译阶段
- 运行阶段
编译阶段检查Java源程序是否符合Java语法,符合的则会生成字节码文件class 字节码文件中不是纯粹的二进制,这种文件无法再操作系统中直接执行。 然后使用JDK中的javac命令进行编译
public class 与 class 的区别
- 一个java源文件中可以有多个class
- 一个java源文件中public class不是必须的
- 一个class会生成一个字节码文件
- 如果存在public 类名应该与文件名相同
- 每一个class 中都可以编写main方法,都可以设定程序的入口
数据类型
基本概念
作用
- 不同的数据有不同的类型,不同的数据类型底层会分配不同大小的空间。
- 数据类型是指导程序JVM在运行阶段应该分配多大的内存空间。
分类
- 基本数据类型
- 引用数据类型
关于基本数据类型,可分为四大类,八小钟
- 整数型 byte short int long
- 浮点型 float double
- 布尔型 boolean
- 字符型 char
范围与大小
| 关键字 | 类型描述 | 大小 | 示例 |
|---|---|---|---|
| byte | 字节型 | 1个字节 | (-128~127) |
| short | 短整型 | 2个字节 | (-32768~32767) |
| int | 整型 | 4个字节 | 1 |
| long | 长整型 | 8个字节 | 1L |
| float | 单精度浮点型 | 4个字节 | 0.0f |
| double | 双精度浮点型 | 8个字节 | 0.00d |
| boolean | 布尔型 | 2个字节 | true/false |
| char | 字符型 | 1个字节 | '\u0000' |
注:字节(byte) 1 byte = 8 bit 即 1 个字节 = 8 个比特位 1 个比特位表示 1 个 2 进制位 1 KB = 1024 Byte 1 MB = 1024 KB
转换规则
- 八种基本数据类型处布尔类型之外,剩下的其中数据类型都可以互相转换
- 小容量转化为大容量,成为自动类型转换,容量从小到大依次是: byte short(char) int long float double
注:任何浮点型类型不管占用多少字节,都比整数型容量大 char和short可表示的种类数量相同,但是char可以取更大的正整数 - 大容量转化为小容量,叫强制类型转化,需要加强制转化符,程序才能编译通过, 但是运行阶段可能会损失精度。
- 字面值没有超过byte short char 取值范围,可以直接赋值给他们
- byte short char混合运算,先转换成int类型再做运算
- 多种数据类型混合运算,先转换成容量最大的那种类型再做运算。
运算符
算数运算符
| 符号 | 描述 |
|---|---|
| +、-、*、/、% | 分别是:加、减、乘、除、取余 |
| ++ | 自加一 |
| -- | 自减一 |
关系运算符
这类运算符会比较两者的关系,返回true/false
| 符号 | 描述 |
|---|---|
| == | 判断左右两个值是否相等 |
| != | 判断左右两个值是否不相等 |
| > < >= <= | 分别是:大于、小于、大于等于、小于等于 |
| += | 追加, i += 1 等同于 i = i + 1 |
逻辑运算符
| 符号 | 描述 |
|---|---|
| && | 逻辑与,两边都为true结果才为true |
| ll | 逻辑或,两边有一个true结果就为true |
| ! | 逻辑非 |
条件运算符
- 三元运算符又称三目运算符
- 语法: 布尔表达式(x) ? 表达式(a) : 表达式(b)
- x为true执行a,false则执行b
控制语句
分类
| 关键字 | 含义 |
|---|---|
| if-else | 根据条件执行不同的代码块 |
| switch | 根据表达式执行不同的分支 |
| for() | 重复执行一段代码,可以指定循环初始值,次数 |
| while | 在满足条件下重复执行一段代码 |
| do-while | 先进行操作再进行判断的循环语句 |
| break | 用于终止switch的执行,并跳出当前代码块 |
| continue | 用于终止本次循环,并执行下次循环 |
| return | 退出当前方法,并返回至调用方法的地方 |
1.if - else
if ("布尔表达式"){
//代码块1
}else{
//代码块2
}
如果if中的布尔表达式返回值为true,执行代码块1,否则执行代码块2
当有多个else时
if ("布尔表达式1"){
//代码块1
}else if("布尔表达式2"){
//代码块2
}
...
else{
//代码块3
}
根据条件的顺序依次判断,如果都为false则执行代码块3
如果if 语句块中只有一行代码,那么括号可以省略不写(一般不使用这种方法)
2. switch语句
switch(int 或 String 类型的字面值或变量){
case 值1:
//当满足值1,执行此代码块
break;
case 值2:
//当满足值2,执行此代码块
break;
default:
//所有的case都不满足,执行此代码块
break;
}
//注意1:switch中表达式可以是整数类型或其包装型、枚举或字符串型(从Java7开始)
//注意2:case语句块中如果没有break关键字,则程序会继续向下执行(不会再进行匹配),直到遇到break或switch执行完成
//这种现象称之为case穿透现象
3. for循环
for (初始条件;循环条件;循环控制变量的更新操作){
//循环体
}
例子:
for (int i = 1; i <=5; i++){
System.out.println("i --> " + i);
}
观察上述函数,初始值我们给定了一个 i = 1
循环体就会先输出变量i的值 --> 1
循环体执行完后会更新循环控制变量i的值 自加一 然后再用更新后的i来判断 i是否小于等于 5
如果为true则继续执行循环体
以此逻辑我们不难推出其输出结果
i -->1
i -->2
i -->3
i-->4
i -->5
i -->6
foreach
它提供了一种简化的语法用来快速遍历集合中元素的值
for (集合元素的数据类型 每次遍历表示当前元素的变量名 : 要遍历的数组或集合){
//循环体
}
例子:
int[] numbers = {1,2,3,4,5};
for(int number : numbers){
System.out.print(number);
}
最后输出结果为:
12345
4. while循环
while(布尔表达式){
//循环体
}
布尔表达式为true时就会执行循环体中的代码块,要注意避免死循环
do-while循环
do{
//循环体
}while(布尔表达式);
这是一种先执行再判断的循环方式
5.控制语句break&continue
break
- break; 可作为完整语句
- break 在 switch 语句中用来终止 switch 语句
- break 在循环语句中(for,while,do while)用来终止循环
continue
- continue; 可作为完整语句
- 表示直接进入下一次循环
6. 跳转语句
通过标签(label)和break联合使用跳出多重循环
//给外层for起名
outerLoop:for (int i = 1; i <= 3; i++) {
System.out.println("Outer loop: " + i);
//内层for命名
innerLoop:for (int j = 1; j <= 3; j++) {
System.out.println("Inner loop: " + j);
if (i == 2 && j == 2) {
break outerLoop; // 在满足条件时跳出外层循环
}
}
}
上面的代码若满足outerLoop的条件会直接结束所有的循环
方法
基本概念
方法本质就是一段代码,用来完成特定功能,可以重复使用。
方法定义在类体中,方法体中不能再嵌套方法(如main方法中不能再定义其他的方法)
方法的结构 [修饰符列表] 返回值类型 方法名 (形式参数列表) { 方法体;
} 结构说明 返回值类型规定执行结果的数据类型,返回值可以是所有的Java类型, 如果方法没有返回值必须写为void如果规定了返回值必须有return,否则JVM会报错 方法名 只要是合法的标识符即可 一般采用驼峰命名法即:首个单词首字母小写后面的都大写 详细请参考阿里巴巴开发手册 形参列表 形参是局部变量 形参可以是多个,之间用逗号隔开 在调用这个方法时,实际传递的参数被称为实参。 实参必须与形参的个数,类型,顺序都要相同 方法体 方法体必须由大括号括起来,自上而下依次进行方法的调用
- 调用带有static的方法
用类名.方法名(实参)的方式调用
2.调用没有static的方法
先创建对象,再使用对象名.方法名(实参)的方式调用 - 方法自己调用自己(被成为方法递归)
使用情况很少,又有栈内存溢出的风险,此处不再讲解
- 调用带有static的方法
方法的重载(Overload) 在同一个类中,功能相似,尽可能让这两个方法名称一致,形参列表不一致 方法重载与返回值类型无关与方法的修饰符列表无关,只与方法名+形参列表有关
例子:
//方法覆盖
public class Overload {
public static void main(String [] args){
System.out.println(sum(1,2));
System.out.println(sum(1.0,2.0)); //此时分别依靠实参数据类型区分。
System.out.println(sum(1L,2L));
}
public static int sum(int a , int b){
return a + b;
}
public static long sum(long a , long b){
return a + b;
}
public static double sum(double a , double b){
return a + b;
}
}
方法的三大特性
Java的三大特性是Java中最基础也是最重要的概念 分别是:
- 封装
封装是将数据和方法隐藏在类的内部,只对外提供公共的访问方式。这有助于保护数据的完整性和安全性,同时简化了编程,使得代码更加模块化。 - 继承 继承允许一个类(子类)继承另一个类(父类)的属性和方法。这有助于实现代码的重用和扩展,提高软件开发的效率。
- 多态 多态是指不同的对象对同一消息做出不同的响应。它允许我们使用父类类型的引用指向子类对象,并根据实际对象类型调用相应的方法。多态性增强了代码的灵活性和可扩展性。
封装
私有成员变量:将类的成员变量设置为私有(使用private关键字),确保这些变量只能在类的内部被访问和修改。外部代码无法直接访问或修改这些私有变量,从而保证了数据的安全性和完整性。
公共方法:私有成员变量不能被外部直接访问,但可以通过在类中定义公共方法(使用public关键字)来提供对这些私有变量的访问和修改。这些公共方法通常包括“getter”方法(用于获取私有变量的值)和“setter”方法(用于设置私有变量的值)。通过这种方式,外部代码可以通过调用这些公共方法来间接地访问和修改私有变量的值,而无需知道其内部实现细节。 代码实例:(User举例)
public class User{
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
继承
继承是面向对象编程(OOP)的四大基本特性之一,它允许我们根据已有的类来创建新类。这种机制有助于实现代码的重用,提高了软件开发的效率。在Java中,继承是通过使用关键字extends来实现的。
1.基本概念
- 父类与子类:被继承的类称为父类(或基类、超类),而继承的类则称为子类(或派生类)。子类可以从父类中继承属性和方法。
- 代码重用:通过继承,子类可以重用父类的代码,从而避免了重复编写相同的代码。这有助于减少代码的冗余,提高代码的可维护性。
- 扩展功能:子类可以在继承父类的基础上,添加新的属性和方法,从而扩展父类的功能。这使得子类能够更具体地满足特定的需求。
2. Java中的继承实现
需要在子类的定义中使用extends关键字,并指定要继承的父类。
例如:
public class Animal {
private String name;
//构造方法
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " is eating.");
}
}
public class Dog extends Animal {
public Dog(String name) {
super(name); // 调用父类的构造方法
}
public void bark() {
System.out.println(name + " is barking.");
}
}
在这个例子中,Dog类继承了Animal类。通过继承,Dog类获得了Animal类的name属性和eat方法。 同时,Dog类还添加了一个新的bark方法。
3. 访问父类的成员
继承的成员:子类可以访问父类的非私有成员。 使用super关键字:子类可以使用super关键字来引用父类的成员。
4. 方法重写(Overriding)
概念:子类可以提供一个与父类相同方法名、参数列表和返回类型的方法,从而覆盖父类中的方法实现。这被称为方法重写。 可以使用@Override注解来明确指示一个方法是重写了父类中的方法。
5. 继承的层次结构
多层继承:一个类可以继承自另一个已经继承了其他类的类,形成多层继承的层次结构。 接口与继承:除了类与类之间的继承,Java还支持接口与类之间的继承关系,这进一步丰富了面向对象编程的灵活性。
6. 继承的优缺点
优点:代码重用、扩展性好、易于维护。 缺点:可能导致类与类之间的耦合度过高,增加系统的复杂性;过多的继承层次可能导致性能下降。
多态
我们来看一个不使用多态的例子
class Master {
public void feed(Dog d){
d.eat();
}
}
class Dog {
public void eat(){
System.out.println("小黑在啃骨头");
}
}
public class Test {
public static void main(String[]args){
Master wu = new Master();
Dog xiaohei = new Dog();
wu.feed(xiaohei);
}
}
在上述例子中我们要增加一个宠物的话,不仅需要新增一个类,还需要修改Master中的代码
所以我们可以说Master类的拓展力很弱
使用多态机制,我们创建一个名为pet的宠物类,使所有的宠物都继承这个类 然后让主人类去喂宠物类
class Master {
public void feed(Pet p){
p.eat();
}
}
class Pet {
public void eat(){
System.out.println("请重写此方法");
}
}
class Dog extends Pet{
public void eat(){
System.out.println("小黑在啃骨头");
}
}
public class Test {
public static void main(String[]args){
Master wu = new Master();
Dog xiaohei = new Dog();
wu.feed(xiaohei);
}
}
这样我们再添加新的宠物时只需要让新的宠物继承宠物类即可,不需要再修改Master类了
这样也就提高了Master类的拓展力
补充
向上转型(upcasting)
子类转化父类型 称为: 自动类型转换
向下转型(downcastinig)
父类转化为子类型(需要加强制转化转换符)称为: 强制类型转换
instanceof 如何使用 格式:引用 instanceof 数据类型名例: a instanceof Animal 运算结果数据类型是布尔类型
true 表示 a 指向的对象是一个Animal类型 false 表示 a 指向的对象不是Animal类型
Java规范要求 强制类型转换前 使用instanceof运算符进项判断 避免出现以上异常
重要关键字
this
this的使用语法this.和this()
this不能出现在静态方法中 this大部分情况都可以省略 this在区分局部变量和实例变量时不能省略(想想setter方法) this() 只能出现在构造方法的第一行,用来调用本类中相对应的构造方法
super
super的语法: super.和super() super不能出现在静态方法中
super大部分情况都可以省略
super()用来调用父类的构造方法
final
final是一个修饰符它有以下规则
- 修饰的类不能被继承
- 修饰的方法不能被覆盖
- 修饰的变量只能赋值一次
- 修饰的实例变量必须手动赋值
- 修饰的引用指向一个对象之后不能发生改变
- 与
static一起修饰的变量被称为 常量
public static final double PI = 3.1415926;
//常量名全部大写
static
- static用来修饰方法,被其修饰的方法可以通过
类名.方法名的方式访问 - static也可用来修饰变量,称为静态变量,静态变量在类加载的时候就完成了初始化,存放在方法区内存中
- 还可以用来定义静态代码块
static{
java语句;
}
静态代码块在类加载时执行,并且只执行一次。
其作用有初始化连接池,解析XML配置文件等等
package & import
打包方便程序的管理 在程序的第一行,只能写一句。
包的命名规范:公司域名倒叙 + 项目名 + 模块名 + 功能要全部小写,并且用.隔开
导报语法格式:
improt 类名;
imptot 包名.类名;
*注意:java.lang.是java的核心类不需要手动引入
使用IDEA打包和导包都会自动进行
抽象类与接口
抽象类(很少使用)
概念
- 类和类之间具有共同特征,提取出来就是抽象类。
例如小猫和小狗都是宠物,它们之间可以提取出宠物这个抽象类 - 由于类本身是不存在的,所以抽象类无法创建对象即:无法实例化
- 但是抽象类有自己的构造方法
- 所以抽象类只能被子类继承,子类可以被实例化
- 所以抽象类不能被final修饰,因为被fianl修饰的类无法被继承
语法:
[修饰符列表] abstract class 类名{类体}
抽象方法(没有方法体的方法 / 未实现的方法)
语法:
public abstract void doSome();
抽象类中的抽象方法,如果被子类继承一定要覆盖。
接口
完全抽象的抽象类,可以被称作特殊的抽象类,我们给他命名为接口
并且用全新的定义方法
[修饰符列表] interface 接口名{}
特点
- 接口可以继承多个接口
interface C extends A,B{}
- 接口中只能有
常量和抽象方法 - 接口中的方法都是公开的并且是抽象的,所以在定义时可以省略
public abstract - 接口中所有的变量都是常量所以也可以简写为
public static final double PI = 3.1415926;
double PI=3.1415926;
作用:与多态类似,面向接口编程,解耦合高拓展
与抽象类的区别
- 接口完全抽象,抽象类半抽象
- 接口没有构造方法,抽象类有构造方法
- 接口支持多继承,类和类只能单继承
- 一个类可以实现多个接口,抽象类只能继承一个抽象类
- 接口中只有常量和抽象方法
接口的实现
类与类之间叫继承 类和接口之间的继承叫实现impements
语法:
interface MyMath{}
class MyMathImpl implements MyMath{}
接口也可以和多态联合使用
MyMath mm = new MyMathImpl();
类可以同时实现多个接口
interface A{}
interface B{}
interface C{}
class D implements A,B,C{}
A a = new D();
B b = new D();
C c = new D();
一个类可以继承另一个类的同时,实现一个接口
注意:extends在前,implements 在后
class A extends B implements C{}A
Object类
概念
Object是Java中所有类的跟类,是直接或间接父类。
所以我们创建的任何类,如果没有指出继承的类,那么它默认会继承Object
Object中定义了一些基本的方法,这些方法在所有的对象中都可用,这些方法包括
1. boolean equals(Object obj)
源码如下:
public boolean equals(Object obj) {
//在Object中它直接用 == 来比较两个对象
return (this == obj);
}
Object建议我们以后所有的类都重写这个方法
String类就重写了这个方法:
public final class String{
public boolean equals(Object anObject) {
// 首先会直接比较两个对象指向的的内存地址
if (this == anObject) {
//如果内存地址相同,就放回true
return true;
}
// 接着在比较传入的anObject对象是否是String类型的实例
if (anObject instanceof String) {
// 如果是先将其强转为 String类型
String anotherString = (String)anObject;
// 再求出其转为字符串后的长度
int n = value.length;
if (n == anotherString.value.length) {
//如果长度相同
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
//逐字比较
while (n-- != 0) {
if (v1[i] != v2[i])
//有不相同的字符就返回false
return false;
i++;
}
// 到此处说明长度相同,且每个字符也都一致
return true;
}
//到此处说明字符串的长度不一致
}
//到此处说明该类型不属于String类型
return false;
}
}
2. String toString()
源码如下:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
Object中toString()的方法也很基础
只返回运行时类的名字通过getClass().getName()获取 和对象哈希码的十六进制表示,通过Integer.toHexString(hashCode())获取 也建议我们修改此方法,让其返回的是通俗易懂的信息
以下是重写的例子:
public class Dog {
//我们定义了一个宠物狗类,他有name和age两个属性
private String name;
private int age;
//我们通过重写toString()方法让其返回,狗狗的name + "名称" , age + “年龄”的格式
@Override
public String toString() {
return "Dog{name='" + name + "', age=" + age + '}';
}
}
3. Class<?> getClass()
Object类中的getClass()方法是用于获取对象的运行时类的Class对象。
这个方法返回一个表示该对象实际所属的类的Class实例。
方法的声明如下:
//被`final`修饰,意味着不能被重写
public final Class<?> getClass()
4.. hashCode()
返回对象的哈希码值 这个方法的默认实现是本地方法(native method),实现在JVM的本地代码
数组
概念
- 数组属于引用数据类型,其父类是Object
- 数组可以容纳多个元素。(数组是一个数据的集合)
- 数组可以存储
基本和引用数据类型 - 数组是引用类型,所以存储再堆内存中
- 数组不能直接存储Java对象,但是可以存储其
引用(内存地址)
分类
- 一维数组
- 二维数组
- 多维数组
二维数组本质就是每个元素是一个一维数组
特点
- 所有的数组都有
length属性,用来获取数组中元素的个数。 - 数组中的元素要求类型统一
- 数组中元素的内存地址是连续的(内存地址是有规律的爱着排列)
- 数组是一种简单的数据结构
- 首元素的内存地址作为整个数组对象的内存地址
- 数组中每个元素都有下标,下标从0开始以1递增,最后一个元素的下标是
length - 1 - 数组中的元素在存或取时都是通过下标来进行
优点
通过下标检索效率极高,可以说是查询效率最高的数据结构 原因:内存地址连续、元素数据类型一致、占用大小一致、可以通过偏移量直接计算出来 因为是通过数学表达式计算出的元素的内存地址,那么10个元素和1w个元素的数组在检索时效率是一样的
缺点
- 为了保证数组中元素地址连续,在随机增删时,后面的元素要统一向前或向后位移
- 不能存储大数据量(因为很难在内存空间上找到一块很大而且连续的内存空间)
注意: 对于最后一个元素的增删效率没有影响
补充
数组的扩容 由于数组一旦确定,长度是不可变的 如果原数组存满,那么会建立一个新的更大的元素,并将原来的数组中元素拷贝到新的数组中。 所以数组拷贝的效率很低,尽量不要让数组扩容
System.arraycopy源码:
//第一个是 源数组,第二个是开始位置,第三个是目标数组,第四个目标数组位置,第五个是拷贝长度
public static native void arraycopy(Object src,int srcPos,Object dest,int destPos,int length);
初始化
以int类型数组举例
- 静态初始化
int[] array = {100,2100,513,12};
- 动态初始化
//此处的5表示数组中元素的个数
//初始的五个元素都是int类型的默认值:0
int[] array = new int[5];
访问时通过数组对象[下标]的方式访问例如array[1]表示访问下标为1的元素
遍历数组:(采用for循环遍历)
for(int i = 0; i < array.length; i++){
System.out.println(a[i]);
}
//同样的方式可以实现倒序遍历
另外可以和多态联合使用如:
Animal Cat Bird 三个类 Cat和Bird extends Animal
可以创建Animal数组,存储其子类 Animal[] animals = {new Cat(),new Bird()}; 如果要遍历子类中特有的方式时,可以在for循环中加上instanceof判断符
if(animals[i] instanceof Cat){
Cat cat = (Cat)animals[i];
}
二维数组
二维数组可以理解为一个特殊的一维数组,它的每一个元素都是一个一维数组。
初始化:
//静态初始化
int[][] array1 = {{1,2,3},{4,5,1}};
//动态初始化
int[][] array2 = new int[3][4];
//意思是创建一个三行四列的二维数组。
二维数组的遍历
也是使用for循环遍历,但是数组内层也有一个一维数组,所以我们也要使用两个for循环嵌套来遍历数组。
public class test{
public static void main(String[] args) {
int[][] array = {{1,2,3},{4,5,6},{7,8,9}};
for(int i =0;i<array.length;i++){
for(int j = 0;j < array[i].length;j++){
System.out.print(array[i][j]);
}
System.out.println();
}
}
}
异常
概念
- 程序执行过程中发生了不正常的情况,而这种不正常的情况就叫做:
异常 - Java提供了异常的处理方式:Java吧该异常信息打印输出到控制台,供程序员参考。
- 程序员看到异常信息后,可以对程序进行修改。
- 异常在Java中都以
类的形式存在 - 每一个异常类都可以创建对象
例如:
int a = 10;
int b = 0;
int c =a / b ;
//JVM运行到这里的时候会new异常对象:
//ArithmeticException("/ by zero")
//并将对象抛出,打印到控制台。
异常的分类

RuntimeException不容易发生,不需要预先处理 ExceptionSubClass容易发生,必须在运行前进行预处理
四种常见的异常:
NullPointerException(空指针异常)ClassCastException(类型转换异常)IndexOutOfBoundsException(数组角标越界异常)NumberFormatException(数字格式化异常)
//应该输入数字,所以会出现数字化格式异常
Inteager a = new Integer(s:"中文");
Java对异常的处理方式
- 在方法声明的位置上,使用
throws关键字上抛。 - 使用
try catch语句惊醒异常的捕捉。 语法:
try {
//可能出现异常的代码
} catch(Exception e) {
//处理异常
}
注意:
- 如果异常采用上抛的方式,在此方法中出现异常的位置后面的代码都不会执行。
try语句块中出现异常的一行,其后面的代码也都不会执行。catch可以写多个,自上而下,范围由小到大。- catch后面也可以采用|符号连接异常类型。
finally子句
特点:
- 用在
try catch语句之后 - 子句中的代码一定会执行,即使
try中出现异常 try中有return;语句,子句中的代码也会执行finally子句中通常写资源的释放/关闭。- 如果
try中有关闭虚拟机的指令System.exit(status: 0);finally就不会执行了
语法
try{
}catch(){
}finally{
}
自定义异常
SUN提供的异常不够用。以后业务出现的异常需要自己去自定义。 定义方式:
步骤一:编写一个类去继承 Exception 或者 RuntimeException 步骤二:提供两个构造方法,一个无参构造方法,一个带有String的参数
下面是一个实际使用自定义异常的例子: 首先,我们定义一个简单的自定义异常类CustomException:
public class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
然后,我们有一个简单的Calculator类,其中包含一个divide方法,这个方法在除数为0时抛出CustomException:
public class Calculator {
public int divide(int numerator, int denominator) throws CustomException {
if (denominator == 0) {
throw new CustomException("除数不能为0");
}
return numerator / denominator;
}
}
最后,在main方法中,我们调用Calculator的divide方法,并处理可能出现的CustomException异常:
public class Main {
public static void main(String[] args) {
Calculator calculator = new Calculator();
try {
int result = calculator.divide(10, 0);
System.out.println("结果是: " + result);
} catch (CustomException e) {
System.err.println("捕获到自定义异常: " + e.getMessage());
}
}
}
在这个例子中,当尝试用10除以0时,Calculator类的divide方法会抛出一个CustomException。main方法中的try-catch块会捕获这个异常,并打印出异常信息。
集合
集合是一个容器,可以容纳其他类型的数据,前面所讲的数组就是一个集合。 所有的集合相关的类和接口都在java.util包下
特点
- 集合不能直接存储基本数据类型(但是代码上不需要体现,因为Java中有自动装箱)
- 另外集合不能直接存储Java对象,而是存储其
内存地址也叫做引用 - 不同集合,底层对应不同的数据结构
- 数据结构就是数据存储的结构,常见的结构有:
数组、二叉树、连败哦、哈希表等。
分类
Java中的集合分为两类 一类是以当个方式存储元素:java.util.Collection
另一类是以键值对的方式存储元素:java.util.Map
图中已经展现出各个集合的特点。
总结常用集合
| 集合 | 存储结构 |
|---|---|
| ArrayList | 数组 |
| LinkedList | 双向链表 |
| HashSet | 底层是HashMap,其存储的元素等于HashMap的key |
| TreeSet | 底层是TreeMap,其存储的元素等于TreeMap的key |
| HashMap | 哈希表 |
| Properties | 线程安全,key和value只能存储String类型,常用做配置类 |
| TreeMap | 二叉树,key可以自动按大小排序 |
Collection
Collection集合没有泛型时,可以存储Object的所有子类 但是存在泛型之后,只能存储泛型的类型
注意:不能存放基本数据类型和对象本身,只能存储封装类或对象的内存地址。
常用方法
| 常用方法 | 说明 |
|---|---|
| boolean add(E e) | 向集合中添加一个元素,添加成功返回true |
| boolean remove(Object o) | 删除某个元素,底层会调用equals方法进行对比,成功返回true |
| int size() | 返回集合中元素的个数 |
| boolean contains(Object o) | 判断集合中是否包含改元素,底层调用equeals(),包含返回ture |
| boolean isEmpty() | 判断集合是否为空,为空返回true |
| void clear() | 清空改集合 |
| Object[] toArray() | 将集合转换为数组 |
演示:
public class Test{
public static void main(String[] args){
//多态,夫类型引用指向子类型对象
Collection c = new ArrayList();
//向集合中添加一个元素1
/*注意Java在中存在自动装箱机制,实际上是:
Integer i = new Integer(1);
*/
c.add(1);
//获取当前集合的元素个数,当前为1
int size = c.size();
//判断当前集合中是否包含1,结果是ture
boolean isContains = c.contains(1);
//清空集合
c.clear();
//判断集合是否为空,结果为ture
boolean isEmpty = c.isEmpty();
}
}
Iterator 迭代器
只有Collection集合才能被迭代器迭代,Map集合不可以使用
| 常用方法 | 说明 |
|---|---|
| boolean hasNext() | 判断当前指向的元素后是否还存在元素 |
| E next() | 返回当前指向的元素,并且指向下一个元素 |
| remove() | 删除迭代器当前指向的元素 |
使用方法:
main(){
//首先需要准备一个Conllection的集合
Collection collections = new ArrayList();
collections.add("元素1");
...
collections.add("元素n");
//获取迭代器对象,改方法继承自Iterator接口
Iterator<String> it = collections.iterator();
//判断当前元素后是否还有元素
while (iterator.hasNext()) {
//使用一个变量接收该元素后,并指向下一个元素
String element = iterator.next();
//打印前面接收的元素
System.out.println(element);
}
}
注意:在迭代的过程中,要删除集合中元素时,请使用迭代器的remove方法。 直接使用Collection中的方法,需要重新获取一个新的迭代器。
List子类
List作为Collection的子类,存在一些特有的方法 这些方法根据该集合的特点而来
| 方法 | 说明 |
|---|---|
| void add(int index, E element) | 向指定下标处添加元素 |
| E get(int index) | 获取指定下标的元素 |
| E set(int index, E elemtnt) | 修改指定下标的元素 |
| int indexOf(Object o) | 返回列表中第一次出现指定元素的索引,如果没有返回-1 |
| int lastIndexOf(Object o) | 返回列表中最后出现的指定元素的索引,如果没有返回-1 |
| E remove(int index) | 删除指定索引的元素 |
ArrayList
特点:
- ArrayList不是线程安全的
- 默认的初始化容量是10
- 底层先创建一个长度为0的数组,添加第一个元素时长度变为10
- 集合的底层时一个Object数组
构造方法:
new ArrayList();//默认初始化方式
new ArrayList(20);//指定初始大小
new ArrayList(一个集合);
//比如将HashSet转化为ArrayList
数组的扩容:
- 增长到原容量的1.5倍
面试题:ArrayList的优缺点
- ArrayList使用最多,与数组的特点相同
- 数组检索效率高//以后经常检索 (由于每个元素占用的空间大小相同,内存地址连续,知道首元素的地址,可根据偏移量计算出目标元素的内存地址)
缺点:
- 数组无法存储大数据量
- 随机增删元素效率低
- 末尾增删不受影响
LinkedList
特点:
- 底层存储在双向链表中
- 由三部分组成:
上一个节点的内存地址、数据、下一个节点的内存地址 - 没有初始化容量,
first和last都是null
Vector(使用很少,了解即可)
- 底层是数组
- 初始化容量是 10
- 扩容之后是原容量的二倍 10-20-40-80
- Vector 都是线程同步/线程安全的,都带有synchronized关键字,使用较少
Map
与Collection没有关系 其存储方式以key-value的形式存储(key和value都是引用数据类型) 不能直接存储对象,只能存储对象的内存地址
Set集合中的元素都等同于Map集合的Key
常用方法
| 方法 | 说明 |
|---|---|
| V put(K key, V value) | 向集合中添加键值对 |
| V get(Object key) | 通过key获取value |
| V remove(Object key) | 通过key删除value |
| Collection< V> values() | 获取所有的value |
| Set< K> keySet() | 返回Map集合中所有的key |
遍历方式
方式一:先获取所有的key,再通过key来获取value
Iterator<Integer> it = keys.interator();
while(it.hasNext()){
//取出一个key
Integer key = it.next();
//通过key获取value
String value = map.get(key);
System.out.println(key+"+"+value);
}
方式二:将Map集合转化为Set集合 效率较高!!适合大数据量
Set<Map.Entry<Integer,String>> set = map.entrySet();
Iterator<Map.Entry<Integer,String>> it2 = map.entrySet();
while(it.hasNext()){
Map.Entry<Integer,String> node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key+"="+value);
}
HashMap
特点:
- 集合的默认初始化容量是16,默认加载因子是0.75
- 初始化容量必须是2的倍数
- 扩容是原来的二倍
- 默认加载因子是HashMap集合容量达到75%时数组开始扩容
- 非线程安全
HashMap的源代码中有以下属性:
final int hash; //实际上是key的hashCode()
final K key
V value
Node < K,V> next;
HashMap 集合允许key值为 null 但是HashMap集合的key null 值只能有一个
了解内容: 如果hashcode();固定为某个值,会使hashMap变成单项列表 这种情况叫:散列分布不均匀 如果hashcode()全部不一样的,会使hashMap变成一维数组 也是散列分布不均匀

散列分布均匀需要重写hascode();有一定的技巧
【最终结论】 放在HashMap key部分的元素和HashSet的元素 需要同时重写equals();和hashcode();
Properties
特点:
- 继承Hashtable
- 初始化容量是11,默认加载因子是0.75
- 扩容是原容量乘以2再加一
- 线程安全
- key和value都是字符串,且不能为空
| 常用方法 | 说明 |
|---|---|
| String getProperty(String key) | 根据键值,获取属性 |
| Object setProperty(String key, String value) | 调用 Hashtable 的方法 put |
TreeMap
特点:
- 底层是二叉树
- 无序不可重复,存入的值会自动按照大小排序(String按字典升序,Integer按大小升序)
- 对自定义的类进行排序时,需要声明比较规则。如果没有实现 java.lang.Comparable比较接口 JVM会报错
注解Annotation
概念
注解:Annotation是一种引用数据类型,编译后生成.Class文件 是Java提供的一种元数据机制,它允许开发者在代码中添加额外的信息或标记。
作用: 这些信息并不直接影响程序的执行逻辑,但可以被编译器、工具或运行时环境读取和使用。
格式 注解以“@”符号开头,后面跟着注解的名称和可选的参数。 例如:@SuppressWarnings(value="unchecked")
使用场景 可以附在package,class,method,field等上面。
Java中自带的注解
| 注解 | 说明 |
|---|---|
| @Override | 重写父类的方法 |
| @Deprecated | 以过时,不推荐使用 |
| @SuppressWarning("value") | 抑制警告,all表所有,unchecked表未检查 |
元注解
作用:元注解负责解释其他注解 Java中定义了4个标准的meta-annotation类型在java.lang.annotation下
| 元注解 | 说明 |
|---|---|
| @Target | 声明注解的使用范围 |
| @Retention | 一般都写为Runtime,运行时有效 |
| @Document | 该注解将被包含在javadoc中 |
| @Inherited | 子类可以结成父类中的该注解 |
自定义注解
基本格式:
[修饰符列表] @interface 注解类型名{
//注解的参数
String name1();
//参数带有默认值
String name2() default "默认值";
}
如果需要在注解添加参数,需要在自定义注解体中声明注解参数 参数类型 + 参数名 + ();
注意:注解中只有一个属性时,建议名称为value
多线程
线程与进程
- 进程:一个应用就是一个进程,如360安全卫士本身是一个进程
- 线程:一个进程的执行单元,如360的电脑优化、木马查杀等功能
作用:提高效率
注意:两个
进程的内存独立不共享,在Java语言中两个线程的堆内存和方法区内存共享,但是栈内存独立
思考:使用了多线程之后main方法结束后,可能程序也不会结束。 因为main方法结束只是代表主线程结束了,主栈空了,其他栈(线程)可能还在。
并发与并行
- 并发:再同一时刻,有多个指令在单个CPU上
交替执行 - 并行:在同一时刻,在多个指令在多个CPU上
同时进行
实现方式
- 继承Thread类,重写run方法
- 实现Runnable接口的方式
- 利用Callable接口和Future接口方式实现
方法三能够获取多线程执行结果
方式一
//新建一个类,继承Thread类
public class OneThread extends Thread{
//重写Run方法
public void run(){
//要多线程执行的代码
}
}
class Test{
public static void main(String[] args){
//创建新建类的实例
OneThread t = new OneThread();
//开启线程
t.start();
//如果要利用多个线程,只需要建多个实例对象
OneThread t1 = new OneThread();
OneThread t2 = new OneThread();
t1.start();
t2.start();
}
}
注意:start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间结束。启动成功的线程会自动调用run()方法,并且run方法在分支栈的栈底部(压栈)
main方法在主栈的栈底部,run方法在分支栈的栈底部。run和main是平级的 如果直接在main方法中调用run方法,相当于普通的调用方法,并不会开辟新的线程。
方式二
//新建一个类,实现 Runnable接口
public class MyThread implements Runnable{
//实现run方法
public void run(){
//想要利用多线程执行的代码
}
}
class Test{
public static void main(String[] args){
MyThread mt = new MyThread();
Thread t = new Thread(mr);
//开启线程
t.start();
}
}
也可以采用匿名内部类的方式
class Test{
public static void main(String[] args){
Thread t = new Thread(new Runable(){
@Override
public void run(){
//想要开启另一个线程执行的代码
}
});
//开启线程
t.start();
}
}
方式三
//新建一个类实现Callable接口
//注意此处的泛型为结果的泛型,此处以返回整数型为例
public class MyCallable implements Callable<Integer>{
//重写Callable中的call方法
public Integer call() throws Exception {
//此处为要利用多线程执行的代码
int number = 1;
return number;
}
}
class{
public static void main(String[] args) throw Exception{
//创建MyCallable的实例
MyCallable mc = new MyCallable();
//创建FutureTask的实例,用来管理多线程运行的结果
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);
//启动线程
t1.start();
//获取多线程运行的结果
int result= ft.get();
}
}
Thread 类中成员方法
| 基本方法 | 说明 |
|---|---|
| String getName() | 获取线程的名字 |
| void setName() | 设置线程的名称(构造方法也可以设置名称) |
| static Thread currentThread() | 获取当前线程的对象 |
| static void sleep(Long time) | 让线程休眠指定的时间ms |
线程默认名称:Thread-0、 Thread-1 ...
优先级方法
Java中采用抢占式调度,所有的线程都是随机执行的
| 优先级方法 | 说明 |
|---|---|
| setPriority(int newPriority) | 设置线程的优先级 |
| final int getProiority() | 获取线程的优先级 |
Java中线程的优先级最低是
1,最高是10,如果没有赋值,则默认是5
守护线程
final void setDaemon(boolean on)
ture:设为守护线程
当非守护线程执行结束之后,守护线程会陆续结束(即使代码块没有执行完毕)
插入线程
public final void join()
将使用该方法的线程插入到当前方法之前。
线程的生命周期
- 刚创建线程对象的
新建状态 - 一直在抢夺执行权的
就绪状态 - 抢夺到执行权的
运行状态 - 遇到sleep或其他阻塞式方法的
阻塞状态 - 线程中代码执行完成后的
死亡状态

注意:
- 线程在
运行期间有可能会被其他线程抢走执行权,从而回到就绪状态 - 阻塞状态结束后,线程会回到就绪状态重新抢夺执行权
同步代码块
由于Java中线程在执行的任何时间都会被抢夺走执行权,如果我们需要让这个线程执行完某个步骤才让其能被夺走,我们可以使用锁来“锁住”这个线程。
格式:
synchronized (锁对象){
操作共享数据的代码
}
注意:锁对象一定是唯一的,一般写为当前类的字节码文件对象如:MyThread.class
特点:
- 锁默认打开,有一个线程进去锁会自动关闭
- 里面的代码全部执行完毕,线程出来锁才会自动打开
面试题
public class ThreadTest {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
try {
//此处会让t线程睡眠5s吗
t.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello World!");
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("T ---> " + i);
}
}
}
答案是主线程睡眠5s,因为sleep方法是静态方法,不能通过对象来调用 代码中存在一个常见的误解关于Thread类的sleep方法的使用。在main方法中尝试调用t.sleep(5000);,这里的t是MyThread类的一个实例,而MyThread是Thread类的子类。然而,sleep方法是Thread类的静态方法,这意味着它应该直接通过类名调用,而不是通过Thread类的实例调用。
正确的调用方式应该是Thread.sleep(5000);,这样会让当前执行的线程(在这个上下文中是主线程)睡眠5秒。
