备战实习,会定期的总结常考的面试题,大家一起加油! 🎯
本文章参考:
- JavaGuide
- Java 全栈知识体系
- Java学习
注意:如果本文中有错误的地方,欢迎评论区指正!🍭
往期链接:
🧭【面试题】计算机网络篇-10道常见面试题p1
⚡【面试题】JVM篇-10道常见面试题p1
🎈【面试题】Java并发篇-10道常见面试题p1
1.说说Java语言的特点?
- Java是一种面向对象的语言
- Java通过Java虚拟机实现了平台无关性,一次编译,到处运行
- 支持多线程
- 支持网络编程并且很方便
- 具有较高的安全性和可靠性
- 编译与解释并存
👨💻面试官追问:解释下什么叫编译什么叫解释?
我们可以将高级编程语言按照程序的执行方式分为两种:
- 编译型 :编译型语言会通过编译器将源代码一次性翻译成可被该平台执行的机器码。一般情况下,编译语言的执行速度比较快,开发效率比较低。常见的编译性语言有 C、C++、Go、Rust 等等。
- 解释型 :解释型语言会通过解释器一句一句的将代码解释(interpret)为机器代码后再执行。解释型语言开发效率比较快,执行速度比较慢。常见的解释性语言有 Python、JavaScript、PHP 等等。
👨💻面试官又问:那为什么说 Java 语言编译与解释并存?
这是因为 Java 语言既具有编译型语言的特征,也具有解释型语言的特征。
因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要①先经过编译步骤,生成字节码(
.class
文件),这种②字节码必须由 Java 解释器来解释执行。
2.解释下什么是面向对象?
面向对象是一种基于面向过程的编程思想,是向现实世界模型的自然延伸,这是一种"万物皆对象"的编程思想。由执行者变为指挥者,在现实生活中的任何物体都可以归为一类事物,而每一个个体都是一类事物的实例。面向对象的编程是以对象为中心,以消息为驱动。
👨💻面试官追问:说一下面向对象和面向过程的区别?
- 编程思路不同︰面向过程以实现功能的函数开发为主,而面向对象要首先抽象出类、属性及其方法,然后通过实例化类、执行方法来完成功能
- 封装性︰都具有封装性,但是面向过程是封装的是功能,而面向对象封装的是数据和功能
- 面向对象具有继承性和多态性,而面向过程没有继承性和多态性,所以面向对象优势很明显
- 面向过程性能比面向对象高:因为类调用时需要实例化,开销比较大,比较消耗资源(所以当性能是最重要的考量因素的时候,比如单片机、嵌入式开发、Linux/Unix 等一般采用面向过程开发)
- 面向对象易维护、易复用、易扩展: 因为面向对象有封装、继承、多态性的特性,所以可以设计出低耦合的系统,使系统更加灵活、更加易于维护
👨💻面试官继续追问:仔细说说面向对象的三大特征?
封装:通常认为封装是把数据和操作数据的方法封装起来,对数据的访问只能通过已定义的接口。
栗子:
/** * @Auther: xppll * @Date: 2021/11/24 19:17 */ public class Person { private String name;//name属性私有化 private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } //只能通过get方法获取 public String getName() { return name; } //封装操作数据的方法 public void work() { System.out.println(name + "---" + age); } }
继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类/基类),得到继承信息的被称为子类(派生类)。
例子:
/** * @Auther: xppll * @Date: 2021/11/24 19:20 */ public class Father { private String name; public void test() { System.out.println("father" + name); } } class son extends Father { private String name; private int age; }
多态:分为编译时多态(方法重载)和运行时多态〈方法重写)。要实现多态需要做两件事:
- 一是子类继承父类并重写父类中的方法(继承&重写)
- 二是用父类型引用子类型对象(向上转型)
这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为。
栗子:
/** * @Auther: xppll * @Date: 2021/11/24 19:24 */ public class Animal { public void eat() { System.out.println("Animal eat..."); } } class Dog extends Animal { @Override public void eat() { System.out.println("Dog eat..."); } } class Cat extends Animal { @Override public void eat() { System.out.println("Cat eat..."); } } class Main { public static void main(String[] args) { Animal dog = new Dog(); Animal cat = new Cat(); dog.eat();//Dog eat... cat.eat();//Cat eat... } }
3.说一下JDK、JRE、JVM三者之间的关系?
- JDK (Java Development Kit)是针对Java开发员的产品,是整个Java的核心,包括了Java运行环境JRE、Java工具和Java基础类库。
- JRE(Java Runtime Environment)是运行JAVA程序所必须的环境的集合,包含JVM标准实现及Java核心类库。
- JVM(Java Virtual Machine)也就是 Java虚拟机的缩写,是整个java实现跨平台的最核心的部分,能够运行以ava语言写作的软件程序。
总的来说就是JDK是Java的开发工具,JRE是Java程序运行所需的环境,JVM是Java虚拟机。它们之间的关系是JDK包含JRE和JVM,JRE包含JVM
5.谈谈你对字节码的理解?
在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class
的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。
简单来说从写Java文件到编译成字节码文件(也就是.class文件)的过程也就是Java文件编译的过程,我们所写的是Java文件而Java虚拟机编译的是字节码文件
👨💻面试官追问:这里JIT有什么用?
.class->机器码
这一步里JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码)所以后面引进了 JIT 编译器(just-in-time compilation),而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。
而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言
6.说说Java的基本数据类型?
Java 中有 8 种基本数据类型,分别为:
- 6 种数字类型 :
byte
、short
、int
、long
、float
、double
- 1 种字符类型:
char
- 1 种布尔型:
boolean
基本类型 | 位数 | 字节 | 默认值 | 包装器类型 |
---|---|---|---|---|
int | 32 | 4 | 0 | Integer |
short | 16 | 2 | 0 | Short |
long | 64 | 8 | 0L | Long |
byte | 8 | 1 | 0 | Byte |
char | 16 | 2 | ‘u0000’ | Character |
float | 32 | 4 | 0f | Float |
double | 64 | 8 | 0d | Double |
boolean | false | Boolean |
需要注意的是:对于 boolean
,官方文档未明确定义,它依赖于 JVM 厂商的具体实现。逻辑上理解是占用 1 位,但是实际中会考虑计算机高效存储因素
👨💻面试官追问:说一下自动装箱和拆箱?
- 装箱:将基本类型用包装器类型包装起来
- 拆箱:将包装器类型转换为基本类型
栗子:
Integer i = 10; //装箱 int n = i; //拆箱
👨💻面试官继续追问:原理了解吗?
什么两行代码对应的字节码:
L1 LINENUMBER 8 L1 ALOAD 0 BIPUSH 10 INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer; PUTFIELD AutoBoxTest.i : Ljava/lang/Integer; L2 LINENUMBER 9 L2 ALOAD 0 ALOAD 0 GETFIELD AutoBoxTest.i : Ljava/lang/Integer; INVOKEVIRTUAL java/lang/Integer.intValue ()I PUTFIELD AutoBoxTest.n : I RETURN
从字节码中,我们发现装箱其实就是调用了
包装类的valueOf()方法
,拆箱其实就是调用了xxxValue()
方法。
Integer i = 10
等价于Integer i = Integer.valueOf(10)
int n = i
等价于int n = i.intValue()
需要注意的是,如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。
7.Java中创建对象的几种方式?
- 使用new关键字
- 使用Class类的newlnstance()方法,该方法调用无参的构造器创建对象(反射)
Class.forName.newInstance()
- 使用clone()方法,
clone()
方法是Object
类的方法,而我们知道所有的类的父类都是Object
,所以每个类都是可以使用clone()
方法的。此类需要实现Cloneable
接口 - 反序列化,比如调用
ObjectlnputStream
类的readObject()
方法。类必须实现Serializable
接口
栗子:
/**
* @Auther: xppll
* @Date: 2021/11/25 12:37
*/
public class Student implements Serializable,Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
/**
* @Auther: xppll
* @Date: 2021/11/25 12:37
*/
public class XppMzz {
public static void main(String[] args) throws Exception {
//1.使用 new 关键字
Student student1 = new Student();
//2.使用反射的Class类的newInstance()方法
Student student2 = Student.class.newInstance();
//2.使用反射的Constructor类的newInstance()方法
Student student3 = Student.class.getConstructor().newInstance();
//3.使用对象克隆clone()方法,类必须实现Cloneable接口,并重写其clone()方法
Student student4 = (Student) student1.clone();
//4.使用反序列化ObjectInputStream 的readObject()方法。类必须实现 Serializable接口
//序列化过程
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student.xpp"));
Student s = new Student();
oos.writeObject(s);
oos.close();
//反序列化过程
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("student.xpp"));
Student student = (Student) ois.readObject();
}
}
8.说说重写和重载的区别?
- 重载:发生在同一个类中,方法名相同、参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分。发生在编译时期(方法重载实现了编译时多态)
- 重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(两同两小一大),如果父类方法访问修饰符为
private
则子类中就不是重写。发生在运行时期(方法重写实现了运行时多态)
9.知道== 和 equals() 的区别吗?
==: 对于基本类型和引用类型的作用效果是不同的
- 对于基本数据类型来说,
==
比较的是值。 - 对于引用数据类型来说,
==
比较的是对象的内存地址。
因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
equals() :用来比较两个对象的内容是否相等。需要注意的是 equals
方法不能用于比较基本数据类型的变量。
- 如果没有对
equals
方法进行重写,则比较的是引用类型的变量所指向的对象的地址 - 很多类重新了
equals
方法,比如String
、Integer
等把它变成了值比较,所以一般情况下equals
比较的是值是否相等
栗子:
public class Student implements Serializable, Cloneable {
private String name;
public Student(String name) {
this.name = name;
}
}
public class XppMzz {
public static void main(String[] args) {
Student s1 = new Student("xpp");
Student s2 = new Student("xpp");
//Student类没有重写equals方法
System.out.println(s1.equals(s2));//false
String str1 = "hhh";
String str2 = "hhh";
//String类对equals方法进行了重写
System.out.println(str1.equals(str2));//true
}
}
10.说说hashCode()的作用?
hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode()
定义在 JDK 的 Object
类中,这就意味着 Java 中的任何类都包含有 hashCode()
函数。
//Object 的 hashCode() 方法是本地方法,也就是用 C 语言或 C++ 实现的,该方法通常用来将对象的内存地址转换为整数之后返回。
public native int hashCode();
👨💻面试官继续追问:为什么重写 equals() 时必须重写 hashCode() 方法?
默认的
hashcode()
方法是根据对象的内存地址经哈希算法得来的。如果没有重写hashCode()
,则该class
的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)而我们希望的是:
- 如果两个对象相同(用
equals
比较返回true
),那么它们的hashCode
值一定要相同- 如果两个对象的
hashCode
相同,它们并不一定相同(用equals
比较返回false
)简单来说就是:如果
equals
方法判断两个对象是相等的,那这两个对象的hashCode
值也要相等。详细文章可参考:为什么重写equals()方法时,必须要求重写hashCode()方法?
👨💻面试官继续追问:有没有可能两个不相等的对象有有相同的 hashcode?
可能的,因为
hashCode()
所使用的哈希算法也许刚好会让多个对象传回相同的哈希值,也就是哈希冲突。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓碰撞也就是指的是不同的对象得到相同的hashCode
)
最后喜欢的小伙伴,记得三连哦!😏🍭😘