抽象类
在继承的层次结构中,每个新子类都使类变得越来越明确和具体。如果从一个子类追溯到父类,类就会变得更通用、更加不明确。类的设计应该确保父类包含它的子类的共同特征。有时候,一个父类设计的非常抽象,以至于它都没有任何具体的实例。这样的类称为抽象类。
当编写一个类时,常常会为该类定义一些方法,这些方法用以描述该类的行为方式,那么这些方法都有具体的方法体。但在某些情况下,某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法。假如Geometric类定义成Circle和Rectangle的父类。Geometric类模拟了几何对象的共同特征。Circle类和Rectangle类分别包含计算面积和周长的方法getArea()和getPerimeter().。因为可以计算所有几何对象的面积和周长。但是这样的方法不能在Geometric类中实现,因为它们的实现取决于几何对象的具体类型。这样的方法称为抽象方法,在方法头中用abstract修饰。包含抽象方法的类就成为一个抽象类,在类头用abstract修饰。
package edu.uestc.avatar.geometric; import java.time.LocalDate; /** * 抽象类可以有构造方法,但是不能创建抽象类的实例,交给子类的构造方法进行调用 * */ public abstract class Geometric { private LocalDate createDate; private String color; private boolean filled; public Geometric() { this.createDate = LocalDate.now();//当前系统日期 } public Geometric(String color, boolean filled) { this.createDate = LocalDate.now();//当前系统日期 this.color = color; this.filled = filled; } public LocalDate getCreateDate() { return createDate; } public void setCreateDate(LocalDate createDate) { this.createDate = createDate; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } public boolean isFilled() { return filled; } public void setFilled(boolean filled) { this.filled = filled; } /** * 抽象方法:所有的子类都具有的共同的行为特征,该行为特征在父类不可描述----抽象方法 * 只有方法的定义没有方法的实现,交由子类去实现 * 包含抽象方法的类必须是一个抽象类 * @return */ public abstract double getArea(); public abstract double getPerimeter(); }
抽象类可以有构造方法,但是不能使用new操作符创建它的实例。抽象方法只有定义没有实现。它的实现由子类提供,一个包含抽象方法的类必须声明为抽象类。
抽象类Geometric为几何对象定义了共同特征(数据和方法),并且提供了合适的构造方法。因为不知道如何计算几何对象的面积和周长,所有getArea()和getPerimeter()定义为抽象方法。这些方法在子类中实现。
子类:Circle.java
package edu.uestc.avatar.geometric; /** * 子类继承抽象类,必选实现抽象类中所有的抽象方法,否则子类也应该是一个抽象类,交由具体的子类去实现 * */ public class Circle extends Geometric{ public static final double PI = 3.14; private double radius; public Circle() { } public Circle(double radius) { this.radius = radius; } public Circle(double radius,String color,boolean filled) { super(color,filled); this.radius = radius; } public double getRadius() { return radius; } public void setRadius(double radius) { this.radius = radius; } /** * 计算圆几何图形的面积 */ public double getArea() { return PI * radius * radius; } public double getPerimeter() { return 2 * PI * radius; } @Override //edu.uestc.avatar.geometric.Circle@24356b public String toString() { String ret = "circle:(area:" + getArea() + ",perimeter:" + getPerimeter(); if(isFilled()) ret += "filled, color:" + getColor(); return ret + ")"; } }
子类:Rectangle.java
package edu.uestc.avatar.geometric; public class Rectangle extends Geometric{ private double width; private double height; public Rectangle() { } public Rectangle(double width,double height) { this.width = width; this.height = height; } public Rectangle(double width,double height,String color,boolean filled) { super(color,filled); this.width = width; this.height = height; } public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } public double getPerimeter() { return 2 * (width + height); } @Override public double getArea() { return width * height; } }
为何要使用抽象方法(提供了程序的灵活性)
可能大家会提出,既然Geometric类不知道如何实现getArea()和getPerimeter()方法,那就干脆不要定义它了,这不是一个好的思路:如下面程序所示,现在有一个工具方法用于去比较两个几何图形的面积是否相同及展示一個几何对象,从中就可以看出使用抽象方法的优势(父类更加通用)
package edu.uestc.avatar.geometric; public class GeometricDemo { public static void main(String[] args) { Geometric geometric1 = new Circle(5); Geometric geometric2 = new Rectangle(5,4); System.out.println("两个几何对象的面积是否相等:" + equalArea(geometric1, geometric2)); displayGeometricObject(geometric1); displayGeometricObject(geometric2); } public static boolean equalArea(Geometric object1, Geometric object2) { //如果父类没有定义getArea(),那么此处就不能调用,必须类型强转为子类类型,这就降低了程序的灵活性 return object1.getArea() == object2.getArea(); } public static void displayGeometricObject(GeometricObject object) { System.out.println(); System.out.println("The area is" + object.getArea()); System.out.println("The perimeter is" + object.getPerimeter()); } }
抽象方法和抽象类的规则如下:
1.抽象类必须使用abstract修饰符来修饰,抽象方法也必须使用abstract修饰符来修饰,抽象方法不能有方法体。
2.抽象类不能被实例化(实例化是为了调用属性和方法,抽象类本身没有方法实现的),无法使用new关键字来调用抽象类的构造器来初始化抽象类的实例。即使抽象类里不包含抽象方法,这个抽象类也不能创建实例。
3.抽象类可以包含属性、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类、枚举类六种成分。抽象类的构造器不能用于创建实例,主要用于被其子类调用
4.含义抽象方法的类(包括直接定义了一个抽象方法:继承了一个抽象父类,但没有完全实现父类包含的抽象方法;以及实现了一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义成抽象类。
示例:抽象的Number类
示例:Calendar和GregorianCalendar
Calendar是一个抽象的基类,可以提取出详细的日历信息。GregorianCalendar(公历类)是抽象类Calendar的一个具体子类。
package edu.cduestc.demo;
import java.text.DateFormatSymbols;
import java.util.Calendar;
import java.util.GregorianCalendar;
/** * GregorianCalendar是抽象类Calendar的一个具体实现子类 * 一个java.uitl.Date的实例表示以毫秒为精度的特定时刻,java.util.Calendar是一个抽象的基类,可以提取出详细的日历信息 * Calendar的子类可以实现特定的日历系统(公历,农历,犹太历等) * get(int field):提取日期和时间信息 常量: 说明 YEAR 日历的年份 MONTH 日历的月份,0表示1月 DATE 日历的天 HOUR 日历的小时(12小时制) HOUR_OF_DAY 日历的小时(24小时制) MINUTE 日历的分钟 SECOND 日历的秒 DAY_OF_WEEK 一周的天数,1表示星期日(建议使用常量表示语义明确) DAY_OF_MONTH 和DATE一样 DAY_OF_YEAR 当前年的天数,1是一年的第一天 WEEK_OF_MONTH 当前月内的星期数,1表示该月的第一个星期 WEEK_OF_YEAR 当前年内的星期数,1表示该月的第一个星期 AM_PM 表示上午还是下午(0:上午,1:下午) * set(int value,int field):用来设置一个域 */
public class PrintCalendarDemo { public static void main(String[] args) { GregorianCalendar calendar = new GregorianCalendar(); int today = calendar.get(Calendar.DAY_OF_MONTH); int month = calendar.get(Calendar.MONTH);//从0开始 //设置这个月的第一天 calendar.set(Calendar.DAY_OF_MONTH, 1); //得到这一天为星期几 int weekday = calendar.get(Calendar.DAY_OF_WEEK); //得到星期的起始日 int firstdayOfWeek = calendar.get(Calendar.DAY_OF_WEEK_IN_MONTH); int indent = 0; while(weekday != firstdayOfWeek) { indent++; calendar.add(Calendar.DAY_OF_MONTH, -1); weekday = calendar.get(Calendar.DAY_OF_WEEK); } String[] weekdayNames = new DateFormatSymbols().getShortWeekdays(); do { System.out.printf("%4s",weekdayNames[weekday]); calendar.add(Calendar.DAY_OF_MONTH, 1); weekday = calendar.get(Calendar.DAY_OF_WEEK); }while(weekday != firstdayOfWeek); System.out.println(); for(int i = 1; i <= indent; i++) { System.out.print(" "); } calendar.set(Calendar.DAY_OF_MONTH, 1); do { int day = calendar.get(Calendar.DAY_OF_MONTH); System.out.printf(day < 10 ? "%5d" : "%4d",day); calendar.add(Calendar.DAY_OF_MONTH, 1); if(day == today)System.out.print("* "); else System.out.print(" "); weekday = calendar.get(Calendar.DAY_OF_WEEK); if(weekday == firstdayOfWeek) System.out.println(); }while(calendar.get(Calendar.MONTH) == month); } }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 零经验选手,Compose 一天开发一款小游戏!
· 一起来玩mcp_server_sqlite,让AI帮你做增删改查!!