Java 有值类型吗?

有人看了我之前的文章『Swift 语言的设计错误』,问我:“你说 Java 只有引用类型(reference type),但是根据 Java 的官方文档,Java 也有值类型(value type)和引用类型的区别的。比如 int,boolean 等原始类型就是值类型。” 现在我来解释一下这个问题。

Java 有值类型,原始类型 int,boolean 等是值类型,其实是长久以来的一种误解,它混淆了实现和语义的区别。不要以为 Java 的官方文档那样写就是权威定论,就可以说“王垠不懂” :) 当你认为王垠不懂一个初级问题的时候,都需要三思,因为他可能是大智若愚…… 看了我下面的论述,也许你会发现自己应该怀疑的是,Java 的设计者到底有没有搞明白这个问题 :P

胡扯结束,现在来说正事。Java,Scheme 等语言的原始类型,比如 char,int,boolean,double 等,在“实现”上确实是通过值(而不是引用,或者叫指针)直接传递的,然而这完全是一种为了效率的优化(叫做 inlining)。这种优化对于程序员应该是不可见的。Java 继承了 Scheme/Lisp 的衣钵,它们在“语义”上其实是没有值类型的。

这不是天方夜谭,为了理解这一点,你可以做一个很有意思的思维实验。现在你把 Java 里面所有的原始类型都“想象”成引用类型,也就是说,所有的 int, boolean 等原始类型的变量都不包含实际的数据,而是引用(或者叫指针),指向堆上分配的数据。然后你会发现这样“改造后”的 Java,仍然符合现有 Java 代码里能看到的一切现象。也就是说,原始类型被作为值类型还是引用类型,对于程序员完全没有区别。

举个简单的例子,如果我们把 int 的实现变成完全的引用,然后来看这段代码:

int x = 1;    // x指向内存地址A,内容是整数1
int y = x; // y指向同样的内存地址A,内容是整数1
x = 2; // x指向另一个内存地址B,内容是整数2。y仍然指向地址A,内容是1。

由于我们改造后的 Java 里面 int 全部是引用,所以第一行定义的 x 并不包含一个整数,而是一个引用,它指向堆里分配的一块内存,这个空间的内容是整数 1。在第二行,我们定 int 变量 y,当然它也是一个引用,它的值跟 x 一样,所以 y 也指向同一个地址,里面的内容是同一个整数:1。在第三行,我们对 x 这个引用赋值。你会发现一个很有意思的现象,虽然 x 指向了 2,y 却仍然指向 1。对 x 赋值并没能改变 y 指向的内容,这种情况就跟 int 是值类型的时候一模一样!所以现在虽然 int 变量全部是引用,你却不能实现共享地址的引用能做的事情:对 x 进行某种操作,导致 y 指向的内容也发生改变。

出现这个现象的原因是,虽然现在 int 成了引用类型,你却并不能对它进行引用类型所特有(而值类型没有)的操作。这样的操作包括:

  1. deref。就像 C 语言里的 * 操作符。
  2. 成员赋值。就像对 C struct 成员的 x.foo = 2

在 Java 里,你没法写像 C 语言的 *x = 2 这样的代码,因为 Java 没有提供 deref 操作符 *。你也没法通过 x.foo = 2 这样的语句改变 x 所指向的内存数据(内容是1)的一部分,因为 int 是一个原始类型。最后你发现,你只能写 x = 2,也就是改变 x 这个引用本身的指向。x = 2 执行之后,原来数字 1 所在的内存空间并没有变成 2,只不过 x 指向了新的地址,那里装着数字 2 而已。指向 1 的其它引用变量比如 y,不会因为你进行了 x = 2 这个操作而看到 2,它们仍然看到原来那个1……

在这种 int 是引用的 Java 里,你对 int 变量 x 能做的事情只有两种:

  1. 读出它的值。
  2. 对它进行赋值,使它指向另一个地方。

这两种事情,就跟你能对值类型能做的两件事情没有区别。这就是为什么你没法通过对 x 的操作而改变 y 表示的值。所以不管 int 在实现上是传递值还是传递引用,它们在语义上都是等价的。也就是说,原始类型是值类型还是引用类型,对于程序员来说完全没有区别。你完全可以把 Java 所有的原始类型都想成引用类型,之后你能对它们做的事情,你的编程思路和方式,都不会因此有任何的改变。

从这个角度来看,Java 在语义上是没有值类型的。值类型和引用类型如果同时并存,程序员必须能够在语义上感觉到它们的不同,然而不管原始类型是值类型还是引用类型,作为程序员,你无法感觉到任何的不同。所以你完全可以认为 Java 只有引用类型,把原始类型全都当成引用类型来用,虽然它们确实是用值实现的。

一个在语义上有值类型的语言(比如 C#,Go 和 Swift)必须具有以下两种特性之一(或者两者都有),程序员才能感觉到值类型的存在:

  1. deref 操作。这使得你可以用 *x = 2 这样的语句来改变引用指向的内容,导致共享地址的其它引用看到新的值。你没法通过 x = 2 让其他值变量得到新的值,所以你感觉到值类型的存在。
  2. 像 struct 这样的“值组合类型”。你可以通过 x.foo = 2 这样的成员赋值改变引用数据(比如 class object)的一部分,使得共享地址的其它引用看到新的值。你没法通过成员赋值让另一个 struct 变量得到新的值,所以你感觉到值类型的存在。

实际上,所有的数据都是引用类型就是 Scheme 和 Java 最初的设计原理。原始类型用值来传递数据只是一种性能优化(叫做 inlining),它对于程序员应该是透明(看不见)的。那些在面试时喜欢问“Java 是否所有数据都是引用”,然后当你回答“是”的时候纠正你说“int,boolean 是值类型”的人,都是本本主义者。

思考题

有人指出,Java 的引用类型可以是 null,而原始类型不行,所以引用类型和值类型还是有区别的。但是其实这并不能否认本文指出的观点,你可以想想这是为什么吗?

【转】Java 有值类型吗?的更多相关文章

  1. JAVA中值类型和引用类型的不同(面试常考)

    转载:https://www.cnblogs.com/1ming/p/5227944.html 1. JAVA中值类型和引用类型的不同? [定义] 引用类型表示你操作的数据是同一个,也就是说当你传一个 ...

  2. 实战 Java 16 值类型 Record - 2. Record 的基本用法

    在上一篇文章实战 Java 16 值类型 Record - 1. Record 的默认方法使用以及基于预编译生成相关字节码的底层实现中,我们详细分析了 Record 自带的属性以及方法和底层字节码与实 ...

  3. java中值类型和引用类型的区别

    [定义] 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值,那么调用这个方法是传入的变量的值也将改变. 值类型表示复制一个当前变量传给方法,当你 ...

  4. java中值类型与引用类型的关系

    值类型:就是java的基本类型.byte.short.int.long.float.char.double.boolean 引用类型:类(class).接口(Interface).数组(Array) ...

  5. Java的值类型和引用类型

    一.问题描述 前几天因为一个需求出现了Bug.说高级点也挺高级,说白点也很简单.其实也就是一个很简单的Java基础入门时候的值类型和引用类型的区别.只是开发的时候由于自己的问题,导致小问题的出现.还好 ...

  6. 学习记录 java 值类型和引用类型的知识

    1. Java中值类型和引用类型的不同? [定义] 引用类型表示你操作的数据是同一个,也就是说当你传一个参数给另一个方法时,你在另一个方法中改变这个变量的值, 那么调用这个方法是传入的变量的值也将改变 ...

  7. java包装类和值类型的关系

    java包装类总是让人疑惑 它与值类型到底是怎么样一种关系? 本文将以int和Integer为例来探讨它们的关系 java值类型有int short char boolean byte long fl ...

  8. Java中引用类型变量,对象,值类型,值传递,引用传递 区别与定义

    一.Java中什么叫做引用类型变量?引用:就是按内存地址查询       比如:String s = new String();这个其实是在栈内存里分配一块内存空间为s,在堆内存里new了一个Stri ...

  9. JAVA初学(1):值类型和引用类型的区别

    JAVA值类型和引用类型的区别(转)                                                          [定义] 引用类型表示你操作的数据是同一个,也就 ...

随机推荐

  1. 解决Android Studio提示gradle project sync failed报错的解决方法

    运行的时候报错,提示:gradle project sync failed 1.打开AS,切换到project目录结构依次进入目录app->gradle->gradle-wrapper.p ...

  2. PAT 1069 1070 1071 1072

    pat 1069 The Black Hole of Numbers 水题,代码如下: #include<cstdio> #include<cstdlib> #include& ...

  3. SpringMVC的页面几种返回方式

    package com.boventech.learning.controller; import java.util.HashMap; import java.util.Map; import or ...

  4. 算法笔记_202:第三届蓝桥杯软件类决赛真题(Java高职)

    目录 1 填算式 2 提取子串 3 机器人行走 4 地址格式转换 5 排日程   前言:以下代码仅供参考,若有错误欢迎指正哦~ 1 填算式 [结果填空] (满分11分) 看这个算式: ☆☆☆ + ☆☆ ...

  5. Atlas:ERROR 1105 (HY000): #07000Proxy Warning - IP Forbidden

    1:遇到一个奇怪的问题 Atlas的管理接口正常 添加一个client之后save config mysql -uroot -p -P1234 -h127.0.0.1 报错了:ERROR 1105 ( ...

  6. cocos2d-js 3.0 rc0 编译release报错 value for keystore is not valid. it must resolve to a single path

    第一次编译是好好的,需要手工输入keystore文件地址和密码等等.第二次不需要输入,然后就直接出错了.   找了一下,发现第一步之后,cocos会记录ant信息到\frameworks\runtim ...

  7. Tomcat中的session实现

    Tomcat中一个会话对应一个session,其实现类是StandardSession,查看源码,可以找到一个attributes成员属性,即存储session的数据结构,为ConcurrentHas ...

  8. VTK中的装配体(vtkAssembly)

    Actors有时也会组合在一起形成层次结构,当其中的某个Actor运动时,会影响到其他Actor的位置.例如,一个机械手臂可能由上臂.前臂.手腕和末端等部分通过关节连接起来.当上臂绕着肩关节旋转时,我 ...

  9. svn 命令行下常用的几个命令

    1.将文件checkout到本地目录    svn checkout path(path是服务器上的目录)    例如:svn checkout svn://192.168.1.1/pro/domai ...

  10. Serv-U精简版FTP服务端

    Window搭建自己的FTP,Serv-U是很不错的选择... 00.运行视图 注意: 此版本是绿色破解版,个人使用很便捷的.不建议商业使用... download: https://pan.baid ...