【JavaSE 第十天 】
一、 对象的多态性
引入:
生活中的多态性:一个事物具备不同的形态。
(例如:人在不同时期,不同环境下,状态是不同的。)
1. 对象多态性前提
- 必须有继承或者是接口实现
- 必须有方法的重写
多态的语法规则:父类或者接口的引用指向自己的子类的对象。
父类 变量(对象名) = new 子类对象(); // 多态写法
对象调用方法,执行的是子类的方法重写
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();
}
解释:
3. 多态的转型
- 多态的程序中,不能调用子类的特有成员。
- 多态的程序中,只能调用子类和父类共有的成员
(1) 基本数据类型的转换
① 自动转换:
byte → short → int → long → float → double
例如:
int a = 1;
byte b = 1;
// b+a 的结果是 int
short s = 1;
int i = 1;
s = s + 1; // 错误的运算
// s + 1 结果是 int 类型
s = (short)(s + 1) // 正确的写法
② 强制类型转换
类型强制转换的公式:
转后类型 变量名 = (转后类型)要转换的数据
(2) 引用数据类型的转换
① 自动转换
// 父类类型 // 子类类型
Animal animal = new Cat();
等号两边类型不同,所以程序出现了自动的类型转换
自动类型转换,小类型转换为大类型,父类为大,子类为小(子类类型转换为父类类型)
Cat 类型自动转换,提升为了父类 Animal 类型:类型向上转型
(这也就是多态程序中,为什么不能调用子类的特有成员)
② 强制类型转换
需要使用子类的特有成员:必须进行强制类型转换
目标:是将已经提升为父类的 Cat 类型,再转回 Cat 类型(子类)
类型强制转换的公式:
转后类型 变量名 = (转后类型)要转换的数据
Cat 变量名 = (Cat)animal; // 类型的向下转型
public static void main(String[] args) {
// 创建对象,多态性
// 父类 = new 任意子类对象(); 扩展性
Animal animal=new Cat();
animal.eat();
// Cat 类的特有功能
// catchMouse() 方法 但是多态编译看父类
// 强制类型转换
// Cat 提升为了 Animal ,再转回 Cat 类型
Cat c=(Cat)animal;
c.catchMouse();
}
(调用 Cat 类型子类的特有成员,不能再次 new 一个 Cat 类型对象,那就是另一个对象(另一块内存))
强转回子类类型会影响多态的扩展性,是否强转类型要看具体情况,是否要调用子类的特有成员。
4. 多态中的转型异常
- 异常 ClassCastException(类型转换异常),在多态中经常发生
在进行强制类型转换的时候发生,例如:
public static void main(String[] args) {
Animal animal=new Dog(); // 子类 Dog 类型也继承自 Animal
animal.eat(); // 调用子类与父类都有的成员方法
Cat c=(Cat)animal; // 这里将 Dog 类型强转为 Cat 类型
c.catchMouse(); // 就会出现 ClassCastException(类型转换异常)
}
- 解决异常:注意对象是什么类型就转换成什么类型
- 这里存在一个运算符方便判断对象的类型的运算符:属于比较运算符,结果是 boolean 类型
- 运算符是关键字 instanceof
- instanceof 的语法格式:
对象名 instanceof 类的名字
- 解释:比较这个对象,是不是由这个类产生的
例如:
c instanceof Cat; // 解释:c 对象是不是由 Cat 类产生的,如果是结果就是 true
解决:保障安全性,强制类型转换之前的安全性判断
if(animal instanceof Dog){
Dog d=(Dog)animal;
d.lookHome();
}
if(animal instanceof Cat){
Cat c=(Cat)animal;
c.catchMouse();
}
二、 抽象类 abstract
程序中:如果知道这个功能存在,但是怎么完成,说不清楚,程序中就出现了抽象。
1. 抽象方法定义
- 使用关键字 abstract 定义抽象方法:
权限修饰符 abstract 返回值类型 方法名字(参数列表);
// abstract 关键字
// 抽象方法没有方法体,不需要 {} ,直接分号结束
- 当一个类中方法是抽象方法的时候,这个类必须是抽象类,在类的关键字 class 前面使用 abstract 修饰
具体使用:
public abstract class 类名{
权限修饰符 abstract 返回值类型 方法名字(参数列表);
}
2. 抽象类的使用方式
- 抽象类不能实例化对象,不能进行 new 对象 操作
类中没有主体的方法存在,建立对象调用抽象方法是绝对错误的,因此不能建立对象 - 需要子类继承抽象类,重写抽象方法。(强制性操作,否则会报错,并且有几个重写几个)
- 创建子类对象(共两类:一种是使用多态性创建对象,一种是只利用子类创建对象)
- (建议)使用多态性创建对象,调用方法执行子类的重写
父类:
public abstract class Animal {
public abstract void eat();
}
子类:
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();
}
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 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();
// 调用 get/set 方法
animal.setName("Tom");
animal.getName();
System.out.println(animal.getName());
}
(2) 抽象类中的构造方法(构造器)
抽象类中具有默认的构造方法,也可以自己定义有参数的构造方法,全部正常使用(遵循继承后的构造方法的特点)。
父类:
public abstract class Animal {
// 自己写的父类的构造方法
public Animal(){
// 此构造方法对象一经创建就会执行
System.out.println("Animal 的构造方法");
}
public abstract void eat();
}
子类:
public class Cat extends Animal{
// 在子类中,也有默认的构造方法,并且第一行(这个位置)就是调用(super();)父类中的构造方法(遵循继承后的构造方法的特点)
public void eat(){
System.out.println("猫吃鱼");
}
}
调用:
public static void main(String[] args) {
Animal animal=new Cat(); // 对象一经创建就会执行父类中的构造方法
// eat 方法不可能执行父类,运行子类重写
animal.eat();
}
(3) 抽象类中可以不定义抽象方法
抽象类中,可以不定义抽象方法,但是,如果有抽象方法存在,这个类必须是抽象类
补充:
/**
* 抽象类中不定义抽象方法,用作适配器
* Web 开发中,浏览器请求的数据分为,部分是固定的,部分是可变的
*/
public abstract class Adapter {
public void gu(){
// 这里写的是固定的部分
}
public void ding(){
// 这里写的是固定的部分
}
/**
* 在子类重写中是可变的部分
*/
}
(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 没有重写
* 还是一个抽象的方法(必须要声明此类为抽象类,即加入 abstract 关键字)
*/
}
(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{
// 重写工作的抽象方法
@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");
c1.work();
Company c2 = new Financial();
c2.setName("李四");
c2.setId("财务部001");
c2.work();
}
4. 抽象类的使用意义:
继承下强制性的方法重写,保证其父类下的子类的功能的完整性。
三、 接口 interface
1. 接口无处不在
身边的接口有:笔记本上 USB 接口、HDMI、TypeC 接口、插座等
例如: USB 接口 : 连接鼠标、键盘、摄像头、手机、移动硬盘等
设备的工作原理不同,但是都可以连接到 USB 接口上,完成他的任务。说明 : 这些设备都满足 USB 的接口规范!!!
接口:就是一个规范,或者称为标准 ,无论什么设备,只要符合接口标准,就可以正常使用,因此接口的扩展性很强大。
2. Java 中接口的定义
- 当一个抽象类中的所有方法全部都是抽象方法的时候,可以将这个抽象类换一个更加贴切的名词,叫做接口,接口是特殊的抽象类。
- 定义接口:使用关键字 interface
语法规范:
public interface 接口名{}
- 接口在编译后,依然是 .class 文件 (JVM 只运行 .class 文件)
3. 接口中成员定义(JDK1.7版本)
(1) 成员变量:
- 成员变量的定义是有固定格式的
- 成员变量的修饰符是固定的
public static final
public static final 数据类型 变量名 = 值;
示例:
/**
* 自定义接口,接口的名字为 MyInterface
* 将关键字 class 改为 interface
* 注意:接口中成员变量的修饰符是固定的,不可以改变,所以完全可以不写
* 另外:还可以挑着写,并且没有顺序规定(建议按照顺序书写习惯)
*/
public interface MyInterface {
// 接口的成员变量 public static final
// final 最终修饰符,变量一经赋值,终身不变
public static final int A=1;
}
对于完全不写的情况,通过 javap(反编译 .class文件)仍然可以看到 public static final
的字样存在
(2) 成员方法:
- 成员方法的定义实际有固定格式的
- 成员方法的修饰符是固定的
public abstract
public abstract 返回值类型 方法名(参数列表);
示例:
public interface MyInterface {
// 接口的成员方法 public abstract 返回值类型 方法名(参数列表);
public abstract void MyInterface();
// 与接口中的成员变量一样,前面的修饰符是固定的,不可以改变,所以可以不写
}
4. 接口的使用方式
- 接口不能建立对象(实例化),不能 new 对象。
- 需要定义类,实现接口(继承类,在接口中称为实现,可以理解为继承)
- 实现接口,使用关键字 implements
- 实现的格式 :
public class 类 implements 接口名{}
- 重写接口中的抽象方法
- 创建子类的对象
示例:
接口:
public interface MyInterFace {
// 定义接口的成员变量
public static final int A=1;
// 定义接口的成员方法
public abstract void myInter();
}
接口的实现类:
/**
* 定义 MyInterFace 接口的 实现类
* 重写接口的抽象方法
*/
public class MyInterFaceImpl implements MyInterFace{
@Override
public void myInter() {
System.out.println("实现类实现接口,重写方法");
}
}
调用:
public static void main(String[] args) {
// 创建对象,多态性,创建接口实现类的对象
MyInterFace my=new MyInterFaceImpl();
my.myInter();
// 输出接口中的成员 A 的值
System.out.println(my.A);
// 这里不能更改 A 的值,但是可以进行运算
// 例如:int i = my.A + 1;
}
5. 接口的多实现
- 类和类之间单继承,具有局限性的问题。接口的出现,是对单继承的改良,它允许一个类同时实现多个接口。
假如存在继承下的多继承,进行对比:
继承下的多继承 | 接口的多实现 |
---|---|
禁止 | 允许 |
子类继承多个父类 | 实现连接着多个接口 |
父类中有可以实例化对象的能力,若多个父类中方法相同,子类全部继承,无法正常调用,出现冲突 | 接口中的方法是抽象的,没有主体,接口的实现需要重写方法,才可以正常调用 |
可以直接对父类建对象 | 不能对接口直接建对象 |
语法格式:
public class C implements A,B{}
(1) 实现类,重写实现的多有接口的抽象方法:
接口 A :
public interface A {
public abstract void a();
}
接口 B :
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();
}
- 弊端:接口越多,实现需要重写的方法就越多
(2) 冲突问题:
接口 D :
public interface D {
public abstract void d();
}
接口 E :
public interface E {
public abstract void d();
}
实现接口 D 和 E :
public class F implements D,E{
@Override
public void d() {
System.out.println("");
}
}
①这种两个接口方法一样,进行一次方法重写就可解决
②当接口 D 变为:public abstract void d();
、而接口 E 变为:public abstract int d();
、导致方法重写时出现冲突,无法实现,导致报错。
③当接口 D 变为:public abstract void d();
、而接口 E 变为:public abstract int d(int a);
、这样就可以实现进行两次方法重写就可以解决,这时就实现了方法的重载。