What is Java ?(三)
What is Java ?(三)
xiaoyan内容:注解
、异常
、Object
、Java8新特性
本篇博客是笔者作为初学者记录自己对Java一些基本概念的理解。内容参考了大量网络资源,篇幅很长,旨在作为个人学习笔记,供自己日后回顾和复习。
注解
在Java中,注解(Annotation)是一种元数据(metadata),它提供了关于程序代码的额外信息,但本身并不直接影响程序的执行。注解可以用于类、方法、字段、参数、局部变量等程序元素上,用于在编译时、运行时或部署时提供额外的信息。
注解本质是一个继承了Annotation的特殊接口,其具体实现类是Java运行时生成的动态代理类。我们通过反射获取注解时,返回的是Java运行时生成的动态代理对象。通过代理对象调用自定义注解的方法,会最终调用AnnotationlnvocationHandler的invoke方法。该方法会从memberValues这个Map中索引出对应的值。而memberValues的来源是Java常量池。
注解的原理
定义注解:
注解是通过@interface
关键字定义的。例如:1
2
3
4public MyAnnotation {
String value();
int count() default 1;
}这个注解
MyAnnotation
有两个元素:value
和count
。value
是必需的,而count
有一个默认值1
。元注解:
元注解是用于注解其他注解的注解。Java提供了几个内置的元注解,用于控制注解的行为:@Retention
:指定注解的保留策略,即注解在什么阶段有效(源码、编译时、运行时)。RetentionPolicy.SOURCE
:注解仅在源码中保留,编译时丢弃。RetentionPolicy.CLASS
:注解在编译时保留,但运行时不可见。RetentionPolicy.RUNTIME
:注解在运行时保留,可以通过反射获取。
@Target
:指定注解可以应用的目标类型(类、方法、字段等)。ElementType.TYPE
:类、接口、枚举。ElementType.METHOD
:方法。ElementType.FIELD
:字段。ElementType.PARAMETER
:方法参数。ElementType.CONSTRUCTOR
:构造函数。ElementType.LOCAL_VARIABLE
:局部变量。ElementType.ANNOTATION_TYPE
:注解类型。ElementType.PACKAGE
:包。
@Documented
:指定注解是否包含在JavaDoc中。@Inherited
:指定注解是否可以被子类继承。@Repeatable
:指定注解是否可以重复应用在同一个元素上。
使用注解:
注解可以应用在类、方法、字段等程序元素上。例如:1
2
3
4
5
6
7
8
9
10
public class MyClass {
private String myField;
public void myMethod() {
// ...
}
}处理注解:
注解本身并不做任何事情,它们需要通过某种方式被处理才能发挥作用。处理注解的方式主要有两种:编译时处理:使用注解处理器(Annotation Processor)在编译时处理注解。例如,Lombok库使用注解处理器在编译时生成代码。
运行时处理:使用反射(Reflection)在运行时获取注解信息。例如:
1
2
3
4
5
6
7public void processAnnotation(Class<?> clazz) {
if (clazz.isAnnotationPresent(MyAnnotation.class)) {
MyAnnotation annotation = clazz.getAnnotation(MyAnnotation.class);
System.out.println("Value: " + annotation.value());
System.out.println("Count: " + annotation.count());
}
}这个方法通过反射检查类上是否存在
MyAnnotation
注解,并获取注解的值。
应用作用域
在Java中,注解可以应用于多种程序元素,包括类、方法、字段(属性)、参数、局部变量等。注解的作用域(即注解可以应用的目标类型)由@Target
元注解指定。以下是一些常见的注解作用域及其对应的ElementType
枚举值:
1. 类
注解可以应用于类、接口、枚举等类型定义上。对应的ElementType
是TYPE
。
1 |
|
使用示例:
1 |
|
2. 方法
注解可以应用于方法定义上。对应的ElementType
是METHOD
。
1 |
|
使用示例:
1 | public class MyClass { |
3. 属性(字段)
注解可以应用于字段(属性)定义上。对应的ElementType
是FIELD
。
1 |
|
使用示例:
1 | public class MyClass { |
其他作用域
除了上述常见的类、方法和字段,注解还可以应用于其他程序元素:
参数:注解可以应用于方法参数上。对应的
ElementType
是PARAMETER
。1
2
3
4
5
public ParameterAnnotation {
String value();
}使用示例:
1
2
3
4
5public class MyClass {
public void myMethod( { String param)
// ...
}
}构造函数:注解可以应用于构造函数上。对应的
ElementType
是CONSTRUCTOR
。1
2
3
4
5
public ConstructorAnnotation {
String value();
}使用示例:
1
2
3
4
5
6public class MyClass {
public MyClass() {
// ...
}
}局部变量:注解可以应用于局部变量上。对应的
ElementType
是LOCAL_VARIABLE
。1
2
3
4
5
public LocalVariableAnnotation {
String value();
}使用示例:
1
2
3
4
5
6
7public class MyClass {
public void myMethod() {
String localVar = "Hello";
// ...
}
}注解类型:注解可以应用于其他注解类型上。对应的
ElementType
是ANNOTATION_TYPE
。1
2
3
4
5
public MetaAnnotation {
String value();
}使用示例:
1
2
3
4
5
6
public MyMethodAnnotation {
String value();
}包:注解可以应用于包声明上。对应的
ElementType
是PACKAGE
。1
2
3
4
5
public PackageAnnotation {
String value();
}使用示例:
1
2
3// 在 package-info.java 文件中
package com.example;
异常
啥是异常?
Java异常主要基于两大类:Throwable
类及其子类。Throwable
的子类主要有两个重要的子类:Error
和Exception
,它们分别代表了不同类型的异常情况。
Error
Error
表示运行环境错误,极难恢复,不能指望程序自己处理这些异常。比如系统崩溃、连接错误等。比如OutOfMemoryError
内存不足错误、StackOverflowError
栈溢出错误。通常程序不应该去捕获这些错误,因为它们表示系统级别的严重问题,程序无法自行恢复。
Exception
Exception
表示运行程序错误,根据发生时期又可以分为编译时异常和运行时异常。
编译时异常(Checked Exception)
在代码编译时出现的异常,必须显式处理(捕获或声明抛出)。这些异常通常是程序外部的异常,比如文件不存在、无法找到类等。对于这些异常必须使用try-catch
块捕获异常,或者在方法签名中使用throws
关键字声明抛出异常。
运行时异常(Unchecked Exception)
程序运行过程中出现的异常,通常由程序逻辑错误引起,不需要显式处理。
- 常见异常:
NullPointerException
:空指针异常。IllegalArgumentException
:非法参数异常。ClassCastException
:类转换异常。IndexOutOfBoundsException
:数组越界异常。
虽然运行时异常不需要显式处理,但建议在代码中进行适当的检查和处理,以提高程序的健壮性。
异常处理机制
Java提供了异常处理机制,通过try-catch
语句块来捕获和处理异常。以下是Java常见的异常处理方式:
try块:包含可能抛出异常的代码。
catch块:捕获并处理异常。
throw语句:用于手动抛出异常。
finally块:无论是否发生异常,都会执行的代码块,通常用于资源清理。
示例代码
以下是一个完整的异常处理示例,展示了如何使用try-catch
语句块、throw
语句和finally
块:
1 | import java.io.FileInputStream; |
抛出异常为什么不用throws?
如果说异常是未检查异常(Unchecked Exception)或已经在方法内部处理,就不需要再使用throws
声明了。
Unchecked Exception:未检查异常,是继承自
RuntimeException
类或者Error
类的异常,编译器不强制要求进行异常处理。因此对于这些异常,不需要在方法签名中使用throws
来声明。捕获和异常处理:另一种常见情况是已经在方法内部捕获了可能抛出的异常并在方法内部处理它们。
try-catch的执行顺序
正常情况下会按顺序执行try
块中的代码,当运行过程中出现异常则跳转到相应的catch
异常捕获块,最后无论是否出现异常,总会执行finally
块。
常见问题
以下这段代码最后会返回什么?
1 | try { |
答案是返回'b'
。因为try
的return
语句先执行,压入返回栈中,而finally
中的return
方法后执行,也压入栈中,返回结果先弹出栈上方的元素,所以会返回'b'
,也就是说finally
中的return
会覆盖try
块的返回语句。
示例代码
以下是一个示例,展示了try-catch-finally
块的执行顺序:
1 | public class TryCatchFinallyExample { |
输出结果:
1 | Executing try block |
在这个示例中,try
块中的return 'a'
语句先执行,但被finally
块中的return 'b'
语句覆盖,因此最终返回值为'b'
。
Object
“==”与“equals”有什么区别?
对于字符串变量,==
与equals
方法是不同的。==
判断的是两个对象的引用是否相同,即比较两个对象的内存首地址是否指向同一个对象,而equals
则判断的是两个对象的值是否相同。
示例代码片段,对于字符串来说:
1 | public class StringComparisonExample { |
对于非字符串来说,若没有对equals
方法重写,则==
和equals
是相同的,都是比较两个对象的内存首地址是否相同,即比较两个对象的引用是否指向同一个变量。
示例代码片段,对于非字符串对象:
1 | public class ObjectComparisonExample { |
“equals”与“hashCode”
equals
方法用于比较两个对象是否相等,而hashCode
方法返回对象的哈希码值。在Java中,如果两个对象通过equals
方法比较相等,那么它们的hashCode
值必须相同。反之,如果两个对象的hashCode
值相同,它们不一定相等。
示例代码片段
1 | public class EqualsHashCodeExample { |
StringBuilder和StringBuffer
由于String
类型是不可变的(immutable),每次对String
进行修改操作时,都会创建一个新的String
对象,这可能会导致性能问题。为了解决这个问题,Java提供了StringBuilder
和StringBuffer
类,它们是可变的(mutable),允许在原有对象上进行修改操作。
区别
线程安全:
StringBuffer
是线程安全的,适用于多线程环境。StringBuilder
不是线程安全的,适用于单线程环境,性能更高。
性能:
- 通常情况下,
StringBuilder
的性能优于StringBuffer
,因为StringBuffer
需要维护线程安全,会带来额外的开销。 String
的性能通常是最差的,因为每次修改都会创建新的对象。
- 通常情况下,
使用场景:
- 在单线程环境中,推荐使用
StringBuilder
以获得最佳性能。 - 在多线程环境中,如果需要线程安全,使用
StringBuffer
。
- 在单线程环境中,推荐使用
示例代码片段
1 | public class StringBuilderStringBufferExample { |
Java 1.8 新特性
Stream API
Java 8 引入了 Stream API,提供了一种更加高效的数据处理方式,特别是对于集合的操作如过滤、排序、映射等。使用 Stream 能够充分利用多核处理器的优势进行并行处理。
示例
以下是一个简单的示例,展示了如何使用 Stream API 对集合进行操作:
1 | import java.util.Arrays; |
解释
创建列表:
- 使用
Arrays.asList
创建一个包含整数的列表。
- 使用
使用 Stream API:
numbers.stream()
:创建一个顺序流。filter(n -> n % 2 == 0)
:过滤出偶数。map(n -> n * n)
:将每个偶数计算平方。collect(Collectors.toList())
:将结果收集到一个列表中。
使用并行流:
numbers.parallelStream()
:创建一个并行流,充分利用多核处理器的优势。
输出
1 | Even squares: [4, 16, 36, 64, 100] |
Stream流的并行API
即 ParallelStream
。并行流其实带着“分而治之”的思想,在处理源数据时,将数据分成多个子流,然后将处理结果汇总为一个流对象。底层逻辑是使用 Fork/Join 框架来实现并行处理。
对CPU密集型的任务来说,并行流使用ForkJoinPool线程池,为每个CPU分配一个任务,这是非常有效率的,但是如果任务不是CPU密集的,而是I/O密集的,并且任务数相对线程数比较大,那么直接用Parallelstream并不是很好的选择。
CompletableFuture
CompletableFuture
是 Java 8 引入的一个类,用于支持异步编程和非阻塞操作。它实现了 Future
接口,并提供了更强大的功能,如组合多个异步任务、处理异常、以及在任务完成时执行回调等。
主要功能
异步执行任务:使用
CompletableFuture.supplyAsync
或CompletableFuture.runAsync
方法来异步执行任务。任务组合:使用
thenApply
、thenAccept
、thenRun
等方法来组合多个异步任务。异常处理:使用
exceptionally
方法来处理异常。任务完成时的回调:使用
whenComplete
方法在任务完成时执行回调。
示例
以下是一个简单的示例,展示了如何使用 CompletableFuture
进行异步编程:
1 | import java.util.concurrent.CompletableFuture; |
解释
异步执行任务:
CompletableFuture.supplyAsync(() -> { ... })
:异步执行一个返回值为String
的任务。任务完成时的回调:
future.thenAccept(result -> { ... })
:在任务完成时执行回调,输出结果。异常处理:
future.exceptionally(ex -> { ... })
:处理任务执行过程中可能抛出的异常。等待任务完成:
future.get()
:等待任务完成并获取结果。
输出
1 | Result: Hello, CompletableFuture! |
再来一个简单的组合示例:
1 | public class Example { |