原始类型包装器
为了在必须使用类类型的地方使用原始类型,每个原始类型都有一个包装器,该包装器将原始类型封装成类类型。
抽象类 Number 作为所有数值类型包装类的父类,定义了返回不同类型值的抽象方法。它的子类有 Byte、Short、Integer、Long、Float 和 Double。这些抽象方法形式为
// xx 表示上述 6 种数值类型的任意一种
xx xxValue();
这些方法返回的值可能是截断或舍入后的,取决于包装类中值的具体大小和调用的方法。
Float 和 Double 定义的常量如下
名称 | 说明 |
---|---|
BYTES | float 或 double 以字节为单位的长度 |
MAX_EXPONENT | 最大指数 |
MAX_VALUE | 最大正数 |
MIN_EXPONENT | 最小指数 |
MIN_NORMAL | 最小正 normal 值 |
MIN_VALUE | 最小正数 |
NaN | 不是数 |
POSITIVE_INFINITY | 正无穷 |
NEGATIVE_INFINITY | 负无穷 |
SIZE | 包装值的位宽 |
TYPE | float 或 double 的 Class 对象 |
最小正数和最小正 normal 值的区别在于,最小正数的 binary point(二进制小数点)前面是 0,而最小正 normal 值的 binary point 前面是 1。
Float 和 Double 提供了 isInfinite()
方法,当值无限大或无限小时,返回 true;否则返回 false。提供的 isNaN()
方法,当值不是数时,返回 true;否则返回 false。
从 JDK9 开始,所有数值类型包装器的接收原始类型和字符串的构造器废弃,使用 valueOf()
方法代替。
整型类型包装器提供将字符串转换成对应原始类型的方法。
// XX 表示 Byte/Short/Integer/Long 其中的一个
XX.parseXX(String);
所有整型类型包装器的 toString()
方法将值转换成字符串。Integer 和 Long 提供了 toBinaryString()
、toOctalString()
和 toHexString()
方法将值转换成以二进制、八进制和十六进制表示的字符串形式。
Character 类提供的 charValue()
方法返回 char 值。定义有如下常量
名称 | 说明 |
---|---|
BYTES | char 以字节为单位的长度 |
MAX_RADIX | 最大基数 |
MIN_RADIX | 最小基数 |
MAX_VALUE | 最大字符值 |
MIN_VALUE | 最小字符值 |
TYPE | char 的 Class 对象 |
Character 的 static char forDigit(int num, int radix)
方法返回整数对应的字符。
static int digit(char digit, int radix)
方法返回字符对应的整数。
int compareTo(Character c)
方法比较两个字符的大小。
getDirectionality()
方法确定字符的方向,方向由预定义的常量表示。
Character.Subset 类用于描述 Unicode 的子集;Character.UnicodeBlock 包含 Unicode 字符块。
最开始,Unicode 字符占 16 位,与 char 大小相同,范围为 0 ~ FFFF。随着 Unicode 字符集的扩充,字符范围扩展到了 0 ~ 10FFFF。字符不再仅用 16 位就可以表示了。从 JDK5 开始,Character 类就支持 32 位字符。
码点(code point)表示 Unicode 字符对应的数值,范围为 0 ~ 10FFFF。值大于 FFFF 的字符称为补充字符(supplemental characters),0 ~ FFFF 的字符称为基本多平面(basic multilingual plane, BMP)。对于补充字符,Java 使用两种方式处理。其中一种为,使用 2 个 char 表示一个字符,第一个 char 称为高代理项(high surrogate),第二个 char 称为低代理项(low surrogate)。另一种方法为,将已存在的部分方法参数类型从 char 改为 int。
Boolean 主要用于 boolean 值需要作为引用传递的情况。构造时,仅当传入的字符为大写或小写的 true 时为 true,否则为 false。Boolean 的 TYPE 表示 boolean 的 Class 对象。
Void
Void 类有名为 TYPE 的常量,引用 void 的 Class 对象。Void 类不能实例化。
Process
Process 抽象类封装了一个进程,它是 Runtime 类的 exec()
方法和 ProcessBuilder 类的 start()
方法返回的对象类型的超类。JDK9 开始,ProcessHandle 对象可以用于处理进程,ProcessHandle.Info 对象可以获取进程相关的信息。ProcessHandle.Info 对象的 totalCpuDuration()
方法返回进程使用的 CPU 时间量,ProcessHandle 类的 isAlive()
方法根据当前进程是否正在执行返回 true 或 false。
Runtime
Runtime 类封装了 Java 运行时环境,不能实例化。Runtime.getRuntime()
方法返回当前 Runtime 对象,调用该对象提供的方法可以控制 JVM 的状态和行为。
totalMemory()
方法返回对象堆(object heap)的大小,freeMemory()
方法返回对象堆的剩余容量大小。gc()
方法调用垃圾回收器。
public static void m() {
var r = Runtime.getRuntime();
System.out.println(r.totalMemory());
System.out.println(r.freeMemory());
String s = "abc";
System.out.println(r.freeMemory());
r.gc();
System.out.println(r.freeMemory());
}
exec()
方法有多种形式,其中一种形式是接收一个将运行的程序名称和调用该程序需要的输入参数。返回 Process 对象。exec()
方法的实现依赖于具体的 OS。Process 对象的 destroy()
方法终止子进程;waitFor()
方法使得当前线程等待,直到 Process 表示的子进程终止;exitValue()
方法返回子进程完成时返回的值。子进程运行时,可以读写标准输入输出,调用 getInputStream()
和 getOutputStream()
方法获取。
public static void m() {
var r = Runtime.getRuntime();
Process p = r.exec("notepad");
p.waitFor();
System.out.println(p.exitValue());
}
Runtime.Version
Runtime.version()
方法返回表示当前平台的 Runtime.Version 对象,它封装了 Java 环境的版本信息,JDK9 引入。
版本信息的前 4 个元素指定了版本号,分别为特性(feature)版本号、临时(interim)版本号、更新版本号和补丁版本号。特性版本号指定了 JDK 的发行版本,从 10 开始;临时版本号出现在两个发行版本之间、更新版本号表示处理安全和其他问题、补丁版本号表示处理紧急问题。后面的元素是可选的。
JDK10 开始,Runtime.Version 引入了下面 4 个方法获取对应类型的版本号
int feature()
int interim()
int update()
int patch()
public static void m() {
Runtime.Version v = Runtime.verion();
System.out.println(v.feature());
System.out.println(v.interim());
System.out.println(v.update());
System.out.println(v.patch());
}
Runtime.Version 对象的 build()
方法返回构建号;pre()
方法返回预发行信息;Optional<String> optional()
方法返回其他版本信息;compareTo(Runtime.Version obj)
和 compareToIgnoreOptional(Runtime.Version obj)
方法比较版本;equals(Object obj)
和 equalsIgnoreOptional(Object obj)
方法比较版本是否相等。List<Integer> version()
方法将版本信息以列表的方式返回;parse(String)
方法接收一个有效版本形式的字符串,将其转换为 Runtime.Version 对象。
ProcessBuilder
ProcessBuilder 类提供了另一种创建和管理进程的方式。
ProcessBuilder(List<String> args);
ProcessBuilder(String... args);
上述的构造器中,第一个参数为程序名,后续的参数为启动程序时指定的命令行参数。
ProcessBuilder 类的部分方法返回 ProcessBuilder.Redirect 抽象类,它封装了和父进程关联的 I/O 源和 target。ProcessBuilder.Redirect 的 to()
方法重定向 I/O target,from()
方法重定向 I/O 源,appendTo()
方法添加文件,file()
方法返回关联的文件对象。
static ProcessBuilder.Redirect to(File f)
static ProcessBuilder.Redirect from(File f)
static ProcessBuilder.Redirect appendTo(File f)
File file()
public static void m() {
var p = new ProcessBuilder("notepad.exe", "inputtext");
p.start();
}
System
System 类有很多静态变量和方法。其中,静态变量 in 表示运行时的标准输入、out 表示运行时的标准输出、err 表示运行时的标准错误输出。currentTimeMillis()
获得从 1970 年开始到当前为止以毫秒为单位流逝的时间。nanoTime()
则获得以纳秒为单位的时间。
public class Elapsed {
// 循环的时间开销为:0 ms
// 循环的时间开销为:1811200 ns
public static void main(String[] args) {
long start = System.currentTimeMillis();
long naStart = System.nanoTime();
for (int i = 0; i < 1_000_000_000; i++) {
int a = 1 + 1;
}
long end = System.currentTimeMillis();
long naEnd = System.nanoTime();
System.out.println("循环的时间开销为:" + (end - start) + " ms");
System.out.println("循环的时间开销为:" + (naEnd - naStart) + " ns");
}
}
arraycopy()
方法用于将一个数组中指定位置开始的元素复制到另一个数组中指定位置。
public class ACDemo {
// a: abcde
// b: mnopq
// a: abcde
// b: abcde
// a: cdede
public static void main(String[] args) {
char[] a = {'a', 'b', 'c', 'd', 'e'};
char[] b = {'m', 'n', 'o', 'p', 'q'};
System.out.println("a: " + new String(a));
System.out.println("b: " + new String(b));
System.arraycopy(a, 0, b, 0, a.length);
System.out.println("a: " + new String(a));
System.out.println("b: " + new String(b));
// 同一个数组可以作为源数组和目标数组
System.arraycopy(a, 2, a, 0, a.length - 2);
System.out.println("a: " + new String(a));
}
}
Object
Object 类的 clone()
方法用于浅复制实现了 Cloneable 接口的类对象。复制之后,如果类中有类类型的成员,则两个对象引用相同的成员对象。打开 I/O 流的对象执行浅复制后会导致两个对象都可以操作打开的流。
Class
Class 对象封装了类或接口的运行时状态,它在类加载时自动创建,不能显式定义 Class 对象。形式为
// T 表示与 Class 对象关联的类或接口的类型
class Class<T>;
public class RTTI {
static class X {
int a;
}
static class Y extends X {
double b;
}
// x 的类型为:a.b.RTTI$X
// y 的类型为:a.b.RTTI$Y
// y 的超类类型为:a.b.RTTI$X
public static void main(String[] args) {
X x = new X();
Y y = new Y();
Class<? extends X> aClass = x.getClass();
System.out.println("x 的类型为:" + aClass.getName());
aClass = y.getClass();
System.out.println("y 的类型为:" + aClass.getName());
System.out.println("y 的超类类型为:" + aClass.getSuperclass().getName());
}
}
ThreadGroup
ThreadGroup 创建一组线程。构造器如下
// 当前线程作为创建的线程组的父线程,线程组名称由 gN 指定
ThreadGroup(String gN);
// 创建的线程组的父线程由 p 指定,线程组名称由 gN 指定
ThreadGroup(ThreadGroup p, String gN);
ThreadGroup 提供了将一组线程作为一个整体管理的方式。
public class ThreadGroupDemo {
static class NewThread extends Thread {
boolean suspend;
NewThread(String name, ThreadGroup g) {
super(g, name);
suspend = false;
}
public void run() {
for (int i = 5; i > 0; i--) {
try {
Thread.sleep(1000);
System.out.println(getName() + " 执行 " + i);
synchronized (this) {
while (suspend) {
wait();
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(getName() + " 退出");
}
synchronized void suspendMethod() {
suspend = true;
}
synchronized void resumeMethod() {
suspend = false;
notify();
}
}
//java.lang.ThreadGroup[name=a,maxpri=10]
// Thread[a1,5,a]
// Thread[a2,5,a]
//
//java.lang.ThreadGroup[name=b,maxpri=10]
// Thread[b1,5,b]
// Thread[b2,5,b]
//
//线程组 a 等待(此时,线程组 a 等待,但修改前的执行无法阻止,如下面 a1 和 a2 执行了 1 次)
//a1 执行 5
//b2 执行 5
//a2 执行 5
//b1 执行 5
//b2 执行 4
//b1 执行 4
//b2 执行 3
//b1 执行 3
//线程组 a 恢复执行
//等待线程完成
//b2 执行 2
//b1 执行 2
//a1 执行 4
//a2 执行 4
//b2 执行 1
//b1 执行 1
//b1 退出
//b2 退出
//a2 执行 3
//a1 执行 3
//a2 执行 2
//a1 执行 2
//a1 执行 1
//a1 退出
//a2 执行 1
//a2 退出
public static void main(String[] args) {
ThreadGroup a = new ThreadGroup("a");
ThreadGroup b = new ThreadGroup("b");
// 将线程分组
NewThread a1 = new NewThread("a1", a);
NewThread a2 = new NewThread("a2", a);
NewThread b1 = new NewThread("b1", b);
NewThread b2 = new NewThread("b2", b);
// 加上当前线程,5 个线程同时执行
a1.start();
a2.start();
b1.start();
b2.start();
// 输出线程组的信息
a.list();
System.out.println();
b.list();
System.out.println();
Thread[] threads = new Thread[a.activeCount()];
a.enumerate(threads);
// 暂停线程组 a 的所有线程
for (Thread t : threads) {
((NewThread)t).suspendMethod();
}
System.out.println("线程组 a 等待");
try {
// 当前线程休眠
Thread.sleep(4000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("线程组 a 恢复执行");
// 恢复线程组 a 的所有线程
for (Thread t : threads) {
((NewThread)t).resumeMethod();
}
System.out.println("等待线程完成");
try {
// 等待线程执行完成
a1.join();
a2.join();
b1.join();
b2.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
参考
[1] Herbert Schildt, Java The Complete Reference 11th, 2019.
[2] difference-between-javas-double-min-normal-and-double-min-value
[3] double-min-value-vs-double-min-normal
[4] What-is-a-binary-point
[5] https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html
[6] https://docs.oracle.com/javase/9/docs/api/java/lang/Runtime.Version.html
[7] https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ProcessBuilder.html
[8] https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ProcessBuilder.Redirect.html
[9] https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/ProcessBuilder.Redirect.Type.html