在Java中,抽象类、接口和枚举是三种常见的特殊类型。这三种特殊类型在Java中都有其特定的用途,抽象类用于定义具有共同特性的类的抽象概念,接口用于定义规范和实现类之间的解耦,而枚举则用于限制变量的取值范围。
一、 抽象类(Abstract Class)
1.简介
抽象类是不能被实例化的类,它用于定义一组相关的类的共同特性和行为。抽象类可以包含抽象方法(没有实现的方法)和具体方法(有实现的方法)。其他类可以通过继承抽象类来扩展其功能,并实现抽象方法。抽象类一般用于定义抽象的概念和通用的方法,而具体实现则需要由子类完成。
2.抽象类的特点
1)抽象类使用abstract关键字进行修饰;
2)抽象类没有方法体,需要使用封号结尾;
3)若类中包含抽象方法,那么该类就必须使用关键字abstract声明成抽象类,final不能修饰抽象类;
4)抽象类里可以没有抽象方法;
5)一个类继承了抽象类,那么必须重写里面的所有抽象方法,除非该子类也声明为抽象类;
6)抽象类里可以提供构造器,但是不能实例化,没有意义;
7)继承了抽象类的子类,可以用构造器创建实例化对象。
3.抽象类的意义
1)为子类提供一个公共的父类;
2)封装子类中的重复内容,如成员变量和方法;
3)定义抽象方法,子类虽然有不同的实现逻辑,但是该方法的定义是相同的。
代码示例:
public class AbstractDemo01 { public static void main(String[] args) { // 代码编译错误,因为抽象类不能使用new关键字实例化 // Animal animal = new Animal(); Animal cat = new Cat();//向上转型 cat = new Cat("小花", 2); Cat cat1 = (Cat) cat;//直接定义Cat类型的变量,向下转型 cat.eat(); cat1.action(); Animal dog = new Dog(); dog.eat(); // dog是Dog类型,但是dog是Animal类型变量,所以dog不能调用action方法 // 需使用向下转型((Dog)dog).action(); // dog.action(); ((Dog)dog).action(); Dog dog1 = (Dog) dog; dog1.action(); dog1.eat(); Dog dog2 = new Dog("小黑", 2); System.out.println(dog2); dog2.action(); dog2.eat(); } } public abstract class Animal { private String name; private int age; public Animal() { } public Animal(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } public abstract void eat(); public abstract String toString(); } class Dog extends Animal { public Dog() { } public Dog(String name, int age) { super(name, age); } public void action() { System.out.println(super.getName()+"叫~~~~汪汪汪"); } @Override public void eat() { System.out.println(super.getName()+"吃骨头"); } @Override public String toString() { return "Dog{" + "name='" + super.getName() + '\'' + ", age=" + super.getAge() + '}'; } } class Cat extends Animal { public Cat() { } public Cat(String name, int age) { super(name, age); } @Override public void eat() { System.out.println(super.getName()+"吃鱼"); } public void action() { System.out.println(super.getName()+"叫~~~~喵喵喵"); } @Override public String toString() { return "Cat{" + "name='" + super.getName() + '\'' + ", age=" + super.getAge() + '}'; } }
二、接口(Interface)
1.简介
接口是一种完全抽象的类,它只包含方法的声明而没有方法的实现。接口可以看作是一组约定,定义了一组类应该实现的方法。其他类可以通过实现接口来达到多重继承的效果,一个类可以实现多个接口。接口主要用于定义规范和实现类之间的解耦,实现类必须实现接口中定义的所有方法。
2.接口的特点
-
使用interface进行定义
-
可以提供成员变量,默认提供public static final进行修饰
-
可以提供成员方法,默认使用public abtract进行修饰
-
接口中不能存在构造器,接口不能实例化,没有任何意义
抽象性:接口中的方法通常是抽象的,这意味着它们只有声明而没有具体的实现。实现接口的类必须提供这些方法的具体实现。
多态性:接口允许类实现多个接口,每个接口可以包含不同的抽象方法。这提供了高度的多态性,使类能够扮演多种角色。
封装性:接口提供了一种封装抽象行为的方式。通过定义接口,你可以隐藏具体实现细节,只暴露类需要遵守的协议。
解耦性:接口降低了类之间的耦合度。实现接口的类不需要关心其他类的内部实现,它们只需要关注接口中定义的行为。
可维护性:使用接口可以提高代码的可维护性。如果接口的定义发生改变,所有实现它的类都需要相应地更新。
扩展性:接口使得软件系统更容易扩展。你可以创建新的类来实现已有的接口,而不需要修改依赖于这些接口的其他类。
标准化:接口可以作为一种标准化的契约,确保不同组件之间的一致性和互操作性。
设计灵活性:接口支持设计模式,如策略模式、适配器模式等,使得类的设计更加灵活。
3.实现接口
1) 与继承不同,一个类可以实现多个接口。接口间使用逗号分开。
2) 使用关键字implements进行实现,必须实现接口中的所有抽象方法
3) 若一个类中没有全部实现接口中的抽象方法,那么该类需要使用abstract声明成抽象类
4.接口间的继承
1)接口之间可以存在继承关系,即一个接口通过关键字extends可以继承另一个接口。
2)子接口继承了父接口中的所有抽象方法
代码示例:
public interface InplementsDemo { void eat(); void sleep(); }
public interface action extends InplementsDemo { void action(); @Override void eat(); @Override void sleep(); }
public class PersonTest implements InplementsDemo{ private String name; private int age; private String sex; private PersonTest(String name,int age,String sex){ this.name = name ; this.age = age; this.sex = sex; } @Override public String toString() { return "PersonTest{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + '}'; } //必须实现接口中的所有方法 @Override public void eat() { System.out.println(name+"吃东西"); } @Override public void sleep() { System.out.println(name+"睡觉"); } public void action() { System.out.println(name+"干活"); } public static void main(String[] args) { PersonTest personTest = new PersonTest("张三",18,"男"); System.out.println(personTest); personTest.eat(); personTest.sleep(); personTest.action(); } }
5.接口在jdk1.8之后的新特性
在 jdk 1.8 之后,给接口添加了若干个新的特性。
1)default
给接口中的方法,添加默认的实现方式。此时,这个方法,可以在实现类中实现,也可以不在实现类 中实现。如果,实现类没有重写实现这个方法,以接口中的实现为准。
interface Calculate {
int calculate(int a, int b);
public default void testMethod(int a, int b) {
System.out.println("calculate: " + (a + b));
}
}
2)static
⽤static修饰的接口中的方法,表示是静态的方法。此时,这个方法必须要添加一个实现。这个方法,在实现类中不能重写实现。只能通过接口来调用。
interface Calculate {
int calculate(int a, int b);
public static void show() {
System.out.println("Calculate Show");
}
}
6.常用接口
1)序列化接口
2)Comparable接口
3)Comparator接口
7.可不可以认为接口是一种特殊的抽象类?
不可以简单地理解接口就是一个特殊的抽象类,尽管两者有一些相似之处。
接口和抽象类在Java中都是用于定义抽象行为的机制。然而,它们之间有几个关键的区别:
1.声明方式:接口使用interface关键字声明,而抽象类使用abstract class关键字。
2.继承限制:一个类可以实现多个接口,但只能继承一个抽象类(单继承)。这种多实现性使得接口在设计中提供了更大的灵活性,允许类具备多种角色。
3.方法实现:接口中所有的方法默认都是抽象的,没有方法体。这意味着实现接口的类必须提供所有接口方法的具体实现。相比之下,抽象类可以包含抽象方法和已实现的方法。
4.成员变量:接口中所有的成员变量默认都是public static final修饰的,这意味着它们是公开的、静态的且不可更改的。而抽象类可以包含各种类型的成员变量,包括非静态和非最终的。
5.设计哲学:接口更强调行为的定义,它代表了一种“我能做什么”的契约。而抽象类更侧重于提供一个基类,它代表了一种“我是什么”的类型。
6.用途:接口常用于定义模块之间的通信协议,使得各个模块之间可以通过接口进行通信。抽象类则更多地用于代码的复用和扩展性的设计。
7.组成:接口只能包含抽象方法和静态常量(static final)。抽象类可以包含实例变量、抽象方法和已实现的方法。
尽管接口与抽象类有这些显著的差异,但它们在Java的面向对象编程中都有其特定的用途。
接口示例:
interface Flyable { void fly(); // 抽象方法 } class Bird implements Flyable { @Override public void fly() { System.out.println("Bird is flying"); } }
抽象类示例:
abstract class Vehicle { protected String brand; // 实例变量 public abstract void start(); // 抽象方法 public void honk() { // 已实现的方法 System.out.println("Honking the horn"); } } class Car extends Vehicle { @Override public void start() { System.out.println(brand + " car is starting"); } public static void main(String[] args) { Car myCar = new Car(); myCar.brand = "Toyota"; myCar.start(); myCar.honk(); } }
三、枚举(Enumeration)
1.简介
枚举是一种特殊的类,它限制一个变量只能取一组固定的值。枚举类型通过关键字"enum"来定义,其中每个枚举值都是枚举类型的一个实例。枚举常用于定义一组固定的常量,例如表示颜色、星期几等。枚举还支持方法的定义,可以为每个枚举值定义不同的行为。例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。
背景:枚举是在JDK1.5以后引入的。
主要用途是:将一组常量,也可以说成是一组离散值组织起来。
2.枚举的定义
1)自定义类实现枚举
-
类内部创建一组对象,通常使用public f static final关键字共同修饰,对外进行暴露
-
枚举对象名通常全部都会大写,这是常量的命名规范
-
可以提供属性,属性应使用private final共同修饰
-
将构造器私有化
-
属性,可以提供getXXX方法,但是不需要提供setXxx方法,属性是只读的。
-
自定义类实现的枚举是不能使用enum里的方法的。
public class Season { public static final Season SPRING = new Season("春天","春暖花开"); public static final Season SUMMER = new Season("夏天","烈日炎炎"); public static final Season AUTUMN = new Season("秋天","落叶归根"); public static final Season WINTER = new Season("冬天","白雪皑皑"); private final String name; private final String desc; public Season(String name, String desc){ this.desc = desc; this.name = name; } public String getName() { return name; } public String getDesc() { return desc; } @Override public String toString() { return super.toString(); }
2)enum关键字实现枚举
-
使用enum关键字定义一个枚举,默认会继承java.lang.Enum类,而且是一个final类,因此不能再继承其他类
-
必须在枚举类的第一行声明枚举类对象。有多个枚举对象时,使用逗号隔开,最后一个用分号结尾
-
可以提供私有的属性
-
可以提供构造器,必须是私有的,如果构造器有形参,定义对象时必须调用显式调用构造器
-
如果使用无参构造器创建枚举对象,则定义对象时,小括号可以省略
/** * enum 枚举类演示 */ public enum Season { SPRING("春天","春暖花开"), SUMMER("夏天","烈日炎炎"), AUTUMN("秋天","落叶归根"), WINTER("冬天","白雪皑皑"); private String name; private String desc; Season(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } public String getDesc() { return desc; } @Override public String toString() { return "Season{" + "name='" + name + '\'' + ", desc='" + desc + '\'' + '}'; }
重写方法形式
-
1.使用enum关键字后,就不能再继承其它类,因为enum会隐式继承Enum,而Java是单继承机制
-
2.枚举类和普通类一样,可以实现接口
public class EnumDemo01Test {
public static void main(String[] args) {
// 使用枚举的values()方法,可以获取到所有的枚举对象,返回的是一个数组
// 自定义类实现的枚举
EnumDemo01[] values = EnumDemo01.values();
for( EnumDemo01 value : values){
System.out.println(value.toString());
}
// 使用枚举的valueOf()方法,可以根据枚举对象的名称获取到枚举对象
EnumDemo01 va = EnumDemo01.valueOf("LEFT");
System.out.println(va);
String name = va.name();
System.out.println(name);
EnumDemo01.DOWN.ShowInfo();
EnumDemo01.RIGHT.ShowInfo();
}
}
public enum EnumDemo01 {
LEFT{
@Override
public void ShowInfo() {
System.out.println("left");
}
}, RIGHT{
@Override
public void ShowInfo(){
System.out.println("right");
}
}, UP{
@Override
public void ShowInfo(){
System.out.println("up");
}
}, DOWN{
@Override
public void ShowInfo(){
System.out.println("down");
}
};
public abstract void ShowInfo();
}
3.enum的常用方法
说明:使用关键字enum时,会隐式继承Enum类,这样我们就可以使用Enum类相关的方法
方法名 | 详细描述 |
---|---|
toString | 得到当前枚举常量的名称。子类可以通过重写这个方法来使结果更易读 |
name | 返回当前对象名(常量名),子类中不能重写 |
ordinal | 返回当前对象的位置号(编号),默认从0开始 |
values | 返回当前枚举类中所有的常量 |
valueOf | 将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常! |
compareTo | 比较两个枚举常量,比较的就是位置号(编号) |
hashCode | Enum实现了hashCode()来和equals()保持一致,它也是不可变的 |
getDeclaningClass | 得到枚举常量所属枚举类型的Class对象。可以用它来判断两个枚举常量是否属于同一个枚举类型 |
clone | 枚举类型不能被Clone。为了防止子类实现克隆方法Enum实现了一个仅抛出Clone Not Supported Exception异常的不变clone() |
代码示例:
/** * enum 枚举类演示 */ public enum Season { SPRING("春天","春暖花开"), SUMMER("夏天","烈日炎炎"), AUTUMN("秋天","落叶归根"), WINTER("冬天","白雪皑皑"); private String name; private String desc; Season(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } public String getDesc() { return desc; } @Override public String toString() { return "Season{" + "name='" + name + '\'' + ", desc='" + desc + '\'' + '}'; } public static void main(String[] args) { Season season = Season.WINTER; System.out.println(season); // 使用values()方法,可以获取一堆枚举对象。 Season[] spring = season.values(); for (Season s: spring) { System.out.println(s); } // 使用name()返回枚举对象的名称。 System.out.println(season.name()); // 使用ordinal()返回枚举对象的索引。默认从0开始 System.out.println(season.ordinal()); // 使用valueOf()返回枚举对象,根据名称获取枚举对象。 //2.如果找到了就返回,如果没有找到就报错 Season autumn = Season.valueOf("AUTUMN"); System.out.println("autumn"+autumn); Season autumn1 = Season.AUTUMN; System.out.println(autumn == autumn1); // 使用compareTo()比较两个枚举常量,比较的就是编号 //解读 //1.就是把Season.SPRING枚举对象的编号 和 Season.WINTER枚举对象的编号进行比较 //2.就是Season.SPRING的编号 - Season.WINTER的编号 System.out.println(season.SPRING.compareTo(season.WINTER)); } }
四、内部类(inner class)
1)成员内部类
定义在一个类的内部,与这个类的成员(属性、方法)平级,并且没有用static修饰的类;
1.访问权限可以是任意的权限,类似于一个类中的成员。
2.实例化的过程,需要先实例化外部类对象,在使用外部类对象进行内部类的实例化;
3.内部类编译后,也会生成,class文件。格式:外部类$内部类.class
public class InnerDemo01 {
public static void main(String[] args) {
outer outer = new outer();
outer.show();
//创建内部类对象
outer.inner inner = outer.new inner();
System.out.println("-------------------");
inner.show();
}
}
class outer{
public String name = "outer";
public int age = 20;
// 内部类inner
//由于这个类是写在类的内部,所以他就叫内部类,
// 并且该类没有static修饰符,被称为成员内部类
//内部类可以访问外部类的成员变量和方法
public class inner{
inner(){
System.out.println("实例化一个内部类对象inner");
}
public String name = "inner";
public int age2 = 21;
public void show(){
System.out.println("outer age" + age);
System.out.println("inner age" + age2);
}
}
public void show(){
System.out.println("name: " + name);
// 创建内部类对象
inner inner = new inner();
inner.show();
}
}
2)静态内部类
基本内容和成员静态类相似,静态内部类用static修饰。
public class StaticDemo01 {
public static void main(String[] args) {
// outer1 outer = new outer1();
// outer.show();
//创建内部类对象,
// 实例化静态内部类的对象的时候,不需要借助外部对象实例化
// outer1.Inner inner = new outer1.inner();
// 不想上面那样写,就先导包,在用静态内部类实例化
// 不然会报错
Inn inner = new Inn();
System.out.println("-------------------");
inner.show();
}
}
class out {
public String name = "outer";
static class Inn {
public String name = "inner";
public int age = 21;
public void show() {
System.out.println("outer name" + name);
System.out.println("inner name" + name);
}
}
public void show() {
System.out.println("name: " + name);
//创建内部类对象
Inn inner = new Inn();
inner.show();
}
}
3)局部内部类
定义在某一个代码段中的中。
1、没有访问权限修饰符。
2、在当前方法中,直接实例化即可
3、内部类编译后,也会生成.class字节码文件。格式:外部类$序号内部类 .class
public class Program {
public static void main(String[] args) {
int a;
// 写在某一个局部代码段中(例如:方法中)
// 这个类,只能在当前的方法中使用
class Inner {
}
Inner inner = new Inner();
test();
}
static void test() {
class Inner {
}
}
}
4)匿名内部类
没有名字的内部类,内部类,通常是需要配合其他的类或者接口一块使用的
在匿名内部类中,一般情况下不去添加新的成员(属性、方法),因为即便进行了添加,得到的对象也是向 上转型后的对象,不能访问子类中的成员。在匿名内部类中,一般是用来做方法的重写实现的。
匿名内部类也会生成 .class字节码文件,命名格式 : 外部类$序号 .class