面试知识整理-Java基础

时间:2022-01-02 22:08:44
  • 三大特征:封装,继承,多态

    • 多态:简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。
  • 抽象:抽象是将一类对象的共同特征总结出来构造类的过程

  • 包装,可以讲基本类型当做对象来使用,抽象只关心对象有那些属性和行为,而不关心这些行为的细节是什么。

  • Integer:当数值在-128-127之间的时候,不会new一个新对象

    • Integer c3 = new Integer(100); Integer d1 = new Integer(100); // false 两个对象不相等

    • Integer c = 100; Integer c4 = 100; // 如果数值在-128-127之间相同(取常量池中的对象),范围外面不同

    • int b2 = 200; Integer b5 = new Integer(200); // 相同

  • && : 短路运算符,即如果第一个条件判断不成立则不会判断第二个条件(||)也是

  • round : 四舍五入,将数字+0.5然后下取整

  • switch : 可以判断的类型, byte short char int String (Long不能判断)

  • 2 << 3 : 右移/2的n次方, 左移*2的n次方

  • 数组有length属性,String有length方法

  • 构造器不能被继承,所以也就不能被重写,但是可以被重载

  • equals 相同两个对象的hashcode相同,hashcode相同,两个对象不一定相同

  • String 为 final类,不可以被继承

  • String和StringBuilder、StringBuffer的区别

    • String是只读字符串,也就意味着String引用的字符串内容是不能被改变的

    • StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。 区别为StringBuffer为线程安全

  • 重载和重写的区别

    • 重载发生在一个类中,而重写发生在子类和父类中

    • 重写要求返回值和参数类型相同,修饰符可以和父父类中的相同或者更广泛,不能声明比父类更多的异常

    • 重载要求参数个数或者类型或者顺序不同,返回值没有要求.

  • 描述一下JVM加载class文件的

    • 首先将.class文件加入到内存,然后进入连接阶段,这个阶段主要验证,准备(静态变量初始化和赋值),解析(符号引用解析为直接引用) 最后初始化,当这个类有父类的时候先去初始化父类,然后初始化这个类.
  • 抽象类和接口有什么异同

    • 接口和抽象类都不能被实例化,但可以定义引用.

    • 接口中所有的方法必须都是抽象方法,而抽象类中可以有普通方法,

    • 接口中的修饰符全是public,而抽象类都可以

    • 有抽象类的方法必须声明为抽象类,而抽象类中不一定有抽象方法

  • Java有内存泄露吗,

    • 理论上没有,因为有垃圾回收机制,但实际中可能存在无用但可达的对象,这些对象不能被GC回收, 例如Hibernate一级缓存中的对象.
  • 静态变量和实例变量

    • 静态变量static修饰,他属于类的,不属于任何一个方法,所以一个静态变量只会有一个拷贝.静态变量可以实现让多个对象共享内存。

    • 实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。

  • 对象克隆的实现方式

    • 实现Cloneable接口并重写Object类中的clone()方法;

    • 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆(ObjectOutputStream)。

  • 垃圾回收(GC)

    • 垃圾回收机制,内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃

    • 分代式垃圾收集 (伊甸园,幸存者乐园,终身颐养园)

  • 一个java文件中可以包含多个类,但是只能有一个公开类,且名字和Java文件相同

  • 一个内部类可以访问他的外部类,包括私有成员

  • final : 类: 不能被继承 方法: 不能被重写 属性: 赋值一次过后不可以再次赋值

  • 先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。

  • String s2 = new String(s1.getBytes("GB2312"), "ISO-8859-1"); GB2312编码的字符串转换为ISO-8859-1

  • try{}里有一个return语句,那么紧跟在这个try后的finally{}里的代码会不会被执行,

    • 会执行 , 如果finally还有return 语句, 则会返回finally内的语句
  • 异常处理: 异常都是Throwable子类

    • throws 声明一个方法可能抛出的异常

    • throw 在方法内抛出异常

  • 常见的异常:

    • ArithmeticException(算术异常)

    • IllegalArgumentException (非法参数异常)

    • IndexOutOfBoundsException (下标越界异常)

    • NullPointerException (空指针异常)

    • ClassCastException

  • final、finally、finalize的区别。

    • final : 修饰符

    • finally : 不管是否发生异常都会执行

    • finalize : GC再销毁对象的时候调用

  • ArrayList使用的数组方式来进行存储,所以查找元素快 ,LinkedList双向链表,插入和删除快,都不是线程安全,可以通过Collections中的synchronizedList方法

  • Collection是一个接口,它是Set、List等容器的父接口;Collections是个一个工具类,

  • sleep 和 wait

    • sleep()方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态)

    • wait()是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程

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

    • sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;

    • 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;

    • sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;

    • sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性

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

    • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;

    • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;

    • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;

    • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

  • 线程同步的方式

    • lock()

    • synchronized (同步代码块和同步方法)

    • 信号量机制

  • 多线程三种实现多线程的方式

    • 继承Thread

    • 实现Runable

    • Callable(可以抛出异常 ,可以有返回值)

  • 线程池:需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

    • newSingleThreadExecutor: 单线程的线程池

    • newFixedThreadPool: 固定大小的线程池

    • newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

    • newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

    • newSingleThreadScheduledExecutor :创建一个单线程化的支持定时的线程池,

  • synchronized 和Lock的异同

    • 都是实现线程同步功能

    • Lock性能更好, 需要自己手动释放锁

  • 实现序列化: Serializable

  • XML文档定义有几种形式?解析XML文档有哪几种方式

    • XML文档定义分为DTD和Schema两种形式,二者都是对XML语法的约束,其本质区别在于Schema本身也是一个XML文件,可以被XML解析器解析,而且可以为XML承载的数据定义类型,约束能力较之DTD更强大。

    • DOM,SAX DOM会将整个文件装入内存,当文件较大的时候会占用内存较多, saxAX是事件驱动型的XML解析方式,它顺序读取XML文件,不需要一次全部装载整个文件。当遇到像文件开头,文档结束,或者标签开头与标签结束时,它会触发一个事件,用户通过事件回调代码来处理XML文件

  • 你在项目中哪些地方用到了XML

    • 数据交换和信息配置 (Web Service )

    • XML配置信息, Spring, Web

  • JDBC链接数据库的步骤

    • 加载驱动

    • 创建连接

    • 创建语句

    • 执行语句

    • 处理结果

    • 关闭资源(关闭资源要和打开资源的顺序相反)

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

    • PreparedStatement接口代表预编译的语句,它主要的优势在于可以减少SQL的编译错误并增加SQL的安全性(减少SQL注射攻击的可能性)

    • PreparedStatement中的SQL语句是可以带参数的,避免了用字符串连接拼接SQL语句的麻烦和不安全

    • 当批量处理SQL或频繁执行相同的查询时,PreparedStatement有明显的性能上的优势,

  • 使用JDBC操作数据库时,如何提升读取数据的性能?如何提升更新数据的性能?

    • 要提升读取数据的性能,可以指定通过结果集(ResultSet)对象的setFetchSize()方法指定每次抓取的记录数(典型的空间换时间策略)

    • 要提升更新数据的性能可以使用PreparedStatement语句构建批处理,将若干SQL语句置于一个批处理中执行。

  • 连接池有什么作用

    • 为了提升系统访问数据库的性能,可以事先创建若干连接置于连接池中,需要时直接从连接池获取,使用结束时归还连接池而不必关闭连接,从而避免频繁创建和释放连接所造成的开销,

    • C3P0、Proxool、DBCP、BoneCP、Druid等。

  • 事务的属性

    • 原子性:事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败;

    • 一致性:事务结束后系统状态是一致的;

    • 隔离性:并发执行的事务彼此无法看到对方的中间状态;

    • 持久性:事务完成后所做的改动都会被持久化,

  • 事务可能发生的错误状态

    • 脏读: A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚(A事务读取时还没有发生回滚,在读取完成时出现错误,B事务回滚)

    • 不可重复读:事务A重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务B修改过了。

    • 幻读:事务A重新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B提交的行。

    • 第一类丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了。

    • 第二类丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失。

  • 四个隔离级别

    • 面试知识整理-Java基础
  • JDBC如何进行事务处理

    • 通过调用setAutoCommit(false)可以设置手动提交事务;当事务完成后用commit()显式提交事务;如果在事务处理过程中发生异常则通过rollback()进行事务回滚。还有Savepoint(保存点)的概念,允许通过代码设置保存点并让事务回滚到指定的保存点
  • 获取一个类的类对象(class)

    • 类型.class, String.class

    • 对象.getClass() "hello".getClass()

    • Class.forName() Class.forName("String")

  • 反射创建对象

    • 通过类对象调用newInstance()

    • 通过类对象的getConstructor()或getDeclaredConstructor()方法获得构造器(Constructor)对象并调用其newInstance()方法创建对象,例如:String.class.getConstructor(String.class).newInstance("Hello");

  • 如何通过反射获取和设置对象私有字段的值

    • 可以通过类对象的getDeclaredField()方法字段(Field)对象,然后再通过字段对象的setAccessible(true)将其设置为可以访问

  • 通过反射获取对象方法

    • 直接通过getMethod("name")获取到方法对象,然后通过invoke方法调用

    • Method m = str.getClass().getMethod("toUpperCase"); m.invoke(str)

  • 简述一下面向对象的"六原则一法则"。

    • 单一职责原则:一个类只做它该做的事情,

    • 开闭原则:软件实体应当对扩展开放,对修改关闭。(抽象类或者接口)

    • 依赖倒转原则:面向接口变成, 声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,

    • 里氏替换原则:任何时候都可以用子类型替换掉父类型(子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题)。

    • 接口隔离原则:接口要小而专,绝不能大而全。

    • 合成聚合复用原则:优先使用聚合或合成关系复用代码。

    • 迪米特法则:一个对象应当对其他对象有尽可能少的了解(尽量用简单的方式把业务呈现给用户,而不让用户看到业务细节)

  • 设计模式:

    • 工厂模式:工厂类可以根据条件生成不同的子类实例,这些子类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作(多态方法)。

    • 代理模式:给一个对象提供一个代理对象,并由代理对象控制原对象的引用。

    • 适配器模式:把一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起使用的类能够一起工作。

  • UML简介

    • UML是统一建模语言,为软件开发的所有阶段提供模型化和可视化支持。使用UML可以帮助沟通与交流,辅助应用设计和文档的生成,还能够阐释系统的结构和行为。
  • UML中常用的图

    • 用例图(捕获需求,用来描述系统的功能)

    • 类图(描述类和类之间的关系)

    • 时序图(描述特定任务的执行顺序以及交互关系)

  • 冒泡排序

import java.util.Comparator;

/**
* 排序器接口(策略模式: 将算法封装到具有共同接口的独立的类中使得它们可以相互替换)
* @author骆昊
*
*/
public interface Sorter { /**
* 排序
* @param list 待排序的数组
*/
public <T extends Comparable<T>> void sort(T[] list); /**
* 排序
* @param list 待排序的数组
* @param comp 比较两个对象的比较器
*/
public <T> void sort(T[] list, Comparator<T> comp);
}
/**
* 冒泡排序
*/
public class BubbleSorter implements Sorter { @Override
public <T extends Comparable<T>> void sort(T[] list) {
boolean swapped = true;
for (int i = 1, len = list.length; i < len && swapped; ++i) {
swapped = false;
for (int j = 0; j < len - i; ++j) {
if (list[j].compareTo(list[j + 1]) > 0) {
T temp = list[j];
list[j] = list[j + 1];
list[j + 1] = temp;
swapped = true;
}
}
}
} @Override
public <T> void sort(T[] list, Comparator<T> comp) {
boolean swapped = true;
for (int i = 1, len = list.length; i < len && swapped; ++i) {
swapped = false;
for (int j = 0; j < len - i; ++j) {
if (comp.compare(list[j], list[j + 1]) > 0) {
T temp = list[j];
list[j] = list[j + 1];
list[j + 1] = temp;
swapped = true;
}
}
}
}
}
  • 折半查找
public class MyUtil {

   public static <T extends Comparable<T>> int binarySearch(T[] x, T key) {
return binarySearch(x, 0, x.length- 1, key);
} // 使用循环实现的二分查找
public static <T> int binarySearch(T[] x, T key, Comparator<T> comp) {
int low = 0;
int high = x.length - 1;
while (low <= high) {
int mid = (low + high) >>> 1;
int cmp = comp.compare(x[mid], key);
if (cmp < 0) {
low= mid + 1;
}
else if (cmp > 0) {
high= mid - 1;
}
else {
return mid;
}
}
return -1;
} // 使用递归实现的二分查找
private static<T extends Comparable<T>> int binarySearch(T[] x, int low, int high, T key) {
if(low <= high) {
int mid = low + ((high -low) >> 1);
if(key.compareTo(x[mid])== 0) {
return mid;
}
else if(key.compareTo(x[mid])< 0) {
return binarySearch(x,low, mid - 1, key);
}
else {
return binarySearch(x,mid + 1, high, key);
}
}
return -1;
}
}