数组
新建
int[] a = new int[n];
长度不可改变
int[] a ={};
Array
// 一维输出为字符串,中括号包围,逗号分隔。
static String toString(xx[] a);
// 二维输出为字符串,中括号包围,逗号分隔。
static String deepToString(xx[] a);
// 返回与a类型相同的数组,长度为length或者end-start,如果end大于length,结果会填充0或false
static xxx[] copyOf(xxx[] a, int end);
static xxx[] copyOf(xxx[] a,int start ,int end);
// 快速排序
static void sort(xxx[] a);
// 使用二分查找在有序数组中查找值v,返回相应下标。否则放回一个负数。-r-1是v插入的位置(保持a有序)
static int binarySearch(xxx[] a, xxx v);
static int binarySearch(xxx[] a,int start ,int end, xxx v);
// 将数组的所有值设置为v
static void fill(xxx[] a, xxx v);
// 如果两个数组带下相同,并且下标相同的元素都对应相等,则返回true
static boolean equals(xxx[] a,xxx[] b)
泛型数组列表
数组列表管理着一个内部的对象引用数组。最终这个数组空间用尽时,数组列表就会创建一个更大的数组,并将所有的对象从较小的数组拷贝到较大的数组。
而使用ensureCapacity()
方法可以预先分配大小,减小重新分配空间的内存消耗
在确定大小后可以使用trimToSize()
方法将存储块大小调整为当前大小。
排序和比较
必须保证正反比较互为相反数
public static void main(String[] args){
Persont[] p = {new Persont("34431"),new Persont("1234343"),new Persont("12")};
Persont[] p2 = {new Persont("34"),new Persont("1234343"),new Persont("12")};
PersontComparator persontComparator = new PersontComparator();
// 数比较
System.out.println(p[0].compareTo(p[1])+"转换"+p[1].compareTo(p[0]));
System.out.println(persontComparator.compare(p2[0],p2[1])+"转换"+persontComparator.compare(p2[1],p2[0]));
//数组排序
Arrays.sort(p);
Arrays.sort(p2,persontComparator);
System.out.println(Arrays.deepToString(p));
System.out.println(Arrays.toString(p2));
}
=================================================
class Persont implements Comparable<Persont> {
private String name;
@Override
public int compareTo(Persont o) {
if (this.name.length() > o.name.length()){
return 1;
}else if (this.name.length() == o.name.length()){
return 0;
}
return -1;
}
}
class PersontComparator implements Comparator<Persont>{
@Override
public int compare(Persont o1, Persont o2) {
if (o1.getName().length() < o2.getName().length()){
return 1;
}else if (o1.getName().length() == o2.getName().length()){
return 0;
}
return -1;
}
}
对象
在构造对象是,可以使用Objects.requireNonNull(a,"不能为空");
,构造时为空直接抛出异常
final作用于类对象时,只是保证引用不能改变,内部可变。
static定义的字段和方法属于类,而不属于任何单个对象。类字段/方法
java方法参数只有按值调用,并不是按引用调用。调用方法时,都会创建一个副本来存放调用
- 方法不能修改基本数据类型的参数
- 方法可以改变对象参数的状态
- 方法不能让一个对象参数引用一个新的对象
时间对象
LoaclDate类
不要使用构造器来构造该类对象。应当使用静态工厂方法
// 获取当前时间
public static LocalDate now();
// 获取指定时间
public static LocalDate of(int year, int month, int dayOfMonth) ;
// 解析文本,转换为对象。也可指定格式
public static LocalDate parse(CharSequence text)
// 返回一个新的对象,在指定类型上加上指定数
public LocalDate plus(long amountToAdd, TemporalUnit unit)
public LocalDate plusYears(long yearsToAdd)
public LocalDate plusMonths(long monthsToAdd)
public LocalDate plusWeeks(long weeksToAdd)
public LocalDate plusDays(long daysToAdd)
// 返回一个新的对象,在指定类型减指定数
public LocalDate minus(long amountToSubtract, TemporalUnit unit)
。。。。
// 获取年月周日
int year = localDate.getYear();
int month = localDate.getMonthValue();
int dayOfMonth = localDate.getDayOfMonth();
DayOfWeek dayOfWeek = localDate.getDayOfWeek();
int value = dayOfWeek.getValue();
String displayName = dayOfWeek.getDisplayName(TextStyle.FULL_STANDALONE, Locale.CHINESE);
System.out.println(year+"年"+month+"月"+dayOfMonth+"日,星期"+value+"代表"+displayName);
包装类
基本特性
-
自动包装:list.add(3)--->list.add(Integer.valueOf(3))
-
自动拆箱:int n = list.gei(i) ---> int n = list.get(i).intValue();
-
条件表达式中使用包装类会自动执行拆箱包装查找
-
装箱和包装是编译器的工作,而不是虚拟机。编译器在生成类的字节码时会插入必要的方法调用。虚拟机只是执行这些字节码
-
包装类对象是不可变的:包含在包装类中的内容不会改变
- 如果想编写一样高修改数字参数值的方法,可以使用org.omg.CORBA包中定义的持有者方法(holder)类型。每个持有者类型都包含一个公共(!)字段value,通过它可以访问存储在其中的值。
枚举
枚举1的构造器总是私有的。
- toString方法:放回枚举常量名。
- static valueOf(Class enumClass,String name)方法:放回指定类中有指定名字的枚举常量
- ordinam方法:放回枚举常量在枚举中声明的位置。从0开始计数
- compareTo(E order)方法:如果该枚举常量出行在order之前,则返回负整数;如果相等返回0;否则返回正整数
反射
反射的作用
- 在运行时分析类的能力
- 在运行时检查对象,例如编写一个适用所有类的toString方法
- 实现泛型数组操作代码
- 利用Method对象,和C++函数指针相似
方法:
ArraysTest arraysTest = new ArraysTest();
Class<? extends ArraysTest> aClass = arraysTest.getClass();
// 获取类的构造函数,通过构造函数new一个新对象
ArraysTest arraysTest1 = aClass.getConstructor().newInstance();
// 找到与类位于同一位置的资源,返回一个可以用来加载资源的URL或者输入流。如果没有找到,返回空,不会报异常或者IO错误
URL resource = aClass.getResource("123.txt");
InputStream resourceAsStream = aClass.getResourceAsStream("123.txt");
System.out.println(aClass);
利用反射分析类的能力
测试demo
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("---------------类对象信息-----------------");
// 实体类的class对象
Class<ReflectionTest> testClass = ReflectionTest.class;
// 通过类名获取class对象
Class<?> cl = Class.forName(testClass.getName());
// 获取其父类class对象
Class<?> superclass = cl.getSuperclass();
// 类的修饰符
String clm = Modifier.toString(cl.getModifiers());
if ("".equals(clm)) {
System.out.print("默认,无修饰符 ");
} else {
System.out.println(clm);
}
System.out.println(cl + " 继承于 " + superclass);
System.out.println("---------------类对象所有字段信息-----------------");
Field[] cfAll = cl.getDeclaredFields();
for (Field cf: cfAll) {
String cfName = cf.getName();
Class<?> cfType = cf.getType();
String cfM = Modifier.toString(cf.getModifiers());
if ("".equals(cfM)) {
System.out.print("默认,无修饰符");
} else {
System.out.print(cfM);
}
System.out.println(" "+cfType+ " " + cfName);
}
System.out.println("---------------类对象所有构造函数信息-----------------");
// 获取类的构造函数(不包括继承成员 cl.getConstructors()方法包括继承成员)
Constructor<?>[] cAll = cl.getDeclaredConstructors();
for (Constructor<?> c : cAll) {
// 构造函数名
String name = c.getName();
// 构造函数的修饰符
String cm = Modifier.toString(c.getModifiers());
// 构造函数的全部参数类型
Class<?>[] cpt = c.getParameterTypes();
// 构造函数全部参数
Parameter[] cp = c.getParameters();
if ("".equals(cm)) {
System.out.print("默认,无修饰符");
} else {
System.out.print(cm);
}
System.out.print(" 构造函数:" + name + "(");
for (int i = 0; i < cpt.length; i++) {
// 构造函数的参数名
System.out.print(cpt[i].getName() + " " + cp[i].getName());
if (i < cpt.length - 1) {
System.out.print(",");
}
}
}
System.out.println(")");
System.out.println("---------------类对象所有方法信息-----------------");
// 获取类的方法(不包括继承成员 cl.getMethods()方法包括继承成员)
Method[] cmAll = cl.getDeclaredMethods();
for (Method cM : cmAll) {
// 方法返回类型
Class<?> returnType = cM.getReturnType();
// 方法名
String cmName = cM.getName();
// 方法修饰符
String cmM = Modifier.toString(cM.getModifiers());
// 方法的全部参数类型
Class<?>[] cmt = cM.getParameterTypes();
// 方法全部参数
Parameter[] cmp = cM.getParameters();
if ("".equals(cmM)) {
System.out.print("默认,无修饰符");
} else {
System.out.print(cmM);
}
System.out.print(" "+returnType.getName()+" " + cmName + "(");
for (int i = 0; i < cmt.length; i++) {
// 方法的参数名
System.out.print(cmt[i].getName() + " " + cmp[i].getName());
if (i < cmt.length - 1) {
System.out.print(",");
}
}
System.out.println(") ");
}
}
使用反射在运行时分析对象
测试demo
public void 使用反射在运行时分析对象(Object instance) throws IllegalAccessException {
// 一个实例对象
// ReflectionTest reflectionTest = new ReflectionTest("a",1,"c",2,"e",3);
// class对象
Class<?> aClass = instance.getClass();
// 所有字段
Field[] declaredFields = aClass.getDeclaredFields();
for (Field f: declaredFields) {
System.out.print(f.getType()+" "+f.getName());
try {
// 获取实例对象的字段属性,当字段为不可访问是会抛出异常IllegalAccessException
// 可通过Filed,Method,Constructor,Executable的公共父类AccessibleObject
// setAccessible设置为可访问
System.out.println("--->"+f.get(instance));
} catch (IllegalAccessException e) {
f.setAccessible(true);
System.out.println(":"+f.get(instance));
}
}
}
==java.lang.reflect.AccessibleObject==
- void setAccessible(boolean flag) : 设置或取消这个可访问对象的可访问标志,如果拒绝抛出IllegalAccessException异常
- boolean setAccessible(boolean flag) : 如果拒绝返回false
==java.lang.reflect.Filed==
- Object get(Object obj) : 返回指定实例对象的该Filed对象的值
- void set(Object obj,Object newValue) : 设置指定实例对象的该Filed对象的值
可了解一下ObjectAnalyzer对象。跟踪已访问过的对象。主要应用在公共toString方法中
使用反射编写泛型数组
==对象数组(Object[])不能强转为具体类型的数组,对象(Object)可以强制为具体类型的数组==
反例
public Object badCopyOf(Object[] arr,int newLength){
Object[] newArr = new Object[newLength];
System.arraycopy(arr,0,newArr,0,Math.min(arr.length,newLength));
return newArr;
}
正例
public Object 使用反射编写泛型数组(Object arr,int newLength){
Class<?> cl = arr.getClass();
if (!cl.isArray()){
return null;
}
// 获取数组内容的类型
Class<?> componentType = cl.getComponentType();
int length = Array.getLength(arr);
Object newArr = Array.newInstance(componentType, newLength);
System.arraycopy(arr,0,newArr,0,Math.min(length,newLength));
return newArr;
}
==java.lang.reflect.Array==
- static Object get(Object array, int index)
- static Object getXXX(Object array, int index) : xxx是基本类型。返回指定数组指定下标的值
- static Object set(Object array, int index)
- static Object setXXX(Object array, int index) : xxx是基本类型。设置指定数组指定下标的值
- static int getLength(Object array):返回指定数组的长度
- static Object newInstance(Class componentType, int length)
- static Object newInstance(Class componentType, int[] length) : 返回指定类型,指定长度的新数组
调用任意方法和构造器
在c和c++中,可以通过一个函数指针执行任意函数,也就是说将一个方法的存储地址传给另外一个方法。
java认为接口和lambda是更好的解决方案。但是反射也允许调用任意的方法。
public void 调用任意方法和构造器() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 调用static方法
Method sqrt = Math.class.getMethod("sqrt", double.class);
double invoke = (Double) sqrt.invoke(null, 4);
System.out.println(invoke);
// 调用不可访问的普通方法
ReflectionTest rt = new ReflectionTest("a",1,"c",2,"e",3);
Class<ReflectionTest> cl = ReflectionTest.class;
Method getA = cl.getMethod("getA");
String a = (String) getA.invoke(rt);
System.out.println("反射修改前的a:"+a);
Method[] declaredMethods = cl.getDeclaredMethods();
for (Method m: declaredMethods) {
if ("setA".equals(m.getName())){
System.out.println(m);
// 设置为可访问
m.setAccessible(true);
// 方法对象调用指定的实例对象
m.invoke(rt,"反射修改");
}
}
a = (String) getA.invoke(rt);
System.out.println("反射修改后的a:"+a);
}
对象继承的设计技巧
- 将公共操作和字段放在超类中
- 不要使用受保护的字段:所有子类以及同包都可以访问,破坏封装性
- 使用继承实现
is-a
关系 - 除非所有继承的方法都有意义,否则不要使用继承
- 在覆盖方法时,不要改变预期的行为
- 使用多态,而不要使用类型信息:例如通过判断对象引用类型后,调用方法。如果是相同的概念。应该将方法放在超类或者接口中
- 不要滥用反射
接口
接口和抽象类都表示通用属性。但是抽象类在java只能单继承,而接口可以多实现。
-
接口
不能实例化
-
接口可以
继承
接口 -
接口
方法默认
自带pulic -
接口
字段默认
自带pulic static final -
接口可以添加静态方法。在定义接口时就实现其内容
-
接口可以提供默认方法。关键字
default
。接口中实现默认内容,在实现类中也覆盖。重要用法在于接口演化
。 -
默认方法冲突:父类冲突和接口方法冲突
- 当子类(实现类)自己重写了方法,其他一律不管,自己想调用谁或者覆盖都可以
- 如果接口和父类都默认实现了一个同名同签名的方法。==父类优先,也就是类优先原则,忽略所有接口中同名同签名的方法==
- 接口冲突则会报错,需要解决
二义性问题
.也就是自身需要实现同名同签名的方法
- 如果实现的两个接口其中一个没有默认实现,自身就必须实现该方法
Lambda表达式
核心:将一个代码块传递到某个对象,本质就是一个代码块。形式为 (参数)->{表达式}
- 如果参数类型可以推导出,参数名可以不需要带参数类型
- 如果只有一个参数,并且参数类型可以推导出,就不许带小括号和参数类型
- 无需指定返回类型,总是由上下文推导得出。
函数式接口
对于只有一个抽象方法的接口,需要这个接口的对象时,就可以提供一个lambda表达式。这种接口就称为函数式接口
==java.util.function中的Predicate以及Supplier接口尤其有用==
方法引用
lambda表达式涉及一个方法
例如a-> System.out.println(a)
可以直接将println方法传递给某个构造器》=》System.out::println
表达式就代表一个方法引用。它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法
::
运算符分隔方法名与对象或者类名。主要有三种情况
- Object::instanceMethod
- Class:instanceMethod
- Class:staticMethod
方法引用示例
方法引用 | 等价的lambda表达式 | 说明 |
---|---|---|
separator::equals | x->separator.equals(x) | 对象和实例方法,lambda方法作为方法的显示的参数 |
String::trim | x->x.trim() | 类和实例方法,隐式参数 |
String::concat | (x,y)->x.concat(y) | 类和实例方法。第一个参数为隐式参数,其余参数会传递到方法 |
Intger::valueOf | x->Intger.valueOf(x) | 静态方法的方法表达式。一个参数 |
Integer::sum | (x,y)->Integer.sum(x,y) | 静态方法的方法表达式。两个个参数 |
Integer::new | x->new Integer(x) | 构造器引用 |
Integer[]::new | n->new Integer[n] | 数组构造器引用 |
只有当lambda表达式的体只调用一个方法而不做其他操作是,才能把lambd表达式重写为方法引用。
变量作用域
lambda表达式有三个部分
- 一个代码块
- 参数
- 自由变量的值,值非参数而且不在代码块中定义的量
lambd表达式可以捕获外围作用域中变量的值。要求
- 要确保捕获的值是明确定义的。
- 引用值不会改变,也就是在代码块中改变值。
- 引用值在外部也不能改变,例如引用for中的
i
。 - 捕获的变量必须实际上是
事实最终变量
- lambda表达式的体育嵌套块有相同的作用域。也就是不能在lambda表达式中声明与局部变量同名的参数或局部变量
- lambda中的this和super代表
创建这个lambda表达式的方法的this参数
处理lambda表达式
常用函数式接口
函数式接口 | 参数类型 | 返回类型 | 抽象方法名 | 描述 | 其他方法 |
---|---|---|---|---|---|
Runnable | 无 | void | run | 作为无参数或返回值的动作运行 | |
Supplier | 无 | T | get | 提供一个T类型的值 | |
Consumer | T | void | accept | 处理一个T类型的值 | andThen |
BiConsumer | T,U | void | accept | 处理T和U类型的值 | andThen |
Function | T | R | apply | 有一个T类型参数的函数 | compose、andThen、identity |
BiFunction | T,U | R | apply | 有T和U类型参数的函数 | andThen |
UnaryOperator | T | T | apply | 类型T上的一元操作符 | compose、andThen、identity |
BinaryOperator | T,T | T | apply | 类型T上的二元操作符 | andThen、maxBy、minBy |
Predicate | T | boolean | test | 布尔值函数 | and、or、negate、isEqual |
BiPredicate | T,U | boolean | test | 有两个参数的布尔值函数 | and、or、negate |
基本类型的函数式接口
函数式接口 | 参数类型 | 返回类型 | 抽象方法名 |
---|---|---|---|
BooleanSupplier | 无 | boolean | getAsBoolean |
PSupplier | 无 | p | getAsP |
PConsumer | p | void | accept |
ObjPConsumer | T,p | void | accept |
PFunction | p | T | apply |
PToQFunction | p | q | applyAsQ |
ToPFunction | T | p | applyAsP |
ToPBiFunction<T,U> | T,U | p | applyAsP |
PUnaryOperator | p | p | applyAsP |
PBinaryOperator | p,p | p | applyAsP |
PPredicate | p | boolean | test |
注:p、q是int、long、double;P、Q是Int、Long、Double
异常
异常对象都派生于java.lang.Throwable
类的一个类实例。其分为error:
==描述java运行时系统的内部错误和资源消耗错误,不应该抛出这种类型的对象==。Exception:
有分为两个分支,RuntimeException:
==由编程错误导致的异常==,IOException:
==程序本身没有问题,但由于I/O错误导致==
运行时异常包括以下情况:(一定是程序本地的问题)
- 错误的强制类型转换
- 数组访问越界
- 访问null指针
I/O异常包括以下情况:
- 试图超越文件结尾继续读取数据
- 试图打开一个不存在的文件
- 试图根据给定的字符串查找class对象,而对这个字符串表示的类并不存在
java将error
和RuntimeException
称为非检查型,而IOException
称为检查型异常
捕获异常
try-with-Resources
当资源属于一个实现了AutoCloseable
接口的类,就可以使用这种语句关闭资源
try (var in new Scanner(new FileInputStream("/d/,,"),StandardCharsets.UTF_8))
{
while(in.hasNext())
sout(in.next());
}
这个代码块正常退出,或者异常是都会调用in.close()方法。还可以指定多个资源。
如果close异常异常,则该异常会被“抑制”,并由addSuppressed()
方法添加到原来的异常。可以通过getSuppressed()
查看
分析堆栈轨迹
一种方法是调用Throwable
类的printStackTrace方法访问堆栈轨迹的文本描述信息
另外一种更加灵活的方式是使用StackWalker
类,他会生成一个StackWalker.StackFrame实例流,其中每个实例分别描述一个栈帧。可以使用以下调用迭代处理这些栈帧。
StackWalker walker = StackWalker.getInstance();
walker.forEach(frame -> analyze frame);
// 懒方法
walker.walk(stream -> process stream)
类名 | 方法 | 描述 |
---|---|---|
java.lang.Throwable | Throwable(Throwable cause) | |
异常的超类 | Throwable(String message,Throwable cause) | 用给定的cause构造一个Throwable对象 |
Throwable initCause(Throwable cause) | 为这个方法设置原因,如果这个对象已经有原因,则抛出异常、返回this | |
Throwable getCause() | 获取设置为这个对象的原因的异常对象。如果没有设置原因则为null | |
StackTraceElement[] getStackTrace() | 获得构造这个对象是调用堆栈的轨迹 | |
void addSuppressed(Throwable t) | 为一个异常增加一个“抑制的”异常,这出行在try-with-Resources语句中,其他t是close方法抛出的异常 | |
Throwable[] getSuppressed() | 得到这个异常所有“抑制的”异常。 | |
java.lang.Exception | Exception(Throwable cause) | |
异常对象 | ,Exception(String message,Throwable cause) | 用给定的cause构造一个Exception对象 |
java.lang.RuntimeException | RuntimeException(Throwable cause) | |
运行时异常对象 | RuntimeException(String message,Throwable cause) | 用给定的cause构造一个RuntimeException对象 |
java.lang.StackWalker (java 9) | static StackWalker getInstance()static StackWalker | |
栈堆轨迹对象 | static StackWalker getInstance(StackWalker.Option option) | |
getInstance(Set<StackWalker.Option> option) | 得到一个StackFrame实例。选项包括StackFrame.Option枚举中的三种类型 | |
forEach(Consumer<? super StackWalker.StackFrame> action) | 在每一个栈帧上完成给定的动作,从最近调用的方法开始 | |
walk(Function<? super Steam<StackWalker.StackFrame>,? extends T> function) | 对一个栈帧流应用给定的函数,返回这个函数的结果 | |
java.lang.StackWalker.StackFrame (java 9) | .... | |
栈堆轨迹中栈帧对象 | ||
java.lang.StackTraceElement | String getFileName() | 得到包含该元素执行点的源文件的文件名,如果这个信息不可以则返回null |
int getLineNumber() | 得到包含该元素执行点的源文件的行号,如果这个信息不可以则返回-1 | |
String getClassName() | 得到包含该元素执行点的类的完全限定名 | |
String getMethodName() | 得到包含1该元素执行点的方法的方法名。构造器的方法名为.静态初始化器的方法名为。无法区分同名的重载方法 | |
boolean inNativeMethod() | 如果这个元素的执行点在一个原生方法中,则返回true | |
String toString() | 返回一个格式化字符串,包含类的方法名、文件名以及行号 |
异常使用技巧
- 异常处理不能替代简单的测试:
- 捕获异常所消耗的运行时间远远大于测试。只有异常情况下才使用异常
- 不要过分细化异常:
- 不要讲每一条语句在放在独立的try语句块中,将正确处理与错误处理分开
- 充分利用异常层次结构
- 不要只抛出RuntimeException异常。应该寻找一个适合的子类或创建自己的异常类
- 不要只捕获Throwable异常,否则会使代码更难读、更难维护
- 不要压制异常
- 如果异常很难出行,可以采取捕获但不处理的方式
- 在检测错误是,“苛刻”要比放任更好
- 在可能出错的地方尽量不要返回null,而是抛出一个异常
- 不要羞于异常传递
- 早抛出,晚捕获
断言
在判断参数是否正确时,如果程序含有大量这种检查,程序运行起来慢很多。
断言机制运行在测试期间向代码插入一些检查,而在生产代码中会自动删除这些检查。关键字为assert condition
和assert condition : expression
这个两个语句都会计算条件,如果结果为false,则会抛出AssertionErre异常,第二个语句中,表达式将传入AssertionError对象的构造器,并转化为一个消息字符串。
// 断言x是一个非负数
assert x>=0;
// 将x的实际值传递给AssertionError对象,以便展示
assert x>=0 : x;
特性:
- 断言失败是致命的,不可恢复的错误
- 断言检查只是在开发或者测试阶段打开。
启用和禁用断言
在默认情况下断言是禁用的,可以在运行程序时用-enableassertions
或ea
选择启用断言.
禁用断言为-disableassertions
或da
java -enableassertions MyApp
// 在某个类或整个包中启用断言
java -ea:MyClass -ea:com.mycompany.mylib MyApp
注意点:不必重新编译程序来启用或禁用断言。这步是类加载器的功能
类名 | 方法 | 描述 |
---|---|---|
java.lang.ClassLoader | void setDefaultAssertionStatus(boolean enabled) | 为通过类加载器加载的类(没有显示的类或包断言状态)启动或禁用断言 |
void setPackageAssertionStatus(String packageName,boolean enabled) | 为给定的包及其子包中的所有类启用或禁用断言 | |
void setClassAssertionStatus(String className, boolean enabled) | 为给定的类和它的内部类启用或禁用断言 | |
void clearAssertionStatus() | 删除所有显示的类和包断言转态设置,并禁用通过这个类加载器加载的所有类的断言 |
日志
基本日志
调用全局日志记录器(global logger) 并调用其info方法:
Logger.getGlobal().info("message")
可以在适当的地方(例如mian方法的最前面)调用 Logger.getGlobel.setLevel(Level.OFF),将会取消所有日志
泛型
一般使用E
表示泛型数组,k
和v
·表示键值对。T
表示任意类型
泛型类
pulic class Pair<T>
泛型方法
class ArrayAlg{
pulic static <T> T getMiddle(T.. a){
return a[a.length/2];
}
}
String middle = ArrayALg.<String>getMiddle("c","c","c");
// 大多数情况下,方法调用中可以省略<String>类型参数
泛型变量的限定
如果要实现特定功能,不加以限定代码可维护性很低。
例如要实现毕竟,就规定泛型必须继承Comparable接口
// 表示T 应该是限定类型的子类型。T和显示类型可以是类也可以是接口
<T entends Comparable>
说明:
-
一个类型变量或者通配符可以有多个限定。
-
限定类型用
&
分隔,用,
分隔类型变量。 -
可以根据需求拥有多个接口超类型,但最多有一个限定是类。如果有一个类作为限定,他必须是限定列表中的第一个限定
泛型代码和虚拟机
虚拟机没有泛型类型对象---所有对象都属于普通类。
类型擦除
任何泛型类型,都会自动提供一个相应的原始类型。这个原始类型是去掉类型参数后的泛型类型名。类型变量会被擦除,并替换为其限定类型(对于无限定的变量侧替换为Object)
tig:标签接口(没有方法的接口)放在限定列表的末尾
转换泛型表达式
编写泛型方法是,如果擦出了返回类型。编译器会会插入强制类型转换。
编译器会把这种类型的方法调用转换为两条虚拟机指令:
- 对原始方法的调用
- 将返回的Object类型强制转换为指定类型
转换泛型方法
如果继承一个泛型类,并且重写其方法,会发生类型擦除与多态的冲突
pulic class pair<T>{
private T second;
pulic void setSecond(T second){
this.second = second;
}
}
class DateInterval extends Pair<LocalDate>{
pulic void setSecond(LocalDate second){
if(second.compareTo(getSecond())>=0){
super.setSecond(second);
}
}
}
在经过类型擦除后,就会产生两个setSecond
方法,一个是Object类型,一个是LocalDate类型。
编译器为了解决这中问题,就产生了桥方法。将两个方法合并为
pulic void setSecond(T second){
setSecond((LocalDate) second);
}
而get方法会生成
pulic Object gettSecond(){
return this.second;
}
pulic LocalDate gettSecond(){
return this.second;
}
不能编写这样的java代码,但是在虚拟机中,会由参数类型和返回类型共同指定一个方法。因此,编译器可以为两个仅返回类型不同的方法生成字节码
总之,对于java泛型的转换,需要记住以下几点
- 虚拟机中没有泛型,只有普通的类和方法
- 所有的类型参数都会替换为他们的限定类型
- 会合成桥方法保持多态
- 为保持类型的安全性,必要时对插入强制类型转换
限制与局限性
-
不能用基本类型实例化类型参数
-
运行时类型查询只适用于原始类型
-
不能创建参数化类型的数组
-
Varargs警告
-
不能实例化类型变量
-
不能构造泛型数组
-
泛型类的静态上下文中类型变量无效
-
不能抛出或捕获泛型类的实例
-
可以取消对检查性异常的检查
-
注意擦除后的冲突
为了支持擦除转换,要施加一个限制:倘若两个接口类是同一个接口的不同参数化,一个类或类型变量是不能同时作为这两个接口类型的子类。如果不这样做,有可能与合成的桥方法产生冲突
泛型类型的继承规则
例如 A继承于B C与C没有任何关系,只是他们都属性C 的子类
通配符类型
通配符子类型限定
<?extends A>
// 限定 类型参数 A 所有子类 因为类型擦除会将参数类型擦除为 限定类型
? extends A getA(){}; 表示父类引用指向子类对象,合法---》例如A = b.getA(); b是a的子类
void setA(? extends A){}; 表示可能发生子类引用指向父类对象,不合法
通配符超类型限定
<?super A>
// 限定 类型参数 为A的所有超类
? super A getA(){}; 表示子类引用执行父类对象,不合法
void setA(? super A){}; 表示可能发生父类引用指向子类对象,合法
无限定通配符
会被擦除为Object,get方法无影响,但set方法完全无法调用。
通配符捕获
反射和泛型
通过反射获取泛型的类型
泛型Class类
==java.lang.Class<T>===
// 返回无参数构造器构造的一个新实例
T newInstance();
// 如果Obj为null或可能转换成类型T,则返回obj;否则抛出一个BadCastException异常
T cast(Object obj);
// 如果T是枚举类型,则返回所有值组成的数组,否则返回null
T[] getEnumCanstants();
// 返回这个类的超类,如果T不是一个类或Object类,则返回null
Class<? super T> getSuperclass();
// 获得公共构造器,或者有给定参数类型的构造器
Constructor<T> getConstructor(Class<?>... parameterTypes);
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes);
===java.lang.reflect.Constructor<T>===
// 返回用指定参数构造的新实例
T newInstance(Object... parameters);
虚拟机中的泛型类型信息
在虚拟机中,擦除的类任然保留原先泛型的微弱记忆。可以使用反射API来确定
- 这个泛型有一个名为T的类型参数
- 这个类型参数有一个子类型限定,其自身又是一个泛型类型
- 这个限定类型有一个通配符参数
- 这个通配符参数有一个超类型限定
- 这个泛型方法有一个泛型数组参数
说明可以重新构造实现者声明的泛型类和方法的所有有关内容。但是,不知道对于特定的对象或方法调用会如何解析类型参数。
为了表述泛型类型声明,可以使用java.lang.refect
包中的接口Type,这个接口包含以下子类型
- Class类,描述具体类型 ==实现Type接口的类,其余都是继承了Type接口的接口==
- TypeVariable接口,描述类型变量(如 T extends Comparble<? super T>)
- WildcardType接口,描述通配符( 如 ? super T)
- ParameterizedType接口,描述泛型类或接口类型(如 Comparable<? super T>)
- GenericArrayType接口,描述泛型数组(如 T[])
相关泛型接口的类
类 | 方法 | 描述 |
---|---|---|
java.lang.Class | TypeVariable<Class>[] getTypeParameters() | 如果这个类型被声明为泛型类型,则获得泛型类型变量(Class 获取这个T),否则获得一个长度0的数组。 |
Type getGenericSuperclass() | 获得这个类型所声明超类的泛型类型;如果这个类型是Object或者不是类类型,则返回null。 | |
Type[] getGenericInterfaces() | 获得这个类型所声明类型的泛型类型(按照声明的次序),否则,如果这个类型没有实现接口,则返回长度为0的数组。 | |
java.lang.reflect.Method | TypeVariable<Class>[] getTypeParameters() | 如果这个方法被声明为一个泛型方法,则获得泛型类型变量,否则返回长度为0的数组 |
Type getGenericReturnType() | 获得这个方法声明的泛型返回类型 | |
Type[] getGenericParameterTypes() | 获得这个方法声明的泛型参数类型。如果这个方法没有参数,返回长度为0的数组 | |
java.lang.reflect.TypeVariable | Type[] getBounds(); | 获得这个类型变量的子类限定,否则,如果该变量无限定,则返回长度为0的数组 |
String getName(); | 获得这个类型变量的名字。例如Class,输出T | |
java.lang.reflect.WildcardType | Type[] getUpperBounds(); | 获得这个类型变量的子类限定,否则,如果没有子类限定,则返回长度为0的数组。 |
Type[] getLowerBounds(); | 获得这个类型变量的超类限定,否则,如果没有超类限定,则返回长度为0的数组 | |
java.lang.reflect.ParameterizedType | Type[] getActualTypeArguments(); | 获得这个参数化类型声明的类型参数 |
Type getRawType(); | 获得这个参数化类型的原始类型 | |
Type getOwnerType(); | 如果是内部类型,则返回其外部类类型,如果是一个顶级类型,则返回null | |
java.lang.reflect.GenericArrayType | Type getGenericComponentType(); | 获得这个数组类型声明的泛型元素类型 |
集合
集合框架
将接口与实现分离
Collection接口
集合类的基本接口。有boolean add(E element)
和Iterator<E> iterator()
迭代器
Iterator接口包含四个方法
public interface Iterator<E> {
// 如果存在另外一个可访问的元素,返回true
boolean hasNext();
// 返回将要访问的下一个对象。如果已经到达了集合的末尾。将抛出一个NoSuchElement异常
E next();
// 删除上次访问的对象。这方法必须紧跟在访问一个元素之后执行。如果上次访问之后集合已经发送了变化,抛出IlleaglState异常
default void remove() {
throw new UnsupportedOperationException("remove");
}
// 访问元素,并传递到指定的动作,直到再没有元素,或者抛出一个异常
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
可以使用while()
循环迭代访问
也可以使用for each
循环表示同样的循环操作。编译器简单地将for each
循环转换为带有迭代器的循环。可以处理任何实现了Iterable接口的对象
这个接口只包含了一个抽象方法Iterator<E> iterator();
而Collection接口扩展了该接口。
也可以不使用循环,而是调用forEachRemaining
方法。只需要提供一个lambda表达式
注意点
- 访问元素的顺序取决于集合类型。
- 当调用next时,迭代器就越过下一个元素,并返回刚刚越过的元素的引用。
- 在remove时,需要先调用next。不能连续remover
==java.util.Collection==
方法 | 描述 |
---|---|
Iterator iterator() | 返回一个用于访问集合中各个元素的迭代器 |
int size() | 返回当前存储在集合中的元素个数 |
boolean isEmpty() | 如果集合中没有元素,返回true |
boolean contains(Object obj); | 如果集合中包含了一个与obj相等的对象,返回true |
boolean containsAll(Collection<?> c); | 如果这个集合包含other集合中所有元素,返回true |
boolean add(E e); | 将一个元素添加到集合中。如果由于这个调用改变了集合,返回true |
boolean addAll(Collection<? extends E> c); | 将other集合中的所有元素添加到这个集合中,如果改变了,返回true |
boolean remove(Object o); | 从这个集合中删除等于obj的对象。如果有匹配的对象被删除,返回true |
boolean removeAll(Collection<?> c); | 从这个集合中删除other集合中存在的所有元素。如果改变了,返回true |
default boolean removeIf(Predicate<? super E> filter); | 从这个集合删除filter返回true的所有元素,如果改变了,返回true |
void clear(); | 从这个集合中删除所有元素 |
boolean retainAll(Collection<?> c); | 从这个集合中删除所有与other集合中元素不同的元素,如果改变了,返回true |
Object[] toArray(); | 返回这个集合中的对象的数组 |
T[] toArray(T[] arrayToFill); | 同上。如果arrayToFill足够大,就将集合中的元素填入这个数组中。剩余空间填补null |
否则,分配一个新数组,其成员类型与arrayToFill的成员类型相同,其长度等于集合的大小,并填充集合元素 |
集合框架中的接口
java集合框架为不同类型的集合定义了大量接口,如图所示
集合中有两大基本接口:Collection
和Map
。
- map是映射键值对
- list是有序集合。元素会增加到容器中的指定位置。可以采用两种方式访问元素
- 使用迭代器访问,
- 使用一个整数索引来访问。也就是随机访问
- ListItertor接口定义了一个方法用于在迭代器位置前面增加一个元素
- Set接口等同于Collection接口。但不允许增加重复元素。equals方法:只要两个集包含相同的元素就认为相等,而不要求有同样的顺序。
- SortedSet就SortedMap接口。提供用于排序的比较器对象。定义了可以得到集合子集视图的方法
- NavigableSet/Map接口。其中包含一些用于搜索和遍历有序集和映射的方法。TreeSet和TreeMap类实现了这些接口
具体集合
集合类型 | 描述 |
---|---|
ArrayList | 可以动态增长和缩减的一个索引序列 |
LinkedList | 可以在任何位置高效插入和删除的一个有序序列 |
ArrayDeque | 实现为循环数组的一个双端队列 |
HashSet | 没有重复元素的一个无序集合 |
TreeSet | 一个有序集 |
EnumSet | 一个包含枚举类型值的集 |
LinkedHashSet | 一个可以记住元素插入次序的集 |
PriorityQueue | 允许高效删除最小元素的一个集合 |
HashMap | 存储键值对关联的一个数据结构 |
TreeMap | 键有序的一个映射 |
EnumMap | 键属于枚举类型的一个映射 |
LinkedHashMap | 可以记住键值对添加次序的一个映射 |
WeakHashMap | 值不会在别处使用时就可以被垃圾回收的一个映射 |
IdentityHashMap | 用=而不是用equals比较键的一个映射 |
图表示这些类的关系
链表(LinkedList)
数组以及动态的ArrayList类。在对数组中间的数据进行删除时开销很大,其原因时之后的元素都要向数组的前端移动。
数组时在连续的存储位置上存放对象引用。而链表则是将每个对象存放在单独的链接中,每个链接还存放着序列上一个以及下一个链接的引用。在Java中,所有链表都是双向链表。
add方法只依赖与迭代器的位置,而remove方法依赖于迭代器的状态。如果有两天迭代器同时操作一个链表,并且改变了其状态,就报出ConrurrentModification
异常
链表不支持随机访问,每一次访问都需要从0开始便利。因为其根本没有缓存位置信息
相关方法介绍
类/接口 | 方法 | 描述 |
---|---|---|
java.lang.List | ListIterator listIterator(); | 返回一个列表迭代器,用来访问列表中的元素 |
接口 | ListIterator listIterator(int index); | 同时。第一次调用这个迭代器的next会返回给定索引的元素 |
void add(int index, E element); | 在给定位置添加一个元素 | |
boolean addAll(int index, Collection<? extends E> c); | 将一个集合中的所有元素添加到给定位置 | |
E remove(int index); | 删除并返回给定位置的元素 | |
E get(int index); | 获取给定位置的元素(开销较大) | |
E set(int index, E element); | 用一个新元素替换给定位置的元素,并返回原来的元素 | |
int indexOf(Object o); | 返回与指定元素相等的元素在列表中第一次出现的位置,如果没有返回-1 | |
int lastIndexOf(Object o); | 返回与指定元素相等的元素在列表中最后一次出现的位置,如果没有返回-1 | |
java.util.ListIterator | void add(E e); | 在当前位置添加一个元素 |
接口 | void set(E e); | 用新元素替换next或previous访问的上一个元素。如果上一次迭代之后列表结构被修改了,就抛出IllegalState异常 |
boolean hasPrevious(); | 当反向迭代列表时,如果还有可以访问的元素,返回true | |
E previous(); | 反向迭代列表。如果已经到达列表头部。抛出NoSuchElement异常 | |
int nextIndex(); | 返回下一次调用next方法时返回的元素的索引 | |
int previousIndex(); | 返回下一次调用previous方法时返回的元素的索引 | |
java.util.LinkedList | LinkedList() | 构造一个空链表 |
类 | LinkedList(Collection<? extends E> c) | 构造一个链表,并且将集合中所有元素添加到链表中 |
void addFirst(E e) | ||
void addLast(E e) | 将一个元素添加到头部或者尾部 | |
E getFirst() | ||
E getLast() | 返回列表头部或者尾部的元素 | |
E removeFirst() | ||
E removeLast() | 删除并返回列表头部或尾部的元素 |
数组列表(ArrayList)
有两种访问元素的协议:
- 通过迭代器
- 通过get和set方法随机访问每一个元素。不适用与链表,但对数组很有用
散列集(HashSet、LinkedHashSet)
用于快速查找对象。散列表为每一个对象计算一个整数,称为散列码(hash code)。散列表是由对象的实例字段得出的一个整数。
在java中,散列表用链表数组实现。每个列表被称为桶。要想查找表中对象的位置,就用先计算其散列码,然后与桶的总数取余,所得到的结果就是保存这个元素的桶的索引。
如果两个元素的桶索引重复了,这种现实称为==散列冲突==,这是需要将新对象与桶中所有对象比较,查看这个对象是否已经存在。
在java8中,如果桶满时会从链表变为平衡二叉树。
如果散列集太满,就会进行==再散列==。而==填装因子==确定何时对散列表进行==再散列==, 默认值为0.75 (0.0~1.0 之间的一个数,确定散列表填充的百分比,当大于这个百分比时,散列表进行再散列,扩大两倍)
相关方法介绍
类 | 方法 | 描述 |
---|---|---|
java.util.HashSet | HashSet() | 构造一个空散列集 |
类 | HashSet(Collection<? extends E> c) | 构造一个散列集,并将集合中所有元素添加到散列集中 |
HashSet(int initialCapacity) | 构造一个空的具有指定容量(桶数)的散列集 | |
HashSet(int initialCapacity, float loadFactor) | 构造一个有指定容量和填装因子的空散列集 |
LinkedHashSet维护着一个运行于所有条目的双重链接列表。
树集(TreeSet)
树集类与散列表十分相似,不过,树集是一个有序集合。使用二叉树(红黑树)的存储结构
可以以任意顺序将元素插入到集合中。在对集合进行遍历时,值将自动地按照排序后的顺序呈现。
要使用树集,必须能够比较元素,这些元素必须实现Comparable接口,或者构造集时必须提供一个Comparator。
相关方法介绍
类/接口 | 方法 | 描述 |
---|---|---|
java.util.TreeSet | TreeSet() | |
类 | TreeSet(Comparator<? super E> comparator) | 构造一个空树集 |
TreeSet(Collection<? extends E> c) | ||
TreeSet(SortedSet s) | 构造一个树集,并增加一个集合或有序集中的所有元素(对于添加有序集,需要保证同样的顺序) | |
java.util.SortedSet | Comparator<? super E> comparator() | 返回用于对元素进行排序的比较强。如果元素用Comparable接口的compareTo方法进行比较则返回null |
接口 | E first(); | |
E last(); | 返回有序集合中的最小元素或最大元素 | |
java.lang.NavigableSet | E higher(E value); | |
接口 | E lower(E value); | 返回大于value的最小元素或小于value的最大元素。没有返回null |
E ceiling(E value); | ||
E floor(E value); | 返回大于等于value的最小元素或小于等于value的最大元素。没有返回null | |
E pollFirst(); | ||
E pollLast(); | 删除并返回这个集合中的最大元素或最小元素。集合为空时返回null | |
Iterator descendingIterator(); | 返回一个按照递减排序遍历集合元素的迭代器 |
队列(Queue接口)与双端队列(Deque接口)
ArrayDeque和LinkedList类实现了这个接口。这两个类都可以提供双端队列。
相关方法介绍
类/接口 | 方法 | 描述 |
---|---|---|
接口java.util.Queue | boolean add(E e); | 如果队列没有满,将给定的元素添加到这个队列的队尾并返回true。如果队列已满,抛出IllegalState异常, |
boolean offer(E e); | 返回false | |
E remove(); | 假如队列不为空,删除并返回这个队列队头的元素。如果队列是空的,抛出NoSuchElement异常 | |
E poll(); | 返回null | |
E element(); | 如果队列不为空,返回这个队列队头的元素,但不删除。如果队列为空,抛出NoSuchElement异常 | |
E peek(); | 返回null | |
接口java.util.Queue | void addFirst(E e); | |
void addLast(E e); | ||
boolean offerFirst(E e); | ||
boolean offerLast(E e); | 将给定的对象添加到双端队列的队头或队尾。如果这个双端队列已满,前面两个方法抛出IllegalState异常,后面两个方法返回false | |
E removeFirst(); | ||
E removeLast(); | ||
E pollFirst(); | ||
E pollLast(); | 如果这个双端队列不为空,删除并返回双端队列队头的元素。如果双端队列为空,前面两个方法抛出NoSuchElement异常,后面两个方法返回null | |
E getFirst(); | ||
E getLast(); | ||
E peekFirst(); | ||
E peekLast(); | 如果这个双端队列非空,返回双端队列队头的元素,但不删除。如果双端队列为空,前面两个方法抛出NoSuchElement异常,后面两个方法返回null | |
java.util.ArrayDeque | ArrayDeque() | |
方法 | ArrayDeque(int numElements) | 用初始容量16或给定的初始容量构造一个无限定双端队列 |
优先队列(priotityQueue)
元素可以按照任意的顺序插入,但会按照有序的顺序进行检索。
优先队列并没有对所有元素进行排序。如果迭代处理这些元素,并不需要对他们进行排序。
使用==堆==的数据结构,是一种可以自组织的二叉树。其添加和删除操作可以让最小的元素移动到根,而不必花费时间对元素进行排序。
和TreeSet一样。即可以保存实现了Comparable接口的类对象,也可以保存构造器中提供的Comparator对象。
相关方法介绍
类 | 方法 | 描述 |
---|---|---|
java.lang.PriorityQueue | PriorityQueue() | |
PriorityQueue(int initialCapacity) | 构造一个存放Comparable对象的优先队列 | |
PriorityQueue(Comparator<? super E> comparator) | ||
PriorityQueue(int initialCapacity, Comparator<? super E> comparator) | 构造一个优先队列,并使用指定的比较器对元素进行排序 |
映射
java类库为映射提供了两个通用的实现:HashMap和TreeMap。
散列映射对键进行散列,树映射根据键的顺序将元素组织为一个搜索树。只对键进行散列或比较函数。
基本映射操作
类/接口 | 方法 | 描述 |
---|---|---|
接口 Map<K,V> | V get(Object Key) | 获取与键关联的值;返回与键关联的对象,或者如果映射中没有对象,则返回null。实现类可以禁止键为null |
default V getOrDefault(Object key, V defaultValue) | 获得与键关联的值;返回与键关联的对象,或者为在映射中找到这个键,则返回defaultValue | |
V put(K key, V value) | 将关联的一对键和值放在映射中。如果这个键已经存在,新的对象将取代与这个键关联的旧对象,这个方法将返回键关联的旧值。如果之前没有这个键,则返回null。实现类可以禁止键或值为null。 | |
void putAll(Map<? extends K, ? extends V> m); | 将给定映射中的所有映射条目添加到这个映射中。 | |
boolean containsKey(Object key); | 如果在映射中已经有这个键,返回true | |
boolean containsValue(Object value); | 如果映射中已经有这个值,返回true | |
default void forEach(BiConsumer<? super K, ? super V> action) | 对这个映射中所有键/值对应用这个动作 | |
类 java.util.HashMap<K,V> | HashMap(); | |
HashMao(int intitalCapacity); | ||
HashMao(int intitalCapacity, float loadFactor); | 用给定的容量和填装因子构造一个空散列映射 | |
类 java.util.TreeMap<K,V> | TreeMap(); | 为实现Comparable接口的键构造一个空的树映射 |
TreeMap(Comparator<? super K> c); | 构造一个树映射,并使用一个指定的比较器对键进行排序 | |
TreeMap(Map<? extends K, ? extends V> m) | 构造一个树映射,并将某个映射中的所有映射条目添加在树映射中 | |
public TreeMap(SortedMap<K, ? extends V> m) | 构造一个数映射,将某个有序映射中的所有映射条目添加到树映射中,并使用与给定的有序映射相同的比较器 | |
接口 java.util.SortedMap<K,V> | Comparator<? super K> comparator(); | 放回对键进行排序的比较器。如果键是用Comparable接口的compareTo方法进行比较,则返回null。 |
K firstKey(); | ||
K lastKey(); | 返回映射中的最小值或最大值 |
更新映射条目
java.util.Map<K,V>
-
default V merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction)
如果key与一个非null键值关联,将函数应用到key和value,将key与结果关联,如果结果为空,则删除这个键。否则,将key与value关联,返回get(key)。==不为空才执行,为空则put默认值==
-
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
将函数应用到key和get(key)。将key与结果关联,如果结果为空,则删除这个key。get(key)。==不管value空不空,都将新值put==
-
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
如果key与一个非空值关联,将函数应用key和get(key),.....==value不为空,将新值put==
-
default V computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction)
如果key与一个空值关联,将函数应用key和get(key),.....==value为空,将新值put==
-
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
在所有映射条目上应用这个函数。将键与非null结果关联,对于null结果,则将相应的键删除。
-
default V putIfAbsent(K key, V value)
如果键不存或者与null值关联,则将它与value关联,并返回null。否则返回关联的值。
映射视图
集合框架不认为映射本身是一个集合。不过,可以得到映射的视图-(一个实现了Collection接口或某个子接口的对象)
映射有三种视图:键集、值集合(不是一个集)以及键值对集。键和键值对可以构成一个集,因为映射中一个键只能有一个副本。
java.util.Map<K,V>
-
Set keySet();
返回映射中所有键的一个集视图。可以从这个集中删除元素。键和相关联的值将从映射中删除,但是不能添加任何元素 -
Collection values();
返回映射中所有值的一个集合视图。可以从这个集合中删除元素,所删除的值以及相对应的键将从这个映射中删除,不过不能添加任何元素。 -
Set<Map.Entry<K, V>> entrySet();
返回Map.Entry对象的一个集视图,可以从这个集中删除元素。它们将从映射中删除。但是不能添加任何元素
java.util.Map.Entry<K,V>
- K getKey();
- V getValue();
返回这个映射条目的键或值 - V setValue(V value);
将相关映射中的值改为新值,并返回原理的值。
tisp:==keySet不是hashSet或TreeSet,而是实现了Set接口的另外某个类的对象==
弱散列映射
WeakHashMap
如果有一个值,它对应的键已经不再程序中的任何地方使用,也就是说某个键的最后一个引用已经消失,那么不再有任何途经可以引用这个值的对象。但是,由于程序中的任何部分不会再有这个键,所以,无法从映射中删除这个键值对。垃圾回收器并不会回收。
垃圾回收器会跟踪活动的对象。只要映射对象是活动的,其中的所有桶也是活动的,他们不能被回收。因此,需要由程序负责从长期存活的映射表中删除那些无用的值。或者,你可以使用WeakHashMap
。当对键的唯一引用来自散列表映射条目时,这个数据结构将与垃圾回收器协同工作一起删除键值对。
原理:
WeakHashMap
使用弱引用保存键。其对象包含另外一个对象的引用,在这里,就是一个散列表键。对于这种类型的对象,垃圾回收器采用一种特定的方式进行处理。正常情况下,如果垃圾回收器发现某个特定的对象已经没有他人引用,就将其回收。然而,如果某个对象只能由WeakHashMap
引用,垃圾回收器也会将其回收,但会将引用这个对象的弱引用放入一个队列。WeakHashMap
将周期性检查队列,以便找出新添加的弱引用。一个弱引用引入队列意味着这个键不再被他人使用,并且已经回收,于是,WeakHashMap将删除相关联的映射条目
java.util.WeakHashMap<K,V>
-
WeakHashMap()
-
WeakHashMap(int initialCapacity)
-
WeakHashMap(int initialCapacity,float loadFactor )
用给定的容量和填装因子构造一个空的散列映射
链接散列集与映射
LinkedHashSet和LinkedHashMap类会记住插入元素项的顺序。这样就可以避免散列表中的项看起来顺序是随机的。在表中插入元素项时,就会并入到双向链表中。如图所示
java.util.LinkedHashSet
-
LinkedHashSet()
-
LinkedHashSet(int initialCapacity)
-
LinkedHashSet(int initialCapacity,float loadFactor )
用给定的容量和填装因子构造一个空的连接散列集
链接散列映射可以使用访问顺序而不是插入来迭代处理映射条目。每次调用get或put时,受到影响的项将从当前的位置删除,并放在项链表的尾部(只影响项在链表的位置,而散列表的桶不会受影响。映射条目总是在键散列码对应的桶中)。要构造这样一个散列映射,需要调用
==LinkedHashMap<K,V>(initialCapacity,loadFactor true)==
访问顺序对于实现缓存的"最近最少使用"原则十分重要。构造LinkedHashMap的一个子类,然后覆盖下面的这个方法:
==protected boolean removeEldestEntry(Map.Entry<K,V> eldest)==
每当方法返回true时,添加一个新映射条目就会导致删除eldest项,例如下面的缓存对多存放100个元素
LinkedHashMap<Object, Object> cache = new LinkedHashMap<Object, Object>(128, 0.75F, true){
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
return size() > 100;
}
};
java.util.LinkedHashMap
-
LinkedHashMap()
-
LinkedHashMap(int initialCapacity)
-
LinkedHashMap(int initialCapacity, float loadFactor )
-
LinkedHashMap(int initialCapacity, float loadFactor,boolean accessOrder)
用给定的容量、填装因子和顺序构造一个空的连接散列映射。accessOrder参数为true时表示访问顺序,为false是表示插入顺序
-
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest)
如果想删除eldest元素,就要覆盖为返回true。eldest参数是预期可能删除的元素。这个方法在向映射中添加一个元素之后调用。其默认实现会返回false。即在默认情况下,老元素不会被删除。不过,可以重新定义这个方法,以便有选择低返回true。例如,如果最老的元素符合一个条件,或者映射超过了一定大小,则返回true。
枚举集与映射
EnumSet是一个枚举类型元素集的高效实现。由于枚举类型只有有限个实例,所以EnumSet内部用位序列实现。如果对应的值在集中,则相应的位被置为1。
EnumSet类没有公共的构造器。要使用静态工厂方法构造这个集:
enum WeekDay {
/**
*
*/
MONDAY,
/**
*
*/
TUESDAY
}
public class MyObjects {
EnumSet<WeekDay> always = EnumSet.allOf(WeekDay.class);
EnumSet<WeekDay> never= EnumSet.noneOf(WeekDay.class);
EnumSet<WeekDay> workday= EnumSet.range(WeekDay.MONDAY);
EnumSet<WeekDay> mwf= EnumSet.of(WeekDay.TUESDAY);
}
可以使用Set接口的常用方法来修改EnumSet
java.util.EnumSet<E extends Enum>
- static <E extends Enum> EnumSet noneOf(Class elementType)
返回一个初始为空的可变集 - static <E extends Enum> EnumSet allOf(Class elementType)
返回一个包含给定枚举类型的所有值的可变集 - static <E extends Enum> EnumSet range(E from, E to)
返回一个包含from到to之间的所有值(包含两个边界元素)的可变集 - static <E extends Enum> EnumSet of(E e)
- static <E extends Enum> EnumSet of(E e1, E e2)
- static <E extends Enum> EnumSet of(E e1, E e2, E e3)
- static <E extends Enum> EnumSet of(E first, E... rest)
返回包括不为null的给定元素的可变集
EnumMap是一个键类型为枚举类型的映射。它可以直接高效地实现为一个值数组。需要在构造器中指定键类型。
java.util.EnumMap<K extends Enum, V)>
-
EnumMap(Class keyType);
构造一个键为给定类型的空的可变映射
标识散列映射
类IdentityHashMap有特殊的用途。在这个类中,键的序列值不是有hashCode函数计算的,而是用System.identityHashCode方法计算的。这是Object.hashCode根据对象的内存地址计算散列码时所使用的方法。而且,在对两个对象进行比较是,IdentityHashMap类使用==,而不使用equals。
也就是说,不同的键对象即使内容相同,也被视为不同的对象。在实现对象遍历算法(如对象串行化)时,这个类非常有用,可以用来跟踪哪些对象已经遍历过。
java.util.IdentityHashMap<K , V)> 类
-
IdentityHashMap();
-
IdentityHashMap(int expectedMaxSize);
构造一个空的标识散列映射集,其容量是大于1.5*expectedMaxSize的2的最小幂值(默认21)
视图与包装器
方法返回一个实现了Set接口的类对象,由这个类的方法操纵原映射。这种集合称为视图。例如keySet方法返回值
小集合
Java9引入了一些静态方法,可以生成给定元素的集或列表,以及给定键/值对的映射。
例如:List name = List.of("Peter","Paul");
Set numbers =Set.of(2,3);
会分别生成包含3个元素的一个列表和一个集。对于映射,需要指定键和值,如下所示:
Map<String,Interger> scores = Map.of("Peter",2,"Paul",3);
元素、键或值不能为null。
List和Set接口有11个方法,分别有0到10个参数,另外还有一个参数个数可变的of方法。提供这种特定性是为了提高效率。
对于Map接口,则无法提供一个参数可变的版本,因为参数类型会在键和值类型之间交替。不过他有一个静态方法ofEntries,能接受任意多个Map.Entry<K,V>对象(可以用静态方法entry创建这些对象)。
Map<String,Interger> scores = ofEntries(
entry("Peter",2),
entry("Paul",3);
)
of和ofEntries方法可以生成某些类的对象,这些类对于每个元素会有一个实例变量,或者有一个后备数组提供支持。
这些集合对象是==不可修改==的。如果试图改变他们的内容,会导致一个UnsupportedOperation异常
如果需要一个可改变的集合,可以把这个不可修改的集合传递到构造器:
List names = new ArrayList<>(LIist.of("Peter","Paul"));
以下方法调用:Collections.nCopies(n,anObject)
会返回一个实例了List接口的不可变的对象,将创建n大小的List,内容全部为anObject。
==注释:==
- of方法是java9新引入的。之前有一个静态方法Arrays.asList,它会返回一个可更改但是大小不可变的列表,也就是说这列表可以set,但是不能add或remove。另外还有遗留方法Collections.emptySet和Collections.singLeton
- Collections类包含很多实用方法,这些方法的参数和返回值都是集合。不要将它与Collection接口混淆。
不可修改的视图
可以集合的不可修改视图。这些实体对现有集合增加一个运行时检查。如果发现视图对视图进行修改,就会报出异常。
每一个方法都定义处理接口。
在使用是可以将一个集合传递给特定的方法,返回的视图不可修改,但是集合的原始引用可以修改。
这些返回的视图只是包装了接口而不是具体的集合对象,所有==只能访问==中定义的方法。
同步视图
如果从多个线程访问集合,就必须确保集合不会被意外地破坏。例如,如果一个线程试图将元素添加到散列表中,另外一个线程视图进行在散列列,其结果是灾难性的。
而同步视图保证一个线程结束了,另外一个线程才会进行操作。
检查型视图
“检查型”视图用来对泛型类型可能出行的问题提供调试支持。实际上将错误类型的元素混入泛型集合中的情况极有可能发生。
例如add时并不会有异常,但是get时就不抛出异常。
而检查型视图在add时如果类型不符,就会抛出异常。
java.util.List 接口
-
static List of() ==9==
-
static List of(E e1) ==9==
........
-
static List of(E e1,E e2,E e3,E e4,E e5,E e6,E e7,E e8,E e9,E e10) ==9==
-
static List of(E... elements) ==9==
生成给定元素的一个不可变的列表,元素不能为空
java.util.Set 接口
-
static Set of() ==9==
-
static Set of(E e1) ==9==
........
-
static Set of(E e1,E e2,E e3,E e4,E e5,E e6,E e7,E e8,E e9,E e10) ==9==
-
static Set of(E... elements) ==9==
生成给定元素的一个不可变的集,元素不能为空
java.util.Map 接口
-
static <K, V> Map<K, V> of() ==9==
-
static <K, V> Map<K, V> of(K k1, V v1) ==9==
........
-
static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2,K k3, V v3,K k4, V v4,K k5, V v5,K k6, V v6,K k7, V v7,K k8, V v8,K k9, V v9,K k10, V v10) ==9==
生成给定键和值的一个不可变的映射,键和值不能为空
-
static <K, V> Map.Entry<K, V> entry(K k, V v) ==9==
生成给定键和值的一个不可变映射,键和值不能为空
-
static <K, V> Map<K, V> ofEntries(Map.Entry<? extends k, ? extends V>... entries) ==9==
生成给定映射条目的一个不可变映射
java.util.Collections 所有方法返回的集合都是内部实现的集合 类
- static Collection unmodifiableCollection(Collection<? extends T> c)
- static Set unmodifiableSet(Set<? extends T> s)
- static SortedSet unmodifiableSortedSet(SortedSet s)
- static NavigableSet unmodifiableNavigableSet(NavigableSet s)
- static List unmodifiableList(List<? extends T> list)
- static <K,V> Map<K,V> unmodifiableMap(Map<? extends K, ? extends V> m)
- static <K,V> SortedMap<K,V> unmodifiableSortedMap(SortedMap<K, ? extends V> m)
- static <K,V> NavigableMap<K,V> unmodifiableNavigableMap(NavigableMap<K, ? extends V> m)
==构造一个集合视图;视图的更改器方法抛出一个异常== - static Collection synchronizedCollection(Collection c)
- static Set synchronizedSet(Set s)
- static SortedSet synchronizedSortedSet(SortedSet s)
- static NavigableSet synchronizedNavigableSet(NavigableSet s)
- static List synchronizedList(List list)
- static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
- static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
- static <K,V> NavigableMap<K,V> synchronizedNavigableMap(NavigableMap<K,V> m)
==构造一个集合视图;视图的方法是同步的== - static Collection checkedCollection(Collection c,Class type)
- static Queue checkedQueue(Queue queue, Class type)
- static Set checkedSet(Set s, Class type)
- static SortedSet checkedSortedSet(SortedSet s,Class type)
- static NavigableSet checkedNavigableSet(NavigableSet s,Class type)
- static List checkedList(List list, Class type)
- static <K, V> Map<K, V> checkedMap(Map<K, V> m,Class keyType,Class valueType)
- static <K,V> SortedMap<K,V> checkedSortedMap(SortedMap<K, V> m,Class keyType,Class valueType)
- static <K,V> NavigableMap<K,V> checkedNavigableMap(NavigableMap<K, V> m,Class keyType,Class valueType)
==构造一个集合视图;如果插入一个错误类型的元素,视图的方法抛出一个ClassCast异常== - static Iterator emptyIterator()
- static ListIterator emptyListIterator()
- static Enumeration emptyEnumeration()
- static final Set emptySet()
- static SortedSet emptySortedSet()
- static NavigableSet emptyNavigableSet()
- static final List emptyList()
- static final <K,V> Map<K,V> emptyMap()
- static final <K,V> SortedMap<K,V> emptySortedMap()
- static final <K,V> NavigableMap<K,V> emptyNavigableMap()
==生成一个空集合、映射或迭代器== - static Set singleton(T value)
- static List singletonList(T value)
- static <K,V> Map<K,V> singletonMap(K key, V value)
==生成一个单例列表、集或映射。在java9中,要使用相应的of方法== - static List nCopies(int n, T value)
==生成一个不可变的列表,包含n个相等的value==
java.util.Arrays
- static List asList(E... array)
==返回一个数组中元素的列表视图。这个数组可修改,但大小不可变==
小小警告:
这三种视图的xxxCollection
方法返回的集合,它的equals方法不调用底层集合的equals方法。实际上它继承了Object的equals方法,只能检查是否是一个对象,无法检查内容是否一致。视图采用这种工作方式,因为相等性检测在层级结构这一层上没有明确定义。
不过xxxSet和xxxList方法会使用底层集合的equals方法和hashCode方法。
子范围
可以为很多集合建立子范围(subrange)视图。在获取子范围时和字符串一样,包头不包尾
java.util.List 接口
- List subList(int fromIndex, int toIndex);
返回给定位置范围内的所有元素的列表视图
java.util.SortedSet 接口
- SortedSet subSet(E fromElement, E toElement);
- SortedSet headSet(E toElement);
- SortedSet tailSet(E fromElement);
返回给定范围内元素的视图
java.util.NavigableSet 接口
- SortedSet subSet(E fromElement, E toElement);
- NavigableSet subSet(E fromElement, boolean fromInclusive,E toElement, boolean toInclusive)
- SortedSet headSet(E toElement);
- SortedSet tailSet(E fromElement);
返回给定范围内元素的视图。boolean 标志决定视图是否包含边界
java.util.SortedMap 接口
- SortedMap<K,V> subMap(K fromKey, K toKey);
- SortedMap<K,V> headMap(K toKey);
- SortedMap<K,V> tailMap(K fromKey)
返回键在给定范围内的映射条目的映射视图
java.util.NavigableMap 接口
- SortedMap<K,V> subMap(K fromKey, K toKey);
- NavigableMap<K,V> subMap(K fromKey, boolean fromInclusive,K toKey, boolean toInclusive);
- SortedMap<K,V> headMap(K toKey);
- SortedMap<K,V> tailMap(K fromKey);
返回键在给定范围内的映射条目的映射视图。boolean 标志决定视图是否包含边界
算法
泛型算法
例如在寻找最大值时,数组、链表数组和链表时查找方式会有区别,而使用泛型可以合并为一个。例如
public static <T extends Comparable<T>> T max(Collection<T> c) {
if (c.isEmpty()) throw new NoSuchElementException();
Iterator<T> iterator = c.iterator();
T largest = iterator.next();
while (iterator.hasNext()){
T next = iterator.next();
if (largest.compareTo(next) < 0){
largest = next;
}
}
return largest;
}
排序和混排
Collections类的sort方法可以对实现了List接口的集合进行排序。列表元素实现了Comparable接口。
如果想使用其他排序方式,可以使用List接口的sort方法并传入Comparator对象。
如果需要降序排序,可以用静态Collections.reverresOrder()。这个方法返回一个比较器,比较器返回相反的比较。
降序也可以比较器的私有方法reversed()。返回当前比较强的逆序比较。
java中使用这些算法方法时的要求:列表必须是可以修改的,但不一定可以改变大小
- 支持set方法,则是可以修改
- 支持add和remove方法,则是可改变大小
java.util.Collections
-
static <T extends Comparable<? super T>> void sort(List list)
-
static void sort(List list, Comparator<? super T> c)
使用稳定的排序算法(相等时不改变顺序)对列表中的元素进行排序。这个算法的时间复杂度是O(n log n),其中n为列表的长度 -
static void shuffle(List<?> list)
-
static void shuffle(List<?> list, Random rnd)
随机地打乱列表中元素的顺序。这个算法的时间复杂度是O(n a(n)),n是列表的长度,a(n)是访问元素的平均时间。
java.util.List
- default void sort(Comparator<? super E> c)
使用给定比较器对列表进行比较
java.util.Comparator
-
default Comparator reversed()
生成一个比较器,将逆置这个比较强提供的顺序 -
static <T extends Comparable<? super T>> Comparator reverseOrder()
生成一个比较器,将逆置Comparable接口提供的顺序。
二分查找
要使用这个方法,集合必须是有序的,否则会返回错误结果。
java.util.Collections
- static int binarySearch(List<? extends Comparable<? super T>> list, T key)
- static int binarySearch(List<? extends T> list, T key, Comparator<? super T> c)
从有序列表中搜索一个键,如果元素类型实现了RandomAccess接口,就使用二分查找,其他情况下都使用线性查找。这个方法的时间复杂度为O(a(n) log n),n为列表的长度,a(n)是访问元素的平均时间。这个方法将返回这个键在列表中的索引,如果在列表中不存在这个键则返回负值i。在这种情况下,这个键应该插入到索引-i-1
的位置上。以保证列表的有序性
其它简单算法
java.util.Collections
- static <T extends Object & Comparable<? super T>> T min(Collection<? extends T> coll);
- static T min(Collection<? extends T> coll, Comparator<? super T> comp);
- static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll);
- static T max(Collection<? extends T> coll, Comparator<? super T> comp);
返回集合中最小或者最大的元素。可提供比较器。 - tatic void copy(List<? super T> to , List<? extends T> from);
将原列表中所有元素复制到目标列表的相应位置上。目标列表的长度至少与原列表一样。 - static void fill(List<? super T> list, T obj)
将列表中的所有位置设置相同的值。 - static boolean addAll(Collection<? super T> c, T... elements)
将所有的值添加到给定的集合中。如果集合改变了,则返回true。 - static boolean replaceAll(List list, T oldVal, T newVal);
将列表中所有值为oldVa的元素替换为newVal - static int indexOfSubList(List source, List target)
- static int lastIndexOfSubList(List source, List target)
返回source中第一个或最后一个等于target的子列表的索引。如果source中不存在target的子列表,则返回-1. - static void swap(List<?> list, int i, int j)
交换给定偏移位置的两个元素 - static void reverse(List<?> list)
逆置列表中元素的顺序。时间复杂度为O(n),n为列表的长度 - static void rotate(List<?> list, int distance)
旋转列表中的元素,将索引i的元素移动都位置(i+d)%list.size()
。时间复杂度为O(n),n为列表的长度 - static int frequency(Collection<?> c, Object o)
返回c中与对象o相等的元素的个数 - static boolean disjoint(Collection c1, Collection c2)
如果两个集合没有共同的元素,则返回true
java.uitl.Collection
- default boolean removeIf(Predicate<? super E> filter)
删除所有匹配的元素
java.uitl.List
-
default void replaceAll(UnaryOperator operator)
对这个列表的所有元素应用这个操作
批操作
java.uitl.Collection
- boolean retainAll(Collection<?> c);
从列表中删除所有未在c中出现的元素。也就是求交集。 - boolean removeAll(Collection<?> c);
从列表中删除所有在c中出现的元素。也就是求差集。 - boolean addAll(Collection<? extends E> c);
从列表中添加所有在c中出现的元素。也就是求并集。
集合与数组的转换
数组转集合使用List.of
:
String[] values = ...;
var staff = new HashSet<>(List.of(values));
集合转数组较为复杂:
如果使用toArray
方法的话,得到的是一个对象数组(Object[])。无法对其强制转换。
不过可以使用一个变体方法,提供一个指定类型而且指定长度的数组。这样一来,返回的数组就会创建相同的数据类型
String values = staff.toArray(new String[staff.size()]);
遗留的集合
Hashtable类
经典的Hashtable类与HashMap类的作用是一样的。实际上,接口也基本相同。与Vector类的方法一样,Hashtable方法也是同步的。
如果对遗留代码的兼容性没有任何要求,就应该使用HashMap。如果需要并发访问,则要使用ConcurrentHashMap。
枚举
遗留的集合使用Enumeration接口遍历元素序列。Enumeration接口有两个方法, hasMoreElements()和nextElement()。这两个方法完全类似与Iterator接口的hasNext方法和next()方法。
如果在遗留的类中发现实现了这个接口。可以使用Collections.list将元素收集到一个ArrayList中。
java.uitl.Collections
- static Enumeration enumeration(final Collection c)
返回一个枚举,可以枚举c的元素 - static ArrayList list(Enumeration e)
返回一个数据列表,其中包含e枚举的元素
java.uitl.Enumeration
- boolean hasMoreElements();
如果还有更多的元素可以查看,则返回true - E nextElement();
返回要检查的下一个元素
属性映射
属性映射(property map)是一种特殊类型的映射结构,它有三种特性
- 键与值都是字符串
- 这个映射可以很容易地保存到文件以及从文件加载
- 有一个二级表存放默认值
由于Properties类实现了Map<Object,Object>。因此可以使用get以及put,但是这样可以插入去除任何对象,最好坚持处理字符串。
java.uitl.Properties
- Properties()
创建一个空属性映射 - Properties(Properties defaults)
用一组默认值创建一个空属性映射 - String getProperty(String key)
获得一个属性。返回与键关联的值,或者如果这个键未在表中出现,则返回默认值表中与这个键关联的值,或者如果键在默认值表中也未出现,则返回null。 - String getProperty(String key, String defaultValue)
调用上面的方法,如果结构为null,则返回defaultValue - Object setProperty(String key, String value)
设置一个属性。返回给定键之前设置的值 - void load(InputStream inStream) throws IOException
从一个输入流加载一个属性映射 - void store(OutputStream out, String comments)
将一个属性映射保存到一个输出流。comments是所存储文件的第一行
java.lang.System
- Properties getProperties()
获取所有系统属性。应用必须有权限获取所有属性,否则抛出一个异常 - String getProperties(String key)
获取给定键名对应的系统属性。应用必须有权限获取所有属性,否则抛出一个异常。以下属性总是允许获取- java.version
- java.vendor
- java.vendor.url
- java.home
- java.class.path
- java.library.path
- java.class.version
- os.name
- os.version
- os.arch
- file.separator
- path.separator
- line.separator
- java.io.tempdir
- user.name
- user.home
- user.dir
- java.compiler
- java.sepcification.version
- java.sepcification.vendor
- java.sepcification.name
- java.vm.sepcification.version
- java.vm.sepcification.vendor
- java.vm.sepcification.name
- java.vm.version
- java.vm.vendor
- java.vm.name
栈
java库的Stack类其中有熟悉的push方法和pop方法。但是Stack类扩展了Vector类,从理论角度看,Vertor类并不太令人满意,甚至可以使用非栈操作的insert和remove方法在任何地方插入和删除值,而不只是在栈顶。
java.util.Stack
-
E push(E item)
将item压入栈并返回item
-
E pop()
弹出并返回栈顶的item。如果栈为空,不要调用该方法
-
E peek()
返回栈顶元素,但不弹出。如果栈为空,不要调用该方法。
位集
BitSet类用于存储一个位序列(它不是数学上的集,如果称为位向量或位数组更合适)。
如果需要高效地存储位序列,就可以使用位集。
由于位集将位包装在字节里,所以使用位集要比使用Boolean对象的ArrayList高效得多
BitSet类提供了一个便于读取、设置或重置各个位的接口。使用这个接口可以避免掩码和其它调整位的操作,如果将存储在int或long变量中就必须做这些烦琐的操作
java.util.BitSet
-
BitSet(int nbits)
创建一个位集,并设置其大小 -
int length()
返回位集的"逻辑长度",即1加上位集最高位的索引。 -
boolean get(int bit)
如果该位处于'开'状态,则返回true,否则返回false -
void set(int bit)
设置该位为"开状态",设置一个位为1 -
void clear(int bit)
设置该位为"关状态",清除一个位为0 -
void and(BitSet set)
这个位集与另外一个位集进行逻辑”与“ -
void or(BitSet set)
这个位集与另外一个位集进行逻辑”或“ -
void xot(Bitset set)
这个位集与另外一个位集进行逻辑”异或“ -
void andNot(BitSet set)
对应另一个位集中设置为1的所有位,将这个位集中相应的位清除位0
并发
线程
多线程程序在更低一层中扩展了多任务的概念:单个程序看起来在同时完成多个任务。每个任务在一个线程中执行,线程是控制线程的简称。如果一个程序可以同时运行多个线程,则称这个程序是多线程的。
多进程与多线程:本地的区别在于每个进程都拥有自己的一整套变量,而线程则共享数据。
线程状态
线程有六种状态:
- New(新建)
- Runnable(可运行)
- Blocked(堵塞)
- Waiting(等待)
- Timed waiting(计时等待)
- Terminated(终止)
要确定一个线程的状态,只需要调用getState方法即可
线程属性
中断线程
线程之前有stop方法来终止,但是已经被弃用。
不过可以使用interrupt方法来请求终止一个线程。
这个方法会设置线程的终端状态。这是每个线程都有的布尔标志。
守护线程
调用setDaemon(true)方法将一个线程转换为守护线程。
唯一的作用是为其它线程提供服务。例如计时器线程。
线程名
setName(xxx)方法设置线程名,在线程转储时可能很有用
未捕获异常的处理器
线程的run方法不能抛出任何检查型异常,但是,非检查型异常可能会导致线程终止。
但线程在死亡之前,会将异常传递到一个处理器。
setDefaultUncaughtExceptionHandler方法设置或获取默认处理器
线程优先级
在java早期版本很有用,现在不要使用线程优先级
同步
如果多个线程同时调用某个对象的修改器。相会相互覆盖。取决于线程访问数据的次序,可能会导致对象被破坏。这种情况称为竞态条件
如果不加同步锁,不同的线程会修改同一个对象。从而出现总额出错。
锁对象
pulic class xxx{
private ReentrantLock bankLock = new ReentrantLock();
public boolean transfer(int from ,int to ,double amount){
bankLock.lock();
try {
。。。。
}finally {
bankLock.unlock();
}
}
}
bankLock是锁对象,每一个xx对象都是拥有自己的一个锁对象。不同的xx对象拥有不同的锁,不同的线程可以方法不同的xx对象。
这种锁称为重入(reentrant)锁,因为线程可以反复获得已拥有的锁。锁有一个持有计数(hold count)来跟踪对lock方法的嵌套调用。线程每一次调用lock后都要调用unlock方法来释放锁。由于这个特性,被一个锁保护的代码可以调用另一个使用相同锁的方法。
java.util.concurrent.locks.Lock 接口
-
void lock()
获得这个锁;如果锁当前被另一个线程占有,则阻塞 -
voidunlock();
释放这个锁
java.util.concurrent.locks.ReentrantLock类
-
ReentrantLock()
构造一个重入锁,用来保护临界区 -
ReentrantLock(Boolean fair);
构造一个采用公平策略的锁。一个公平锁倾向与等待时间最长的线程。不过,这种公平保证可能严重影响性能。所以,默认情况下,不要求锁是公平的。
==注意点==
- 要注意确保临界区中的代码不要因为抛出异常而跳出临界区。如果在临界区代码结束之前抛出了异常,finally子句将释放锁,但是对象可能处于被破坏的状态
- 公平锁比常规锁==慢很多==
条件对象
通常,线程进入临界区后却发现只有满足了某个条件之后它才能执行。可以使用一个条件对象
来管理那些以及已经获得了一个锁却不能做有用工作的线程。条件对象也对称为条件变量
例如银行转账,如果当前账户没钱,将这个线程放入等待集中,如果有人往这个账户转账了,解除等待线程的阻塞。
通过bankLock.newCondition()获得条件对象。在不满足条件时调用await方法。将当前线程放入这个条件的等待集。当锁可用时,该线程并不会变为可运行状态。实际上,它任保持非活动状态,直到另一个线程在同一个条件上调用signalAll方法。
signalAll方法调用会重新激活等待这个条件的所以线程。当这些线程从等待集中移出时,它们再次成为可运行的线程,调度器最终将再次将它们激活。同时,它们会尝试重新进入该对象。一旦锁可用,它们中的某个线程将从await调用返回,得到这个锁,并从之前暂停的地方继续执行。
==注意点==
- 但一个线程调用await方法时,它没有办法重新自行激活,它寄希望与其它线程。如果没有其它线程来重新激活等待的线程,它就永远不会在运行了。这将导致死锁线程。最终一定需要在某个其它线程调用signaAll方法。
- signalAll调用不会立即激活一个等待的线程。它只是解除等待线程的阻塞,使这些线程可以在当前线程释放锁之后竞争访问对象。
java.util.concurrent.locks.Lock 接口
- Condition newCondition();
返回一个与这个锁相关联的条件对象
java.util.concurrent.locks.Condition 接口
-
void await()
将该线程放在这个条件的等待集中 -
void signal();
从该条件的等待集中随机选择一个线程,解除其阻塞状态
-
void signalAll();
解除该条件等待集中所有线程的阻塞状态
synchronized关键字
小总结:
- 锁用来保护代码片段,一次只能有一个线程执行被保护的代码
- 锁可以管理试图进入被保护代码段的线程
- 一个锁可以有一个或多个相关联的条件对象
- 每个条件对象管理那些已经进入被保护代码段但还不能运行的线程
在使用Lock/Condition接口时时现实定义锁。而使用synchronized关键字可以隐式定义锁,因为java中每个对象都有一个内部锁
内部对象锁只有一个关联条件。wait方法等价于await方法。notifyAll/notify方法等价于signalAll/signal方法
将静态方法声明为同步也是合法。如果调用这样的一个方法,它会获得相关类对象的内部锁。因此,没有其它线程可以调用这个类的该方法或任何其它同步静态方法。
内部锁和条件存在的限制:
- 不能中断任何正在尝试获得锁的线程
- 不能指定尝试获得锁的超时时间
- 每个锁仅有一个条件可能是不够的。
在代码中应该选择哪种做法?Lock和Condition对象还是同步方法?
- 最后既不使用Lock/Condition也不使用synchronized关键字。在许多情况下,可以使用
java.util.concurrent
包中的某种机制,它会处理所有锁定,例如阻塞队列。还可以使用并行流。 - 如果关键字适合程序,那么尽量使用这种做法,这样可以减少编写的代码量,还能减少出错的概率
- 如果特别需要Lock/Condition结构提供的额外能力,则使用
java.lang.Object 类
-
public final native void notifyAll()
解除在这个对象上调用wait方法的那些线程的阻塞状态。该方法只能在同步方法或同步块中调用。如果当前线程不是对象锁的所有者,该方法会抛出IllegalMonitorState异常 -
public final native void notify()
随机选择一个在这个对象上调用wait方法的线程,解除其阻塞状态。该方法只能在一个同步方法或同步块中调用。如果当前线程不是对象锁的所有者,该方法会抛出IllegalMonitorState异常
-
public final void wait()
导致一个线程进入等待状态,直到它得到通知。该方法只能在同步方法或同步块中调用。如果当前线程不是对象锁的所有者,该方法会抛出IllegalMonitorState异常
-
public final void wait(long timeout, int nanos)
-
public final void wait(long timeout)
导致一个线程进入等待状态,直到它得到通知或者经过了指定的时间。该方法只能在同步方法或同步块中调用。如果当前线程不是对象锁的所有者,该方法会抛出IllegalMonitorState异常。纳秒数不能超过100 0000
同步块
每一个java对象都有一个锁。线程可以通过调用同步方法获得锁。还有另外一个机制可以获得锁:即进入一个同步块。
public void transfer(Object accounts, int from ,int to, int amount){
synchronized (accounts){
....
}
....
}
在这里必须保证accounts对自己内部的所有更改方法使用内部锁
监视器概念
特性:
- 监视器时只包含私有字段的类
- 监视器类的每个对象有一个关联的锁
- 所有方法由这锁锁定。换句话说,如果客户端调用obj.method(),那么obj对象的锁在方法调用开始时自动获得,并且当方法返回时自动释放该锁。因为所有的字段时私有的,这样的安排可以确保一个线程处理字段时,没有其它线程能够访问这些字段。
- 锁可以有任意多个相关联的条件。
java以不太严格的方式采用了监视器概念,java中的每一个对象都有一个内部锁和一个内部条件。如果一个方法用synchronized关键字声明。那么。它表现得就像时一个监视器方法。可以通过调用wait/notifyAll/notify来访问条件变量。
不过java对象在以下3个重要方法不同于监视器,这削弱了线程的安全性:
- 字段不要求是private
- 方法不要求是synchronized
- 内部锁对客户可见
volatile字段
volatile关键字为实例字段的同步访问提供了一种免锁机制。如果声明一个字段为volatile,那边编译器和虚拟机就知道该字段可能被另外一个线程并发更新。
例如一个boolean标志位done。他的值有一个线程设置,一个线程查询。使用锁可以是
private boolean done;
pulic synchronized boolean isDone(){return done;}
pulic synchronized void setDone(){done = true;}
或者使用内部对象锁不是好办法。如果另一个线程已经对该对象加锁,isDone和setDone方法可能被堵塞。如果有这个问题,可以为这个变量使用一个单独的锁。但是,这会很麻烦。
这时使用volatile就很合适
private volatile boolean done;
pulic boolean isDone(){return done;}
pulic void setDone(){done = true;}
==注意==
- volatile变量不是提供原子性。不能保证读取,写入等操作不被中断
final变量
将一个字段声明为final时,可以安全的访问一个共享字段。
其它线程会在构造器完成构造后才能看到这个变量。
如果不使用final,就不能保证其它线程看到的该字段更新后的值,它们看到的可能时null。
原子性
假设对共享变量除了3赋值之外不进行其它操作,那么可以将这些共享变量声明为volatile
java.util.concurrent.atomic包中有很多类使用了很高效的机器级指令来保证其它操作的原子性。
==详细 java核心技术卷I,p582==
死锁
例如:
- 账户1:200rmb
- 账户2:300rmb
- 线程1:从账户1转300rmb到账户2
- 线程2:从账户2转300rmb到账户1
很显然,线程1和2都被阻塞。两个线程都无法执行下去,导致程序被挂起。这种情况叫做死锁
java编程语言无法避免或打破这种死锁。必须仔细设计程序,确保不会出现死锁。
线程局部变量
利用ThreadLocal类来生成一个局部变量供所有线程使用
例如
public static final ThreadLocal<SimpleDateFormat> FORMAT = ThreadLocal.withInitial(()->new SimpleDateFormat("yyyy-MM-dd"));
private String dateStamp = FORMAT.get().format(new Date());
在一个给定线程中首次调用get时,会调用构造器中的lambda表达式。在此之后,get方法会返回属于当前线程的那个实例
java.lang.ThreadLocal类
-
T get()
得到这个线程的当前值,如果时首次调用get,会调用initialize来得到这个值 -
void set(T t)
为这个线程设置一个新值
-
void remove()
删除对应这个线程的值
-
public static 《S》 ThreadLocal《S》 withInitial(Supplier<? extends S> supplier)
创建一个线程局部变量。其初始值通过调用这给定的提供者生成
java.util.concurrent.ThreadLocalRandom类
- static ThreadLocalRandom current()
返回特定于当前线程的Random类的实例
线程安全的集合
阻塞队列
当试图向队列中添加元素而队列已满,或是向从队列中移除元素而队列为空的时候,阻塞队列(blocking queue)将导致线程阻塞。在协调多个线程之间的合作是,十分有用。
工作线程可以周期性地将中间结果存储在阻塞队列中。其它工作线程移除中间结果,并进一步进行修改。队列对自动地平衡负载。
例如第一组线程运行的比第二组线程慢,第二组在等待结果时会阻塞。如果第一组线程运行得更快,队列会填满,直到第二组赶上来。
方法 | 正常动作 | 特殊情况下的动作 |
---|---|---|
add | 添加一个元素 | 如果队列满,则抛出异常 |
offor | 添加一个元素并返回true | 如果队列满,则返回false |
put | 添加一个元素 | 如果队列满,则阻塞 |
element | 返回队头元素 | 如果队列为空,则抛出异常 |
peek | 返回队头元素 | 如果队列为空,则返回null |
poll | 移除并返回队头元素 | 如果队列为空,则返回null |
remove | 移除并返回队头元素 | 如果队列为空,则抛出异常 |
take | 移除并返回队头元素 | 如果队列为空,则阻塞 |
阻塞队列的方法
在使用阻塞队列时,不要使用抛出异常的方法。而是使用相同的方法替代
在添加和移除时,可以设置超时时间。例如offer方法和poll方法
// 时间数量是100,单位是毫秒
boolean success = q.offer(x, 100, TimeUnit.MILLISECONDS);
Object head = q.poll(100, TimeUnit.MILLISECONDS);
java.util.concurrent.ArrayBlockingQueue 类
-
ArrayBlockingQueue(int capacity)
-
ArrayBlockingQueue(int capacity, boolean fair)
构造一个有指定的容量和公平性设置的阻塞队列。队列实现为一个循环数组
java.util.concurrent.LinkedBlockingDeque 类
java.util.concurrent.LinkedBlockingQueue 类
-
LinkedBlockingDeque()
-
LinkedBlockingQueue()
构造一个屋上限的阻塞队列或双向队列。实现为一个链表
-
LinkedBlockingDeque(int capacity)
-
LinkedBlockingQueue(int capacity)
根据指定容量构建一个有限的阻塞队列或双向队列,实现为一个链表
java.util.concurrent.DelayQueue 类
-
DelayQueue()
构造一个包含Delayed元素的无上限阻塞队列。只有那些延迟已经到期的元素可以从队列中移除
java.util.concurrent.Delay 接口
-
long getDelay(TimeUnit unit);
得到该对象的延迟,用给定的时间单位度量
java.util.concurrent.PriorityBlockingQueue 类
-
PriorityBlockingQueue()
-
PriorityBlockingQueue(int capacity)
-
PriorityBlockingQueue(int capacity, Comparator<? super E> comparator)
构造一个无上限阻塞优先队列,实现为一个堆。优先队列的默认初始容量为11。如果没有指定比较器,则元素必须实现Comparator接口
java.util.concurrent.BlockingQueue 接口
-
void put(E e)
添加元素,必要时阻塞
-
E take()
移除并返回队头元素,必要时阻塞
-
boolean offer(E e, long timeout, TimeUnit unit)
添加给定元素,如果成功返回true。必要时阻塞,直到元素已经添加或者超时
-
E poll(long timeout, TimeUnit unit)
移除并返回队头元素,必要时阻塞,直到元素可用或者超时用完。失败时返回null
java.util.concurrent. BlockingDeque 接口
-
void putFirst(E e)
-
void putLast(E e)
添加元素,必要时阻塞
-
E takeFirst()
-
E takeLast()
移除并返回队头或队尾元素,必要时阻塞
-
boolean offerFirst(E e, long timeout, TimeUnit unit)
-
boolean offerLast(E e, long timeout, TimeUnit unit)
添加给定元素,如果成功返回true。必要时阻塞,直到元素已经添加或者超时
-
E pollFirst(long timeout, TimeUnit unit)
-
E pollLast(long timeout, TimeUnit unit)
移除并返回队头或队尾元素,必要时阻塞,直到元素可用或者超时用完。失败时返回null
java.util.concurrent. TransferQueue 接口
-
void transfer(E e)
-
boolean tryTransfer(E e, long timeout, TimeUnit unit)
传输一个值,或者尝试在给定的超时时间内传输这个值,这个调用将阻塞,直到另一个线程将元素删除。第二个方法会在调用成功后返回true
高效的映射、集和队列
java.util.concurrent包提供了映射、有序集和队列的高效实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet和ConcurrentLinkedQueue
这些集合使用复杂的算法,通过运行并发地访问数据结构的不同部分尽可能的减少竞争。
这些类的size方法不一定在常量时间内完成操作。确定这些集合的大小需要遍历
集合返回弱一致性的迭代器。这意味着迭代器不一定反映出它们构造一直的所有更改,但是,它们不会将同一个值返回两次,也不会抛出异常。
并发散列映射可以高效地支持大量阅读器和一定数量的书写器。默认情况下认为可以有之多16个同时运行的书写器线程。虽然可以添加,但是如果同一时间大于16个,则其它线程将暂时阻塞。
java.util.concurrent. ConcurrentLinkedDeque 类
-
ConcurrentLinkedDeque()
构造一个可以被多线程安全访问的无上限非阻塞的队列
java.util.concurrent. ConcurrentSkipListSet 类
-
ConcurrentSkipListSet()
-
ConcurrentSkipListSet(Comparator<? super E> comparator)
构造一个可以被多线程安全访问的有序集。第一个构造器要求元素实现Comparable接口
java.util.concurrent. ConcurrentHashMap<K, V> 类
java.util.concurrent. ConcurrentSkipListMap<K, V> 类
-
ConcurrentHashMap()
-
ConcurrentHashMap(int initialCapacity)
-
ConcurrentHashMap(int initialCapacity,float loadFactor, int concurrencyLevel)
构造一个可以被多线程安全访问的散列映射表。默认初始容量为16。如果每个桶的平均负载超过装载因子,表的大小会重新调整。默认值为0.75。并发级别是估计的并发书写器的线程数 -
ConcurrentSkipListMap()
-
ConcurrentSkipListMap(Comparator<? super K> comparator)
构造一个可以被多线程安全访问的有序散列映射表。第一个构造器要求元素实现Comparable接口
映射条目的原子更新
==具体内容在java核心卷一P596==
对并发散列映射的批操作
JavaAPI为并发散列映射提供了批操作,即使有其他线程在处理映射,这些操作也能安全地执行。
批操作会遍历映射,处理遍历过程中找到的元素.这里不会冻结映射的当前快照
除非你恰好知道批操作运行时映射不会被修改,否则就要把结果看作是映射状态的一个近似.
有3种不同的操作:
- search(搜索):为每一个键或值应用一个函数,直到函数生成一个非null的结果。然后搜索终止,返回这个函数的结果.
- reduoe (归约):组合所有键或值,这里要使用所提供的一个累加函数。
- forEach为所有键或值应用一个函数
每个操作都有4个版本:
- operationKeys: 处理键。
- operationValues: 处理值。
- operation: 处理键和值。
- operationEntries:处理Map.Entry对象。
对于上述各个操作,需要指定一个参数化阈值( parallelism threshold)。如果映射包含的元素多于这个阈值,就会并行完成批操作。如果希望批操作在一个线程中运行,可以使用阈值Long.MAX _VALUE。如果希望用尽可能多的线程运行批操作,可以使用阈值1。
search
方法。有以下版本:
U searchKeys(long threshold, BiFunction<? super K, ? extends U> f)
U searchValues(long threshold, BiFunctions? super V, ? extends U> f)
U search(long threshold, BiFunction<? super K, ? super V,? extends U> f)
U searchEntries(long threshold, BiFunction<Map Entry<K, V>, ? extends U> f)
==具体内容在java核心卷一P599==
并发集视图
==具体内容在java核心卷一P600==
写数组的拷贝
CopyOnWriteArrayList 和 CopyOnWriteArraySet是线程安全的集合,其中所有更改器会建立底层数组的一个副本。
如果迭代访问集合的线程数超过更改集合的线程数,这样的安排是很有用的。当构造一个迭代器的时候,它包含当前数组的一个引用。如果这个数组后来被更改了,迭代器仍然引用旧数组,但是,集合的数组已经替换。因而,原来的迭代器可以访问一致的(但可能过时的)视图,而且不存在任何同步开销。
并发数组算法
Arrays类提供了大量并发化操作。一般都是parallel
开头。例如排序parallelSort,添加parallelSetAll
任务和线程池
异步算法
进程
流
从迭代到流
流遵循着做什么而非怎么做
的原则。流表面上看起来和集合很类似,都可以让我们转换和获取数据。但是,它们之间存在着显著的差异:
1、 流并不存储其原始。这些元素可能存储在底层的集合中,或者是按需生成的。
2、 流的操作不会修改其数据源。例如,filter方法不会从流中移除元素,而是生成一个新的流,其中不包含被过滤的元素
3、流的操作是尽可能惰性执行的。这意味着直至需要其结果时,操作才会执行。因此甚至可以操作无限流
流的工作流程包含三个阶段的操作管道:
- 创建一个流
- 指定将初始流转换为其它流的中间操作,可能包含多个步骤
- 应用终止操作,从而产生结果。这个操作会强制执行之前的惰性操作。从此之后,这个流就再也不能用了。
流的创建
在执行流的操作时,我们并没有修改流背后的集合。流并没有收集其数据,数据一直存储在单独的集合中。如果修改了该集合,那么流操作的结果就会变成未定义的。JDK文档将这种要求称为不干涉性
接口/类 | 方法 | 描述 |
---|---|---|
java.util.stream.Stream 接口 | static Stream of(T... t) | 产生一个元素为给定值的流 |
static Stream empty() | 产生一个包含任何元素的流 | |
static Stream generate(Supplier s) | 产生一个无限流,它的值是通过反腐调用函数s而构建的。 | |
static Stream iterate(T seed,UnaryOperator f) | 产生一个无限流,它的元素包含seed、在seed上调用f产生的值、在前一个元素上调用f产生的值,等等。第一个方法产生一个无限流。 | |
static Stream iterate(T seed, Predicate<? super T> predicate, UnaryOperator f) | 该方法的流会在碰到第一个不满足hasNext谓词的元素时终止。9 | |
static Stream ofNullable(T t) | 如果t为null。返回一个空流,否则返回包含t的流 | |
java.util.Spliterators 类 | Spliterator.OfInt spliteratorUnknownSize(PrimitiveIterator.OfInt iterator, int characteristics) | 用特定的特性(一种包含诸如Spliterator.ORDERED之类的常量的位模式)将一个迭代器转换为一个具有未知尺寸的可分割的迭代器 |
java.util.Arrays 类 | static Stream stream(T[] array, int startInclusive, int endExclusive) | 产生一个流,它的元素是由数组中指定范围内的元素构成的 |
java.util.Collection 接口 | default Stream stream(); | |
default Stream parallelStream(); | 产生当前集合中所有元素的顺序流或并行流 | |
java.util.regex.Pattern 类 | Stream splitAsStream(final CharSequence input) | 产生一个流,他的元素是输入中由该模式界定的部分 |
java.nio.file.Files 类 | static Stream lines(Path path) | |
static Stream lines(Path path, Charset cs) | 产生一个流,他的元素是指定文件中的行,该文件的字符集为UTF-8,或者为指定的字符集 | |
java.util.stream.StreamSupport 类 | static Stream stream(Spliterator spliterator, boolean parallel) | 产生一个流,它包含了由给定的可分割迭代器产生的值 |
java.lang.Iterable 类 | Spliterator spliterator() | 为这个迭代器产生一个可分割的迭代器,默认实现不分割也不报告尺寸 |
java.util.Scanner | pulic Stream tokens() 9 | 产生一个字符串流,该字符串流是调用这个扫描器的next方法时返回的 |
Pattern 的splitAsStream方法会按照某个正则表达式来分割一个CharSequence对象。例如使用下面的语句来将一个字符串分割为一个个的单词
Pattern.compile("\\PL+").splitAsStream(s)
Scanner.tokens方法会产生一个扫描器的符号器。另一种从字符串中获取单词流的方式是:
// java 9
List<String> words = new Scanner(s).tokens();
如果持有一个Iterable对象不是集合,那么可以通过以下的调用将其转换为一个流
Stream<Object> stream = StreamSupport.stream(iterable.spliterator(), false);
如果我们持有的是Iterator对象,并且希望得到一个由他的结果构成的流,那么可以使用以下语句
Stream<Object> stream = StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false);
filter、map和flatMap方法
单子论。
类/接口 | 方法 | 描述 |
---|---|---|
java.util.stream.Stream 接口 | Stream filter(Predicate<? super T> predicate); | 产生一个流,它包含当前流中所有满足谓词条件的元素 |
Stream map(Function<? super T, ? extends R> mapper); | 产生一个流程,它包含将mapper应用于当前流中所有元素所产生的结果 | |
Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper); | 产生一个流,它是通过将mapper应用于当前流中所有元素所产生的结果连接到一起而获得的。(注意,这里的每一个结果都是一个流) |
抽取子流和组合流
类/接口 | 方法 | 描述 |
---|---|---|
java.util.stream.Stream 接口 | Stream limit(long maxSize); | 产生一个流,其中包含了当前流中最初的maxSize个元素 |
Stream skip(long n); | 产生一个流,它的元素是当前流中除了前n个元素之外的所有元素 | |
9 | Stream takeWhile(Predicate<? super T> predicate); | 产生一个流,它的元素是当前流中所有满足谓词条件的元素 |
9 | Stream dropWhile(Predicate<? super T> predicate); 9 | 产生一个流,它的元素是当前流中排除不满足谓词条件的元素之外的所有元素。 |
static Stream concat(Stream<? extends T> a, Stream<? extends T> b) | 产生一个流,它的元素是a的元素后面跟着b的元素 |
其他的流转换
类/接口 | 方法 | 描述 |
---|---|---|
java.util.stream.Stream 接口 | Stream distinct(); | 产生一个流,包含当前流中所有不同的元素。为一个元素调用 Object.equals(Object) |
Stream sorted(); | ||
Stream sorted(Comparator<? super T> comparator); | 产生一个流,它的元素是当前流中的所有元素按照顺序排列的。第一个方法要求元素必须是实现了Comparable | |
Stream peek(Consumer<? super T> action); | 产生一个流,它与当前流中的元素相同,在获取其中每个元素是,会将其传递给action |
简单约简
约简是一种终结操作,它会将流约简为可以在程序中使用的非流值。
这些约简操作的方法返回的是一个Optional的值,他要么在其中包装了结果,要么表示没有任何值。
类/接口 | 方法 | 描述 |
---|---|---|
java.util.stream.Stream 接口 | Optional max(Comparator<? super T> comparator); | |
Optional min(Comparator<? super T> comparator); | 分别产生这个流的最大元素和最小元素,使用给定的比较强定义的排序规则,如果这个流为空,会产生一个空的optional对象。终结操作 | |
Optional findFirst(); | ||
Optional findAny(); | 分别产生这个流的第一个元素和任意一个元素,如果这个流为空,会产生一个空的optional对象。终结操作 | |
boolean anyMatch(Predicate<? super T> predicate); | ||
boolean allMatch(Predicate<? super T> predicate); | ||
boolean noneMatch(Predicate<? super T> predicate); | 分别在这个流中任意元素、所有元素和没有任何元素匹配给定谓词条件时返回true。终结操作 |
findFirst返回非空集合中的第一个值。通常在于filter组成使用时很有用。findAny不强调使用第一个匹配,而是使用任意的匹配都可以,这个方法在并行处理流时很有效,因为流可以报告任何它找到的匹配而不是被限制为必须报告第一匹配。
anyMatch方法接受一个断言引元,因此不需要使用filter
Optional类型
Optional对象是一种包装器对象,要么包装类型T的对象,要么没有包装任何对象,对于第一种情况,称为这种值是存在的。Optional类型被称为一种更安全的方式,用来替代类型T的引用,这种引用要么引用某个对象,要么为null。但是,他只有在正确使用的情况下才会更安全。以下几个小结为说明如何正确使用
获取Optional值
通常,在没有任何匹配时,希望使用某种默认值
类/接口 | 方法 | 描述 |
---|---|---|
java.util.Optional 类 | T orElse(T other) | 产生这个optional的值,或者在该optional为空时,产生other |
T orElseGet(Supplier<? extends T> other) | 产生这个optional的值,或者在该optional为空时,产生调用other的结果 | |
T orElseThrow(Supplier<? extends X> exceptionSupplier) | 产生这个optional的值,或者在该optional为空时,抛出调用exceptionSupplier的结果(异常) |
消费optional值
只有在其存在的情况下才消费该值(调用指定的方法)
类/接口 | 方法 | 描述 |
---|---|---|
java.util.Optional 类 | void ifPresent(Consumer<? super T> action) | 如果该optional不为空,就将它的值传递给cation |
9 | void ifPresentOrElse(Consumer<? super T> consumer, Runnable emptyAction) | 如果该optional不为空,就将它的值传递给cation,或者调用emptyAction |
管道化Optional值
保持Optional完整,使用map方法来转换Optional内部的值
类/接口 | 方法 | 描述 |
---|---|---|
java.util.Optional<T> 类 | Optional map(Function<? super T, ? extends U> mapper) | 产生一个Optional,如果当前的option的值存在,那么所产生的option的值是通过,将给定函数应用于当前的option的值而得到的;否则,产生一个空的optional |
Optional filter(Predicate<? super T> predicate) | 产生一个optional,如果当前的optional的值满足给定的谓语条件,那么所产生的optional的值就是当前optional的值;否则,产生一个空的optional | |
9 | Optional or(Supplier<? extends Optional<? extends T>> supplier) | 如果当前optional不为空,则产生当前的optional;否则有supplier产生一个optional |
不适合使用Optional值的方式
正确用法的提示
- Optional类型的变量永远都不应该为null
- 不要使用Optional类型的域。因为其代价是额外多出来一个对象。在类的内部,使用nul表示缺失的域更易于操作
| 类/接口 | 方法 | 描述 |
| ------------------------ | ------------------- | ------------------------------------------------------------ |
| java.util.Optional 类 | T get() | |
| 10 | T orElseThrow() | 产生这个optional的值,或者在该optiona为空时,抛出一个NoSuchElementException异常 |
| | boolean isPresent() | 如果该optional不为空,则返回true |
创建Optional值
类/接口 | 方法 | 描述 |
---|---|---|
java.util.Optional 类 | static Optional of(T value) | |
static Optional ofNullable(T value) | 产生一个具有给定值的Optional。如果value为null,第一个方法抛出一个NullPointerException异常,第二个方法返回一个空的optional | |
static Optional empty() | 产生一个空的optional |
用flatMap构建Optional值的函数
假设有一个产生Optional的函数f,并且目标类型T具有一个可以产生Optional对象的方法g。那就可以调用
Optional<U> result = s.f().flatMap(T::g)
类/接口 | 方法 | 描述 |
---|---|---|
java.util.Optional 类 | Optional flatMap(Function<? super T, Optional> mapper) | 如果optional存在,产生将mapper应用于当前optional值所产生的结果,或者在当前optional为空时,返回一个空optional |
可以直接将flatMap的调用链接起来1,从而构建这些步骤构成的管道,只要所有步骤都成功,该管道才成功。
将Optional转换为流
P17
收集结果
几种方式:
- iterator方法产生访问元素的迭代器
- forEach方法,将函数应用于每个元素。在并行流中,会以任意顺序遍历每个元素。而forEachOrdered方法则按照流中的顺序处理,这会丧失并行处理的优势
- toArray方法返回一个Object数组。因为无法在运行时创建泛型数组。如果向要让数组具有正确的类型,可以将其传递到数组构造器中
- 便捷方法collect,它会接受一个Collector接口的实例。收集器是一种收集众多元素并产生单一结果的对象,Collectors类提供了大量用于生成常见收集器的工厂方法
| 类/接口 | 方法 | 描述 |
| ----------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| java.util.stream.BaseStream 接口 | Iterator iterator() | 产生一个用于获取当前流中各个元素的迭代器。这是一种终结操作 |
| java.util.stream.Stream 接口 | void forEach(Consumer<? super T> action); | |
| | void forEach(Consumer<? super T> action) | 在流的每个元素上调用action,第一个方法按照任意顺序,第二个方法按照流中的顺序。这是一种终结操作 |
| | Object[] toArray(); | |
| | A[] toArray(IntFunction<A[]> generator); | 产生一个对象数组,或者在将引用A[]::new 传递给构造器时,返回一个A类型的数组。这是一种终结操作 |
| | <R, A> R collect(Collector<? super T, A, R> collector); | 使用给定的1收集器来收集当前流中的元素。Collectors类有用于多种收集器的工厂方法 |
| java.util.stream.Collectors 类 | static Collector<T, ?, List> toList() | |
| 10 | static Collector<T, ?, List> toUnmodifiableList() | |
| | static Collector<T, ?, Set> toSet() | |
| 10 | static Collector<T, ?, Set> toUnmodifiableSet() | 产生一个将元素收集到列表或集合中的收集器 |
| | static <T, C extends Collection> Collector<T, ?, C> toCollection(Supplier collectionFactory) | 产生一个将元素收集到任意集合中的收集器。可以传递一个诸如TreeSet::new 的构造器引用 |
| | static Collector<CharSequence, ?, String> joining() | |
| | static Collector<CharSequence, ?, String> joining(CharSequence delimiter) | |
| | static Collector<CharSequence, ?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) | 产生一个连接字符串的收集器。分隔符会置于字符串之间,而第一个字符串之前可以有前缀,最后一个字符串之后可以有后缀。如果没有指定,那么它们都为空 |
| | static Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper) | |
| | static Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper) | |
| | static Collector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper) | 产生能够生成(Int|Long|Double)SummaryStatistics对象的收集器,通过它们可以获得将mapper应用于每个元素后所产生的结果的数量、总和、平均值、最大值和最小值 |
| (Int|Long|Double)SummaryStatistics 类 | long getCount() | 产生汇总后的元素的个数 |
| | (Int|Long|Double)getSum() | |
| | double getAverage() | 产生汇总后元素的总和或平均值,或者在没有任何元素时返回0 |
| | (Int|Long|Double) getMin() | |
| | (Int|Long|Double) getMax() | 产生汇总后的最大值和最小值,或者在没有任何元素时,返回(Int|Long|Double).(MAX|MIN)_VALUE |
收集到映射表中
类 | 方法 | 描述 |
---|---|---|
java.util.stream.Collectors 类 | static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper) | |
static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,BinaryOperator mergeFunction) | ||
static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,BinaryOperator mergeFunction, Supplier mapSupplier) | ||
10 | static <T, K, U> Collector<T, ?, Map<K,U>> toUnmodifiableMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) | |
10 | static <T, K, U> Collector<T, ?, Map<K,U>> toUnmodifiableMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper,BinaryOperator mergeFunction) | 产生一个收集器,它会产生一个映射表、不可修改的映射表或并发映射表。 |
static <T, K, U> Collector<T, ?, ConcurrentMap<K,U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) | keyMapper和valueMapper函数会应用于每个收集到的元素上,从而在所产生的映射表中生成键/值项。 | |
static <T, K, U> Collector<T, ?, ConcurrentMap<K,U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator mergeFunction) | 在默认情况下,当两个元素所产生相同的键时,会抛出一个IllegalStateException异常。但可以提供一个mergeFunction来合并具有相同键的值 | |
static <T, K, U, M extends ConcurrentMap<K, U>> Collector<T, ?, M> toConcurrentMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator mergeFunction,Supplier mapSupplier) | 默认情况下,其结果时一个HashMap或ConcurrentHashMap。但可以提供一个mapSupplier,它会产生所期望的映射表实例(例如Tree Map::new) |
群组和分区
如果调用groupingByConcurrent方法,就会在使用并行流时获得一个被并行流组装的并行映射表。这与toConcurrentMap方法完全类似
类 | 方法 | 描述 |
---|---|---|
java.util.stream.Collectors 类 | static <T, K> Collector<T, ?, Map<K, List>> groupingBy(Function<? super T, ? extends K> classifier) ; | |
static <T, K> Collector<T, ?, ConcurrentMap<K, List>> groupingByConcurrent(Function<? super T, ? extends K> classifier) ; | 产生一个收集器,它会产生一个映射表,其键时将classifier应用于所有收集到的元素上所产生的结果,而值是由具有相同键的元素构成的一个个列表 | |
static Collector<T, ?, Map<Boolean, List>> partitioningBy(Predicate<? super T> predicate) ; | 产生一个收集器,它会产生有一个映射表,其键是true/false,而值是满足/不满足断言的元素构成的列表 | |
下游收集器 | static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) | 产生一个收集器,该收集器会产生一个映射表,其中的键是将classifier应用到所有收集到的元素上之后产生的结果,而值时使用下游收集器收集所具有相同的键的元素所产生的结果 |
static Collector<T, ?, Long> counting() | 产生一个可以对收集到的元素进行计数的收集器 | |
static Collector<T, ?, Integer> summingInt(ToIntFunction<? super T> mapper) | ||
static Collector<T, ?, Long> summingLong(ToLongFunction<? super T> mapper) | ||
static Collector<T, ?, Double> summingDouble(ToDoubleFunction<? super T> mapper | 产生一个收集器,对将mapper应用到收集到的元素上之后产生的结果计算总和 | |
static Collector<T, ?, Optional> minBy(Comparator<? super T> comparator) | ||
static Collector<T, ?, Optional> maxBy(Comparator<? super T> comparator) | 产生一个收集器,使用comparator指定的排序方法,计算收集到的元素中的最大值和最小值 | |
static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream, Function<R,RR> finisher) | 产生一个收集器,它会将元素发送到下游收集器中,然后将finisher函数应用到其结果上 | |
static <T, U, A, R> Collector<T, ?, R> mapping(Function<? super T, ? extends U> mapper,Collector<? super U, A, R> downstream) | 产生一个收集器,它会在每个元素上调用mapper,并将结果发送到下游收集器中 | |
static Collector<T, ?, IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper) | ||
static Collector<T, ?, LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper) | ||
static Collector<T, ?, DoubleSummaryStatistics> summarizingDouble(ToDoubleFunction<? super T> mapper) | 产生一个收集器,对将mapper应用到收集到的元素上之后产生的每个组结果的总和、数量、平均值、最小值和最大值 |
将收集器组合起来是一种强大的方法、但是它也可能会导致产生非常复杂的表达式。最佳用法是于groupingBy和partitioningBy一起处理“下游的”映射表中的值。否则,应用直接在流上应用诸如map,reduce,count,max,min等方法
简约操作
reduce方法是一种用于从流中计算某个值的通用机制,其最简单的形式将接受一个二元函数,并从前开始持续应用它
类/接口 | 方法 | 描述 |
---|---|---|
java.util.stream.Stream 接口 | Optionalreduce(BinaryOperator accumulator); | 用给定的accumulator函数产生流中元素的累计总和。 |
T reduce(T identity, BinaryOperator accumulator) | 如果提供了幺元,那么第一个被累计的元素就是该幺元(例如0)。 | |
U reduce(U identity, BiFunction<U, ? super T, U> accumulator,BinaryOperator combiner) | 如果提供了组合器,那么它会用来将分别累计的各个部分整合成总和 | |
R collect(Supplier supplier,BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner) | 将元素收集到类型R的结果中。在每个部分上,都会调用supplier来提供初始结果,调用accumulator来交替地将元素添加到结果中,并调用combiner来整合两个结果 |
有时reduce会不够通用。例如要收集Bitset中的结果。如果收集操作时并行的,那么就不能直接将元素放到单个bitset中,因为bitset对象不是线程安全的。因此每个部分都需要以其自己的空集开始,并且reduce只能让我们提供一个幺元值。因此,应该使用collect,它会接受单个引元:
1、 一个提供者,它会创建目标对象的新实例。例如散列表的构造器
2、 一个累积器,它会将一个元素添加到该目标上。例如add方法
3、 一个组合器,它会将两个对象合并成一个,例如addAll
例子:Bitset result = stream.collect(BitSet::new, BitSet::set, BitSet::or)
基本类型流
对于基本类型double、float、long、short、char、byte、boolean。流库中具有专门的类型IntStream、LongStream、DoubleStream,用来直接存储基本类型数值,而无须使用包装器。如果要存储short、char、byte和boolean,可以使用IntStream;而对于float,可以使用DoubleStream。
通常,基本类型流上的方法于对象流上的方法类似。主要差异为:
- toArray方法会返回基本类型数组
- 产生可选结果的方法会返回OptionalInt、OptionalLong或OptionalDouble。这些类于Optional类类似,但是具有getAsInt、getAsLong和getAsDouble方法,而不是get方法
- 具有分别返回总和、平均值、最大值和最小值的方法。对象类没有
- summaryStatistics方法会产生一个类型Int|Double|LongSummarStatistics的对象,它们同时报告流的总和、数量、平均值、最大值和最小值。
Random类具有ints、longs和doubles方法,它们会返回由随机数构成的基本类型路。如果需要的时并行流中的随机数,就要使用SplittableRandom类
类/接口 | 方法 | 描述 |
---|---|---|
java.util.stream.IntStream 接口 | static IntStream range(int startInclusive, int endExclusive) | |
static IntStream rangeClosed(int startInclusive, int endInclusive) | 产生一个由给定范围内的整数构成的IntStream | |
static IntStream of(int... values) | 产生一个由给定元素构成的IntStream | |
int[] toArray() | 产生一个当前流中的元素构成的数组 | |
int sum(); | ||
OptionalDouble average(); | ||
OptionalInt max(); | ||
OptionalInt min(); | ||
IntSummaryStatistics summaryStatistics(); | 产生当前流中元素的总和、平均值、最大值、最小值,或者可以获取所有这四个值的对象 | |
Stream boxed(); | 产生用于当前流中的元素的包装器对象流 | |
java.util.stream.LongStream 接口 | static LongStream range(long startInclusive, long endExclusive) | |
static LongStream rangeClosed(long startInclusive, long endExclusive) | 产生一个由给定范围内的整数构成的LongStream | |
static LongStream of(long ... values) | 产生一个由给定元素构成的LongStream | |
long [] toArray() | 产生一个当前流中的元素构成的数组 | |
int sum(); | ||
OptionalDouble average(); | ||
OptionalLong max(); | ||
OptionalLong min(); | ||
LongSummaryStatistics summaryStatistics(); | 产生当前流中元素的总和、平均值、最大值、最小值,或者可以获取所有这四个值的对象 | |
Stream boxed(); | 产生用于当前流中的元素的包装器对象流 | |
java.util.stream.DoubleStream 接口 | static DoubleStreamof(double... values) | 产生一个由给定元素构成的DoubleStreamof |
double[] toArray() | 产生一个当前流中的元素构成的数组 | |
doublesum(); | ||
OptionalDouble average(); | ||
OptionalDouble max(); | ||
OptionalDouble min(); | ||
DoubleSummaryStatistics summaryStatistics() | 产生当前流中元素的总和、平均值、最大值、最小值,或者可以获取所有这四个值的对象 | |
Stream boxed(); | 产生用于当前流中的元素的包装器对象流 | |
java.lang.CharSequence | IntStream codePoints() | 产生由当前字符串的所有Unicode码点构成的流 |
java.util.Random | IntStream ints() | |
IntStream ints(long streamSize) | ||
IntStream ints(int randomNumberOrigin, int randomNumberBound) | ||
IntStream ints(long streamSize, int randomNumberOrigin,int randomNumberBound) | ||
LongStream longs() | ||
LongStream longs(long streamSize) | ||
LongStream longs(long randomNumberOrigin, long randomNumberBound) | ||
LongStream longs(long streamSize, long randomNumberOrigin,long randomNumberBound) | ||
DoubleStream doubles() | ||
DoubleStream doubles(long streamSize) | ||
DoubleStream doubles(double randomNumberOrigin, double randomNumberBound) | ||
DoubleStream doubles(long streamSize, double randomNumberOrigin,double randomNumberBound) | 产生随机数流。如果提供了streamSize,这个流就是具有给定数量元素的有限流。当提供了边界,其元素将位于randomNumberOrigin(包含)和randomNumberBound(不包含)的区间内。 | |
java.util.Optional(Int|Duble|Long) 类 | static Optional(Int|Duble|Long) of((int|duble|long) value) | 用所提供的基本类型产生一个可选对象 |
(Int|Duble|Long) getAs(Int|Duble|Long)(); | 产生当前可选对象的值,或者在其为空时抛出一个NoSuchElementException异常 | |
(Int|Duble|Long) orElse((Int|Duble|Long) other); | ||
(Int|Duble|Long) orElseGet((int|duble|long)Supplier other); | 产生当前可选值,或者在这个为空时产生可代替的值 | |
void ifPresent((Int|Duble|Long)Consumer consumer) | 如果当前可以选对象不为空,则将其值传递给consumer | |
java.util.(Int|Duble|Long)SummarStatistics 类 | long getCout(); | |
(int|duble|long) getSum(); | ||
double getAverage(); | ||
(int|duble|long) getMax(); | ||
(int|duble|long) getMin(); | 产生收集到的元素的数量、总和、平均值、最大值和最小值 |
并行流
使用并行流的要求
- 并行化会导致大量的开销,只有面对非常大的数据集才划算
- 只有在底层的数据源可以被有效地分割为多个部分时,将流并行化才有意义
- 并行流使用的线程池可以会因诸如文件I/O或网络访问这样的操作被阻塞而饿死。只有面对海量的内存数据和运算密集处理,并行流才会工作最佳
接口 | 方法 | 描述 |
---|---|---|
java.util.stream.BaseStream<T, S extends BaseStream<T, S>> | S parallel(); | 产生一个于当前流中元素相同的并行流 |
S unordered(); | 产生一个于当前流中元素相同的无序流 | |
java.util.Collection | Stream parallelStream() | 用当前集合中的元素产生一个并行流 |
输入与输出
输入/输出流
读写二进制数据
对象输入/输出流与序列化
在面对重复的对象时。例如网络传输时,不能去保持和恢复特定对象的内存地址,因为当对象被重新加载时,它可能占据的时与原来完全不同的内存地址。
与此不同的是,每个对象都是一个序列号保存的,这就是这种机制之所以被称为对象序列化的原因。下面是其算法:
- 当遇到的每一个对象引用都关联一个序列号
- 对于每个对象,但第一次遇到时,保存其对象数据到输出中。
- 如果某个对象之前已经被保存过,那么只写出“与之前保存过的序列号为x的对象相同”
在读回对象时,整个过程时反过来的
- 对于对象输入流中的对象,在第一次遇到其序列号时,构建它,并使用流中数据来初始化它,然后记录这个顺序号和新对象直接的关联
- 当遇到“与之前保存过的序列号为x的对象相同”这一标记时,获取与这个序列号相关联的对象引用
操作文件
Path
表示的是一个目录名序列,其后还可以跟着一个文件名。路径中的第一个部件可以是根路径
类/接口 | 方法 | 描述 |
---|---|---|
java.nio.file.Paths 类 | static Path get(String first, String... more) | 通过连接给定的字符串创建一个路径 |
java.nio.file.Path 接口 | Path resolve(Path other); | |
Path resolve(String other); | 如果other是绝对路径,那么就返回other;否则,返回通过连接this和other获得的路径。q = p.resolve(r) | |
Path resolveSibling(Path other); | ||
Path resolveSibling(String other); | 如果other是绝对路径,那么就返回other,否则,返回通过连接this的父路径和other获得的路径。 | |
Path relativize(Path other); | 返回用this进行解析,相对于other的相对路径. r = p.relativize(q) | |
Path normalize(); | 移除诸如. 和.. 等冗余的路径元素 | |
Path toAbsolutePath(); | 返回与该路径等价的绝对路径 | |
Path getParent(); | 返回父路径,或者在该路径没有父路径时,返回null | |
Path getFileName(); | 返回该路径的最后一个部件,或者在该路径没有任何部件时,返回null | |
Path getRoot(); | 返回该路径的根部分,或者在该路径没有任何部件时,返回null | |
File toFile(); | 从该路径中创建一个file对象 | |
java.io.File 类 | Path toPath() | 从该文件中创建一个Path对象 |
读写文件
Files类可以使得普通文件操作变得快捷。但只适用处理中等长度的文本文件。如果要处理的文件长度比较大,或者是二进制文件,还是使用输入/输出流或者读入器/写出器
类 | 方法 | 描述 |
---|---|---|
java.nio.file.Files 类 | static byte[] readAllBytes(Path path) | |
static List readAllLines(Path path, Charset cs) | ||
static List readAllLines(Path path) | 读入文件的内容 | |
static Path write(Path path, byte[] bytes, OpenOption... options) | ||
static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs, OpenOption... options) | ||
static Path write(Path path,Iterable<? extends CharSequence> lines, OpenOption... options) | 将给定内容写出到文件中,并返回path | |
static BufferedWriter newBufferedWriter(Path path, Charset cs, OpenOption... options) | ||
static BufferedReader newBufferedReader(Path path, Charset cs) | ||
static InputStream newInputStream(Path path, OpenOption... options) | ||
static OutputStream newOutputStream(Path path, OpenOption... options) | 打开一个文件,用于读入或写出 |
创建目录
类 | 方法 | 描述 |
---|---|---|
java.nio.file.Files 类 | static Path createFile(Path path, FileAttribute<?>... attrs) | |
static Path createDirectory(Path dir, FileAttribute<?>... attrs) | ||
static Path createDirectories(Path dir, FileAttribute<?>... attrs) | 创建一个文件或目录,createDirectories方法还会创建路径中所有的中间目录 | |
static Path createTempFile(Path dir, String prefix, String suffix, FileAttribute<?>... attrs) | ||
static Path createTempFile(String prefix, String suffix, FileAttribute<?>... attrs) | ||
static Path createTempDirectory(Path dir,String prefix, FileAttribute<?>... attrs) | ||
static Path createTempDirectory(String prefix, FileAttribute<?>... attrs) | 在适合临时文件的位置,或者在给定的父目录中,创建一个临时文件或目录。返回所有创建的文件或目录的路径 |
复制或移动目录
类 | 方法 | 描述 |
---|---|---|
java.nio.file.Files 类 | static Path copy(Path source, Path target, CopyOption... options) | |
static Path move(Path source, Path target, CopyOption... options) | 将source复制或移动到给定位置,并返回target | |
static long copy(InputStream source, Path target, CopyOption... options) | ||
static long copy(Path source, OutputStream target) | 从输入流复制到文件中,或者从文件复制到输出流中,返回复制的字节数 | |
static void delete(Path path) | ||
static boolean deleteIfExists(Path path) | 删除给定文件或空目录。第一个方法在文件或目录不存在情况下抛出异常,而第二个方法在这种情况下返回false |
对文件操作而言可用的选项
枚举 | 选项 | 描述 |
---|---|---|
StandardOpenOption | READ | 用于读取而打开 |
该枚举与newBufferedWriter、newInputStream、newOutputStream、write一起使用 | WRITE | 用于写入而打卡 |
APPEND | 如果用于写入而打开,那么在文件末尾追加 | |
TRUNCATE_EXISTING | 如果用于写入而打开,那么移除已有内容 | |
CREATE | 自动在文件不存在的情况下创建新文件 | |
CREATE_NEW | 创建新文件并且在文件已存在的情况下会创建失败 | |
DELETE_ON_CLOSE | 当文件被关闭时,尽“可能”地删除该文件 | |
SPARSE | 给文件系统一个提示,表示该文件时稀疏的 | |
SYNC | ||
DSYNC | 要求对文件数据|数据和元数据的每次更新都必须同步写入到存储设备中 | |
StandardCopyOption | REPLACE_EXISTING | 原子性地移动文件 |
与copy和move一起使用 | COPY_ATTRIBUTES | 复制文件的属性 |
ATOMIC_MOVE | 如果目标已存在,则替换它 | |
LinkOption :与上面所有方法以及exists、isDirectory、isRegularFile等一起使用 | NOFOLLOW_LINKS | 不要跟踪符号链接 |
ExtendedCopyOption:与find、walk、walkFileTree一起使用 | INTERRUPTIBLE | 跟踪符号链接 |
获取文件信息
getOwner方法将文件的拥有者作为java.nio.file.attribute.UserPrincipal的一个实例返回。所有的文件系统都会报告一个基本属性集,它们被封装在java.nio.file.attribute.BasicFileAttributes接口中,这些属性与上述信息由部分重叠,基本文件属性包括:
- 创建文件、最后移除访问以及最后一次修改文件时间,这些时间都表示成java.nio.file.attribute.FileTime。
- 文件时常规文件、目录还是符号链接,抑或是三者都不是
- 文件尺寸
- 文件主键,这是某种类的对象,具体所属类与文件系统相关,有可能时文件的唯一标识符,也可能不是
要获取这些属性,可以调用“
BasicFileAttributes attr = Files.readAttributes(path, BasicFileAttributes.class);
类 | 方法 | 描述 |
---|---|---|
java.nio.file.Files 类 | static boolean exists(Path path, LinkOption... options) | |
static boolean isHidden(Path path) | ||
static boolean isReadable(Path path) | ||
static boolean isWritable(Path path) | ||
static boolean isExecutable(Path path) | ||
static boolean isRegularFile(Path path, LinkOption... options) | ||
static boolean isDirectory(Path path, LinkOption... options) | ||
static boolean isSymbolicLink(Path path) | 检查由路径指定的文件的给定属性 | |
static long size(Path path) | 获取文件按字节数度量的尺寸 | |
static A readAttributes(Path path, Class type,LinkOption... options) | 读取类型为A的文件属性 | |
java.nio.file.attribute.BasicFileAttributes接口 | FileTime creationTime(); | |
FileTime lastAccessTime(); | ||
FileTime lastModifiedTime(); | ||
boolean isRegularFile(); | ||
boolean isDirectory(); | ||
boolean isSymbolicLink(); | ||
boolean isOther(); | ||
long size(); | ||
Object fileKey(); | 获取所请求的属性 |
读取目录中的项
静态的Files.list方法会返回一个可以被读取目录中各个项的Stream对象。目录时被惰性读取的,这使得处理具有大量项的目录可以变得更高效
但list方法不会进入子目录。为了处理目录中所有的子目录,需要使用Files.walk方法,这个方法在只要遍历到的项是目录,那么在继续访问它的兄弟项之前,会先进入它。
如果要限制访问数的深度,可以调用Files.walk(pathToRoot, depth)。这个可变长参数只可提供一个选项
需要注意的是,该方法无法很容易地来删除目录树。因为必须在删除父目录之前先删除子目录。
使用目录流
当需要对遍历过程进行更加细粒度的控制时,应该使用Files.newDirectoryStream对象,它会产生一个DirectoryStream。注意,他不是流的子接口,而是专门用于目录遍历的接口,它是Iterable的子接口,因此可以在增强for循环中使用目录流。
在遍历时,可以使用glob模式来过滤文件:
模式 | 描述 | 实例 |
---|---|---|
* | 匹配路径组成部分中0个或多个字符 | *.java匹配当前目录中所有java文件 |
** | 匹配跨目录边界的0个或多个字符 | **.java匹配在所有子目录中的java文件 |
? | 匹配一个字符 | ????.java匹配所有四个字符的java文件(不包括扩展名) |
[...] | 匹配一个字符集合,可以使用连线符[0-9]或取反符[!0-9] | Test[0-9A-F].java匹配Testx.jvar, 其中x是一个16进制数字 |
{...} | 匹配由逗号隔开的多个可选项之一 | *.{java, class}匹配所有的java文件和类文件 |
\ | 转义上述任意默认的字符以及\字符 | *\**匹配所有文件名中包含*的文件 |
==tips:如果使用windows的glob语句,则必须对反斜杠转义两次:一次为glob语法转义,一次为java字符串转义.例如Files.newDirectoryStream(dir,"C:\\\\")==
如果要访问某个目录的所有子孙成员,可以转而调用walkFileTree方法,并向其传递一个FileVisitor类型的对象,这个对象会得到下列通知:
- 在遇到一个文件或目录时:FileVisitResult visitFile(T file, BasicFileAttributes attrs)
- 在一个目录被处理前:FileVisitResult preVisitDirectory(T dir, BasicFileAttributes attrs)
- 在一个目录被处理后:FileVisitResult postVisitDirectory(T dir, IOException exc)
- 在试图访问文件或目录是发生错误,例如没有权限打开目录:FileVisitResult visitFileFailed(T file, IOException exc)
对于上述每种情况,都可以指定是否希望执行下面的操作:
- 继续访问下一个文件:FileVisitResult.CONTINUE
- 继续访问,但是不再访问这个目录下的任何项了:FileVisitResult.SKIP_SUBTREE
- 继续访问,但是不再访问这个文件的兄弟目录(和该文件在同一个目录下的文件)了:FileVisitResult.SKIP_SIBLINGS
- 终止访问:FileVisitResult.TERMINATE
当有任何方法抛出异常是,就会终止访问,而这个异常会从walkFileTree方法中抛出
可以使用便捷类SimpleFileVisitor,但是其除了visitFileFailed方法之外的所有方法并不做任何处理而是直接继续访问,而visitFileFailed方法会抛出由失败导致的异常,并进而终止访问。
内存映射文件
文件加锁机制
正则表达式
卷2,p109
XML
网络
获取Web数据
数据库编程
日期和时间API
时间线
Java的Date和Time Api规范要求java使用的时间尺度为:
- 每天86400秒
- 每天正午与官方时间精确pip
- 在其他时间上,以精确定义的方式与官方时间接近匹配
在java中,==Instant表示时间线上的某个点==。被称为“新纪元”的时间线原点被设置为穿过伦敦格林尼治皇家天文台的本初子午线所在时区的1970年1月1日的午夜。从该原点开始,时间按照每天86400秒向前或向回度量,精确到纳米。
静态方法调用Instant.now()会给出当前时刻。可以按照常用的方式,用equals和compareTo方法来比较两个Instant对象,因此可以将Instant对象用作时间戳
Duration是两个时刻之间的时间量。可以通过调用其方法获得Duration按照传统单位度量的时间长度
类 | 方法 | 描述 |
---|---|---|
java.time.Instant | static Instant now() | 从最佳的可用系统时钟中获取当前的时刻 |
Instant plus(TemporalAmount amountToAdd) | ||
Instant minus(TemporalAmount amountToSubtract) | 产生一个时刻,该时刻与当前时刻距离给定的时间量。Duration和Period实现了TemporalAmount 接口 | |
Instant (plus|minus)(Nanos|Millis|Seconds)(long number) | 产生一个时刻,该时刻与当前时间距离给定数量的纳秒、毫秒和秒 | |
java.time.Duration | static Duration of(Nanos|Millis|Seconds|Minutes|Hours|Days)(long number) | 产生一个给定数量的指定时间单位的时间间隔 |
static Duration between(Temporal startInclusive, Temporal endExclusive) | 产生一个在给定时间点之间的Duration对象。Instant实现了Temporal接口,LocalDate/LocalDateTime/LocalTIme和ZonedDateTime也实现了 | |
long toNanos() | ||
long toMillis() | ||
long getSeconds() | long toSeconds() 在9中新增 | |
long toMinutes() | ||
long toHours() | ||
long toDays() | 获取当前时长按照方法中的时间单位度量的数量 | |
int to(Nanos|Millis|Seconds|Minutes|Hours) Part | ||
long to(Days|Hours|Minutes|Seconds|Millis|Nanos)Part | 当前时长中给定时间单位的不错。例如,在一百秒的时间间隔中,分钟的部分是1,秒的部分是40 | |
Instant puls(TemporalAmount amountToAdd) | ||
Instant minus(TemporalAmount amountToSubtract) | 产生一个时刻,该时刻与当前时刻距离给定的时间量。 | |
Duration multipliedBy(long multiplicand) | ||
Duration dividedBy(long divisor) | ||
Duration negated() | 产生一个时长,该时长是通过当前时刻乘以或除以给定的量或-1得到的 | |
boolean isZero() | ||
boolean isNegative(); | 如果当前Duration对象是0或负数,则返回true | |
Duration (plus|minus)(Nanos|Millis|Seconds|Minutes|Hours|Days)(long number) | 产生一个时长,该时长是通过时刻加上或减去给定的数量的指定时间单位得到的 |
Instant和Duration类都是不可修改的类型。内部方法都是返回新实例
本地日期
两个Instant之间的时长时Duration,而用于本地日期的等价物是Period,他表示的是流逝的年、月或日的数量
类 | 方法 | 描述 |
---|---|---|
java.time.LocalDate | static LocalDate now() | 获取当前的LocalDate |
static LocalDate of(int year, int month, int dayOfMonth) | ||
static LocalDate of(int year, Month month, int dayOfMonth) | 用给定的年、月(1到12之间的整数或Month枚举)和日(1到31之间)产生一个本地日期 | |
LocalDate (plus|minus)(Day|Weeks|Months|Years)(long number) | 产生一个LocalDate,该对象是通过在当前对象上加上或减去给定数量的时间单位获得的 | |
LocalDate plus(TemporalAmount amountToAdd) | ||
LocalDate minus(TemporalAmount amountToSubtract) | 产生一个本地日期,该对象与当前对象距离给定的时间量。使用Period | |
LocalDate withDayOfYear(int dayOfYear) | ||
LocalDate withDayOfMonth(int dayOfMonth) | ||
LocalDate withMonth(int month) | ||
LocalDate withYear(int year) | 返回一个新的对象,将年日期、月份日期、月或年修改为给定值 | |
int getDayOfMonth() | 获取月份日期(0到31之间) | |
int getDayOfYear() | 获取年日期(0到366之间) | |
DayOfWeek getDayOfWeek() | 获取星期日期,返回某个DayOfWeek枚举值 | |
Month getMonth() | ||
int getMonthValue() | 获取用Month枚举值表示的月份,或者用1到12之间的数字表示的月份 | |
int getYear() | 获取年份,在-999999999到999999999之间 | |
Period until(ChronoLocalDate endDateExclusive) | 获得直到给定终止日期的period。LocalDate和Date类针对非公历实现了ChronoLocalDate接口 | |
boolean isBefore(ChronoLocalDate other) | ||
boolean isAfter(ChronoLocalDate other) | 如果该日期在给定日期的之前或之后。返回true | |
boolean isEqual(ChronoLocalDate other) | 如果该日期与给定日期相同,返回true | |
boolean isLeapYear() | 如果当前是闰年,则返回true。即能被4整除,但是不能被100整除,或者能够被400整除 | |
9 | Stream datesUntil(LocalDate endExclusive) | |
9 | Stream datesUntil(LocalDate endExclusive, Period step) | 产生一个日期流,从当前的LocalDate对象直到参数endExclusive指定得到日期,其中步长为1,或给定的step |
java.time.Period | static Period of(int years, int months, int days) | |
static Period of(Day|Weeks|Months|Years)(long number) | 用给定数量的时间单位产生一个Period对象 | |
int get(Day|Months|Years)() | 获取当前Period对象的日、月或年 | |
Period (plus|minus)(Day|Months|Years)(long number) | 产生一个Period,该对象是通过在当前对象加上或减去给定数量的时间单位获得的 | |
Period plus(TemporalAmount amountToAdd) | ||
Period minus(TemporalAmount amountToSubtract) | 产生一个时刻,该时刻与当前时刻距离给定的时间量 | |
Period with(Day|Months|Years)(int number) | 返回一个新的Period, 将日、月、年修改为给定值 |
日期调整器
在TemporalAdjusters类中提供了大量用于常见调整的静态方法。
// 当天日期,假如为星期三
LocalDate now = LocalDate.now();
// 获取该日期下个星期三的日期
LocalDate next = now.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY));
// 获取该日期下个星期三的日期,如果当天为星期三,则返回当天日期
LocalDate nextOrSame = now.with(TemporalAdjusters.nextOrSame(DayOfWeek.WEDNESDAY));
// 获取该日期上个星期三的日期
LocalDate previous = now.with(TemporalAdjusters.previous(DayOfWeek.WEDNESDAY));
// 获取该日期上个星期三的日期,如果当天为星期三,则返回当天日期
LocalDate previousOrSame = now.with(TemporalAdjusters.previousOrSame(DayOfWeek.WEDNESDAY));
// 指定获取该日期月份第几个星期几的日期
LocalDate dayOfWeekInMonth = now.with(TemporalAdjusters.dayOfWeekInMonth(1, DayOfWeek.MONDAY));
// 指定获取该日期月份第一个星期几的日期
LocalDate firstInMonth = now.with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
// 指定获取该日期月份最后一个星期几的日期
LocalDate lastInMonth = now.with(TemporalAdjusters.lastInMonth(DayOfWeek.MONDAY));
// 获取该日期年份第一天的日期
LocalDate firstDayOfYear = now.with(TemporalAdjusters.firstDayOfYear());
// 获取该日期月份第一天的日期
LocalDate firstDayOfMonth = now.with(TemporalAdjusters.firstDayOfMonth());
// 获取该日期年份最后一天的日期
LocalDate lastDayOfYear = now.with(TemporalAdjusters.lastDayOfYear());
// 获取该日期月份最后一天的日期
LocalDate lastDayOfMonth = now.with(TemporalAdjusters.lastDayOfMonth());
// 获取该日期下一年第一天的日期
LocalDate firstDayOfNextYear = now.with(TemporalAdjusters.firstDayOfNextYear());
// 获取该日期下一月第一天的日期
LocalDate firstDayOfNextMonth = now.with(TemporalAdjusters.firstDayOfNextMonth());
还可以通过TemporalAdjuster接口来创建自己的调整器。例如下面的nextWorkday1()。==注意:==lambda表达式的参数为Temporal,他必须被强制转换为LocalDate,可以使用TemporalAdjusters.ofDateAdjuster
方法来避免强制转换,该方法得到的参数是类型为UnaryOperator<LocalDate>
的lambda表达式
TemporalAdjuster nextWorkday1 = w -> {
LocalDate result = (LocalDate) w;
do{
result = result.plusDays(1);
}while (result.getDayOfWeek().getValue() >= 6);
return result;
};
TemporalAdjuster nextWorkday2 = TemporalAdjusters.ofDateAdjuster(w -> {
LocalDate result = w;
do{
result = result.plusDays(1);
}while (result.getDayOfWeek().getValue() >= 6);
return result;
});
本地时间
LocalTime表示当日时刻。而且它并不关心AM/PM。相关的问题在格式化和解析中解决
还有一个表示日期和时间的LocalDateTime类。这个类时候存储固定时区的时间点。但是,如果在计算中需要跨越夏令时,或者需要处理不同时区的用户,那么就应该使用ZonedDateTime类
类 | 方法 | 描述 |
---|---|---|
java.time.LocalTime | static LocalTime now() | 获取当前的LocalTime |
static LocalTime of(int hour, int minute) | ||
static LocalTime of(int hour, int minute, int second) | ||
static LocalTime of(int hour, int minute, int second, int nanoOfSecond) | 用给定的小时(0到23之间)、分钟、秒(0到59之间)和纳秒(0到999.999.99之间)产生一个LocalTime | |
LocalTime (plus|minus)(Hour|Minutes|Seconds|Nanos)(long number) | 产生一个LocalTime,该对象是通过在当前对象上加上或减去给定数量的时间单位获得的 | |
LocalTime plus(TemporalAmount amountToAdd) | ||
LocalTime minus(TemporalAmount amountToSubtract) | 产生一个本地时间,该对象与当前对象距离给定的时间量。使用Period | |
LocalTime with(Hour|Minute|Second|Nano)(int value) | 返回一个新的LocalTime ,将小时、分钟、秒或纳秒修改为指定的值 | |
int getHour() | 获取小时(0到23之间) | |
int getMinute() | ||
int getSecond() | 获取分钟或秒(0到59之间) | |
int getNano() | 获取纳秒,在-999999999到999999999之间 | |
boolean isBefore(ChronoLocalDate other) | ||
boolean isAfter(ChronoLocalDate other) | 如果该时刻在给定时刻的之前或之后。返回true | |
int toSecondOfDay() | ||
long toNanoOfDay() | 产生自午夜到当前LocalTime的秒或纳秒 |
时区时间
每一个时区都有一个ID,调用ZoneId.getAvailableZoneIds可以获取所有时区的ID。
给定一个时区的ID,静态方法ZoneId.of(id)可以产生一个ZoneId对象。可以通过localDateTime.atZone(zoneId)将一个LocalDateTIme对象转换为ZonedDateTime对象,或者通过调用静态方法ZonedDateTime.of(year..., zoneId)来构造一个ZonedDateTime对象。
时区时间的方法都很直观,但是在遇到夏令时就会变的复杂。
当夏令时开始时,时钟会向前拨快一小时。例如2012年,中欧地区在3月31日2:00切换到夏令时,如果试图构建3月31日2:30,实际上会获得3:30.
反之,当夏令时结束时,时钟会向回拨慢一小时,这样同一个本地时间就会出现两次。当在构建位于这个时间段内的时间对象时,就会得到这两个时刻中较早的一个。
一个小时后的时间会具有相同的小时和分钟,但是时区的偏移量会发生变化。
还需要在调整跨越夏令时边界的日期时特别注意,例如将一个会议在下个星期,不要直接加上一个7天的Duration,而是应该使用Period类
ZonedDateTime skipped = ZonedDateTime.of(LocalDate.of(2013, 3, 31),
LocalTime.of(2, 30),
ZoneId.of("Europe/Berlin")
);
// 2013-03-31T03:30+02:00[Europe/Berlin]
System.out.println(skipped);
ZonedDateTime ambiguous = ZonedDateTime.of(LocalDate.of(2013, 10, 27),
LocalTime.of(2, 30),
ZoneId.of("Europe/Berlin")
);
ZonedDateTime anHourLater = ambiguous.plusHours(1);
// 2013-10-27T02:30+02:00[Europe/Berlin]-加一小时->2013-10-27T02:30+01:00[Europe/Berlin]
System.out.println(ambiguous +"-加一小时->"+anHourLater);
类 | 方法 | 描述 |
---|---|---|
java.time.ZonedDateTime | static ZonedDateTimenow() | 获取当前的ZonedDateTime |
static ZonedDateTime of(LocalDate date, LocalTime time, ZoneId zone) | ||
static ZonedDateTime of(LocalDateTime localDateTime, ZoneId zone) | ||
static ZonedDateTime of(int year, int month, int dayOfMonth,int hour, int minute, int second, int nanoOfSecond, ZoneId zone) | ||
static ZonedDateTime ofInstant(Instant instant, ZoneId zone) | 用给定的参数和时区产生一个ZonedDateTime | |
static ZonedDateTime ofLocal(LocalDateTime localDateTime, ZoneId zone, ZoneOffset preferredOffset) | ||
static ZonedDateTime ofInstant(LocalDateTime localDateTime, ZoneOffset offset, ZoneId zone) | 用给定的参数、时区和偏移量产生一个ZonedDateTime | |
ZonedDateTime (plus|minus)(Day|Weeks|Months|YearsHour| Minutes|Seconds|Nanos)(long number) | 产生一个ZonedDateTime,该对象是通过在当前对象上加上或减去给定数量的时间单位获得的 | |
ZonedDateTime plus(TemporalAmount amountToAdd) | ||
ZonedDateTime minus(TemporalAmount amountToSubtract) | 产生一个ZonedDateTime,该对象与当前对象距离给定的时间量。使用Period | |
ZonedDateTime with(DayOfMonth|DayOfYear|Month| Year|Hour|Minute|Second|Nano)(int value) | 返回一个新的ZonedDateTime,将指定的时间单位修改为指定的值 | |
ZonedDateTime withZoneSameLocal(ZoneId zone) | ||
ZonedDateTime withZoneSameInstant(ZoneId zone) | 返回一个新的ZonedDateTime,位于给定的时区,它与当前对象要么表示相同的时刻,要么表示相同的本地时间。 | |
int getYear() | 获取年份,在-999.999.999到999.999.999之间 | |
int getDayOfYear() | 获取年份日期(1到366之间) | |
Month getMonth() | ||
int getMonthValue() | 获取用Month枚举值表示的月份,或者用1到12之间的数字表示的月份 | |
int getDayOfMonth() | 获取月份日期(1到31之间) | |
DayOfWeek getDayOfWeek() | 获取星期日期,返回DayOfWeek枚举 | |
int getHour() | 获取小时(0到23之间) | |
int getMinute() | ||
int getSecond() | 获取分钟或秒(0到59之间) | |
int getNano() | 获取纳秒,在-999.999.999到999.999.999之间 | |
ZoneOffset getOffset() | ||
LocalDateTime toLocalDateTime() | ||
LocalDate toLocalDate() | ||
LocalTime toLocalTime() | ||
Instant toInstant () | 生成本地日期时间、日期、时间或相应的瞬间 | |
boolean isBefore(ChronoLocalDate other) | ||
boolean isAfter(ChronoLocalDate other) | 如果该时区时期时间在给定时区时期时间的之前或之后。返回true |
格式化和解析
DateTimeFormatter类提供了三种用于打印日期/时间值的格式器。
DateTimeFormatter被设计用来替代DateFormat。如果需要向后兼容,调用DateTimeFormatter的静态方法toFormat()
-
预定义格式器
-
locale相关的格式器
-
带有定制模式的格式器
预定义的格式器
格式器 描述 实例 BASIC_ISO_DATE 年、月、日时区偏移量,中间没有分隔符 19690716-0500 ISO_LOCAL_DATE、ISO_LOCAL_TIME、ISO_LOCAL_DATE_TIME 分隔符为-、:、T 1969-0716、09:32:00、1969-07-16T09:32:00 ISO_OFFSET_DATE、ISO_OFFSET_TIME、ISO_OFFSET_DATE_TIME 类似ISO_LOCAL_XXX,但是有时区偏移量 1969-0716-05:00、09:32:00-05:00、1969-07-16T09:32:00-05:00 ISO_ZONED_DATE_TIME 有时区偏移量和时区ID 1969-07-16T09:32:00-05:00[America/New_york] ISO_INSTANT 在UTC中,用z时区ID来表示 1969-07-16T09:32:00:00Z ISO_DATE、ISO_TIME、ISO_DATE_TIME 类似ISO_OFFSET_xxx,但是时区信息是可选的 1969-0716-05:00、09:32:00-05:00、1969-07-16T09:32:00-05:00[America/New_york] ISO_ORDINAL_DATE LoaclaDate的年和年日期 1969-197 ISO_WEEK_DATE LocalDate的年、星期和星期日期 1969-W29-3 RFC_1123_DATE_TIME 用于邮件时间戳的标准,编纂于RFC822并在RFC1123中将年份更新到4位 Wed, 16 Jul 1969 09:32:00-0500 使用标准的格式器,直接调用format方法即可
标准格式器主要是为了机器可读的时间戳而设计的。为了向人类表示日期1和时间,可以使用locale相关的格式器。
locale相关的格式化风格
风格 日期 时间 SHORT 7/19/69 9:32 AM MEDIUM Jul 16, 1969 9:32:00 AM LONG July 16, 1969 9:32:00 AM EDT FULL Wednesday,July 16, 1969 9:32:00 AM EDT 静态方法ofLocalizedDate、ofLocalizedTime和ofLocalizedDateTime可以创建这种格式器。
最后,可以通过指定模式来定制自己的日期格式。
常用的日期/时间格式的格式化符号
时间域或 实例 时代(公元) G:AD, GGGG: Anno Domini, GGGGG: A 公元的年 yy: 69, yyyy: 1969 年份的月 M: 7, MM: 07, MMM:Jul, MMMM: July, MMMMM:J 月份的天 d: 6, dd:06 星期的天 e:3, E: Wed, EEEE: WednesDay, EEEEE: W 24小时制的小时 H: 9 , HH:09 12小时制AM PM的小时 K: 9, KK: 09 一天的AM或PM a: AM 小时的分钟 mm: 02 分钟的秒 ss: 00 秒的纳秒 nnnnnn: 000000 时区ID VV: America/New_York 时区名 z: EDT, zzzz: Eastern Daylight Time, V: ET, VVVV: Eastern time 时区偏移量 x: -04, xx: -0400, xxx: -04:00, XXX: 于xxx相同,但是z表示0 本地化的时区偏移量 o: GMT-4, oooo: GMT-04:00 修改后的儒略日 g : 58243 为了解析字符串中的日期/时间值。可以使用众多的静态parse方法。
类 方法 描述 java.time.format.DateTimeFormatter String format(TemporalAccessor temporal) 格式化给定值。Instant、LocalTime、LocalDate、LocalDateTime、ZonedDateTIme,以及许多其它类,都实现了TemporalAccessor 接口 static DateTimeFormatter ofLocalizedDate(FormatStyle dateStyle) static DateTimeFormatter ofLocalizedTime(FormatStyle timeStyle) static DateTimeFormatter ofLocalizedDateTime(FormatStyle dateTimeStyle) static DateTimeFormatter ofLocalizedDateTime(FormatStyle dateStyle, FormatStyle timeStyle) 产生一个用于给定风格的格式器,FormatStyle枚举的值包括SHORT、MEDIUN、LONG、Full tatic DateTimeFormatter ofPattern(String pattern) static DateTimeFormatter ofPattern(String pattern, Locale locale) 用给定的模式和时区产生一个格式器。 DateTimeFormatter withLocale(Locale locale) 用给定的时区产生一个等价于当个格式器的格式器 java.time.LocalDate static LocalDate parse(CharSequence text) static LocalDate parse(CharSequence text, DateTimeFormatter formatter) 用默认的格式器或给定的格式器产生一个LocalDate java.time.ZonedDateTime static ZonedDateTime parse(CharSequence text) static ZonedDateTime parse(CharSequence text, DateTimeFormatter formatter) 用默认的格式器或给定的格式器产生一个ZonedDateTime
与遗留代码的转换操作
类 | 转换到遗留类 | 转换自遗留类 |
---|---|---|
Instant--java.util.Date | Date.from(instant) | date.toInstant() |
ZonedDateTime--java.util.GregorianCalendar | GregorianCalendar.from(zoned) | cal.toZonedDateTime() |
Instant--java.sql.Timestamp | Timestamp.from(instant) | timestamp.toInstant() |
LocalDateTime--java.sql.Timestamp | Timestamp.valueOf(localDateTime) | timestamp.toLocalDateTime() |
LocalDate--java.sql.Date | Date.valueOf(localDate) | date.toLocalDate() |
LocalTime--java.sql.Time | Time.valueOf(localTime) | time.toLocalTime() |
DateTimeFormatter--java.text.DateFormat | formatter.toFormat() | 无 |
java.util.TimeZone--ZoneId | TimeZone.getTimeZone(id) | timeZone.toZoneId() |
java.io.file.attribute.FileTime--Instant | FileTime.from(instant) | fileTime.toInstant() |
国际化
locale
locale由五个部分组成:
- 一种语言,由2个或三个小写字母表示。常见的ISO-639-1语言代码如下:
语言 | 代码 | 语言 | 代码1 |
---|---|---|---|
Chinese | zh | Italian | it |
Danish | da | Japanese | ja |
Dutch | nl | Korean | lo |
English | en | Norwegian | mo |
French | fr | Portuguses | pt |
Finish | fi | Spanish | es |
German | de | Swedish | sv |
Greek | el | Tukish | tr |
- 可选的一段脚本,由首字母大写的四个字母表示。例如Hant-》繁体中文
- 可选的一个国家或地区,由2个大写字母或3个数字表示,常见的ISO-3166-1国家代码如下:
国家 | 代码 | 国家 | 代码 |
---|---|---|---|
Austria | AT | Japan | JP |
Belgium | BE | Korea | KR |
Canada | CA | The Netherlands | NL |
China | CN | Norway | NO |
Denmark | DK | Portugal | PT |
Finland | FI | Spain | ES |
Germany | DE | Sweden | SE |
Great Britain | GB | Switzerland | CH |
Ireland | IE | Turkey | TR |
Italy | IT | United States | US |
- 可选的一个变体,用于指定各种杂项特性,例如方言或拼写规则。变体现在已经很少使用
- 可选的一个扩展。扩展描述了日历和数字等内容的本地偏好。
类 | 方法 | 描述 |
---|---|---|
java.util.Locale | Locale(String language) | |
Locale(String language, String country) | ||
Locale(String language, String country, String variant) | 用给定的语言、国家和变量创建一个locale。在新代码中不要使用变体,应该使用IETF BCP47语言标签 | |
static Locale forLanguageTag(String languageTag) | 构建与给定的语言标签相对应的locale | |
static Locale getDefault() | 返回默认的locale | |
static synchronized void setDefault(Locale newLocale) | 设定默认的locale | |
final String getDisplayName() | 返回一个在当前的locale中所表示的用来描述locale的名字 | |
String getDisplayName(Locale inLocale) | 返回一个在给定的locale中所表示的用来描述locale的名字 | |
String getLanguage() | 返回语言代码,他是两个小写字母组成的ISO 639代码 | |
final String getDisplayLanguage() | 返回在当前locale中所表示的语言名称 | |
String getDisplayLanguage(Locale inLocale) | 返回在给定locale中所表示的语言名称 | |
String getCountry() | 返回国家代码,它是由两个大写字母组成的ISO 3166代码 | |
static String[] getISOCountries() | ||
9 | static Set getISOCountries(Locale.IsoCountryCode type) | 获取所有两字母的国家代码,或者所有2、3、4个字母的国家代码。type参数是枚举常量PART1_ALPHA2、PART1_ALPHA3、PART之一 |
final String getDisplayCountry() | 返回在当前locale中所表示的国家名 | |
String getDisplayCountry(Locale inLocale) | 返回在指定locale中所表示的国家吗 | |
String toLanguageTag() | 返回该locale的语言标签,例如:de-CH | |
final String toString() | 返回locale的描述,包括国家和语言,用下划线分割(比如,“de_CH”)。应该只在调试时使用该方法 |
为了方便起见,由许多为各个国家或者语言预定于的locale对象。在Locale的静态常量中
数字格式
使用格式器(formatter)对象的集合,它可以对java.text包中的数字值进行格式化和解析
格式化数字值
通过以下步骤对特定locale的数字进行格式化:
- 使用Locale的方法,获得到locale对象
- 使用一个“工厂方法得到一个格式器对象
- 使用这个格式器对象来完成格式化和解析工作
工厂方法是NumberFormat的静态方法。总共由三个工厂方法,getNumberInstance、getCurrencyInstance、getPercentInstance
,分别是对数字、货币量和百分比进行格式化和解析.
使用format方法进行格式化。使用parse方法进行解析,能处理小数点和分隔符以及其他语言中的数字。
parse方法返回类型是抽象类型的Number。返的对象是一个Double和Long的包装器对象,这取决与被解析的数字是否是浮点数。如果不关心这两者的差异,可以直接使用Number类的doubleValue方法来读取被包装的数字
Locale china = Locale.CHINA;
NumberFormat numberInstance = NumberFormat.getNumberInstance(china);
String format = numberInstance.format(123456.78);
Number parse = numberInstance.parse(" 123.123".trim());
System.out.println(format+"\n"+parse);
==在解析时,如果数字文本的格式不正确,该方法会抛出一个PeaseException方法。例如,字符串以空白字符开头是不允许的(可以调用trim方法来去掉它)。但是,在任何数字之后的字符都将被忽略,所以不会抛出异常。==
还需要注意的式,getXXXInstance工厂方法返回的类并非是NumberFormat类型的。NumberFormat类型是一个抽象类,而得到的格式器实际上是他的子类。
抽象类 | 方法 | 描述 |
---|---|---|
java.text.NumberFormat | static Locale[] getAvailableLocales() | 返回一个Locale对象的数组,其成员为可用的NumberFormat格式器 |
final static NumberFormat getNumberInstance() | ||
static NumberFormat getNumberInstance(Locale inLocale) | ||
final static NumberFormat getCurrencyInstance() | ||
static NumberFormat getCurrencyInstance(Locale inLocale) | ||
final static NumberFormat getPercentInstance() | ||
static NumberFormat getPercentInstance(Locale inLocale) | 为当前或给定的locale提供处理数字、货币量或百分比的格式器 | |
final String format(long number) | ||
final String format(double number) | 对给定的浮点数或整数进行格式化并以字符串的形式返回结果 | |
Number parse(String source) | 解析给定的字符串并返回数字值 | |
void setParseIntegerOnly(boolean value) | ||
boolean isParseIntegerOnly() | 设置或获取一个标志,该标志指示这个格式器是否应该只解析整数值 | |
void setGroupingUsed(boolean newValue) | ||
boolean isGroupingUsed() | 设置或获取一个标志,该标志指示这个格式器是否会添加或识别十进制分隔符(比如:100,000) | |
int getMaximumIntegerDigits() | ||
void setMaximumIntegerDigits(int newValue) | ||
int getMinimumIntegerDigits() | ||
void setMinimumIntegerDigits(int newValue) | ||
int getMaximumFractionDigits() | ||
void setMaximumFractionDigits(int newValue) | ||
int getMinimumFractionDigits() | ||
void setMinimumFractionDigits | 设置或获取整数或小数部分所允许的最大或最小位数 |
货币
使用getCurrencyInstance方法。但是这种方法灵活性不好,它返回的是一个只针对一种货币的格式器。如果要同时处理两种,应该使用Currency类来控制格式器处理的货币。可以通过将一个货币标识符传给静态的Currency.getInstance方法来得到一个Currency对象,然后对每一个格式器都调用setCurrency方法
Locale china = Locale.CHINA;
NumberFormat currencyInstance = NumberFormat.getCurrencyInstance(china);
currencyInstance.setCurrency(Currency.getInstance("EUR"));
货币标识符 ISO4217
货币值 | 标识符 | 货币代码 | 货币值 | 标识符 | 货币代码 |
---|---|---|---|---|---|
U.S.Doller | USD | 840 | Chinese Renminbi(yuan) | CNY | 156 |
Euro | EUR | 978 | Indian Rupee | INR | 356 |
British Pound | GBP | 826 | Russian Ruble | RUB | 643 |
Japanese Yen | JPY | 392 |
类 | 方法 | 描述 |
---|---|---|
java.util.Currency | static Currency getInstance(String currencyCode) | |
static Currency getInstance(Locale locale) | 返回与给定的ISO4217货币代码或给定的locale中国家相对应的Currency对象 | |
String toString() | ||
String getCurrencyCode() | ||
int getNumericCode() | ||
int getNumericCode() | ||
9 | String getNumericCodeAsString() | 获取该货币的ISO4217代码 |
String getSymbol() | ||
String getSymbol(Locale locale) | 根据默认或给定的locale得到该货币的格式化符号。具体的表示形式取决于locale | |
int getDefaultFractionDigits() | 获取该货币小数后的默认位数 | |
static Set getAvailableCurrencies() | 获取所有可用的货币 |
日期和时间
当格式化日期和时间时,需要考虑四个与locale相关的问题:
- 月份和日期应该用本地语言来表示
- 年、月、日的顺序1要符号本地习惯
- 公历可能不是本地首选的日期表示方法
- 必须要考虑本地的时区
排序和规范化
对于不同国家,对于排序有着不同的定义:
为了获取locale敏感的比较器,可以调用静态的Collator.getInstant方法。
因为Collator类实现了Comparator接口,因此,可以传递一个Collator对象给List.sort(Comparator)方法来对一组字符串进行排序。
排序器有几个高级设置项,可以设置排序器的强度来选择不同的排序行为。字符间的差别可以被分为首要的(primary)、次要的(secondary)和再次的(tertiary)。
类 | 方法 | 描述 |
---|---|---|
java.text.Collator 抽象类 | static synchronized Locale[] getAvailableLocales() | 返回locale对象的一个数组,该Collator对象可用于这些对象 |
static synchronized Collator getInstance() | ||
static Collator getInstance(Locale desiredLocale) | 为默认或给定locale返回一个排序器 | |
int compare(String source, String target) | 如果a在b之前,则返回负值;如果相等返回0;否则返回正值 | |
boolean equals(String source, String target) | 如果a和b相等,则返回true,否则返回false | |
synchronized void setStrength(int newStrength) | ||
synchronized int getStrength() | 设置或获取排序器的强度。更强的排序器可以区分更多的词。强度的值可以是Collator.PRIMARY、Collator.SECONDARY、Collator.TRETIARY | |
synchronized void setDecomposition(int decompositionMode) | ||
synchronized int getDecomposition() | 设置或获取排序器的分解模式。分解模式越细,判断两个字符串是否相等是就越严格。分解的等级值可以是Collator.NO_DECOMPOSITION、Collator.CANONICAL_DECOMPOSITION、Collator.FULL_DECOMPOSITION | |
CollationKey getCollationKey(String source); | 返回一个排序器键,这个键包含一个对一组字符按特定格式分解的结果,可以快速地和其他排序器键进行比较 | |
java.text.CollationKey 抽象类 | int compareTo(CollationKey target) | 如果这个键在target之前,则返回负值;相等返回0;否则返回正值 |
java.text.Normalizer | String normalize(CharSequence src, Form form) | 返回src的规范化格式,form的值是ND、NKD、NC或NKC之一 |
消息格式化
文本输入和输出
资源包
脚本、编译与注解处理
java平台的脚本机制
脚本语言是一种通过在运行时解释程序文本,从而避免使用通常的编辑/编译/链接/运行循环的语言。优势:
- 便于快速变更,鼓励不断试验
- 可以修改运行着的程序的行为
- 支持程序用户的定制化1
另一方面,大多数脚本都缺乏可以编写复杂应用收益的特性,例如强类型、封装和模块化
详细使用java核心技术卷2 p349
编译器API
使用注解
注解是那些插入到源代码中使用其他工具可以对其进行处理的标签。这些工具可以在源码层次上进行操作,或者可以处理编译器在其中放置了注解的类文件。
注解不会改变程序的编译方式。java编译器对于包含注解和不包含注解的代码会生成相同的虚拟机指令。
为了能够收益于注解,需要选择一个处理工具,然后向处理工具可以理解的代码中插入注解,之后运用该处理工具处理代码。
注解一般的用法:
- 附属文件的自动生成,例如部署描述或bean信息类
- 测试、日志、事务语义等代码的自动生成
在java中注解是当作一个修饰符来使用的,它被置于被注解项之前,中间没有分隔符。
注解本身并不会做任何事情,他需要工具支持才会有用。
每个注解都必须通过一个注解接口来定义。@interface声明创建一个真正的java接口。处理注解的工具将接受那些实现了这个注解接口的对象。
注解一般是在运行时处理的。另外也可以在源码级别上对他们进行处理。运行是处理流程:
- 源文件:@Action ListenerFor
- java编译器
- 类文件:@Action ListenerFor
- java虚拟机
- 程序运行API:反射机制处理注解
接口 | 方法 | 描述 |
---|---|---|
java.lang.reflect.AnnotatedElement | boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果该项具有给定类型的注解,则返回true |
T getAnnotation(Class annotationClass); | 获得给定类型的注解,如果该项不具有这样的注解,则返回null | |
T[] getAnnotationsByType(Class annotationClass) | 获得某个可重复注解类型的所有注解,或者返回长度为0的数组 | |
T[] getDeclaredAnnotationsByType(Class annotationClass) | 指定 | |
Annotation[] getAnnotations(); | 获得作用于该项的所有注解,包括继承而来的注解。如果没有出行任何注解,那么返回长度为0的数组 | |
Annotation[] getDeclaredAnnotations(); | 获得为该项声明的所有注解,不包含继承而来的注解。如果没有,返回长度0的数组 | |
T getDeclaredAnnotation(Class annotationClass) | 指定 |
注解语法
注解接口
注解是由注解接口来定义的:
modifiers @interface AnnotationName{
elementDeclaration1
elementDeclaration2
...
}
每个元素声明都具有下面这种形式:
type elementDeclaration();
或者
type elementDeclaration() default value;
所有注解接口都隐式地扩展自java.lang.annotation.Annotation接口。这个接口是一个常规接口,不是一个注解接口。
注解接口无法扩展。换句话说所有的注解接口都直接扩展自java.lang.annotation.Annotation。不需要为注解接口提供任何实现类
注解元素的的类型为以下几种:
- 基本类型
- String
- Class(具有一个可选的类型参数,例如Class<? entends MyClass>)
- enum类型
- 注解类型
- 由前面所述类型组成的数组(由数组组成的数组不是合法的元素类型)
pulic @interface BugReport{
enum Status {UN,CO,FI,NO};
bolean assigedTO(); default "[none]";
Class<?> testCase(); default Void.class;
Status status() default Status.UN;
Reference ref() default @Refrence();
String[] reportedBy();
}
接口 | 方法 | 描述 |
---|---|---|
java.lang.annotation.Annotation | Class<? extends Annotation> annotationType(); | 返回class对象,它用于描述该注解对象的注解接口。注意:调用注解对象上的getCalss方法可以返回真正的类,而不是接口 |
boolean equals(Object obj); | 如果obj是一个实现了与该注解对象相同的注解接口的对象,并且如果该对象和obj的所有元批次相等,返回true | |
int hashCode(); | 返回一个与equals方法兼容、由注解接口名以及元素值衍生而来的散列码 | |
String toString(); | 返回一个包含注解接口名以及元素值的字符串表示 |
注解
每一个注解都具有以下的格式
@AnnotationName(elementName1=value1,elementName2=value2,..)
注解中的元素顺序无关紧要。如果某个元素的值并未指定,那么就使用声明的默认值
==警告==
默认值并不是和注解存储在一起的;相反地,它们是动态计算而来的。例如修改一个元素的默认值,然后重新编译,那么注解将使用新的默认值
由两种特殊的方式简化注解:
- 如果没有指定元素,要么是因为注解中没有任何元素,要么是因为所有元素都由默认值,那么就不需要圆括号。这种注解称为标记注解
- 如果一个元素具有特殊的名称value,并且没有指定其他元素,那么可以忽略掉这个元素名以及等号。这种注解称为单值注解
一个项可以由多个注解;如果注解被声明为可重复的,那么可以多次重复使用同一个注解
==警告==
- 因为注解是编译器计算而来的,因此,所有元素值必须是编译器常量
- 一个注解元素永远不能设置为null,甚至不允许其默认值为null。这样在实际应用中会相当不方便。必须1使用其它的默认值
如果元素值为一个数组,可以将它的值用括号括起来。如果该元素具有单值,可以忽略这个括号
注解各类声明
注解可以出现在许多地方,这些地方可以分为两类:声明和类型用法声明注解可以出现在下列声明处:
- 包
- 类(包括enum)
- 接口(包括注解接口)
- 方法
- 构造器
- 实例域(包含enum常量)
- 局部变量
- 参数变量
- 类型参数
对于类和接口,需要将注解放置在class和interface关键字之前;
对于变量,需要放在类型之前;
泛化类或方法中的类型参数可以:pulic class Cahce<@Immutable V>{...}
包是在文件package-info.java中注解的,该文件只包含以注解先导的包语句:
@GPL(version=3)
package com.horstman.corejava;
impot ori.gnu.GPL
对局部变量的注解只能在源码级别上进行处理。类文件并不描述局部变量。因此,所有局部变量注解在编译完一个类的时候就会被遗弃掉。同样地,对包的注解不能再源码级别之外存在
类型用法注解
声明注解提供了正在被声明的项的相关信息。例如:public User getUser(@NonNull String userId)
就断言userId参数不为空
例如要断言一个list类型的参数,其中所有的字符串不为空:List<@NonNull String>
类型用法注解可以出现在下面的位置:
- 与泛化类型参数一起使用:List<@NonNull String>, Comparator<@NonNull String> reverseOrder();
- 数组中的任何位置:@NonNull String[][] words(words[i][j]不为null),String @NonNull [][] words(words不为null),String[]@NonNull[] words(words[i]不为null)
- 与超类和实现接口一起使用:class Warning extends @Localized Message
- 与构造器调用一起使用:new @Localized String(..)
- 与强制转型和instanceof检查一起使用:(@Localized String)text,if(text instanceof @Localized String)。(这些注解只供外部工具使用,它们对强制类型和instanceof检查不会产生任何影响)
- 与异常规约一起使用:pulic String read() throws @Localized IOException
- 与通配符和类型边界一起使用:List<@Localized ? extends Message>, List<? extends @Localized String Message>
- 与方法和构造器引用一起使用:@Localized Message::getText
不可以出现的地方
- @NonNull String.class
- import java.lang.@NonNull String;
==注释==
可以将注解放置在诸如private和static这样的其它修饰符的前面或后面。但习惯的做饭,是将类型用法注解放置在其它修饰符的后面和将声明注解放置在其它修饰符的前面
注解this
标准注解
java.lang、java.lang.annotation和javax.annnotaiton包中定义了大量的注解接口。其中四个为元注解,用于描述注解接口的行为属性,其它的三个是规则注解,可以用它们来注解你的源代码中的项
注解接口 | 应用场合 | 目的 |
---|---|---|
Deprecated | 全部 | 将项标记为已过时 |
SuppressWarnings | 除了包和注解之外的所有情况 | 阻止某个给定类型的警告信息 |
SafeVarargs | 方法和构造器 | 断言varargs参数可安全使用 |
Override | 方法 | 检查该方法是否覆盖某一个超类方法 |
FunctionalInterface | 接口 | 将接口标记为只有一个抽象方法的函数式接口 |
PostConstruct PreDestroy | 方法 | 被标记的方法应该在构造之后或移除之前立即被调用 |
Resource | 类、接口、方法、域 | 在类或接口上:标记为在其它地方要用到的资源。在方法或域:为“注入”标记 |
Resources | 类、接口 | 一个资源数组 |
Generated | 全部 | |
Target | 注解 | 指明可以应用这个注解的那些项 |
Retention | 注解 | 指明这个注解可以保留多久 |
Documented | 注解 | 指明这个注解应该包含在注解项的文档中 |
Inherited | 注解 | 指明当这个注解应用于一个类的时候,能够被它的子类继承 |
Repeatable | 注解 | 指明这个注解可以在同一个项上应用多次 |
用于编译的注解
@Deprecated
注解可以被添加到任何不在鼓励使用的项上。所有,当使用一个已过时的项时,编译器会发出警告。该注解一直持久化到运行时
@SuppressWarnings
注解会告知编译器阻止特定类型的警告信息,例如:@SuppressWarnings("unchecked")
@Generated
注解的目的是供代码生成器工具来使用的。任何生成的源代码都可以被注解,从而于程序员提供的代码区分开。例如,代码编辑器可以隐藏生成的代码,或者代码生成器可以移除生成代码的旧版本。每个注解都必须包含代码生成器的唯一标识符,而日期字符串和注释字符串是可选的
用于管理资源的注解
@PostConstruct和@PreDestroy注解用于控制对象声明周期的环境中,例如Web容器和应用服务器。标记了这些注解的方法应该在对象被构建之后,或者在对象被移除之前,紧接着调用
@Resource注解用于资源注入。
元注解
@Target
元注解可以应用于一个注解,以限制该注解可以应用到哪些项上。
其取值包括:
元素类型 | 注解适用场合 | 元素类型 | 注解适用场合 |
---|---|---|---|
ANNOTATION_TYPE | 注解类型声明 | FIELD | 成员域(包括enum常量) |
PACKAGE | 包 | PARAMETER | 方法或构造器参数 |
TYPE | 类(包括enum)及接口(包括注解类型) | LOCAL_VARIABLE | 局部变量 |
METHOD | 方法 | TYPE_PARAMETER | 类型参数 |
CONSTRUCTOR | 构造器 | TYPE_USE | 类型用法 |
一个没有@Target限制的注解可以应用于任何项上。
@Retention
元注解用于指定一条注解应该保留多长时间。只能将其指定的值:
保留规则 | 描述 |
---|---|
SOURCE | 不包括在类文件的注解 |
CLASS | 包括在类文件中的注解,但是虚拟机不需要将它们载入。默认值 |
RUNTIME | 包括在类文件中的注解,并由虚拟机载入,通过反射API可以获得它们 |
@Documented
为归档工具提供一些提示
@Inherited
元注解只能应用于对类的注解。如果一个类具有继承注解,那么他的所有子类都自动具有同样的注解,这使得创建一个于Serializable这样的标记接口具有相同运行方式的注解变得很容易
@Repeatable
元注解将同种类型的注解多次应用于某一项是合法的。为了向后兼容,可以重复注解的实现者需要提供一个容器注解,它可以将这些重复注解存储到一个数组中。
@Repeatable(TestCase.class)
pulic @interface TestCase{
String params();
String expected();
}
pulic @interface TestCases{
TestCase[] Value();
}
无论何时,只要用户提供了两个或更多个@TestCase注解,那么它们就会自动地被包装到一个@TestCases注解中
在处理可重复注解时必须特别仔细。如果调用getAnnotation来查找某个可重复注解,而该注解又确实重复了,那么就会得到nul。因为重复注解被包装到了容器注解中。在这种情况下,应该调用getAnnotationByType。这个调用会遍历容器,并给出一个重复注解的数组。如果只有一条注解,那么该数组的长度就为1,通过使用这个方法,就不用操心如何处理容器注解了
源码级注解处理
除了分析正在运行的程序中的注解,还有另外一种用法是自动处理源代码以产生更多的源代、配置文件、脚本或其它任何想要生成的东西。
注解处理器
注解处理器已经被集成到了java编译器中。在编译过程中,可以通过运行以下命令来调用注解处理器:javac -processor ProcessorClassName1,ProcessorClassName1,..sourceFiles
编译器会定位源文件中的注解。每个注解处理器会依次执行,并得到它表示感兴趣的注解。如果某个注解处理器创建了一个新的源文件,那么上述的过程将重复执行。如果某次处理循环没有再产生任何新的源文件,那么就编译所有的元素。
注解处理只能产生新的源文件,无法修改已有的源文件。
注解处理器通常通过扩展AbstractProcessor类而实现Processor接口。例如
@SupportedAnnotationTypes("src.注解.ToString")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ToStringAnnotationProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
return false;
}
}
处理器可以声明具体的注解类型或诸如“com.cyp*”这样的通配符(com.cyp包及其所有子包中的注解),甚至是“*”所有注解
再每一轮中,process方法都会被调用一次,调用时会传递给由这一轮在所有文件中发现的所有注解构成的集,以及包含了有关当前处理伦次的信息的RoundEnvironmet引用
字节码工程
java平台模块系统
java9