第六章 Java的面向对象编程
学习目标
- Java声明类和对象
- 方法重载
- 方法可变参数
- 方法参数传递
- 递归
- 封装、继承和多态
- 继承
- 多态
- 构造器
- import和package关键字
- 抽象类和抽象方法
1.方法相关练习
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。 创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。 问题一:打印出3年级(state值为3)的学生信息。 问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
提示:
- 生成随机数:Math.random(),返回值类型double;
- 四舍五入取整:Math.round(double d),返回值类型long。
public class StudentTest { public static void main(String[] args) { Student[] studentArr= new Student[20]; for (int i = 0; i < 20; i++) { studentArr[i] = new Student(i+1); studentArr[i].state = (int)((Math.random()*10)+1); studentArr[i].score = (int)((Math.random()*100)); } int temp = 0; for (int i = 0; i < studentArr.length - 1; i++) { for (int j = 0; j < studentArr.length - i - 1; j++) { if(studentArr[j].score>studentArr[j+1].score){ temp = studentArr[j].score; studentArr[j].score = studentArr[j+1].score; studentArr[j+1].score = temp; } } } for (int i = 0; i < 20; i++) { studentArr[i].showMyInfo(); } } }
public class Student { public int number; public int state; public int score; public Student() {} public Student(int number) { this.number = number; } // 用于每次打印Student对象的信息,代码复用,不必每次想打印时都再写一遍 public void showMyInfo() { System.out.println("number=" + number + "\tstate=" + state + "\tscore=" + score); } }
2.方法重载
2.1 需求
计数器类中已有方法:做两个int类型的加法
public int add(int a, int b)
想要增加新的方法:做两个double类型的加法
public double add(double a, double b)
为了满足更多使用情况,还想有更多方法:
public int add(int a, int b, int c)
小结:在一个类中,很可能会有很多类似的需求,为了满足这些需求,我们会声明很多相似的方法。同时为了让方法的调用者体验更好、更容易找到所需方法,这些功能相近的方法最好使用『同一个方法名』。
2.2 前提
- 同一个类中
- 同名的方法
2.3 方法重载的好处
- 没有重载不方便:让方法调用者,在调用方法的时候,不必为了相似的功能而查阅文档,查找各种不同的方法名,降低学习成本,提高开发效率。
- 有了重置很方便:在调用一系列重载的方法时,感觉上就像是在调用同一个方法。对使用者来说,只需要知道一个方法名就能够应对各种不同情况。
2.4 规则限制
限制的来源:本质上只要让系统能够区分清楚我们具体要调用哪一个方法。
- 在同一个类中,如果两个方法的方法名一致,那么参数列表必须不同。
- 参数列表区分
- 要么是参数个数不同
- 要么是参数类型不同
- 要么参数的不同类型的位置不同
2.5 重载方法举例
2.5.1 参数个数不同
public int add(int a, int b) public int add(int a, int b, int c)
2.5.2 参数类型不同
public int add(int a, int b) public double add(double a, double b)
或
public double add(int a, double b) public double add(double a, int b)
3.方法可变参数
3.1 需求
在实际开发过程中,确实有一些情况不确定在调用方法时传入几个参数。所以为了让调用方法时能够弹性传参,JavaSE5.0标准增加了可变参数功能。
3.2 声明和调用
在方法体内,可变参数按照数组形式来处理!!!
// 能够计算任意个数整数之和的加法 // 使用String ... args来声明可变参数 public String add(String ... args) { System.out.println("暗号:可变参数"); String sum = ""; // 在方法体内,可变参数按照数组形式来处理 for (int i = 0; i < args.length; i++) { sum = sum + args[i]; } return sum; }
测试代码:
String addResultStr = calculator.add("a"); System.out.println("addResultStr = " + addResultStr); addResultStr = calculator.add("a", "b"); System.out.println("addResultStr = " + addResultStr); addResultStr = calculator.add("a", "b", "c"); System.out.println("addResultStr = " + addResultStr); addResultStr = calculator.add(); System.out.println("addResultStr = " + addResultStr);
3.3 语法规则
- 可变参数必须在整个参数列表的最后
- 当调用方法时实际传入的参数既匹配可变参数列表方法,又匹配一个具体的参数列表方法,那么系统会优先调用具体的参数列表方法
举例:调用方法add(5, 3)
可变参数方法:add(int ... args)
具体参数方法:add(int i, int j)【系统会调用这个方法】
- 一个方法只能声明一个可变参数
4.方法参数值传递
4.1 基本数据类型
4.2 引用数据类型
5.递归
5.1 概念
方法自己调用自己。使用递归想要达到的目的是让方法中的代码重复执行,而且往往是在上一次执行的结果的基础上,再进一步执行。
5.2 代码示例
5.2.1 计算1~N的和
// 计算1~N之间的总和:使用递归 public int sumOperationRecursion(int number) { if (number == 1) { return 1; }else{ return number + sumOperationRecursion(number - 1); } }
5.2.2 没有退出机制的递归调用
// 没有退出机制的递归调用 public void recursionToDie() { recursionToDie(); }
Exception in thread "main" java.lang.StackOverflowError at com.atguigu.object.test.MethodRecursion.recursionToDie(MethodRecursion.java:35) at com.atguigu.object.test.MethodRecursion.recursionToDie(MethodRecursion.java:35) at com.atguigu.object.test.MethodRecursion.recursionToDie(MethodRecursion.java:35) at com.atguigu.object.test.MethodRecursion.recursionToDie(MethodRecursion.java:35) at com.atguigu.object.test.MethodRecursion.recursionToDie(MethodRecursion.java:35) at com.atguigu.object.test.MethodRecursion.recursionToDie(MethodRecursion.java:35)
内存耗尽的原因:方法每一次开始执行时都会向系统申请栈内存中的一块空间用来存放局部变量。方法结束时释放这块空间。而没有退出机制的递归调用会不断申请新空间,而完全不释放。
栈:stack;堆:heap
5.2.3 用递归实现阶乘
5!=5×4×3×2×1
// 声明一个方法用来实现阶乘 public int factorial(int i) { if (i == 1) { return 1; } else { return i * factorial(i - 1); } }
6.封装
6.1 含义
将对象中数据或代码逻辑隐藏起来。对数据的操作在类的内部完成,对外界隐藏实现的细节。
6.2 好处
- 对象(或组件)内部代码实现的细节可以对外隐藏。
- 简化外部使用对象时的操作难度。外部使用对象时,调用对象暴露出来的方法即可。
- 让整个系统的开发组件化、模块化程度更高,更有利于实现:高内聚、低耦合。
6.3 示例
public class MarryObject { // 将属性的权限修饰符设置为私有,不允许外部直接访问 private int age; // 对外暴露的获取数据的getXxx()方法 public int getAge() { return age; } // 对外暴露的设置数据的setXxx()方法 public void setAge(int ageOutter) { // 在方法内部,根据内部的逻辑,对外界数据进行修正 if (ageOutter < 20) { age = 20; } else if (ageOutter > 60) { age = 60; } else { age = ageOutter; } } }
6.4 权限修饰符
修饰符名称 | 含义 | 本类 | 同包其他类 | 子类 | 同工程其他类 |
private | 私有 | √ | × | × | × |
default | 缺省 | √ | √ | × | × |
protected | 受保护 | √ | √ | √ | × |
public | 公共 | √ | √ | √ | √ |
PS:对class的权限修饰符只有public和缺省两种
- public:表示这个类可以在工程中任意位置访问(开发中实际使用的方式)
- 缺省:表示这个类只能在同一个包内访问(实际开发不会使用这种方式)
7.构造器
注意:构造器和方法是两码事,各是各的。
7.1 构造器引入
当我们使用new关键字创建一个类的对象时,就是在使用这个类的构造器了:
如果我们没有明确声明构造器,那么系统回给类分配一个隐式的构造器。
7.2 构造器的作用
- 作用1:创建对象。
- 作用2:在创建对象的过程中,对类进行初始化操作。这些操作写在构造器的大括号中。
- 初始化操作放到构造器里面,由构造器自动完成,程序员就不必在创建对象后还想着对这对象做初始化操作。
7.3 构造器的组成
7.4 构造器的参数
7.4.1 有一个参数构造器例子
public class Soldier { private String soldierName; public Soldier(String soldierName) { this.soldierName = soldierName; } }
注意:当类中声明了有参构造器之后,系统将不再给这个类分配无参的构造器。
7.4.2 一个类可以有多个构造器
根据参数列表不同,多个构造器之间是重载关系。规则和方法重载一致:
- 要么参数个数不同
- 要么参数类型不同
public class Soldier { private int age; private String soldierName; private String weapon; private String gender; public Soldier() { } public Soldier(int age) { this.age = age; } public Soldier(String soldierName) { this.soldierName = soldierName; } public Soldier(String soldierName, String weapon) { this.soldierName = soldierName; this.weapon = weapon; } public Soldier(String soldierName, String weapon, String gender) { this.soldierName = soldierName; this.weapon = weapon; this.gender = gender; } public Soldier(int age, String soldierName, String weapon, String gender) { this.gender = gender; this.weapon = weapon; this.soldierName = soldierName; this.age = age; } }
7.5 构造器相关语法规则
- Java语言中,每个类都至少有一个构造器
- 默认构造器的修饰符与所属类的修饰符一致
- 一旦显式定义了构造器,则系统不再提供默认构造器
- 一个类可以创建多个重载的构造器
- 父类的构造器不可被子类继承
7.6 相关快捷键
7.6.1 打开快捷菜单
Alt+Insert(有的电脑需要按住Fn键,再按Insert才能生效 )
7.6.2 声明五参构造器
当快捷菜单中选中的是Constructor时,回车,弹出下面窗口:
7.6.3 声明全部参数构造器
7.6.4 在快捷菜单输入字符匹配菜单项
输入gets就可以直接匹配到菜单项:Getter and Setter
7.6.5 生成getter和setter方法
通常针对所有属性生成getter、setter方法
7.6.6 生成toString()方法
7.7 类的属性赋值的顺序
- 系统根据这个属性的类型来设置默认值。
private int age;
- 显示初始化
private int age = 6;
- 构造器初始化
public Person(int age) { this.age = age; }
- 调用方法或直接给属性赋值
person.setAge(5);
或
person.age = 18;
8.JavaBean
8.1 实际项目开发中用到的类
- JavaBean
- 用来描述现实世界中的具体数据:Book、Employee、Department、Product、……
- 也有其他名称:domain(领域模型)、POJO(Plain old Java Object普通的传统的Java对象)、entity(实体类)
- 功能性组件:用来执行具体代码逻辑对应的操作
- BookServlet
- BookService
- BookDao
- 工具类:通常用来把某个特定操作封装到工具方法中。工具类通常是用类名来直接调用方法。
- Aarrys.sort()
8.2 JavaBean的要求
必须有的组成部分:
- 私有的属性
- 针对私有属性设置的公共的getter()、setter()方法
- 无参构造器
8.3 getter()和setter()方法的规则
属性声明:
private int age;
getter()、setter()方法:
public int getAge() { return age; } public void setAge(int age) { this.age = age; }
规则描述如下:
- getXxx()或setXxx()方法取方法名字符串
- 去掉get或set(剩下Xxx)
- Xxx首字母大写
8.4 IDEA生成UML图
9.this关键字
9.1 含义
- 类的方法中:代表调用当前方法的对象
- 类的构造器中:代表当前构造器正在创建的对象
9.2 this能干什么
9.2.1 调用属性
System.out.println("我的年龄:" + this.age); System.out.println("我的姓名:" + this.soldierName); System.out.println("我的武器:" + this.weapon);
9.2.2 调用方法
System.out.println("我的性别:" + this.getGender());
9.2.3 调用构造器
在类中的某个构造器中调用这个类的其他构造器:
public class Tiger { private String tigerName; private int tigerAge; public Tiger() { System.out.println("这里是Tiger类的无参构造器"); } public Tiger(String tigerName) { this(); this.tigerName = tigerName; System.out.println("这里是Tiger类的一参构造器"); } public Tiger(String tigerName, int tigerAge) { this("aaa"); this.tigerName = tigerName; this.tigerAge = tigerAge; System.out.println("这里是Tiger类的两参构造器"); } }
9.2.4 this调用构造器的规则
- 在构造器中调用其他构造器,this()语句必须在第一行
- 各个构造器之间调用不能形成闭环
循环调用逻辑如下:
一个构造器中调用其他构造器的操作只能有一个
10.package关键字
用来声明当前类所在的包。package声明所在包的语句必须是在类的第一行。若缺省该语句,则指定为无名包。
11.import关键字
在JDK编译Java源程序的时候,并不能保证所有的类都能够被JDK默认识别到。JDK无法默认识别的类就需要我们通过import语句导入。
- 精确导入:在import关键字后面指定全类名
import java.util.Scanner;
- 全部导入:在import关键字后面指定包名.*,*表示该包下的所有类,不包含该包下的子包中的类
import java.util.*;
语法规则:
- 使用的位置:在package声明和类声明之间
- 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。
- 如果导入一个类之后,又需要用到另一个同名的类,那么就需要使用全类名来引用
// 这里导入了一个Book类 import java.awt.print.Book; public class ImportTest { public static void main(String[] args) { // 但是这里又要使用另外一个Book类 // 为了区分二者,这里使用全类名来引用 com.atguigu.object.test.Book book = new com.atguigu.object.test.Book(); } }
- 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。
例如Scanner类,只能导入到java.util包下的类才行,不能直接导入java包下的类,不然会找不到Scanner类,注意*号的运用。
- import static组合的使用:静态导入。让当前类可以直接调用导入的类中的静态属性或方法,不必写类名了。
12.继承
12.1 生活中的继承
生活中,子继承父的遗产,不是全部,只能是部分。
12.2 程序中的继承
在程序中,也能出现继承的关系,让一个类去继承另一个类。
比如A类继承B类
- 出现继承的关系
- A类是B类的子类,或者称为派生类
- B类是A类的父类,或者称为超类,基类
- 子类可以直接拥有父类的成员(不是全部)
12.3 继承的语法格式
继承使用关键字 extends,表示继承的意思
注意: extend 本身含义,扩展,延伸
- 定义格式
class B{} class A extends B{} // A 继承 B类
- 继承的入门案例 -- 子类拥有父类成员
/** * 定义的是 Teacher和Manager类的共性内容 * 姓名年龄 */ public class Person { String name ; int age; }
/** * 管理类,班主任类 */ public class Manager extends Person{ }
/** * 老师类 * 重复,只做一次 * 想法: * String name; * int age; * 换一个地方定义(共性抽取) * Teacher和Manager共用 * 使用Person中的成员 * Teacher继承Person */ public class Teacher extends Person{ }
public static void main(String[] args) { /** * 创建对象, Person的子类对象 * Person : Teacher,Manager */ Teacher teacher = new Teacher(); //teacher对象,调用成员变量 //teacher子类对象调用继承的成员变量 teacher.name = "张三"; teacher.age = 20; Manager manager = new Manager(); manager.name = "李四"; manager.age = 22; System.out.println("teacher.name = " + teacher.name); System.out.println("teacher.age = " + teacher.age); System.out.println("manager.name = " + manager.name); System.out.println("manager.age = " + manager.age); }
12.4 继承的好处
上面案例,使用程序中的继承,使用继承的好处:
- 减少代码量
- 复用性提高
- 继承的存在,导致了面向对象的最后一个特征多态。继承有弊端: 类和类之间的紧密性更强.(扩展性越差)
13.继承后成员特点
13.1 继承后成员变量特点
子类和父类中的成员变量同名
- 调用的使用: 子类自己有,使用自己的,子类没有,使用父类
public class Fu{ String s = "父类"; } public class Zi extends Fu{ String s = "子类"; } public static void main(String[] args) { //创建对象,子类对象 Zi zi = new Zi(); //子类对象,调用成员s System.out.println(zi.s); }
13.2 super关键字
super关键字是超级的意思,在子类中调用父类的成员,使用此关键字
super.变量 调用父的成员变量 super.方法() 调用的是父类的成员方法
this表示当前对象,super表示父类在内存中的存储空间,不是对象。super是在对象中区分子父类的成员。
public class Fu{ String s = "父类"; } public class Zi extends Fu { String s = "子类"; public void print(){ String s = "方法"; System.out.println(s); System.out.println(this.s); System.out.println(super.s); } } public static void main(String[] args) { //创建子类对象,调用方法 Zi zi = new Zi(); zi.print(); }
13.3 继承后的成员方法特点
方法重写override:子类父类出现了一模一样的方法,称为子类重写了父类的方法。又称为覆盖或者复写。调用子类重写的方法,假如子类没有重写,用父类的方法。
下面代码是写成3个java文件
public class Fu { public void print(){ System.out.println("父类方法print"); } } public class Zi extends Fu { /** * 要重写父类的方法 * 直接复制 */ public void print(){ System.out.println("子类方法print::重写"); } } public static void main(String[] args) { //创建子类对象 Zi zi = new Zi(); zi.print(); }
13.4 方法重写的意义
继承本质是扩展的意思,延伸的意思,依靠方法的重写来实现。
方法重写引入案例,理解重写的意义。
/** * 定义手机类 * 早年代手机 */ public class Phone { /** * 定义手机的来电显示功能 * 数字移动电话 */ public void showingCall(){ System.out.println("来电显示号码"); } } /** * 新手机: * 功能变强,但是原有功能继续复用 * 继承和方法重写的思想 */ public class Iphone extends Phone { //重写父的方法 //方法名字不变,用户不需要重新知 public void showingCall(){ //复用显示号码功能,父类的方法中,已经完成了 //调用父类的方法 super.showingCall(); //新增的功能 System.out.println("显示大头像"); System.out.println("归属地"); System.out.println("意思推销"); } } public static void main(String[] args) { //创建手机对象 Phone phone = new Phone(); phone.showingCall(); System.out.println("=========="); //创建新手机对象 Iphone iphone = new Iphone(); iphone.showingCall(); }
13.5 方法重写小问题
方法重写需要考虑权限的。保证子类方法的权限要大于或者等于父类方法权限。
- 父类的方法权限是public
class Fu{ public void print(){} } class Zi extends Fu{ public void print(){} //正确 protected void print(){} //错误,权限降低 void print(){} //错误,权限降低 private void print(){} //错误,权限降低 }
- 父类的方法权限是protected
class Fu{ protected void print(){} } class Zi extends Fu{ public void print(){} //正确 protected void print(){} //正确 void print(){} //错误,权限降低 private void print(){} //错误,权限降低 }
- 父类方法权限是默认
class Fu{ void print(){} } class Zi extends Fu{ public void print(){} //正确 protected void print(){} //正确 void print(){} //正确 private void print(){} //错误,权限降低 }
- 十分重要:如果父类的方法权限是private,子类不知道该方法的存在,没有继承的说法。
13.6 继承后构造方法特点
构造方法特点:子类的构造方法中,第一行存在隐式代码 (写不写都存在),代码是super():调用父类的无参数构造方法。
public class Fu { public Fu(){ System.out.println("父类构造方法"); } } public class Zi extends Fu { public Zi(){ super(); //调用父类的无参数构造方法. System.out.println("子类构造方法"); } }
- 子类的构造方法,无论重载多少个,第一行肯定也是super();
public class Zi extends Fu { public Zi(){ super(); //调用父类的无参数构造方法. System.out.println("子类构造方法"); } public Zi(int a){ super(); //调用父类的无参数构造方法. System.out.println("子类构造方法::重载的"); } }
- 父类没有无参数构造方法,子类的构造方法中,super(传递参数)
- 父类中存在多个构造方法,子类的构造方法只要调用到其中的一个即可
public class Fu { /** * 父类的构造方法 * 加上参数,有参数的构造 */ public Fu(int a){ System.out.println("父类构造方法" + a); } public Fu(String s){ System.out.println("父类构造方法" + s); } } public class Zi extends Fu { public Zi(){ //调用父类的无参数构造方法 //父类中没有无参数构造 //传递响应的参数 super(7); } public Zi(String s){ //调用父类构造方法,保证调用其中一个 super("字符串"); } }
14.继承特点
14.1 单继承
一个类只能继承一个类,不允许同时继承多个类。
class A extends B,C{} //不允许的行为
单继承存在局限性,解决局限性问题,接口的概念。
14.2 多层继承
class A extends B{} class B extends C{} //多层继承,允许实现 //class C extends Object{}
A类可以同是拥有B和C的成员,B只能拥有C的成员
A类中super调用的是B类成员,如果B类没有成员,调用C成员
Object类是java中的皇帝,所有的类都是Object子类
15.面向对象的多态
引入:生活中的多态性!你自己的身份是学生,你的身份职场精英,患者。在不同的时期不同环境,状态是不同的。
生活中的多态性:一个事物具备的不同形态。
15.1 对象多态性前提
- 必须有继承或者是接口实现
- 必须有方法的重写
多态的语法规则:父类或者接口的引用指向自己的子类的对象
父类 变量(对象名) = new 子类对象(); //多态写法
对象调用方法,执行的子类的方法重写。
15.2 多态中成员的特点
- 多态中成员变量的特点
- 编译 : 父类中没有成员变量,编译失败
- 运行 : 运行父类中的成员变量
- 多态中成员方法的特点
- 编译 : 父类中没有成员方法,编译失败
- 运行 : 运行子类的方法重写
- 简练 : 成员方法编译看左边,运行看右边。成员变量都是左边
Person p = new Student();
public class Person { String s = "父类成员"; public void eat(){ System.out.println("人在吃饭"); } }
public class Student extends Person { String s = "子类成员"; public void eat(){ System.out.println("学生吃饭"); } }
public static void main(String[] args) { Person p = new Student(); //对象p,子类对象,调用成员变量s System.out.println(p.s); //子类对象调用方法 p.eat(); }
15.3 多态的转型
多态的程序中,不能调用子类的特有成员!!
只能调用子类父类的共有成员!!
转后类型 变量名 = (转后类型)要转的数据; //公式
public static void main(String[] args) { //创建对象,多态性 //父类 = new 任意子类对象() 扩展 Animal animal = new Cat(); animal.eat(); //Cat类的特有功能 catchMouse()方法 //类型转换,强制 //Cat提升为了Animal,转回Cat类型 Cat c = (Cat)animal; c.catchMouse(); }
子类转向父类是自动转型,父类转型子类需要强转符号!!!
补充: 多态性提升扩展性,是否需要强制转换,根据实际功能需求来决定。
15.4 多态中的转型异常
异常ClassCastException 类型转换异常,在多态中经常发生.
是在进行类型的强制转换的时候发生,我们现在的案例是Dog不能转成Cat.
需要解决这个异常 : 对象是Cat转Cat,是Dog换Dog
运算符 : 比较运算符,结果是boolean类型
运算符是关键字 instanceof
instanceof的语法格式:
对象名 instanceof 类的名字 解析: 比较这个对象,是不是由这个类的实例化 c instanceof Cat 解释: c对象是不是Cat类产生的,如果是结果就是true
强制类型转换之前的安全性判断
public static void main(String[] args) { //多态创建对象 Animal animal = new Dog(); animal.eat(); //判断 animal是不是Cat类的对象 //boolean b = animal instanceof Dog ; //System.out.println(b); //调用子类的特有方法 if (animal instanceof Cat){ //if为true,强制转换为Cat Cat c = (Cat)animal; c.catchMouse(); } if (animal instanceof Dog){ Dog d = (Dog)animal; d.lookHome(); } }
15.5 多态的转型案例
public static void main(String[] args) { //创建对象,多态 Person p1 = new Faculty(); //p1对象的属性,赋值本科 degree 子类的特有成员 //判断p1对象是不是Faculty类产生 if (p1 instanceof Faculty){ Faculty f = (Faculty)p1; f.setDegree("本科"); System.out.println(f.getDegree()); } Person p2 = new Staff(); //判断p2对象是不是Staff类产生 if (p2 instanceof Staff){ Staff s = (Staff)p2; s.setDuty("职员"); System.out.println( s.getDuty()); } }
16.抽象类 abstract
抽象的概念:凡是说不清楚的都是抽象。
例子:我买了一台手机,买了一直笔,都是抽象概念。
具体:华为Meta40Pro,金属,16G+512。
程序中:我知道这个功能存在,但是怎么完成就说不清楚,程序中也出现了抽象。
16.1 抽象方法定义
使用关键字 abstract定义抽象方法
权限修饰符 abstract 返回值类型 方法名字(参数列表) ; abstract关键字 抽象方法没有方法体, 不需要{},直接分号结束
当一个类中的方法是抽象方法的时候,这个类必须是抽象类,在类的关键字class前面使用abstract修饰。
public abstract class 类名{}
public abstract class Animal { /** * 动物吃什么? * 说不清楚,抽象,可以不说 */ public abstract void eat(); }
16.2 抽象类的使用方式
- 抽象类不能实例化对象,不能new对象.
- 为什么不能建立对象,类中有没有主体的方法存在,建立对象调用抽象方法是绝对的错误,因此不能建立对象
- 需要子类继承抽象类,重写抽象方法
- 创建子类对象
- 使用多态性创建对象,调用方法执行子类的重写
public class Cat extends Animal{ /** * 重写父类的方法 * 去掉修饰符 abstract * 添加主体 {} */ public void eat(){ System.out.println("猫吃鱼"); } }
public static void main(String[] args) { //创建Animal的子类对象 Animal animal = new Cat(); //eat方法不可能执行父类,运行子类的重写 animal.eat(); }
16.3 抽象类中成员的定义
16.3.1 抽象类中能否定义成员变量
可以定义成员变量,成员变量私有修饰,提供方法 get/set,由子类的对象使用
public abstract class Animal { //抽象类中能否定义成员变量 private String name; public abstract void eat(); public String getName() { return name; } public void setName(String name) { this.name = name; } }
public static void main(String[] args) { Animal animal = new Cat(); animal.eat(); //animal对象调用方法 get/ set animal.setName("tom"); String name = animal.getName(); System.out.println(name); }
16.3.2 抽象类中有构造方法吗
抽象类中有构造方法,不写有默认的
public abstract class Animal { public Animal(){ System.out.println("Animal的构造方法"); } public Animal(String name){ this.name = name; System.out.println("有参数String的构造方法"); } //抽象类中能否定义成员变量 private String name; public abstract void eat(); public String getName() { return name; } public void setName(String name) { this.name = name; } }
public class Cat extends Animal { public Cat(){ //调用父类的有参数构造方法 super("张三"); } @Override public void eat() { System.out.println("猫吃鱼"); } }
16.3.3 抽象中能否不定义抽象方法
抽象类中,可以不定义出抽象方法.
但是,如果有抽象方法存在,这个类必须是抽象类
16.4 子类还是抽象类的问题
当一个子类继承一个抽象类的时候,子类必须重写全部的抽象方法。假如子类重写了部分抽象方法,这个子类依然还是抽象类。
public abstract class Animal { public abstract void eat(); public abstract void sleep(); }
/** * Cat继承父类Animal,Cat类拥有了父类的成员 * 父类有什么,我就有什么 */ public abstract class Cat extends Animal { public void eat(){} /** * 方法sleep没有重写 * 还是一个抽象的方法 */ // public abstract void sleep(); }
16.5 员工案例
/** * 公司类 * 定义的是所有员工的共性内容 */ public abstract class Company { private String name; //员工姓名 private String id; // 员工编号,唯一标识 //工作行为,具体到某个岗位是不同,无法写出具体的工作内容 public abstract void work(); public String getName() { return name; } public void setName(String name) { this.name = name; } public String getId() { return id; } public void setId(String id) { this.id = id; } }
/** * 研发部类 */ public class Development extends Company{ //重写工作的抽象方法 //work方法中,输出自己的姓名和工号呢 @Override public void work() { //调用父类的方法 System.out.println(super.getName()+"::"+super.getId()+"研发部的员工在开发程序"); } }
/** * 定义财务部类 */ public class Financial extends Company { @Override public void work() { System.out.println(super.getName()+"::"+super.getId()+"财务部员工在算账"); }
public static void main(String[] args) { //创建对象,子类对象,多态性 Company c1 = new Development(); //父类的方法,属性赋值 c1.setName("张三"); c1.setId("研发部001"); //System.out.println(c1.getName() +"::"+c1.getId()); c1.work(); Company c2 = new Financial(); c2.setName("李四"); c2.setId("财务部001"); c2.work(); }
17.接口interface
17.1 接口无处不在
身边的接口有哪些,笔记本上USB接口,HDMI,TypeC接口,插座
USB接口 : 连接鼠标,键盘,摄像头,手机,移动硬盘,电风扇。设备的工作原理不同,但是都可以连接到USB接口上,完成他的任务。说明了一个问题 : 这些设备都满足USB的接口规范!!!
接口:就是一个规范,或者称为标准 ,无论什么设备,只要符合接口标准,就可以正常使用。
接口的扩展性很强大。
17.2 Java中接口定义
当一个抽象类中的所有方法全部是抽象的时候,可以将这个抽象类换一个更加贴切的名词,叫他接口。接口是特殊的抽象类。
定义接口,使用关键字 interface
语法规范:
public interface 接口名{}
接口在编译后,依然还是.class文件!!!
17.3 接口中成员定义
成员变量
- 成员变量的定义是具有固定格式
- 成员变量的修饰符是固定 public static final
public static final 数据类型 变量名 = 值 ;
成员方法
- 成员方法的定义是具有固定格式
- 成员方法的修饰符固定为 public abstract
public abstract 返回值类型 方法名(参数列表) ;
17.4 接口的使用方式
- 接口不能建立对象,不能new
- 需要定义类,实现接口(继承类,在接口中称为实现,理解为继承)
- 实现接口,使用新的关键字 implements
- 实现的格式
class 类 implements 接口名{}
- 重写接口中的抽象方法
- 创建子类的对象
/** * 定义好的接口 */ public interface MyInterFace { //接口的成员变量 public static final int A = 1; //接口的成员方法 public abstract void myInter(); }
/** * 定义MyInterFace接口的实现类 * 重写接口的抽象方法 */ public class MyInterFaceImpl implements MyInterFace{ public void myInter(){ System.out.println("实现类实现接口,重写方法"); } }
public static void main(String[] args) { //创建对象,多态性,创建接口实现类的对象 MyInterFace my = new MyInterFaceImpl(); my.myInter(); //输出接口中的成员A的值 System.out.println(my.A); }
17.5 接口的多实现
类和类之间单继承,局限性的问题。接口的出现,是对单继承的改良,允许一个类同时实现多个接口。
语法格式:
class 类名 implements 接口A,接口B{}
实现类,重写实现的多有接口中的抽象方法
public interface A { public abstract void a(); }
public interface B { public abstract void b(); }
/** * 实现接口A和B */ public class C implements A,B{ @Override public void a() { System.out.println("重写A接口方法"); } @Override public void b() { System.out.println("重写B接口方法"); } }
public static void main(String[] args) { C c = new C(); c.a(); c.b(); }