Java基础面试题
什么是Java?
Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。
Java语言作为静态面向对象编程语言的优秀代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。
Java语言有哪些特点?
- 面向对象(封装,继承,多态);
- 平台无关性:Java 是“一次编写,到处运行(Write Once,Run any Where)”的语言,通过Java 的虚拟机机制 Java 语言编写的程序具有了很好的可移植性;
- 支持多线程;
- 编译与解释并存;
什么是字节码?采用字节码的好处是什么?
字节码,就是Java程序经过编译产生的.class文件,字节码能够被虚拟机识别,从而实现Java程序的跨平台性。
Java 程序从源代码到运行主要有三步:
- 编译:将代码(.java)编译成虚拟机可以识别理解的字节码(.class)
- 解释:虚拟机执行字节码(.class),将字节码翻译成机器能识别的机器码
- 执行:对应的机器执行二进制机器码
把Java程序编译成Java字节码,不同的平台安装对应的Java虚拟机,这样就可以可以实现Java语言的平台无关性。
怎么理解 Java“编译与解释并存”?
编译型语言: 是指编译器针对特定的操作系统将源代码一次性翻译成可被该平台执行的机器码;
解释型语言: 是指解释器对源程序逐行解释成特定平台的机器码并立即执行。
Java 语言既具有编译型语言的特征,也具有解释型语言的特征。
因为 Java 程序要经过先编译,后解释两个步骤,由 Java 编写的程序需要先经过编译步骤,生成字节码(*.class 文件)。
这种字节码必须再经过JVM,解释成操作系统能识别的机器码,在由操作系统执行。因此,我们可以认为 Java 语言编译与解释并存。
Java有哪些数据类型?
Java的数据类型严格来说分为两大类:基本数据类型(Primitive Type) 和引用数据类型(Reference Type)。
基本数据类型(8种)
它们直接存储值,不涉及对象创建,存储在栈(Stack)中,效率高。
| 类型 | 关键字 | 占用字节 | 默认值 | 取值范围/说明 |
|---|---|---|---|---|
| 整型 | byte |
1 | 0 | -128 ~ 127 |
short |
2 | 0 | -32768 ~ 32767 | |
int (默认) |
4 | 0 | 约 -21亿 ~ 21亿 | |
long |
8 | 0L | 极大,赋值时末尾需加 L (如 100L) |
|
| 浮点型 | float |
4 | 0.0f | 单精度,赋值时末尾需加 f (如 3.14f) |
double (默认) |
8 | 0.0d | 双精度,精度更高,小数默认为此类型 | |
| 字符型 | char |
2 | '\u0000' | 单引号包裹,支持Unicode(如 'A', '中') |
| 布尔型 | boolean |
1(概念上) | false | 只有 true 和 false,不能与整型互转 |
引用数据类型(无数种)
它们存储的是对象的引用(内存地址),实际对象存储在堆(Heap)中。默认值均为 null。
- 类(Class):包括
String、Integer等包装类,以及自定义的类。 - 接口(Interface):如
List、Runnable。 - 数组(Array):如
int[]、String[]。 - 枚举(Enum):如
enum Color {RED, GREEN}。 - 注解(Annotation):如
@Override。
补充:void
严格来说,void 不是数据类型,它代表“无返回值”,只能用于方法声明。
⚠️特别提醒(包装类)
Java为每个基本类型都提供了对应的包装类(Wrapper Class),用于在集合(如 ArrayList)中存储:
int→Integerboolean→Booleanchar→Character- 其他(如
double→Double)首字母大写即可。
从 Java 5 开始,它们之间支持自动装箱(int 转 Integer)和自动拆箱,但要注意 == 比较在包装类中,范围在 -128~127 时缓存生效(相等),超过范围则创建新对象(不相等)。
例如 new Integer(200) == 200 为 true(拆箱比较值):只要数值一样,因为拆箱的原因,包装类==基本类肯定就是true。
但 new Integer(200) == new Integer(200) 为 false(比较引用地址):因为比较的值大于127,超过了缓存的值,就是比较地址,那么就是false,当小于127时,从缓存池里使用同一个缓存值对象进行比较,那就是true。
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型;
switch 是否能作用在 byte/long/String上?
Java5 以前 switch(expr)中,expr 只能是 byte、short、char、int。
从 Java 5 开始,Java 中引入了枚举类型, expr 也可以是 enum 类型。
从 Java 7 开始,expr还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
面向对象有哪些特性
面向对象编程的三大特性:封装、继承、多态
封装
封装把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的⽅法。
继承
继承是使⽤已存在的类的定义作为基础创建新的类,新类的定义可以增加新的属性或新的方法,也可以继承父类的属性和方法。通过继承可以很方便地进行代码复用。
关于继承有以下三个要点:
- ⼦类拥有⽗类对象所有的属性和⽅法(包括私有属性和私有⽅法),但是⽗类中的私有属性和⽅法⼦类是⽆法访问,只是拥有。
- ⼦类可以拥有⾃⼰属性和⽅法,即⼦类可以对⽗类进⾏扩展。
- ⼦类可以⽤⾃⼰的⽅式实现⽗类的⽅法。
多态
多态(Polymorphism) 的核心定义可以用一句话概括:
父类引用(或接口引用)指向子类对象,调用同一个方法时,表现出多种不同的行为形态。
在 Java 中,多态主要分为编译时多态(重载) 和运行时多态(重写)。面试和源码分析中,绝大多数情况指的是运行时多态。
一、运行时多态的三个必要条件
不是随便写写就叫多态,必须同时满足以下三点:
- 继承(或实现):子类继承父类,或实现类实现接口。
- 方法重写(Override):子类重写了父类的非
private、非static方法。 - 父类引用指向子类对象:
Father f = new Son();
二、核心口诀:编译看左边,运行看右边(重中之重)
这是 Java 多态最核心的底层绑定规则:
- 编译阶段(看左边):编译器只检查引用类型(左边)里有没有这个方法。如果父类没有,编译器直接报错。
- 运行阶段(看右边):程序运行时,JVM 会去查找实际对象类型(右边)里有没有重写该方法。如果重写了,调用子类的;否则调用父类的。
看代码实战(经典动物示例):
class Animal {
String name = "动物";
public void eat() {
System.out.println("动物吃饭");
}
public static void sleep() {
System.out.println("动物睡觉");
}
}
class Dog extends Animal {
String name = "小狗";
@Override
public void eat() {
System.out.println("狗啃骨头");
}
public static void sleep() {
System.out.println("狗趴着睡");
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Dog(); // 多态:父类引用指向子类对象
// 1. 调用普通重写方法 -> 运行看右边(调用 Dog 的 eat)
a.eat(); // 输出:狗啃骨头 ✅ 这就是多态
// 2. 访问成员变量(属性)-> 编译看左边(变量不参与多态)
System.out.println(a.name); // 输出:动物(不是"小狗"!)
// 3. 调用静态方法 -> 编译看左边(静态方法属于类,不参与多态)
a.sleep(); // 输出:动物睡觉(不是"狗趴着睡"!)
}
}
三、⚠️多态中的向上转型与向下转型
-
向上转型(隐式,自动):
Animal a = new Dog();
这是多态的写法,子类自动转为父类。缺点是丢失子类特有方法(比如Dog独有的watchDoor(),用a无法调用)。 -
向下转型(显式,强制):
Dog d = (Dog) a;
把父类引用转回子类引用,为了调用子类特有方法。
注意:向下转型有风险,如果a实际指向的不是Dog(比如Cat),强制转成Dog会抛出ClassCastException(类型转换异常)。
所以必须先用instanceof做判断:
if (a instanceof Dog) {
Dog d = (Dog) a;
d.watchDoor(); // 安全调用
}
四、三大“不参与多态”的坑
| 成员类型 | 是否参与多态 | 调用规则 |
|---|---|---|
| 普通非静态方法 | ✅ 参与 | 运行看右边(子类重写则调子类) |
| 成员变量(属性) | ❌ 不参与 | 编译看左边(永远调父类的属性,子类同名属性只是隐藏);可以通过普通非静态方法去获取属性 |
| 静态方法(static) | ❌ 不参与 | 编译看左边(静态方法属于类,不属于对象) |
| 私有方法(private) | ❌ 不参与 | 子类无法重写,自动调父类 |
五、为什么需要多态?
- 解耦:方法参数写成父类(如
void feed(Animal a)),传入Dog、Cat都行,不需要写多个重载方法。 - 开闭原则:对扩展开放(新增子类),对修改关闭(不需要改
feed方法的代码)。 - 代码复用:大家熟知的
List list = new ArrayList();就是多态,我们面向接口编程,底层换LinkedList时,其他代码完全不用动。
访问修饰符public、private、protected、以及不写(默认)时的区别?
Java 的访问修饰符从“最开放”到“最封闭”依次是:public > protected > 默认(不写) > private。
| 修饰符 | 本类内部 | 同包其他类 | 不同包的子类(继承) | 不同包的非子类(任意位置) | 总结关键词 |
|---|---|---|---|---|---|
private |
✅ | ❌ | ❌ | ❌ | “独享”(仅类内可见) |
| 默认(不写) | ✅ | ✅ | ❌ | ❌ | “同包共享”(包私有) |
protected |
✅ | ✅ | ✅ | ❌ | “继承特权”(同包+子类) |
public |
✅ | ✅ | ✅ | ✅ | “全球公开”(任何地方) |
实际开发中的选择经验
- 属性(字段):99% 用
private,配合public或protected的getter/setter。 - 工具类内部辅助方法:用
private(不希望外部知道逻辑细节)。 - 同一个包内协作的方法:用 默认(不写)。
- 供子类重写但不需要对外暴露的方法:用
protected。 - 对外的核心接口方法:用
public。