Java面试题-Java基础

1. 什么是Java?

Java是一门面向对象的编程语言,可跨平台的语言,与操作系统无关。

2. Java的特点?

  • 简单性
  • 面向对象(封装、继承、多态)
  • 平台无关性
  • 可靠性
  • 安全性
  • 多线程
  • 网络编程
  • 编译与解释共存

3. Java面向对象的三大特性?你是怎么理解这三大特性的?

Java ⾯向对象编程三⼤特性: 封装 继承 多态

封装

封装就是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。
封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。

继承

继承就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接
访问父类中的非私有的属性和行为。

关于继承如下3点请记住

1.子类拥有父类所有的属性和方法(包括私有属性和私有方法),但是父类的私有属性和私有方法子类是无法访问的只是拥有
2.子类可以拥有自己的属性和方法,即子类对父类进行扩展;
3.子类可以拥有自己的方式对父类进行实现。

多态

多态就是指同一行为,具有多个不同表现形式。

在Java中有两种形式可以实现多态:继承(多个子类对同一方法重写)和接口(实现接口并覆盖接口中的同一方法)

4. JDK和JRE有什么区别?

JDK:开发工具包,为Java提供了开发环境和运行环境;
JRE:运行环境包,为Java提供了运行环境;

JDK包含JRE,还有编译器(javac)和工具(javadoc和jdb)
JRE包含Java虚拟机(JVM),Java类库和Java命令和一些基础构件

5. Oracle JDK 和 OpenJDK 的对比?(了解)

  1. OracleJDK每6个月发布一次主要版本,而OpenJDK每三个月发布一次;
  2. OpenJDk是一个参考模型并且是完全开源的,而OracleJDk是OpenJDK的一种实现,并不是完全开源的;
  3. OracleJDK比OpenJDK更稳定;
  4. 在响应性能和JVM性能方面,OracleJDK与OpenJDK相比提供了更好的性能;
  5. OpenJDK不会为即将发布的版本提供长期的支持,每次用户都必须通过更新最新版本来获得支持;
  6. OracleJDK根据二进制代码许可协议获得许可,而OpenJDK是根据GPL v2许可获得许可。

6. 面向对象和面向过程的区别?

面向过程:面向过程的性能比面向对象高,因为类的调用过程需要要实例化对象,开销比较大,比较消耗资源;
面向对象:面向对象是易维护、易复用、易扩展,面向对象具有封装、继承、多态三大特性,可以设计出低耦合的系统,
使系统更加灵活,更加易于维护。

7. 如何理解访问修饰符的区别?

在这里插入图片描述

8. 八种基本数据类型的大小,以及他们的封装类?

在这里插入图片描述
1.int是基本数据类型,Integer是int的封装类,是引用类型。int默认值是0,而Integer默认值是null,所以Integer能区分出0和null的情况。一旦java看到null,就知道这个引用还没有指向某个对象,再任何引用使用前,必须为其指定一个对象,否则会报错。

2.基本数据类型在声明时系统会自动给它分配空间,而引用类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后才可以赋值。数组对象也是一个引用对象,将一个数组赋值给另一个数组时只是复制了一个引用,所以通过某一个数组所做的修改在另一个数组中也看的见。

9. 自动装箱与拆箱?

装箱:将基本数据类型转换为引用数据类型;
拆箱:将引用数据类型转换为基本数据类型;

转换规则参考:https://blog.csdn.net/qq_39995253/article/details/79438093

9.1 int与integer、double和Double直接的转换

在这里插入图片描述

9.2 float、Float与String之间的转换

在这里插入图片描述

9.3 char与Character、String之间的转换

在这里插入图片描述

9.4 byte[]、char[]与String之间的转换

在这里插入图片描述

9.5 char[]与byte[]之间的转换

在这里插入图片描述

10. 重载和重写的区别?

重载发生在一个类中,同名的方法如果有不同的参数列表(参数类型不同、参数个数不同或者二者都不同、参数顺序不同)则视为重载;

重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方
法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

方法重载的规则:

  • 1.方法名一致,参数列表中参数的顺序,类型,个数不同
  • 2.重载与方法的返回值无关,存在于父类和子类,同类中。
  • 3.可以抛出不同的异常,可以有不同修饰符

方法重写的规则:

  • 1.参数列表必须完全与被重写方法的一致,返回类型必须完全与被重写方法的返回类型一致
  • 2.构造方法不能被重写,声明为 final 的方法不能被重写,声明为 static 的方法不能被重写,但是能够被再次声明。
  • 3.访问权限不能比父类中被重写的方法的访问权限更低。
  • 4.重写的方法能够抛出任何非强制异常(UncheckedException,也叫非运行时异常),无论被重写的方法是否抛出异常。
    但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

11. String、StringBuffer、StringBuilder的区别?

1)可变性

  • String 类中使⽤ final 关键字修饰字符数组保存字符串,所以对象是不可变的
  • StringBuilder 与 StringBuffer 都继承⾃ AbstractStringBuilder 类在 AbstractStringBuilder 中也是使⽤字符数组保存字符串 char[]value, 但是没有⽤ final 关键字修饰,所以这两种对象都是可变的

2)线程安全性

  • String 中的对象是不可变的,也就可以理解为常量,线程安全
  • StringBuffer 对⽅法加了同步锁或者对调⽤的⽅法加了同步锁,所以是线程安全的
  • StringBuilder 并没有对⽅法进⾏加同步锁,所以是⾮线程安全的

3)性能

  • 每次对 String 类型进⾏改变的时候,都会⽣成⼀个新的 String 对象,然后将指针指向新的 String对象
  • StringBuffer 每次都会对 StringBuffer 对象本身进⾏操作,⽽不是⽣成新的对象并改变对象引⽤
  • 相同情况下使⽤ StringBuilder 相⽐使⽤ StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的⻛险。

对于三者使⽤的总结:

  1. 操作少量的数据: 适⽤ String
  2. 单线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuilder
  3. 多线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuffer

12. 抽象类与接口的区别?

抽象类与接口的区别,这是一个经常被问到面试题,区别主要有以下几点,我们可以从几个方面进行分析:
(一) 继承方面:
(1) 抽象类只能单继承;接口可以多实现

(二) 成员属性方面
(1) 抽象类中可以有普通属性,也可以有常量
(2) 接口中的成员变量全部默认是常量,使用public static final修饰,这个可以省略不写

(三) 代码块方面:
(1) 抽象类可以含初始化块;接口不能含初始化块

(四) 构造函数方面:
(1) 接口不能有构造函数
(2) 抽象类可以有构函数,但是这里的构造函数不是用来创建对象的,而且用来被实现类调用进行初始化操作的

(五) 方法方面:
(1) 接口里面不能定义静态方法;抽象类里面可以定义静态方法
(2) 接口里面只能是抽象方法;抽象类里面可以有抽象方法也可以有普通方法

上面就是接口与抽象类的区别,在说完区别之后,我们可以补充一下接口与抽象类之间的相同之处
(1) 接口与抽象类都不能被实例化,需要被其他进行实现或继承
(2) 接口与抽象类里面都能包含抽象方法,实现接口或继承抽象类的子类都必须实现这些抽象方法

针对相同之处的第二点,我再细说一下
(一) 超类是接口:
(1) 如果使用接口继承(extends,无法使用implements)这个接口,那么在这个子类接口中,可以实现其父类接口中的抽象方法(这种方法我从没看见过,但是在今天的面试中,面试官问我接口中是否可以有具体的实现方法,可以将这种情况举出来)
(2) 如果使用抽象类实现这个接口,可以在这个子类抽象类中实现父类接口中的抽象方法
(3) 如果使用具体类实现这个接口,那么就必须实现父类接口中的所有抽象方法

(二) 超类是抽象类:
(1) 接口不能继承抽象类
(2) 如果使用抽象类继承这个抽象类,可以在这个子类抽象类中实现父类接口中的抽象方法
(3) 如果使用具体类继承这个抽象类,那么就必须实现父类抽象类中的所有抽象方法

对于上面的说明,所以在相同之处的第二点处主要是针对具体类继承抽象类或实现接口来说的。

原文链接:https://blog.csdn.net/wxw20147854/article/details/88712029

13. 成员变量与局部变量的区别?

  1. 语法形式上看:成员变量是属于类的,⽽局部变量是在⽅法中定义的变量或是⽅法的参数;

  2. 成员变量可以被 public , private , static 等修饰符所修饰,⽽局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰

  3. 从变量在内存中的存储⽅式来看:如果成员变量是使⽤ static 修饰的,那么这个成员变量是属于类的,如果没有使⽤ static 修饰,这个成员变量是属于实例的。对象存于堆内存,如果局部变量类型为基本数据类型,那么存储在栈内存,如果为引⽤数据类型,那存放的是指向堆内存对象的引⽤或者是指向常量池中的地址

  4. 从变量在内存中的⽣存时间上看:成员变量是对象的⼀部分,它随着对象的创建⽽存在,⽽局部变量随着⽅法的调⽤⽽⾃动消失。

  5. 成员变量如果没有被赋初值:则会⾃动以类型的默认值⽽赋值(⼀种情况例外:被 final 修饰的成员变量也必须显式地赋值),⽽局部变量则不会⾃动赋值。

14. == 与 equals的区别?

== : 它的作用是判断两个对象的地址是否相同。即,判断两个对象是不是同一个对象。
基本数据类型 == 比较的是值;
引用数据类型 == 比较的是内存地址;

equals():它的作用也是判断两个对象是否相等。但它一般有两种使用情况:

  • 情况1∶类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
  • 情况2∶类覆盖了equals()方法。一般,我们都覆盖equals()方法来比较两个对象的内容是否相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。

15. hashCode 与 equals

1)hashCode()介绍:

hashCode()的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置

2)为什么要有hashCode?

我们以“HashSet如何检查重复”为例子来说明为什么要有hashCode?
当你把对象加入 HashSet时,HashSet 会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode值作比较,如果没有相符的 hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals(方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。(摘自我的Java启蒙书《Head First Java》第二版)。这样我们就大大减少了equals 的次数,相应就大大提高了执行速度。
在这里插入图片描述

3)为什么重写 equals 时必须重写 hashCode ⽅法?

如果两个对象相等,则 hashcode ⼀定也是相同的。两个对象相等,对两个对象分别调⽤ equals⽅法都返回 true。
但是,两个对象有相同的 hashcode 值,它们也不⼀定是相等的 。因此,equals ⽅法被覆盖过,则 hashCode ⽅法也必须被覆盖。

4)Object若不重写hashCode()的话,hashCode()是怎么计算出来的?

Object 的 hashcode ⽅法是本地⽅法,也就是⽤ c 语⾔或 c++ 实现的,该⽅法通常⽤来将对象的 内存地址 转换为整数之后返回。

5)若对一个类不重写,它的equals()方法是如何比较的?

类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。

16. final 关键字使用

类:被修饰的类,不能被继承。
方法:被修饰的方法,不能被重写。
变量:被修饰的变量,不能被重新赋值。

17. Java中的异常处理

异常的根类是java.lang.Throwable ,其下有两个子类:java.lang.Errorjava.lang.Exception ,平常所说的异常指java.lang.Exception。

Throwable中的常用方法:
public void printStackTrace() :打印异常的详细信息。
包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
public String getMessage() :获取发生异常的原因。
提示给用户的时候,就提示错误原因。

在这里插入图片描述

18. 当try…cath…finally遇到return时

详情了解:Java基础10-异常、线程中2.7当try…cath…finally遇到return时
https://blog.csdn.net/Yearingforthefuture/article/details/114412015

  • 最终结论
    任何执行try 或者catch中的return语句之前,都会先执行finally语句,如果finally存在的话且有return语句,那么程序就return了,所以finally中的return是一定会被return的,编译器把finally中的return实现为一个warning。

19. IO流的分类

  • 按照流的流向分,可以分为输⼊流和输出流;
  • 按照操作单元划分,可以划分为字节流和字符流;
  • 按照流的⻆⾊划分,可分为节点流和处理流。

IO流结构图:
在这里插入图片描述

20. BIO,NIO,AIO 有什么区别?

  • BIO (Blocking I/O): 同步阻塞 I/O 模式数据的读取写⼊必须阻塞在⼀个线程内等待其完成。在活动连接数不是特别⾼(⼩于单机 1000)的情况下,这种模型是⽐较不错的;

  • NIO (Non-blocking/New I/O): NIO 是⼀种同步⾮阻塞的 I/O 模型,在 Java 1.4 中引⼊了NIO 框架,对应 java.nio 包,提供了 Channel , Selector,Buffer 等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它⽀持⾯向缓冲的,基于通道的 I/O 操作⽅法。NIO 提供了与传统 BIO 模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和ServerSocketChannel 两种不同的套接字通道实现,两种通道都⽀持阻塞和⾮阻塞两种模式。阻塞模式使⽤就像传统中的⽀持⼀样,⽐较简单,但是性能和可靠性都不好;⾮阻塞模式正好与之相反。对于低负载、低并发的应⽤程序,可以使⽤同步阻塞 I/O 来提升开发速率和更好的维护性;对于⾼负载⾼并发的(⽹络)应⽤,应使⽤ NIO 的⾮阻塞模式来开发;

  • AIO (Asynchronous I/O): AIO 也就是 NIO 2。在 Java 7 中引⼊了 NIO 的改进版 NIO 2,它是异步⾮阻塞的 IO 模型。异步 IO 是基于事件和回调机制实现的,也就是应⽤操作之后会直接返回,不会堵塞在那⾥,当后台处理完成,操作系统会通知相应的线程进⾏后续的操作。AIO 是异步 IO 的缩写,虽然 NIO 在⽹络操作中,提供了⾮阻塞的⽅法,但是 NIO 的 IO ⾏为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程⾃⾏进⾏ IO 操作,IO 操作本身是同步的。查阅⽹上相关资料,我发现就⽬前来
    说 AIO 的应⽤还不是很⼴泛,Netty 之前也尝试使⽤过 AIO,不过⼜放弃了。

详情了解请参考:https://blog.csdn.net/Yearingforthefuture/article/details/114683463

21. 深拷⻉和浅拷⻉的区别?

  1. 浅拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉,此为浅拷⻉。

  2. 深拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内
    容,此为深拷⻉。

在这里插入图片描述

22. 什么是值传递和引用传递?

值传递:当一个参数按照值的方式在两个方法之间传递时,调用者和被调用者其实是用的两个不同的变量——被调用者中的变量(原始值)是调用者中变量的一份拷贝,对它们当中的任何一个变量修改都不会影响到另外一个变量。

引用传递:而当一个参数按照引用传递的方式在两个方法之间传递时,调用者和被调用者其实用的是同一个变量,当该变量被修改时,双方都是可见的。

基本类型的变量存储的都是实际的值,而引用类型的变量存储的是对象的引用——指向了对象在内存中的地址。值和引用存储在 stack()中,而对象存储在 heap()中。

在这里插入图片描述
之所以有这个区别,是因为:

  • 优势是,存取速度比堆要快,仅次于直接位于 CPU 中的寄存器。但缺点是,栈中的数据大小与生存周期必须是确定的。

  • 优势是可以动态地分配内存大小生存周期不必事先告诉编译器,Java 的垃圾回收器会自动收走那些不再使用的数据。但由于要在运行时动态分配内存,存取速度较慢

详情请参考链接:https://juejin.cn/post/6844904135519649799

23. Java8的新特性,请简单的介绍一下

1)接口的默认方法

Java 8使我们能够通过使用 default 关键字向接口添加非抽象方法实现。 此功能也称为虚拟扩展方法。

第一个例子:
在这里插入图片描述
Formula 接口中除了抽象方法计算接口公式还定义了默认方法 sqrt。 实现该接口的类只需要实现抽象方法 calculate。 默认方法 sqrt 可以直接使用。当然你也可以直接通过接口创建对象,然后实现接口中的默认方法就可以了,我们通过代码演示一下这种方式。
在这里插入图片描述
2)Lambda表达式
首先看看在老版本的Java中是如何排列字符串的:
在这里插入图片描述
只需要给静态方法 Collections.sort 传入一个 List 对象以及一个比较器来按指定顺序排列。通常做法都是创建一个匿名的比较器对象然后将其传递给 sort 方法。

在Java 8 中你就没必要使用这种传统的匿名对象的方式了,Java 8提供了更简洁的语法,lambda表达式:

在这里插入图片描述
详情请参考链接:https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247484744&idx=1&sn=9db31dca13d327678845054af75efb74&chksm=cea24a83f9d5c3956f4feb9956b068624ab2fdd6c4a75fe52d5df5dca356a016577301399548&token=1082669959&lang=zh_CN&scene=21#wechat_redirect

24. Java中Comparator和Comparable的区别?

Comparable接口用于定义对象的自然顺序,是排序接口,而comparator通常用于定义用户定制的顺序,是比较接口。我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个"该类的比较器"来进行排序。Comparable总是只有一个,但是可以有多个comparator来定义对象的顺序。

Comparator 定义在类的外部
Comparable 定义在 类的内部

25. final, finally, finalize 的区别?

1. final
类:被修饰的类,不能被继承。
方法:被修饰的方法,不能被重写。
变量:被修饰的变量,不能被重新赋值。

2.finally
finally作为异常处理的一部分,它只能用在try/catch语句中,只有与finally对应的try语句块得到执行的情况下,finally语句块才会执行。以上两种情况在执行try语句块之前已经返回或抛出异常,所以try对应的finally语句并没有执行。

3. finalize  
finalize()是在java.lang.Object里定义的,也就是说每一个对象都有这么个方法。这个方法在gc启动,该对象被回收的时候被调用。其实gc可以回收大部分的对象(凡是new出来的对象,gc都能搞定,一般情况下我们又不会用new以外的方式去创建对象),所以一般是不需要程序员去实现finalize的。

特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。

使用finalize还需要注意一个事,调用super.finalize();

一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。 所以,推荐不要使用finalize()方法,它跟析构函数不一样。

26. 抽象类能使用 final 修饰吗?

不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。

特此声明:内容是摘抄一些大佬的笔记来学习的。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
java面试题真的很多,下面我来回答一个有关多线程的问题。 在Java中实现多线程有两种方式,一种是继承Thread类,另一种是实现Runnable接口。这两种方式有何区别? 继承Thread类的方式是直接定义一个类继承Thread,并重写它的run()方法。然后创建该类的对象,并调用对象的start()方法来启动线程。这种方式简单直接,但因为Java是单继承的,所以如果某个类已经继承了其他类,就不能再直接继承Thread类实现多线程。 实现Runnable接口的方式是定义一个类实现Runnable接口,并实现其唯一的抽象方法run()。然后创建Thread类的对象,将实现了Runnable的对象作为参数传递给Thread类的构造方法。最后调用Thread对象的start()方法来启动线程。这种方式灵活性更大,因为Java允许一个类实现多个接口,所以即使某个类已经继承了其他类,仍然可以通过实现Runnable接口来实现多线程。 另一个区别在于资源共享的问题。继承Thread类的方式,不管是数据还是方法,都是线程自己拥有的,不存在共享的情况。而实现Runnable接口的方式,多个线程可以共享同一个对象的数据和方法,因为多个线程共同操作的是同一个Runnable对象。 总结来说,继承Thread类方式简单直接,但只能通过单继承来实现多线程;实现Runnable接口方式更灵活,可以解决单继承的限制,并且多个线程可以共享同一个Runnable对象的数据和方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值