一、 面向对象
- 什么是面向对象?跟面向过程有什么区别?
- 面向过程:就像做菜步骤 —— 洗菜、切菜、炒菜、装盘。关注的是“每一步怎么做”。
- 面向对象:就像点外卖 —— 你有一个“厨师”对象,有一个“骑手”对象。你只管跟“厨师”说“做菜”,跟“骑手”说“送餐”。关注的是“谁来做这件事”。
- Java 是面向对象的,因为现实世界就是由对象(人、车、订单)组成的,更自然。
- 三大特性:封装、继承、多态
- 封装:把数据藏起来,只留几个按钮给别人用。比如你的银行卡余额是私有的(private),别人不能直接改,只能通过“存钱”、“取钱”这种公开方法(public)来操作。好处:安全、好维护。
- 继承:子类继承父类,就能直接用父类的属性和方法,还可以自己加新东西。比如“狗”继承“动物”,动物能吃饭,狗也能,而且狗还能叫。好处:代码复用。
- 多态:同一个行为,不同对象干出不同结果。比如“动物”都有一个“叫”的方法,狗叫是“汪汪”,猫叫是“喵喵”。你写代码时可以用“动物”类型的变量,运行时真正执行的是具体动物(狗或猫)的叫法。好处:灵活,易扩展。
- 重载(Overload)和重写(Override)区别
- 重载:同一个类里,方法名相同,参数列表不同(个数、类型、顺序)。就像“吃饭”这个方法,可以“吃米饭”,也可以“吃面条,喝汤”。编译器在编译时就能决定调用哪个。
- 重写:子类重新定义父类的方法,方法名、参数、返回类型都一样。比如父类动物“叫”是“?”,子类狗重写“叫”为“汪汪”。运行时决定调用哪个(多态的基础)。
二、 数据类型与关键字
- Java 有哪些基本数据类型?
8 种,记住口诀:整、浮、字、布。
- 整数:
byte(1字节),short(2),int(4),long(8) - 浮点数:
float(4),double(8) - 字符:
char(2) - 布尔:
boolean(true/false)
注意:String 不是基本类型,是引用类型(类)。
5. final 关键字有什么用?
- 修饰变量:变量变成常量,赋值后不能改。比如
final int MAX = 100; - 修饰方法:方法不能被重写。
- 修饰类:类不能被继承,比如
String类就是final的。
6. static 关键字什么意思?
- 静态的,属于类本身,不属于某个对象。就像“全校学生的总数”,不依赖某个学生,用
Student.totalCount就能访问。 - 静态方法里不能直接访问非静态成员(因为还没创建对象呢)。
- 静态代码块:类加载时执行一次,用来初始化静态资源。
三、 字符串
7. String 为什么是不可变的?
String 类里存字符的数组是 final 的,而且不提供修改它的方法。你每次对 String 做 + 或 concat,其实是创建了一个新字符串,原来的没变。好处:安全(多线程不用加锁)、可以缓存(字符串常量池)。
8. String, StringBuilder, StringBuffer 区别?
String:不可变,适合不经常改的字符串。StringBuilder:可变,线程不安全,但快。适合单线程下频繁拼接。StringBuffer:可变,线程安全(方法加了synchronized),但慢一些。适合多线程下拼接。
一句话:单线程用 StringBuilder,多线程用 StringBuffer,不动用 String。
四、 集合框架(重中之重)
9. ArrayList 和 LinkedList 区别?
ArrayList:底层是数组。查找快(有下标),增删慢(因为要移动后面元素)。LinkedList:底层是双向链表。增删快(只改前后指针),查找慢(要挨个遍历)。- 实际中
ArrayList用得更多,因为大部分场景是遍历和查。
10. HashMap 原理?简单说下。
- 底层是“数组 + 链表 + 红黑树”。你存一个键值对,先算 key 的哈希值,然后通过哈希函数算出应该放在数组的哪个位置(桶)。
- 如果那个位置是空的,直接放;如果已经有个节点(哈希冲突),就挂在它后面形成链表。链表太长(>8)且数组长度≥64,就转成红黑树,提高查询效率。
- 取数据时,同样算哈希,找到桶,然后遍历链表/树,用
equals找匹配的 key。 - 扩容:当元素个数超过阈值(容量 * 负载因子0.75),就翻倍扩容,所有元素重新分配位置(rehash)。
11. HashMap 和 Hashtable 区别?
Hashtable是古早产物,线程安全(方法都加synchronized),不允许 null 键和 null 值。HashMap线程不安全,允许一个 null 键和多个 null 值,性能更好。想要线程安全可以用ConcurrentHashMap。
12. ConcurrentHashMap 为什么高效?
- 它把数据分成多个段(JDK1.7)或者直接用更细粒度的 CAS + synchronized(JDK1.8 之后)。多个线程可以同时操作不同的桶,不用锁整个表,所以并发性能好。
五、 异常处理
13. throw 和 throws 区别?
throw:在方法体内“手动”抛出一个异常对象。比如throw new RuntimeException();throws:写在方法声明后面,表示这个方法可能会抛出某种异常,调用方必须处理或继续往上抛。
14. checked 异常和 unchecked 异常区别?
- checked 异常(受检异常):编译器强制你必须处理(try-catch 或 throws)。比如
IOException,SQLException。一般是外部环境问题,比如文件没找到。 - unchecked 异常(非受检异常):不强制处理,一般是程序 bug。比如
NullPointerException,ArrayIndexOutOfBoundsException。运行时才报出来。
六、 反射与泛型
- 什么是反射?有什么作用?
反射就是在程序运行时,动态地获取类的信息(有哪些方法、字段、构造器),甚至能调用方法、修改字段。就像你拿到一个黑盒子,反射能让你透视里面的结构。用途:框架(Spring 创建对象)、动态代理、注解处理器等。缺点:有性能开销,破坏封装。
- 泛型是什么?有什么用?
泛型就是“类型参数”,让你写一个类或方法时可以先用一个占位符(比如 T),等到使用时再指定具体类型。比如 ArrayList<String> 就是只能放 String 的列表。好处:编译时类型检查,避免强制类型转换,更安全。
七、 JVM 内存结构(基础)
- JVM 内存主要分哪几块?
- 堆:所有对象和数组都在这,也是垃圾回收的主要区域。线程共享。
- 栈(虚拟机栈):每个方法执行时会创建一个栈帧,存局部变量、操作数栈、方法返回地址。线程私有。
- 方法区(JDK1.8 之后叫元空间):存类信息、常量、静态变量。线程共享。
- 程序计数器:记录当前线程执行到哪一行字节码。线程私有。
- 本地方法栈:给 native 方法用的。
- 什么时候会内存溢出(OOM)?
- 堆内存溢出:不断创建对象,但对象都被引用着,垃圾回收不了,最后堆满了。比如无限往
ArrayList里加对象。 - 栈内存溢出:递归调用太深,每次调用都压栈,栈空间不够了。
八、 多线程基础(简单版)
- 创建线程有哪几种方式?
- 继承
Thread类,重写run()。 - 实现
Runnable接口,传给Thread。 - 实现
Callable接口(有返回值,可抛异常),配合FutureTask。 - 用线程池
ExecutorService。
20. synchronized 原理?
它可以锁住一个对象或一个类的方法/代码块。底层是每个对象有一个监视器锁(monitor)。当线程进入 synchronized 代码块时,会尝试获取对象的锁,如果被别的线程占了,就阻塞等待。锁释放后,别的线程才能拿到。JDK1.6 之后做了优化,有偏向锁、轻量级锁、重量级锁。