Java学习笔记(一)
使用命令行工具
- 主要的工具
- javac 编译
- java 运行(控制台及图形界面程序)
- javaw 运行图形界面程序
- appletViewer 运行applet程序
- 另外常用的几个工具
- jar 打包工具
- javadoc 生成文档
- Javap 常看类信息及反汇编
使用package时的编译
文件及路径一致
程序中使用package语句
使用import语句
编译及运行
假设文件路径为:
则 javac -d classes src\edu\pku\tds\ui\*.java src\edu\pku\tds\util\*.java src\edu\pku\tds\*.java
java -cp classes edu.pku.tds.PackageTest
使用jar打包
- 编译 javac A.java
- 打包 jar cvfm A.jar A.man A.class
- c表示创建(create),v表示显示详情(verbose),f表示指定文件名,m表示清单文件
- 其中A.man是清单文件(manifest),内容如下(清单文件可以任意命名,常见的是用MANIFEST.MF):
- Manifest-Version: 1.0
- Class-Path: .
- Main-Class: A
- 运行 java -jar A.jar
Java数据类型
Java是一种强类型语言。每个变量都必须要有一种数据类型。Java一共有八种基本的数据类型。其中有4种整型,2中浮点型,一种用于表示Unicode编码的字符单元的字符类型char类型,和一种用于表示真值的boolean类型。(注意:String不是基本数据类型)
整型
整型用于表示没有小数部分的数值,它允许是负数。类型如下:
类型 | 字节大小 | 取值范围 |
---|---|---|
byte | 1字节 | -128 ~ 127 |
short | 2字节 | -32 768 ~ 32 767(3万左右) |
int | 4字节 | -2 147 483 648 ~ 2 147 483 647(20亿左右) |
long | 8字节 | -9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807 |
此外,可以用不同的前后缀来表示不同的进制:
- 0x 前缀表示十六进制数值
- 0 前缀表示八进制数值
- 0b 前缀表示二进制数值
在书写较大的数字时,可以用_来分割,例如 0b000_111_010_100 表示468,Java编译器回去除这些下划线。
注意:在c和c++中,int 和 long 常因处理器位数不同而占用不同的字节数。但是在Java中所有的数据类型所占的字节数量与平台无关。另外,Java中没有任何无符号类型(unsigned)。
浮点类型
浮点类型用于表示有小数部分的数值,类型如下:
类型 | 字节数 | 取值范围 |
---|---|---|
float | 4 | [-3.40282346638528860e+38 , -1.40129846432481707e-45] ∪ [1.40129846432481707e-45 ~ 3.40282346638528860e+38] |
double | 8 | [-1.79769313486231570e+308,-4.94065645841246544e-324] ∪ [4.94065645841246544e-324,1.79769313486231570e+308] |
后缀F表示float类型,后缀D表示double类型,当没有后缀时,默认为double类型。
注意:浮点数值不适用于禁止出现舍入误差的计算中。例如:System.out.println(2.0 - 1.1); 将打印出0.8999999999999999而不是0.9。主要原因是浮点数在系统中采用二进制表示,而在二进制中无法精确地表示 1/10,就像十进制中无法精确地表示 1/3 一样。
char类型
char类型用于表示单个字符。
Unicode编码单元可以表示为十六进制数值。范围为 \u0000 到 \uffff 。例如 \u03C0 表示圆周率 Π。
boolean类型
boolean类型有两个值:true false。整型和布尔类型之间不能相互转换。
注意:在c和c++中,数值和指针可以替代boolean值,0相当于false,非0值相当于true。但是在Java中不能这样。
数组
一维数组
声明及创建
1 | //一维数组的声明 |
数组的遍历
1 | //for循环 |
数组常用方法(Arrays类)
1 | import java.util.Arrays; |
多维数组
声明及创建
1 | //声明 |
遍历
1 | //普通for循环 |
若要快速打印二维数组的所有元素,也可以使用Arrays类中的deepToString方法(返回String类型元素):
1 | Arrays.deepToString(array); |
不规则数组
Java其实没有多维数组,只有一维数组,多维数组其实就是数组的数组。例如 int[][] array = new int[10][10]; 既不同于 C 中 int array[10][10]; 也不同于 int (*array)[6] = new int[10][10]; 这正是 Java 的优势所在。
基于这个优势,Java 可以很方便的对两行进行交换:
1 | int[] temp = array[i]; |
在构造数组时,也可以给数组每行不同的空间大小:
1 | int[][] array = new int[4][]; |
Java对象和类
类的五大成员:属性、方法、构造器、代码块、内部类
定义
完整的类定义
1 | 类声明 |
1 | [public][abstract | final] class className [extends superclassName] [implements InterfaceNameList]{ |
完整的Java源文件
源文件的名称必须与属性为 public 的类的类名完全相同
在一个.java 文件中,package 语句和 public 类最多只能有一个
1 | 指定文件中的类所在的包,0个或1个 |
1 | package packageName; |
常用访问修饰符
Java中有四种常用的访问修饰符
- default(默认,即什么也不写的时候就自动加上):在同一包内可见。可以使用的对象:类、接口、变量、方法。
- private:在同一类中可见。可以使用的对象:变量、方法、内部类。
- public:对所有类可见。可以使用的对象:类、接口、变量、方法。
- protected:对同一包内的类和所有子类可见。可以使用的对象:变量、方法、内部类。
访问权限如下表:
修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
protected | Y | Y | Y | Y/N | N |
public | Y | Y | Y | Y | Y |
对于 protected :
- 基类的 protected 是包内可见的,并且子类也是可见的
- 若子类和基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问 基类的 对象实例中的 受protected约束的方法
继承
this 和 super
this 关键字的作用:
- this 调用本类中的属性和方法
- this 调用本类中的其他构造器
1 | public class Father { |
super 关键字的作用:
- super 调用基类中的非私有属性和方法
- super 调用父类中的构造器,在构造函数中使用super时,super只能放在第一句
1 | class Son extends Father { |
注意:虽然 super 和 this 用法相似,但是他们并不是一个概念。this 实际上是对当前对象的引用,而 super 并不是对一个对象的引用,他只是一个指示编译器调用超类方法的特殊关键字。
1 | Father father = this; //right |
方法重写(override)
注意事项和使用细节:
方法重写也叫方法覆盖,需要满足下列条件
- 子类的方法的参数,方法名称,要和父类方法的参数,方法名称完全一样。
- 子类方法的返回类型和父类方法返回类型一样,或是父类返回类型的子类。比如父类方法返回类型是Object,子类方法返回类型是String。
- 子类方法不能缩小父类方法的访问权限。
多态
类的多态
- 一个对象的编译类型可以和运行类型不一致。
- 编译类型在创建时就确定了,不能改变。
- 运行类型可以变化。
- =左边是编译类型,=右边是运行类型。
可以用二元运算符 instanceof 判断运行类型。
动态绑定
Java 具有动态绑定机制
- 当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
- 当调用对象属性的时候,没有动态绑定机制,哪里声明就在哪里使用
即:方法有动态绑定机制,属性没有动态绑定机制
抽象类
抽象类使用 abstract 关键词修饰,它的基本功能依旧存在,成员变量、成员方法和构造方法的访问方式和普通类一样。抽象类不能实例化对象,必须被继承才能被使用。包含抽象方法的类就是抽象类。
书写格式:
1 | //抽象类格式 |
Java常用类
枚举类enumeration(enum)
枚举是一组常量的集合。也就是说枚举属于一种特殊的类,里面只包含一组有限的特定的对象。
自定义枚举类
实现方法:
- 将构造器私有化,防止直接new出来
- 去掉set方法,防止属性被修改
- 直接在内部创建固定的对象
- 对外暴露对象(通过为对象添加 public static 修饰符)
- 优化:加入一个final修饰符
1 | //Season.java |
使用enum关键字生成枚举类
实现方法:
- 使用关键字enum替代class
- public static final Season SPRING = new Season(“春天”, “温暖”); -> SPRING(“春天”, “温暖”); 即:常量名(实参列表);
- 如果有多个常量(对象),使用逗号间隔,最后一个用分号
- 定义的常量对象必须写在最前面
1 | //Season01.java |
enum常用方法
方法名 | 详细描述 |
---|---|
valueOf | 将字符串转化为枚举对象,要求字符串必须为已有常量名,否则报异常 |
toString | Enum类已经重写该方法了,返回的是当前对象名。子类可以重写该方法,用来返回对象的属性信息 |
equals | 类似于二元运算符 == |
getDeclaringClass | 得到枚举常量所属的枚举类型 |
name | 返回当前对象名(常量名),子类不能重写 |
ordinal | 返回当前对象的位置号(默认从0开始) |
compareTo | 比较两个枚举常量,比较的就是位置号 |
clone | 枚举类不能被clone。Enum类实现了一个仅抛出异常的不变clone(),防止子类实现克隆方法。 |
values | 返回当前枚举类中的所有常量 |
String类
说明:
- String 对象用于保存字符串,也就是一组字符序列
- 字符串的字符使用 Unicode 字符编码,一个字符(不区分字母还是汉字)占两个字节大小
- String 类有很多构造器,常用的有:
- String str = new String();
- String str = new String(String original);
- String str = new String(char[] a);
- String str = new String(char[] a, int startIndex, int count);
- String str = new String(byte[] b);
- String 类实现了 Serializable 接口(可以串行化:可以在网络上传输,可以保存到文件)
- String 类实现了 Comparable 接口(String 对象可以比较大小)
- String 是 final 类,不能被其他的类继承
- String 内部实现时:private final char value[]; 存储时本质就是不可变字符数组
声明和创建
1 | //声明 |
两种字符串创建方式的区别:
方式一:先从常量池查看是否有”Hello, world!”数据空间,如果有,则直接指向;如果没有,则重新创建然后指向。所以str最终指向的是常量池的空间地址。相当于值传递。
方式二:先在堆中创建空间,里面维护了value属性,指向常量池的”Hello, world!”数据空间。如果常量池没有”Hello, world!”,则重新创建。所以使用这种方法,实际上是创建了两个对象。相当于引用。
String类常用方法
1 | 字符串的比较: |
StringBuilder 和 StringBuffer 类
String 中的内容是不可变的,当需要对字符串中的内容做修改时,应使用StringBuilder和StringBuffer类。(上述方法提到的修改String内容的方法实际上是创建了新对象,使用StringBuilder和StringBuffer修改时不会产生新对象)
区别
区别:StringBuilder 类是在 Java5 之后才出现的。StringBuilder 的方法是线程不安全的(不能同步访问),但是它的效率更高。
常用方法
1 | StringBuilder类/StringBuffer类 |
String, StringBuilder, StringBuffer 对比
- String 不可变序列,效率低,但是复用率高,适合在不对字符串修改时使用
- StringBuffer 可变序列,效率较高,但是线程安全,多线程操作时是首选
- StringBuilder 可变序列,效率最高,但是线程不安全,只能在单线程程序中使用
日期和时间
Calendar 类说明:
- Calendar 是一个抽象类,并且构造器是 private
- 可以通过 getInstance() 来获取实例
- Calendar 提供了大量的方法和字段给程序员
- Calendar 没有提供对应的格式化的类,因此需要程序员自己组合来输出
- 如果要按照 24 小时进制来获取时间,Calendar.HOUR ==改成==> Calendar.HOUR_OF_DAY
1 | //获取时间 |
注意:Calendar 返回月份时,默认是从0开始,所以要加1
前两代日期类的不足的分析:
JDK1.0 中包含了一个 java.util.Date 类,但是它的大多数方法已经在 JDK1.1 引入 Calendar 类之后被弃用了。而 Calendar 也存在的问题是:
- 可变性:像日期和时间等这样的类应该是不可变的。
- 偏移性:Date 中的年份是从 1900 开始的,而月份都从 0 开始。
- 格式化:格式化只对 Date 有用,Calendar 则不行。
- 此外,他们也是线程不安全的;不能处理闰秒等(每隔2天,多出1秒)。
第三代日期常用方法(JDK8 加入)
- LocalDate (日期==年月日)
- LocalTime (时间==时分秒)
- LocalDateTime (日期时间==年月日时分秒)
1 | LocalDateTime ldt = LocalDateTime.now(); |
可以使用 DateTimeFormatter 类对日期进行格式化,代码如下:
1 | LocalDateTime ldt = LocalDateTime.now(); |
其他
Instant 时间戳:
1 | //1. 通过静态方法 now() 获取表示当前时间戳的对象 |
还有一系列的 plus 和 minus 方法可以对日期进行加减操作
1 | //看看 890 天后是什么时候 |
常用包装类
Java 为每一个内置数据类型提供了对应的包装类。所有的包装类(Integer, Long, Byte, Double, Float, Short)都是抽象类 Number 的子类。
对应关系如下:
包装类 | 基本数据类型 |
---|---|
Boolean | boolean |
Byte | byte |
Short | short |
Integer | int |
Long | long |
Character | char |
Float | float |
Double | double |
继承关系如下:
从内置类型到包装类叫做装箱,从包装类对象到内置类型叫做拆箱。
只有变成了包装类才能使用类中的方法,而且一般编译器会自动进行装箱、拆箱的过程。
除 Character 外,字符串都可以通过 parseXXX(String str) 的方法转化为基本数据类型
Math数学类
Math 类常用方法(Math 类的方法都为 static 形式,可以直接使用):
1 | 1. 三角函数方法 |
System 类
常用方法:
- exit 退出当前程序
- arrayCopy 复制数组元素,比较适合底层调用。一般使用 Arrays.copyOf 完成复制数组。
1 | int[] source = {1, 2, 3}; |
- currentTimeMillens 返回当前时间距离 1970-1-1 的毫秒数
- gc 运行垃圾回收机制 System.gc();
Java接口
接口实际上是一种规范。实现接口,是对 Java 单继承的一种补充。接口也具有多态性。
注意事项:
- 接口不能被实例化
- 接口中的所有方法都是 public 类型,接口中的抽象方法可以不用 abstract 修饰
- 一个普通类实现接口就必须将接口中的所有方法实现
- 抽象类去实现接口时,可以不实现接口的抽象方法
- 一个类可以实现多个接口
- 接口中的属性,只能是 final,而且是 public static final 修饰符
- 接口中属性的访问形式:接口名.属性名
- 接口不能继承其他的类,但是可以继承多个别的接口
- 接口的修饰符只能是 public 和默认,和类的修饰符一样
例子如下:
1 | //camera.java |
1 | //computer.java |
1 | //machine.java |
1 | //interface.java |
Java动态代理
动态代理有两种实现方式:
- JDK 动态代理(重要)
- CGLIB 动态代理
CGLIB(了解)
使用继承机制,通过在子类中重写同名方法完成对父类的代理
其要求被代理的类不能为 final,方法也不能为 final
要求较宽松,只要类能被继承就能使用此方法,效率相对较高
JDK 动态代理
重要的类和方法
Proxy 类:用于创建代理对象的类,其中有很多静态方法
静态方法:newProxyInstance
1 | public static Object newProxyInstance(ClassLoader loader, |
参数:
- ClassLoader loader 要代理的目标对象的类加载器,负责向内存中加载对象,可以通过反射获取
- Class<?>[] interfaces 目标对象实现的接口
- InvocationHandler h 我们自己写的,代理类要完成的功能
返回值:
- 代理对象
实现动态代理的步骤
- 创建接口,定义目标类要完成的功能
- 创建目标类实现接口
- 创建 InvocationHandler 接口的实现类,在 invoke 方法中完成代理类的功能
- 调用目标方法
- 增强功能
- 使用 Proxy 类的静态方法,创建代理对象,并返回
1 | //可以用这个类,自动生成代理类 |
反射
- 反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息(比如成员变量,构造器,成员方法等等),并能操作对象的属性以及方法。反射在设计模式和框架底层都会用到。
- 加载完类之后,在堆中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象包含了类的完整结构信息。可以通过这个对象得到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射。
在反射中,可以把方法视为对象
Java 程序在计算机执行有三个阶段
反射相关的主要类
- java.lang.Class 代表一个类,Class 对象表示某个类加载后在堆中的对象
- java.lang.reflect.Method 代表类的方法,Method 对象表示某个类的方法
- java.lang.reflect.Field 代表类的成员变量,Field 对象表示每个类的成员变量
- java.lang.reflect.Constructor 代表类的构造方法,Constructor 对象表示构造器
这些类在 java.lang.reflection
Class 类
基本介绍
- Class 也是类,因此也继承 Object 类
- Class 类对象不是 new 出来的,而是系统创建的
- 对于某个类的 Class 类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记得自己是由哪个 Class 实例所生成
- 通过 Class 可以完整地得到一个类的完整结构,通过一系列API
- Class 对象是存放在堆中的
- 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等)
Class 类的常用方法
如下表:
方法名 | 功能说明 |
---|---|
static Class forName(String name) | 返回指定类名 name 的 Class 对象 |
Object newInstance() | 调用缺省构造函数,返回该 Class 对象的一个实例 |
getName() | 返回此 Class 对象所表示的实体(类、接口、数组类、基本类型等)名称 |
Class getSuperClass() | 返回当前 Class 对象的父类的 Class 对象 |
Class[] getInterfaces() | 返回当前 Class 对象的接口 |
ClassLoader getClassLoader() | 返回该类的类加载器 |
Class getSuperclass() | 返回表示 此 Class 所表示的实体 的超类的 Class |
Constructor[] getConstructors() | 返回一个包含某些 Constructor 对象的数组 |
Field[] getDeclaredFields() | 返回 Field 对象的一个数组 |
Method getMethod(String name, Class … paramTypes) | 返回一个 Method 对象,此对象的形参类型为 paramType |
实例:
1 | public static void main(String[] args) throws Exception { |
获取 Class 类对象的常用方法
- 前提:已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取,可能抛出 ClassNotFoundException,示例:Class cls1 = Class.forName(“com.pushihao.Cat”);
应用场景:多用于配置文件,读取类全路径,加载类
- 前提:若已知具体的类,通过类的 class 获取,该方式最为安全可靠,程序性能最高,示例:Class cls2 = Cat.class;
应用场景:多用于参数传递,比如通过反射得到对应构造器对象
- 前提:若已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象,实例:Class cls3 = 对象.getClass();
应用场景:通过创建好的对象,获取 Class 对象
- 其他方式,示例:
1 | ClassLoader cl = 对象.getClass().getClassLoader(); |
- 基本数据(int, char, boolean, float, double, byte, long, short)可通过.class得到 Class 类对象:Class cls5 = 基本数据类型.class;
- 基本数据类型对应的包装类,可以通过.TYPE得到 Class 类对象:Class cls6 = 包装类.TYPE;
类加载
基本说明:
反射机制是 java 实现动态语言的关键,也就是通过反射实现类动态加载。
- 静态加载:编译时就加载相关的类,如果没有此类就报错(不管用到没用到),依赖性太强
- 动态加载:运行时才加载需要的类,如果运行时没有用到该类,即使不存在也不报错,降低了依赖性
类加载时机:
前三个都是静态加载,只有4是动态加载
- 当创建对象时(new)
- 当子类被加载时
- 调用类中的静态成员时
- 通过反射
1 | //假设没有Dog类,也没有Person类 |
类加载阶段:
加载 -> 准备(验证 -> 准备 -> 解析)-> 初始化
通过反射获取类的结构信息
java.lang.Class 类
- getName 获取全类名
- getSimpleName 获取简单类名
- getFields 获取所有public修饰的属性,包含本类以及父类的
- getDeclaredFields 获取本类中所有属性
- getMethods 获取所有public修饰的方法,包含本类以及父类的
- getDeclaredMethods 获取本类中所有方法
- getConstructors 获取所有public修饰的构造器,只包含本类
- getDeclaredConstructors 获取本类中所有构造器
- getPackage 以Package形式放回包信息
- getSuperClass 以Class形式返回父类信息
- getInterfaces 以Class[]形式返回接口信息
- getAnnotations 以Annotation[]形式返回所有注解信息
java.lang.reflect.Field 类
getModifiers 以int形式返回修饰符
说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16。多个修饰符时就是相加的结果
getType 以Class形式返回类型
getName 返回属性名
java.lang.reflect.Method 类
getModifiers 以int形式返回修饰符
说明:默认修饰符是0,public是1,private是2,protected是4,static是8,final是16。多个修饰符时就是相加的结果
getReturnType 以Class形式获取返回类型
getName 返回方法名
getParameterTypes 以Class[]形式返回参数类型的数组
java.lang.reflect.Constructor 类
- getModifiers 以int形式返回修饰符
- getName 返回构造器名(全类名)
- getParameterTypes 以Class[]形式返回参数类型数组
反射暴破
假设有这么一个类:
1 | package com.pushihao.Reflection_.Explode; |
通过反射创建对象
- 方式一:调用类中的 public 修饰的无参构造器 class 对象
- 方式二:调用类中的指定构造器
- Class 类相关方法
- newInstance:调用类中的无参构造器,获取对应类的对象
- getConstructor:根据参数列表,获取对应的构造器对象
- getDecalaredConstructor:根据参数列表,获取对应的构造器对象
- Constructor 类相关方法
- setAccessible:爆破(暴力破解:反射可以使用类中的私有构造器/方法)
- newInstance:调用构造器
1 | public static void main(String[] args) throws Exception { |
通过反射访问类中的属性
- 根据属性名获取 Field 对象
1 | Field f = class对象.getDeclaredField(属性名); |
- 暴破
1 | f.setAccessible(true); |
- 访问
1 | f.set(对象实例, 新值); |
如果是静态属性,则 set 和 get 中的参数可以写成 null
1 | public static void main(String[] args) throws Exception { |
通过反射访问类中的方法
- 根据方法名和参数列表获取 Method 方法对象
1 | Method method = class对象.getMethod(方法名, class对象); |
- 获取对象实例
1 | Object o = class对象.getConstructor().newInstance(); |
- 暴破
1 | method.setAccessible(true); |
- 访问
1 | Object returnValue = method.invoke(o, 实参列表); |
如果是静态方法,则 invoke 的参数 o 可以写成 null
1 | public static void main(String[] args) throws Exception { |
Java集合
集合框架图如下:
大致可以看出,Java 集合框架主要包含两种容器。一种是存储元素集合的 Collection,另一种是存储键值对的 Map。
总结:
- 集合主要分为两组(单列集合,双列集合)
- Collection 接口有两个重要的子接口 List 和 Set,他们实现的子类都是单列集合
- Map 接口实现的子类则是双列集合,存放K-V键值对
Collection 接口
Collection接口常用方法:
1 | add |
使用迭代器遍历 Collection
说明:
- Iterator 对象称为迭代器,主要用于遍历 Collection 集合中的元素
- 所有实现了 Collection 接口的集合类都有一个 iterator() 方法,用以返回一个实现了 iterator 接口的对象,即可以返回一个迭代器
- Iterator 仅用于集合的遍历,Iterator 本身并不存放对象
1 | Iterator iterator = array.iterator(); |
List 接口
List 接口基本介绍
- List 集合类中的元素有序,并且可以重复
- List 集合中的每个元素都有其相应的顺序索引,即支持索引
- List 容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取相应元素
List 接口常用方法
1 | void add(int index, Object o); |
Vector、ArrayList 与 LinkedList 比较
- Vector 是线程安全的,但效率相对较低,所以在多线程时应使用 Vector
- ArrayList 和 Vector 都是以 Object 数组来存储的。在存储时如果数据个数超出了内部数组当前的长度,Vector 会自动增长为原数组大小的一倍,而 ArrayList 是50%,所以在存放大量数据时应使用 Vector 更有优势
- ArrayList 是基于动态数组来实现,而 LinkedList 是基于链表来实现。所以当需要大量随机访问元素时,ArrayList 是首选,而当需要大量的插入或删除元素时,LinkedList 是首选。(类比数组与链表)
Set接口
Set 接口实现了 Collection 接口,所以 Collection 的方法 Set 接口的实现类也能用
List 接口与 Set 接口对比
- Set 接口存储的数据是无序的,并且不能重复。而 List 接口实例存储的是有序的,可以重复的元素
- Set 检索效率较低,但是插入和删除效率高(因为插入和删除不会引起其他元素的位置的改变(内部实现其实还是哈希表与红黑树 HashSet、TreeSet))
- List 查找效率高,但是插入和删除效率较低(因为会引起其他元素位置的改变)
Map 接口
说明
Map 接口的常用实现类:HashMap、HashTable 和 Properties
HashMap 说明
- HashMap 是 Map 接口使用频率最高的实现类
- HashMap 是以 Key-Value 键值对的方式来存放数据
- Key 不能重复,但是 Value 可以重复
- 允许使用 null 键和 null 值
- 如果添加相同的 Key,则会覆盖原来的 Key-Value,相当于是修改(Key 不会替换,Value 会替换)
- 元素是无序的,底层是以哈希表的方式来存储的
- HashMap 没有实现同步,因此是线程不安全的
HashTable 说明
- 存放的元素是键值对
- HashTable 的键和值都不能为 null,否则抛出 NullPointerException
- HashTable 是线程安全的,而 HashMap 是线程不安全的
Map 常用操作
增删改查等操作
1 | Map map = new HashMap(); |
遍历
1 | //注:x.1 都是使用for-each,x.2 都是使用迭代器 |
开发中如何选择集合实现类
分析如下:
- 先判断存储的类型(一组对象或一组键值对)
- 一组对象:Collection 接口
- 允许重复:List
- 增删多:LinkedList(底层维护了一个双向链表)
- 改查多:ArrayList(底层维护了一个 Object 类型的可变数组)
- 不允许重复:Set
- 无序:HashSet(底层是 HashMap,维护了一个哈希表)
- 排序:TreeSet
- 插入和取出顺序一致:LinkedHashSet(底层维护了数组+双向链表)
- 允许重复:List
- 一组键值对:Map 接口
- 键无序:HashMap(底层是哈希表 jdk7:数组+链表 jdk8:数组+链表+红黑树)
- 键排序:TreeMap
- 键插入和取出顺序一致:LinkedHashMap
- 读取文件:Properties
Collections 工具类
工具类介绍
- Collections 是一个用来操作 Set、List 和 Map 等集合的工具类
- Collections 中提供了一系列静态的方法对集合元素进行排序、查询和修改等操作
排序操作(均为static方法)
- reverse(List); 将List元素反转
- shuffle(List); 对List集合元素进行随机排序
- sort(List); 根据元素的自然顺序对指定List集合元素按升序排序
- sort(List, Comparator); 根据指定的排序方式对List排序
- swap(List, int i, int j); 将List集合中下标为 i 的元素与下标为 j 的元素进行交换
查找、替换操作
- Object max(Collection); 根据元素的自然顺序(字典序),返回最大元素
- Object max(Collection, Comparator); 根据指定顺序(如长度最大等)返回最大元素
- 如:返回长度最大
- Object maxObject = Collections.max(list, new Comparator() {
- @Override
- public int compare(Object o1, Object o2) {
- return ((String) o1).length() - ((String) o2).length();
- }
- })
- 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 synchronizedList(List list);
1 | private static List<Connection> sqlList = Collections.synchronizedList(new ArrayList<>()); |
Map接口
Map 接口实现类的特点(JDK8):
- Map 与 Collection 并列存在,用于保存具有映射关系的数据:Key-Value
- Map 中的 Key 和 Value 可以是任何引用类型的数据,会封装到 HashMap$Node 对象中,常用 String 类作为Key
- Map 中的 Key 不允许重复,原因和 HashSet 一样
- Map 中的 Value 可以重复
- Map 的 Key 可以为 null,value 也可以为 null
- Key 和 Value 之间存在单向一一对应关系,即通过 Key 总能找到对应的 value
- Map 存放数据的 key-value 示意图,一对 k-v 是放在一个 Node 中的,又因为 Node 实现了 Entry 接口,有些书也说一对 k-v 就是一个 Entry
Map 接口常用方法
- put
- remove
- get
- size
- isEmpty
- clear
- containsKey