Bootstrap

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

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

今日分享 3 道面试题目!

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

目录

  • 问题一:Java 的 Optional 类是什么?它有什么用?
  • 问题二:Java 的 IO 流是什么?
  • 问题三:什么是 Java 的网络编程?

问题一:Java 的 Optional 类是什么?它有什么用?

Optional 是 Java 8 引入的一种容器类,位于 java.util 包中,用于表示可能包含或不包含非空值的对象。它旨在通过优雅的方式解决**NullPointerException** 问题,使代码更安全和简洁。


Optional 的作用

  1. 避免显式的 null 检查

    • 通过提供工具方法处理值是否存在,减少显式的 if-else 语句。
  2. 提升代码可读性

    • 代码逻辑更清晰,意图更明确。
  3. 防止出现 NullPointerException

    • 避免直接操作可能为 null 的对象。
  4. 帮助函数式编程

    • 提供方法链式调用,适合在流式处理和函数式编程中使用。

Optional 的核心方法

以下是 Optional 类的常用方法及其功能:

方法描述
of(T value)创建一个包含非空值的 Optional。若传入 null 会抛出 NullPointerException
ofNullable(T value)创建一个可能为空的 Optional(允许传入 null)。
empty()创建一个空的 Optional 实例。
isPresent()判断是否包含值(是否为非空)。
isEmpty()(Java 11 引入)判断是否为空(是否为 null)。
get()获取值,若值为空则抛出 NoSuchElementException
orElse(T other)值存在则返回值,否则返回默认值。
orElseGet(Supplier<? extends T>)值存在则返回值,否则通过 Supplier 提供默认值。
orElseThrow(Supplier<Throwable>)值存在则返回值,否则抛出由 Supplier 提供的异常。
map(Function<? super T,? extends U>)如果值存在,应用函数并返回新的 Optional,否则返回空的 Optional
flatMap(Function<? super T,Optional<U>>)类似 map(),但要求返回值必须是 Optional
filter(Predicate<? super T>)如果值满足谓词条件,则返回原值,否则返回空的 Optional

Optional 的使用示例

  1. 避免显式 null 检查

    import java.util.Optional;
    
    public class OptionalExample {
        public static void main(String[] args) {
            String value = "Hello, Optional!";
            Optional<String> optional = Optional.ofNullable(value);
    
            // 不用显式 null 检查
            String result = optional.orElse("Default Value");
            System.out.println(result); // 输出 "Hello, Optional!"
        }
    }
    
  2. 结合 map()filter()

    public class OptionalExample {
        public static void main(String[] args) {
            Optional<String> optional = Optional.ofNullable("Hello");
    
            optional.filter(val -> val.startsWith("H"))
                    .map(String::toUpperCase)
                    .ifPresent(System.out::println); // 输出 "HELLO"
        }
    }
    
  3. 处理可能为 null 的返回值

    public class OptionalExample {
        public static void main(String[] args) {
            Optional<String> optional = getNullableValue();
    
            // 如果值存在则输出,否则输出默认值
            System.out.println(optional.orElse("Default Value"));
        }
    
        private static Optional<String> getNullableValue() {
            return Optional.ofNullable(null); // 模拟可能为 null 的返回值
        }
    }
    
  4. 避免 NoSuchElementException

    public class OptionalExample {
        public static void main(String[] args) {
            Optional<String> optional = Optional.empty();
    
            // 避免直接调用 get() 导致异常
            System.out.println(optional.orElse("No Value")); // 输出 "No Value"
        }
    }
    

Optional 的注意事项

  1. 不要滥用 Optional

    • Optional 是用来处理返回值的工具,不应将它用于类字段序列化对象
    • 如果值必定非空,直接使用普通对象即可;若值可能为空,首选 Optional
  2. 避免调用 get()

    • get() 方法在值为空时会抛出异常,应尽量使用 orElse()orElseThrow()ifPresent() 等安全方法。
  3. 性能注意

    • Optional 本质上是一个封装对象的容器,可能有少量性能开销,不适合在性能要求较高的场景中广泛使用。

Optional 的实际应用场景

  1. 避免 null 返回值

    • 对于 DAO(数据访问对象)方法、服务层方法的返回值,避免直接返回 null
  2. 处理方法链

    • 在链式调用中,避免 null 导致链条断裂。
  3. 替代传统的 null 检查逻辑

    • 简化代码,提高可读性。

扩展:Optional 的意义与缺陷

意义
  • Optional 是 Java 语言对 空值安全处理 的一次尝试。
  • 它对函数式编程的支持非常友好,特别是在流式操作和数据处理时。
缺陷
  • Optional 并未完全取代 null,尤其是在复杂业务场景中,null 的问题依然存在。
  • 滥用 Optional 可能导致代码冗长,并增加不必要的开销。

总结

  • Optional 是一个容器类,用于优雅处理值的缺失,避免 NullPointerException
  • 它提供了许多工具方法,如 map()filter()orElse(),方便处理可能为空的场景。
  • 使用原则
    • 方法返回值中建议使用 Optional 代替直接返回 null
    • 避免将 Optional 用于类字段或方法参数。

问题二:Java 的 IO 流是什么?

Java 的 IO 流 是指 Java 提供的用于处理数据输入和输出的工具类和接口。它们位于 java.io 包中,支持文件、网络、内存缓冲区等数据源之间的数据读写操作。

IO 流分为 输入流(Input Stream)和 输出流(Output Stream),从方向上看:

  • 输入流:从数据源读取数据到程序中。
  • 输出流:从程序输出数据到目标位置。

IO 流的分类

1. 按数据处理方式分类
  • 字节流:用于处理 二进制数据,操作单位是 字节8-bit)。
    • 输入流:InputStream 及其子类。
    • 输出流:OutputStream 及其子类。
  • 字符流:用于处理 文本数据,操作单位是 字符16-bit Unicode)。
    • 输入流:Reader 及其子类。
    • 输出流:Writer 及其子类。
2. 按流的方向分类
  • 输入流:用于从数据源读取数据。
  • 输出流:用于向目标输出数据。
3. 按功能分类
  • 节点流:直接操作数据源(如文件、内存、网络等),例如 FileInputStreamFileReader
  • 处理流:对节点流进行功能增强,主要用于数据的转换、缓存等操作,例如 BufferedInputStreamBufferedReader

IO 流的体系结构

Java 的 IO 流可以归纳为以下继承体系:

1. 字节流
  • 抽象基类:
    • 输入流:InputStream
    • 输出流:OutputStream
  • 常见实现类:
    • 文件流:FileInputStreamFileOutputStream
    • 缓冲流:BufferedInputStreamBufferedOutputStream
    • 对象流:ObjectInputStreamObjectOutputStream
    • 管道流:PipedInputStreamPipedOutputStream
2. 字符流
  • 抽象基类:
    • 输入流:Reader
    • 输出流:Writer
  • 常见实现类:
    • 文件流:FileReaderFileWriter
    • 缓冲流:BufferedReaderBufferedWriter
    • 字符数组流:CharArrayReaderCharArrayWriter
    • 输入转换流:InputStreamReader
    • 输出转换流:OutputStreamWriter

IO 流的常用类与示例

1. 字节流操作文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ByteStreamExample {
    public static void main(String[] args) {
        String inputPath = "input.txt";
        String outputPath = "output.txt";

        try (FileInputStream fis = new FileInputStream(inputPath);
             FileOutputStream fos = new FileOutputStream(outputPath)) {

            int data;
            while ((data = fis.read()) != -1) {
                fos.write(data);
            }
            System.out.println("文件复制成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2. 字符流读取文件
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class CharStreamExample {
    public static void main(String[] args) {
        String filePath = "example.txt";

        try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
3. 序列化与反序列化(对象流)
import java.io.*;

class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

public class ObjectStreamExample {
    public static void main(String[] args) {
        String filePath = "person.ser";

        // 序列化
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filePath))) {
            Person person = new Person("Alice", 25);
            oos.writeObject(person);
            System.out.println("对象已序列化!");
        } catch (IOException e) {
            e.printStackTrace();
        }

        // 反序列化
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filePath))) {
            Person person = (Person) ois.readObject();
            System.out.println("反序列化对象:" + person);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

IO 流的常见问题

  1. 为什么需要缓冲流?

    • 缓冲流(如 BufferedReaderBufferedInputStream)通过内置缓存提高读写效率,减少对底层数据源的访问次数。
  2. close()try-with-resources 的区别

    • close() 手动关闭流,易遗忘或处理不当。
    • try-with-resources 是自动关闭资源的语法糖,更加安全和推荐。
  3. 字符流与字节流的区别

    • 字符流适合处理文本文件,支持字符编码转换。
    • 字节流更通用,可处理所有类型的文件,包括图片、视频、音频等。

扩展:NIO 和 IO 的区别

Java 还提供了 NIO(New IO),是 IO 的改进版本。两者的核心区别如下:

特性IO(传统 IO)NIO(新 IO)
阻塞模型默认是阻塞 IO非阻塞 IO 支持
缓冲方式面向流(Stream-Oriented)面向缓冲区(Buffer-Oriented)
多路复用不支持支持(Selector 实现多路复用)
性能较低,适合简单 IO 操作高性能,适合高并发和大文件传输场景

总结

  • Java IO 流是用于处理数据输入和输出的重要工具,分为 字节流字符流
  • 常见类如 FileInputStreamBufferedReaderObjectOutputStream 等,适合不同场景。
  • 使用 IO 流时应注意资源管理和性能优化,例如使用缓冲流和 try-with-resources
  • 在性能和并发需求较高的场景中,可以选择 NIO 来替代传统 IO。

问题三:什么是 Java 的网络编程?

Java 的网络编程是指使用 Java 提供的网络库和 API,通过网络协议(如 TCP/IP、UDP 等)实现计算机之间的通信。Java 的网络编程基于 Socket(套接字)模型,提供了面向流的高层封装,开发者可以通过 Java 程序发送和接收数据,构建客户端与服务器之间的通信。


Java 网络编程的核心概念

1. Socket(套接字)
  • 套接字是网络编程的核心,用于在通信双方之间建立连接。
  • 它可以理解为通信双方的一个端点。
  • Socket 分为:
    • TCP 套接字:提供可靠的点对点连接,使用流传输(SocketServerSocket)。
    • UDP 套接字:提供无连接的数据报传输(DatagramSocketDatagramPacket)。
2. IP 地址
  • 标识网络中的设备位置(如 192.168.1.1)。
  • Java 提供了 InetAddress 类,用于获取 IP 地址和主机名。
3. 端口
  • 标识设备中的通信程序(范围 0~65535)。
  • 常见端口:HTTP(80)、HTTPS(443)、FTP(21)等。
4. 协议
  • TCP(Transmission Control Protocol)
    • 面向连接,数据传输可靠,顺序无误。
    • 适合需要保证数据完整性的场景,如文件传输、Web 服务。
  • UDP(User Datagram Protocol)
    • 无连接,数据包可能丢失,但速度快。
    • 适合实时性要求高的场景,如视频流、在线游戏。

Java 网络编程的常用类

  1. TCP 通信

    • 客户端:Socket
    • 服务器端:ServerSocket
  2. UDP 通信

    • 套接字:DatagramSocket
    • 数据包:DatagramPacket
  3. IP 地址操作

    • InetAddress

Java 网络编程示例

1. TCP 通信示例

服务端:

import java.io.*;
import java.net.*;

public class TCPServer {
    public static void main(String[] args) {
        int port = 8080;
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("服务器已启动,等待客户端连接…");

            // 等待客户端连接
            Socket socket = serverSocket.accept();
            System.out.println("客户端已连接:" + socket.getInetAddress());

            // 接收客户端发送的数据
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String message = reader.readLine();
            System.out.println("收到客户端消息:" + message);

            // 向客户端发送响应
            PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
            writer.println("消息已收到:" + message);

            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端:

import java.io.*;
import java.net.*;

public class TCPClient {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 8080;

        try (Socket socket = new Socket(host, port)) {
            System.out.println("已连接到服务器:" + socket.getInetAddress());

            // 向服务器发送数据
            PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
            writer.println("你好,服务器!");

            // 接收服务器的响应
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String response = reader.readLine();
            System.out.println("收到服务器消息:" + response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
2. UDP 通信示例

服务端:

import java.net.*;

public class UDPServer {
    public static void main(String[] args) {
        int port = 8080;
        try (DatagramSocket serverSocket = new DatagramSocket(port)) {
            System.out.println("UDP 服务器已启动,等待数据…");

            byte[] buffer = new byte[1024];
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

            // 接收数据
            serverSocket.receive(packet);
            String message = new String(packet.getData(), 0, packet.getLength());
            System.out.println("收到客户端消息:" + message);

            // 发送响应
            String response = "消息已收到:" + message;
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, packet.getAddress(), packet.getPort());
            serverSocket.send(responsePacket);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端:

import java.net.*;

public class UDPClient {
    public static void main(String[] args) {
        String host = "localhost";
        int port = 8080;

        try (DatagramSocket clientSocket = new DatagramSocket()) {
            String message = "你好,服务器!";
            byte[] buffer = message.getBytes();
            InetAddress address = InetAddress.getByName(host);

            // 发送数据
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, port);
            clientSocket.send(packet);

            // 接收响应
            byte[] responseBuffer = new byte[1024];
            DatagramPacket responsePacket = new DatagramPacket(responseBuffer, responseBuffer.length);
            clientSocket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
            System.out.println("收到服务器消息:" + response);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

扩展:Java 网络编程的特点

  1. 平台无关性

    • Java 提供的网络 API 封装了底层操作,无需关心操作系统差异。
  2. 高层封装

    • Java 提供了简单易用的类(如 SocketServerSocket),隐藏了复杂的网络通信细节。
  3. 多线程支持

    • Java 网络编程可以结合多线程实现高效的并发通信(如处理多个客户端的请求)。

常见问题与优化建议

  1. 问题:客户端与服务器连接失败

    • 检查主机地址和端口是否正确。
    • 防火墙可能阻止了连接。
  2. 问题:TCP 的连接未正常关闭

    • 解决方案:使用 try-with-resources 自动关闭资源。
  3. 优化:高并发场景

    • 传统 IO 的阻塞模型在高并发场景下性能有限,可以考虑使用 NIONetty 等框架。

总结

  • Java 的网络编程通过 Socket 提供了高层封装,支持 TCP 和 UDP 通信。
  • 常用类包括 SocketServerSocketDatagramSocketInetAddress
  • 在开发时应注意资源管理、多线程支持以及高性能需求,选择合适的通信模型(如阻塞或非阻塞)。

总结

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

明天见!🎉

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;