类的介绍
(1) 类的定义
- 在 Java 编程语言中,类是创建对象的模板。它是面向对象编程(OOP)的核心概念之一,用于封装数据(属性或者字段)和对这些数据执行的操作(方法)。
- 类定义了对象的类型以及对象可以执行的操作,通过类,我们可以创建类的实例,也就是对象。
- 按照不同的特性与用途,将类划分为:抽象类、枚举类、内部类的形式进行一一的介绍。
(2) 如何声明类
- 在 Java 中,类是通过 class 关键字来定义的,后跟类的名称。类名通常以大写字母开头,按照驼峰命名法进行命名。类中可以包含字段(Field)、方法(Method)和构造器(Constructor)等成员。
- 代码样例
[访问修饰符] class 类名 {
// 成员变量(属性)
[修饰符] 数据类型 变量名;
// 构造函数
[修饰符] 类名([参数列表]) {
// 初始化代码
}
// 成员方法(行为)
[修饰符] 返回类型 方法名([参数列表]) {
// 方法体
}
}
// 类的定义
public class Person {
// 成员变量
private String name;
private int age;
// 默认构造函数
public Person() {
System.out.println("默认构造函数被调用");
}
// 有参构造函数
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("有参构造函数被调用");
}
// 成员方法
public void displayInfo() {
System.out.println("Name: " + name + ", Age: " + age);
}
}
(3) 访问修饰符
- 访问修饰符决定了类、变量、方法和构造器等的访问级别。
- Java 主要有四种访问修饰符:private,default,protected 和 public
Private
- private 是最严格的访问级别,当一个类成员(无论是字段、方法还是内部类)被声明为 private 时,他只能在声明它的类的内部被访问。
- 换而言之,处理包含它的类本身,其他类都无法直接访问该成员。
Default
- 当一个类成员没有使用任何访问修饰符时,它被认为具有包访问权限。
- 具有包访问全权限的成员可以被声明它的类以及同一包中的所有类访问。
- 即:如果一个类和另一个类在同一包中,那么他们可以相互访问彼此的默认成员。
Protected
- protected 访问修饰符允许同一包中的类或子类访问特定的成员。
- 如果两个类位于同一个包中,并且其中一个类声明了一个 protected 成员,那么这两个类都可以访问该成员。
- 另外,如果有一个类是另一个类的子类(即使它们位于不同的包中),那么子类也可以访问父类的 protected 成员。
Public
- public 访问修饰符是最宽松的访问级别。
- 它允许任何其他类访问一个特定的成员,无论该类与声明该成员的类是否位于同一包中,或者是否存在任何继承关系。
抽象类
(1) 抽象类的定义
- 抽象类是一种特殊的类,它不能被实例化,只能被继承。
- 抽象类中可以包含抽象方法和非抽象方法。
- 抽象方法是没有实现的方法,必须在子类中被实现。
- 抽象类的存在主要是为了让子类共享一些方法或属性,同时又要求子类必须实现某些方法。
- 抽象类通常用于定义一些基本的操作,而具体的实现则由子类完成。
(2) 抽象类的特点
- 抽象类不能通过 new 关键字实例化对象,如果想要使用这个抽象类,则抽象类必须被继承。
- 当我们定义了一个抽象类之后,在其中定义一些公共的抽象方法,此时如果有类去继承这个抽象类,则会强制子类去实现抽象类的方法;
- 在实际开发中,抽象类常常被定义为统一模板,提供公共的抽象方法,不同的子类,通过继承抽象类,对公共的抽象方法进行不同的实现。
(3) 具体的代码实现
- 定义抽象类和抽象方法
package com.service;
public abstract class AnimalService {
//在抽象类中定义公共的抽象方法
//在具体的实现类中进行不同的实现
public abstract void speak();
}
- 定义子类,继承抽象类,实现抽象方法
package com.service.impl;
import com.service.AnimalService;
public class CatServiceImpl extends AnimalService {
public void speak() {
System._out_.println("喵喵");
}
}
package com.service.impl;
import com.service.AnimalService;
public class DogServiceImpl extends AnimalService {
public void speak() {
System._out_.println("汪汪");
}
}
- 实际调用
package com.web;
import com.service.impl.CatServiceImpl;
import com.service.impl.DogServiceImpl;
public class App {
public static void main(String[] args) {
CatServiceImpl catService = new CatServiceImpl();
catService.speak();
DogServiceImpl dogService = new DogServiceImpl();
dogService.speak();
}
}
枚举类
(1) 枚举类的定义
- 枚举类是 Java5 引入的一种类型,在 Java5 之前,Java 并没有内置的枚举类型,只能通过自定义类来实现类似的枚举类的功能
- 枚举类是一种特殊的数据结构,常用于定义一组固定命名的常量。枚举类在 Java 中使用 enum 定义。
(2) 枚举类的实现
- 当我们使用 enum 关键字,定义了一个枚举类之后,通过反编译工具可以看到
package com.service.Enum;
public enum ColorEnum {
_RED_,
_GREEN_,
_BLUR_;
}
- 通过反编译枚举类可以看到,枚举类是 public 和 final 修饰的,因此枚举类他不能够像其他类一样被继承;
- 枚举类都继承自 java/lang/Enum;
- 此外,对于我们定义的三个实例变量,通过反编译可以看到,三个变量均被 public static final 修饰;
- 并且在枚举类内部,还定义了静态的 values 方法,它会返回一个包含所有枚举实例的 Color[]结构的列表,以及 valuesOf(String input)方法,它通过传入一个枚举常量字符串,返回对应的 Color 实例。
(3) 枚举类中常用的方法
- 在枚举类中比较特有的方法,应该是 valuesOf() 方法和 **ordinal() **方法。
- vaulesOf() 方法返回执行名称的枚举常量;
- ordinal() 方法返回的是常量对应的位置编号;
- compareTo() 比较两个枚举常量,比较的是常量的位置编号;
(4) 枚举类的实际应用
- 在实际项目中,枚举类中常定义两个属性,状态码和描述信息,之后通过状态码,返回具体的描述信息.
@Getter
@AllArgsConstructor
public enum ErrorEnums {
_SYS_ERROR_(500, "服务异常"),
_INVALID_REQUEST_(1001, "非法访问");
private final Integer code;
private final String message;
public static ErrorEnums valueOf(Integer code) {
return Arrays._stream_(_values_())
.filter(item -> Objects._equals_(code, item.getCode()))
.findAny()
.orElse(ErrorEnums._SYS_ERROR_);
}
}
- 在枚举类中自定义方法
- 在 Java 中,我们可以在枚举类中定义自己的方法,就像在任何其他类中一样;
package com.service.Enum;
public enum ColorEnum {
_RED_,
_GREEN_,
_BLACK_,
_BLUR_;
public boolean isDark(){
if (this == _RED _|| this == _BLACK_){
return true;
}else {
return false;
}
}
}
(5) 枚举类的特点
- 类型安全:在多线程环境下,同样也是线程安全的;因为枚举是隐式静态 final 的,通过反编译可以得出,这就意味着它们再编译时就被初始化了,并且在运行时是不可变对象。由于它们在类加载时就被创建了,所以在多线程的环境下时线程安全的。
- 自动序列化:从源码中可以看出,枚举类的父类 Java/lang/Enum 已经实现了序列化接口,所以,枚举类无需再实现序列化;并且如果在单例模式下,如果实现了 Serializable 接口,在进行序列化和反序列化时可能会创建新的对象,而枚举类不会出现这种情况。
内部类
静态内部类
(1) 静态内部类的定义
- 在 Java 中,静态内部类是一种特殊的类定义方式,它位于另一个类的内部,但与外部类的实例没有直接关联。
- 静态内部类通过使用 static 关键字来定义,从而拥有了与普通类相似的行为,但又保留了一些特殊的访问特性。
(2) 静态内部类的特点
- 访问权限:静态内部类可以访问外部类的静态成员变量,但是不能直接访问外部类的非静态成员。
- 类加载与初始化:静态内部类的加载与初始化遵循 Java 的类加载机制。与外部类不同,静态内部类不会被外部类的加载和初始化触发,只有在被明确引用时(如创建其实例或者访问其静态成员)才会被加载和初始化。
- 类的生命周期:静态内部类的生命周期与其外部类的实例无关,他是完全独立的,因此,创建静态内部类的实例不需要外部类的实例。
- 编译后的类文件:同编译成员内部类一样,静态内部类会生成单独的 .class 文件,文件名通常为 外部类名 $ 静态内部类名.class
(3) 静态内部类的使用场景
- 实现单例模式:当使用静态内部类实现单例模式时,它可以保证单例的懒加载和线程安全。
- Builder 模式:静态内部类常用于实现 Builder 模式,简化对象的创建过程。
- 工具类:当需要一个仅包含静态方法的类时,可以将这个类定义为静态内部类,这样可以更好的将其与包含它的外部类相关联
(4) 静态内部类的使用示例
- 静态内部类的创建和使用
package com.demo.Static;
public class OuterClass {
private static String _staticField _= "静态字段";
private String instanceField = "实例字段";
public static class StaticInnerClass {
public void printStaticField() {
System._out_.println(_staticField_); // 可以访问外部类的静态字段
// System.out.println(instanceField); // 编译错误,不能访问外部类的实例字段
}
}
public static void main(String[] args) {
OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
inner.printStaticField(); // 输出:静态字段
}
}
- 静态内部类与 Builder 模式
package com.demo.Static;
public class Person {
private String name;
private int age;
private String address;
private Person(Builder builder) {
this.name = builder.name;
this.age = builder.age;
this.address = builder.address;
}
public static class Builder {
private String name;
private int age;
private String address;
public Builder setName(String name) {
this.name = name;
return this;
}
public Builder setAge(int age) {
this.age = age;
return this;
}
public Builder setAddress(String address) {
this.address = address;
return this;
}
public Person build() {
return new Person(this);
}
}
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", address='" + address + "'}";
}
public static void main(String[] args) {
// 使用Builder模式创建Person对象
Person person = new Person.Builder()
.setName("小小")
.setAge(25)
.setAddress("Bei Jing")
.build();
System._out_.println(person);
}
}
成员内部类
(1) 成员内部类的定义
- 成员内部类是最常见的内部类型,它作为外部类的一个成员存在;
- 成员内部类不能定义任务静态成员,因为它依赖于外部类的实例;
- 要创建成员内部类的实例,必须需要外部类的实例。
(2) 成员内部类的特点
-
访问外部类成员:
- 成员内部类可以直接访问外部类的所有成员,包括私有成员;
- 这意味着内部类可以访问外部类的私有数据和受保护的功能,这有助于实现更加紧密的封装和更复杂的逻辑。
-
生命周期依赖:
- 成员内部类的实例与外部类的实例之间存在依赖关系,创建一个成员内部类实例时,必须先有一个外部类的实例存在;
- 不能独立创建一个成员内部类的对象,除非它被嵌套在外部类的一个方法或者代码块中,且该方法或者代码块正在被外部类的一个实例调用;
-
不能有静态成员:
- 成员内部类不能有静态字段,静态方法,或者静态初始块,因为它本身依赖于外部类的实例。
-
编译后的类文件:
- 编译成员内部类时,编译器会生成两个独立的类文件,一个外部类.class 文件,另外一个是内部类.class 文件,其命名格式通常是 外部类 $ 内部类.class
- 编译成员内部类时,编译器会生成两个独立的类文件,一个外部类.class 文件,另外一个是内部类.class 文件,其命名格式通常是 外部类 $ 内部类.class
(3) 成员内部类的使用示例
package com.demo.Static;
// 外部类
public class MembersClass {
private String province;
// 成员内部类
public class InClass{
public void showProvince(String province){
System._out_.println("my province is:"+province);
}
}
public static void main(String[] args) {
// 获取成员内部类的实例,需要依靠外部类
InClass inClass = new MembersClass().new InClass();
inClass.showProvince("BeiJing");
}
}
局部内部类
(1) 局部内部类的定义
- 局部内部类是一种特殊的内部类,它被定义在一个方法,代码块或者其他局部作用域内;
- 这种设计允许将类的定义限制在非常特定的范围中,只在需要创建的地方创建和使用;
(2) 局部内部类的特点
-
访问权限与可见性:
- 局部内部类对外部是不可见的,即不能从外部类的其他方法或者外部其他类中直接访问。
- 他只能在其定义的局部作用域内被创建和使用。由于其局限性,局部内部类没有访问修饰符。
-
访问外部资源:
- 尽管作用域有限,局部内部类却可以访问其所在作用域内的所有局部变量,前提是这些变量必须是 final。
- 此外,局部内部类还可以访问外部类的所有成员,这一点与非局部内部类一致
-
生命周期和垃圾回收:
- 局部内部类的生命周期与其依赖的局部变量密切相关,如果一个局部内部类对象引用了其外部方法的局部变量,那么只有这个局部内部类对象是可达的,所引用的局部变量不会被垃圾回收。
(3) 局部内部类的使用场景
- 临时性的类需求:当某个功能的逻辑仅在特定方法中需要,并且该功能需要封装成类,但是又不需要在整个类层次结构中公开,可以使用局部内部类。
- 事件处理回调:在实现事件驱动模型时,有时需要为特定事件创建一个匿名或者局部内部类的监听器,这些监听器只在注册事件时创建,并在事件触发时调用其方法。
- 适配器模式:在需要快速创建一个满足特定接口的适配器对象时,局部内部类可以提供简洁的实现方式。
(4) 局部内部类的使用示例
package com.demo;
public class LocalInnerClass {
private String sharedField = "外部类定义";
public void processData(){
final String importantValue = "外部类方法的变量";
// 定义局部内部类
class DataProcessor{
void doProcessor(){
System._out_.println("外部类方法中的局部变量"+importantValue);
// 可以访问外部类成员
System._out_.println("外部类成员变量"+LocalInnerClass.this.sharedField);
}
}
// 创建并使用局部内部类实例
DataProcessor processor = new DataProcessor();
processor.doProcessor();
}
public static void main(String[] args) {
// 调用非静态方法,间接调用局部内部类
LocalInnerClass method = new LocalInnerClass();
method.processData();
}
}
匿名内部类
(1) 匿名内部类的定义
- 匿名内部类是 Java 中的一种特殊的内部类,它没有名字,既在定义时不需要使用 class 关键字命名;
- 匿名内部类主要用于简化代码,特别是在只需要使用一次某个类的实例,且该类的实现相对简单的情况下;
- 匿名内部类通常用于实现接口或者继承抽象类,并在创建该类实例的同时定义其实现;
(2) 匿名内部类的特点
- 无名称:匿名内部类没有名称,定义时直接省略了类名,因为,无法在其他地方再次引用或者创建该类的实例
- 继承或实现:匿名内部类必须继承一个父类,或者实现一个接口。这是匿名内部类存在的主要目的,即在创建对象时同时提供具体的实现;
- 一次性使用:由于匿名内部类没有名称,所以它通常伴随着 new 关键字一起使用,创建并初始化一个该类的实例,这种”创建即使用“的特性使得匿名内部类适用于只需要一次性使用的场景。
(3) 匿名内部类的使用示例
- 使用父类
package com.demo;
public class AnonymousOuterClass {
class InnerClass{
private String innerField = "field in InnerClass";
public void getField(){
System._out_.println(innerField);
}
}
public void anonymousInnerMethod(){
// 声明并实例化匿名内部类
InnerClass innerClass = new InnerClass(){
private String innerField = "a";
@Override
public void getField(){
// 输出匿名内部类中的成员变量 innerField
System._out_.println(innerField);
// 输出父类的成员变量 innerField
super.getField();
}
};
innerClass.getField();
}
}
class AnonymousOuterClassTest{
public static void main(String[] args) {
AnonymousOuterClass anonymousOuterClass = new AnonymousOuterClass();
anonymousOuterClass.anonymousInnerMethod();
}
}
- 使用接口
package com.demo.Static;
public class AnonymousOuterClass {
interface InnerInterface{
void getField();
}
public void anonymousInnerInterfaceMethod(){
InnerInterface innerInterface = new InnerInterface() {
private String innerField = "field inner interface";
@Override
public void getField() {
// 输出匿名内部类中的成员变量 innerField
System._out_.println(innerField);
}
};
innerInterface.getField();
}
}
class AnonymousOuterClassTest{
public static void main(String[] args) {
AnonymousOuterClass anonymousOuterClass = new AnonymousOuterClass();
anonymousOuterClass.anonymousInnerInterfaceMethod();
}
}