目录
多态
概述
同一个对象在不同时刻体现出来的不同状态。
举例:
猫可以是猫的类型。
Cat m = new Cat();
同时猫也是一种动物,也可以把猫称为动物。
Animal c = new Cat();
实现前提
- 有继承或者实现关系
- 有方法重写
- 有父类或父接口引用指向子类对象
class Father{
public void show(){
System.out.println("Show Father");
}
}
//继承关系
class Son extends Father{
//方法重写
public void show(){
System.out.println("Show Son");
}
}
class DtDemo{
public static void main(String[] args){
//要有父类引用指向子类对象
Father f = new Son();//Show Son
}
}
分类
- 具体类多态
class Fu{ }
class Zi extends Fu{
方法重写;
}
Fu f = new Zi();//调用
- 抽象类多态
abstract class Fu{ }
class Zi extends Fu{
方法重写;
}
Fu f = new Zi();//调用
- 接口多态
interface Fu{ }
class Zi implements Fu{
方法重写;
}
Fu f = new Zi();//调用
成员访问特点
(左边) Father f = new Son();(右边)
-
成员变量
编译看左边,运行看左边。
-
构造方法
子类的构造都会默认访问父类构造。
-
成员方法
编译看左边,运行看右边。
由于成员方法存在方法重写,覆盖了父类成员方法,所以运行看右边
-
静态方法
编译看左边,运行看左边。
静态与类相关,算不上重写,所以访问还是左边的
口诀的具体说明看例子:
class Father{
public int num = 100;
public void show(){
System.out.println("Show Father");
}
public static void function(){
System.out.println("function Father");
}
}
class Son extends Father{
public int num = 1000;//与父类的变量重名
public int num2 = 200; //子类自己的变量
//方法重写
public void show(){
System.out.println("Show Son");
}
public void method() {
System.out.println("Method Son");
}
public static void function(){
System.out.println("function Son");
}
}
class DtDemo{
public static void main(String[] args){
//要有父类引用指向子类对象
Father f = new Son();
System.out.println(f.num); //结果是:100 ,运行看左边
//下面这句会编译出错,无法找到符号num2,编译看左边。
//System.out.println(f.num2);
f.show(); //结果是:Show Son,运行看右边
//下面这句会编译出错,无法找到符号method,编译看左边。
//f.method();
f.function(); //结果是:function Father。运行看左边
}
}
个人理解:编译看左边,指的是Father f = new Son();创建的对象仍然是Father引用类型,如果对象f 调用的是Father类中没有的成员,则编译报错。运行看左边或者右边这样理解:把父类看成C中的结构体,成员变量看成结构体中的数据,成员方法看成函数指针,指针指向父类函数,静态方法看成只能指向父类函数的指针;子类继承自父类,可以看成子类中包含了父类这个结构体,且含有自己的数据;此时这句“Father f = new Son();”便可看成一个小的结构体的指针指向一个大的结构体,因此能访问到的成员变量和静态方法只有Father结构体自身的,所以运行看左边;但子类中存在方法重写,也就意味着父类中的函数指针此时指向了子类的函数,所以运行看右边。
此理解仅供参考,具体Java是怎么处理的,可参考:https://www.cnblogs.com/crane-practice/p/3671074.html
内存图
好处与弊端
- 好处:
- 提高代码的维护性(继承体现)
- 提高代码的扩展性(多态体现)
- 弊端:
- 父类不能使用子类的特有功能。
现象:子可以当作父使用,父不能当作子使用。
解决方法:把父类的引用强制转换为子类的引用(向下转型)
转型问题
- 向上转型:
从子到父: Father f = new Son();
(理解:一个小的结构体的指针指向一个大结构体,所以大结构体的部分内容对于该指针来说并不可见) - 向下转型
从父到子: Son s = (Son) f;
(理解:一个小的结构体的指针被强制转化成大结构体的指针,对大结构体的全部内容可见)
例子
class Animal{
public void eat() {
System.out.println("eat");
}
public void sleep() {
System.out.println("sleep");
}
}
//工具类
class AnimalTool{
public static void userCat(Cat cat) {
cat.eat();
cat.sleep();
}
public static void userDog(Dog dog) {
dog.eat();
dog.sleep();
}
public static void userAnimal(Animal animal) {
animal.eat();
animal.sleep();
}
}
class Dog extends Animal{
public void eat() {
System.out.println("狗吃肉");
}
public void sleep() {
System.out.println("狗站着睡觉");
}
}
class Cat extends Animal{
public void eat() {
System.out.println("猫吃鱼");
}
public void sleep() {
System.out.println("猫趴着睡觉");
}
}
class Pig extends Animal{
public void eat() {
System.out.println("猪吃白菜");
}
public void sleep() {
System.out.println("猪侧着睡觉");
}
public class DtDemo {
public static void main(String[] args) {
//一般模式
Cat c = new Cat();
c.eat();
c.sleep();
Cat c2 = new Cat();
c.eat();
c.sleep();
Cat c3 = new Cat();
c.eat();
c.sleep();
System.out.print("--------------------\n");
//问题来了,每次创建对象之后调用的方法是相似的,仅对象名不一样
//是否能改进呢?
//改进方法:创建工具类
//下面效果跟上面的效果一致
AnimalTool.userCat(c);
AnimalTool.userCat(c2);
AnimalTool.userCat(c3);
System.out.print("--------------------\n");
//类似的,对于狗,可以先通过在工具类中创建工具,再将创建好的对象传进去
Dog d = new Dog();
Dog d2 = new Dog();
Dog d3 = new Dog();
AnimalTool.userDog(d);
AnimalTool.userDog(d2);
AnimalTool.userDog(d3);
System.out.print("--------------------\n");
//新的问题:当有新的类继承自Animal类,我们就要在工具类中写新的方法供新的类使用
//我们能不能使用工具类的一个方法,让不同的类(继承自同一父类)都可以调用呢?
//从而对以后新的动物,也能使用
//解决方法:使用多态
Pig p = new Pig();
Pig p2 = new Pig();
Pig p3 = new Pig();
AnimalTool.userAnimal(p);
AnimalTool.userAnimal(p2);
AnimalTool.userAnimal(p3);
}
}
小结:
- 当应用中多次对相似的类调用一样的方法时,不妨将其封装成工具类
- 当继承自同一父类的多个子类调用同一方法时,不烦考虑多态
抽象类
定义
把多个具有共性的东西提取到一个类中,即继承的做法,但这些共性在某些时候,虽然方法声明一致,但具体的方法实现不同。所以在定义这些共性的方法时,不能给出具体的方法体。
而一个没有具体方法体的方法即是抽象方法。
在一个类中,如果有抽象方法,该类必须定义为抽象类
特点
- 抽象类和抽象方法必须使用关键字abstract修饰
- 抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
- 抽象类有构造方法,但不能实例化
- 抽象类的子类可以是
- 抽象类,可不重写抽象方法
- 具体类,这个类必须重写抽象类中的所有抽象方法
成员特点
- 成员变量
既可以是变量,也可以是常量
- 构造方法
有。带不带带参数均可。
用于子类访问父类数据的初始化 - 成员方法
既可以是抽象的,也可以是非抽象的
抽象类的成员方法特性:
- 抽象方法:强制要求子类做的事
- 非抽象方法:子类继承的事情,提高代码复用性
例子:
abstract class Animal {
public int age = 10;
public final String name = "Animal";
// public abstract void eat() {} //空方法体,这个会报错,抽象方法不能有主体
public abstract void eat();
public void show() {
System.out.println("Show");
}
public Animal() {
// TODO Auto-generated constructor stub
}
public Animal(String name, int age) {
}
}
//抽象类继承抽象类
abstract class Dog extends Animal {
}
//具体类继承抽象类
class Cat extends Animal {
@Override
public void eat() {
// TODO Auto-generated method stub
System.out.print("猫吃鱼");
}
}
public class AbstractDemo {
public static void main(String[] args) {
//Animal an = new Animal() ;
//Animal是抽象的,无法实例化
Animal animal = new Cat();
animal.eat();
}
}
注意事项
- 抽象类有构造方法,不能实例化,那么构造方法有什么作用?
用于子类访问父类数据的初始化
- 抽象类的实例化是通过具体的子类实现的,以多态的方式
例如:Animal a = new Cat();
- 一个类如果没有抽象方法,却定义为抽象类,有什么作用?
为了不让创建对象
- abstract不能和哪些关键字共存?
a:final 冲突
b:private 冲突
c:static 无意义
案例:
/**
*
* @author Administrator
*
* 需求:假如开发一个系统时需要对员工类进行设计,员工包含有3个属性:姓名,工号以及工资。
* 经理也是员工,除了含有员工的属性外,还有一个奖金属性。
* 请使用继承的思想设计出员工类和经理类,要求类中提供必要的方法进行属性访问。
*
* 分析:
* 普通员工类:
* 成员变量:姓名,工号,工资
* 成员方法:工作
* 经理类:
* 成员变量:姓名,工号,工资,奖金
* 成员方法:工作
*
实现:
员工类:
普通员工类:
经理类:
*/
//员工类
abstract class Employee{
private String name;
private String id;
private int salary;
public Employee() {
// TODO Auto-generated constructor stub
}
public Employee(String name,String id,int salary) {
// TODO Auto-generated constructor stub
this.name = name;
this.id = id;
this.salary = salary;
}
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 int getSalary() {
return salary;
}
public void setSalary(int salary) {
this.salary = salary;
}
public abstract void work();
}
//普通员工类
class Programmer extends Employee{
public Programmer() {
// TODO Auto-generated constructor stub
}
public Programmer(String name,String id,int salary) {
// TODO Auto-generated constructor stub
super(name, id, salary);
}
@Override
public void work() {
// TODO Auto-generated method stub
System.out.println("按照需求写代码");
}
}
//经理类
class Manager extends Employee{
private int bonus;
public Manager() {
// TODO Auto-generated constructor stub
}
public Manager(String name,String id,int salary,int bonus) {
super(name, id, salary);
this.bonus = bonus;
}
@Override
public void work() {
// TODO Auto-generated method stub
System.out.println("跟客户谈需求");
}
public int getBonus() {
return this.bonus;
}
public void setBonus(int bonus) {
this.bonus = bonus;
}
}
public class AbstractTest {
public static void main(String[] args) {
//普通员工测试
Employee emp = new Programmer();
emp.setName("Jame");
emp.setSalary(8000);
emp.setId("001");
System.out.println(emp.getName()+"---"+emp.getId()+"---"+emp.getSalary());
emp.work();
System.out.println("---------------------");
emp = new Programmer("Cherry", "003", 7500);
System.out.println(emp.getName()+"---"+emp.getId()+"---"+emp.getSalary());
emp.work();
System.out.println("---------------------");
//经理类测试
//由于用到了向下转型,不建议
emp = new Manager();
emp.setName("Tom");
emp.setId("005");
emp.setSalary(8500);
Manager manager = (Manager)emp;
manager.setBonus(2000);
System.out.println(manager.getName()+"---"+manager.getId()+"---"+manager.getSalary()+"---"+manager.getBonus());
emp.work();
System.out.println("---------------------");
//由于子类有特有的功能,所以我们推荐下面方式
Manager m = new Manager();
m.setName("Alice");
m.setId("007");
m.setSalary(9000);
m.setBonus(2500);
System.out.println(m.getName()+"---"+m.getId()+"---"+m.getSalary()+"---"+m.getBonus());
m.work();
System.out.println("---------------------");
m = new Manager("Helen", "009", 10000, 1500);
System.out.println(m.getName()+"---"+m.getId()+"---"+m.getSalary()+"---"+m.getBonus());
m.work();
System.out.println("---------------------");
}
}
运行结果:
接口
定义
对于某些类,仅仅提供该类本身具有的功能和属性,但现实中,该类总是会有机会被赋予额外的功能。
这种扩展功能,Java提供了接口表示。
特点
- 接口用关键字interface 修饰:
interface 接口名{ } - 类实现接口用implements 修饰:
class 类名 implements 接口名{ } - 接口不能实例化,只能按多态方式实例
- 接口的实现类可以是:
- 抽象类,意义不大
- 具体类,这个类必须重写接口中的所有抽象方法
例子:
//定义动物培训的接口
interface AnimalTrain{
public abstract void jump();
}
//抽象类实现接口
abstract class Dog implements AnimalTrain{
}
//具体类实现接口
class Cat implements AnimalTrain{
public void jump(){
System.out.println("猫可以跳高了");
}
}
class InterfaceDemo{
public static void main(String[] args){
//AnimalTrain是抽象的,无法实例化
//AnimalTrain at = new AnimalTrain();
//at.jump();
AnimalTrain at = new Cat();
at.jump();//猫可以跳高了
}
}
成员特点
- 成员变量
只能是常量,并且是静态的。
默认修饰符:public static final - 构造方法
没有构造方法
- 成员方法
只能是抽象方法
默认修饰符:public abstract
例子:
interface Inter{
public int num = 10;
public final num2 = 20;
public static final int num3 = 30;
//错误:需要标识符
//public Inter(){}
//接口中方法不能有主体
//public void show(){}
public abstract void show();
}
//接口名+Impl这种格式是接口的实现类格式
/*
class InterImpl implements Inter{
public InterImpl(){
super();
}
public void show(){}
}
*/
//等同于上面注释部分
/*
所有的类都默认继承自一个类:Object
类Object 是类层次结构的根类,每个类都使用Object作为超类
*/
class InterImpl extends Object implements Inter{
public InterImpl(){
super();
}
public void show(){}
}
//测试类
class InterfaceDemo2{
public static void main(String[] args){
//创建对象
Inter i = new InterImpl();
System.out.println(i.num);
System.out.println(i.num2);
//i.num = 100;
//i.num2 = 200;
//System.out.println(i.num);//无法为最终变量num分配值
//System.out.println(i.num);//无法为最终变量num2分配值
System.out.println(Inter.num);
System.out.println(Inter.num2);
}
}
类与类,类与接口,接口与接口的关系
- 类与类
继承关系:只能单继承或多层继承 - 类与接口
实现关系:可以单实现或多实现
一个类可以在继承另一个类的同时,实现多个接口
- 接口与接口
继承关系:可以单继承或多继承
例子:
interface Father{
public abstract void show();
}
interface Mother{
public abstract void show2();
}
interface Sister extends Father,Mother{
}
class Son extends Object implements Father,Mother{
public void show(){
System.out.println("Show Son");
}
public void show2(){
System.out.println("Show2 Son");
}
}
class InterdaceDemo3{
public static void main(String[] args){
Father f = new Son();
f.show();
//f.show2(); //报错
Mother m = new Son();
//m.show(); //报错
m.show2();
}
}
抽象类和接口的区别
成员区别
区别 | 抽象类 | 接口 |
---|---|---|
成员变量 | 可以是变量,也可以是常量 | 只可以是常量 |
构造方法 | 有 | 无 |
成员方法 | 可以抽象,也可以非抽象 | 只可以是抽象 |
关系区别
区别 | 关系 | 说明 |
---|---|---|
类与类 | 继承 | 只能单继承 |
类与接口 | 实现 | 可单实现或多实现 |
接口与接口 | 继承 | 可单继承或多继承 |
设计理念区别
- 抽象类
被继承体现的是:“is a”的关系。抽象类定义的是该继承体系的共性功能。
- 接口
被实现体现的是:“like a”的关系。接口中定义的是该继承体系的扩展功能。
案例:
/**
* 老师和学生案例,加入抽烟的额外功能
* 分析:从具体到抽象
* 老师:姓名,年龄,吃饭,睡觉
* 学生:姓名,年龄,吃饭,睡觉
*
* 由于有共性功能,我们提出一个父类:人类
*
* 人类:
* 姓名,年龄
* 吃放();
* 睡觉(){ }
*
* 抽烟的额外功能不是人一开始就具备的,所以把它定义为接口
* 抽烟接口
*
* 部分老师抽烟:实现抽烟接口
* 部分学生抽烟:实现抽烟接口
*
* 实现:从抽象到具体
* 使用:具体
*/
//定义抽烟接口
interface Smoking{
//抽烟的抽象方法
public abstract void smoke();
}
//定义抽象人类
abstract class Person{
private String name;
private int age;
public Person() {
// TODO Auto-generated constructor stub
}
public Person(String name,int age) {
// TODO Auto-generated constructor stub
this.name = name;
this.age = age;
}
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 abstract void eat();
public void sleep() {
System.out.println("睡觉了");
}
}
//具体老师类
class Teacher extends Person{
public Teacher() {
// TODO Auto-generated constructor stub
}
public Teacher(String name,int age) {
// TODO Auto-generated constructor stub
super(name, age);
}
public void eat() {
System.out.println("吃红烧肉");
}
}
//具体学生类
class Student extends Person{
public Student() {
// TODO Auto-generated constructor stub
}
public Student(String name,int age) {
// TODO Auto-generated constructor stub
super(name, age);
}
public void eat() {
System.out.println("吃鸡腿");
}
}
//抽烟的老师
class SmokeTeacher extends Teacher implements Smoking{
public SmokeTeacher() {
// TODO Auto-generated constructor stub
}
public SmokeTeacher(String name,int age) {
// TODO Auto-generated constructor stub
super(name, age);
}
@Override
public void smoke() {
// TODO Auto-generated method stub
System.out.println("抽烟的老师");
}
}
//抽烟的学生
class SmokeStudent extends Student implements Smoking{
public SmokeStudent() {
// TODO Auto-generated constructor stub
}
public SmokeStudent(String name,int age) {
// TODO Auto-generated constructor stub
super(name, age);
}
@Override
public void smoke() {
// TODO Auto-generated method stub
System.out.println("抽烟的学生");
}
}
public class InterfaceDemo {
public static void main(String[] args) {
//测试学生
SmokeStudent ss = new SmokeStudent();
ss.setName("Tom");
ss.setAge(18);
System.out.println(ss.getName()+"---"+ss.getAge());
ss.eat();
ss.sleep();
ss.smoke();
System.out.println("---------------------");
ss = new SmokeStudent("Alice", 16);
System.out.println(ss.getName()+"---"+ss.getAge());
ss.eat();
ss.sleep();
ss.smoke();
System.out.println("---------------------");
//测试老师
SmokeTeacher st = new SmokeTeacher();
st.setName("Helen");
st.setAge(40);
System.out.println(st.getName()+"---"+st.getAge());
st.eat();
st.sleep();
st.smoke();
System.out.println("---------------------");
st = new SmokeTeacher("Amami", 42);
System.out.println(st.getName()+"---"+st.getAge());
st.eat();
st.sleep();
st.smoke();
System.out.println("---------------------");
}
}
多态中的形式参数和返回值问题
形式参数问题
方法中的形式参数为引用类型时,所需传入的实际参数为:
- 具体类名:需要该类的对象(可用匿名对象)
- 抽象类名:需要该类的子类对象
- 接口名:需要该接口的实现类对象
例子:
//抽象类
abstract class Person{
public abstract void study();
}
class PersonDemo{
public void method(Person p){
p.study();
}
}
//具体类
class Student extends Person{
public void study(){
System.out.println("好好学习,天天向上");
}
}
class StudentDemo{
public void method(Student s){
s.study();
}
}
//接口
interface Love{
public abstract void love();
}
class LoveDemo{
public void method(Love l){
l.love();
}
}
class Teacher implements Love{
public void love(){
System.out.println("老师爱学生");
}
}
class ParameterTest{
public static void main(String[] args){
//具体类测试
//普通方式
Student s = new Student();
StudentDemo sd = new StudentDemo();
sd.method(s);
//匿名方式
new StudentDemo().method(new Student());
//抽象类测试
PersonDemo pd = new PersonDemo();
Person p = new Student();
pd.method(p);
//接口测试
LoveDemo ld = new LoveDemo();
Love l = new Teacher();
ld.method(l);
}
}
返回值问题
方法中的返回值为引用类型时,所需返回的类型为:
- 具体类名:返回该类的对象(可用匿名对象)
- 抽象类名:返回该类的子类对象
- 接口名:返回该接口的实现类对象
链式编程
每次调用方法完毕,返回的是一个对象,且该对象不再使用时,则可以考虑链式编程。
格式:对象.方法1().方法2()…方法n();
这种用法:方法1()调用完毕后,返回的应该是一个对象;
方法2()调用完毕后,返回的应该是一个对象;
方法n()调用完毕后,返回可以是对象,也可以不是对象;
例子:
//抽象类
abstract class Person{
public abstract void study();
}
class PersonDemo{
public Person getPerson(){
//Person p = new Student();
//return p;
return new Student();
}
}
//具体类
class Student extends Person{
public void study(){
System.out.println("好好学习,天天向上");
}
}
class StudentDemo{
public Student getStudent(){
//Student s = new Student();
//return s;
return new Student();
}
}
//接口
interface Love{
public abstract void love();
}
class LoveDemo{
public Love getLove(){
//Love l = new Teacher();
//return l;
//等同于下面
return new Teacher();
}
}
class Teacher implements Love{
public void love(){
System.out.println("好好学习天天向上");
}
}
class ReturnTest{
public static void main(String[] args){
//具体类测试
StudentDemo sd = new StudentDemo();
Student s = sd.getStudent();//new Student();
s.study();
//链式调用
sd.getStudent().study();
//抽象类测试
PersonDemo pd = new PersonDemo();
Person p = pd.getPerson(); // new Student(); 多态
p.study();
//链式调用
pd.getPerson().study();
//接口测试
LoveDemo ld = new LoveDemo();
Love l = ld.getLove(); //new Teacher(); 多态
l.love();
//链式调用
ld.getLove().love();
}
}