2023-java面试最新总结

1. Java中的原始数据类型都有哪些,它们的大小及对应的封装类是什么?

 

  • boolean
    boolean数据类型非true即false。这个数据类型表示1 bit,但是它的大小并没有精确定义。
    《Java虚拟机规范》中如是说:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。这样我们可以得出boolean类型单独使用是4个字节,在数组中又是1个字节。那虚拟机为什么要用int来代替boolean呢?为什么不用byte或short,这样不是更节省内存空间吗?实际上,使用int的原因是,对于当下32位的CPU来说,一次进行32位的数据交换更加高效。
    综上,我们可以知道:官方文档对boolean类型没有给出精确的定义,《Java虚拟机规范》给出了“单独时使用4个字节,boolean数组时1个字节”的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。这其实是一种时空权衡。
    boolean类型的封装类是Boolean。
  • byte——1 byte——Byte
  • short——2 bytes——Short
  • int——4 bytes——Integer
  • long——8 bytes——Long
  • float——4 bytes——Float
  • double——8 bytes——Double
  • char——2 bytes——Character

2. 谈一谈”==“与”equals()"的区别。

对象类型不同

equals():是超类Object中的方法。

==:是操作符。

比较的对象不同

equals():equals是Object中的方法,在Object中equals方法实际"ruturn (this==obj)",用到的还是"==",说明如果对象不重写equals方法,实际该对象的equals和"=="作用是一样的,都是比较的地址值(因为"=="比较的就是地址值),但是大部分类都会重写父类的equals方法,用来检测两个对象是否相等,即两个对象的内容是否相等,例如String就重写了equals方法,用来比较两个字符串内容是否相同。

==:用于比较引用和比较基本数据类型时具有不同的功能,比较引用数据类型时,如果该对象没有重写equals方法,则比较的是地址值,如果重写了,就按重写的规则来比较两个对象;基本数据类型只能用"=="比较两个值是否相同,不能用equals(因为基本数据类型不是类,不存在方法)。

运行速度不同

equals():没有==运行速度快。

==:运行速度比equals()快,因为==只是比较引用。

3. Java中的四种引用及其应用场景是什么?

  • 强引用: 通常我们使用new操作符创建一个对象时所返回的引用即为强引用

  • 软引用: 若一个对象只能通过软引用到达,那么这个对象在内存不足时会被回收,可用于图片缓存中,内存不足时系统会自动回收不再使用的Bitmap

  • 弱引用: 若一个对象只能通过弱引用到达,那么它就会被回收(即使内存充足),同样可用于图片缓存中,这时候只要Bitmap不再使用就会被回收

  • 虚引用: 虚引用是Java中最“弱”的引用,通过它甚至无法获取被引用的对象,它存在的唯一作用就是当它指向的对象回收时,它本身会被加入到引用队列中,这样我们可以知道它指向的对象何时被销毁。

  • (1byte=8bit,Bit-map的基本思想就是用一个bit位来标记某个元素对应的Value,而Key即是该元素)

4. object中定义了哪些方法?
clone(), equals(), hashCode(), toString(), notify(), notifyAll(), wait(), finalize(), getClass()

5. ArrayList, LinkedList, Vector的区别是什么?

  • ArrayList: 内部采用数组存储元素,支持高效随机访问,支持动态调整大小

  • LinkedList: 内部采用链表来存储元素,支持快速插入/删除元素,但不支持高效地随机访问

  • Vector: 可以看作线程安全版的ArrayList

6. String, StringBuilder, StringBuffer的区别是什么?

  • String: 不可变的字符序列,若要向其中添加新字符需要创建一个新的String对象,即为定长

  • StringBuilder: 可变字符序列,支持向其中添加新字符(无需创建新对象)

  • StringBuffer: 可以看作线程安全版的StringBuilder,StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。

  • StringBuilder和StringBuffer默认长度为16

7.HashMap和HashTable的区别

  • HashTable是线程安全的,而HashMap不是

  • HashMap中允许存在null键和null值,而HashTable中不允许

8.HashMap的实现原理

在jdk1.8之前,HashMap的底层数据结构是数组+链表,也称为哈希表。
jdk1.8以及1.8之后,HashMap的底层数据结构是数组+链表+红黑树,也称为哈希表。

比如现在有一批数据需要插入到HashMap这个集合中,也就是上面那一批数据。要想往集合里插入数据,肯定得现new出一个集合,此时集合的构造器会为我们创建一个数组。因为jdk版本的不太,创建的数组类型也就不一样。
jdk1.8之前构造方法会创造一个长度为16的Entry[] table数组,jdk1.8以及只有不再构造方法的时候创建数组,而是在第一次调用put方法的时候创建一个Node[] table数组,这点类似于ArrayList集合。
如下图所示,当要向HashMap中存入如下这些数据的时候,比如第一组数据“12,丽丽”的时候,首先会调用hash(key)方法计算出以key值为代表的对象的hashCode值,然后再根据某种算法,也就是方法indexFor(hash,length)(这个算法小编暂时也没搞明白,太难了)计算出k-value这个数据的哈希值,即这个数据在底层数组中的存储位置。如果计算出的哈希值对应的数组的索引地址刚好没有储存数据的话,就会直接将当前这个数据存储进去。比如:“12,丽丽”计算的哈希值是1,那么如果数组索引为1的位置没有储存数据的话,就会直接把丽丽这个数据存储进去。后面数据的存储方式就以此类推。
如果要插入的数据计算出的哈希值刚好与之前的某一个相同了,就比如下图中举的例子“12,明明”,刚好计算出的哈希值也是1,这样的话就与前面丽丽的一样了,这种情况我们称为哈希碰撞,也是哈希冲突。后面的“明明”的值就会替换掉“丽丽”的值,但是key值并没有替换,还是“丽丽”的key12。
如果,有一组要插入的数据“18,空空”,根据key计算的hashcode比如是8,但是呢,然后经过某种算法之后计算出的哈希值依旧是1,也就是存储在数组中索引的位置。由于空空计算出的哈希值也是1,那么就会与之前的丽丽、明明发生哈希碰撞,但是他们的key通过hashcode()方法计算出的hashcode不一样,也就不能替换,会在“明明”的前或者后面的位置重新创建一个位置,这样就会形成一种单链表。到底是在原来数据的前面位置插入还是后面插入,HashMap会采用“7上8下的原则”,也就是说,jdk版本是1.7以及之前的话,就会选择在前面插入从而形成链表,jdk1.8之后的话,就会选择在后面插入从而形成链表。这也对应了HashMap的底层为什么是数组+链表的原理了。

9. TreeMap, LinkedHashMap, HashMap的区别是什么?

  • HashMap的底层实现是散列表,因此它内部存储的元素是无序的;

  • TreeMap的底层实现是红黑树,所以它内部的元素的有序的。排序的依据是自然序或者是创建TreeMap时所提供的比较器(Comparator)对象。

  • LinkedHashMap可以看作能够记住插入元素的顺序的HashMap。

 10.Collection与Collections的区别是什么?

  • Collection是Java集合框架中的基本接口;
  • Collections是Java集合框架提供的一个工具类,其中包含了大量用于操作或返回集合的静态方法。

11. 对于“try-catch-finally”,若try语句块中包含“return”语句,finally语句块会执行吗?
会执行。只有两种情况finally块中的语句不会被执行:**

  • 调用了System.exit()方法;

  • JVM“崩溃”了。

12.Java中的异常层次结构

我们可以看到Throwable类是异常层级中的基类。

Error类表示内部错误,这类错误使我们无法控制的;

Exception表示异常,RuntimeException及其子类属于未检查异常,这类异常包括ArrayIndexOutOfBoundsException、NullPointerException等,我们应该通过条件判断等方式语句避免未检查异常的发生。

IOException及其子类属于已检查异常,常用于数据的读写,编译器会检查我们是否为所有可能抛出的已检查异常提供了异常处理器,,例如FileNotFoundException。

13.Override, Overload的含义与区别

重载:发生在同一个类中,方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同,发生在编译时。
重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类;如果父类方法访问修饰符为private则子类中就不是重写。

14.接口与抽象类的区别

定义的关键字不同

接口使用关键字 interface 来定义。 抽象类使用关键字 abstract 来定义。

继承或实现的关键字不同

接口使用 implements 关键字定义其具体实现。 抽象类使用 extends 关键字实现继承。

子类扩展数量不同

接口的实现类可以有多个,抽象类的子类只能继承一个父类

属性访问控制符不同

接口中只能是public,抽象类中可以是public、private、protect

静态代码块的使用不同

接口中不能使用静态代码块,抽象类中可以使用静态代码块。


15.简述Java中创建新线程的两种方法

继承Thread类(假设子类为MyThread),并重写run()方法,然后new一个MyThread对象并对其调用start()即可启动新线程。

实现Runnable接口(假设实现类为MyRunnable),而后将MyRunnable对象作为参数传入Thread构造器,在得到的Thread对象上调用start()方法即可。

16. 简述Java中进行线程同步的方法

(ReentrantLock是显示锁,手动开启和关闭锁,别忘记关闭锁;

synchronized 是隐式锁,出了作用域自动释放;

ReentrantLock只有代码块锁,synchronized 有代码块锁和方法锁;

使用 ReentrantLock锁,JVM 将花费较少的时间来调度线程,线程更好,并且具有更好的扩展性(提供更多的子类);

使用顺序:

ReentrantLocksynchronized 同步代码块> synchronized 同步方法)

如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响,从而实现线程同步。

17.ThreadLocal的设计理念与作用

ThreadLocal的作用是提供线程内的局部变量,在多线程环境下访问时能保证各个线程内的ThreadLocal变量各自独立。也就是说,每个线程的ThreadLocal变量是自己专用的,其他线程是访问不到的。

ThreadLocal最常用于以下这个场景:多线程环境下存在对非线程安全对象的并发访问,而且该对象不需要在线程间共享,但是我们不想加锁,这时候可以使用ThreadLocal来使得每个线程都持有一个该对象的副本。

用来存储用户的session、处理数据库的连接或者数据库事务。

ThreadLocal<T>其实是与线程绑定的一个变量。ThreadLocal和Synchonized都用于解决多线程并发访问。

但是ThreadLocal与synchronized有本质的区别:

1、Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

2、Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本

,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。

而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。

18.wait(),sleep() 的区别

wait(): Object类中定义的实例方法。在指定对象上调用wait方法会让当前线程进入等待状态(前提是当前线程持有该对象的monitor),此时当前线程会释放相应对象的monitor,这样一来其它线程便有机会获取这个对象的monitor了。当其它线程获取了这个对象的monitor并进行了所需操作时,便可以调用notify方法唤醒之前进入等待状态的线程。

sleep(): Thread类中的静态方法,作用是让当前线程进入休眠状态,以便让其他线程有机会执行。进入休眠状态的线程不会释放它所持有的锁。

19.线程池的优势与用法及相关面试题

优势: 实现对线程的复用,避免了反复创建及销毁线程的开销;使用线程池统一管理线程可以减少并发线程的数目,而线程数过多往往会在线程上下文切换上以及线程同步上浪费过多时间。

用法: 我们可以调用ThreadPoolExecutor的某个构造方法来自己创建一个线程池。但通常情况下我们可以使用Executors类提供给我们的静态工厂方法来更方便的创建一个线程池对象。创建了线程池对象后,我们就可以调用submit方法提交任务到线程池中去执行了;线程池使用完毕后我们要记得调用shutdown方法来关闭它。

说说线程池创建需要的那几个核心参数的含义

ThreadPoolExecutor 最多包含以下七个参数:

corePoolSize:线程池中的核心线程数
maximumPoolSize:线程池中最大线程数
keepAliveTime:闲置超时时间
unit:keepAliveTime 超时时间的单位(时/分/秒等)
workQueue:线程池中的任务队列
threadFactory:为线程池提供创建新线程的线程工厂
rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略

说说submit(和 execute两个方法有什么区别?

        submit() 和 execute() 都是用来执行线程池的,只不过使用 execute() 执行线程池不能有返回方法,而使用 submit() 可以使用 Future 接收线程池执行的返回值。

线程池中核心线程数量大小怎么设置?

CPU密集型任务:比如像加解密,压缩、计算等一系列需要大量耗费 CPU 资源的任务,大部分场景下都是纯 CPU 计算。尽量使用较小的线程池,一般为CPU核心数+1。 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,会造成CPU过度切换。

IO密集型任务: 比如像 MySQL 数据库、文件的读写、网络通信等任务,这类任务不会特别消耗 CPU 资源,但是 IO 操作比较耗时,会占用比较多时间。 可以使用稍大的线程池,一般为2*CPU核心数。 IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候有其他线程去处理别的任务,充分利用CPU时间。

临时线程什么时候创建?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程;

什么时候会开始拒绝任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务;

Executors 执行器创建线程池很多基本上都是在 ThreadPoolExecutor 构造方法上进行简单的封装,特殊场景根据需要自行创建。可以把Executors理解成一个工厂类。Executors可以创建6 种不同的线程池类型。

下面对这六个方法进行简要的说明:

newFixedThreadPool: 创建一个数量固定的线程池,超出的任务会在队列中等待空闲的线程,可用于控制程序的最大并发数。
newCacheThreadPool: 短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并试图缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存。如果现有线程没有可用的,则创建一个新线程并添加到池中,如果有被使用完但是还没销毁的线程,就复用该线程。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。
newScheduledThreadPool: 创建一个数量固定的线程池,支持执行定时性或周期性任务。
newWorkStealingPool: Java 8 新增创建线程池的方法,创建时如果不设置任何参数,则以当前机器CPU 处理器数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。
newSingleThreadExecutor: 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
newSingleThreadScheduledExecutor: 此线程池就是单线程的newScheduledThreadPool。

线程池如何关闭?
线程池关闭,可以使用 shutdown() 或 shutdownNow() 方法,它们的区别是:

shutdown():不会立即终止线程池,而是要等所有任务队列中的任务都执行完后才会终止。执行完 shutdown 方法之后,线程池就不会再接受新任务了。
shutdownNow():执行该方法,线程池的状态立刻变成 STOP 状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,执行此方法会返回未执行的任务。

了解过线程池的工作原理吗?

 当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程池进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行拒绝执行策略

线程池状态

RUNNING: 线程池的初始化状态,可以添加待执行的任务。
SHUTDOWN:线程池处于待关闭状态,不接收新任务仅处理已经接收的任务。
STOP:线程池立即关闭,不接收新的任务,放弃缓存队列中的任务并且中断正在处理的任务。
TIDYING:线程池自主整理状态,调用 terminated() 方法进行线程池整理。
TERMINATED:线程池终止状态。

20.简述java中IO和NIO

java IO是面向流的,这意味着我们需要每次从流中读取一个或多个字节,直到读取完所有字节;NIO是面向缓冲的,也就是说会把数据读取到一个缓冲区中,然后对缓冲区中的数据进行相应处理。

Java IO是阻塞IO,而NIO是非阻塞IO。

21.反射的作用与原理

反射的作用概括地说是运行时获取类的各种定义信息,比如定义了哪些属性与方法。

原理是通过类的class对象来获取它的各种信息。

哪里用到了反射:

JDBC中,利用反射动态加载了数据库驱动程序。

很多框架都用到反射机制,注入属性,调用方法,如Spring

Web服务器中利用反射调用了Sevlet的服务方法

类加载机制:java源文件经过编译后产生一个字节码文件。Java虚拟机把描述类的数据加载到内存中,对数据进行处理后成为一个对象实例,而这个对象为Class类的实例。

常见的调用方式:

Class.forName()静态方法,可以利用类名在CLASSPATH中查找对应的类,并且装载到内存,返回这个class。

.newInstance()方法,会利用默认构造器创建类实例

22.java中的设计模式

设计模式:可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性

创建型模式: 包括工厂模式(又可进一步分为简单工厂模式、工厂方法模式、抽象工厂模式)、建造者模式、单例模式。

结构型模式: 包括适配器模式、桥接模式、装饰模式、外观模式、享元模式、代理模式。

行为型模式: 包括命令模式、中介者模式、观察者模式、状态模式、策略模式。

Spring开发中的工厂设计模式

1).Spring IOC

看过Spring源码就知道,在Spring IOC容器创建bean的过程是使用了工厂设计模式

Spring中无论是通过xml配置还是通过配置类还是注解进行创建bean,大部分都是通过简单工厂来进行创建的。

当容器拿到了beanName和class类型后,动态的通过反射创建具体的某个对象,最后将创建的对象放到Map中。

2).为什么Spring IOC要使用工厂设计模式创建Bean呢

在实际开发中,如果我们A对象调用B,B调用C,C调用D的话我们程序的耦合性就会变高。(耦合大致分为类与类之间的依赖,方法与方法之间的依赖。)

在很久以前的三层架构编程时,都是控制层调用业务层,业务层调用数据访问层时,都是是直接new对象,耦合性大大提升,代码重复量很高,对象满天飞

为了避免这种情况,Spring使用工厂模式编程,写一个工厂,由工厂创建Bean,以后我们如果要对象就直接管工厂要就可以,剩下的事情不归我们管了。Spring IOC容器的工厂中有个静态的Map集合,是为了让工厂符合单例设计模式,即每个对象只生产一次,生产出对象后就存入到Map集合中,保证了实例不会重复影响程序效率。

23.动态代理的相关面试

动态代理就是在运行时根据需求动态生成这些代理类,它具有静态代理的所有优点并且它便于创建、灵活性强。
动态代理的应用,最常见的就是实现AOP、典型的应用场景还有事务、日志、权限校验。

说到动态代理就要说代理模式。

代理模式是一个对象A持有另一个对象对象B的实例,当调用对象A时,对象A会委托对象B来执行,同时,A可以在执行前后做一些操作,比如记录日志、打开事务关闭事务等。代理模式的优点是,调用者并不知道对象对象A是一个代理,它就把A当成B用,而A所做的额外操作对于调用者来说是透明的;代理模式的缺点就是比较难写,因为它要把对象B所具有的所有功能在对象A中提供,并在A中调用B,而且当需要代理的对象多起来,每一个对象你都得手动编写一个代理。

不同的动态代理工具实现起来的方式不同,不过有几个组件是一般的动态代理工具中都会提供的:

  1. 被代理类——你希望扩展功能的类
  2. 代理类——代理类不需要你来编写,是自动生成的,是发布给调用者的实际对象,这里面包含了你想扩展的功能
  3. 调用拦截——这里是当你的代理类中的方法被调用时,你应该做什么,是否调用被代理类的对应方法?是否拦截异常?发生异常怎么办?委托前后应该做什么?

同时,动态代理工具一般都会自动生成一个Java类的字节码、这个类就是代理类,然后会用ClassLoader来加载这个类到JVM中。

有些动态代理要求被代理类必须实现接口,比如JDK的动态代理,有些没这些限制,比如Cglib。

24.面向对象和面向过程的区别

面向过程
优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
缺点:没有面向对象易维护、易复用、易扩展
面向对象
优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
缺点:性能比面向过程低

25.Java的四个基本特性(抽象、封装、继承,多态)

抽象:就是把现实生活中的某一类东西提取出来,用程序代码表示,我们通常叫做类或者接口。抽象包括两个方面:一个是数据抽象,一个是过程抽象。数据抽象也就是对象的属性。过程抽象是对象的行为特征。
封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行封装隐藏。封装分为属性的封装和方法的封装。
继承:是对有着共同特性的多类事物,进行再抽象成一个类。这个类就是多类事物的父类。父类的意义在于抽取多类事物的共性。
多态:允许不同类的对象对同一消息做出响应。方法的重载、类的覆盖正体现了多态。

26.构造器Constructor是否可被override

构造器不能被重写,不能用static修饰构造器,只能用public
private protected这三个权限修饰符,且不能有返回语句。

27.访问控制符public,protected,private,以及默认的区别

private只有在本类中才能访问;
public在任何地方都能访问;
protected在同包内的类及包外的子类能访问;

28.

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值