目录
封装(Encapsulation)
基本介绍
面向对象编程有三大特征:封装、继承和多态。
封装介绍
封装(encapsulation)就是把抽象出的数据[属性]和对数据的操作[方法]封装在一起,数据被保护在内部,程序的其他部分只有通过被授权的操作[方法],才能对数据进行操作
封装的理解和好处
-
隐藏实现细节:方法(连接数据库)<-- 调用(传入参数..)
-
可以对数据进行验证,保证安全合理
封装的实现步骤 (三步)
-
将属性进行私有化 private [不能直接修改属性]
-
提供一个公共的(public)set方法,用于对属性判断并赋值
public void setXxx(类型 参数名){ //Xxx 表示某个属性
//加入数据验证的业务逻辑
属性 = 参数名;
}
-
提供一个公共的(public)get方法,用于获取属性的值
public 数据类型 getXxx(){ //权限判断,Xxx 某个属性
return xx;
}
继承(Extend)
继承给编程带来的便利
1) 代码的复用性提高了
2) 代码的扩展性和维护性提高了
继承的深入讨论/细节问题@@@
1) 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
2) 子类必须调用父类的构造器, 完成父类的初始化
3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过
4) 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
5) super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
6) super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
7) java 所有类都是 Object 类的子类, Object 是所有类的基类
8) 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
9) 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。
思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B,B 继承 C】
10) 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
super 关键字
基本介绍
super 代表父类的引用,用于访问父类的属性、方法、构造器
基本语法
1.访问父类的属性,访问父类的 private 属性
super.属性名;
2.访问父类的方法,不能访问父类的 private 方法
super.方法名(参数列表);
3.访问父类的构造器
super(参数列表); //只能放在构造器的第一句,且只能出现一句
super 给编程带来的便利/细节@@@
1.调用父类构造器的好处
分工明确,父类属性由父类初始化,子类属性由子类初始化
2.当子类中有和父类中的成员(属性和方法)重名时,为了访问父类中的成员,必须通过 super。如果没有重名,使用 super、this、或直接访问是一样的效果!
3.super 的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用 super 去访问爷爷类的成员;如果多个上级类(基类)中都有同名的成员,使用 super 去访问遵循就近原则。A->B->C,当然也需要遵守访问权限的相关规则
super 和 this 的比较
No. | 区别点 | this | super |
1 | 访问属性 | 访问本类中的属性,如果本类中没有此属性则从父类中继续查找 | 直接从父类开始查找属性 |
2 | 调用方法 | 访问本类中的方法,如果本类没有次方法则从父类继续查找 | 直接从父类开始查找方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在构造器首行 |
3 | 特殊 | 表述当前对象 | 子类中访问父类对象 |
方法重写/覆盖(override)
基本介绍
方法覆盖(重写)就是子类有一个方法,和父类的某个方法的名称,返回类型,参数一样,那么我们就说子类的这个方法覆盖了父类的方法
重写注意事项和使用细节 @@@
方法重写也叫方法覆盖,需要满足下面的条件
1.子类的方法的形参列表,方法名称,要和父类的方法的形参列表,方法名称完全一样。
2.子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类。比如 父类 返回类型是 Object,子类方法返回类型是 String。
3.子类方法不能缩小父类方法的访问权限。
public > protected > 默认 > private
方法重写和重载的区别
名称 | 发生范围 | 方法名 | 形参列表 | 返回类型 | 修饰符 |
重载(overload) | 本类 | 必须一样 | 类型,个数或者顺序至少有一个不同 | 无要求 | 无要求 |
重写(override) | 父子类 | 必须一样 | 相同 | 子类重写的方法返回类型和父类返回类型一致,或者是其子类 | 子类方法不能缩小父类方法的访问范围 |
多态(Polymorphism)
面向对象编程-多态
多[多种]态[状态]基本介绍
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
多态的具体体现
1) 方法的多态
重写和重载就体现多态
2) 对象的多态 (核心,困难,重点)
(1)一个对象的编译类型和运行类型可以不一致
(2)对象的编译类型在定义对象时,就确定了,不能改变
(3)对象的运行类型是可以变化的
(4)编译类型看定义时 = 号的左边,运行类型看 = 号的右边
多态注意事项和细节讨论
多态的前提是:
两个对象(类)存在继承关系
多态的向上转型
(1)本质:父类的引用指向了子类的对象
(2)语法:父类类型 引用名 = new 子类类型();
(3)特点:编译类型看左边,运行类型看右边。
可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有成员;最终运行效果看子类的具体实现!
多态的向下转型
(1)语法:子类类型 引用名 = (子类类型) 父类引用;
(2)只能强转父类的引用,不能强转父类的对象
(3)要求父类的引用必须指向的是当前目标类型的对象
(4)当向下转型后,可以调用子类类型中所有的成员
属性没有重写之说!属性的值看编译类型
instanceOf 比较操作符,用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
java 的动态绑定机制(非常非常重要.) @@@
Java 重要特性: 动态绑定机制
1.当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
2.当调用对象属性时,没有动态绑定机制,哪里声明哪里使用
多态的应用
1) 多态数组
数组的定义类型为父类类型,里面保存的实际元素类型为子类类型
1) 多态参数
方法定义的形参类型为父类类型,实参类型允许为子类类型
equals 方法 ==和equals的对比
1) == 是一个比较运算符
-
既可以判断基本类型,又可以判断引用类型
-
如果判断基本类型,判断的是值是否相等
-
如果判断引用类型,判断的是地址是否相等,即判定是不是同一个对象
2) equals 是Object类中的方法
-
只能判断引用类型
-
默认判断的是地址是否相等,子类中往往重写该方法,用于判断是否相等
类变量和类方法(Static)
什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象修改它时,修改的也是同一个变量
定义类变量语法
访问修饰符 static 数据类型 变量名; //推荐
static 访问修饰符 数据类型 变量名;
访问类变量语法
类名.类变量名 //推荐
对象名.类变量名
【静态变量的访问修饰符的访问权限与范围 和 普通属性是一样的】
类变量使用注意事项和细节讨论@@@
-
什么时候需要类变量
当我们需要让某个类的所有对象共享一个变量时,就可以考虑使用类变量(静态变量)
-
类变量与实例变量(普通变量)区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的
-
加上 static 称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
-
类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问
-
实例变量不能通过 类名.类变量名 方式访问
-
类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了
-
类变量的生命周期是随类的加载开始,随着类消亡而销毁
类方法基本介绍
类方法也叫静态方法
形式如下:
访问修饰符 static 数据返回类型 方法名(){ } //推荐
static 访问修饰符 数据返回类型 方法名(){ }
类方法的调用
使用方式:
类名.类方法名
对象名.类方法名
类方法使用注意事项和细节讨论@@@
-
类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:类方法中无 this 参数 ;普通方法中隐含着 this 参数
-
类方法可以通过类名调用,也可以通过对象名调用
-
普通方法和对象有关,需要通过 对象名.方法名(参数) ,不能通过类名调用
-
类方法中不允许使用和对象有关的关键字,比如 this 和 super 。普通方法(成员方法)可以
-
类方法(静态方法)中只能访问 静态变量或静态方法
-
普通成员方法,既可以访问 非静态成员,也可以访问静态成员
小结:静态方法只能访问静态的成员,非静态的方法可以访问(所有成员)静态成员和非静态成员(必须遵守访问权限)
代码块 (Codeblock)
基本介绍
代码块又称为初始化块,属于类中的成员[即是类的一部分],类似与方法,将逻辑语句封装在方法体中,通过{}包围起来
但和方法不同,没有方法名,没有返回值,没有参数,只有方法体,而且不用通过对象或类显示调用,而是加载类时,或创建对象时隐式调用
基本语法
修饰符{
代码
};
说明注意:
-
修饰符 可选,要写的话,也只能写 static
-
代码块分为两类,使用 static 修饰的叫静态代码块,没有 static 修饰的,叫普通代码块/非静态代码块
-
逻辑语句可以为任何逻辑语句(输入,输出,方法调用,循环,判断等)
-
; 号可以写,也可以省略
代码块的好处
-
相当于另外一种形式的构造器(对构造器的补充机制),可以作初始化的操作
-
场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
代码块使用注意事项和细节讨论@@@
-
static 代码块也叫静态代码块,作用就是对类进行初始化。而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行一次
-
类什么时候被加载
①创建对象实例时(new)
②创建子类对象实例,父类也会被加载
③使用类的静态成员时(静态属性,静态方法)
-
普通代码块,在创建对象实例时,就会被隐式调用
被创建一次就会调用一次
如果只是使用类的静态成员时,普通代码块并不会执行
小结:@@@
-
static 代码块是类加载时执行,只会执行一次
-
普通代码块是在创建对象时调用的,创建一次,调用一次
-
类加载的三种情况,需要记住
①创建对象实例时(new) ②创建子类对象实例,父类也会被加载 ③使用类的静态成员时(静态属性,静态方法)
-
创建一个对象时,在一个类调用顺序是:
①调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
②调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用)
③调用构造方法
-
构造器的最前面其实隐含了 super() 和 调用普通代码块;静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
-
当创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
①父类的静态代码块和静态属性
②子类的静态代码块和静态属性
③父类的普通代码块和普通属性初始化
④父类的构造方法
⑤子类的普通代码块和普通属性初始化
⑥子类的构造方法
(优先级一样,按定义顺序执行)
-
静态代码块只能直接调用静态成员(静态属性和方法),普通代码块可以调用任意成员
final 关键字
基本介绍
final 可以修饰类,属性,方法和局部变量
在某些情况下,程序员可能有以下需求,就会使用到 final :
-
当不希望类被继承时
-
当不希望父类的某个方法被子类覆盖/重写时
-
当不希望类的某个属性值被修改时
-
当不希望某个局部变量被修改
final 使用注意事项和细节讨论@@@
-
final 修饰的属性又叫常量,一般用 XX_XX_XX 来命名
-
final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
①定义时
②在构造器中
③在代码块中
-
如果 final 修饰的属性是静态的,则初始化的位置只能是
①定义时
②在静态代码块中
不能在构造器中赋值
-
final 类不能继承,但是可以实例化对象
-
如果类不是 final 类,但是含有 final 方法,则该方法虽然不能被重写,但是可以被继承
-
一般来说,如果一个类已经是 final 类了,就没有必要再将方法修饰成 final 方法
-
final 不能修饰构造方法(即构造器)
-
final 和 static 往往搭配使用,效率更高,不会导致类加载。底层编译做了优化处理
-
包装类(Integer,Double,Float,Boolean,String 等都是 final 类
抽象类 (Abstract)
抽象类的介绍
-
用 abstract 关键字来修饰一个类时,这个类就叫抽象类
访问修饰符 abstract 类名 {
// 成员;
}
-
用 abstract 关键字来修饰一个方法时,这个方法就是抽象方法
访问修饰符 abstract 返回类型 方法名 (参数列表) ;
// 没有方法体
-
抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现抽象类
10.6.4 抽象类使用的注意事项和细节讨论@@@
-
抽象类不能被实例化
-
抽象类不一定要包含抽象方法。也就是说,抽象类可以没有 abstract 方法
-
一旦类包含了 abstract 方法,则这个类必须声明为 abstract
-
abstract 只能修饰类和方法,不能修饰属性和其他的
-
抽象类可以有任意成员【抽象类本质还是类】
-
抽象方法不能有主体,即不能实现
-
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为 abstract 类
-
抽象方法不能使用 private,final,static 来修饰,因为这些关键字都是和重写相违背的
接口 (Interface)
基本介绍
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据具体情况把这些方法写出来。语法:
interface 接口名 {
// 属性;
// 抽象方法;
}
class 类名 implements 接口 {
// 自己的属性;
// 自己的方法;
// 必须实现接口的所有抽象方法;
}
注意事项和细节@@@
-
接口不能被实例化
-
接口中所有的方法是 public 方法,接口中抽象方法,可以不用 abstract 修饰
-
一个普通类实现接口,就必须将该接口所有方法都实现
-
抽象类实现接口,可以不用实现接口的方法
-
一个类同时可以实现多个接口
-
接口中的属性,只能是 final 的,而且是 public static final 修饰符 比如:int a=1; //实际上是 public static final int a=1;(必须初始化)
-
接口中属性的访问形式:接口名.属性名
-
接口不能继承其他的类,但是可以继承多个别的接口
-
接口的修饰符只能是 public 和默认,这点和类的修饰符是一样的
实现接口 vs 继承类
当子类继承了父类,就自动的拥有父类的功能。如果子类需要扩展功能,可以通过实现接口的方式扩展,可以理解为 实现接口
1.接口和继承解决的问题不同
继承的价值主要在于:解决代码的复用性和可维护性
接口的价值主要在于:设计,设计好各种规范(方法),让其他类去实现这些方法。即更加的灵活
2.接口比继承更加灵活
继承是满足 is-a 的关系,而接口只需要满足 like-a 的关系
3.接口在一定程度上实现代码解耦【即:接口规范性+动态绑定机制】
接口与抽象类的默认访问权限
关于抽象类
JDK 1.8时,抽象类的方法默认访问权限变为 default(JDK 1.8以前,抽象类的方法默认访问权限为 protected)
关于接口
JDK 1.9时,接口中的方法可以是 public , default 也可以是 private (JDK 1.8以前,接口中的方法必须是 public 的 JDK 1.8时,接口中的方法可以是 public,也可以是 default)
内部类 (InnerClass)
基本语法
public class InnerClass01 { //外部其他类
public static void main(String[] args) {
}
}
class Outer { //外部类
private int n1 = 100; //属性
public Outer(int n1) { //构造器
this.n1 = n1;
}
public void m1() { //方法
System.out.println("m1()");
}
{//代码块
System.out.println("代码块...");
}
class Inner { //内部类, 在 Outer 类的内部
}
}
内部类的分类
定义在外部类的局部位置上(比如方法内):
-
局部内部类(有类名)
-
匿名内部类(没有类名)
定义在外部类的成员位置上:
-
成员内部类(没有 static 修饰)
-
静态内部类(使用 static 修饰)
局部内部类的使用
说明:局部内部类是定义在外部类的局部位置,比如方法中,并且有类名
-
可以直接访问外部类的所有成员,包含私有成员
-
不能添加访问修饰符,因为它的地位就是一个局部变量。局部变量是不能使用修饰符的。但是可以使用 final 修饰,因为局部变量也可以使用 final
-
作用域:仅仅在定义它的方法或代码块中
-
局部内部类访问外部类的成员 访问方式:直接访问
-
外部类访问局部内部类的成员 访问方式:创建对象,再访问
(注意:必须在作用域内)
小结:(1)局部内部类定义在方法中/代码块中
(2)作用域在方法体或者代码块中
(3)本质仍然是一个类
-
外部其他类不能访问局部内部类(因为局部内部类地位是一个局部变量)
-
如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类.this.成员)去访问
匿名内部类的使用(重要!!!!!!!)@@@
-
本质是类(2)内部类(3)该类没有名字(4)同时还是一个对象
说明:匿名内部类是定义在外部类的局部位置,比如方法中,并且没有类名
匿名内部类的基本语法
new 类或接口 (参数列表) {
类体;
}
-
匿名内部类的语法比较奇特,因为匿名内部类既是一个类的定义,同时它本身也是一个对象,因此从语法上看,它既有定义类的特征,也有创建对象的特征,因此可以调用匿名内部类方法
-
可以直接访问外部类的所有成员,包含私有成员
-
不能添加访问修饰符,因为它的地位就是一个局部变量
-
作用域:仅仅在定义它的方法中或代码块中
-
匿名内部类访问外部类成员 访问方式:直接访问
-
外部其他类不能访问匿名内部类(因为匿名内部类地位是一个局部变量)
-
如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
成员内部类的使用
说明:成员内部类是定义在外部类的成员位置,并且没有 static 修饰
-
可以直接访问外部类的所有成员,包含私有成员
-
可以添加任意访问修饰符(public,protected,默认,private),因为它的地位就是一个成员
-
作用域:和外部类的其他成员一样,为整个类体(在外部类的成员方法中创建成员内部类对象,再调用方法)
-
成员内部类 访问 外部类成员(比如:属性)访问方式:直接访问
-
外部类 访问 成员内部类 访问方式:创建对象,再访问
-
外部其他类 访问 成员内部类
方式 1 : outer08.new Inner08(); 相当于把 new Inner08()当做是outer08 成员 这就是一个语法,不要特别的纠结
方式 2 :在外部类中,编写一个方法,可以返回对象
-
如果外部类和内部类的成员重名时,内部类访问的话,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.this.成员)去访问
静态内部类的使用
说明:静态内部类是定义在外部类的成员位置,并且有 static 修饰
-
可以直接访问外部类的所有静态成员,包含私有成员,但不能直接访问非静态成员
-
可以添加任意访问修饰符(public,protected,默认,private),因为它的地位就是一个成员
-
作用域:同其他的成员,为整个类体
-
静态内部类 访问 外部类(比如:静态属性)访问方式:直接访问所有静态成员
-
外部类 访问 静态内部类 访问方式:创建对象,在访问
-
外部其他类 访问 静态内部类
方式 1 :因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
方式 2 :编写一个方法,可以返回静态内部类的对象实例
-
如果外部类和静态内部类的成员重名时,静态内部类访问时,默认遵循就近原则,如果想访问外部类的成员,则可以使用(外部类名.成员)去访问
枚举(Enumoration)
枚举的二种实现方式
-
自定义类实现枚举
-
使用 enum 关键字实现枚举
自定义类实现枚举
-
将构造器私有化,目的防止 直接 new
-
去掉 setXxx 方法, 防止属性被修改,可以提供 get 方法
-
在本类内部,直接创建固定的对象
-
对外暴露对象(通过为对象添加 public final static 修饰符)
enum 关键字实现枚举
enum 关键字实现枚举注意事项
-
当我们使用 enum 关键字开发一个枚举类时,默认会继承 Enum 类, 而且是一个 final 类
-
传统的 public static final Season2 SPRING = new Season2("春天", "温暖"); 简化成 SPRING("春天", "温暖"), 这里必须知道,它调用的是哪个构造器
-
如果使用无参构造器 创建 枚举对象,则实参列表和小括号都可以省略
-
当有多个枚举对象时,使用逗号“ , ”间隔,最后有一个分号“ ; ”结尾
-
枚举对象必须放在枚举类的行首
enum 常用方法
-
toString:Enum 类已经重写过了,返回的是当前对象名,子类可以重写该方法,用于返回对象的属性信息
-
name:返回当前对象名(常量名),子类中不能重写
-
ordinal:返回当前对象的位置号,默认从 0 开始
-
values:返回当前枚举类中所有的常量
-
valueOf:将字符串转换成枚举对象,要求字符串必须 为已有的常量名,否则报异常!
-
compareTo:比较两个枚举常量,比较的就是编号!
enum 实现接口
-
使用 enum 关键字后,就不能再继承其它类了,因为 enum 会隐式继承 Enum,而 Java 是单继承机制
-
枚举类和普通类一样,可以实现接口,如下形式:
enum 类名 implements 接口 1,接口 2 { };
异常(Exception)
异常体系图
执行过程中所发生的异常事件可分为两大类:
-
Error(错误):Java虚拟机无法解决的严重问题。如:JVM系统内部错误,资源耗尽等严重情况。比如:StackOverflowError(栈溢出)和OutOfMemoryError(内存溢出) 是严重错误,程序会崩溃
-
Exception:其他因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。Exception 分为两大类:运行时异常(程序运行时,发生的异常)和编译时异常(编程时,编译器检查出的异常)
常见的运行时异常 (RuntimeException)
-
NullPointerException 空指针异常
当应用程序试图在需要对象的地方使用 null 时,抛出该异常
-
ArithmeticException 数学运算异常
当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例
-
ArrayIndexOutOfBoundsException 数组下标越界异常
用非法索引访问数组时抛出的异常。如果索引为负或大于等于数组大小,则该索引为非法索引
-
ClassCastException 类型转换异常
当试图将对象强制转换为不是实例的子类时,抛出该异常
-
NumberFormatException 数字格式不正确异常
当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常(使用异常我们可以确保输入是满足条件数字)
常见的编译异常
-
SQLException 操作数据库时,查询表可能发生异常
-
IOException 操作文件时,发生的异常
-
FileNotFoundException 当操作一个不存在的文件时,发生异常
-
ClassNotFoundException 加载类,而该类不存在是,发生异常
-
EOFException 操作文件,到文件末尾,发生异常
-
IllegalArguementException 参数异常
异常处理的方式
-
try-catch-finally 程序员在代码中捕获发生的异常,自行处理
-
throws 将发生的异常抛出,交给调用者(方法)来处理,最顶级的处理者就是JVM
try-catch 方式处理异常(快捷键 ctrl + alt + t)
try {
// 可疑代码;
// 将异常生成对应的异常对象,传递给 catch 块;
} catch (异常) {
// 对异常的处理;
} finally {
//释放资源等;
}
try-catch 方式处理异常-注意事项
-
如果异常发生了,则异常发生后面的代码不会执行,直接进入到 catch 块
-
如果异常没有发生,则顺序执行 try 的代码块,不会进入到 catch
-
如果希望不管是否发生异常,都执行某段代码(比如关闭连接,释放资源等)则使用如下代码- finally
-
如果 try 代码块有可能有多个异常,可以使用多个 catch 分别捕获不同的异常,相应处理。要求子类异常写在前面,父类异常写在后面
-
可以进行 try-finally 配合使用, 这种用法相当于没有捕获异常, 因此程序会直接崩掉/退出。应用场景,就是执行一段代码,不管是否发生异常, 都必须执行某个业务逻辑
throws 异常处理
基本介绍
在方法声明中用 throws 语句可以声明抛出异常的列表, throws 后面的异常类型可以是方法中产生的异常类型,也可以是它的父类
基本语法
访问修饰符 方法名 () throws 异常类型 {
}
注意事项和使用细节
-
对于编译异常,程序中必须处理,比如 try-catch 或者 throws
-
对于运行时异常,程序中如果没有处理,默认就是 throws 的方式处理
-
子类重写父类的方法时,对抛出异常的规定:子类重写的方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出的异常类型的子类型
-
在 throws 过程中,如果有方法 try-catch , 就相当于处理异常,就可以不必 throws
自定义异常
基本概念
当程序中出现了某些“错误”,但该错误信息并没有在 Throwable 子类中描述处理,这个时候可以自己设计异常类,用于描述该错误信息
自定义异常的步骤
-
定义类:自定义一个异常类名继承 Exception 或 RuntimeException
-
如果继承Exception,属于编译异常
-
如果继承 RuntimeException,属于运行异常(一般继承 RuntimeException)
自定义异常基本语法
public class 类名 {
public static void main(String[] args){
//设置抛出异常机制
throw new 自己定义的异常名;
}
}
class 自己定义的异常名 extends RuntimeException {
public 自己定义的异常名 (String message) {//构造器
super(message);
}
}
throw 和 throws的区别
意义 | 位置 | 后面跟的东西 | |
throws | 处理异常的一种方式 | 方法声明处 | 异常类型 |
throw | 手动生成异常对象的关键字 | 方法体中 | 异常对象 |
常用类
包装类 (Wrapper)
包装类的分类
-
针对八种基本数据类型相应的引用类型—包装类
-
有了类的特点,就可以调用类中的方法。
基本数据类型 | 包装类 | |
boolean | Boolean | |
char | Char | |
byte | Byte | 父类 Number |
short | Short | |
int | Int | |
long | Long | |
float | Float | |
double | Double |
包装类和基本数据的转换
-
jdk5 以前是手动装箱和拆箱方式
-
jdk5 以后(含jdk5)是自动装箱和拆箱方式
-
自动装箱底层调用的是 valueOf 方法,比如 IntegerOf()
-
其它包装类的用法类似,不一一举例
这里以 int 和 Integer 演示
public class Integer01 {
public static void main(String[] args) {
//演示 int <--> Integer 的装箱和拆箱
//jdk5 前是手动装箱和拆箱
//手动装箱 int->Integer
int n1 = 100;
Integer integer = new Integer(n1);
Integer integer1 = Integer.valueOf(n1);
//手动拆箱 Integer -> int
int i = integer.intValue();
//jdk5 后,就可以自动装箱和自动拆箱
int n2 = 200;
//自动装箱 int->Integer
Integer integer2 = n2; //底层使用的是 Integer.valueOf(n2)
//自动拆箱 Integer->int
int n3 = integer2; //底层仍然使用的是 intValue()方法
}
}
包装类型和 String 类型的相互转换
这里以 Integer 和 String 演示,其它类似
public class WrapperVSString {
public static void main(String[] args) {
//包装类(Integer)->String
Integer i = 100;//自动装箱
//方式 1
String str1 = i + "";
//方式 2
String str2 = i.toString();
//方式 3
String str3 = String.valueOf(i);
//String -> 包装类(Integer)
String str4 = "12345";
//方式 1 使用到自动装箱
Integer i2 = Integer.parseInt(str4);
//方式 2 使用构造器
Integer i3 = new Integer(str4);
}
}
Integer 类和 Character 类的常用方法
public class WrapperMethod {
public static void main(String[] args) {
System.out.println(Integer.MIN_VALUE); //返回最小值
System.out.println(Integer.MAX_VALUE);//返回最大值
System.out.println(Character.isDigit('a'));//判断是不是数字
System.out.println(Character.isLetter('a'));//判断是不是字母
System.out.println(Character.isUpperCase('a'));//判断是不是大写
System.out.println(Character.isLowerCase('a'));//判断是不是小写
System.out.println(Character.isWhitespace('a'));//判断是不是空格
System.out.println(Character.toUpperCase('a'));//转成大写
System.out.println(Character.toLowerCase('A'));//转成小写
}
}
String 类
String 类的理解和创建对象
-
String 对象用语保存字符串,也就是一组字符序列
-
字符串常量对象是双引号括起的字符序列。例如:"你好","123","boy" 等
-
字符串的字符使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节
代码示例
public class String01 {
public static void main(String[] args) {
//1.String 对象用于保存字符串,也就是一组字符序列
//2. "jack" 字符串常量, 双引号括起的字符序列
//3. 字符串的字符使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节
//4. String 类有很多构造器,构造器的重载
// 常用的有 String s1 = new String(); //
//String s2 = new String(String original);
//String s3 = new String(char[] a);
//String s4 = new String(char[] a,int startIndex,int count)
//String s5 = new String(byte[] b)
//5. String 类实现了接口 Serializable【String 可以串行化:可以在网络传输】
// 接口 Comparable [String 对象可以比较大小]
//6. String 是 final 类,不能被其他的类继承
//7. String 有属性 private final char value[]; 用于存放字符串内容
//8. 一定要注意:value 是一个 final 类型, 不可以修改(需要功力):即 value 不能指向
// 新的地址,但是单个字符内容是可以变化
String name = "jack";
name = "tom";
final char[] value = {'a','b','c'};
char[] v2 = {'t','o','m'};
value[0] = 'H';
//value = v2; 不可以修改 value 地址
}
}
创建 String 对象的两种方式与区别
方式一:直接赋值 String s1 = "hsp";
先从常量池查看是否有 "hsp" 数据空间,如果有,直接指向;如果没有则重新创建,然后指向。s1 最终指向的是常量池的空间地址
方式二:调用构造器 String s2 = new String("hsp");
先在堆中创建空间,里面维护了 value 属性,指向常量池的 hsp 空间。如果常量池没有 "hsp" ,重新创建,如果有,直接通过 value 指向。最终指向的是堆中的空间地址
字符串的特性
-
String 是一个 final 类,代表不可变的字符序列
-
字符串是不可变的。一个字符串对象一旦被分配,期内容是不可变的
String 类的常见方法
-
equals 比较内容是否相同,区分大小写
-
equalsIgnoreCase 忽略大小写的判断内容是否相等
-
length 获取字符的个数,字符串的长度
-
indexOf 获取字符在字符串对象中第一次出现的索引,索引从 0 开始,如果找不到,返回 -1
-
lastIndexOf 获取字符在字符串中最后一次出现的索引,索引从 0 开始,如果找不到,返回 -1
-
substring 截取指定范围的子串
-
toUpperCase 转换成大写
-
toLowerCase 转换成小写
-
concat 拼接字符串
-
replace 替换字符串中的字符
-
split 分割字符串
-
toCharArray 转换成字符数组
-
compareTo 比较两个字符串的大小,如果前者大,则返回正数,后者大,则返回负数,如果相等,返回 0
-
format 格式字符串(%s , %d , %.2f,%c 称为占位符)
-
这些占位符由后面变量来替换
-
%s 表示后面由 字符串来替换
-
%d 是整数来替换
-
%.2f 表示使用小数来替换,替换后,只会保留小数点两位, 并且进行四舍五入的处理
-
%c 使用 char 类型来替换
StringBuffer 类
基本介绍
-
java.lang.StringBuffer 代表可变字符序列,可以对字符串内容进行增删
-
很多方法与 String 相同,但 StringBuffer 是可变长度的
-
StringBuffer 是一个容器
String VS StringBuffer
-
String 保存的是字符串常量,里面的值不能更改,每次 String 类的更新实际上就是更改地址,效率较低
-
StringBuffer 保存的是字符串变量,里面的值可以更改,每次 StringBuffer 的更新实际上可以更新内容,不用每次更新地址,效率较高
String 和 StringBuffer 相互转换
代码示例
public class StringAndStringBuffer {
public static void main(String[] args) {
//看 String --> StringBuffer
String str = "hello tom";
//方式 1 使用构造器
//注意: 返回的才是 StringBuffer 对象,对 str 本身没有影响
StringBuffer stringBuffer = new StringBuffer(str);
//方式 2 使用的是 append 方法
StringBuffer stringBuffer1 = new StringBuffer();
stringBuffer1 = stringBuffer1.append(str);
//看看 StringBuffer ->String
StringBuffer stringBuffer3 = new StringBuffer("韩顺平教育");
//方式 1 使用 StringBuffer 提供的 toString 方法
String s = stringBuffer3.toString();
//方式 2: 使用构造器来搞定
String s1 = new String(stringBuffer3);
}
}
StringBuffer 类常见方法
代码示例
public class StringBufferMethod {
public static void main(String[] args) {
StringBuffer s = new StringBuffer("hello");
//增
s.append(',');// "hello,"
s.append("张三丰");//"hello,张三丰"
s.append("赵敏").append(100).append(true).append(10.5);
System.out.println(s);//"hello,张三丰赵敏 100true10.5"
//删
/*
* 删除索引为 >=start && <end 处的字符
* 解读: 删除 11~14 的字符 [11, 14)
*/
s.delete(11, 14);
System.out.println(s);//"hello,张三丰赵敏 true10.5"
//改
//老韩解读,使用 周芷若 替换 索引 9-11 的字符 [9,11)
s.replace(9, 11, "周芷若");
System.out.println(s);//"hello,张三丰周芷若 true10.5"
//查找指定的子串在字符串第一次出现的索引,如果找不到返回-1
int indexOf = s.indexOf("张三丰");
System.out.println(indexOf);//6
//插
//老韩解读,在索引为 9 的位置插入 "赵敏",原来索引为 9 的内容自动后移
s.insert(9, "赵敏");
System.out.println(s);//"hello,张三丰赵敏周芷若 true10.5"
//长度
System.out.println(s.length());//22
}
}
Stringbuilder 类
基本介绍
-
一个可变的字符序列。此类提供一个与 StringBuffer 兼容的 API ,但不保证同步(StringBuilder 不是线程安全)。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区单个线程使用的时候。如果可以,建议有限采用该类,因为在大多数实现中,它比 StringBuffer 要快
-
在StringBuilder 上的主要操作是 append 和 insert 方法,可以重载这些方法,以接收任意类型的数据
StringBuilder 常用方法
StringBuilder 和 StringBuffer 均代表可变的字符,方法是一样的,所以使用和 StringBuffer 一样
String、StringBuffer 和 StringBuilder 的比较
-
StringBuilder 和 StringBuffer 非常类似,均代表可变的字符序列,而且方法也是一样
-
String :不可变字符序列,效率低,但是复用率高(如果要对 String 做大量修改,不要使用 String)
-
StringBuffer :可变字符序列,效率较高(增删),线程安全
-
SringBuilder :可变字符序列,效率最高,线程不安全
String、StringBuffer 和 StringBuilder 的选择
-
如果字符串存在大量的修改操作,一般使用 StringBuffer 或 Stringbuilder
-
如果字符串存在大量的修改操作,并在单线程的情况,使用 StringBuiler
-
如果字符串存在大量的修改操作,并在多线程的情况,使用 StringBuffer
-
如果对字符串很少修改,被多个对象引用,使用 String
Math 类
基本介绍
Math 类包含用于执行基本数学运算的方法,如初等指数、对数、平方根和三角函数
常见方法示例(均为静态方法)
public class MathMethod {
public static void main(String[] args) {
//看看 Math 常用的方法(静态方法)
//1.abs 绝对值
int abs = Math.abs(-9);
System.out.println(abs);//9
//2.pow 求幂
double pow = Math.pow(2, 4);//2 的 4 次方
System.out.println(pow);//16
//3.ceil 向上取整,返回>=该参数的最小整数(转成 double);
double ceil = Math.ceil(3.9);
System.out.println(ceil);//4.0
//4.floor 向下取整,返回<=该参数的最大整数(转成 double)
double floor = Math.floor(4.001);
System.out.println(floor);//4.0
//5.round 四舍五入 Math.floor(该参数+0.5)
long round = Math.round(5.51);
System.out.println(round);//6
//6.sqrt 求开方
double sqrt = Math.sqrt(9.0);
System.out.println(sqrt);//3.0
//7.random 求随机数
// random 返回的是 0 <= x < 1 之间的一个随机小数
// 思考:请写出获取 a-b 之间的一个随机整数,a,b 均为整数 ,比如 a = 2, b=7
// 即返回一个数 x 2 <= x <= 7
// 老韩解读 Math.random() * (b-a) 返回的就是 0 <= 数 <= b-a
// (1) (int)(a) <= x <= (int)(a + Math.random() * (b-a +1) )
// (2) 使用具体的数给小伙伴介绍 a = 2 b = 7
// (int)(a + Math.random() * (b-a +1) ) = (int)( 2 + Math.random()*6)
// Math.random()*6 返回的是 0 <= x < 6 小数
// 2 + Math.random()*6 返回的就是 2<= x < 8 小数
// (int)(2 + Math.random()*6) = 2 <= x <= 7
// (3) 公式就是 (int)(a + Math.random() * (b-a +1) )
for (int i = 0; i < 100; i++) {
System.out.println((int) (2 + Math.random() * (7 - 2 + 1)));
}
//max , min 返回最大值和最小值
int min = Math.min(1, 9);
int max = Math.max(45, 90);
System.out.println("min=" + min);
System.out.println("max=" + max);
}
}
Arrays 类
基本介绍
Arrays 里面包含了一系列静态方法,用于管理或操作数组(比如排序和搜索)
常见方法示例(均为静态方法)
public class ArraysMethod01 {
public static void main(String[] args) {
//1. toString 返回数组的字符串形式
Integer[] integers = {1, 20, 90};
System.out.println(Arrays.toString(integers));
//2. sort 排序
Integer[] sortArr = {1, -1, 7, 0, 3};
Arrays.sort(sortArr);
System.out.println(Arrays.toString(sortArr));
//3. binarySearch 二分查找
Integer[] searchArr = {1,2,4,7,23,45,47,89};
int index = Arrays.binarySearch(searchArr,23);
System.out.println(index);
//4. copyOf 数组copy
Integer[] newArr = Arrays.copyOf(searchArr, searchArr.length-1);
System.out.println(Arrays.toString(newArr));
//5. fill 数组元素填充
Integer[] num = {1,2,3,4,5,7,6};
Arrays.fill(num,66);
System.out.println(Arrays.toString(num));
}
}
System 类
常见方法示例
public class System_ {
public static void main(String[] args) {
//1. exit 退出当前程序
System.out.println("ok1");
// //1. exit(0) 表示程序退出
// //2. 0 表示一个状态 , 正常的状态
System.exit(0);//程序执行到此就会结束
System.out.println("ok2");
//2. arraycopy :复制数组元素,比较适合底层调用,
// 一般使用 Arrays.copyOf 完成复制数组
int[] src={1,2,3};
int[] dest = new int[3];// dest 当前是 {0,0,0}
//主要是搞清楚这五个参数的含义
// * @param src the source array. // srcPos: 从源数组的哪个索引位置开始拷贝
// * @param srcPos starting position in the source array. // dest : 目标数组,即把源数组的数据拷贝到哪个数组
// * @param dest the destination array. // destPos: 把源数组的数据拷贝到 目标数组的哪个索引
// * @param destPos starting position in the destination data. // length: 从源数组拷贝多少个数据到目标数组
// * @param length the number of array elements to be copied.
System.arraycopy(src, 0, dest, 0, src.length);
System.out.println("dest=" + Arrays.toString(dest));//[1, 2, 3]
//3. currentTimeMillens:返回当前时间距离 1970-1-1 的毫秒数
System.out.println(System.currentTimeMillis());
}
}
BigInteger 和 BigDecimal 类
基本介绍
-
BigInteger 适合保存比较大的整型
-
BigDecimal 适合保存精度更高的浮点型(小数)
public class BigInteger_ {
public static void main(String[] args) {
//当我们编程中,需要处理很大的整数,long 不够用
//可以使用 BigInteger 的类来搞定
// long l = 23788888899999999999999999999l;
// System.out.println("l=" + l);
BigInteger bigInteger = new BigInteger("23788888899999999999999999999");
BigInteger bigInteger2 = new BigInteger("1009999999999999999999999999999");
System.out.println(bigInteger);
//1. 在对 BigInteger 进行加减乘除的时候,需要使用对应的方法,不能直接进行 + - * /
//2. 可以创建一个 要操作的 BigInteger 然后进行相应操作
BigInteger add = bigInteger.add(bigInteger2);
System.out.println(add);//加
BigInteger subtract = bigInteger.subtract(bigInteger2);
System.out.println(subtract);//减
BigInteger multiply = bigInteger.multiply(bigInteger2);
System.out.println(multiply);//乘
BigInteger divide = bigInteger.divide(bigInteger2);
System.out.println(divide);//除
}
}
public class BigDecimal_ {
public static void main(String[] args) {
//当我们需要保存一个精度很高的数时,double 不够用
//可以是 BigDecimal
// double d = 1999.11111111111999999999999977788d;
// System.out.println(d);
BigDecimal bigDecimal = new BigDecimal("1999.11");
BigDecimal bigDecimal2 = new BigDecimal("3");
System.out.println(bigDecimal);
//1. 如果对 BigDecimal 进行运算,比如加减乘除,需要使用对应的方法
//2. 创建一个需要操作的 BigDecimal 然后调用相应的方法即可
System.out.println(bigDecimal.add(bigDecimal2));
System.out.println(bigDecimal.subtract(bigDecimal2));
System.out.println(bigDecimal.multiply(bigDecimal2));
//System.out.println(bigDecimal.divide(bigDecimal2));//可能抛出异常 ArithmeticException
//在调用 divide 方法时,指定精度即可. BigDecimal.ROUND_CEILING
//如果有无限循环小数,就会保留 分子 的精度
System.out.println(bigDecimal.divide(bigDecimal2, BigDecimal.ROUND_CEILING));
}
}
日期类(Date)
第三代日期类
public class LocalDate01 {
public static void main(String[] args) {
//第三代日期
//1. 使用 now() 返回表示当前日期时间的 对象
LocalDateTime ldt = LocalDateTime.now(); //LocalDate.now();//LocalTime.now()
System.out.println(ldt);
//2. 使用 DateTimeFormatter 对象来进行格式化
// 创建 DateTimeFormatter 对象
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String format = dateTimeFormatter.format(ldt);
System.out.println("格式化的日期=" + format);
System.out.println("年=" + ldt.getYear());
System.out.println("月=" + ldt.getMonth());//英文月份
System.out.println("月=" + ldt.getMonthValue());//数字月份
System.out.println("日=" + ldt.getDayOfMonth());
System.out.println("时=" + ldt.getHour());
System.out.println("分=" + ldt.getMinute());
System.out.println("秒=" + ldt.getSecond());
LocalDate now = LocalDate.now(); //可以获取年月日
LocalTime now2 = LocalTime.now();//获取到时分秒
//提供 plus 和 minus 方法可以对当前时间进行加或者减
//看看 890 天后,是什么时候 把 年月日-时分秒
LocalDateTime localDateTime = ldt.plusDays(890);
System.out.println("890 天后=" + dateTimeFormatter.format(localDateTime));
//看看在 3456 分钟前是什么时候,把 年月日-时分秒输出
LocalDateTime localDateTime2 = ldt.minusMinutes(3456);
System.out.println("3456 分钟前 日期=" + dateTimeFormatter.format(localDateTime2));
}
}
集合
集合的好处
-
可以动态保存任意多个对象,使用比较方便
-
提供了一系列方便的操作对象的方法
-
使用集合添加、删除新元素的示意代码
集合的框架体系
Collection 接口和常用方法
Collection 接口实现类的特点
-
Collection实现子类可以存放多个元素,每个元素可以是 Object
-
有些 Collection 的实现类,可以存放重复元素,有些不可以
-
有些 Collection 的实现类,有些是有序的(List),有些不是有序的(Set)
-
Collection 接口没有直接的实现子类,是通过它的子接口 List 和 Set 来实现的
Collection 接口遍历元素方式 1-使用 Iterator(迭代器)
基本介绍
-
Iterator 对象成为迭代器,主要用于遍历 Collection 集合中的元素
-
所有实现了 Collection 接口的集合类都有一个 iterator() 方法,用以返回一个实现了 Iterator 接口的对象,即可以返回一个迭代器
-
Iterator 仅用于遍历集合,Iterator 本身并不存放对象
基本语法
//方式一
Iterator iterator = 集合名.iterator();// 得到一个集合的迭代器
while (iterator.hasNext()) { // while 快捷键 -> itit
// hasNext() 判断是否还有下一个元素,如果没有则退出
Object next = iterator.next(); // 接收元素
// next() 作用:1.下移 2.将下移以后集合位置上的元素返回
System.out.println(next);
}
//方式二
for(元素类型 元素名 : 集合名或数组名) {
System.out.println(元素名);
}
List 接口和常用方法
List 接口基本介绍
List 接口是 Collection 接口的子接口
-
List 集合类中元素有序(即添加顺序和取出顺序一致),且可重复
-
List 集合类中的每个元素都有其对应的顺序索引,即支持索引
-
List 容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
-
JDK API 中 List 接口的常用实现类有:ArrayList , LinkedList , Vector
List 接口的常用方法
代码示例
public class ListMethod {
@SuppressWarnings({"all"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("张三丰");
list.add("贾宝玉");
// void add(int index, Object ele):在 index 位置插入 ele 元素
//在 index = 1 的位置插入一个对象
list.add(1, "韩顺平");
System.out.println("list=" + list);
// boolean addAll(int index, Collection eles):从 index 位置开始将 eles 中的所有元素添加进来
List list2 = new ArrayList();
list2.add("jack");
list2.add("tom");
list.addAll(1, list2);
System.out.println("list=" + list);
// Object get(int index):获取指定 index 位置的元素
System.out.println(list.get(3));
// int indexOf(Object obj):返回 obj 在集合中首次出现的位置
System.out.println(list.indexOf("tom"));//2
// int lastIndexOf(Object obj):返回 obj 在当前集合中末次出现的位置
list.add("韩顺平");
System.out.println("list=" + list);
System.out.println(list.lastIndexOf("韩顺平"));
// Object remove(int index):移除指定 index 位置的元素,并返回此元素
list.remove(0);
System.out.println("list=" + list);
// Object set(int index, Object ele):设置指定 index 位置的元素为 ele , 相当于是替换. list.set(1, "玛丽");
System.out.println("list=" + list);
// List subList(int fromIndex, int toIndex):返回从 fromIndex 到 toIndex 位置的子集合
// 注意返回的子集合 fromIndex <= subList < toIndex
List returnlist = list.subList(0, 2);
System.out.println("returnlist=" + returnlist);
}
}
List 的三种遍历方式 [ArrayList, LinkedList,Vector]
方式一:使用 iterator
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
Object o = iterator.next();
System.out.println(o);
}
方式二:使用增强 for
for(Object o : list) {
System.out.println(o);
}
方式三:使用普通 for
for(int i=0; i<list.size(); i++){
Object object = list.get(i);
System.out.println(object);
}
ArrayList 和 LinkedList 的比较
底层结构 | 增删的效率 | 改查的效率 | |
ArrayList | 可变数组 | 较低,通过数组扩容 | 较高 |
ListedList | 双向链表 | 较高,通过链表追加 | 较低 |
Set 接口和常用方法
Set 接口基本介绍
-
无序(添加和取出的顺序不一致),没有索引
-
不允许重复元素,所以最多包含一个 null
-
JDK API 中 Set 接口的常用实现类有:HashSet , TreeSet
Set 接口的常用方法
和 List 接口一样,Set 接口也是 Collection 的子接口,因此,常用方法和 Collection 接口一样
Set 接口的遍历方式
同 Collection 的遍历方式一样,因为 Set 接口是 Collection 接口的子接口
-
可以使用迭代器
-
增强 for
-
不能使用索引的方式来获取
Set 接口实现类-HashSet
HashSet 的全面说明
-
HashSet 实现了 Set 接口
-
HashSet 底层实际上是 HashMap
-
可以存放 null 值,但是只能有一个 null
-
HashSet 不保证元素是有序的,取决于 hash 后,在确定索引的结果(即,不保证存放元素的顺序和取出的顺序一致)
-
不能有重复元素/对象,在前面 Set 接口使用已经讲过
Set 接口实现类-LinkedHashSet
LinkedHashSet 的全面说明
-
LinkedHashSet 是 HashSet 的子类
-
LinkedHashSet 底层是一个 LinkedHashMap ,底层维护了一个 数组 + 双向链表
-
LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的
-
LinkedHashSet 不允许添加重复元素
Map 接口和常用方法
Map 接口实现类的特点
-
Map 与 Collection 并列存在。用于保存具有映射关系的数据:Key-Value(双列元素)
-
Map 中的 Key 和 Value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中
-
Map 中的 key 不允许重复,原因和 HashSet 一样
-
Map 中的 value 可以重复
-
Map 的 key 可以为 null ,value 也可以为 null ,注意 key 为 null 只能有一个,value 为 null 可以多个
-
常用 String 类作为 Map 的key
-
key 和 value 之间存在单向一对一关系,即通过指定的 key 总能找到对应的 value
Map 接口遍历方法
@SuppressWarnings({"all"})
public class MapFor {
public static void main(String[] args) {
Map map = new HashMap();
map.put("邓超", "孙俪");
map.put("王宝强", "马蓉");
map.put("宋喆", "马蓉");
map.put("刘令博", null);
map.put(null, "刘亦菲");
map.put("鹿晗", "关晓彤");
//第一组: 先取出 所有的 Key , 通过 Key 取出对应的 Value
Set keyset = map.keySet();
//(1) 增强 for
System.out.println("-----第一种方式-------");
for (Object key : keyset) {
System.out.println(key + "-" + map.get(key));
}
//(2) 迭代器
System.out.println("----第二种方式--------");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
Object key = iterator.next();
System.out.println(key + "-" + map.get(key));
}
//第二组: 把所有的 values 取出
Collection values = map.values();
//这里可以使用所有的 Collections 使用的遍历方法
//(1) 增强 for
System.out.println("---取出所有的 value 增强 for----");
for (Object value : values) {
System.out.println(value);
}
//(2) 迭代器
System.out.println("---取出所有的 value 迭代器----");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
Object value = iterator2.next();
System.out.println(value);
}
//第三组: 通过 EntrySet 来获取 k-v
Set entrySet = map.entrySet();// EntrySet<Map.Entry<K,V>>
//(1) 增强 for
System.out.println("----使用 EntrySet 的 for 增强(第 3 种)----");
for (Object entry : entrySet) {
//将 entry 转成 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
//(2) 迭代器
System.out.println("----使用 EntrySet 的 迭代器(第 4 种)----");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
Object entry = iterator3.next();
//System.out.println(next.getClass());//HashMap$Node -实现-> Map.Entry (getKey,getValue)
//向下转型 Map.Entry
Map.Entry m = (Map.Entry) entry;
System.out.println(m.getKey() + "-" + m.getValue());
}
}
}
Map 接口实现类-HashMap
HashMap 小结
-
Map 接口的常用实现类:HashMap , Hashtable 和 Properties
-
HashMap 是 Map 接口使用频率最高的实现类
-
HashMap 是以 key-value 键值对的方式来存储数据(HashMap$Node 类型)
-
key 不能重复,但是值可以重复,允许使用 null 键和 null值
-
如果添加相同的 key ,则会覆盖原来的 key-value ,等同于修改(key 不会替换,value 会替换)
-
与 HashSet 一样,不保证映射的顺序,因为底层是以 hash 表的方式来存储的
-
HashMap 没有实现同步,因此线程是不安全的,方法没有做同步的互斥操作,没有 synchronized
Map 接口实现类-Hashtable
Hashtable 的基本介绍
-
存放的元素是键值对:即 K-V
-
Hashtable 的键和值都不能为 null ,否则会抛出 NullPointerException
-
Hashtable 使用方法基本上和 HashMap 一样
-
Hashtable 是线程安全的(synchronized),HashMap 是线程不安全的
Hashtable 和 HashMap 对比
版本 | 线程安全(同步) | 效率 | 允许null键null值 | |
HashMap | 1.2 | 不安全 | 高 | 可以 |
Hashtable | 1.0 | 安全 | 较低 | 不可以 |
Map 接口实现类-Properties
基本介绍
-
Properties 类继承自 Hashtable 类并且实现了 Map接口,也是使用一种键值对的形式来保存数据
-
它的使用特点和 Hashtable 类似
-
Properties 还可以用于从 xxx.properties 文件中,加载数据到 Properties 类对象,并进行读取和修改
-
说明:工作后 xxx.properties 文件通常作为配置文件,这个知识点在IO流举例
总结 开发中如何选择集合实现类
在开发中,选择什么集合实现类,主要取决于业务操作特点,然后根据集合实现类特性进行选择,分析如下:
-
先判断存储类型(一组对象[单列]或一组键值对[双列])
-
一组对象[单列]:Collection接口
允许重复:List
增删多:LinkedList(底层维护了一个双向链表)
改查多:ArrayList(底层维护 Object 类型的可变数组)
不允许重复:Set
无序:HashSet(底层是 HashMap,维护了一个哈希表 即(数组 + 链表 + 红黑树))
排序:TreeSet
插入和取出顺序一致:LinkedHashSet ,维护数组 + 双向链表
-
一组键值对[双列]:Map
键无序:HashMap(底层是:哈希表)
键排序:TreeMap
键插入和取出顺序一致:LinkedHashMap
读取文件:Properties
Collections 工具类
Collections 工具类介绍
-
Collections 是一个操作 Set , List 和 Map 等集合的工具类
-
Collections 中提供了一系列静态的方法对集合元素进行排序,查询和修改等操作
排序操作:(均为 static 方法)
-
reverse(List):反转 List 中元素的顺序
-
shuffle(List):对 List 集合元素进行随机排序
-
sort(List):根据元素的自然顺序对指定 List 集合元素按升序排序
-
sort(List , Comparator):根据指定的 Comparator 产生的顺序对 List 集合元素进行排序
-
swap(List , int , int):将指定 List 集合中的 i 处元素和 j 处元素进行交换
查找,替换:
-
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
-
Object max(Collection , Comparator):根据 Comparator 指定的顺序返回给定集合中的最大元素
-
Object min(Collection)
-
Object min(Collection , Comparator)
-
int frequency(Collection , Object):返回指定集合中指定元素的出现次数
-
void copy(List dest , List src):将 src 中的内容复制到 dest 中
-
boolean replaceAll(List list , Object oldVal , Object newVal):使用新值替换 List 对象的所有旧值
泛型
泛型的好处
-
编译时,检查添加元素的类型,提高了安全性
-
减少了类型转换的次数,提高效率
-
不再提示编译警告
泛型的语法
泛型的声明
interface 接口 <T> {}
class 类 <K,V> {}
// 说明:
// 1.其中,T,K,V 不代表值,而是表示类型,且只能是引用类型
// 2.任意字母都可以。常用 T 表示,是 Type 缩写
泛型的实例化
要在类名后面指定类型参数的值(类型)。如:
List <Object> strList = new ArrayList <String> ();
Iterator <Customer> iterator = customers.iterator();
// 说明:
//在给泛型指定具体类型后,可以传入该类型或者其子类类型
自定义泛型
自定义泛型类
class 类名 <T,R,...> { //...表示可以由多个泛型
成员;
}
注意细节
-
普通成员(属性,方法)可以使用泛型
-
使用泛型的数组,不能初始化
-
静态方法中不能使用类的泛型
-
泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定类型)
-
如果在创建对象时,没有指定类型,默认为 Object
自定义泛型接口
interface 接口名 <T,R,...>{
}
注意细节
-
接口中,静态成员也不能使用泛型(这个和泛型类的规定一样)
-
泛型接口的类型,在继承接口或者实现接口时确定
-
没有指定类型,默认为 Object
自定义泛型方法
修饰符 <T,R,...> 返回类型 方法名(参数列表){
}
注意细节
-
泛型方法,可以定义在普通类中,也可以定义在泛型类中
-
当泛型方法被调用时,类型会确定
-
public void eat(E e) {},修饰符后没有 <T,R,...> ,eat 方法不是泛型方法,而是使用了泛型
泛型的继承和通配符说明
-
泛型不具备继承性
-
<?> :支持任意泛型类型
-
<? extends A>:支持A类以及A类的子类,规定了泛型的上限
-
<? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限