41Java对象基础

Java对象

Java创建对象除了new还有别的什么方式?

  • 通过反射创建对象:通过 Java 的反射机制可以在运行时动态地创建对象。可以使用 Class 类的 newInstance() 方法或者通过 Constructor 类来创建对象。
1
2
3
4
5
6
7
8
9
10
11
12
public class MyClass {
public MyClass() {
// Constructor
}
}

public class Main {
public static void main(String[] args) throws Exception {
Class<?> clazz = MyClass.class;
MyClass obj = (MyClass) clazz.newInstance();
}
}
  • 通过反序列化创建对象:通过将对象序列化(保存到文件或网络传输)然后再反序列化(从文件或网络传输中读取对象)的方式来创建对象,对象能被序列化和反序列化的前提是类实现Serializable接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.*;

public class MyClass implements Serializable {
// Class definition
}

public class Main {
public static void main(String[] args) throws Exception {
// Serialize object
MyClass obj = new MyClass();
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("object.ser"));
out.writeObject(obj);
out.close();

// Deserialize object
ObjectInputStream in = new ObjectInputStream(new FileInputStream("object.ser"));
MyClass newObj = (MyClass) in.readObject();
in.close();
}
}
  • 通过clone创建对象:所有 Java 对象都继承自 Object 类,Object 类中有一个 clone() 方法,可以用来创建对象的副本,要使用 clone 方法,我们必须先实现 Cloneable 接口并实现其定义的 clone 方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyClass implements Cloneable {
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
MyClass obj1 = new MyClass();
MyClass obj2 = (MyClass) obj1.clone();
}
}

如何获取私有对象?

在 Java 中,私有对象通常指的是类中被声明为 private 的成员变量或方法。由于 private 访问修饰符的限制,这些成员只能在其所在的类内部被访问。

不过,可以通过下面两种方式来间接获取私有对象。

  • 使用公共访问器方法(getter 方法):如果类的设计者遵循良好的编程规范,通常会为私有成员变量提供公共的访问器方法(即 getter 方法),通过调用这些方法可以安全地获取私有对象。

  • 反射机制。反射机制允许在运行时检查和修改类、方法、字段等信息,通过反射可以绕过 private 访问修饰符的限制来获取私有对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.lang.reflect.Field;

class MyClass {
private String privateField = "私有字段的值";
}

public class Main {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
MyClass obj = new MyClass();
// 获取 Class 对象
Class<?> clazz = obj.getClass();
// 获取私有字段
Field privateField = clazz.getDeclaredField("privateField");
// 设置可访问性
privateField.setAccessible(true);
// 获取私有字段的值
String value = (String) privateField.get(obj);
System.out.println(value);
}
}

异常

介绍一下Java异常

Java异常类层次结构图:imgJava的异常体系主要基于两大类:Throwable类及其子类。Throwable有两个重要的子类:Error和Exception,它们分别代表了不同类型的异常情况。

  1. Error(错误):表示运行时环境的错误。错误是程序无法处理的严重问题,如系统崩溃、虚拟机错误、动态链接失败等。通常,程序不应该尝试捕获这类错误。例如,OutOfMemoryError、StackOverflowError等。
  2. Exception(异常):表示程序本身可以处理的异常条件。异常分为两大类:
    • 非运行时异常:这类异常在编译时期就必须被捕获或者声明抛出。它们通常是外部错误,如文件不存在(FileNotFoundException)、类未找到(ClassNotFoundException)等。非运行时异常强制程序员处理这些可能出现的问题,增强了程序的健壮性。
    • 运行时异常:这类异常包括运行时异常(RuntimeException)和错误(Error)。运行时异常由程序错误导致,如空指针访问(NullPointerException)、数组越界(ArrayIndexOutOfBoundsException)等。运行时异常是不需要在编译时强制捕获或声明的。
    • SecurityException我们直接获取[Unsafe],调用其中属性方法时会出现异常:
    • ConcurrentModificationException-并发修改异常 –线程不安全集合案例
    • BeanCurrentlyInCreationException—循环依赖

Java异常处理有哪些?

异常处理是通过使用try-catch语句块来捕获和处理异常。以下是Java中常用的异常处理方式:

  • try-catch语句块:用于捕获并处理可能抛出的异常。try块中包含可能抛出异常的代码,catch块用于捕获并处理特定类型的异常。可以有多个catch块来处理不同类型的异常。
1
2
3
4
5
6
7
8
9
10
11
try {
// 可能抛出异常的代码
} catch (ExceptionType1 e1) {
// 处理异常类型1的逻辑
} catch (ExceptionType2 e2) {
// 处理异常类型2的逻辑
} catch (ExceptionType3 e3) {
// 处理异常类型3的逻辑
} finally {
// 可选的finally块,用于定义无论是否发生异常都会执行的代码
}
  • throw语句:用于手动抛出异常。可以根据需要在代码中使用throw语句主动抛出特定类型的异常。
1
throw new ExceptionType("Exception message");
  • throws关键字:用于在方法声明中声明可能抛出的异常类型。如果一个方法可能抛出异常,但不想在方法内部进行处理,可以使用throws关键字将异常传递给调用者来处理。
1
2
3
public void methodName() throws ExceptionType {
// 方法体
}
  • finally块:用于定义无论是否发生异常都会执行的代码块。通常用于释放资源,确保资源的正确关闭。
1
2
3
4
5
6
7
try {
// 可能抛出异常的代码
} catch (ExceptionType e) {
// 处理异常的逻辑
} finally {
// 无论是否发生异常,都会执行的代码
}

try catch中的语句运行情况

try块中的代码将按顺序执行,如果抛出异常,将在catch块中进行匹配和处理,然后程序将继续执行catch块之后的代码。如果没有匹配的catch块,异常将被传递给上一层调用的方法。

try{return “a”} finally{return “b”}这条语句返回啥

finally块中的return语句会覆盖try块中的return返回,因此,该语句将返回”b”。

注解

能讲一讲Java注解的原理吗?

注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。

我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationInvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。

对注解解析的底层实现了解吗?

注解本质上是一种特殊的接口,它继承自 java.lang.annotation.Annotation 接口,所以注解也叫声明式接口,例如,定义一个简单的注解:

1
2
3
public @interface MyAnnotation {
String value();
}

编译后,Java 编译器会将其转换为一个继承自 Annotation 的接口,并生成相应的字节码文件。

根据注解的作用范围,Java 注解可以分为以下几种类型:

  • 源码级别注解 :仅存在于源码中,编译后不会保留(@Retention(RetentionPolicy.SOURCE))。
  • 类文件级别注解 :保留在 .class 文件中,但运行时不可见(@Retention(RetentionPolicy.CLASS))。
  • 运行时注解 :保留在 .class 文件中,并且可以通过反射在运行时访问(@Retention(RetentionPolicy.RUNTIME))。

Java注解的作用域呢?

注解的作用域(Scope)指的是注解可以应用在哪些程序元素上,例如类、方法、字段等。Java注解的作用域可以分为三种:

  1. 类级别作用域:用于描述类的注解,通常放置在类定义的上面,可以用来指定类的一些属性,如类的访问级别、继承关系、注释等。
  2. 方法级别作用域:用于描述方法的注解,通常放置在方法定义的上面,可以用来指定方法的一些属性,如方法的访问级别、返回值类型、异常类型、注释等。
  3. 字段级别作用域:用于描述字段的注解,通常放置在字段定义的上面,可以用来指定字段的一些属性,如字段的访问级别、默认值、注释等。

java中的值传递和引用传递

image-20251124215833211

反射

什么是反射?

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。

反射具有以下特性:

  1. 运行时类信息访问:反射机制允许程序在运行时获取类的完整结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等。
  2. 动态对象创建:可以使用反射API动态地创建对象实例。
  3. 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法实现,允许你传入对象实例和参数值来执行方法。
  4. 访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的get()和set()方法完成的。

image-20251124220602549

Java 新特性

Java 8 你知道有什么新特性?

下面是 Java 8 主要新特性的整理表格,包含关键改进和示例说明:

特性名称 描述 示例或说明
Lambda 表达式 简化匿名内部类,支持函数式编程 (a, b) -> a + b 代替匿名类实现接口
函数式接口 仅含一个抽象方法的接口,可用 @FunctionalInterface 注解标记 Runnable, Comparator, 或自定义接口 @FunctionalInterface interface MyFunc { void run(); }
Stream API 提供链式操作处理集合数据,支持并行处理 list.stream().filter(x -> x > 0).collect(Collectors.toList())
Optional 类 封装可能为 null 的对象,减少空指针异常 Optional.ofNullable(value).orElse("default")
方法引用 简化 Lambda 表达式,直接引用现有方法 System.out::println 等价于 x -> System.out.println(x)
接口的默认方法与静态方法 接口可定义默认实现和静态方法,增强扩展性 interface A { default void print() { System.out.println("默认方法"); } }
并行数组排序 使用多线程加速数组排序 Arrays.parallelSort(array)
重复注解 允许同一位置多次使用相同注解 @Repeatable 注解配合容器注解使用
类型注解 注解可应用于更多位置(如泛型、异常等) List<@NonNull String> list
CompletableFuture 增强异步编程能力,支持链式调用和组合操作 CompletableFuture.supplyAsync(() -> "result").thenAccept(System.out::println)

Object

构造器方法

  • Optional.of(T t) : 创建一个 Optional 实例,t 必须非空
  • Optional.empty() : 创建一个空的 Optional 实例
  • Optional.ofNullable(T t):t 可以为 null

获取类方法

  • T get(): 如果调用对象包含值不为 null,返回该值,否则抛异常
  • T orElse(T other) :如果有值则将其返回,否则返回指定的 other 对象
  • T orElseGet(Supplier<? extends T> other) :如果有值则将其返回,否则返回由 Supplier 接口实现提供的对象。
  • T orElseThrow(Supplier<? extends X> exceptionSupplier) :如果有值则将其返回,否则抛出由 Supplie

判断类方法

  • boolean isPresent() : 判断是否包含对象
  • void ifPresent(Consumer<? super T> consumer) :如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它。
  • void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) :如果有值,就执行 Consumer 接口的实现代码,并且该值会作为参数传给它,否则执行 Runnable 接口的实现代码。

使用 Optional 的最佳实践

  1. 推荐在方法返回值中使用 Optional
    • 明确告诉调用方返回值可能不存在。
    • 例如:Optional<User> findById(Long id)
  2. 不要将 Optional 用于实体类的字段
    • Optional 是一个容器类,不适合用于序列化和持久化场景。
  3. 避免直接使用 get()
    • 直接调用 get() 方法会引发异常,应该优先使用 orElse()ifPresent() 等安全方法。
  4. 不要滥用 Optional
    • 对于简单的 null 检查,可能无需引入 Optional。使用它的场景应该是明确表示“值可能不存在”的语义。

序列化

#怎么把一个对象从一个jvm转移到另一个jvm?

  • 使用序列化和反序列化:将对象序列化为字节流,并将其发送到另一个 JVM,然后在另一个 JVM 中反序列化字节流恢复对象。这可以通过 Java 的 ObjectOutputStream 和 ObjectInputStream 来实现。
  • 使用消息传递机制:利用消息传递机制,比如使用消息队列(如 RabbitMQ、Kafka)或者通过网络套接字进行通信,将对象从一个 JVM 发送到另一个。这需要自定义协议来序列化对象并在另一个 JVM 中反序列化。
  • 使用远程方法调用(RPC):可以使用远程方法调用框架,如 gRPC,来实现对象在不同 JVM 之间的传输。远程方法调用可以让你在分布式系统中调用远程 JVM 上的对象的方法。
  • 使用共享数据库或缓存:将对象存储在共享数据库(如 MySQL、PostgreSQL)或共享缓存(如 Redis)中,让不同的 JVM 可以访问这些共享数据。这种方法适用于需要共享数据但不需要直接传输对象的场景。

I/O

Java怎么实现网络IO高并发编程?

可以用 Java NIO ,是一种同步非阻塞的I/O模型,也是I/O多路复用的基础。

传统的BIO里面socket.read(),如果TCP RecvBuffer里没有数据,函数会一直阻塞,直到收到数据,返回读到的数据, 如果使用BIO要想要并发处理多个客户端的i/o,那么会使用多线程模式,一个线程专门处理一个客户端 io,这种模式随着客户端越来越多,所需要创建的线程也越来越多,会急剧消耗系统的性能。

image-20240820112641716

NIO 是基于I/O多路复用实现的,它可以只用一个线程处理多个客户端I/O,如果你需要同时管理成千上万的连接,但是每个连接只发送少量数据,例如一个聊天服务器,用NIO实现会更好一些。

image-20240820112656259

BIO、NIO、AIO区别是什么?

  • BIO(blocking IO):就是传统的 java.io 包,它是基于流模型实现的,交互的方式是同步、阻塞方式,也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用是可靠的线性顺序。优点是代码比较简单、直观;缺点是 IO 的效率和扩展性很低,容易成为应用性能瓶颈。
  • NIO(non-blocking IO) :Java 1.4 引入的 java.nio 包,提供了 Channel、Selector、Buffer 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。
  • AIO(Asynchronous IO) :是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作

其他

#有一个学生类,想按照分数排序,再按学号排序,应该怎么做?

可以使用Comparable接口来实现按照分数排序,再按照学号排序。首先在学生类中实现Comparable接口,并重写compareTo方法,然后在compareTo方法中实现按照分数排序和按照学号排序的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Student implements Comparable<Student> {
private int id;
private int score;

// 构造方法和其他属性、方法省略

@Override
public int compareTo(Student other) {
if (this.score != other.score) {
return Integer.compare(other.score, this.score); // 按照分数降序排序
} else {
return Integer.compare(this.id, other.id); // 如果分数相同,则按照学号升序排序
}
}
}

然后在需要对学生列表进行排序的地方,使用Collections.sort()方法对学生列表进行排序即可:

1
2
3
List<Student> students = new ArrayList<>();
// 添加学生对象到列表中
Collections.sort(students);

end


41Java对象基础
http://example.com/2025/11/22/41Java基础对象/
作者
無鎏雲
发布于
2025年11月22日
许可协议