Java核心卷Ⅱ(原书第10版)笔记(下)
写在最前面,个人认为,卷Ⅱ更适合当手册使用,更多的是讲API的使用,前两章内容比较实际,要是合并到卷一就好了。
Java核心卷Ⅱ(原书第10版)笔记(上)
文章目录
第 6 章 日期和时间 API(简单看)
Java 1.0 有一个
Date
类,事后证明它过于简单了,当 Java 1.1 引入Calendar
类之后,Date
类中的大部分方法就被弃用了。但是,Calendar
的 API 还不够给力,它的实例是易变的,并且它没有处理诸如闰秒这样的问题。第 3 次升级很吸引人,那就是 Java SE 8 中引人的java.time
API, 它修正了过去的缺陷,并且应该会服役相当长的一段时间。
6.1 时间线
闰秒: 官方时间的监护者们时常需要将绝对时间与地球自转进行同步,地球有轻微的颤动,所以衙要更加精确的定义。首先,官方的秒需要稍作调整,从 1972 年开始,偶尔需要插人"闰秒"。(在理论上,偶尔也需要移除 1 秒,但是这还从来没发生过)
Java 的 Date 和 Time API 规范要求 Java 使用的时间尺度为:
- 每天 86’400 秒
- 每天正午与官方时间精确匹配
- 在其他时间点上,以精确定义的方式与官方时间接近匹配
在 Java 中, Instant
表示时间线上的某个点。 Duration
是两个时刻之间的时间量。 这两个类都是不可修改的类。
6.2 本地时间
LocalDate
是带有年、月、日的日期。为了构建 LocalDate
对象,可以使用 now
或 of
静态方法:
有些方法可能会创建出并不存在的日 期。 例如,在 1 月 31 日上加上 1 个月不应该产生 2 月 31 日 。 这些方法并不会抛出异常 ,而是会返回该月有效的最后一天。例如:
// 下面两行代码都将产生 2016 年 2 月 29 日 。
LocalDate.of(2016 , 1, 31).plusMonths(1);
LocalDate.of(2016, 3, 31).minusMonths(1);
6.3 日期调整器
对于日程安排应用来说,经常儒要计箕诸如“每个月的第一个星期二”这样的日期。TemporalAdjusters
类提供了大量用于常见调整的静态方法。例如,某个月的第一个星期二可以像下面这样计算:
LocalDate firstTuesday = LocalDate.of(year, month, 1).with(TemporalAdjusters .nextOrSame(DayOfWeek.TUESDAY));
也可中继实现 TemporalAdjuster
接口来创建自己的调整器(用 lambda 很方便)。
6.4 本地时间
获取当日时刻:
LocalTime rightNow = LocalTime.now();
LocalTime bedtime= LocalTime.of(22, 30); // or LocalTime.of(22, 30, 0)
LocalTime
自身并不关心 AM/PM。这种愚蠢的设计将问题抛给格式器去解决。
6.5 时区时间
互联网编码分配管理机构 ( Internet Assigned Numbers Authority, IANA ) 保存若一个数据库 ,里面存储着世界上所有已知的时区 ( www.iana.org/time-zones ), 它每年会更新数次,而批量更新会处理夏令时的变更规则。Java 使用了 IANA 数据库。
每个时区都有一个 ID, 例如 America/New_York 和 Europe/Berlin。要想找出所有可用的时区,可以调用
Zoneld.getAvailableZonelds
。在本书撰写之时,打将近 600 个ID。
给定一个时区 ID, 静态方法 Zoneld.of(id)
可以产生一个 Zoneld 对象。可以通过调用 local.atZone(zoneld)
用这个对象将 LocalDateTime
对象转换为 ZonedDateTime
对象,或者可以通过悯用静态方法 ZonedDateTime.of(year ,month,day,hour,minute,second,nano,zoneld)
来构造一个 ZonedDateTime 对象。例如:
// 1969-07-16T09:32-04: OO[America/New_York]
ZonedDatelime apollo11launch = ZonedDateTime.of(1969, 7, 16, 9, 32, 0,
0,ZoneId.of("America/New_York"));
// 调用 apollo11launch.tolnstant 可以获得对应的 Instant 对象。
// 反之,调用 instant.atZone(Zoneid.of("UTC")) 可以获得格林威治皇家天文台的 ZonedDateTime 对象。
UTC:代表“协调世界时”,这是英文 “Coordinated Universal Time” 和法文 “Temps Unjversel Coordine” 首字母缩写的折中,它与这两种语言中的缩写都不一致。UTC 是不考虑夏令时的格林威冶皇家天文台时间。
6.6 格式化和解析
DateTimeFormatter
DateTimeFormatter
:java.time.format.DateTimeFormatter
类被设计用来替代 java.util.DateFormat
。如果你为了向后兼容性而需要后者的示例,那么可以调用formatter.toFormat()
。
DateTimeFormatter
类提供了三种用于打印日期/时间值的格式器:- 预定义的格式器(参见表 6-6)
- Locale 相关的格式器
- 带有定制模式的格式器
6.7 与遗留代码的互操作
java.time 类与遗留类之间的转换
类 | 转换到遗留类 | 转换自遗留类 |
---|---|---|
Instant <–> java.util.Date | Date.from(instant) | date.toInstant() |
ZonedDateTlme <–> java.util.GregorianCalendar | GregorianCalendar.from(zonedDateTime) | cal.toZonedDateTime() |
Instant <–> java.sql.Timestamp | TimeStamp.from(instant) | timestamp.toInstant() |
LocalDateTime <–> java.sql.Timestamp | Timestamp.valueOf(localDateTime) | timeStamp.toLoca1DateTime() |
LocalDate <–> Java.sql.Date | Date.valueOf(localOate) | date.toLocalDate() |
LocalTime <–> java.sql.Time | Time.valueOf(localTime) | time.toLocalTime() |
DateTimeFonnatter <–> java.text.DateFormat | formatter.toFonnat() | 无 |
java.util.TimeZone <–> ZoneId | Timezone.getTimeZone(id) | timeZone.toZoneId() |
java.nio.file.attribute.FileTime <–> Instant | FileTime.from(instant) | fileTime.toinstant() |
第 7 章 国际化(可略)
7.1 Locale 对象
Java SE 为各个国家和语言预定义了 Locale
对象,如:Locale.CHINA
、Locale.CHINESE
。
7.2 数字格式
NumberFormat
类有一个静态工厂方法,它们接受一个 Locale
类型的参数。总共有3 个工厂方法: getNumberInstance
、 getCurrencyInstance
和 getPercentInstance
,这些方法返回的对象可以分别对数字、货币址和百分比进行格式化和解析。
Locale loc = Locale.GERMAN;
NumberFormat currFmt = NumberFormat.get(urrencylnstance(loc) ;
double amt = 123456.78;
String result = currFmt.format(amt);
7.3 货币
可以使用 NumberFormat.getCurrencyInstance
方法来格式化货币的值。但是,这个方法的灵活性不好,它返回的是一个只针对一种货币的格式器。
应该使用 Currency
类来控制被格式器所处理的货币。可以通过将一个货币标识符传给静态的 Currency.getInstance
方法来得到一个 Currency
对象,然后对每一个格式器都调用 setCurrency
方法。
NumberFormat euroFormatter = NumberFonnat.getCurrencylnstance(locale.US) ;
euroFormatter.setCurrency(Currency.getlnstance("EUR"));
7.4 日期和时间
- 当格式化日期和时间时,需要考虑 4 个与
Locale
相关的问题:- 月份和星期应该用本地语言来表示。
- 年月日的顺序要符合本地习惯。
- 公历可能不是本地首选的日期表示方法。
- 必须要考虑本地的时区。
java.time
包中的 DateTimeFormatter
类可以处理这些问题。
7.5 排序和范化
为了获得 Locale
敏感的比较符,可以调用静态的 Collator.getInstance
方法:
Collator coll = Collator.getInstance(locale);
words.sort(coll); // Collator implements Comparator<Object>
7.6 消息格式化(略)
Java 类库中有 一个 MessageFormat
类,它与用 printf
方法进行格式化很类似,但是它支持 Locale
, 并且会对数字和日期进行格式化。
7.7 文本文件和字符集(略)
7.8 资源包(略,目前开发没使用到这部分)
当本地化一个应用时,可能会有大员的消息字符串、按钮标签和其他的东西需要被翻译。 为了能灵活地完成这项任务,你会希望在外部定义消息字符串,通常称之为资源( resource )。 翻译人员不需要接触程序源代码就可以很容易地编辑资源文件。在 Java 中 ,你要使用属性文件来设定字符串资源,并为其他类型的资源实现相应的类。
7.9 一个完整的例子(略略略)
第 8 章 脚本、编译与注解处理(可略,实际开发没怎么用到)
脚本语言是一种通过在运行时解释程序文本,从而避免使用通常的编辑/编译/链接/运行循环的语言。
另一方面,大多数脚本语言都缺乏可以使编写复杂应用受益的特性,例如强类型、封装和模块化。
因此人们在尝试将脚本语言和传统语言的优势相结合。脚本 API 使你可以在 Java 平台上实现这个目的,它支待在 Java 程序中对用 JavaScript 、 Groovy 、 Ruby, 甚至是更奇异的诸如Scheme 和 Haskell 等语言编写的脚本进行调用。
- 脚本语言有许多优势:
- 便于快速变更,鼓励不断试验 。
- 可以修改运行着的程序的行为 。
- 支持程序用户的定制化 。
8.1 Java 平台的脚本(略)
8.2 编译器 API(略)
8.3 使用注解(略,真的只是讲使用注解……)
8.4 注解语法
8.4.1 注解接口
注解是由注解接口来定义的:
public @interface BugReport
{
String assignedTo() default "[none]";
int severity()
}
所有的注解接口都隐式地扩展自 java.lang.annotation.Annotation
接口。这个接口是一个常规接口,不是一个注解接口。
你无法扩展注解接口。换句话说,所有的注解接口都直接扩展自 java.lang.annotation.Annotation
。
- 注解元素的类型为下列之一:
- 基本类型 (int 、 short, 、 long 、 byte 、 char 、 double 、 float 或者 boolean) 。
- String 。
- Class (具有一个可选的类型参数,例如 Class<? extends MyClass >) 。
- enum 类型 。
- 注解类型 。
- 由前面所述类型组成的数组(由数组组成的数组不是合法的元素类型) 。
下面是一些合法的元素声明的例子:
public @interface BugReport{
enum Status { UNCONFIRMED, CONFIRMED, FIXED, NOTABUG };
boolean showStopper() default false;
String assignedToO default "(none)";
Class<?> testCase() default Void.class;
Status status() default Status.UNCONFIRMED;
Reference ref() default @Reference(); // an annotation type
String[] reportedBy();
}
8.4.2 注解
正常使用(没设置的值使用默认值):
@SugReport(assignedTo="Harry", severity=10)
标记注解(所有的值都用默认值):
@SugReport
单值注解(如果一个元素具有特殊的名字 value , 并且没有指定其他元素,那么你就可以忽略掉这个元素名以及等号):
@SugReport("yellowButton")
默认值并不是和注解存储在一起的;相反地,它们是动态计算而来的。例如,如果你将元素
assignedTo
的默认值更改为"[]" 然后重新编译BugReport
接口,那么注解@BugReport(severity = 10)
将使用这个新的默认值,甚至在那些在默认值修改之前就已经编译过的类文件中也是如此。
注意事项:
- 因为注解是由编译器计算而来的,因此,所有元素值必须是编译期常量。
- 一个注解元素永远不能设置为
null
, 甚至不允许其默认值为null
这样在实际应用中会相当不方便。 你必须使用其他的默认值,例如""
或者Void.class
。 - 注解中引入循环依赖是一种错误。例如,因为
BugReport
具有一个注解类型为Reference
的元素,所以Reference
就不能再拥有一个类型为BugReport
的元素。
8.4.3 注解各类声明(略,告诉你注解可以放哪里)
8.4.5 注解 this(略)
8.5 标准注解
java SE 在
java.lang
、java.lang.annotation
和javax.annotation
包中定义了大员的注解接口 。其中四个是元注解,用于描述注解接口的行为屈性,其他的三个是规则接口,可以用它们来注解你的源代码中的项。
8.5.1 用于编译的注解
@Deprecated
注解可以被添加到任何不再鼓励使用的项上。所以,当你使用一个巳过时的项时 , 编译器将会发出警告。这个注解与Javadoc
标签@deprecated
具有同等功效。@SuppressWarnings
注解会告知编译器阻止特定类型的警告信息。@Override
这种注解只能应用到方法上。编译器会检查具有这种注解的方法是否真正覆盖了一个来自于超类的方法。@Generated
注解的目的是供代码生成工具来使用。任何生成的源代码都可以被注解,从而与程序员提供的代码区分开。
8.5.2 用于管理资源的注解
@PostConstruct
注解的方法应该在对象被构建之后调用。@PreDestroy
注解的方法应该在对象被构建之前调用。@Resource
注解用于资源注入。
8.5.3 元注解
@Target
元注解可以应用于一个注解,以限制该注解可以应用到哪些项上。使用ElementType
的值,可以指定任意数量的元素类型。@Retention
元注解用于指定一条注解应该保留多长时间。使用RetentionPolicy
的值,默认是CLASS
。@Documented
元注解为像 Javadoc 这样的归档工具提供了一些提示。@Inherited
元注解只能应用于对类的注解。 如果一个类具有继承注解(@Inherited
),那么它的所有子类都自动具有同样的注解。
对于 Java SE 8 来说,将同种类型的注解多次应用于某一项是合法的。为了向后兼容,可重复注解的实现者需要提供一个容器注解,它可以将这些重复注解存储到一个数组中。例如,下面是如何定义
@TestCase
注解以及它的容器的代码:
// 无论何时,只要用户提供了两个或更多个 @TestCase 注解,那么它们就会自动地被包装到一个 @TestCases 注解中。
@Repeatable(TestCases.class)
@interface TestCase
{
String params();
String expected();
}
@interface TestCases
{
TestCase[] value();
}
在处理可重复注解时必须非常仔细。如果调用
getAnnotation
来查找某个可重复注解,而该注解又确实重复了,那么就会得到null
。这是因为重复注解被包装到了容器注解中。
在这种情况下,应该调用getAnnotationsByType
。 这个调用会"遍历"容器,并给出一个重复注解的数组。如果只有一条注解,那么该数组的长度就为 1 。 通过使用这个方法,你就不用操心如何处理容器注解了。
8.6 源码级注解处理
注解处理器只能产生新的源文件,它无法修改已有的源文件。(PS:那 Lombok 为什么可以呢?Lombok 原理分析与功能实现 跟 8.7 字节码工程 内容很像)
8.6.1 注解处理
注解处理器通常通过扩展 AbstractProcessor
类而实现 Processor
接口。例如:
// 处理器可以声明具体的注解类型或诸如" com.horstmann* "这样的通配符 (com.horstmann 包及其所有子包中的注解),甚至是 "*" (所有注解) 。
@SupportedAnnotationTypes("com.horstmann.annotations.ToString")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class ToStringAnnotationProcessor extends AbstractProcessor{
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvi ronment currentRound){
// todo:
}
}
8.6.2 语言模型 API (后续通过别的文章了解,书中没怎么说)
8.6.3 使用注解来生成源码
不能将方法放到原来的类中,因为注解处理器只能产生新的类,而不能修改已有类。
8.7 字节码工程(后续通过别的文章了解,比如lombok的原理解读等)
第 9 章 安全(略)
- Java 技术提供了以下三种确保安全的机制:
- 语言设计特性(对数组的边界进行检查,无不受检查的类型转换,无指针箕法等)。
- 访间控制机制,用于控制代码能够执行的操作(比如文件访间,网络访问等)。
- 代码签名,利用该特性,代码的作者就能够用标准的加密算法来认证 Java 代码。 这样,该代码的使用者就能够准确地知道谁创建了该代码,以及代码被标识后是否被修改过 。
9.1 类加载器(略,将的太高深了,后期补……)
虚拟机只加载程序执行时所需要的类文件。
类加载机制并非只使用单个的类加载器。每个 Java 程序至少拥有三个类加载器:
- 引导类加载器:引导类加载器负责加载系统类(通常从 JAR 文件
rt.jar
中进行加载),通常是用 C 语言来实现的。 - 扩展类加载器:扩展类加载器用于从
jre/lib/ext
目录加载“标准的扩展”,可以将 JAR 文件放入该目录,这样即使没有任何类路径,扩展类加载器也可以找到其中的各个类 。。 - 系统类加载器(有时也称为应用类加载器):系统类加载器用于加载应用类。它在由
CLASSPATH
环境变量或者-classpath
命令行选项设置的类路径中的目录里或者是 JAR/ZIP 文件里查找这些类 。
在 Oracle 的 Java 语言实现中,扩展类加载器和系统类加载器都是用 Java 来实现的。它们都是 URLClass loader
类的实例 。
9.2 安全管理器与访问权限(略,后期补……开发中有使用OAuth2之类的框架)
9.3 用户认证(略,书中使用JAAS,有接触再回头看)
9.4 数字签名(略,后期补)
Java 编程语言已经实现了 MD5
、SHA-1
, SHA-256
, SHA-384
和 SHA-512
。
MessageDigest
类是用于创建封装了指纹算法的对象的"工厂",它的静态方法 getInstance
返回继承了MessageDigest
类的某个类的对象。
9.5 加密(略)
第 10 章 高级 Swing(略)
第 12 章 本地方法(略,后期补)
我们建议只有在必需的时候才使用本地代码。特别是在以下三种情况下,也许可以使用本地代码:
- 你的应用需要访问的系统特性和设备通过 Java 平台是无法实现的 。
- 你已经有了大量的测试过和调试过的用另一种语言编写的代码,并且知道如何将其导出到所有的目标平台上。
- 通过基准测试,你发现所编写的 Java 代码比用其他语言编写的等价代码要慢得多 。
Java 平台有一个用于和本地 C 代码进行互操作的 API, 称为 Java 本地接口 (JNI) 。 我们将在本章讨论 JNI 编程。
12.1 从 Java 程序中调用 C 函数
Java 编程语言使用关键字 native
表示本地方法。关键字 native
提醒编译器该方法将在外部定义。
本地方法既可以是静态的也可以是非静态的。