Java编程-内部类使用案例与注解使用案例
局部内部类使用案例
局部内部类定义在外部类的方法之中,它可以访问外部类中的所有字段,但是不能够添加类的访问控制符(final可以),其性质相当于一个局部变量,作用域局限在其对应的代码块中,但是外部类要访问内部类必须通过实例化调用方法:
public class OuterClass {
private String content = "Content of Class-OuterClass";
private static String staticContent = "staticContent of Class-OuterClass";
public void powerTest(){
System.out.println("Function of Class-OuterClass");
}
public void checkVisitPower(){
final class InnerClass{
private String content = "Content of Class-InnerClass";
public void showOuterInfo(){
System.out.println(content);
System.out.println(OuterClass.this.content);
System.out.println(staticContent);
powerTest();
}
}
var obj = new InnerClass();
obj.showOuterInfo();
}
public static void main(String[] args) {
var obj = new OuterClass();
obj.checkVisitPower();
}
}
注意:外部其他类不能够访问内部类,因为内部类仅仅相当于本外部类方法中的一个局部变量,外部类与内部类的成员重名时遵循就近原则,如果想要访问外部类中的同名成员需要借助this
,如示例中的:
System.out.println(OuterClass.this.content);
这里需要注意的是,在内部类中的方法获取的this
实质上是它自己的this
,而这一步我们手动指定的实质上是指定了内部类捕获到的外部类的this
,因为本质上它们相当于是嵌套的,内部类的this
只是将外部类的this
进行了掩盖,但仍然保有
匿名内部类使用案例
匿名局部类是一种没有显式名称的局部内部类。它通常用于创建只需要使用一次的类的实例。匿名局部类通常用于实现接口或继承抽象类,可以在创建实例的同时提供实现。在许多框架中是比较常见的,创建匿名内部类时需要注意编译类型与运行类型。
interface AnonymityTestInterface {
void startTestFunction();
}
public class AnonymityTestMain{
public static void main(String[] args) {
// 编译类型为AnonymityTestInterface
// 运行类型为AnonymityTestMain$1 此名称为JVM自动分配
AnonymityTestInterface objectTest = new AnonymityTestInterface() {
@Override
public void startTestFunction() {
System.out.println("This is a AnonymityClass");
}
};
objectTest.startTestFunction();
System.out.println(objectTest.getClass());
}
}
可见我们在上述代码中实际上借助匿名内部类实现了接口,即相当于在局部实现了:
class AnonymityTestMain$1 implements AnonymityTestInterface
对于普通类和抽象类我们可以像如下这样建立匿名类,并且执行重写或增加方法(这里以普通类进行演示):
class BaseClass{
protected int id = 233;
public int getId() {
return id;
}
}
public class AnonymityTestMain{
public static void main(String[] args) {
// 编译类型为BaseClass
// 运行类型为AnonymityTestMain$1
BaseClass objectTest = new BaseClass(){
@Override
public int getId(){
System.out.println("Id of AnonymityClass is : " + id);
this.checkAddFunction();
return id;
}
public void checkAddFunction(){
System.out.println("The checkAddFunction has been started");
}
};
System.out.println(objectTest.getId());
}
}
和前面接口处是类似的,其语法层面相当于:
class AnonymityTestMain$1 extends BaseClass
需要强调的是,在添加了额外方法的匿名类中,通常情况下是无法在外部直接访问新增的方法的,因为其编译类型仍然是父类,即根据继承中的向上转型原则,父类的引用是无法直接访问到子类的新增方法的。
还需要注意的是,这里的自动生成的一个类名
AnonymityTestMain$1
,它反映的实质上是Java中的动态绑定过程,Java默认情况下所有的方法都是动态绑定的,也就是说Java中的方法调用是在运行时确定的,所以getClass
获取到的元数据来自于内部类,而不是外部的BaseClass
匿名内部类的双重特性
匿名内部类是一种没有显式定义类名的内部类,它同时具有类和对象的特性。在Java中,匿名内部类可以看作是一个对象,因为它可以实现接口或继承一个类,并且可以被赋值给一个变量。同时,匿名内部类也可以看作是一个类,因为它可以拥有方法和字段,并且可以在代码中被实例化和使用。
上述代码中我们实质上已经体现出了一个匿名内部类的两大特性:
- 类方面的特性,因为它可以进行重写类方法等操作
- 对象的特性,因为它可以被变量所接收,并在下文根据变量来调用方法
不过我们还应该注意一种写法,我们实质上是可以直接在内部类的后面直接调用方法的:
public class Main{
public static void main(String[] args) {
int objectId = new BaseClass(){
@Override
public int getId(){
System.out.println("Id of AnonymityClass is : " + id);
return id;
}
}.getId();
System.out.println(objectId);
}
}
成员内部类使用案例
public class OuterClass {
private int outerField;
public OuterClass(int outerField) {
this.outerField = outerField;
}
// 成员内部类
public class InnerClass {
public void display() {
System.out.println("OuterField: " + outerField);
}
}
public static void main(String[] args) {
OuterClass outer = new OuterClass(233);
OuterClass.InnerClass inner = outer.new InnerClass();
inner.display();
}
}
成员内部类比局部类更加直接,地位就相当于一个类的成员,它的使用有以下特点:
- 作为类的成员,它可以访问到外部类的所有成员
- 因为是外部类成员,它允许使用访问控制符来控制该成员内部类的可见性
- 外部类必须通过实例化该成员内部类才能够访问内部类成员,并且成员内部类也只能通过外部方法来初始化自身
静态内部类使用案例
public class OuterClass {
private static int outerStaticField = 1;
private int outerField = 2;
// 静态内部类
public static class StaticInnerClass {
public void display() {
// 无法直接访问外部类的非静态成员,需要通过实例访问
OuterClass outer = new OuterClass();
System.out.println("OuterField: " + outer.outerField);
System.out.println("OuterStaticField: " + outerStaticField);
}
}
public static void main(String[] args) {
StaticInnerClass inner = new StaticInnerClass();
inner.display();
}
}
-
静态内部类可以脱离外部类的实例单独存在,与外部类实例无关。
-
静态内部类可以访问外部类的静态成员和方法,但不能直接访问外部类的实例变量和实例方法,需要通过实例化外部类对象来访问。
-
静态内部类的命名空间独立于外部类,可以有相同名称的静态内部类存在,互不影响。
-
静态内部类可以直接通过
new OuterClass.StaticInnerClass()
来实例化,无需先实例化外部类。 -
静态内部类的生命周期可以超出外部类,即使外部类实例被销毁,静态内部类的实例仍然存在。
-
静态内部类可以访问外部类的静态成员,因此可以实现资源共享和代码重用的功能。
自定义枚举体案例
class SeasonEnum {
public static final SeasonEnum SPRING = new SeasonEnum("spring", "Mild and blooming");
public static final SeasonEnum SUMMER = new SeasonEnum("summer", "Hot and sunny");
public static final SeasonEnum AUTUMN = new SeasonEnum("autumn", "Cool and colorful");
public static final SeasonEnum WINTER = new SeasonEnum("winter", "Cold and snowy");
private final String season;
private final String describe;
private SeasonEnum(String seasonName, String aDescribe){
season = seasonName;
describe = aDescribe;
}
@Override
public String toString() {
return "The " + season + " is " + describe;
}
}
实质上是借助静态成员实现的枚举体
标准枚举体案例
标准枚举体实质上就是对上述的定义进行了简化,并且需要强调的是:
在枚举体中,构造函数默认是私有的,因此在枚举体中显式使用 private
修饰符来声明构造函数是冗余的。这是因为枚举体的设计初衷就是为了限制创建枚举实例的方式,只能通过枚举常量来创建实例,而不能通过其他方式。
enum
关键字声明的类隐式继承自Java的Enum
类(不可再继承其他类),并自动成为一个final类。在书写时,作为枚举的字段必须放在最前面,每项用,
分割,最后一项以;
收尾
但是值得注意的是enum类虽然不能够再继承其他类,但是却可以实现接口
enum SeasonEnumStandard{
// 注意 作为枚举内容时将会调用类的构造器
SPRING ("spring", "Mild and blooming"),
SUMMER ("summer", "Hot and sunny"),
AUTUMN ("autumn", "Cool and colorful"),
WINTER ("winter", "Cold and snowy");
private final String season;
private final String describe;
SeasonEnumStandard(String seasonName, String aDescribe){
season = seasonName;
describe = aDescribe;
}
@Override
public String toString() {
return "The " + season + " is " + describe;
}
}
在构造函数是无参数时,我们可以直接省略
()
其他枚举体中的方法使用案例:
public class EnumStandard {
public static void main(String[] args) {
// 获取枚举值的名字
System.out.println(SeasonEnumStandard.SPRING.name());
// 获取枚举值在序列中的定义数 0为起始值
System.out.println(SeasonEnumStandard.AUTUMN.ordinal());
// 获取枚举数序列中所有枚举值的内容
for (var value: SeasonEnumStandard.values()){
System.out.println(value);
}
System.out.print("\n");
// 将String转换为枚举值 不在枚举序列中的值将会抛出异常
System.out.println(SeasonEnumStandard.valueOf("SUMMER"));
// 将两个枚举值进行序列比较 返回当前枚举值与目标枚举值的差值
System.out.println(SeasonEnumStandard.SPRING.compareTo(SeasonEnumStandard.WINTER));
}
}
注解作用与基本注解
在Java中,注解(Annotation)是一种用来为程序元素(类、方法、变量等)添加元数据(metadata)的形式化标签。它们提供了有关程序的额外信息,可以用于编译时进行检测、运行时处理或者在编译和部署阶段生成代码。
在Java SE(Standard Edition)中,注解可以用于为代码提供额外的信息,例如告诉编译器如何处理类、方法、变量等。常见的注解包括 @Override
、@Deprecated
和 @SuppressWarnings
等。
在Java EE(Enterprise Edition)中,注解扮演着更为重要的角色,它们被广泛用于标记和配置应用程序中的各种组件,如Servlet、EJB、JPA实体等。这些注解可以简化开发人员对应用程序的配置和管理,提高了代码的可读性和可维护性。
Java SE中的注解主要用于提供编译时的辅助信息,而Java EE中的注解则更多地用于配置和管理应用程序的各个组件。
Override注解
@Override
:用于标识一个方法覆盖了父类的方法,且只能用于方法。使用此注解后,编译器将会进行校验,以确定是否真的进行了重写,从而避免潜在错误。
class Parent {
public void print() {
System.out.println("Parent");
}
}
class Child extends Parent {
@Override
public void print() {
System.out.println("Child");
}
}
@interface注解类
注意在@Override的源码中出现的@interface:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
在这里,@interface
是用来声明一个注解的关键字,表示我们正在定义一个新的注解类。@Target
表示这个注解可以用在方法上,@Retention
表示它的保留策略(Retention Policy)被设置为 SOURCE,这意味着编译器会在编译时丢弃这个注解,它不会被包含在编译后的 class 文件中。
Deprecated注解
@Deprecated
:用于标识某个程序元素(类、方法等)已经过时,不推荐使用。
public class DeprecatedExample {
@Deprecated
public void oldMethod() {
System.out.println("This method is deprecated.");
}
}
@Deprecated
class DeprecatedClass {
public void deprecatedMethod() {
System.out.println("This method is deprecated.");
}
}
- Deprecated注解源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
/**
* Returns the version in which the annotated element became deprecated.
* The version string is in the same format and namespace as the value of
* the {@code @since} javadoc tag. The default value is the empty
* string.
*
* @return the version string
* @since 9
*/
String since() default "";
/**
* Indicates whether the annotated element is subject to removal in a
* future version. The default value is {@code false}.
*
* @return whether the element is subject to removal
* @since 9
*/
boolean forRemoval() default false;
}
-
@Documented
: 这是一个元注解,用于指示注解应该包含在生成的文档中。当一个注解被标记为@Documented
时,它的信息会被包含在 Javadoc 文档中,使得注解的信息可以被文档化。 -
@Retention(RetentionPolicy.RUNTIME)
用于指定注解的保留策略。在这里,@Deprecated
注解被指定为在运行时保留,这意味着可以通过反射等机制在运行时获取到这个注解的信息。 -
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
用于指定注解可以应用的目标元素类型。在这里,@Deprecated
注解可以应用在构造方法、字段、局部变量、方法、包、模块、参数和类型上。 -
public @interface Deprecated { ... }
定义了@Deprecated
注解的具体内容。在这个注解中,包含了两个成员方法since()
和forRemoval()
,分别用于指定元素被标记为过时的版本和标记元素是否将来会被移除。这些信息可以帮助开发者了解过时元素的相关情况。
SuppressWarning注解
@SuppressWarnings
:用于抑制编译器警告。
public class SuppressWarningsExample {
@SuppressWarnings({"unchecked", "rawtypes"})
public static void main(String[] args) {
List list = new ArrayList();
list.add("Hello");
list.add("World");
// 使用了原始类型的 List,会导致 "unchecked" 警告
// 使用了未检查的转换,会导致 "rawtypes" 警告
// 使用 @SuppressWarnings({"unchecked", "rawtypes"}) 可以抑制这两种警告
}
}
@SuppressWarnings
注解可以用来抑制特定类型的警告,其作用范围可以是单个变量、方法、类、或者整个方法体。具体来说,@SuppressWarnings
的作用范围取决于它所放置的位置:
单个变量或表达式:可以将 @SuppressWarnings
注解直接放置在变量或表达式之前,例如:
@SuppressWarnings("unchecked")
List<String> list = new ArrayList();
方法或构造函数:可以将 @SuppressWarnings
注解放置在方法或构造函数的声明上,例如:
@SuppressWarnings("unchecked")
public void myMethod() {
List<String> list = new ArrayList();
}
类:可以将 @SuppressWarnings
注解放置在类的声明上,以抑制类中所有方法的警告,例如:
@SuppressWarnings("unchecked")
public class MyClass {
// 类的所有方法都将抑制 "unchecked" 警告
}
方法体:可以将 @SuppressWarnings
注解放置在方法体内的局部变量或表达式之前,例如:
public void myMethod() {
@SuppressWarnings("unchecked")
List<String> list = new ArrayList();
}
需要注意的是,@SuppressWarnings
注解只对编译器有效,不会影响程序运行时的行为。使用时应尽量精确指定需要抑制的警告类型,避免过度使用导致隐藏潜在问题。
- SuppressWarning注解源码
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}
- SuppressWarning注解支持的标记列表
警告类型 | 描述 |
---|---|
“all” | 抑制所有类型的警告 |
“rawtypes” | 抑制使用原始类型(raw types)的警告 |
“unchecked” | 抑制未经检查的转换警告 |
“deprecation” | 抑制使用过时 API 的警告 |
“removal” | 抑制对已删除 API 的警告 |
“serial” | 抑制没有序列化 ID 的类实现 Serializable 接口的警告 |
“finally” | 抑制没有返回或抛出异常的 finally 块的警告 |
“fallthrough” | 抑制 switch 语句中缺少 break 的警告 |
“path” | 抑制在类路径中找不到的类、包或资源的警告 |
“unchecked” | 抑制执行了未检查操作的警告(例如使用原始类型集合) |
“rawtypes”,“unchecked” | 抑制所有与未检查操作相关的警告 |
“static-access” | 抑制对静态成员的访问警告 |
“unused” | 抑制未使用的代码的警告 |
“unused”,“rawtypes” | 抑制未使用的代码和使用原始类型的警告 |
“unused”,“unchecked” | 抑制未使用的代码和未经检查的转换的警告 |
元注解概念
Java 的元注解(meta-annotation)是一种用于注解其他注解的注解。元注解可以用来为注解提供更多的元数据信息,例如指定注解的保留策略、目标元素类型等。Java 中有几种常见的元注解,包括 @Retention
、@Target
、@Documented
和 @Inherited
等。
元注解 | 功能描述 |
---|---|
@Retention | 指定注解的保留策略,即注解在什么时候生效。可选值有 RetentionPolicy.SOURCE(源代码级别)、RetentionPolicy.CLASS(类文件级别)和 RetentionPolicy.RUNTIME(运行时级别)。 |
@Target | 指定注解可以应用的目标元素类型,如类、方法、字段等。 |
@Documented | 指定注解是否包含在 Javadoc 中。如果一个注解被 @Documented 修饰,那么它将会出现在生成的文档中。 |
@Inherited | 指定注解是否可被继承,默认情况下注解不会被继承。如果一个注解被 @Inherited 修饰,那么它将会被子类继承。 |