📑前言
本文主要是【Java】——Java基础面试题的文章,如果有什么需要改进的地方还请大佬指出⛺️
🎬作者简介:大家好,我是听风与他🥇
☁️博客首页:CSDN主页听风与他
🌄每日一句:狠狠沉淀,顶峰相见
目录
- 📑前言
- Java的特点
- Java是如何实现跨平台的?
- Java 与 C++ 的区别
- JDK/JRE/JVM三者的关系
- Java程序是编译执行还是解释执行?
- 面向对象和面向过程的区别?
- 面向对象有哪些特性?
- 面向对象编程的六大原则?
- 数组到底是不是对象?
- Java的基本数据类型有哪些?
- 为什么不能用浮点型表示金额?
- 什么是值传递和引用传递?
- 了解Java的包装类型吗?为什么需要包装类?
- 自动装箱和拆箱
- 两个Integer 用== 比较不相等的原因
- 📑文章末尾
Java的特点
Java是一门面向对象的编程语言。面向对象和面向过程的区别参考下一个问题。
Java具有平台独立性和移植性。
- Java有一句口号:
Write once, run anywhere
,一次编写、到处运行。这也是Java的魅力所在。而实现这种特性的正是Java虚拟机JVM。已编译的Java程序可以在任何带有JVM的平台上运行。你可以在windows平台编写代码,然后拿到linux上运行。只要你在编写完代码后,将代码编译成.class文件,再把class文件打成Java包,这个jar包就可以在不同的平台上运行了。
Java具有稳健性。
- Java是一个强类型语言,它允许扩展编译时检查潜在类型不匹配问题的功能。Java要求显式的方法声明,它不支持C风格的隐式声明。这些严格的要求保证编译程序能捕捉调用错误,这就导致更可靠的程序。
- 异常处理是Java中使得程序更稳健的另一个特征。异常是某种类似于错误的异常条件出现的信号。使用
try/catch/finally
语句,程序员可以找到出错的处理代码,这就简化了出错处理和恢复的任务。
Java是如何实现跨平台的?
Java是通过JVM(Java虚拟机)实现跨平台的。
JVM可以理解成一个软件,不同的平台有不同的版本。我们编写的Java代码,编译后会生成.class 文件(字节码文件)。Java虚拟机就是负责将字节码文件翻译成特定平台下的机器码,通过JVM翻译成机器码之后才能运行。不同平台下编译生成的字节码是一样的,但是由JVM翻译成的机器码却不一样。
只要在不同平台上安装对应的JVM,就可以运行字节码文件,运行我们编写的Java程序。
因此,运行Java程序必须有JVM的支持,因为编译的结果不是机器码,必须要经过JVM的翻译才能执行。
Java 与 C++ 的区别
- Java 是纯粹的面向对象语言,所有的对象都继承自 java.lang.Object,C++ 兼容 C ,不但支持面向对象也支持面向过程。
- Java 通过虚拟机从而实现跨平台特性, C++ 依赖于特定的平台。
- Java 没有指针,它的引用可以理解为安全指针,而 C++ 具有和 C 一样的指针。
- Java 支持自动垃圾回收,而 C++ 需要手动回收。
- Java 不支持多重继承,只能通过实现多个接口来达到相同目的,而 C++ 支持多重继承。
JDK/JRE/JVM三者的关系
JVM
英文名称(Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。Java 能够跨平台运行的核心在于 JVM 。
所有的java程序会首先被编译为.class的类文件,这种类文件可以在虚拟机上执行。也就是说class文件并不直接与机器的操作系统交互,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。
针对不同的系统有不同的 jvm 实现,有 Linux 版本的 jvm 实现,也有Windows 版本的 jvm 实现,但是同一段代码在编译后的字节码是一样的。这就是Java能够跨平台,实现一次编写,多处运行的原因所在。
JRE
英文名称(Java Runtime Environment),就是Java 运行时环境。我们编写的Java程序必须要在JRE才能运行。它主要包含两个部分,JVM 和 Java 核心类库。
JRE是Java的运行环境,并不是一个开发环境,所以没有包含任何开发工具,如编译器和调试器等。
如果你只是想运行Java程序,而不是开发Java程序的话,那么你只需要安装JRE即可。
JDK
英文名称(Java Development Kit),就是 Java 开发工具包
学过Java的同学,都应该安装过JDK。当我们安装完JDK之后,目录结构是这样的
可以看到,JDK目录下有个JRE,也就是JDK中已经集成了 JRE,不用单独安装JRE。
另外,JDK中还有一些好用的工具,如jinfo,jps,jstack等。
最后,总结一下JDK/JRE/JVM,他们三者的关系
JRE = JVM + Java 核心类库
JDK = JRE + Java工具 + 编译器 + 调试器
分享一份大彬精心整理的大厂面试手册,包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等高频面试题,非常实用,有小伙伴靠着这份手册拿过字节offer~
需要的小伙伴可以自行下载:
链接:https://pan.xunlei.com/s/VNgU60NQQNSDaEy9z955oufbA1?pwd=y9fy#open in new window
备用链接:https://pan.quark.cn/s/cbbb681e7c19open in new window
Java程序是编译执行还是解释执行?
先看看什么是编译型语言和解释型语言。
编译型语言
在程序运行之前,通过编译器将源程序编译成机器码可运行的二进制,以后执行这个程序时,就不用再进行编译了。
优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高,可以脱离语言环境独立运行。
缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。
总结:执行速度快、效率高;依靠编译器、跨平台性差些。
代表语言:C、C++、Pascal、Object-C以及Swift。
解释型语言
定义:解释型语言的源代码不是直接翻译成机器码,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。在运行的时候才将源程序翻译成机器码,翻译一句,然后执行一句,直至结束。
优点:
- 有良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器(如虚拟机)。
- 灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。
缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。
总结:解释型语言执行速度慢、效率低;依靠解释器、跨平台性好。
代表语言:JavaScript、Python、Erlang、PHP、Perl、Ruby。
对于Java这种语言,它的源代码会先通过javac编译成字节码,再通过jvm将字节码转换成机器码执行,即解释运行 和编译运行配合使用,所以可以称为混合型或者半编译型。
面向对象和面向过程的区别?
面向对象和面向过程是一种软件开发思想。
- 面向过程就是分析出解决问题所需要的步骤,然后用函数按这些步骤实现,使用的时候依次调用就可以了。
- 面向对象是把构成问题事务分解成各个对象,分别设计这些对象,然后将他们组装成有完整功能的系统。面向过程只用函数实现,面向对象是用类实现各个功能模块。
以五子棋为例,面向过程的设计思路就是首先分析问题的步骤:
1、开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。 把上面每个步骤用分别的函数来实现,问题就解决了。
而面向对象的设计则是从另外的思路来解决问题。整个五子棋可以分为:
- 黑白双方
- 棋盘系统,负责绘制画面
- 规则系统,负责判定诸如犯规、输赢等。
黑白双方负责接受用户的输入,并告知棋盘系统棋子布局发生变化,棋盘系统接收到了棋子的变化的信息就负责在屏幕上面显示出这种变化,同时利用规则系统来对棋局进行判定。
面向对象有哪些特性?
面向对象四大特性:封装,继承,多态,抽象
1、封装就是将类的信息隐藏在类内部,不允许外部程序直接访问,而是通过该类的方法实现对隐藏信息的操作和访问。 良好的封装能够减少耦合。
2、继承是从已有的类中派生出新的类,新的类继承父类的属性和行为,并能扩展新的能力,大大增加程序的重用性和易维护性。在Java中是单继承的,也就是说一个子类只有一个父类。
3、多态是同一个行为具有多个不同表现形式的能力。在不修改程序代码的情况下改变程序运行时绑定的代码。实现多态的三要素:继承、重写、父类引用指向子类对象。
- 静态多态性:通过重载实现,相同的方法有不同的參数列表,可以根据参数的不同,做出不同的处理。
- 动态多态性:在子类中重写父类的方法。运行期间判断所引用对象的实际类型,根据其实际类型调用相应的方法。
4、抽象。把客观事物用代码抽象出来。
面向对象编程的六大原则?
- 对象单一职责:我们设计创建的对象,必须职责明确,比如商品类,里面相关的属性和方法都必须跟商品相关,不能出现订单等不相关的内容。这里的类可以是模块、类库、程序集,而不单单指类。
- 里式替换原则:子类能够完全替代父类,反之则不行。通常用于实现接口时运用。因为子类能够完全替代基(父)类,那么这样父类就拥有很多子类,在后续的程序扩展中就很容易进行扩展,程序完全不需要进行修改即可进行扩展。比如IA的实现为A,因为项目需求变更,现在需要新的实现,直接在容器注入处更换接口即可.
- 迪米特法则,也叫最小原则,或者说最小耦合。通常在设计程序或开发程序的时候,尽量要高内聚,低耦合。当两个类进行交互的时候,会产生依赖。而迪米特法则就是建议这种依赖越少越好。就像构造函数注入父类对象时一样,当需要依赖某个对象时,并不在意其内部是怎么实现的,而是在容器中注入相应的实现,既符合里式替换原则,又起到了解耦的作用。
- 开闭原则:开放扩展,封闭修改。当项目需求发生变更时,要尽可能的不去对原有的代码进行修改,而在原有的基础上进行扩展。
- 依赖倒置原则:高层模块不应该直接依赖于底层模块的具体实现,而应该依赖于底层的抽象。接口和抽象类不应该依赖于实现类,而实现类依赖接口或抽象类。
- 接口隔离原则:一个对象和另外一个对象交互的过程中,依赖的内容最小。也就是说在接口设计的时候,在遵循对象单一职责的情况下,尽量减少接口的内容。
简洁版:
- 单一职责:对象设计要求独立,不能设计万能对象。
- 开闭原则:对象修改最小化。
- 里式替换:程序扩展中抽象被具体可以替换(接口、父类、可以被实现类对象、子类替换对象)
- 迪米特:高内聚,低耦合。尽量不要依赖细节。
- 依赖倒置:面向抽象编程。也就是参数传递,或者返回值,可以使用父类类型或者接口类型。从广义上讲:基于接口编程,提前设计好接口框架。
- 接口隔离:接口设计大小要适中。过大导致污染,过小,导致调用麻烦。
数组到底是不是对象?
先说说对象的概念。对象是根据某个类创建出来的一个实例,表示某类事物中一个具体的个体。
对象具有各种属性,并且具有一些特定的行为。站在计算机的角度,对象就是内存中的一个内存块,在这个内存块封装了一些数据,也就是类中定义的各个属性。
所以,对象是用来封装数据的。
java中的数组具有java中其他对象的一些基本特点。比如封装了一些数据,可以访问属性,也可以调用方法。
因此,可以说,数组是对象。
也可以通过代码验证数组是对象的事实。比如以下的代码,输出结果为java.lang.Object。
Class clz = int[].class;
System.out.println(clz.getSuperclass().getName());
由此,可以看出,数组类的父类就是Object类,那么可以推断出数组就是对象。
Java的基本数据类型有哪些?
- byte,8bit
- char,16bit
- short,16bit
- int,32bit
- float,32bit
- long,64bit
- double,64bit
- boolean,只有两个值:true、false,可以使⽤用 1 bit 来存储
简单类型 | boolean | byte | char | short | Int | long | float | double |
---|---|---|---|---|---|---|---|---|
二进制位数 | 1 | 8 | 16 | 16 | 32 | 64 | 32 | 64 |
包装类 | Boolean | Byte | Character | Short | Integer | Long | Float | Double |
在Java规范中,没有明确指出boolean的大小。在《Java虚拟机规范》给出了单个boolean占4个字节,和boolean数组1个字节的定义,具体 还要看虚拟机实现是否按照规范来,因此boolean占用1个字节或者4个字节都是有可能的。
为什么不能用浮点型表示金额?
由于计算机中保存的小数其实是十进制的小数的近似值,并不是准确值,所以,千万不要在代码中使用浮点数来表示金额等重要的指标。
建议使用BigDecimal或者Long来表示金额。
什么是值传递和引用传递?
- 值传递是对基本型变量而言的,传递的是该变量的一个副本,改变副本不影响原变量。
- 引用传递一般是对于对象型变量而言的,传递的是该对象地址的一个副本,并不是原对象本身,两者指向同一片内存空间。所以对引用对象进行操作会同时改变原对象。
java中不存在引用传递,只有值传递。即不存在变量a指向变量b,变量b指向对象的这种情况。
了解Java的包装类型吗?为什么需要包装类?
Java 是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将 int 、double 等类型放进去的。因为集合的容器要求元素是 Object 类型。
为了让基本类型也具有对象的特征,就出现了包装类型。相当于将基本类型包装起来,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
自动装箱和拆箱
Java中基础数据类型与它们对应的包装类见下表:
原始类型 | 包装类型 |
---|---|
boolean | Boolean |
byte | Byte |
char | Character |
float | Float |
int | Integer |
long | Long |
short | Short |
double | Double |
装箱:将基础类型转化为包装类型。
拆箱:将包装类型转化为基础类型。
当基础类型与它们的包装类有如下几种情况时,编译器会自动帮我们进行装箱或拆箱:
- 赋值操作(装箱或拆箱)
- 进行加减乘除混合运算 (拆箱)
- 进行>,<,==比较运算(拆箱)
- 调用equals进行比较(装箱)
- ArrayList、HashMap等集合类添加基础类型数据时(装箱)
示例代码:
Integer x = 1; // 装箱 调⽤ Integer.valueOf(1)
int y = x; // 拆箱 调⽤了 X.intValue()
两个Integer 用== 比较不相等的原因
下面看一道常见的面试题:
Integer a = 100;
Integer b = 100;
System.out.println(a == b);
Integer c = 200;
Integer d = 200;
System.out.println(c == d);
输出:
true
false
为什么第三个输出是false?看看 Integer 类的源码就知道啦。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
Integer c = 200;
会调用 调⽤Integer.valueOf(200)
。而从Integer的valueOf()源码可以看到,这里的实现并不是简单的new Integer,而是用IntegerCache做一个cache。
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
}
...
}
这是IntegerCache静态代码块中的一段,默认Integer cache 的下限是-128,上限默认127。当赋值100给Integer时,刚好在这个范围内,所以从cache中取对应的Integer并返回,所以a和b返回的是同一个对象,所以==
比较是相等的,当赋值200给Integer时,不在cache 的范围内,所以会new Integer并返回,当然==
比较的结果是不相等的。