基础面试题
一、Java基础知识
1.1 基本概念
1.1.1Java语言有哪些优点
- Java是纯面向对象语言
- 平台无关性【一次编译,到处运行】
- Java提供了很多内置的类库
- 提供了对Web应用开发的支持
- 安全性和健壮性好
- 简洁,易理解
1.1.2Java与C/C++有什么不异同
相同点:Java与C++都是面向对象语言。
不同点:
- Java是解释性语言,而C++是编译性语言,需要经过编译->链接->执行生成可执行文件【二进制代码】
- Java的运行速度比C++慢
- Java可以跨平台执行,而C++不行。
- Java不支持多重继承,但引入了接口的方式,可以同时实现多个接口。而C++可以多重继承。
- C++语言中,需要开发人员去管理对内存的分配,而Java提供了垃圾回收器来实现垃圾的自动回收。
1.1.3 为什么需要使用public static void main( String[] args)这方法?
该方法是Java程序的入口方法,JVM在运行程序时,会首先查找main方法。
1.1.4 如何实现在main()之前输出“Hello World!"
加一段静态代码块,就能实现。由于静态代码块在类加载时就会被执行。因此,利用静态代码块实现该功能。
public class Main{
static{
System.out.println("Hello world1");
}
public static void main(String []args){
System.out.println("Hello world2");
}
}
//执行结果:
//Hello world1
//Hello world2
1.1.5Java程序初始化的顺序是怎么样?
当实例化对象时,对象所有在类的所有成员变量首先要进行初始化。当所有类成员完成初始化后,才会调用对象所在类的构造函数
创建对象。初始化原则:1.静态对象(变量)优先于非静态对象(变量)初始化,其中,静态对象(变量)只初始化一次,而非静态对象(变量)可能会初始化一次。2.父类初始化优先于子类。
3.按照成员变量的定义顺序进行初始化。
1.1.6 Java中的作用域有哪些?
1.1.7 一个Java文件中是否可以定义多个类
可以定义多个类,但只能有一个public修饰一个类。
1.1.8 什么是构造函数
1.构造函数必须与类名相同
2.每个类可以有多个构造函数
3.构造函数的参数可以有参和无参构造。
4.构造函数一定是伴随new操作一起调用,且不能由编写者直接调用,必须由系统调用。
5.构造函数的主要作用是完成对象的初始化工作。
6.构造函数不能被继承,也就不能被覆盖【重写】。但是能被重载。
7.子类可以通过super关键字来调用父类的构造函数
8.当父类和子类都没有定义构造函数时,编译器会为父类生成一个默认的无参数的构造函数,给子类也生成一个默认的无参构造函数。
1.1.9为什么Java中有些接口没有任何方法?
由于Java不支持多重继承,即一个类只能有一个父类,为了克服单继承的缺点,Java中引入了接口这一概念。接口是抽象方法定义的集合(接口也可以定义一些常量值),是一种特殊的抽象类。
接口中只包含方法的定义,没有方法的实现。接口中的所有方法都是抽象的,都是用public作用域修饰符来修饰,接口常量默认值使用public static final修饰。
一个类可以实现多个接口,因此通常可以采用实现多个接口的方式来间接达到多重继承的目的。
1.1.10Java中的clone方法有什么作用?
浅复制和深复制
1.1.11什么是反射机制
反射机制是Java中一个非常重要的特性,它允许程序在运行时进行自我检查,同时也允许对其内部的成员进行操作。
反射机制可以在运行时动态地创建类的对象。
使用场景:
动态加载类:通过反射可以在运行时动态加载类,实现插件式架构或热部署等功能。
实例化对象:通过反射可以在运行时创建类的实例,而无需显式地调用构造方法。
调用方法:通过反射可以在运行时调用类的方法,包括私有方法和受保护方法。
访问属性:通过反射可以在运行时访问类的属性,包括私有属性和受保护属性。
动态代理:反射机制可以用于创建动态代理对象,这在实现AOP(面向切面编程)和远程方法调用等场景中非常常见。
序列化和反序列化:Java反射机制可以用于实现对象的序列化和反序列化,从而在网络通信和持久化存储中使用。
测试和调试:反射可以用于编写测试代码,例如在JUnit测试框架中,通过反射可以调用私有方法和访问私有属性,从而进行单元测试。
//动态加载
package cn.weh;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class DynamicClassLoader {
public static void main(String[] args) {
try {
// 动态加载类
Class<?> dynamicClass = Class.forName("cn.weh.DynamicClass");
// 创建对象实例
Object instance = dynamicClass.getDeclaredConstructor().newInstance();
// 调用方法
Method method = dynamicClass.getMethod("dynamicMethod");
method.invoke(instance);
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException
| NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
4种创建对象有哪些?
(1)new实例化一个对象。
Student student=new Student();
(2)通过反射机制创建对象。
Student student = (Student) Class.forName("weh.Student").newInstance();
Student student = Student.class.newInstance();
Constructor<Student> constructor=Student.class.getConstructor();
Student student=constructor.newInstance();
(3)通过clone()方法创建一个对象。
【clone()方法是一个本地方法,需要实现标识接口Cloneable。】
package day0914;
public class Student implements Cloneable {
private String name;
private Student() {
}
private Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String args[]) {
Student student = new Student("jack");
try {
Student student1 = (Student) student.clone();
System.out.print(student1.getName());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
(4)通过反序列化的方式创建对象。
import java.io.*;
public class SerializationExample {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化对象到文件
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
try {
oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
MyObject myObject = new MyObject(123, "Java");
oos.writeObject(myObject);
// 反序列化对象从文件
ois = new ObjectInputStream(new FileInputStream("object.dat"));
MyObject deserializedObject = (MyObject) ois.readObject();
System.out.println("反序列化对象: " + deserializedObject);
} finally {
if (oos != null) {
oos.close();
}
if (ois != null) {
ois.close();
}
}
}
}
class MyObject implements Serializable {
private int number;
private String name;
public MyObject(int number, String name) {
this.number = number;
this.name = name;
}
@Override
public String toString() {
return "MyObject{" +
"number=" + number +
", name='" + name + '\'' +
'}';
}
}
1.1.12 package有什用?
package【包】是一个比较抽象的逻辑概念。
作用:(1)提供多层命名空间,解决命名冲突。
(2)对类按功能进行分类,使项目的组织更加清晰。
1.2 面向对象技术
(1)面向对象与面向过程有什么区别?
- 出发点不同:面向对象是集中于对象与对象之间的接口上;而面向过程是主要强调于过程的抽象化和模块化。
- 层次逻辑关系不同:面向对象是以对象的集合类作为基本单位,而面向过程是以表达过程的模块作为基本单位。
- 数据处理方式与控制程序的方式不同:面向对象是将数据与对应的代码封装成一个整体,只能自己修改数据内容,其他类不得修改,控制程序是以“事件驱动”来激活和运行程序;面向过程是直接通过程序来处理数据,控制程序上是按照设计调用或返回的程序的方式,不是自由操作的,各模块之间存在被控制与控制、被调用与调用。
- 分析设计和编码转换方式不同
(2)面向对象有哪些特征?
抽象:忽视一个主题与当前的目标无关的内容,只选择其中的一部分。抽象分为:过程抽象、数据抽象;
继承:继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。
🎈 提高代码复用性,层次化设计;
封装:封装是指将客观事物抽象成类,每个类对自身的数据和方法实行保护。
🎈隐藏内部实现细节,提高代码模块化和可重用性;
多态:多态是指允许不同类的对象对同一消息做出响应。
🎈提高代码可读性、扩展性和可维护性。
(3)面向对象的开发方式有什么优点?
1.较高的开发效率
2.保证软件的高可维护性
(4)重载和覆盖(重写)
重载
1.重载是通过不同的方法参数来区分的,例如不同的参数个数、不同的参数类型或不同的参数顺序。
2.不能通过方法的访问权限,返回值类型和抛出的异常类型来进行重载。
3.对于继承来说,如果基类方法的访问权限为private,那么就不能再派生类对其重载;如果派生类也定义了一个同名的函数,这只是一个新的方法,不会达到重载的效果。
重写
1.派生类中的重写方法必须要和基类中被覆盖的方法有相同的函数名和参数。
2.派生类中的覆盖方法的返回值必须和基类中被覆盖的方法所抛出的异常一致。
(5)Java中提供了哪两种用于多态的机制?
答:编译时多态和运行时多态。
编译时多态是通过方法的重载实现的,运行时多态是通过方法的覆盖【重写】实现的。
(6)抽象类和接口有什么异同?
abstract只能修饰类或者方法,不能修饰属性。
接口就是指一个方法的集合,接口中的所有方法都没有方法体,在Java中,接口是通过关键字interface来实现的。
抽象类(abstract class)和接口(interface)都是支持抽象类定义的两种机制。
前者表示的是一个实体,后者表示的是一个概念。
相同点:
- 都不能被实例化。
- 接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能被实例化。
不同点:
- 接口只有定义,其方法不能在接口中实现,只能通过接口实现类来实现接口中定义的方法,而抽象类可以有定义与实现,其方法可以在抽象类中被实现。
- 接口需要实现【implements】,但抽象类只能被继承【extends】。一个类可以实现多个接口,但一个类只能继承一个抽象类,因此使用接口可以间接地达到多重继承的目的。
- 接口强调特定的功能实现;而抽象类强调所属关系。
- 接口中定义的成员变量默认为public static final,只能够有静态的不能被修改的数据成员。而抽象类可以有自己的数据成员的变量,也可以有非抽象的成员方法,而且抽象类中的成员变量默认为default。
(7)内部类有哪些?
静态内部类、成员内部类、局部内部类、匿名内部类
(8)如何获取父类的类名
this.getClass().getName();//该类的名
super.getClass().getName();//获取父类
(9)this与super有什么区别?
this用来指向当前实例对象。
super用来访问父类的方法或成员变量。注意:当子类构造函数需要显示调用父类构造函数时,super()必须为构造函数中的第一条语句。
public class Test {
public Test(){
System.out.println("Test !!!");
}
}
class T extends Test{
public T(){
super();
test();
}
public void test(){
System.out.println("TTTT");
}
public static void main(String[] args) {
new T();
}
}
//运行结果
//Test !!!
//TTTT
1.3关键字
(1)命名规则
(2)break、continue、return的区别
(3)final、finally、finalize的区别
(4)assert有什么作用?
(5)static关键字有哪些作用?
为某特定数据类型或对象分配单一的存储空间,而与创建对象的个数无关。
实现某个方法或属性与类而不是对象关联在一起。
static主要有4种使用情况:成员变量、成员方法、代码块、内部类。
特别注意:不得在成员函数内部定义static变量。
错误写法:
class Test{ public static test(){ static final int i =0; //错误 } }
(6)instanceof有什么用?
instanceof是Java中一个二元运算符,它的作用是判断一个作用类型的变量所指向的对象是否一个类(接口、抽象类、父类)的实例。结果是boolean类型的数据。
1.4 基本类型与运算
基本数据类型:
笔试题:
(1)
byte b = 128;// byte的取值范围为[-128,127],所以错误
boolean flag = null;//错误,因为该类型只能是true或者false;
float f = 0.9239//错误,0.9239是double类型,需进行数据类型转换;
long a = 2147483648L; V
(2)String是基本的数据类型吗?
答:不是,String是引用类型,基本数据类型有byte、int、char、long、float、double、boolean和short.
(3)int 和Integer有什么区别?
答:Java语言提供两种不同的类型,即引用类型和原始类型(或内置类型)。int 是Java语言的原始数据类型,Integer是Java语言为int提供的封装类。
(4)赋值语句float f = 3.4是否正确?
答:错误,数据3.4默认情况下是double类型,即双精度浮点数,将double类型数据赋值给float类型的变量,会造成精度损失。因此,需要对3.4进行强转。float f = (float)3.4
(5)Math类中round、ceil、floor方法的功能各是什么?
答:round方法表示四舍五入;
ceil方法的功能是向上取整;
floor方法的功能是向下取整。
1.5 字符串与数组
(1)字符串创建与存储的机制是什么?
(2)new String("abc")创建了几个对象?
答:一个或两个。如果常量池中原来有“abc”,那么只创建一个对象;如果常量池中原来没有字符串“abc”,那么就会创建两个对象。
(3)“==”、equals和hashCode有什么区别?
答:“==”运算符用来比较两个变量的值是否相等;
equals是Object类提供的方法之一。每一个Java类都继承了Object类,所以每一个对象都具有equals这个方法。比较的是两个对象中的内容是否相同。
hashCode方法是从Object类中继承过来的,它也用来鉴定两个对象是否相等。
(4)String、StringBuffer、StringBuilder和StringTokenizer有什么区别?
String用于字符串操作,属于不可变类,而StringBuffer也是用于字符串操作,但StringBuffer属于可变类。
当一个字符串需要经常被修改时,使用StringBuffer比使用String要好很多。
StringBuilder也可以被修饰的字符串,它与StringBuffer类似,都是字符串缓冲区,但StringBuilder不是线程安全的,如果只是在单线程中使用字符串缓冲区,那StringBuilder的效率会高一些。因此在只有单线程访问时可以使用StringBuilder,当有很多个线程访问时,最好使用线程安全的StringBuffer。
执行效率比较:
StringBuilder > StringBuffer > String;
StringTokenizer是用来分割字符串的工具类。
//结果
Welcome
to
our
country
(5)Java中数据是不是对象
数组是指具有相同类型的数据的集合。根据数据的特点来说,封装了一些数据,同时提供了一些属性和方法,这样说来,数据是对象。
(6)数组的初始化方式有哪几种?
- int[] a = new int[5];
- int[] a = {1,2,3,4,5};
(7)length属性与length()方法有什么区别?
数据提供了length属性来获取数组的长度;String提供了length()方法来计算字符串的长度。
1.6 异常处理
(1)finally块中的代码什么时候被执行?
try{}finally{}
try{}catch(){}finally{}
(2)异常处理的原理是什么?
异常是指程序运行时,所发生的非正常情况或错误,,当程序违反了语义规则时,JVM就会出现的错误表示为一个异常并抛出。
- 使用throw抛出:Error、Throwable、Exception、RuntimeException
(3)运行时异常和普通异常有什么区别?
Error表示程序在运行期间出现了非常严重的错误,并且该错误是不可恢复的;
Exception表示可恢复的异常,是编译器可以捕捉到的。它包含两种类型:检查异常(checked exception)和运行时异常。
检查异常
检查异常是在程序中最经常碰到的异常。
i.异常的发生并不会导致程序出错。
ii.程序依赖于不可靠的外部条件,比如系统IO流。
运行时异常对于运行时异常,编译器没有强制性对其进行捕获并处理。
1.7 I/O流
IO流的实现机制是什么?
流本质就是数据传输,分为字节流和字符流。
Java中有几种类型的流?
常见的流有两种,分别为字节流与字符流。
流的作用主要是为了改善程序性能并且使用方便。
管理文件和目录的类型是什么?
Java提供了非常重要的类(File)来管理文件和文件夹,通过类不仅能够查看文件或目录的属性,而且还可以实现对文件或目录的创建、删除与重命名等操作。
Java Socket是什么?
Socket称为套接字,用来实现不同虚拟机或不同计算机之间的通信。
Java NIO是什么?
在非阻塞IO出现之前,Java是通过传统的Socket来实现基本的网络通信功能的。以服务器端为例,其实现基本流程如下:
NIO通过Selector、Channel和Buffer来实现非阻塞的IO操作,其实现原理如下:
NIO在网络编程中有着非常重要的作用,与传统的Socket方式相比,由于NIO采用了非阻塞的方式,在处理大量并发请求时,使用NIO要比使用Socket效率高出很多。
什么是Java序列化?
Java提供了两种对象持久化的方式,分别为序列化和外部序列化。
(1)序列化(Serialization)
在分布式环境下,当进行远程通信时,以二进制序列的形式在网络上传送。
如何实现序列化呢?其实,所有要实现序列化的类都必须实现Serializable接口,Serializable接口位于java.lang包中,它里面没有包含任何方法。使用一个输出流(例如FileOutputStream)来构造一个ObjectOutStream(对象流)对象,使用该对象的writeObject方法就可以将obj对象写出(即保存其状态),要恢复时可以使用其对应的输入流。
(2)外部序列化
Java语言还提供了另外一种方式来实现对象持久化,即外部序列化。
外部序列化与序列化主要区别在于序列化是内置的API,只需实现Serializable接口,开发人员不需要编写任何代码就可以实现对象的序列化,而使用外部序列化时,Externlizable接口中的读写方法必须由开发人员来实现。
结果:
3 //1+2=3
12 // ""+1 + 2 = 12
1.8 JVM
Java平台与内存管理
为什么Java是平台独立性语言?
平台独立性是指可以在一个平台上编写和编译程序,而在其他平台上运行。保证Java具有平台独立性的机制为“中间码”和“Java虚拟机【JVM】”。Java程序被编译后不是生成能在硬件平台上可执行的代码,而是生成一个“中间码”
一个Java程序运行从上到下的环境次序:
Java程序 、JRE/JVM、操作系统、硬件
Java程序经编译后会产生字节码再由JVM执行。
Java平台与其他平台有哪些区别
Java平台是一个纯软件的平台,该平台可以运行在一些基于硬件的平台(例如Linux、Windows等)之上。Java平台主要包含两个模块:JVM与Java API。
JVM加载class文件的原理机制是什么?
Java语言是一种具有动态性的解释性语言,类只有被加载到JVM中后才能运行。
类的加载方式分为隐式加载与显式加载两种。
隐式加载:new Test();
显式加载: class.forName("Test");
什么是GC?
垃圾回收(GC)是一个非常重要的概念,它的主要作用是回收程序中不再使用的内存。
一个以上的变量引用该对象,该对象就不会被垃圾回收。
(1)提高了开发人员的生产效率。
(2)避免因开发人员错误地操作内存而导致应用程序的崩溃,保证了程序的稳定性。
相关算法:
- 标记-清除算法(Mark and Sweep):这是最基本的垃圾回收算法之一。该算法分为两个阶段:标记阶段和清除阶段。在标记阶段,垃圾收集器将遍历整个对象图,标记所有活动对象。在清除阶段,垃圾收集器将清除未标记的对象,释放内存空间。
import java.util.HashSet; import java.util.Set; public class MarkSweepGarbageCollector { // 假设的内存区域 private Set<Object> memory = new HashSet<>(); // 根集合 private Set<Object> roots = new HashSet<>(); // 标记集合 private Set<Object> marked = new HashSet<>(); public void addToMemory(Object obj) { memory.add(obj); } public void addToRoots(Object obj) { roots.add(obj); } public void collectGarbage() { marked.clear(); // 从根集合开始标记所有可达对象 mark(roots); // 清除未标记的对象 sweep(); } private void mark(Set<Object> objects) { for (Object obj : objects) { if (!marked.contains(obj)) { marked.add(obj); // 如果对象是复合对象,递归标记它的成员 if (obj instanceof CompositeObject) { mark(((CompositeObject) obj).getReferences()); } } } } private void sweep() { Set<Object> unreachable = new HashSet<>(); // 将所有未标记的对象加入到unreachable集合 for (Object obj : memory) { if (!marked.contains(obj)) { unreachable.add(obj); } } // 移除未标记的对象 memory.removeAll(unreachable); } // 复合对象的示例,包含对其他对象的引用 static class CompositeObject { private Set<Object> references = new HashSet<>(); public void addReference(Object obj) { references.add(obj); } public Set<Object> getReferences() { return references; } } }
复制算法(Copy):该算法将堆内存分为两个部分,一半为活动对象的存储区,另一半为空闲的存储区。在垃圾回收时,将所有活动对象复制到空闲存储区,并清除原存储区中的所有对象。该算法的优点是回收效率高,但代价是需要额外的空间。
标记-整理算法(Mark and Compact):该算法是一种改进的标记-清除算法。在标记阶段,垃圾收集器将标记所有活动对象,并将它们向一端移动。在整理阶段,垃圾收集器将清除未标记的对象,并将活动对象向一端移动,从而使得内存空间连续。
分代收集算法(Generational Collection):该算法将内存分为几个不同的代,通常是年轻代和老年代。大部分对象会在创建之后很快变为垃圾,所以将它们放在较小的年轻代中进行垃圾回收。经过多次回收后仍然存活的对象会被移动到老年代,然后在老年代中进行垃圾回收。
并发垃圾回收算法(Concurrent Collection):该算法允许垃圾回收与应用程序并发进行。垃圾收集器会与应用程序同时运行,并在需要时对堆内存进行回收。这种算法的优点是减少了应用程序暂停的时间,提高了系统的响应能力。
解释:对象不再被引用了,就可以回收。C
是否可以主动通知JVM进行垃圾回收?
答:由于垃圾回收器的存在,Java语言本身没有给开发人员提供显式释放已分配内存的方法,也就是说,开发人员不能实时地调用垃圾回收器对某个对象或所有对象进行垃圾回收。但开发人员却可以通过System.gc()方法来“通知”垃圾回收器运行,当然,JVM也并不会保证垃圾回收器马上就会运行。
Java是否存在内存泄漏问题?
内存泄漏是指一个不再被程序使用的对象或变量还在内存中占有存储空间。
内存泄漏主要有两种情况:1.在堆中申请的空间没有被释放;2.对象已不再被使用,但还仍然在内存中保留着。
- 堆和栈存储
Java中的堆和栈有什么区别?
如下图所示: