# 数据管理
这一部分简单的讨论一下 Java 的数据类型和数据存储的问题.
# 数据类型
Java 中的数据类型和 JS 中的比较相似, 包括了基本类型和对象类型. 可能会好奇, Java 不是纯面向对象的语言吗? 那保留基本类型是不是违背了这个准则? 我觉得是这样, 但是从性能方面考虑, 将一个简单的数字或字符封装成一个对象, 实在不像个好主意.
# 基本类型
Java 的基本类型一共八种, 表中的 void
是当变量没有被赋值时的值. 基本类型不会创建对象, 而是创建一个并非是引用的「自动」变量, 直接存储「值」;
得益于 JVM 的底层支持, Java 的基本类型并不会像 C 那样, 在不同的设备上有不同的大小;
基本类型 | 默认值 | 大小 | 最小值 | 最大值 | 包装器类型 |
---|---|---|---|---|---|
boolean | false | - | - | - | Boolean |
char | '\u0000' (null) | 16 bit | Unicode 0 | Unicode 2^16 - 1 | Character |
byte | (byte) 0 | 8 bit | -128 | 127 | Byte |
short | (short) 0 | 16 bit | -12^15 | 2^15 - 1 | Short |
int | 0 | 32 bit | -2^31 | 2^31 - 1 | Integer |
long | 0L | 64 bit | -2^63 | 2^63 - 1 | Long |
float | 0.0f | 32 bit | IEEE754 | IEEE754 | Float |
double | 0.0d | 64 bit | IEEE754 | IEEE754 | Double |
void | - | - | - | - | - |
# 对象类型
Java 要求使用引用操纵对象. 这个地方, 我们看一看两个比较特殊的对象: 数组和字符串
# 数组
如果说 String[] a
看起来还倒像即将引用一个对象, 那么 int[] a
就给人感觉距离对象有些遥远了. 但是不管是哪种形式的数组, 本质上都是对象.
数组的创建是在运行时进行的, 所有的数组都会包含一个固定成员, 即 length , 标明数组的长度. 从另一角度来说, 数组的长度是运行时确定的, 而且不可修改; 相比之下, C11 版本之前要求数组的长度在编译时被确定, 就显得很不方便.
// 初始化数组的两种方式
int[] a = {1, 2};
int[] a = new int[length];
2
3
代码中, 第二种初始化数组的方式看起来要好得多, 长得更像是创建一个数组对象.
Java 的数据类型分为基本类型和对象类型, 那么数组里面就可能有存在基本类型和对象引用. 如果数组元素在创建时没有被赋值, 前者将被自动赋为相应基本类型的默认值, 后者将被赋值为 null
.
对于数组拷贝, 拷贝可以有两类内容, 一是数组的引用, 二是数组对象本身. 数组引用和其他对象引用的操作一致, 而数组对象本身可以使用 Array.copyOf
方法.
# 字符串
从编译器的角度, 他偏爱共享 String 对象. 如果我们在一个程序中的两个位置分别使用了具有相同字面量的字符串, 那么就会有两个 String 类型的引用指向同一个 String 对象.
在 Java 中, 每当我们认为自己是在修改一个 String 对象时, 其实是在创建新的 String 对象.
TIP
在一些情况下生成的 String 对象, 例如 +
substring
等, 编译器是不会共享.
在编译器的这种偏好下, 问题产生了: 如何比较两个 String 对象的字面量是否相同
String a = "hello";
System.out.println(a.substring(0, 3) == "hel"); // false
System.out.println("hel".equals(a.substring(0, 3))); // true
2
3
上面的代码使用了 equals
方法, String 类专门重写了这个方法, 将其由父类的 ==
对象地址比较改为值比较.
与 String 相关的还有一个重要的东西: StringBuilder
字符串构造器. 因为每次修改一个 String 对象就会创建一个新的 String 对象. 如果有大量的字符串拼接需求, 这种方式是低效的.
StringBuilder builder = new StringBuilder();
builder.append(ch);
bui1der.append(str);
String completedString = builder.toString(); // 输出拼接结果
2
3
4
最后的最后, 提一下码点和代码单元. Java 的每个字符使用 Unicode 编码.
Java 中的每个字符都对应一个码点, 即一个 Unicode 的具体编码.
Java 的 char 为 16bits , 这个尺寸就是 Java 代码单元的大小, 大部分的基础字符都是一个代码单元的大小, 但是有些特殊字符会占用两个代码单元.
# 数据装(拆)箱与转型
# 装(拆)箱
基本类型如何与对象相互转换? JVM 支持自动打(拆)包, 在必要的时候, 将基本类型转成相应的包装器类型, 同时, 也允许将包装器类型转为基本类型.
# 转型
转型包括向上转型与向下转型. 在笔记一开始讨论过, 为了减小耦合, 应该尽可能地操纵基类的引用, 想办法将对象向更加抽象(向上)的方向转变; 但是, 向上转型意味着屏蔽了子类对象的一些属性或者方法, 那么在必要的时候, 还需要强制类型转换来向下转型, 保证正常的功能.
一般来说, 将一个对象向上转型成其基类的对象是一件比较安全的事情, Java 的继承机制允许子类对象直接代替基类对象; 但是将一个标明了是基类的对象向下转型为子类的对象, 是比较危险的, 毕竟不能无中生有.
# 数据存储
存储数据的 5 个位置
- 寄存器
高速存储器 - 堆栈
位于内存, 「引用」数据和「基本类型」在其中, Java 对象不在其中; Java 系统需要知道堆栈内所有顶的确切生命周期; - 堆
位于内存, 一个通用内存池, 存储所有 Java 对象 相比堆栈内的数据管理, 堆内的数据管理要灵活的多 - 常量存储
代码内部 - 非内存存储
- 流对象(网路中)
- 持久化对象(数据库中)
TIP
数据存储中两个非常重要的位置, 堆栈 (存储对象引用和基本类型) 和堆 (存储对象).