目录
本章介绍
通过上一篇文章,笔者已经带大家对包有了一个清晰的认知,接下来就让笔者带大家了解下Java编
程一个源代码的基本组成结构-类 - 抽象类 - 接口 - 枚举 - 数组,笔者认为任何一门编程语言学习之
前,有必要了解源代码的基本组成成分,才能更快更好的入门。
一、类
1. 基本介绍
在我们Java编程中,Java类常用于创建单个对象,下文笔者将采用示例的方式讲述一个类的
定义:
/**
* @program: 01JavaSE
* @ClassName ClassDemo01
* @description:
* @author: CodingW
* @create: 2025-01-18-14-37
* @Version 1.0
**/
public class ClassDemo01 {
String name;
int size;
String color;
void run() {
}
void stop() {
}
}
紧接着会告诉大家,一个类当中,可以包含什么样的成员。
类可以包含以下任何变量类型。
局部变量 - 在方法,构造函数或块中定义的变量称为局部变量。 变量将在方法中声明和初始化,
并且在方法完成时将销毁变量。
实例变量 - 实例变量是类中的变量,但在方法之外。 在实例化类时初始化这些变量。 可以从类的
任何方法,构造函数或块内部访问实例变量。
类变量 - 类变量是使用static关键字修饰,它是在类中的方法之外声明的变量。
一个类可以有任意数量的方法。 在上面的例子中,run(),stop()是类的方法。
以下是在学习Java语言类和对象时,需要了解的一些重要主题内容。
构造函数
在讨论类时,最重要的子主题之一是构造函数。
每个类都有一个构造函数。
如果没有为类显式编写构造函数,
Java编译器会自动为该类构建一个默认构造函数。
每次创建新对象时,都将至少调用一个构造函数。
构造函数的主要规则是它与类具有相同的名称。
一个类可以有多个构造函数。
以下是构造函数的示例
/**
* @program: 01JavaSE
* @ClassName ClassDemo02
* @description:
* @author: CodingW
* @create: 2025-01-18-14-38
* @Version 1.0
**/
public class ClassDemo02 {
// 无参数构造函数
public ClassDemo02() {
}
// 有参数构造函数
public ClassDemo02(String name) {
// 此构造函数有一个参数:name。
}
}
2. 类模板
/**
* @program: ${PROJECT_NAME}
* @ClassName ${NAME}
* @description:
* @author: CodingW
* @create: ${YEAR}-${MONTH}-${DAY}-${HOUR}-${MINUTE}
* @Version 1.0
**/
public class ${NAME} {
}
二、抽象类
1. 简介
我们知道,类用来模拟现实事物。一个类可以模拟一类事物,而某个类的一个实例化对象可以模拟
某个属于该类的具体的事物。
类中描绘了该类所有对象共同的特性,当一个类中给出的信息足够全面时,我们就可以实例化该
类;
比方说,在Dog类中定义了name,age,fur_color,sex等属性,以及habit,eat等行为时,我
们就可以创建一个Dog类对象,来模拟某个具体的Dog,比如你家的宠物狗,或者是神犬小七等。
但是,当一个类中给出的信息不够全面时,(比方说有无法确定的行为),它给出的信息不足以描
绘出一个具体的对象,这时我们往往不会实例化该类,这种类就是抽象类。打个比方,对于Animal
类,是,所有的动物都有吃喝的行为,定义eat方法可以描述动物“吃”这一行为,但是每种动物吃的
都不一样,因此一个eat方法并不能准确描述吃什么,怎么吃。这时Animal给出的信息就不足够描
述一个对象,我们就不能去实例化Animal类。
在Java中,我们通过在类前添加关键字abstract(抽象的)来定义抽象类。
如下图所示 :
/**
* @program: 01JavaSE
* @ClassName Animal
* @description:
* @author: CodingW
* @create: 2025-01-18-14-39
* @Version 1.0
**/
public class Animal {
//Animal类此时就是一个抽象类。
}
class Dog extends Animal {
//Dog类继承了Animal类,是Animal类的子类。
}
2. 抽象方法
2.1. 简介
我们将“只有方法声明,没有方法体”的一类方法统称为抽象方法,抽象方法用关键字abstract修
饰。需要注意的是,如果一个方法已经确定是抽象类,那么它绝不能再有方法体,即不能出现大括
号,而是只需要在()后面添加一个分号即可,否则IDEA会提示报错信息,如下图所示:
还要注意一点,如果某个类中已经出现了抽象方法,那这个类必须定义成抽象类,否则会报错,
也就是说,拥有抽象方法的类一定是抽象类;但是抽象类不一定有抽象方法。
2.2. 应用
当父类需要定义一个方法,却不能明确该方法的具体实现细节时,可以将该方法定义为abstract,
具体实现细节延迟到子类。(让子类重写这个方法)
就比如我们刚才说的——Animal类中的eat() 方法,我们可以先将其定义为抽象类,然后在子类
中,比如说Dog类中重写eat() 方法,给出对Dog类对象“吃”这一行为的一些具体描述。
以Animal类,Dog类和Test类为例,代码如下 :
package com.wclass.d2_abstract_class.demo03;
/**
* @program: JavaSE
* @ClassName Animal
* @description:
* @author: W哥
* @create: 2024-12-21-22-35
* @Version 1.0
**/
public abstract class Animal { // 父类
//将Animal类中的eat() 方法定义为抽象类,具体实现延迟到子类。
public abstract void eat();
}
class Dog extends Animal { // 子类
// 子类重写父类的抽象方法,也称为子类实现了该抽象方法。
public void eat() {
System.out.println("狗是杂食性动物,喜食肉类,喂养时应该以动物蛋白为主,素食为辅。");
}
}
class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
}
}
运行结果 :
2.3. 特点
① 若父类中定义了一个抽象方法,要求其所有非抽象子类都必须重写该抽象方法。
否则IDEA会报错,如下图所示 :
package com.wclass.d2_abstract_class.demo04;
/**
* @program: JavaSE
* @ClassName Animal
* @description:
* @author: W哥
* @create: 2024-12-21-22-39
* @Version 1.0
**/
public abstract class Animal {
// 若父类中定义了一个抽象方法,要求其所有非抽象子类都必须重写该抽象方法。
public abstract void eat();
}
//class Dog extends Animal {
//// // 子类重写父类的抽象方法,也称为子类实现了该抽象方法。
//// public void eat() {
//// System.out.println("狗是杂食性动物,喜食肉类,喂养时应该以动物蛋白为主,素食为辅。");
//// }
//}
② 前面我们说了,抽象方法用abstract关键字修饰。这里再补充一点——抽象方法不能再使用
private,final 或者static关键字来修饰,即abstract不能与private,final或static共同出现,这是因
为定义抽象方法的目的就是想将方法的具体实现延迟到子类,最终是要被子类重写的,而private,
final,static这几个关键字都和“方法重写”的目的背道而驰。
如果你固执己见,非要让abstract和这几个关键字一同出现,IDEA也是毫不客气,直接报错,如下
图所示 :
package com.wclass.d2_abstract_class.demo05;
/**
* @program: JavaSE
* @ClassName Animal
* @description:
* @author: W哥
* @create: 2024-12-21-22-43
* @Version 1.0
**/
public abstract class Animal {
// abstract不能与private,final或static共同出现
// private abstract void eat();
// public abstract final void eat();
// public abstract static void eat();
}
3. 抽象类特点
3.1. 关于abstract关键字
abstract关键字只能用于修饰类和方法,用于声明抽象类和抽象方法。
其中,抽象类必须使用abstract关键字修饰。
声明时格式如下 :
访问权限修饰符 abstract class 类名{ // }
访问权限修饰符 abstract 返回值类型 方法名(形参列表);
举个例子,如下 :
//抽象类
public abstract class Animal {
//抽象方法
public abstract void eat(); //抽象方法最后加一个分号即可,不可以有大括号。
}
3.2. 抽象类不能被实例化,只能创建其子类对象
即,我们不能创建抽象类对象(这里的对象指的是堆空间中真正的对象,即不能“new 抽象
类”),原因我们在开篇抽象类的概述中也提到了,这里不再赘述。
如果你头铁,非要创建抽象类对象,IDEA也不是吃素的,直接报错,如下图所示 :
当然,如果抽象类的子类没有用abstract关键字修饰,那么我们可以创建其子类对象,如下图所示 :
3.3. 抽象类子类的两个选择
如果某个类继承了一个抽象类,那么这个类有两个选择——要么实现父类所有的抽象方法,要么子
类本身也定义成抽象类。
当然,肯定也不会是瞎jb想定义成抽象类就定义成抽象类的😂,要满足我们我们上面所说的定义
抽象类的条件——类中提供的信息不足以描述一个对象,或者类中有无法确定的行为需要延迟到子
类实现。
还是给大家举个栗子。up现在在character包下创建一个Food类,将Food类定义为抽象类,并定义
showNutrition()抽象方法,该方法将来要打印出食物的主要营养,具体实现延迟到子类;然后分别
创建子类Meat类和Fruit类去继承Food类,我们在Meat类中重写Food类的showNutrition() 方法,使
其打印出肉类的主要营养价值;同时,另一个子类Fruit类不去实现showNutrition() 方法,而是将其
定义为抽象类。最后,以Test类为测试类,在测试类中创建子类对象并调用showNutrition() 方法。
Food类,Meat类,Fruit类,Test类代码如下 :
/**
* @program: 01JavaSE
* @ClassName Food
* @description:
* @author: CodingW
* @create: 2025-01-18-14-41
* @Version 1.0
**/
public abstract class Food { /** 父类 : Food类 */
//记住,抽象方法没有方法体
public abstract void showNutrition();
}
class Meat extends Food { /** 子类 : Meat类 */
@Override
public void showNutrition() {
System.out.println("肉类是蛋白质、脂肪、维生素B2、维生素B1、烟酸和铁的重要来源。");
}
}
abstract class Fruit extends Food { /** 子类 : Fruit类 */
}
class Test { /** 测试类 : Test类 */
public static void main(String[] args) {
Meat meat = new Meat();
meat.showNutrition();
}
}
运行结果 :
我们也可以再定义一个CiLi类表示水果中的刺梨,然后让刺梨类去继承Fruit类,并在CiLi类中去实
现showNutrition() 方法;
Fruit类不变,Test类和CiLi类代码如下 :
class CiLi extends Fruit {
@Override
public void showNutrition() {
System.out.println("刺梨是当之无愧的水果界的VC之王,VC含量高达2585mg/100g!");
}
}
class Test {
public static void main(String[] args) {/** 测试类 : Test类 */
Meat meat = new Meat();
meat.showNutrition();
System.out.println("----------------------------------------");
CiLi ciLi = new CiLi();
ciLi.showNutrition();
}
}
运行结果 :
3.4. 抽象类的成员
3.4.1. 成员变量
抽象类既可以有静态的成员变量,也可以有非静态的成员变量。
既可以有静态的成员常量,也可以有非静态的成员常量。
3.4.2. 成员方法
抽象类既可以有(非私有的)抽象方法(注意抽象方法一定是非私有非静态非final,因为abstract关键字与
private关键字,final关键字,static关键字不能同时存在);
也可以有非抽象方法(非抽象方法就可以用private,final和static关键字来修饰了,具体使用时,根据实际需求合
理应用)。
3.4.3. 构造器
抽象类可以和非抽象类一样拥有构造器,并且支持构造器的重载。
3.4.4. 知识小结
其实吧,说上面一大堆都是废话😂。
抽象类中的成员只比非抽象类多一种——抽象方法。其他都和非抽象类一样。
大家只要记住抽象方法怎么写,怎么用就彳亍了。😎
3.5. 代码演示
以Fruit类为演示类,代码如下 :
package com.wlcass.basic.d2_class_abs_int_ano_arr.d2_abstract_class.d2_food;
public abstract class Fruit { //Fruit类是抽象类
//抽象类中可定义的成员:
//1.非静态变量和静态变量
private String name = "水果名儿是有长有短";
private static String size = "水果的大小是有大有小";
//2.非静态常量和静态常量
public final String COLOR = "水果的颜色是五光十色";
public static final String FORM = "水果的形态是千奇百怪";
//3.抽象方法和非抽象方法
public abstract void nutrition();
private final static void suitCrowds() {
System.out.println("人人都适合吃水果!");
}
public void invokeSuitCrowds() {
Fruit.suitCrowds();
}
//4.构造器可以重载
public Fruit() {
System.out.println("Fruit's name = " + name);
}
public Fruit(String name) {
this.name = name;
}
}
这些成员都可以在抽象类中定义,只要语法正确,IDEA是不会报错的,
当然,具体怎么使用这些成员就看你自己了,根据实际情况来定。
3.6. 抽象类练习
3.6.1. 要求
已知西风骑士团的三位骑士分别是琴,可莉和优菈,请分别定义类来描述它们,要求给出每一个西
风骑士的姓名,年龄和性别;并且,这三人均可以使用元素之力,分别是元素战技和元素爆发,但
每位骑士的战技和爆发都不一样。
其中,琴使用风元素,元素战技为风压箭,元素爆发为蒲公英之风;可莉使用火元素,元素战技为
蹦蹦炸弹,元素爆发为轰轰火花;优菈使用冰元素,元素战技为冰朝的涡旋,元素爆发为凝浪之光
剑。
请在这些类中正确选择一个类定义成抽象类,并在该类中定义抽象方法elemental_kill() 和
elemental_burst(),要求这两个抽象方法将来在实现时,需要在控制台打印出当前骑士的元素战技
和元素爆发;并在该抽象类中定义非抽象方法,要求该方法可以在控制台打印出当前骑士的基本信
息。
3.6.2. 思路
① 阅读提干后我们得知,总共有三个角色,这三个角色均属于名为“西风骑士团”的一个组织。因
此,我们可以分别定义四个类来分别描述“西风骑士团”,“琴”,“可莉”,“优菈”,再加上测试类,因
此我们总共需要定义五个类。
② “西风骑士团”可以代表一类人,由于每位骑士的元素战技和元素爆发均不相同,这个类并不能提
供足够的信息来描述一个具体的“骑士”对象。所以,我们可以定义Knights类来表示“西风骑士团”,
并将其定义为抽象类。又因为琴,可莉,优菈均属于西风骑士团的一员,因此我们可以分别定义
Qin类,Keli类以及Youla类来描述这三位骑士,并让Qin类,Keli类和Youla类继承Knights类。
③ 题干要求定义两个抽象方法elemental_kill() 和 elemental_burst()来分别打印出当前骑士的元素
战技和元素爆发。既然我们已经确定Knights类为抽象类,这就没啥好说了,在Knights类中定义这
两个方法即可。
④ 又因为题干还要求我们在抽象类中定义方法打印出当前骑士的基本信息,因此,我们可以在
Knights类定义name,age,sex这些属性;根据JavaBean标准,我们需要将这些属性全部设为私
有,并给出公共的访问这些属性的方法,然后给出Knights类的无参构造和带参构造(注意,
Knights是抽象类,无法被实例化,因此我们给出Knights构造器的目的不是为了创建Knights类对
象,而是为了在子类的带参构造中使用super语句调用父类构造器;接着,再定义一个printInfo方
法,用于打印出当前骑士的姓名,年龄和性别。
⑤ 最后,我们可以定义测试类Test类,并分别创建Qin类,Keli类和Youla类对象,调用
elemental_kill()方法,elemental_burst() 方法,以及printInfo方法。
3.6.3. 代码
代码如下 :
package com.wlcass.basic.d2_class_abs_int_ano_arr.d2_abstract_class.d3_knight;
public abstract class Knights { /** 骑士类 */
private String name;
private int age;
private String sex;
public Knights() {
}
public Knights(String name, int age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public abstract void elemental_skill();
public abstract void elemental_burst();
public void printInfo() {
System.out.println("西风骑士——" + getName() + "," + getAge() + "岁,性别" + getSex());
}
}
class Qin extends Knights{ /** 琴类 */
public Qin(String name, int age, String sex) {
super(name, age, sex);
}
@Override
public void elemental_skill() {
System.out.println("琴