Java中的数据类型
<h1 id="java-数据类型详解">Java 数据类型详解</h1><h2 id="目录">目录</h2>
<ul>
<li>数据类型分类</li>
<li>基本数据类型</li>
<li>引用类型</li>
<li>基本类型 vs 引用类型</li>
<li>包装类</li>
<li>String 详解</li>
<li>字符串常量池</li>
<li>内存布局</li>
<li>对象生命周期</li>
<li>常见问题</li>
</ul>
<hr>
<h2 id="数据类型分类">数据类型分类</h2>
<pre><code>Java 数据类型
├── 基本类型(8种)
│ ├── 整数类型:byte, short, int, long
│ ├── 浮点类型:float, double
│ ├── 字符类型:char
│ └── 布尔类型:boolean
└── 引用类型
├── 类(如 String)
├── 接口
└── 数组
</code></pre>
<hr>
<h2 id="基本数据类型">基本数据类型</h2>
<h3 id="整数类型">整数类型</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>位数</th>
<th>范围</th>
<th>默认值</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>byte</code></td>
<td>8位</td>
<td>-128 ~ 127</td>
<td>0</td>
<td><code>byte b = 100;</code></td>
</tr>
<tr>
<td><code>short</code></td>
<td>16位</td>
<td>-32,768 ~ 32,767</td>
<td>0</td>
<td><code>short s = 10000;</code></td>
</tr>
<tr>
<td><code>int</code></td>
<td>32位</td>
<td>-21亿 ~ 21亿</td>
<td>0</td>
<td><code>int i = 100000;</code></td>
</tr>
<tr>
<td><code>long</code></td>
<td>64位</td>
<td>极大</td>
<td>0L</td>
<td><code>long l = 100000L;</code></td>
</tr>
</tbody>
</table>
<pre><code class="language-java">bytea = 127; // ✅ 最大值
// byte b = 128;// ❌ 超出范围
int c = 100;
longd = 100000L; // long 需加 L 或 l
longe = 10000000000L; // ✅ 超出int范围必须加L
// longf = 10000000000; // ❌ 编译错误,超出 int 范围
</code></pre>
<h3 id="浮点类型">浮点类型</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>位数</th>
<th>精度</th>
<th>默认值</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>float</code></td>
<td>32位</td>
<td>单精度(7位有效数字)</td>
<td>0.0f</td>
<td><code>float f = 3.14f;</code></td>
</tr>
<tr>
<td><code>double</code></td>
<td>64位</td>
<td>双精度(15位有效数字)</td>
<td>0.0</td>
<td><code>double d = 3.14;</code></td>
</tr>
</tbody>
</table>
<pre><code class="language-java">double a = 3.14; // 默认是 double
// float b = 3.14;// ❌ 编译错误
floatc = 3.14f; // ✅
</code></pre>
<h3 id="字符类型">字符类型</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>位数</th>
<th>说明</th>
<th>默认值</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>char</code></td>
<td>16位</td>
<td>单个 Unicode 字符</td>
<td>\u0000</td>
<td><code>char c = 'A';</code></td>
</tr>
</tbody>
</table>
<pre><code class="language-java">char a = 'A'; // 字符
char b = '中'; // 中文
char c = 65; // 数字对应字符 'A'
char d = '\u0041';// Unicode 转义,'A'
char e = '\u0000';// null 字符,char 的默认值
// char f = 'AB';// ❌ char 只能存单个字符
</code></pre>
<h3 id="布尔类型">布尔类型</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>取值</th>
<th>默认值</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>boolean</code></td>
<td>true, false</td>
<td>false</td>
<td><code>boolean flag = true;</code></td>
</tr>
</tbody>
</table>
<pre><code class="language-java">boolean a = true;
boolean b = false;
</code></pre>
<hr>
<h2 id="引用类型">引用类型</h2>
<h3 id="什么是引用类型">什么是引用类型</h3>
<p><strong>引用类型存储的是对象的地址(引用),对象本身存储在堆中。</strong></p>
<h3 id="常见引用类型">常见引用类型</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>说明</th>
<th>示例</th>
</tr>
</thead>
<tbody>
<tr>
<td>String</td>
<td>字符串类</td>
<td><code>String s = "hello";</code></td>
</tr>
<tr>
<td>数组</td>
<td>数组</td>
<td><code>int[] arr = {1, 2, 3};</code></td>
</tr>
<tr>
<td>接口</td>
<td>接口类型</td>
<td><code>List<String> list;</code></td>
</tr>
<tr>
<td>自定义类</td>
<td>用户定义的类</td>
<td><code>Person p = new Person();</code></td>
</tr>
</tbody>
</table>
<hr>
<h2 id="基本类型-vs-引用类型">基本类型 vs 引用类型</h2>
<table>
<thead>
<tr>
<th>对比维度</th>
<th>基本类型</th>
<th>引用类型(如 String)</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>存储位置</strong></td>
<td>栈中,直接存值</td>
<td>栈存引用,堆存对象</td>
</tr>
<tr>
<td><strong>大小</strong></td>
<td>固定(如 int 4字节)</td>
<td>不固定</td>
</tr>
<tr>
<td><strong>默认值</strong></td>
<td>0/false/\u0000</td>
<td>null</td>
</tr>
<tr>
<td><strong>比较</strong></td>
<td><code>==</code> 比较值</td>
<td><code>==</code> 比较地址,<code>equals()</code> 比较内容</td>
</tr>
</tbody>
</table>
<h3 id="内存图示">内存图示</h3>
<pre><code>基本类型:
栈
┌─────────┐
│ int a │ = 10
└─────────┘
直接存值
引用类型:
栈 堆
┌─────────┐ ┌─────────┐
│String s │ ────────→│ "hello" │
└─────────┘ └─────────┘
引用变量 实际对象
</code></pre>
<h3 id="比较示例">比较示例</h3>
<pre><code class="language-java">// 基本类型
int a = 10;
int b = 10;
System.out.println(a == b);// true(比较值)
// 引用类型
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false(不同地址)
System.out.println(s1.equals(s2)); // true(内容相同)
</code></pre>
<hr>
<h2 id="包装类">包装类</h2>
<p>每个基本类型都有对应的包装类:</p>
<table>
<thead>
<tr>
<th>基本类型</th>
<th>包装类</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>byte</code></td>
<td><code>Byte</code></td>
<td></td>
</tr>
<tr>
<td><code>short</code></td>
<td><code>Short</code></td>
<td></td>
</tr>
<tr>
<td><code>int</code></td>
<td><code>Integer</code></td>
<td></td>
</tr>
<tr>
<td><code>long</code></td>
<td><code>Long</code></td>
<td></td>
</tr>
<tr>
<td><code>float</code></td>
<td><code>Float</code></td>
<td></td>
</tr>
<tr>
<td><code>double</code></td>
<td><code>Double</code></td>
<td></td>
</tr>
<tr>
<td><code>char</code></td>
<td><code>Character</code></td>
<td></td>
</tr>
<tr>
<td><code>boolean</code></td>
<td><code>Boolean</code></td>
<td></td>
</tr>
</tbody>
</table>
<pre><code class="language-java">int a = 10; // 基本类型
Integer b = 10; // 包装类
Integer c = Integer.valueOf(10); // 显式创建
// 自动装箱/拆箱
Integer d = a; // 自动装箱
int e = d; // 自动拆箱
</code></pre>
<hr>
<h2 id="string-详解">String 详解</h2>
<h3 id="string-是什么">String 是什么?</h3>
<p><strong>String 是类,不是基本类型,是不可变的引用类型。</strong></p>
<h3 id="string-的不可变性">String 的不可变性</h3>
<pre><code class="language-java">String a = "hello";
String b = a; // a 和 b 指向同一对象
a = "world"; // a 指向新对象
System.out.println(b); // 输出: hello(b 不变)
System.out.println(a == b); // false(已不是同一对象)
</code></pre>
<pre><code>内存变化:
修改前:
a ──→ ["hello"]
↑
b
修改后:
a ──→ ["world"]← 新对象
b ──→ ["hello"]← 原对象不变
</code></pre>
<h3 id="为什么-string-不可变">为什么 String 不可变?</h3>
<pre><code class="language-java">// String 简化版源码
public final class String {
private final char value[];// final,不能修改
// 所有修改操作都返回新 String
public String substring(int beginIndex) {
return new String(value, beginIndex, count);
}
}
</code></pre>
<table>
<thead>
<tr>
<th>原因</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>不可变</strong></td>
<td>String 对象一旦创建,内容不能改</td>
</tr>
<tr>
<td><strong>final</strong></td>
<td>String 类内部用 final 修饰字符数组</td>
</tr>
<tr>
<td><strong>安全</strong></td>
<td>多线程环境下,无需加锁就能安全共享</td>
</tr>
</tbody>
</table>
<h3 id="string-vs-可变对象">String vs 可变对象</h3>
<pre><code class="language-java">// String - 不可变
String a = "hello";
String b = a;
a = "world";
System.out.println(b);// hello(不变)
// StringBuilder - 可变
StringBuilder sb1 = new StringBuilder("hello");
StringBuilder sb2 = sb1;
sb1.append(" world");
System.out.println(sb2);// hello world(变了!)
</code></pre>
<hr>
<h2 id="字符串常量池">字符串常量池</h2>
<h3 id="什么是字符串常量池">什么是字符串常量池</h3>
<pre><code>┌─────────────────────────────────────────────────────┐
│ 字符串常量池 │
│(JVM 堆内存中的一块特殊区域,专门存储字符串字面量)│
│ │
│ "hello" │
│ "world" │
│ "java" │
└─────────────────────────────────────────────────────┘
</code></pre>
<h3 id="字面量-vs-new">字面量 vs new</h3>
<table>
<thead>
<tr>
<th>方式</th>
<th>写法</th>
<th>存储位置</th>
<th>是否复用</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>隐式(字面量)</strong></td>
<td><code>String s = "hello";</code></td>
<td>字符串常量池</td>
<td>✅ 复用</td>
</tr>
<tr>
<td><strong>显式(new)</strong></td>
<td><code>String s = new String("hello");</code></td>
<td>堆内存(新对象)</td>
<td>❌ 新对象</td>
</tr>
<tr>
<td><strong>动态创建</strong></td>
<td><code>scanner.nextLine()</code></td>
<td>堆内存(新对象)</td>
<td>❌ 新对象</td>
</tr>
</tbody>
</table>
<h3 id="字面量隐式赋值">字面量(隐式赋值)</h3>
<pre><code class="language-java">String a = "hello";
String b = "hello";
</code></pre>
<p><strong>流程:</strong></p>
<ol>
<li>JVM 检查常量池中是否有 <code>"hello"</code></li>
<li>有 → 直接引用(复用)</li>
<li>无 → 在常量池创建 <code>"hello"</code>,然后引用</li>
</ol>
<pre><code>常量池:
┌─────────┐
│ "hello" │ ← a ─┐
└─────────┘ │
│ 两者指向同一对象
├─ b
</code></pre>
<pre><code class="language-java">System.out.println(a == b);// true(同一对象)
</code></pre>
<h3 id="new显式赋值">new(显式赋值)</h3>
<pre><code class="language-java">String c = new String("hello");
String d = new String("hello");
</code></pre>
<p><strong>流程:</strong></p>
<ol>
<li>先在常量池检查/创建 <code>"hello"</code></li>
<li>然后在堆内存<strong>强制创建新对象</strong></li>
<li>变量引用堆上的新对象(不是常量池)</li>
</ol>
<pre><code>常量池: 堆内存:
┌─────────┐ ┌─────────┐
│ "hello" │ ───→ │ "hello" │ ← c
└─────────┘ └─────────┘
┌─────────┐
│ "hello" │ ← d
└─────────┘
</code></pre>
<pre><code class="language-java">System.out.println(c == d); // false(不同对象)
System.out.println(c == a); // false(c 在堆,a 在常量池)
System.out.println(c.equals(a)); // true(值相同)
</code></pre>
<h3 id="scanner-输入">scanner 输入</h3>
<pre><code class="language-java">String c = scanner.nextLine(); // 输入: hello
String d = scanner.nextLine(); // 输入: hello
System.out.println(c == d); // false(每次新对象)
</code></pre>
<hr>
<h2 id="内存布局">内存布局</h2>
<h3 id="java-版本差异">Java 版本差异</h3>
<table>
<thead>
<tr>
<th>版本</th>
<th>常量池位置</th>
</tr>
</thead>
<tbody>
<tr>
<td>Java 6 及之前</td>
<td>方法区(永久代 PermGen)</td>
</tr>
<tr>
<td>Java 7+</td>
<td><strong>堆</strong></td>
</tr>
<tr>
<td>Java 8+</td>
<td><strong>堆</strong>(方法区改为元空间 Metaspace)</td>
</tr>
</tbody>
</table>
<p><strong>现代 Java(7+):字符串常量池在堆上。</strong></p>
<h3 id="完整内存布局">完整内存布局</h3>
<pre><code>String s1 = "first";
String s2 = new String("second");
栈:
┌─────────┐ ┌─────────┐
│ s1 │ │ s2 │
└─────────┘ └─────────┘
↓ 引用 ↓ 引用
堆(都在堆上):
┌───────────────────────────────────────────────────┐
│ │
│常量池区域: 普通堆区域: │
│┌─────────┐ ┌─────────────────────┐ │
││ "first" │ │ String对象 │ │
││"second" │ │ value → "second" │ │
│└─────────┘ └─────────────────────┘ │
│ ↓ │
│ ┌─────────┐ │
│ │"second" │ ← 在常量池 │
│ └─────────┘ │
│ │
└───────────────────────────────────────────────────┘
</code></pre>
<h3 id="new-stringxxx-的步骤">new String("xxx") 的步骤</h3>
<pre><code>1. 先处理 "xxx" 字面量
→ 检查常量池有没有 "xxx"
→ 没有 → 在常量池创建 "xxx"
→ 有 → 复用
2. 再执行 new String()
→ 在普通堆创建新对象
→ 对象内部引用常量池的 "xxx"
</code></pre>
<pre><code class="language-java">String s = new String("second");
// 等价于:
String literal = "second"; // 先处理字面量,进常量池
String s = new String(literal); // 再创建堆对象
</code></pre>
<hr>
<h2 id="对象生命周期">对象生命周期</h2>
<h3 id="对象的一生">对象的一生</h3>
<pre><code>┌─────────────────────────────────────────────────────────┐
│ 对象一生 │
├─────────────────────────────────────────────────────────┤
│ │
│1. 创建 →new String() / scanner.nextLine() │
│ ↓ │
│2. 使用 →被变量引用,可以访问 │
│ ↓ │
│3. 失去引用 →变量指向其他对象或离开作用域 │
│ ↓ │
│4. 可回收 →等待 GC 清理 │
│ ↓ │
│5. 回收 →GC 自动回收内存 │
│ │
└─────────────────────────────────────────────────────────┘
</code></pre>
<h3 id="示例引用丢失">示例:引用丢失</h3>
<pre><code class="language-java">String s1 = scanner.nextLine();// 创建对象
s1 = scanner.nextLine(); // s1 指向新对象,原对象可被回收
</code></pre>
<h3 id="垃圾回收gc">垃圾回收(GC)</h3>
<p><strong>GC = Garbage Collection,Java 自动内存管理机制。</strong></p>
<pre><code>GC 工作原理:
1. 定期扫描堆内存
2. 找出没有被引用的对象
3. 回收内存
</code></pre>
<h3 id="不同区域的回收策略">不同区域的回收策略</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>生命周期</th>
<th>回收方式</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>字面量(常量池)</strong></td>
<td>程序运行期间</td>
<td>一般不回收</td>
</tr>
<tr>
<td><strong>动态对象(堆)</strong></td>
<td>到无引用时</td>
<td>GC 回收</td>
</tr>
<tr>
<td><strong>基础类型(栈)</strong></td>
<td>作用域内</td>
<td>自动销毁</td>
</tr>
</tbody>
</table>
<pre><code class="language-java">String a = "hello";// 常量池,程序运行期间存在
String b = scanner.nextLine();// 堆对象
b = null;// 失去引用,等待 GC
</code></pre>
<h3 id="intern-方法">intern() 方法</h3>
<p><strong>将字符串内容放入常量池。</strong></p>
<pre><code class="language-java">String s = new String("aaa");
s.intern();// 将 "aaa" 内容放入常量池(如果还没)
</code></pre>
<pre><code class="language-java">String s1 = new String("aaa");
String s2 = new String("aaa");
String s3 = "aaa";// 字面量,常量池
String s4 = s1.intern();// 返回常量池中的引用
System.out.println(s1 == s2); // false(堆对象,不同)
System.out.println(s1 == s3); // false(堆 vs 常量池)
System.out.println(s3 == s4); // true(都是常量池)
</code></pre>
<hr>
<h2 id="常见问题">常见问题</h2>
<h3 id="q1-u0000-是什么">Q1: \u0000 是什么?</h3>
<p><strong>\u0000</strong> 是 Unicode 字符的转义表示,表示 Unicode 值为 0 的字符。</p>
<table>
<thead>
<tr>
<th>方面</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Unicode 值</strong></td>
<td>0</td>
</tr>
<tr>
<td><strong>名称</strong></td>
<td>null 字符 / 空字符</td>
</tr>
<tr>
<td><strong>可见性</strong></td>
<td>不可见(控制字符)</td>
</tr>
<tr>
<td><strong>Java 中</strong></td>
<td>char 的默认值</td>
</tr>
</tbody>
</table>
<pre><code class="language-java">char c = '\u0000'; // null 字符
char d = 0; // 等价写法
System.out.println(c == d); // true
System.out.println((int)c); // 输出: 0
</code></pre>
<h3 id="q2-string-引用相同但值不同">Q2: String 引用相同但值不同?</h3>
<p><strong>对于 String,不可能。</strong></p>
<pre><code class="language-java">String a = "hello";
String b = a;
a = "world";
System.out.println(b);// hello(b 不变)
</code></pre>
<p><strong>核心原因:</strong> String 是不可变的,引用相同则值必相同。</p>
<h3 id="q3-scannernextline-返回什么">Q3: scanner.nextLine() 返回什么?</h3>
<p><strong>永远返回 String,无论输入什么。</strong></p>
<pre><code class="language-java">Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine();// 输入 27 → "27"(字符串)
// 如果要转整数
int num = Integer.parseInt(input);// "27" → 27
</code></pre>
<table>
<thead>
<tr>
<th>方法</th>
<th>输入 27</th>
<th>返回类型</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>nextLine()</code></td>
<td><code>"27"</code></td>
<td>String</td>
</tr>
<tr>
<td><code>nextInt()</code></td>
<td><code>27</code></td>
<td>int</td>
</tr>
<tr>
<td><code>nextDouble()</code></td>
<td><code>27.0</code></td>
<td>double</td>
</tr>
</tbody>
</table>
<h3 id="q4-常量池在堆上吗">Q4: 常量池在堆上吗?</h3>
<p><strong>是的,现代 Java(7+)中,字符串常量池在堆上。</strong></p>
<p>但常量池是堆中特殊管理的区域,与普通堆对象有区别:</p>
<table>
<thead>
<tr>
<th>对比维度</th>
<th>常量池对象</th>
<th>普通堆对象</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>位置</strong></td>
<td>堆(特殊区域)</td>
<td>堆(普通区域)</td>
</tr>
<tr>
<td><strong>创建方式</strong></td>
<td>字面量、intern()</td>
<td>new String()</td>
</tr>
<tr>
<td><strong>去重</strong></td>
<td>✅ 自动去重</td>
<td>❌ 不去重</td>
</tr>
<tr>
<td><strong>生命周期</strong></td>
<td>一般长期存在</td>
<td>无引用后 GC 回收</td>
</tr>
</tbody>
</table>
<hr>
<h2 id="总结速查表">总结速查表</h2>
<h3 id="基本类型速查">基本类型速查</h3>
<table>
<thead>
<tr>
<th>类型</th>
<th>位数</th>
<th>范围</th>
<th>默认值</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>byte</code></td>
<td>8</td>
<td>-128 ~ 127</td>
<td>0</td>
</tr>
<tr>
<td><code>short</code></td>
<td>16</td>
<td>±3.2万</td>
<td>0</td>
</tr>
<tr>
<td><code>int</code></td>
<td>32</td>
<td>±21亿</td>
<td>0</td>
</tr>
<tr>
<td><code>long</code></td>
<td>64</td>
<td>极大</td>
<td>0L</td>
</tr>
<tr>
<td><code>float</code></td>
<td>32</td>
<td>单精度</td>
<td>0.0f</td>
</tr>
<tr>
<td><code>double</code></td>
<td>64</td>
<td>双精度</td>
<td>0.0</td>
</tr>
<tr>
<td><code>char</code></td>
<td>16</td>
<td>单字符</td>
<td>\u0000</td>
</tr>
<tr>
<td><code>boolean</code></td>
<td>1</td>
<td>true/false</td>
<td>false</td>
</tr>
</tbody>
</table>
<h3 id="string-对比速查">String 对比速查</h3>
<table>
<thead>
<tr>
<th>操作</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>String a = "hello";</code> <br> <code>String b = "hello";</code></td>
<td><code>a == b</code> → <strong>true</strong></td>
</tr>
<tr>
<td><code>String c = new String("hello");</code> <br> <code>String d = new String("hello");</code></td>
<td><code>c == d</code> → <strong>false</strong></td>
</tr>
<tr>
<td><code>a == c</code></td>
<td><strong>false</strong>(常量池 vs 堆)</td>
</tr>
<tr>
<td><code>a.equals(c)</code></td>
<td><strong>true</strong>(值相同)</td>
</tr>
</tbody>
</table>
<h3 id="记忆口诀">记忆口诀</h3>
<pre><code>基础类型 引用类型
↓ ↓
栈 栈 + 堆
直接值 引用+对象
字面量 scanner输入
↓ ↓
常量池 堆新对象
可复用 不复用
String→ 引用同,值必同(不可变)
StringBuilder → 引用同,值随变(可变)
</code></pre>
<p>注:同步发布于金蝶开发者社区:Java数据类型</p><br><br>
来源:https://www.cnblogs.com/chenxvhua/p/19563547
頁:
[1]