欢迎来到每日 Java 面试题分享栏目!
订阅专栏,不错过每一天的练习
今日分享 3 道面试题目!
评论区复述一遍印象更深刻噢~
在 Java 中,内部类(Inner Class)是定义在另一个类内部的类。它可以访问外部类的成员,包括私有成员。内部类在 Java 中有多种形式,具有独特的作用和应用场景。让我们详细了解一下。
内部类是定义在另一个类(外部类)内部的类。根据定义位置的不同,Java 内部类可以分为以下几种类型:
成员内部类是定义在外部类的成员区域(即字段、方法、构造函数等的同一位置)内的类。它可以访问外部类的所有成员,包括私有成员。
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
}
}
解释:
InnerClass
是 OuterClass
的成员内部类,能够访问外部类的所有成员。静态内部类与普通的成员内部类的不同之处在于,它是静态的,因此不能访问外部类的实例成员。它可以直接访问外部类的静态成员。
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
是一个静态内部类,它只能访问外部类的静态成员。局部内部类是定义在方法、构造器或代码块内部的类。它们只能在定义它们的方法内部使用。
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
或有效的)。匿名内部类没有类名,通常在创建对象的同时定义它。它用于实现接口或继承类时,简化代码。
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
接口的实现是通过匿名内部类完成的,省略了类的定义,直接在创建时实现接口方法。提高代码的封装性:
简化代码结构:
访问外部类的成员:
实现多态:
特性 | 成员内部类 | 静态内部类 | 局部内部类 | 匿名内部类 |
---|---|---|---|---|
定义位置 | 在外部类内部的成员区域 | 在外部类内部,声明为 static 的类 |
在方法或构造函数内部 | 没有名称,在实例化时立即定义 |
访问权限 | 访问外部类的所有成员(包括私有成员) | 只能访问外部类的静态成员 | 只能访问方法中的局部变量(通常是 final ) |
用于实现接口或继承类时,简化代码 |
创建方式 | 需要外部类的实例来创建 | 可以直接通过外部类名创建 | 只能在方法内创建,不能在外部创建 | 在创建对象时直接定义 |
通过灵活使用内部类,Java 开发者可以更高效地组织和管理代码,提高代码的可维护性和可扩展性。
Java 8 是一个重要的版本,带来了很多重要的新特性,尤其是与函数式编程、流式 API 和新的日期时间 API 相关的特性。下面是 JDK 8 中的一些关键新特性:
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 表达式,简化了线程的创建。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()
方法用于遍历和打印结果。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
类可以选择重写该方法。Java 8 引入了全新的日期时间 API,解决了原先 java.util.Date
和 java.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()
获取当前日期和时间。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()
方法提供一个默认值。函数式接口是指只包含一个抽象方法的接口。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 表达式来实现。方法引用是 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 表达式。特性 | 描述 |
---|---|
Lambda 表达式 | 支持函数式编程,简化了代码的书写,增强了可读性。 |
Stream API | 用于高效处理集合,支持流式操作和并行计算。 |
默认方法 | 允许接口定义默认实现,增强了接口的灵活性。 |
新的日期时间 API | 提供了更清晰和线程安全的日期时间处理方式。 |
Optional 类 | 用于防止空指针异常,提供了更安全的空值处理方式。 |
函数式接口 | 只有一个抽象方法的接口,可以与 Lambda 表达式结合使用。 |
方法引用 | 作为 Lambda 表达式的一种简写方式,增强代码简洁性。 |
Java 8 的新特性显著增强了 Java 的灵活性和表达能力。通过 Lambda 表达式、Stream API、Optional 类等,Java 使得函数式编程成为可能,并提高了代码的简洁性和可维护性。这些特性使得 Java 8 成为一个现代化且功能强大的开发平台,适合处理复杂的业务逻辑和大规模数据处理任务。
在 Java 中,String
、StringBuffer
和 StringBuilder
都是用于处理字符串的类,但它们在设计、性能和线程安全性方面有所不同。以下是它们的详细对比。
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
是不可变的,修改时会创建新的对象,因此它在进行多次字符串拼接时效率较低。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
。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
}
}
解释:
StringBuilder
和 StringBuffer
在实现上非常相似,唯一的区别在于线程安全性,StringBuilder
在没有线程同步的情况下,性能较好。特性 | String | StringBuffer | StringBuilder |
---|---|---|---|
线程安全 | 是(不可变) | 是(通过同步) | 否 |
性能 | 较低(每次修改都创建新对象) | 较高(可以修改内容) | 更高(没有同步机制) |
可变性 | 不可变 | 可变(修改原对象内容) | 可变(修改原对象内容) |
适用场景 | 常用于常量、字符串不需要修改的场景 | 适用于多次修改、需要线程安全的场景 | 适用于单线程场景下需要频繁修改字符串 |
线程安全性 | 由于不可变,线程安全 | 通过方法加锁确保线程安全 | 不保证线程安全 |
内存管理 | 每次修改都会创建新的对象(会占用更多内存) | 在原对象上修改,内存使用更高效 | 在原对象上修改,内存使用最优 |
String
由于不可变性,每次修改都会创建一个新的对象,这导致了性能的较低。StringBuffer
和 StringBuilder
都支持在原有对象上修改,因此它们比 String
在进行频繁修改时性能更优。StringBuffer
加入了同步机制,适用于线程安全的场景,但同步机制会带来性能损失。StringBuilder
是非线程安全的,适用于单线程环境中,性能更高。String
:当你需要表示常量字符串,或字符串内容不需要变化时,使用 String
最为合适。StringBuffer
:在多线程环境中需要频繁修改字符串时,使用 StringBuffer
。StringBuilder
:在单线程环境下进行大量字符串操作时,StringBuilder
是最佳选择,提供了最高的性能。StringBuffer
(线程安全)。如果是单线程环境,StringBuilder
性能会更好。String
是不可变的,这意味着字符串操作(如拼接)每次都会创建新的 String
对象。使用 StringBuffer
或 StringBuilder
可以避免这种性能问题。String
:适用于不可变的字符串,通常用于常量和字符串不会被频繁修改的情况。StringBuffer
:适用于多线程环境中,需要频繁修改字符串的场景,线程安全,但性能较低。StringBuilder
:适用于单线程环境中,频繁修改字符串的场景,性能最好,但不保证线程安全。根据不同的应用场景选择合适的类,可以提高代码的性能和可维护性。
今天的 3 道 Java 面试题,您是否掌握了呢?持续关注我们的每日分享,深入学习 Java 面试的各个细节,快速提升技术能力!如果有任何疑问,欢迎在评论区留言,我们会第一时间解答!
明天见!