1.语言的分代:
第1代:机器语言
机器语言是最底层的计算机编程语言,它是由二进制数构成的一系列指令,直接与计算机硬件交互。每个二进制位模式代表一条特定的指令或数据地址。因为它是直接在硬件上执行的,所以运行效率非常高,但是编写和维护代码非常困难,并且不便于移植到其他类型的计算机上。
第2代:汇编语言
汇编语言是对机器语言的一种符号化表示,它使用助记符(mnemonics)来代替二进制指令码,使得程序更容易阅读和编写。尽管汇编语言比机器语言更易于理解,但它仍然是一种与硬件紧密相关的语言,每一种处理器架构都需要不同的汇编语言。此外,汇编语言仍然是面向机器的,而不是面向问题的。
第3代:高级语言
高级语言是为了解决汇编语言的局限性而发展起来的,它们更接近自然语言,提供了更多的抽象层次。高级语言通常包括控制结构(如条件语句和循环)、数据类型、子程序等特性,使得程序员能够以更自然的方式表达算法和解决问题。
- 面向过程的语言:C
C语言是一种中级语言,它结合了高级语言的便利性和汇编语言的灵活性。C语言提供了对内存的直接访问和其他底层操作,这使得它非常适合于系统编程,如操作系统或编译器的开发。C语言也是很多现代编程语言的基础。
- 面向对象的语言
面向对象编程(OOP)是一种编程范式,它使用“对象”——数据和可以作用于这些数据上的方法的集合——来设计应用程序和计算机程序。面向对象的语言支持封装、继承和多态性等特性。
- C++ 是C语言的一个扩展,引入了面向对象的概念,同时保留了对底层硬件的访问能力。C++被广泛用于游戏开发、嵌入式系统和高性能应用。
- Java 是一种完全的面向对象语言,强调平台无关性。Java代码可以在任何安装了Java虚拟机(JVM)的平台上运行,这使得Java成为企业级应用开发和安卓应用开发的主要语言之一。
- C# 是由微软开发的一种面向对象语言,它为.NET框架提供支持。C#融合了C++和Java的优点,并增加了许多现代化的功能,适用于Web应用程序、桌面应用程序等多种领域。
- Python 是一种解释型的、面向对象的动态语言,以其简洁清晰的语法著称。Python广泛应用于快速原型制作、脚本编写、数据分析、人工智能等领域。
- Go 是由Google设计的一种静态类型、编译型的并发语言。Go语言简单高效,特别适合网络服务器、数据管道和大规模生产系统。
- JavaScript 最初是为了在浏览器中实现客户端脚本而设计的,但现在也广泛用于服务器端编程(例如Node.js)。JavaScript支持事件驱动、函数式编程和基于原型的编程风格。
2.Java 平台有三个主要版本
分别针对不同类型的计算环境。下面是对 JavaSE、JavaEE 和 JavaME 的简要介绍:
- JavaSE (Java Platform, Standard Edition)
JavaSE 提供了标准版的 Java 平台,包含了开发一般桌面和商务应用程序的所有必要组件。它定义了 Java 编程语言的核心 API,包括基本的数据类型、线程管理、文件 I/O、网络编程等功能。JavaSE 是构建桌面应用程序和跨平台服务端应用程序的基础,也是学习 Java 编程的起点。
- JavaEE (Java Platform, Enterprise Edition),现称为 Jakarta EE
JavaEE 原先是 Sun Microsystems 开发的企业级 Java 平台,后来 Sun Microsystems 被 Oracle 收购后继续发展。2018 年,随着 JavaEE 规范被移交给 Eclipse Foundation,它被重新命名为 Jakarta EE。Jakarta EE 提供了一套企业级应用开发的标准,包括 Web 服务、组件模型、管理和通信 API 等。Jakarta EE 在 JavaSE 的基础上增加了一系列企业级功能,比如 EJB(Enterprise JavaBeans)、JPA(Java Persistence API)、JMS(Java Message Service)、Servlets、JSP(JavaServer Pages)、JSF(JavaServer Faces)等。这些技术让开发者能够构建复杂的、分布式的、可伸缩的应用程序。
- JavaME (Java Platform, Micro Edition)
JavaME 是为嵌入式设备和消费电子产品设计的 Java 平台。它为资源受限的设备(如手机、PDA 和电视顶盒)提供了一个配置和可选的 Profile 层。配置是指一个特定的虚拟机和一组核心类库,而 Profile 则是在配置之上添加的一组特定的 API。JavaME 包括了多种配置,如 CLDC(Connected Limited Device Configuration)和 CDC(Connected Device Configuration),以及相应的 Profile,如 MIDP(Mobile Information Device Profile)用于移动设备。
每个版本的 Java 都有其特定的目标和用途。JavaSE是构建大多数普通应用程序的基础;JavaEE针对企业级应用,提供了一整套开发工具和服务;而 JavaME 则专注于嵌入式系统和移动设备。根据你的开发需求,你可以选择合适的 Java 版本来进行开发。
3.一个”.java”源文件中是否可以包括多个类?有什么限制?
在一个.java源文件中可以有多个类,但最多只能有一个公共类,并且这个公共类的名字必须与源文件名一致。其余的类可以是非公共的,并且可以有多个。这样的规定有助于保持代码的组织清晰,并且使得编译器能够正确地识别和处理这些类。
4.Java 的优势
跨平台性(Write Once, Run Anywhere):
- Java程序可以在任何安装了Java虚拟机(JVM)的平台上运行,无需修改代码。这意味着一次编写的应用程序可以在不同的操作系统(如Windows、Linux、macOS等)上运行。
面向对象:
- Java是一种面向对象的编程语言,它支持封装、继承和多态等面向对象的特性,有助于构建可维护和可扩展的软件。
丰富的API和库:
- Java拥有庞大的标准库,提供了丰富的类和接口,覆盖了从基础数据结构到网络编程、图形用户界面(GUI)等各个方面的功能。
自动垃圾回收:
- Java有自动垃圾回收机制,这减轻了程序员管理内存的负担,降低了内存泄漏和其他内存相关错误的风险。
安全性:
- Java设计时考虑了安全性,例如,它有沙箱执行环境的概念,限制了代码对系统资源的访问,从而减少了恶意代码的潜在风险。
多线程支持:
- Java内置了对多线程编程的支持,使得开发并发应用程序变得相对简单。
健壮性:
- Java语言的设计强调类型检查和异常处理,有助于编写出更稳定、错误更少的代码。
企业级支持:
- Java是企业级应用开发的首选语言之一,许多大型企业系统和金融服务系统都是用Java开发的。Java EE(现在称为Jakarta EE)为企业级应用提供了丰富的规范和框架。
社区和生态系统:
- Java拥有庞大的开发者社区和成熟的生态系统,这意味着有丰富的学习资源、工具、框架和第三方库可供使用。
良好的性能:
- 虽然Java通常不被认为是性能最高的语言,但随着JVM的不断优化和即时编译技术的进步,Java的性能已经非常接近甚至在某些方面超过了传统的编译型语言。
跨行业的应用:
- Java广泛应用于金融服务、电子商务、移动应用(Android)、大数据处理、云计算等领域。
持续的更新和发展:
- Oracle和Java社区持续对Java语言进行更新和改进,确保它能够适应新的技术趋势和市场需求。
5.常用的几个命令行操作都有哪些?
- javac
javac 是 Java 编译器,用来将 Java 源代码编译成字节码(.class 文件)。使用示例:
javac HelloWorld.java
这条命令会将 HelloWorld.java 文件编译成 HelloWorld.class 字节码文件。
- java
java 命令用于启动 Java 虚拟机(JVM),并执行指定的类(该类必须包含一个主方法 main 方法)。使用示例:
java HelloWorld
这条命令会执行 HelloWorld.class 文件中的主方法 public static void main(String[] args)。
- jar
jar 命令用于创建、更新和管理 JAR(Java ARchive)文件。JAR 文件是一个 ZIP 格式的归档文件,通常包含多个 .class 文件和其他资源文件。使用示例:
jar cvf myapp.jar MyClass.class OtherClass.class
这条命令会创建一个名为 myapp.jar 的 JAR 文件,并将 MyClass.class 和 OtherClass.class 文件打包进去。
- javadoc
javadoc 命令用于生成 HTML 格式的文档,这些文档是从源代码中的文档注释提取出来的。使用示例:
javadoc -d docs HelloWorld.java
这条命令会根据 HelloWorld.java 文件中的文档注释生成 HTML 文档,并将它们放置在 docs 目录下。
- jps
jps 命令(Java Process Status Tool)用于显示正在运行的 Java 应用程序的进程 ID 和主类名。这对于调试和监控运行中的 Java 应用程序很有帮助。使用示例:
jps
这条命令会列出所有正在运行的 Java 进程的信息。
- javap
javap 是 Java 反汇编器,它可以显示编译后的 Java 字节码。这对于调试和理解字节码层面上的执行逻辑非常有用。使用示例:
javap -c HelloWorld
这条命令会显示 HelloWorld.class 文件中的字节码。
- jconsole
jconsole 是一个监视和管理 Java 应用程序的工具,它可以帮助开发者监控和调整正在运行的 Java 应用程序的性能。使用示例:
jconsole
启动后可以选择连接到本地或远程的 Java 进程。
- jstat
jstat 工具用于收集有关 Java 应用程序的垃圾收集和内存使用情况的统计信息。使用示例:
jstat -gcutil <pid>
这条命令会显示指定进程 ID 的 Java 应用程序的垃圾收集利用率。
- jdb
jdb 是一个文本界面的 Java 调试器,允许开发者设置断点、单步执行、检查变量值等。使用示例:
jdb -classpath <path> MyApplication
这条命令会启动 MyApplication 的调试会话。
6.Java 中是否存在内存溢出、内存泄漏?如何解决?举例说明
内存溢出(OOM)
内存溢出是指Java虚拟机(JVM)在尝试分配内存时,没有足够的空间来存储新的数据,导致无法创建新的对象。
常见原因:
- 内存分配过大:创建了过大的对象或对象数组,超出了JVM堆内存的限制。
- 内存泄漏:长期存活的对象持续占用内存,未被垃圾收集器回收。
- 资源密集型操作:如大量加载图片、文件等,未及时释放资源。
- 递归调用过深:递归算法没有正确的终止条件,导致栈溢出。
解决方法:
- 增加堆内存:通过调整JVM启动参数(如
-Xmx
和-Xms
)来增加最大堆内存。 - 优化代码:检查代码中是否有不必要的大对象创建,优化数据结构和算法。
- 使用内存分析工具:使用如VisualVM、JProfiler等工具来分析内存使用情况,找出内存泄漏或占用异常的对象。
- 调整垃圾收集器:根据应用特点选择合适的垃圾收集器,调整其参数来优化垃圾收集行为。
内存泄漏(Memory Leak)
内存泄漏是指程序在分配了内存之后,未能释放已不再使用的内存。
常见原因:
- 静态集合:使用静态集合(如
static List
)存储对象,导致对象无法被垃圾收集。 - 长生命周期对象持有短生命周期对象引用:例如,一个大的缓存对象持有许多小对象的引用,即使这些小对象不再需要。
- 第三方库或框架问题:使用的某些库或框架可能存在内存泄漏问题。
- 资源未关闭:如数据库连接、文件流等没有正确关闭。
解决方法:
- 代码审查:定期进行代码审查,特别是关注对象的创建和释放。
- 使用弱引用:对于那些不需要长期持有的对象,可以使用弱引用(
WeakReference
)。 - 资源管理:确保所有资源(如文件、数据库连接、网络连接等)在使用完毕后被正确关闭。
- 内存分析工具:使用内存分析工具来检测内存泄漏,例如Eclipse Memory Analyzer Tool (MAT)。
7.如何看待Java是一门半编译半解释型的语言
Java的编译过程
- 源代码编译:Java源代码(.java文件)首先被编译器(javac)编译成字节码(.class文件)。这个过程是编译型语言的典型特征,因为源代码被转换成了中间形式的字节码,而不是直接转换成机器码。
- 字节码:字节码是一种与平台无关的中间代码,它不是直接在硬件上执行的机器码,而是在Java虚拟机(JVM)上运行的指令集。
Java的运行过程
- 解释执行:当Java程序运行时,JVM通过类加载器加载字节码,然后使用即时编译器(Just-In-Time, JIT)将字节码转换成特定平台的机器码。这个过程可以看作是解释执行,因为字节码在执行时才被转换成机器码。
- JIT编译优化:JIT编译器在程序运行时动态地将热点代码(频繁执行的代码段)编译成优化后的机器码,以提高执行效率。这意味着,即使是同一段Java代码,在不同的运行阶段或不同的执行路径上,都可能被编译成不同的机器码。
结论
从这个角度来看,Java既不是纯粹的编译型语言,也不是纯粹的解释型语言,而是一种结合了编译和解释两种机制的语言。这种设计让Java具有以下优势:
- 跨平台性:由于字节码的平台无关性,Java程序可以在任何安装了JVM的平台上运行。
- 性能优化:JIT编译器可以在运行时对代码进行优化,提高执行效率。
- 安全性:字节码的执行在JVM的控制下,有助于防止恶意代码直接操作硬件。
因此,将Java描述为“半编译半解释型”的语言,有助于更全面地理解其工作原理和特点。这种设计使得Java在保持高度的可移植性的同时,也能够提供相对高效的执行性能。
8.关键字、保留字
Java 关键字(共50个):
-
访问控制:public、protected、private
-
类、方法和变量修饰符:abstract、class、extends、final、implements、interface、native、new、static、strictfp、synchronized、transient、volatile
-
程序控制语句:break、case、continue、default、do、else、for、if、instanceof、return、switch、while
-
错误处理:assert、catch、finally、throw、throws、try
-
包相关:import、package
-
基本类型:boolean、byte、char、double、float、int、long、short
-
变量引用:super、this、void
-
保留关键字:goto、const
Java 保留字(目前有2个):
- goto
- const
需要注意的是,虽然goto
和const
是Java的保留字,但它们在Java语言中并没有实际的用途,且在当前版本的Java中不被使用。此外,Java语言中还有三个特殊的直接量:true
、false
和null
,它们虽然不是关键字,但在编程中具有特定的含义。
9.标识符
- 标识符:凡是可以自己命名的地方,都是标识符。
比如:类名、变量名、方法名、接口名、包名、常量名等
-
记住:标识符命名的规则(必须要遵守的,否则编译不通过)
> 由26个英文字母大小写,0-9 ,_或 $ 组成
> 数字不可以开头。
> 不可以使用关键字和保留字,但能包含关键字和保留字。
> Java中严格区分大小写,长度无限制。
> 标识符不能包含空格。
- 记住:标识符命名的规范
> 包名:多单词组成时所有字母都小写:xxxyyyzzz。
例如:java.lang、com.xyafu.edu
> 类名、接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz
例如:HelloWorld,String,System等
> 变量名、方法名:多单词组成时,第一个单词首字母小写,第二个单词开始每个单词首字母大写:xxxYyyZzz
例如:age,name,bookName,main,binarySearch,getName
> 常量名:所有字母都大写。多单词时每个单词用下划线连接:XXX_YYY_ZZZ
例如:MAX_VALUE,PI,DEFAULT_CAPACITY
10.变量的基本使用
-
变量的理解:内存中的一个存储区域,该区域的数据可以在同一类型范围内不断变化
-
变量的构成包含三个要素:数据类型、变量名、存储的值
-
Java中变量声明的格式:数据类型 变量名 = 变量值
定义变量时,变量名要遵循标识符命名的规则和规范。
说明: ① 变量都有其作用域。变量只在作用域内是有效的,出了作用域就失效了。 ② 在同一个作用域内,不能声明两个同名的变量 ③ 定义好变量以后,就可以通过变量名的方式对变量进行调用和运算。 ④ 变量值在赋值时,必须满足变量的数据类型,并且在数据类型有效的范围内变化。
11.基本数据类型变量的使用
- 基本数据类型(8种):
整型:byte \ short \ int \ long
浮点型:float \ double
字符型:char
布尔型:boolean
- 引用数据类型:
类(class)
数组(array)
接口(interface)枚举(enum)
注解(annotation)
记录(record)
12.基本数据类型变量间的运算规则(重点)
-
自动类型提升
测试基本数据类型变量间的运算规则。
1. 这里提到可以做运算的基本数据类型有7种,不包含boolean类型。
2. 运算规则包括:
① 自动类型提升
② 强制类型转换3. 此VariableTest3.java用来测试自动类型提升
规则:当容量小的变量与容量大的变量做运算时,结果自动转换为容量大的数据类型。
byte 、short 、char ---> int ---> long ---> float ---> double
特别的:byte、short、char类型的变量之间做运算,结果为int类型。
说明:此时的容量小或大,并非指占用的内存空间的大小,而是指表示数据的范围的大小。
long(8字节) 、 float(4字节)
13.强制类型转换
/*
规则:
1. 如果需要将容量大的变量的类型转换为容量小的变量的类型,需要使用强制类型转换
2. 强制类型转换需要使用强转符:()。在()内指明要转换为的数据类型。
3. 强制类型转换过程中,可能导致精度损失。
*/
class VariableTest4 {
public static void main(String[] args) {
double d1 = 12;//自动类型提升
//编译失败
//int i1 = d1;int i2 = (int)d1;
System.out.println(i2);
long l1 = 123;
//编译失败
//short s1 = l1;
short s2 = (short)l1;
System.out.println(s2);
//练习
int i3 = 12;
float f1 = i3;//自动类型提升
System.out.println(f1); //12.0float f2 = (float)i3; //编译可以通过。只不过可以省略()而已。
//精度损失的例子1:
double d2 = 12.9;
int i4 = (int)d2;
System.out.println(i4);//精度损失的例子2:
int i5 = 128;
byte b1 = (byte)i5;
System.out.println(b1); //-128
//实际开发举例:
byte b2 = 12;
method(b2);long l2 = 12L;
//编译不通过
//method(l2);
method((int)l2);
}public static void method(int num){ //int num = b2;自动类型提升
System.out.println("num = " + num);
}
}
14.String类的使用、与基本数据类型变量间的运算(重点)
-
String的认识:字符串。使用一对""表示,内部包含0个、1个或多个字符。
-
String与8种基本数据类型变量间的运算:+。运算的结果是String类型。
15.常识:进制的认识
数制表示法
- 二进制:以0B或0b开头表示二进制数,例如
0B1010
或0b1010
。 - 十进制:我们日常使用的数字系统,没有特定的前缀。
- 八进制:以0开头,例如
0123
。 - 十六进制:以0x或0X开头,例如
0x1A
或0X1A
。
二进制的理解
正数
对于正数,二进制的原码、反码和补码是相同的。原码就是直接将十进制数转换为二进制表示。
负数
对于负数,情况就有所不同:
- 原码:直接表示负数的二进制形式,最高位为符号位(0表示正,1表示负),其余位表示数值。
- 反码:正数的反码与其原码相同,负数的反码是其原码除符号位外所有位取反(0变1,1变0)。
- 补码:正数的补码与其原码相同,负数的补码是其反码加1。
计算机内部使用补码来表示和存储所有整数,包括负数。补码系统简化了计算机的算术运算,特别是减法运算可以转换为加法运算。
二进制与十进制之间的转换
- 二进制转十进制:从右到左,将每个位的值(0或1)乘以2的幂次方(从0开始),然后求和。例如,二进制
1011
转换为十进制是1 \times 2^3 + 0 \times 2^2 + 1 \times 2^1 + 1 \times 2^0 = 111×23+0×22+1×21+1×20=11。 - 十进制转二进制:通过不断除以2并取余数的方式,然后将余数倒序排列。例如,十进制数
11
转换为二进制是1011
。
二进制与八进制、十六进制间的转换
- 二进制转八进制:将二进制数从右到左每三位一组(不足三位时在左边补0),然后将每组转换为对应的八进制数。例如,二进制
101101
转八进制是55
。 - 八进制转二进制:将每个八进制数字转换为对应的三位二进制数。例如,八进制
55
转二进制是101101
。 - 二进制转十六进制:将二进制数从右到左每四位一组(不足四位时在左边补0),然后将每组转换为对应的十六进制数。例如,二进制
101101
转十六进制是2D
。 - 十六进制转二进制:将每个十六进制数字转换为对应的四位二进制数。例如,十六进制
2D
转二进制是101101
。
16.运算符(较常用的是重点)
算术运算符
用于执行基本的数学运算,如加、减、乘、除等。
+
:加法-
:减法*
:乘法/
:除法%
:取模(余数)
赋值运算符
用于将表达式的值赋给变量。
=
:基本赋值+=
:加后赋值(例如a += b
等同于a = a + b
)-=
:减后赋值*=
:乘后赋值/=
:除后赋值%=
:取模后赋值
比较运算符(关系运算符)
用于比较两个值,并返回布尔值(true或false)。
==
:等于!=
:不等于>
:大于<
:小于>=
:大于等于<=
:小于等于
逻辑运算符
用于执行布尔逻辑运算。
&&
:逻辑与(AND)||
:逻辑或(OR)!
:逻辑非(NOT)
位运算符
直接对整数类型的位进行操作。
&
:按位与(AND)|
:按位或(OR)^
:按位异或(XOR)~
:按位取反(NOT)<<
:左移>>
:右移>>>
:无符号右移
条件运算符(三元运算符)
?:
:条件表达式,格式为条件 ? 表达式1 : 表达式2
,如果条件为真,则结果为表达式1,否则为表达式2。
其他运算符
instanceof
:用于测试对象是否为特定类的实例或其子类的实例。new
:用于创建对象实例。
17.高效的方式计算2 * 8的值
- 使用 <<
18.&和&&的区别?
在 Java 中,&
是按位与操作符,适用于整数类型的操作;&&
是逻辑与操作符,用于布尔表达式,它具有短路特性,即如果第一个操作数为 false
,则不会计算第二个操作数。
19.Java中的基本类型有哪些?String 是最基本的数据类型吗?
- Java中的基本类型有八种:
byte、short、int、long、float、double、boolean、char
- String 不是基本数据类型,它是引用类型,即对象。String 类型是由类定义创建的对象,因此它不是基本类型的一部分。
20.Java开发中计算金额时使用什么数据类型?
- 在Java中处理货币或金融相关的计算时,通常推荐使用 BigDecimal 类来避免浮点数的精度损失问题。这是因为 float 或 double 类型可能会导致不精确的结果,尤其是在涉及货币时,这种不精确是不可接受的。
21.char型变量中能不能存储一个中文汉字,为什么?
- 可以的。char c1 = '中';
- char c2 = 'a'。
- 因为char使用的是unicode字符集,包含了世界范围的所有的字符。
22.代码分析
第一段代码:
short s1 = 1;
s1 = s1 + 1;
s1 + 1 的操作会导致类型提升,因为 + 操作符两边的类型会被提升到 int 类型(这是Java的自动类型提升规则之一)。所以 s1 + 1 的结果也是一个 int 类型的值。然后当你试图将这个 int 类型的值赋给 short 类型的变量 s1 时,编译器会报错,因为你需要显式地进行类型转换。
第二段代码:
short s1 = 1;
s1 += 1;
在这段代码中,使用了 += 运算符,这是一种复合赋值运算符。在这种情况下,Java 会自动处理类型转换,先将表达式的结果转换成 short 类型再进行赋值,所以这段代码可以直接通过编译并正确运行。
23.int i=0; i=i++执行这两句化后变量 i 的值为
在 Java 中,表达式 i = i++ 是一个典型的例子,展示了自增运算符(++)放在变量后面时的行为。这种情况下,i++ 表示先使用 i 的当前值,然后再将 i 的值加 1。
具体来说,对于以下代码:
int i = 0;
i = i++;
执行过程如下:
int i = 0; 初始化 i 为 0。
i = i++; 这一行中,首先 i 的当前值(即 0)被用来作为赋值操作的右侧的值。然后 i 自增,变为 1。
因此,在执行完 i = i++; 后,i 的值仍然是 0。这是因为赋值操作 = 的左侧是 i,而右侧是自增之前的 i 的值,即 0。
总结:执行完这两句代码后,变量 i 的值仍为 0。
24.如何将两个变量的值互换
- 使用第三个变量:
int a = 1;
int b = 2;
int temp = a;
a = b;
b = temp;
- 使用算术运算:
int a = 1;
int b = 2;
a = a + b; // a现在等于3
b = a - b; // b现在等于1
a = a - b; // a现在等于2
- 使用位运算:
int a = 1;
int b = 2;
a = a ^ b; // 使用异或操作
b = a ^ b; // 现在b等于原来的a
a = a ^ b; // 现在a等于原来的b
25.boolean 占几个字节
在Java中,boolean 类型的大小并不是固定的,JVM 规范并没有明确规定 boolean 的确切大小。实际上,boolean 类型通常被实现为占一个字节(8位),但这取决于具体的JVM实现。需要注意的是,虽然 boolean 在内部可能使用一个字节表示,但是 boolean[] 数组的实现可能会有所不同,可能会使用位压缩等技术来节省空间。
26.为什么Java中0.1 + 0.2结果不是0.3?
在代码中测试0.1 + 0.2,你会惊讶的发现,结果不是0.3,而是0.3000……4。这是为什么?
几乎所有现代的编程语言都会遇到上述问题,包括 JavaScript、Ruby、Python、Swift 和 Go 等。引发这个问题的原因是,它们都采用了IEEE 754标准
。
IEEE是指“电气与电子工程师协会”,其在1985年发布了一个IEEE 754计算标准,根据这个标准,小数的二进制表达能够有最大的精度上限提升。但无论如何,物理边界是突破不了的,它仍然
不能实现“每一个十进制小数,都对应一个二进制小数”
。正因如此,产生了0.1 + 0.2不等于0.3的问题。
具体的:
整数变为二进制,能够做到“每个十进制整数都有对应的二进制数”,比如数字3,二进制就是11;再比如,数字43就是二进制101011,这个毫无争议。
对于小数,并不能做到“每个小数都有对应的二进制数字”。举例来说,二进制小数0.0001表示十进制数0.0625 (至于它是如何计算的,不用深究);二进制小数0.0010表示十进制数0.125;二进制小数0.0011表示十进制数0.1875。看,对于四位的二进制小数,二进制小数虽然是连贯的,但是十进制小数却不是连贯的。比如,你无法用四位二进制小数的形式表示0.125 ~ 0.1875之间的十进制小数。
所以在编程中,遇见小数判断相等情况,比如开发银行、交易等系统,可以采用四舍五入
或者“同乘同除
”等方式进行验证,避免上述问题。
例如,在二进制中 0.1 可能被表示为一个无限循环的小数,因此在计算机中只能用一个近似的有限数表示。这种近似会导致 0.1 + 0.2 的结果接近但不完全等于 0.3。为了避免这种精度问题,通常建议在涉及到货币或其他需要高精度计算的情况下使用 BigDecimal 类。