Java 后台开发面试题分享三

重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?

重载:同一个类,同一个方法名,不同的参数列表(参数的个数,类型和顺序不同);构造器可以重载;方法的重载与返回值类型和形参变量名无关,建议返回值类型最好相同。
重写:子类重写覆盖从父类继承下来的方法。要求方法名相同、参数列表相同以及返回值类型相同,从 jdk 1.5 开始支持返回子类类型(如果是泛型,父类泛型擦除后与子类相同就可以);方法的访问权限不能变小;子类方法不能抛出比父类更大的异常;两个方法要同为 static 或同为非 static。


为何类型转型 Integer[] => Object[] 可以,而 Integer[] => int[] 却不行

Integer[]Object[] 是小引用类型数组到大引用类型数组的转换,可以自动转换。

Integer[]int[] 是引用数据类型数组到基本数据类型数组的转换,直接强转会报错,需要遍历数组一个个地转换。


try - catch - finally - return 执行顺序

不管是否有异常产生,finally 块中代码都会执行。

当 try 和 catch 中有 return 语句时,finally 块的代码仍然会执行。

如果 finally 中没有 return 时,而且 return 在 finally 的前面,那么无论 finally 中的代码怎么样,返回的值都不会改变,仍然是之前 return 语句中保存的值。

如果 finally 中有 return 时,try 和 catch 的 return 就会失效。


线程的生命周期(状态)有哪些

新建状态(New):当线程对象被创建后,即进入了新建状态,如:Thread t = new MyThread()

就绪状态(Runnable):当调用线程对象的 start() 方法 t.start(),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待 CPU调度执行,并不是说执行了 t.start() 此线程立即就会执行。

运行状态(Running):当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。

阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对 CPU 的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被 CPU 调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

  1. 等待阻塞 – 运行状态中的线程执行 wait() 方法,使本线程进入到等待阻塞状态;

  2. 同步阻塞 – 线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

  3. 其他阻塞 – 通过调用线程的 sleep() 或 join() 或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep() 状态超时、join() 等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了 run() 方法,该线程结束生命周期。


Runnable 接口和 Callable 接口的区别

1、Callable 接口的 call() 方法可以有返回值(通过 Future 接口的 get() 方法,不过此方法是阻塞性的),而 Runnable 接口的 run() 方法没有返回值。

2、Callable 接口的 call() 方法可以声明抛出异常,而 Runnable 接口的 run() 方法不可以声明抛出异常(run 方法出现异常时会直接抛出,打印出堆栈信息,不过可以通过自定义 ThreadFactory 的方法来捕捉异常)。


Java 的安全性主要体现在哪里

语言层次的安全性主要体现在:

  1. Java 使用“引用”来替换强大但又危险的指针。由于指针可进行移动运算,指针可随便指向一个内存区域,而不管这个区域是否可用,这样做是危险的,因为原来这个内存地址可能存储着重要数据或者是其他程序运行所占用的,并且使用指针也容易出现数组越界异常。
  2. 垃圾回收机制:不需要程序员直接控制内存回收,由垃圾回收器在后台自动回收不再使用的内存。避免了因忘记及时回收而导致内存泄露;避免了因程序错误回收程序核心类库的内存而导致系统崩溃。
  3. 异常处理机制:Java 异常机制主要依赖于 try、catch、finally、throw、throws 五个关键字。
  4. 强制类型转换:只有在满足强制转换规则的情况下才能强转成功。

底层的安全性:Java 在字节码的传输过程中使用了公开密钥加密机制 (public-key cryptography)。

在运行环境提供了四级安全性保障机制:字节码校验器,类装载器,运行时内存布局,文件访问限制。


创建线程池有哪几种方式

  1. newFixedThreadPool(int nThreads) 创建一个固定长度的线程池;每当提交一个任务就创建一个线程,直到达到线程池的最大数量,这时线程规模将不再变化;当其中某个线程发生未预期的错误而结束时,线程池会补充一个新的线程。
  2. newCachedThreadPool() 创建一个可缓存的线程池;如果线程池的规模超过了处理需求,将自动回收空闲线程,而当需求增加时,则可以自动添加新线程;线程池的规模不存在任何限制。
  3. newSingleThreadExecutor() 这是一个单线程的 Executor;它创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它;它的特点是能确保依照任务在队列中的顺序来串行执行。
  4. newScheduledThreadPool(int corePoolSize) 创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于 Timer。

Synchronized 和 Lock 的区别

原始构成

  • Synchronized 是关键字,属于 JVM 层面,底层是通过 monitorenter 和 monitorexit 完成,依赖于 monitor 对象来完成。由于 wait/notify 方法也依赖于 monitor 对象,因此只有在同步块或方法中才能调用这些方法。
  • Lock 是 java.util.concurrent.locks.lock 包下的,是 API 层面的锁。

使用方法

  • Synchronized 不需要用户手动释放锁,代码完成之后系统自动让线程释放锁。
  • ReentrantLock 需要用户手动释放锁,没有手动释放可能导致死锁。

等待是否可以中断

  • Synchronized 不可中断,除非抛出异常或者正常运行完成。
  • ReentrantLock 可以中断。一种是通过 tryLock(long timeout, TimeUnit unit),另一种是 lockInterruptibly() 放代码块中,调用 interrupt() 方法进行中断。

加锁是否公平

  • Synchronized 是非公平锁
  • ReentrantLock 默认非公平锁,可以在构造方法传入 boolean 值,true 代表公平锁,false 代表非公平锁。

锁绑定多个 Condition

  • Synchronized 只有一个阻塞队列,只能随机唤醒一个线程或者唤醒全部线程。
  • ReentrantLock 用来实现分组唤醒,可以精确唤醒。

String s = new String("xyz"); 创建了几个对象?是否可以继承 String 类?

两个或一个。”xyz” 对应一个对象,这个对象放在字符串常量池,常量 ”xyz” 不管出现多少遍,都是字符串常量池的那一个;而 new String 每写一遍,就创建一个新的对象,它使用常量 ”xyz” 来创建出一个新 String 对象。如果之前已经创建了一个 ”xyz”,那么就直接从字符串常量池拿,这时只创建了一个 StringObject;但如果之前没有创建过 “xyz”,那么就会创建一个 “xyz” 常量对象并放入字符串常量池,然后 new String 再新建一个对象指向 “xyz”,这种情况下创建两个对象。

至于 String 类是否继承,答案是否定的,因为 String 类的用了 final 修饰,所以是不可继承的。


HashSet 的使用和原理(hashCode() 和 equals())

  1. HashSet 的底层是通过 HashMap 实现的,而 HashMap 的底层是采用哈希表进行数据管理的。哈希表的查询速度特别快,时间复杂度为 O(1)。哈希表需要用到哈希码 hashcode,hashcode 是一个整数值,代表对象的特征。address = hash(hashcode) ,其中 hash 是哈希函数,address 是哈希表数据的存储地址。在 Java 的哈希表中判断两个元素是否重复要使用到 hashCode() 和 equals()。hashCode() 决定数据在表中的存储位置,而 equals() 判断是否存在相同数据。
  2. 如果要把自定义类放入 HashSet 集合,就必须重写 hashCode()。如果自定义类不重写,它调用的就是 Object 的 hashCode(),但是 Object 的 hashCode() 实际上是引用对象的地址。另外,系统类已经覆盖了 hashCode() 方法,不需要重写。
  3. HashSet 添加的元素是存放在 HashMap 的 key 位置上,而 HashMap 的 value 则取了默认常量 PRESENT,是一个空对象。
  4. HashSet 的 add 方法是通过 HashMap 的 put 方法实现的:首先计算要增加对象的哈希码,根据该值来得到一个位置用来存放当前对象。如果在该位置没有一个对象存在的话,那么集合 Set 认为该对象在集合中不存在,直接增加进去。如果在该位置有对象存在的话,接着将准备增加到集合中的对象与该位置上的对象进行 equals 方法比较;如果该 equals 方法返回 false,那么集合认为集合中不存在该对象,然后将该对象通过链表或红黑树的方式插入;如果 equals 方法返回 true,就说明元素重复。

想了解更多,欢迎关注我的微信公众号:Renda_Zhang

猜你喜欢

转载自blog.csdn.net/qq_40286307/article/details/108890773