每日 Java 面试题分享【第 4 天】

欢迎来到每日 Java 面试题分享栏目!
订阅专栏,不错过每一天的练习

今日分享 3 道面试题目!

评论区复述一遍印象更深刻噢~

目录

  • 问题一:什么是 Java 内部类?它有什么作用?
  • 问题二:JDK8 有哪些新特性?
  • 问题三:Java 中 String、StringBuffer 和 StringBuilder 的区别是什么?

问题一:什么是 Java 内部类?它有什么作用?

满分回答

在 Java 中,内部类(Inner Class)是定义在另一个类内部的类。它可以访问外部类的成员,包括私有成员。内部类在 Java 中有多种形式,具有独特的作用和应用场景。让我们详细了解一下。


1. 内部类的定义

内部类是定义在另一个类(外部类)内部的类。根据定义位置的不同,Java 内部类可以分为以下几种类型:

  • 成员内部类(Member Inner Class)
  • 静态内部类(Static Nested Class)
  • 局部内部类(Local Inner Class)
  • 匿名内部类(Anonymous Inner Class)

2. 内部类的类型

2.1 成员内部类(Member Inner Class)

成员内部类是定义在外部类的成员区域(即字段、方法、构造函数等的同一位置)内的类。它可以访问外部类的所有成员,包括私有成员。

示例代码
class OuterClass {
    private String outerField = "Outer Field";
    
    class InnerClass {
        void printOuterField() {
            System.out.println(outerField); // 访问外部类的私有字段
        }
    }
}

public class MemberInnerClassExample {
    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        OuterClass.InnerClass inner = outer.new InnerClass();
        inner.printOuterField();  // 输出:Outer Field
    }
}

解释

  • InnerClassOuterClass 的成员内部类,能够访问外部类的所有成员。
  • 在外部类中创建内部类对象时,需要通过外部类的实例来创建。

2.2 静态内部类(Static Nested Class)

静态内部类与普通的成员内部类的不同之处在于,它是静态的,因此不能访问外部类的实例成员。它可以直接访问外部类的静态成员。

示例代码
class OuterClass {
    private static String staticField = "Static Field";
    
    static class StaticInnerClass {
        void printStaticField() {
            System.out.println(staticField);  // 访问外部类的静态字段
        }
    }
}

public class StaticNestedClassExample {
    public static void main(String[] args) {
        OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
        inner.printStaticField();  // 输出:Static Field
    }
}

解释

  • StaticInnerClass 是一个静态内部类,它只能访问外部类的静态成员。
  • 与成员内部类不同,静态内部类不需要通过外部类的实例来创建。

2.3 局部内部类(Local Inner Class)

局部内部类是定义在方法、构造器或代码块内部的类。它们只能在定义它们的方法内部使用。

示例代码
class OuterClass {
    void outerMethod() {
        class LocalInnerClass {
            void printMessage() {
                System.out.println("This is a local inner class");
            }
        }
        
        LocalInnerClass localInner = new LocalInnerClass();
        localInner.printMessage();  // 输出:This is a local inner class
    }
}

public class LocalInnerClassExample {
    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        outer.outerMethod();
    }
}

解释

  • LocalInnerClass 是在 outerMethod 方法内定义的局部内部类,只能在该方法内使用。
  • 局部内部类不能有访问修饰符,它只能访问方法内的局部变量和参数(这些变量必须是 final 或有效的)。

2.4 匿名内部类(Anonymous Inner Class)

匿名内部类没有类名,通常在创建对象的同时定义它。它用于实现接口或继承类时,简化代码。

示例代码
interface Greeting {
    void sayHello();
}

public class AnonymousInnerClassExample {
    public static void main(String[] args) {
        Greeting greeting = new Greeting() {  // 匿名内部类
            public void sayHello() {
                System.out.println("Hello from Anonymous Inner Class!");
            }
        };
        greeting.sayHello();  // 输出:Hello from Anonymous Inner Class!
    }
}

解释

  • Greeting 接口的实现是通过匿名内部类完成的,省略了类的定义,直接在创建时实现接口方法。

3. 内部类的作用

  1. 提高代码的封装性

    • 内部类允许你将类的实现隐藏在外部类中,仅暴露出外部类需要提供的接口,增强了封装性。
  2. 简化代码结构

    • 内部类可以减少类与类之间的耦合,尤其是在某些类仅用于与外部类配合使用时,可以通过内部类来简化代码。
  3. 访问外部类的成员

    • 内部类可以直接访问外部类的私有成员(包括私有字段和方法),在一些需要实现细节封装的情况下非常有用。
  4. 实现多态

    • 匿名内部类尤其适用于实现接口或抽象类,用于简化代码,避免为每个简单的实现单独定义一个类。

4. 总结

特性 成员内部类 静态内部类 局部内部类 匿名内部类
定义位置 在外部类内部的成员区域 在外部类内部,声明为 static 的类 在方法或构造函数内部 没有名称,在实例化时立即定义
访问权限 访问外部类的所有成员(包括私有成员) 只能访问外部类的静态成员 只能访问方法中的局部变量(通常是 final 用于实现接口或继承类时,简化代码
创建方式 需要外部类的实例来创建 可以直接通过外部类名创建 只能在方法内创建,不能在外部创建 在创建对象时直接定义

5. 扩展讲解

实际应用场景
  • 事件监听器:在 GUI 编程中,匿名内部类常用于实现事件监听器。
  • 线程实现:在多线程编程中,匿名内部类常用于简化线程的实现,避免单独定义一个类。
  • 设计模式:在一些设计模式中(如策略模式、观察者模式)使用内部类来实现某些功能,减少外部代码的复杂度。

通过灵活使用内部类,Java 开发者可以更高效地组织和管理代码,提高代码的可维护性和可扩展性。


问题二:JDK8 有哪些新特性?

满分回答

Java 8 是一个重要的版本,带来了很多重要的新特性,尤其是与函数式编程、流式 API 和新的日期时间 API 相关的特性。下面是 JDK 8 中的一些关键新特性:


1. Lambda 表达式

Lambda 表达式是 Java 8 的核心特性之一,它使得 Java 支持函数式编程风格。Lambda 表达式可以让你把代码块作为参数传递给方法,并使代码更加简洁和可读。

示例代码
// Lambda 表达式的示例
public class LambdaExample {
    public static void main(String[] args) {
        // 使用 Lambda 表达式创建一个线程
        Runnable r = () -> System.out.println("Hello from Lambda!");
        new Thread(r).start();
    }
}

解释

  • Runnable r = () -> System.out.println("Hello from Lambda!"); 是一个 Lambda 表达式,简化了线程的创建。

2. Stream API

Java 8 引入了 Stream API,它提供了一种高效且支持并行处理的方式来处理集合(如 List、Set 和 Map)。Stream API 使得集合的操作更加简洁和声明式,支持过滤、映射、排序等常见操作。

示例代码
import java.util.Arrays;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // 使用 Stream API 进行过滤和打印
        names.stream()
             .filter(name -> name.startsWith("A"))
             .forEach(System.out::println);  // 输出:Alice
    }
}

解释

  • names.stream() 创建一个流,filter() 方法用于筛选符合条件的元素,forEach() 方法用于遍历和打印结果。

3. 默认方法(Default Methods)

Java 8 引入了默认方法,它允许接口中定义有实现的方法。通过默认方法,接口可以向现有接口添加新方法,而无需影响实现这些接口的类。

示例代码
interface Animal {
    default void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog implements Animal {
    // 可以选择重写接口的默认方法
    @Override
    public void sound() {
        System.out.println("Dog barks");
    }
}

public class DefaultMethodExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound();  // 输出:Dog barks
    }
}

解释

  • Animal 接口定义了一个默认方法 sound(),而 Dog 类可以选择重写该方法。

4. 新的日期和时间 API(java.time)

Java 8 引入了全新的日期时间 API,解决了原先 java.util.Datejava.util.Calendar 类在使用中的很多问题。新的日期时间 API 遵循了 ISO 和其他国际标准,提供了更好的易用性和线程安全。

示例代码
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;

public class DateTimeExample {
    public static void main(String[] args) {
        LocalDate date = LocalDate.now();  // 获取当前日期
        LocalTime time = LocalTime.now();  // 获取当前时间
        LocalDateTime dateTime = LocalDateTime.now();  // 获取当前日期和时间
        
        System.out.println("Date: " + date);
        System.out.println("Time: " + time);
        System.out.println("DateTime: " + dateTime);
    }
}

解释

  • LocalDate.now() 获取当前日期,LocalTime.now() 获取当前时间,LocalDateTime.now() 获取当前日期和时间。
  • 新的日期时间 API 提供了清晰和简洁的方法来操作日期和时间。

5. Optional 类

Optional 类是 Java 8 中用于避免空指针异常(NullPointerException)的重要特性。Optional 可以包装一个可能为 null 的值,提供一系列方法来检查、转换或处理 null 值。

示例代码
import java.util.Optional;

public class OptionalExample {
    public static void main(String[] args) {
        String name = "John";

        // 使用 Optional 包装可能为空的对象
        Optional<String> optionalName = Optional.ofNullable(name);

        // 如果值存在,执行操作
        optionalName.ifPresent(n -> System.out.println(n.length()));  // 输出:4

        // 如果值为 null,则返回默认值
        String defaultName = optionalName.orElse("Unknown");
        System.out.println(defaultName);  // 输出:John
    }
}

解释

  • Optional.ofNullable(name) 用来包装一个可能为 null 的值。
  • ifPresent() 方法只有在值存在时才执行操作,orElse() 方法提供一个默认值。

6. 函数式接口(Functional Interface)

函数式接口是指只包含一个抽象方法的接口。Java 8 引入了 @FunctionalInterface 注解,用于明确标示一个接口是函数式接口。函数式接口可以作为 Lambda 表达式的目标类型。

示例代码
@FunctionalInterface
interface Calculator {
    int add(int a, int b);
}

public class FunctionalInterfaceExample {
    public static void main(String[] args) {
        // 使用 Lambda 表达式实现函数式接口
        Calculator calculator = (a, b) -> a + b;
        System.out.println(calculator.add(5, 3));  // 输出:8
    }
}

解释

  • Calculator 是一个函数式接口,定义了一个抽象方法 add(),可以使用 Lambda 表达式来实现。

7. 方法引用(Method References)

方法引用是 Lambda 表达式的一种简写方式,它允许直接引用类或对象的方法。这使得代码更加简洁和可读。

示例代码
import java.util.Arrays;
import java.util.List;

public class MethodReferenceExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

        // 使用方法引用来打印列表中的元素
        names.forEach(System.out::println);
    }
}

解释

  • System.out::println 是方法引用,它简化了 names.forEach(name -> System.out.println(name)); 这段 Lambda 表达式。

8. 新特性总结

特性 描述
Lambda 表达式 支持函数式编程,简化了代码的书写,增强了可读性。
Stream API 用于高效处理集合,支持流式操作和并行计算。
默认方法 允许接口定义默认实现,增强了接口的灵活性。
新的日期时间 API 提供了更清晰和线程安全的日期时间处理方式。
Optional 类 用于防止空指针异常,提供了更安全的空值处理方式。
函数式接口 只有一个抽象方法的接口,可以与 Lambda 表达式结合使用。
方法引用 作为 Lambda 表达式的一种简写方式,增强代码简洁性。

9. 扩展讲解

  • 函数式编程:Java 8 引入了函数式编程的支持(通过 Lambda 和 Stream API),使得 Java 更加灵活且简洁。你可以通过函数式编程方法处理集合、流、事件等,而不是单纯的使用传统的命令式编程。
  • 并行处理:Stream API 支持并行流,可以非常容易地将数据处理任务并行化,利用多核 CPU 来提升性能。

结论

Java 8 的新特性显著增强了 Java 的灵活性和表达能力。通过 Lambda 表达式、Stream API、Optional 类等,Java 使得函数式编程成为可能,并提高了代码的简洁性和可维护性。这些特性使得 Java 8 成为一个现代化且功能强大的开发平台,适合处理复杂的业务逻辑和大规模数据处理任务。


问题三:Java 中 String、StringBuffer 和 StringBuilder 的区别是什么?

满分回答

在 Java 中,StringStringBufferStringBuilder 都是用于处理字符串的类,但它们在设计、性能和线程安全性方面有所不同。以下是它们的详细对比。


1. String 类

特点
  • 不可变性String 是一个不可变类,意味着一旦创建了一个 String 对象,其内容就不能改变。每次对 String 进行修改时,都会创建一个新的 String 对象。
  • 线程安全:由于 String 是不可变的,它是线程安全的。
  • 性能:由于每次修改都会创建新的对象,因此 String 在进行大量修改时效率较低。
示例代码
public class StringExample {
    public static void main(String[] args) {
        String str = "Hello";
        str = str + " World"; // 创建了一个新的 String 对象
        System.out.println(str); // 输出:Hello World
    }
}

解释

  • String 是不可变的,修改时会创建新的对象,因此它在进行多次字符串拼接时效率较低。

2. StringBuffer 类

特点
  • 可变性StringBuffer 是可变的,意味着它的内容可以被修改而不创建新的对象。
  • 线程安全StringBuffer 是线程安全的,它通过对方法加锁来保证线程安全,但这种加锁机制会带来性能的开销。
  • 性能:由于支持内容的直接修改,StringBuffer 在进行多次修改时比 String 更高效。
示例代码
public class StringBufferExample {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("Hello");
        sb.append(" World");  // 在原有的字符串上进行修改
        System.out.println(sb);  // 输出:Hello World
    }
}

解释

  • StringBuffer 通过修改其内部的字符数组来实现字符串的拼接,因此它的性能优于 String

3. StringBuilder 类

特点
  • 可变性StringBuilder 也与 StringBuffer 类似,是可变的,可以直接修改内容而不创建新的对象。
  • 线程不安全StringBuilder 是非线程安全的。它在设计时没有加入同步机制,因此在多线程环境中可能出现问题,但它的性能比 StringBuffer 更高。
  • 性能:由于不需要线程同步,StringBuilder 在单线程情况下比 StringBuffer 更高效。
示例代码
public class StringBuilderExample {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");
        sb.append(" World");  // 在原有的字符串上进行修改
        System.out.println(sb);  // 输出:Hello World
    }
}

解释

  • StringBuilderStringBuffer 在实现上非常相似,唯一的区别在于线程安全性,StringBuilder 在没有线程同步的情况下,性能较好。

4. String、StringBuffer 和 StringBuilder 的对比

特性 String StringBuffer StringBuilder
线程安全 是(不可变) 是(通过同步)
性能 较低(每次修改都创建新对象) 较高(可以修改内容) 更高(没有同步机制)
可变性 不可变 可变(修改原对象内容) 可变(修改原对象内容)
适用场景 常用于常量、字符串不需要修改的场景 适用于多次修改、需要线程安全的场景 适用于单线程场景下需要频繁修改字符串
线程安全性 由于不可变,线程安全 通过方法加锁确保线程安全 不保证线程安全
内存管理 每次修改都会创建新的对象(会占用更多内存) 在原对象上修改,内存使用更高效 在原对象上修改,内存使用最优

5. 扩展讲解

性能对比
  • String 由于不可变性,每次修改都会创建一个新的对象,这导致了性能的较低。
  • StringBufferStringBuilder 都支持在原有对象上修改,因此它们比 String 在进行频繁修改时性能更优。
  • StringBuffer 加入了同步机制,适用于线程安全的场景,但同步机制会带来性能损失。
  • StringBuilder 是非线程安全的,适用于单线程环境中,性能更高。
适用场景
  • String:当你需要表示常量字符串,或字符串内容不需要变化时,使用 String 最为合适。
  • StringBuffer:在多线程环境中需要频繁修改字符串时,使用 StringBuffer
  • StringBuilder:在单线程环境下进行大量字符串操作时,StringBuilder 是最佳选择,提供了最高的性能。

6. 常见陷阱与注意事项

  • 线程安全的选择
    • 如果你的代码是多线程的,并且需要在多个线程中修改同一个字符串对象,应选择 StringBuffer(线程安全)。如果是单线程环境,StringBuilder 性能会更好。
  • String 的不可变性
    • String 是不可变的,这意味着字符串操作(如拼接)每次都会创建新的 String 对象。使用 StringBufferStringBuilder 可以避免这种性能问题。

结论

  • String:适用于不可变的字符串,通常用于常量和字符串不会被频繁修改的情况。
  • StringBuffer:适用于多线程环境中,需要频繁修改字符串的场景,线程安全,但性能较低。
  • StringBuilder:适用于单线程环境中,频繁修改字符串的场景,性能最好,但不保证线程安全。

根据不同的应用场景选择合适的类,可以提高代码的性能和可维护性。


总结

今天的 3 道 Java 面试题,您是否掌握了呢?持续关注我们的每日分享,深入学习 Java 面试的各个细节,快速提升技术能力!如果有任何疑问,欢迎在评论区留言,我们会第一时间解答!

明天见!

你可能感兴趣的:(【2025最新版】Java,面试宝典-日更,java,开发语言)