Java基础常见面试题总结
1、Java语言有哪些特点
- 简单易学
- 面向对象:封装,继承,多态
- 平台无关性(Java虚拟机实现平台无关性)跨平台
- 支持多线程
- 可靠性
- 安全性
- 支持网络编程并且很方便。java.net
- 编译与解释并存
“Java强大的生态才是Java语言最大的优势”
- JVM:是运行java字节码的虚拟机。
- JDK和JRE: JDK是功能齐全的Java SDK ,包含了JRE。JRE是Java运行时的环境,它是运行已编译Java程序所需的所有内容的集合。
- JIT运行时编译。当JIT编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。机器码的运行效率肯定是高于Java解释器的,这也解释了我们为什么经常说Java是编译与解释共存的语言
编译型:编译型语言会通过编译器将源代码一次性翻译成可被该平台执行的机器码。一般情况下,编译语言的执行速度比较快,开发效率比较低。 C、C++、Go、Rust等
解释性语言:解释性语言会通过解释器一句一句的将代码解释为机器代码后再执行,解释型语言开发效率比较快,执行速度比较慢。Python、JavaScript、PHP
而java则是“编译与解释并存”,因为Java程序要经过先编译,后解释两个步骤:由Java编写的程序需要先经过编译步骤,生成字节码(.class文件),这种字节码必须由Java解释器来解释执行。
2、Java和C++的区别?
- Java不提供指针来直接访问内存,程序内存更加安全
- Java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承
- Java有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存
- C++同时支持方法重载和操作符重载,但是Java只支持方法重载。
3、自增自减运算符
- ++和-- 运算符可以放在变量之前也可以放在变量之后,当运算符放在变量之前时,先自增/减,再赋值;当运算符放在变量之后时,先赋值,再自增/减。a++输出的是a的值,++a输出的是a+1的值
4、移位运算符
移位运算符是最基本的运算符之一。移位操作中,**作的数据被视为二进制数,移位就是将其向左或向右移动若干位的运算。实际上支持的类型只有int和long
在Java代码里常用 << 、 >> 、 >>>三种移位运算符:
<< : 左移运算符,向左移若干位,高位丢弃,低位补零, x<<1 ,相当于 x 乘以2 ;
->>: 带符号右移,向右移若干位,高位补符号位,低位丢弃。x>>1,相当于x除以2;
->>> :无符号右移,忽略符号位,空位都以0补齐。
5、continue、break、return区别
- continue:跳出本次循环(本次循环剩下的语句不再执行),继续下一次循环。
- break:跳出整个循环体,继续执行循环下面的语句
- return:跳出所在方法,结束该方法的运行
6、变量:
成员变量和局部变量的区别?
- 语法形式:从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量。成员变量可以被static、public、private等修饰符修饰,而局部变量不能够被访问控制修饰符及static所修饰。成员变量和局部变量都能被final所修饰。
- 存储方式:成员变量使用static修饰,那么这个成员变量是属于类的,如果没有用static修饰,那么是属于实例。而对象存在于堆内存。局部变量存在于栈内存
- 生存时间:成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡
- 默认值:成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值。而局部变量不会自动赋值。
静态变量有什么作用?
静态变量可以被类的所有实例共享,无论一个类创建了多少对象,它们都共享一份静态变量。
通常情况下,静态变量会被final关键字修饰成为常量
7、方法
静态方法为什么不能调用非静态成员?
- 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态方法属于实例对象,只有在对象实例化后才存在,需要通过类的实例对象去访问;
- 在类的非静态成员不存在的时候,静态方法就已经存在了。此时去调用内存中还不存在的非静态成员,属于非法操作
静态方法和实例方法有什么区别?
- 调用方式:静态方法调用无需创建对象,可以直接class.方法名,当然也可以创建对象然后 对象.方法名。而实例方法只能后者形式(对象.方法名)去调用 ;不过,我们一般用 类名.方法名来调用静态方法,以免混淆。静态方法属于这个类。
- 访问类成员是否存在限制: 静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员,而实例方法不存在这个限制。
重载和重写
- 重载:方法名相同,参数类型不同、个数不同、顺序不同、方法返回值不同和访问修饰符不同等。
- 重写:子类对父类的允许访问的方法的实现过程进行重新编写。方法的重写遵循“两同两小一大”,两同是指方法名相同、形参列表相同;两小是指子类返回值类型应比父类方法返回值更小或相等,子类方法声明抛出的异常类应该比父类方法声明抛出的异常类更小或相等;一大是指子类方法的访问权限比父类更大或相等。
如果方法的返回类型是void和基本数据类型,则重写时返回值不可修改。如果方法的返回值是引用类型,重写时可以返回该引用类型的子类
可变长参数
可变长参数就是允许在调用方法时传入不定长度的参数。(0个或者多个)
9、基本数据类型
八种基本数据类型
byte、short、int、long、float、double、char、boolean
Java为每一个基本数据类型都引入了对应的包装类型:Byte、Short、Integer、Long、Float、Double、Character、Boolean
基本类型和包装类型的区别
- 成员变量包装类型不赋值就是null,而基本类型有默认值且不是null
- 包装类型可用于泛型,而基本类型不可以
- 包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中
- 相比于对象类型,基本数据类型占用的空间非常小
Java基本数据类型的包装类型的大部分都用到了缓存机制来提升性能
eg:
输出的是 false
第一行代码会发生装箱,也就是说这行代码等价于 Integer i1 = Integer.valueOf(40) ,因此i1是直接使用的缓存中的对象。而 第二行代码会直接创建新的对象。所以最后输出的是false
所有整型包装类对象之间值的比较,全部使用equals方法比较
因为对于-128至127之间的赋值,Integer对象是在缓存中产生,会复用已有对象,这个区间内的Integer值可以用==进行判断,但是这个区间以外的数据,会在堆上产生,不会复用已有对象,是一个大坑。所以,所有的Integer值比较都用equals方法是最好的
为什么浮点数运算的时候会有精度丢失的风险?
这个和计算机保存浮点数的机制有很大关系。我们知道计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况。所以浮点数没有办法用二进制精确表示
解决浮点数运算的精度丢失问题
BigDecimal 可以实现对浮点数的运算,不会造成精度丢失。设计到钱的场景。
超过long整型的数据,用BigInteger类型表示
10、面向对象基础
面向对象和面向过程区别
- 面向对象:抽象出对象,然后用对象执行方法的方式解决问题
- 面向过程:把解决问题的过程拆成一个个方法,通过执行方法解决问题
面向对象开发的程序一般更易维护、易复用、易扩展
如果一个类没有声明构造方法,该程序能正确执行吗
可以的。一个类即使没有声明执行方法也会有默认的不带参数的构造方法。如果我们自己添加了类的构造方法(不论是有参还是无参),java就不会再添加默认的无参构造。所以,如果我们重载了有参的构造方法,就要把无参构造的方法也写出来。
构造方法有哪些特点
- 构造方法是一种特殊的方法,主要作用是完成对对象的初始化工作
- 名字与类名相同
- 没有返回值,但不能用void声明构造函数
- 生成类的对象时自动执行,无需调用
- 构造方法不能被override(重写),但是可以overload(重载),所以可以看到一个类中多个构造函数
接口和抽象类有什么区别
共同点:
- 都不能被实例化
- 都可以包含抽象方法
- 都可以有默认实现的方法
抽象方法是指没有方法体的方法,同时抽象方法还必须使用abstract修饰。拥有抽象方法的类就是抽象类,抽象类也要使用abstract关键字声明。
区别:
- 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系
- 一个类只能继承一个类,但是可以实现多个接口
- 接口中的成员变量只能是public、static、final java面试的基础类型 类型的,不能被修改且必须由初始值,而抽象类的成员变量默认default,可在子类中被重新定义,也可被重新赋值。
浅拷贝、深拷贝、引用拷贝
- 浅拷贝:浅拷贝对象和原对象共用同一个内部对象
- 深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。Address不同
- 引用拷贝:两个不同的引用指向同一个类
11、Java常见类
== 和 equals()的区别
- 对于基本类型来说是比较值,对于引用类型来说是比较对象的内存地址;因为Java只有值传递,所以,对于来说,不管是比较基本数据类型还是引用类型,其本质比较的都是值
- equals()不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。
hashCode()有什么用?
hashCode()的作用是获取哈希码,也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置
当你把对象加入到HashSet时,HashSet会先计算对象的hashCode值来判断对象加入的位置,同时也会与其他已经加入的对象的hashCode值相比较。如果没有相符的hashCode,HashSet会假设对象没有重复出现。但是如果发现有相同的hashCode值的对象,这时就会调用equals()方法来检查这两个对象是否真的相同。如果二者相同,HashSet就不会让其加入操作成功,如果不同的话,就会重新散列到其他位置。这样我们就大大减少了equals的次数 即先判断hashcode值,相等再调用equals
- 如果两个对象hashCode值不相等,可以直接认为这两个对象不相等
- 如果两个对象hashCode值相等,且equals()方法也返回true,才认为这两个对象相等
- equals()方法判断两个对象相等,那么他们的hashCode值也要相等
- 两个对象有相同的hashCode值,他们也不一定相等(哈希碰撞)
String为什么是不可变的
- 保存字符串的数组被final修饰且为私有的,并且String类没有提供/暴露修改这个字符串的方法
- String类被final修饰导致其不能被继承,进而避免了子类破坏。
字符串常量池的作用
字符串常量池是JVM为了提升性能和减少内存消耗针对字符串(String类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建
12、异常
Exception和Error有什么区别?
java中,所有的异常都有一个共同的祖先 java.lang包中的Throwable类。
- Exception:程序本身可以处理的异常,可以通过catch来进行捕获。
- Error:属于程序无法处理的错误,不建议通过catch捕获。例如Java虚拟机运行错误
finally中的代码一定会执行吗
不一定。在某些情况下,finally中的代码不会被执行,比如finally之前虚拟机被终止运行,程序所在的线程死亡,关闭CPU
异常使用有哪些需要注意的地方?
- 不要把异常定义为静态变量,因为这样会导致异常栈信息错乱。每次手动抛出异常,我们都需要手动new一个异常对象抛出
- 抛出的异常信息一定要有意义
- 建议抛出更具体的异常
- 使用日志打印异常之后就不要再抛出异常了(两者不要同时存在于一段代码逻辑中)
14、反射
什么是反射
反射之所以被称为框架的灵魂,主要因为它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射,你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
反射的优缺点
- 反射可以让我们的代码更加灵活,为各种框架提供开箱即用的功能提供了便利
- 增加了安全问题,比如可以无视泛型参数的安全检查,另外反射的性能也要稍差些,不过对于框架来说实际是影响不大的
反射的应用场景。虽然平时大部分写的是业务代码,很少会接触到直接使用反射机制的场景。但是,正是因为反射,我们才能这么轻松的使用各种框架。这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。另外,像Java的一大利器注解的实现,也使用到了反射。
15、注解
什么是注解
注解是Java5开始引入的新特性,可以看做是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用
注解的解析方法
- 编译期直接扫描:编译器在编译java代码的时候扫描对应的注解并处理
- 运行期通过反射处理:像框架中自带的注解(Spring框架中的@Value)都是通过反射来进行处理的
16、SPI
什么是SPI
Service Provider Interface ,专门提供给服务提供者或者扩展框架功能的开发者去使用的一个接口
SPI将服务接口和具体的服务实现分离开来,将服务调用方和服务实现者解耦,能够提升程序的扩展性、可维护性。修改或者替换服务实现并不需要修改调用方。
SPI的优缺点
- 通过SPI机制能够大大地提高接口设计的灵活性
- 缺点:需要遍历加载所有的实现类,不能做到按需加载,这样效率相对较低;当多个ServiceLoader同时load时,会有并发问题
17、I/O
Java I/O流
IO即 Input/Output ,输入和输出。IO流在java中分为输入流和输出流,而根据数据的处理方式,分为字节流和字符流
I/O流为什么要分为字节流和字符流?
- 字符流是由java虚拟机将字节转换得到的
- 如果我们不知道源码类型的话,使用字节流的过程中很容易出现乱码问题
18、语法糖
版权声明:
本文来源网络,所有图片文章版权属于原作者,如有侵权,联系删除。
本文网址:https://www.bianchenghao6.com/h6javajc/24679.html