面试专题:语法基础
大约 12 分钟
面试专题:语法基础
核心理论
1.1 Java语法体系概览
Java语法体系可分为基础语法、面向对象语法和高级特性三个层次,是Java编程的基石。
1.2 语法基础核心考点
| 类别 | 高频考点 | 重要程度 | 
|---|---|---|
| 关键字 | static, final, this, super, volatile, transient | ★★★★★ | 
| 数据类型 | 基本类型与包装类区别、自动装箱/拆箱、String不可变性 | ★★★★★ | 
| 控制流程 | 分支结构、循环效率对比、break与continue | ★★★☆☆ | 
| 面向对象 | 构造方法、重写与重载、多态实现原理 | ★★★★★ | 
| 高级特性 | 泛型擦除、注解原理、函数式接口 | ★★★★☆ | 
代码实践
2.1 关键字应用场景
2.1.1 static关键字用法
/**
 * static关键字使用示例
 * 包含静态变量、静态方法、静态代码块和静态内部类
 */
public class StaticDemo {
    // 静态变量:属于类,所有实例共享
    public static int staticVar = 0;
    // 实例变量:属于对象,每个实例独立
    public int instanceVar = 0;
    // 静态代码块:类加载时执行,仅执行一次
    static {
        System.out.println("静态代码块执行");
        staticVar = 10;
    }
    // 构造方法
    public StaticDemo() {
        instanceVar++;
        staticVar++;
    }
    // 静态方法:只能访问静态成员,无this引用
    public static void staticMethod() {
        System.out.println("静态变量值: " + staticVar);
        // System.out.println(instanceVar); // 编译错误:不能访问实例变量
    }
    // 实例方法:可以访问静态和实例成员
    public void instanceMethod() {
        System.out.println("实例变量值: " + instanceVar);
        System.out.println("静态变量值: " + staticVar);
    }
    // 静态内部类:不能访问外部类的实例成员
    public static class StaticNestedClass {
        public void nestedMethod() {
            System.out.println("静态内部类访问静态变量: " + staticVar);
        }
    }
    public static void main(String[] args) {
        // 直接通过类名访问静态成员
        StaticDemo.staticMethod();
        System.out.println("通过类名访问静态变量: " + StaticDemo.staticVar);
        // 创建实例
        StaticDemo instance1 = new StaticDemo();
        StaticDemo instance2 = new StaticDemo();
        // 实例变量各自独立
        System.out.println("instance1实例变量: " + instance1.instanceVar); // 1
        System.out.println("instance2实例变量: " + instance2.instanceVar); // 1
        // 静态变量共享
        System.out.println("instance1静态变量: " + instance1.staticVar); // 12
        System.out.println("instance2静态变量: " + instance2.staticVar); // 12
        // 静态内部类使用
        StaticNestedClass nested = new StaticNestedClass();
        nested.nestedMethod();
    }
}2.1.2 final关键字用法
/**
 * final关键字使用示例
 * 可修饰类、方法、变量
 */
public class FinalDemo {
    // final变量:必须初始化,初始化后不可修改
    public static final int CONSTANT = 100; // 常量,通常全大写
    private final String name;
    // final参数:方法内不可修改
    public void finalParameter(final int param) {
        // param = 20; // 编译错误:final参数不可修改
        System.out.println("final参数: " + param);
    }
    // 构造方法:必须初始化final实例变量
    public FinalDemo(String name) {
        this.name = name; // 正确:在构造方法中初始化final变量
    }
    // final方法:不可被子类重写
    public final void finalMethod() {
        System.out.println("这是final方法,不可重写");
    }
    // final变量的getter
    public String getName() {
        return name;
    }
}
// final类:不可被继承
final class FinalClass {
    // 类中的成员可以是各种类型
}
// 编译错误:Cannot inherit from final 'FinalClass'
// class SubClass extends FinalClass {}2.2 数据类型与类型转换
2.2.1 基本类型与包装类
/**
 * 基本类型与包装类对比示例
 * 包括自动装箱/拆箱和缓存机制
 */
public class WrapperClassDemo {
    public static void main(String[] args) {
        // 基本类型
        int primitiveInt = 10;
        boolean primitiveBoolean = true;
        // 包装类
        Integer wrapperInt = Integer.valueOf(10);
        Boolean wrapperBoolean = Boolean.TRUE;
        // 自动装箱:基本类型 -> 包装类
        Integer autoBoxing = primitiveInt;
        // 自动拆箱:包装类 -> 基本类型
        int autoUnboxing = wrapperInt;
        // 包装类缓存机制
        Integer a = 127;
        Integer b = 127;
        Integer c = 128;
        Integer d = 128;
        System.out.println(a == b); // true:缓存范围内
        System.out.println(c == d); // false:超出缓存范围
        System.out.println(a.equals(b)); // true:equals比较值
        System.out.println(c.equals(d)); // true:equals比较值
        // 常见问题:NullPointerException
        Integer nullInteger = null;
        try {
            int value = nullInteger; // 自动拆箱时null会抛出NPE
        } catch (NullPointerException e) {
            System.out.println("空包装类拆箱抛出NPE");
        }
        // 字符串转基本类型
        int num = Integer.parseInt("123");
        double decimal = Double.parseDouble("3.14");
        // 基本类型转字符串
        String intStr = String.valueOf(123);
        String boolStr = Boolean.toString(true);
    }
}2.2.2 String特性与操作
/**
 * String类特性与常用操作示例
 * 不可变性、常量池、字符串操作
 */
public class StringDemo {
    public static void main(String[] args) {
        // String不可变性演示
        String str1 = "hello";
        String str2 = str1;
        str1 = str1 + " world";
        System.out.println(str1); // hello world
        System.out.println(str2); // hello(str2仍指向原对象)
        // 字符串常量池
        String s1 = "java";
        String s2 = "java";
        String s3 = new String("java");
        String s4 = new String("java").intern();
        System.out.println(s1 == s2); // true:同一常量池对象
        System.out.println(s1 == s3); // false:s3是堆中对象
        System.out.println(s1 == s4); // true:intern()返回常量池对象
        // 常用字符串操作
        String text = "  Java Programming  ";
        System.out.println(text.trim()); // 去除首尾空格:"Java Programming"
        System.out.println(text.toLowerCase()); // 转小写:"  java programming  "
        System.out.println(text.indexOf("Pro")); // 查找子串位置:6
        System.out.println(text.substring(2, 6)); // 截取子串:"Java"
        System.out.println(text.replace("Programming", "Coding")); // 替换:"  Java Coding  "
        // 字符串拼接性能对比
        long start = System.currentTimeMillis();
        String normalConcat = "";
        for (int i = 0; i < 10000; i++) {
            normalConcat += i; // 性能差,每次创建新对象
        }
        System.out.println("普通拼接耗时: " + (System.currentTimeMillis() - start) + "ms");
        start = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            sb.append(i); // 性能好,可修改
        }
        String builderConcat = sb.toString();
        System.out.println("StringBuilder拼接耗时: " + (System.currentTimeMillis() - start) + "ms");
        // 线程安全的StringBuffer
        StringBuffer sbf = new StringBuffer();
        sbf.append("thread").append("safe");
    }
}2.3 控制流程与异常处理
2.3.1 循环结构效率对比
/**
 * 不同循环结构的性能对比
 * for、foreach、while循环适用场景
 */
public class LoopPerformanceDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 100000; i++) {
            list.add("element" + i);
        }
        // for循环
        long start = System.currentTimeMillis();
        for (int i = 0; i < list.size(); i++) {
            String element = list.get(i);
        }
        System.out.println("普通for循环耗时: " + (System.currentTimeMillis() - start) + "ms");
        // 优化for循环(缓存size)
        start = System.currentTimeMillis();
        int size = list.size();
        for (int i = 0; i < size; i++) {
            String element = list.get(i);
        }
        System.out.println("优化for循环耗时: " + (System.currentTimeMillis() - start) + "ms");
        // foreach循环
        start = System.currentTimeMillis();
        for (String element : list) {
            // do nothing
        }
        System.out.println("foreach循环耗时: " + (System.currentTimeMillis() - start) + "ms");
        // while循环
        start = System.currentTimeMillis();
        int index = 0;
        while (index < list.size()) {
            String element = list.get(index);
            index++;
        }
        System.out.println("while循环耗时: " + (System.currentTimeMillis() - start) + "ms");
    }
}2.3.2 异常处理最佳实践
/**
 * 异常处理最佳实践示例
 * 包括try-catch-finally、try-with-resources和自定义异常
 */
public class ExceptionHandlingDemo {
    // 自定义异常
    static class BusinessException extends Exception {
        private int errorCode;
        public BusinessException(String message, int errorCode) {
            super(message);
            this.errorCode = errorCode;
        }
        public int getErrorCode() {
            return errorCode;
        }
    }
    // 资源关闭传统方式
    public static void traditionalResourceHandling() {
        FileInputStream fis = null;
        try {
            fis = new FileInputStream("file.txt");
            // 读取文件操作
        } catch (FileNotFoundException e) {
            System.err.println("文件未找到: " + e.getMessage());
        } finally {
            // 确保资源关闭
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    System.err.println("关闭流失败: " + e.getMessage());
                }
            }
        }
    }
    // JDK 7+ try-with-resources自动关闭资源
    public static void tryWithResources() {
        try (FileInputStream fis = new FileInputStream("file.txt")) {
            // 读取文件操作
        } catch (FileNotFoundException e) {
            System.err.println("文件未找到: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("IO异常: " + e.getMessage());
        } // 资源自动关闭
    }
    // 异常处理最佳实践
    public static void processOrder(int orderId) throws BusinessException {
        if (orderId <= 0) {
            // 抛出具体业务异常
            throw new BusinessException("订单ID必须为正数", 400);
        }
        try {
            // 业务逻辑处理
            System.out.println("处理订单: " + orderId);
            // 可能抛出异常的操作
        } catch (NullPointerException e) {
            // 捕获具体异常而非通用Exception
            throw new BusinessException("订单数据为空", 500);
        } finally {
            // 释放资源或记录日志等收尾工作
            System.out.println("订单处理结束,orderId: " + orderId);
        }
    }
    public static void main(String[] args) {
        try {
            processOrder(123);
            processOrder(-1);
        } catch (BusinessException e) {
            System.err.println("业务异常: " + e.getMessage() + ", 错误码: " + e.getErrorCode());
        }
        traditionalResourceHandling();
        tryWithResources();
    }
}设计思想
3.1 Java语法设计哲学
Java语法设计遵循"简单、面向对象、分布式、健壮、安全、平台无关、可移植、高性能、多线程、动态"的原则,其中语法层面最核心的设计思想包括:
- 简单性:相比C++移除了指针、多重继承等复杂特性
- 面向对象:一切皆对象(除基本类型外),单继承多实现
- 安全性:强类型检查、异常处理、内存自动管理
- 平台无关:一次编写,到处运行(WORA)
3.2 语法特性背后的设计考量
3.2.1 String不可变性设计
String类被设计为不可变的主要原因:
- 安全性:字符串常被用作参数,不可变性保证参数不被修改
- 缓存优化:可缓存hashCode,提高HashMap等容器的性能
- 线程安全:不可变对象天然线程安全
- 常量池优化:字符串常量池可以复用相同内容的字符串
3.2.2 自动装箱与拆箱的权衡
自动装箱/拆箱是Java 5引入的语法糖,其设计权衡:
- 优点:简化代码,消除基本类型与包装类之间的转换代码
- 缺点:可能导致性能问题和NullPointerException
- 优化:对常用值(如-128~127的整数)实现缓存机制
避坑指南
4.1 关键字使用误区
4.1.1 static关键字常见错误
/**
 * static关键字使用误区示例
 */
public class StaticPitfalls {
    // 误区1:静态方法访问非静态成员
    public static void staticMethod() {
        // System.out.println(nonStaticVar); // 编译错误
        // nonStaticMethod(); // 编译错误
    }
    private int nonStaticVar;
    private void nonStaticMethod() {}
    // 误区2:构造方法误用static
    // public static StaticPitfalls() {} // 编译错误
    // 误区3:static导入导致命名冲突
    // import static java.lang.Math.PI;
    // import static java.awt.geom.Arc2D.PI;
    // 此时使用PI会导致编译错误
    // 误区4:静态内部类访问外部类实例成员
    public static class StaticNestedClass {
        public void accessOuter() {
            // System.out.println(nonStaticVar); // 编译错误
        }
    }
}4.1.2 final关键字使用陷阱
/**
 * final关键字使用陷阱示例
 */
public class FinalPitfalls {
    // 陷阱1:final引用的对象内容可修改
    private final List<String> list = new ArrayList<>();
    public void addElement() {
        list.add("element"); // 允许:final只保证引用不变
        // list = new ArrayList<>(); // 不允许:修改引用
    }
    // 陷阱2:final数组的元素可修改
    private final int[] array = {1, 2, 3};
    public void modifyArray() {
        array[0] = 100; // 允许:数组引用不变,内容可变
        // array = new int[5]; // 不允许:修改引用
    }
    // 陷阱3:final方法内的局部变量可修改
    public final void finalMethod() {
        int localVar = 10;
        localVar = 20; // 允许:方法final不影响局部变量
    }
    // 陷阱4:错误的初始化时机
    private final int lateInitVar;
    // public FinalPitfalls() {}
    // 编译错误:final变量lateInitVar未初始化
    // 正确做法:在构造方法中初始化
    public FinalPitfalls() {
        lateInitVar = 100;
    }
}4.2 类型转换与字符串操作问题
4.2.1 类型转换异常处理
/**
 * 类型转换常见问题及解决方案
 */
public class TypeConversionPitfalls {
    public static void main(String[] args) {
        // 问题1:字符串转数字时的格式错误
        try {
            int num = Integer.parseInt("abc");
        } catch (NumberFormatException e) {
            System.out.println("字符串格式错误,无法转为数字");
        }
        // 问题2:向下转型不安全
        Object obj = "string";
        try {
            Integer num = (Integer) obj; // 运行时异常
        } catch (ClassCastException e) {
            System.out.println("类型转换异常: " + e.getMessage());
        }
        // 解决方案:使用instanceof检查
        if (obj instanceof Integer) {
            Integer num = (Integer) obj;
        } else {
            System.out.println("对象不是Integer类型");
        }
        // 问题3:浮点精度丢失
        double result = 0.1 + 0.2;
        System.out.println(result); // 0.30000000000000004而非0.3
        // 解决方案:使用BigDecimal
        BigDecimal bd1 = new BigDecimal("0.1");
        BigDecimal bd2 = new BigDecimal("0.2");
        BigDecimal sum = bd1.add(bd2);
        System.out.println(sum); // 0.3
    }
}4.2.2 String操作性能问题
/**
 * String操作性能问题及优化
 */
public class StringPerformancePitfalls {
    // 问题1:循环中字符串拼接
    public String loopConcat(int count) {
        String result = "";
        for (int i = 0; i < count; i++) {
            result += i; // 每次循环创建新对象,O(n²)复杂度
        }
        return result;
    }
    // 优化方案:使用StringBuilder
    public String optimizedConcat(int count) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < count; i++) {
            sb.append(i); // O(n)复杂度
        }
        return sb.toString();
    }
    // 问题2:不必要的字符串创建
    public void unnecessaryStringCreation() {
        String str1 = new String("hello"); // 不推荐:创建了两个对象(堆和常量池)
        String str2 = "hello"; // 推荐:只使用常量池对象
        // 问题3:频繁调用String.substring()
        String largeString = "abcdefghijklmnopqrstuvwxyz";        
        for (int i = 0; i < 1000; i++) {
            String substr = largeString.substring(0, 5); // JDK 6及之前会导致内存泄漏
        }
    }
    public static void main(String[] args) {
        StringPerformancePitfalls demo = new StringPerformancePitfalls();
        int count = 10000;
        long start = System.currentTimeMillis();
        demo.loopConcat(count);
        System.out.println("循环拼接耗时: " + (System.currentTimeMillis() - start) + "ms");
        start = System.currentTimeMillis();
        demo.optimizedConcat(count);
        System.out.println("StringBuilder拼接耗时: " + (System.currentTimeMillis() - start) + "ms");
    }
}深度思考题
思考题1:Java中的值传递与引用传递区别
思考题回答:Java中只有值传递,没有引用传递。当参数是基本类型时,传递的是值的副本;当参数是对象时,传递的是对象引用的副本,而非对象本身。
示例证明:
public class PassByValueDemo {
    // 基本类型参数
    public static void modifyPrimitive(int num) {
        num = 100;
    }
    // 对象类型参数
    public static void modifyObject(StringBuilder sb) {
        sb.append(" world"); // 修改对象内容
        sb = new StringBuilder("new"); // 修改引用副本,不影响原引用
    }
    public static void main(String[] args) {
        int x = 10;
        modifyPrimitive(x);
        System.out.println(x); // 输出10,未被修改
        StringBuilder sb = new StringBuilder("hello");
        modifyObject(sb);
        System.out.println(sb); // 输出"hello world",对象内容被修改
    }
}结论:Java始终采用值传递。对于对象,传递的是引用的值,这使得我们可以修改对象的内容,但无法改变原引用的指向。
思考题2:JDK 8到JDK 21的语法特性演进
思考题回答:JDK 8至JDK 21的重要语法特性演进:
- JDK 8 (2014) - Lambda表达式:引入函数式编程能力
- 方法引用:简化Lambda表达式
- Stream API:支持集合的函数式操作
- 默认方法:接口可以有默认实现
 
- JDK 9 (2017) - 接口私有方法:接口中可定义私有辅助方法
- try-with-resources增强:支持final变量
 
- JDK 10 (2018) - var关键字:局部变量类型推断
- 不可变集合工厂方法:List.of(), Set.of(), Map.of()
 
- JDK 11 (2018) - String新增方法:isBlank(), lines(), strip(), repeat()
- Lambda参数类型推断增强
 
- JDK 12 (2019) - switch表达式预览:支持返回值
- 字符串缩进方法indent()
 
- JDK 13 (2019) - switch表达式增强:使用yield返回值
- 文本块预览:使用
 
