GeekIBLi

基础面试题目

2021-07-26

1.String不可变

String 对象的不可变性

了解了 String 对象的实现后,你有没有发现在实现代码中 String 类被 final 关键字修饰了,而且变量 char 数组也被 final 修饰了。

我们知道类被 final 修饰代表该类不可继承,而 char[] 被 final+private 修饰,代表了 String 对象不可被更改。Java 实现的这个特性叫作 String 对象的不可变性,即 String 对象一旦创建成功,就不能再对它进行改变。

Java 这样做的好处在哪里呢?

第一,保证 String 对象的安全性。假设 String 对象是可变的,那么 String 对象将可能被恶意修改。

第二,保证 hash 属性值不会频繁变更,确保了唯一性,使得类似 HashMap 容器才能实现相应的 key-value 缓存功能。

第三,可以实现字符串常量池。在 Java 中,通常有两种创建字符串对象的方式,一种是通过字符串常量的方式创建,如 String str=“abc”;另一种是字符串变量通过 new 形式的创建,如 String str = new String(“abc”)。

当代码中使用第一种方式创建字符串对象时,JVM 首先会检查该对象是否在字符串常量池中,如果在,就返回该对象引用,否则新的字符串将在常量池中被创建。这种方式可以减少同一个值的字符串对象的重复创建,节约内存。

2.String 和 StringBuilder、StringBuffer 的区别?

https://www.cnblogs.com/weibanggang/p/9455926.html

3.描述一下 JVM 加载 class 文件的原理机制?

https://www.cnblogs.com/williamjie/p/11167920.html

4.char 型变量中能不能存贮一个中文汉字,为什么?

正确答案:

char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,

所以,char型变量中当然可以存储汉字啦。不过,如果某个特殊的汉字没有被包含在unicode编码字符集中,

那么,这个char型变量中就不能存储这个特殊汉字。

补充说明:unicode编码占用两个字节,所以,char类型的变量也是占用两个字节

5.抽象类(abstract class)和接口(interface)有什么异同?

https://blog.csdn.net/aptentity/article/details/68942916

6.静态嵌套类(Static Nested Class)和内部类(Inner Class)的不同?

https://blog.csdn.net/machinecat0898/article/details/80071242

7.抽象的(abstract)方法是否可同时是静态的(static),(native), synchronized 修饰?

答:都不能。
抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。
本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。
synchronized和方法的实现细节有关,抽象方法不涉及实现细节,因此也是相互矛盾的。

8.如何实现对象克隆

https://www.cnblogs.com/fnlingnzb-learner/p/10649509.html

9.内部类可以引用它的包含类(外部类)的成员吗?有没有什么限制

https://www.cnblogs.com/aademeng/articles/11084885.html
https://www.cnblogs.com/dolphin0520/p/3811445.html

静态内部类:它是用static修饰的,在访问限制上它只能访问外部类中的static所修饰的成员变量或者是方法
成员内部类:成员内部类是最普通的内部类,它可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
【注意】当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
局部内部类:局部内部类是定义在外围类的方法中的,在访问的时候它可以直接访问外围类的所有成员!但是不能随便访问局部变量,除非这个局部变量被final修饰。
匿名内部类:

10.Java 中的 final 关键字有哪些用法?

https://www.cnblogs.com/dotgua/p/6357951.html
多线程下的final语义 👇
https://www.codercc.com/backend/basic/juc/concurrent-keywords/final.html#_1-final%E7%9A%84%E7%AE%80%E4%BB%8B

  • 修饰变量
    基本类型的变量,值是不可以变化的
    引用类型的变量,引用是不可以变化的,但是可以修改引用的值
    方法参数: 保证这个变量在这个方法中的值不会发生变化
  • 修饰方法
    它表示该方法不能被覆盖。这种使用方式主要是从设计的角度考虑,即明确告诉其他可能会继承该类的程序员,不希望他们去覆盖这个方法。这种方式我们很容易理解,然而,关于private和final关键字还有一点联系,这就是类中所有的private方法都隐式地指定为是final的,由于无法在类外使用private方法,所以也就无法覆盖它
  • 修饰类
    用final修饰的类是无法被继承的

11.Thread 类的 sleep()方法和对象的 wait()方法都可以让线程暂停执行,它们有什么区别?

sleep()方法是Thread类

sleep是Thread的静态native方法,可随时调用,会使当前线程休眠,并释放CPU资源,但不会释放对象锁;

wait()方法是Object类

wait()方法是Object的native方法,只能在同步方法或同步代码块中使用,调用会进入休眠状态,并释放CPU资源与对象锁,需要我们调用notify/notifyAll方法唤醒指定或全部的休眠线程,再次竞争CPU资源.

注意:
sleep(long millis)存在睡眠时间,不算特点
因为wait()方法存在重载wait(long timeout),即设置了等待超时时间
它们两个都需要再次抢夺CPU资源

12.线程的 sleep()方法和 yield()方法有什么区别?

sleep()方法在给其他线程运行机会时不考虑线程的优先级。因此会给低优先级的线程运行的机会,而yield()方法只会给相同优先级或更高优先级的线程运行的机会。
线程执行sleep()方法后会转入阻塞状态,所以执行sleep()方法的线程在指定的时间内肯定不会被执行,而yield()方法只是使当前线程重新回到就绪状态,所以执行yield()方法的线程有可能在进入到就绪状态后又立马被执行。

13.线程的基本状态以及状态之间的关系

https://blog.csdn.net/zhangdongnihao/article/details/104029972 https://juejin.cn/post/6885159254764814349

14.访问修饰符 public,private,protected,以及不写(默认)时的区别?

15.请说出与线程同步以及线程调度相关的方法。

(1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;
(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;
(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

补充:Java 5 通过 Lock 接口提供了显式的锁机制(explicit lock),增强了灵活性以及对线程的协调。Lock 接口中定义了加锁(lock())和解锁(unlock())的方法,同时还提供了 newCondition()方法来产生用于线程之间通信的 Condition 对象;此外,Java 5 还提供了信号量机制(semaphore),信号量可以用来限制对某个共享资源进行访问的线程的数量。在对资源进行访问之前,线程必须得到信号量的许可(调用 Semaphore 对象的 acquire()方法);在完成对资源的访问后,线程必须向信号量归还许可(调用 Semaphore 对象的 release()方法)。

16.synchronized 关键字的用法?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SyncDemo {

final Object lock = new Object();

public synchronized void m1(){}

public void m2(){
synchronized (SyncDemo.class){
// TODO
}
synchronized (lock){

}
synchronized (this){

}
}

public synchronized static void m3(){

}
}

17.Java 中如何实现序列化,有什么意义?

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。
序列化是为了解决对象流读写操作时可能引发的问题(如果不进行序列化可能会存在数据乱序的问题)。要实现序列化,需要让一个类实现 Serializable 接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过 writeObject(Object)方法就可以将实现对象写出(即保存其状态);如果需要反序列化则可以用一个输入流建立对象输入流,然后通过 readObject 方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆

18.阐述 JDBC 操作数据库的步骤

下面的代码以连接本机的 Oracle 数据库为例,演示 JDBC 操作数据库的步骤。
(1) 加载驱动。
Class.forName("oracle.jdbc.driver.OracleDriver");
(2) 创建连接。
Connection con = DriverManager.getConnection("jdbc:oracle:thin:@localhost:1521:orcl","scott", "tiger");
(3) 创建语句。

1
2
3
PreparedStatement ps = con.prepareStatement("select * from emp where sal between ? and ?");
ps.setint(1, 1000);
ps.setint(2, 3000);

(4)执行语句。
ResultSet rs = ps.executeQuery();
(5)处理结果。

1
2
3
4
while(rs.next()) {
System.out.println(rs.getint("empno") + " - " +
rs.getString("ename"));
}

(6) 关闭资源。

1
2
3
4
5
6
7
8
9
10
finally {
if(con != null) {
try {
con.close();
}
catch (SQLException e) {
e.printStackTrace();
}
}
}

提示:关闭外部资源的顺序应该和打开的顺序相反,也就是说先关闭 ResultSet、再关闭 Statement、在关闭 Connection。上面的代码只关闭了 Connection(连接),虽然通常情况下在关闭连接时,连接上创建的语句和打开的游标也会关闭,但不能保证总是如此,因此应该按照刚才说的顺序分别关闭。此外,第一步加载驱动在 JDBC 4.0 中是可以省略的(自动从类路径中加载驱动),但是我们建议保留。

19.Statement 和 PreparedStatement 有什么区别?哪个性能更好?

与 Statement 相比,
①PreparedStatement 接口代表预编译的语句,它主要的优势在于可以减少 SQL 的编译错误并增加 SQL 的安全性(减少 SQL 注射攻击的可能性);②PreparedStatement 中的 SQL 语句是可以带参数的,避免了用字符串连接拼接 SQL 语句的麻烦和不安全;
③当批量处理 SQL 或频繁执行相同的查询时,PreparedStatement 有明显的性能上的优势,由于数据库可以将编译优化后的SQL语句缓存起来,下次执行相同结构的语句时就会很快(不用再次编译和生成执行计划)。

补充:为了提供对存储过程的调用,JDBC API 中还提供了 CallableStatement 接口。存储过程(Stored Procedure)是数据库中一组为了完成特定功能的 SQL 语句的集合,经编译后存储在数据库中,用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执行它。虽然调用存储过程会在网络开销、安全性、性能上获得很多好处,但是存在如果底层数据库发生迁移时就会有很多麻烦,因为每种数据库的存储过程在书写上存在不少的差别。

20.在进行数据库编程时,连接池有什么作用?

由于创建连接和释放连接都有很大的开销(尤其是数据库服务器不在本地时,每次建立连接都需要进行 TCP 的三次握手,释放连接需要进行 TCP 四次握手,造成的开销是不可忽视的),为了提升系统访问数据库的性能,可以事先创建若干连接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭连接,从而避免频繁创建和释放连接所造成的开销,这是典型的用空间换取时间的策略(浪费了空间存储连接,但节省了创建和释放连接的时间)。池化技术在Java 开发中是很常见的,在使用线程时创建线程池的道理与此相同。基于 Java 的开源数据库连接池主要有:C3P0、Proxool、DBCP、BoneCP、Druid 等。

补充:在计算机系统中时间和空间是不可调和的矛盾,理解这一点对设计满足性能要求的算法是至关重要的。大型网站性能优化的一个关键就是使用缓存,而缓存跟上面讲的连接池道理非常类似,也是使用空间换时间的策略。可以将热点数据置于缓存中,当用户查询这些数据时可以直接从缓存中得到,这无论如何也快过去数据库中查询。当然,缓存的置换策略等也会对系统性能产生重要影响,对于这个问题的讨论已经超出了这里要阐述的范围。

21.什么是 DAO 模式?

DAO(Data Access Object)顾名思义是一个为数据库或其他持久化机制提供了抽象接口的对象,在不暴露底层持久化方案实现细节的前提下提供了各种数据访问操作。在实际的开发中,应该将所有对数据源的访问操作进行抽象化后封装在一个公共API中。
用程序设计语言来说,就是建立一个接口,接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中,当需要和数据源进行交互的时候则使用这个接口,并且编写一个单独的类来实现这个接口,在逻辑上该类对应一个特定的数据存储。DAO 模式实际上包含了两个模式,一是 DataAccessor(数据访问器),二是 Data Object(数据对象),前者要解决如何访问数据的问题,而后者要解决的是如何用对象封装数据。

22.Java 中是如何支持正则表达式操作的?

Java 中的 String 类提供了支持正则表达式操作的方法,包括:matches()、replaceAll()、replaceFirst()、split()。此外,Java 中可以用 Pattern 类表示正则表达式对象,它提供了丰富的 API 进行各种正则表达式操作。面试题: - 如果要从字符串中截取第一个英文左括号之前的字符串,例如:北京市(朝阳区)(西城区)(海淀区),截取结果为:北京市,那么正则表达式怎么写?

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.regex.Matcher;
import java.util.regex.Pattern;
class RegExpTest {
public static void main(String[] args) {
String str = "北京市(朝阳区)(西城区)(海淀区)";
Pattern p = Pattern.compile(".*?(?=\()");
Matcher m = p.matcher(str);
if(m.find()) {
System.out.println(m.group());
}
}
}
Tags: Java