本文围绕 Java 基础核心知识点,整理了多道面试题并作出详细解析。内容涵盖基本数据类型与引用类型的区别、== 和 equals () 的差异、String 相关类的特点、面向对象三大特性、接口与抽象类的不同、final 和 static 关键字的用法、重载与重写的区别、异常体系、反射、泛型、集合框架、多线程实现方式以及 sleep () 和 wait () 的区别等核心内容。
Tips:文章中大部分知识点也是笔者当初面试初级Java时被问到的八股文。
Java中的基本数据类型共8种,分为四大类:
byte
(1字节)、short
(2字节)、int
(4字节)、long
(8字节)float
(4字节)、double
(8字节)char
(2字节,存储Unicode字符)boolean
(1字节,值为true
或false
)基本数据类型与引用类型的核心区别:
int
默认0);引用类型默认值为null
。示例:
public class DataTypeDemo {
public static void main(String[] args) {
// 基本类型:直接存储值
int a = 10;
int b = a; // 复制值,a和b相互独立
b = 20;
System.out.println(a); // 输出10(a不受b影响)
// 引用类型:存储对象地址
StringBuffer sb1 = new StringBuffer("hello");
StringBuffer sb2 = sb1; // 复制引用,指向同一对象
sb2.append(" world");
System.out.println(sb1); // 输出"hello world"(sb1受sb2影响)
}
}
==
和equals()
的区别是什么?==
:用于比较值是否相等。<!---->
10 == 10
为true
)。<!---->
equals()
:是Object
类的方法,默认实现与==
一致(比较地址),但可被重写用于比较对象内容。<!---->
String
类重写了equals()
,比较字符串内容是否相同;equals()
以实现内容比较。示例:
public class EqualsDemo {
public static void main(String[] args) {
// 基本类型比较
int x = 5;
int y = 5;
System.out.println(x == y); // true(值相等)
// 引用类型比较
String s1 = new String("java");
String s2 = new String("java");
System.out.println(s1 == s2); // false(地址不同)
System.out.println(s1.equals(s2)); // true(String重写equals,内容相同)
// 自定义类(未重写equals)
Person p1 = new Person("张三");
Person p2 = new Person("张三");
System.out.println(p1.equals(p2)); // false(默认比较地址)
}
static class Person {
String name;
Person(String name) { this.name = name; }
// 若重写equals:
// @Override
// public boolean equals(Object o) {
// if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
// Person person = (Person) o;
// return Objects.equals(name, person.name);
// }
}
}
三者均用于处理字符串,核心区别在于可变性和线程安全性:
String
:不可变(底层是final char[]
),每次修改都会创建新对象,效率低,线程安全(不可变天然安全)。StringBuffer
:可变,底层是char[]
,修改时直接操作原数组,效率高;线程安全(方法加synchronized
),但多线程场景下性能有损耗。StringBuilder
:可变(同StringBuffer
),线程不安全(无同步锁),单线程下效率高于StringBuffer
(推荐使用)。示例:
public class StringDemo {
public static void main(String[] args) {
// String:不可变,每次拼接创建新对象
String s = "a";
s += "b"; // 实际创建新String对象,原对象被回收
System.out.println(s); // "ab"
// StringBuffer:线程安全,适合多线程
StringBuffer sb = new StringBuffer("a");
sb.append("b"); // 直接修改原数组
System.out.println(sb); // "ab"
// StringBuilder:非线程安全,单线程效率更高
StringBuilder sd = new StringBuilder("a");
sd.append("b");
System.out.println(sd); // "ab"
}
}
三者是面向对象的三大特性:
private
属性通过get/set
方法访问)。extends
关键字),Java单继承(一个类只能有一个父类)。示例:
// 封装:Person类隐藏name,通过方法暴露
class Person {
private String name; // 私有属性,封装细节
// 公共方法暴露接口
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
// 继承:Student继承Person,复用name属性和方法
class Student extends Person {
private int score;
// 扩展父类功能
public void setScore(int score) { this.score = score; }
}
// 多态:父类引用指向子类对象,调用重写方法
class Animal {
public void sound() { System.out.println("动物叫"); }
}
class Dog extends Animal {
@Override // 重写父类方法
public void sound() { System.out.println("汪汪叫"); }
}
public class OOPDemo {
public static void main(String[] args) {
// 封装使用
Person p = new Person();
p.setName("张三");
System.out.println(p.getName()); // 张三
// 继承使用
Student s = new Student();
s.setName("李四"); // 继承父类方法
s.setScore(90);
// 多态使用
Animal animal = new Dog(); // 父类引用指向子类对象
animal.sound(); // 输出"汪汪叫"(调用子类重写的方法)
}
}
两者均用于抽象设计,但核心区别如下:
特性 | 接口(Interface) | 抽象类(Abstract Class) |
---|---|---|
继承方式 | 多实现( | 单继承( |
方法 | 默认为 | 可包含抽象方法和具体方法 |
属性 | 只能是 | 可包含任意修饰符的属性(普通变量) |
构造器 | 无 | 有(供子类调用) |
设计目的 | 定义行为规范(“是什么”) | 代码复用(“是什么+怎么做”) |
示例:
// 接口:定义规范
interface Flyable {
int MAX_HEIGHT = 1000; // 隐式为public static final
void fly(); // 隐式为public abstract
// Java 8+默认方法
default void land() {
System.out.println("降落");
}
}
// 抽象类:包含部分实现
abstract class Bird {
String name;
Bird(String name) { this.name = name; } // 构造器
abstract void sing(); // 抽象方法
// 具体方法
void eat() {
System.out.println(name + "吃东西");
}
}
// 类可实现多个接口,继承一个抽象类
class Eagle extends Bird implements Flyable {
Eagle(String name) { super(name); }
@Override
public void fly() {
System.out.println(name + "在" + MAX_HEIGHT + "米高空飞");
}
@Override
void sing() {
System.out.println(name + "鸣叫");
}
}
final
关键字的用法?final
可修饰类、方法、变量,含义不同:
String
类)。<!---->
示例:
// final类:不可被继承
final class FinalClass { }
// class SubClass extends FinalClass {} // 编译错误:无法继承final类
class Parent {
// final方法:不可被重写
public final void finalMethod() {
System.out.println("父类final方法");
}
}
class Child extends Parent {
// @Override // 编译错误:无法重写final方法
// public void finalMethod() {}
}
public class FinalDemo {
public static void main(String[] args) {
// final基本类型变量:值不可变
final int a = 10;
// a = 20; // 编译错误
// final引用类型变量:地址不可变,内容可改
final StringBuilder sb = new StringBuilder("hello");
sb.append(" world"); // 合法:修改对象内容
// sb = new StringBuilder("new"); // 编译错误:修改引用地址
}
}
static
关键字的作用?static
用于修饰类的成员(变量、方法、代码块),表示属于类而非实例:
类名.变量名
访问,内存中仅一份。类名.方法名
调用,不能访问非静态成员(无this
引用)。示例:
public class StaticDemo {
// 静态变量:所有实例共享
static int count;
// 非静态变量:每个实例独立
int id;
// 静态代码块:类加载时执行
static {
count = 0;
System.out.println("静态代码块执行");
}
public StaticDemo() {
id = ++count; // 每次创建实例,id递增
}
// 静态方法:可直接通过类名调用
static void showCount() {
System.out.println("总实例数:" + count);
// System.out.println(id); // 编译错误:静态方法不能访问非静态变量
}
// 静态内部类
static class StaticInner {
void print() {
System.out.println("静态内部类");
// System.out.println(id); // 编译错误:不能访问外部类非静态成员
}
}
public static void main(String[] args) {
StaticDemo.showCount(); // 直接调用静态方法:总实例数:0
new StaticDemo();
new StaticDemo();
StaticDemo.showCount(); // 总实例数:2
// 静态内部类使用
StaticInner inner = new StaticInner();
inner.print();
}
}
示例:
class OverloadDemo {
// 重载:同一类中方法名相同,参数不同
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) { // 参数类型不同
return a + b;
}
public int add(int a, int b, int c) { // 参数个数不同
return a + b + c;
}
}
class Parent {
// 父类方法
public void doSomething() {
System.out.println("父类方法");
}
protected String getInfo() {
return "父类信息";
}
}
class Child extends Parent {
// 重写:子类方法与父类方法签名一致
@Override
public void doSomething() {
System.out.println("子类方法");
}
// 重写:返回值是父类返回值的子类(String的子类仍是String)
@Override
public String getInfo() { // 访问权限不能严于父类(protected→public合法)
return "子类信息";
}
}
try-catch-finally
的执行顺序?Java异常体系以Throwable
为根类,分为两类:
Error
:严重错误(如OutOfMemoryError
),程序无法处理,通常由JVM抛出。Exception
:可处理的异常,分两类:<!---->
IOException
),需try-catch
或throws
声明。NullPointerException
),编译时无需处理。try-catch-finally
执行顺序:
try
:执行可能抛出异常的代码。catch
块处理,再执行finally
。finally
。finally
:无论是否发生异常,只要JVM未退出就一定会执行(常用于释放资源)。示例:
import java.io.FileNotFoundException;
import java.io.FileReader;
public class ExceptionDemo {
public static void main(String[] args) {
FileReader reader = null;
try {
// 可能抛出受检异常FileNotFoundException
reader = new FileReader("test.txt");
System.out.println("文件打开成功"); // 若文件不存在,此行不执行
} catch (FileNotFoundException e) { // 处理受检异常
System.out.println("异常:文件未找到");
} finally { // 无论是否异常,均执行
if (reader != null) {
try {
reader.close(); // 释放资源
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println("finally执行");
}
// 非受检异常(运行时异常)
String s = null;
try {
System.out.println(s.length()); // 抛出NullPointerException
} catch (NullPointerException e) {
System.out.println("处理空指针异常");
}
}
}
反射是Java的动态特性,允许程序在运行时获取类的信息(属性、方法、构造器等),并动态操作类或对象(如创建实例、调用方法)。核心类位于java.lang.reflect
包(Class
、Method
、Field
等)。
应用场景:
@Test
注解,通过反射识别测试方法);示例:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
class User {
private String name;
public User() {}
public User(String name) { this.name = name; }
private void sayHello() {
System.out.println("Hello, " + name);
}
}
public class ReflectionDemo {
public static void main(String[] args) throws Exception {
// 1. 获取Class对象(三种方式)
Class<?> clazz = User.class;
// Class<?> clazz = new User().getClass();
// Class<?> clazz = Class.forName("User");
// 2. 反射创建对象(调用无参构造)
User user = (User) clazz.newInstance();
// 3. 反射调用有参构造
Constructor<?> constructor = clazz.getConstructor(String.class);
User user2 = (User) constructor.newInstance("张三");
// 4. 反射调用私有方法
Method method = clazz.getDeclaredMethod("sayHello");
method.setAccessible(true); // 突破私有访问限制
method.invoke(user2); // 输出"Hello, 张三"
}
}
泛型(Generic)是JDK 5引入的特性,允许在定义类、接口、方法时指定类型参数(如List<String>
),编译时检查类型安全性,避免运行时类型转换异常。
作用:
ClassCastException
。泛型擦除:编译后泛型信息被擦除(替换为Object
或上限类型),是“伪泛型”。
示例:
import java.util.ArrayList;
import java.util.List;
// 自定义泛型类
class Box<T> {
private T value;
public T getValue() { return value; }
public void setValue(T value) { this.value = value; }
}
// 泛型方法
class GenericMethod {
public static <E> void printArray(E[] array) {
for (E e : array) {
System.out.print(e + " ");
}
System.out.println();
}
}
public class GenericDemo {
public static void main(String[] args) {
// 泛型集合:编译时检查类型
List<String> strList = new ArrayList<>();
strList.add("java");
// strList.add(123); // 编译错误:类型不匹配
String s = strList.get(0); // 无需强制转换
// 自定义泛型类使用
Box<Integer> intBox = new Box<>();
intBox.setValue(100);
int num = intBox.getValue(); // 自动转型
// 泛型方法使用
Integer[] intArray = {1, 2, 3};
String[] strArray = {"a", "b", "c"};
GenericMethod.printArray(intArray); // 1 2 3
GenericMethod.printArray(strArray); // a b c
}
}
Java集合框架主要分为Collection
(单值集合)和Map
(键值对集合),List
和Set
属于Collection
:
特性 | List | Set | Map |
---|---|---|---|
存储结构 | 有序集合(插入顺序) | 无序集合(无索引) | 键值对映射(key→value) |
元素重复性 | 允许重复元素 | 不允许重复元素(依赖 | key不重复,value可重复 |
常用实现类 |
|
|
|
索引访问 | 支持( | 不支持 | 不支持(通过key访问value) |
示例:
import java.util.*;
public class CollectionDemo {
public static void main(String[] args) {
// List:有序、可重复
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("apple"); // 允许重复
System.out.println(list); // [apple, banana, apple](保持插入顺序)
System.out.println(list.get(0)); // apple(通过索引访问)
// Set:无序、不可重复
Set<String> set = new HashSet<>();
set.add("apple");
set.add("banana");
set.add("apple"); // 重复元素不被添加
System.out.println(set); // [banana, apple](无序)
// Map:键值对,key唯一
Map<String, Integer> map = new HashMap<>();
map.put("apple", 10);
map.put("banana", 20);
map.put("apple", 15); // 覆盖原key的值
System.out.println(map.get("apple")); // 15(通过key获取value)
System.out.println(map.keySet()); // [banana, apple](key集合)
}
}
两者均实现Map
接口,用于存储键值对,核心区别:
特性 | HashMap | Hashtable |
---|---|---|
线程安全 | 非线程安全(效率高) | 线程安全(方法加 |
null值支持 | 允许 | 不允许 |
继承关系 | 继承 | 继承 |
迭代器 | 快速失败迭代器( | 枚举器( |
初始容量 | 初始容量16,扩容因子0.75 | 初始容量11,扩容因子0.75 |
示例:
import java.util.HashMap;
import java.util.Hashtable;
public class MapDifference {
public static void main(String[] args) {
// HashMap:允许null key和value
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put(null, "null key");
hashMap.put("key1", null);
System.out.println(hashMap.get(null)); // null key
System.out.println(hashMap.get("key1")); // null
// Hashtable:不允许null
Hashtable<String, String> hashtable = new Hashtable<>();
try {
hashtable.put(null, "value"); // 抛NullPointerException
hashtable.put("key", null); // 抛NullPointerException
} catch (NullPointerException e) {
System.out.println("Hashtable不允许null");
}
}
}
Java中实现多线程的3种核心方式:
Thread
类:重写run()
方法,调用start()
启动线程(start()
会调用run()
,并创建新线程)。Runnable
接口:重写run()
方法,将实例传入Thread
构造器,调用start()
启动。Callable
接口:重写call()
方法(有返回值,可抛异常),结合FutureTask
获取结果。推荐使用接口实现(避免单继承限制,便于多线程共享资源)。
示例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
// 方式1:继承Thread
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread方式:" + Thread.currentThread().getName());
}
}
// 方式2:实现Runnable
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable方式:" + Thread.currentThread().getName());
}
}
// 方式3:实现Callable(有返回值)
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable方式:" + Thread.currentThread().getName();
}
}
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 方式1启动
new MyThread().start();
// 方式2启动
Thread thread2 = new Thread(new MyRunnable());
thread2.start();
// 方式3启动
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
new Thread(futureTask).start();
System.out.println(futureTask.get()); // 获取返回值
}
}
sleep()
和wait()
的区别?两者均用于线程等待,核心区别:
特性 |
|
|
---|---|---|
所属类 |
|
|
锁释放 | 不释放锁(持有锁休眠) | 释放锁(需在 |
唤醒方式 | 时间到自动唤醒,或被 | 需 |
使用场景 | 暂停线程执行一段时间 | 线程间通信(等待/唤醒机制) |
示例:
public class SleepWaitDemo {
public static void main(String[] args) {
Object lock = new Object();
// 演示sleep:不释放锁
new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获得锁");
try {
Thread.sleep(2000); // 休眠2秒,不释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1释放锁");
}
}).start();
// 演示wait:释放锁
new Thread(() -> {
synchronized (lock) {
System.out.println("线程2获得锁");
try {
lock.wait(2000); // 释放锁,等待2秒或被唤醒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2继续执行");
}
}).start();
}
}
执行结果:
线程1获得锁
(2秒后)
线程1释放锁
线程2获得锁
(2秒后或被唤醒)
线程2继续执行
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。