多态简介

概念:多态意味着允许不同类的对象对同一消息做出不同的响应
分类:多态分为编译时多态和运行时多态,一般我们说java的多态,就是指运行时多态
— 编译时多态,通过方法重载在设计时实现多态
— 运行时多态,程序运行时动态决定调用哪个方法
条件:满足继承关系,父类引用指向子类对象
实现:通过子类重写父类方法,实现多态

  1. package com.test;
  2. import com.animal.Animal;
  3. import com.animal.Cat;
  4. import com.animal.Dog;
  5. public class Test {
  6. public static void main(String[] args) {
  7. Animal one = new Animal();
  8. Animal two = new Cat();
  9. Animal three = new Dog();
  10. one.eat(); // 动物,会吃东西
  11. two.eat(); // 猫,会吃东西
  12. three.eat(); // 狗,会吃东西
  13. }
  14. }

通过这个例子可以看到,对象one,two,three虽然都是Animal类型,但是由于指向的对象的类型是不同的,导致eat方法执行不同的处理,这就是多态

向上转型

  1. 又称向上转型,隐式转型,自动转型
  2. 意思是父类引用指向子类实例
  3. 可以调用父类派生的方法
  4. 可以调用子类重写父类的方法,且执行的是子类重写后的处理
  5. 无法调用子类独有的方法
  6. 父类中的静态方法无法被子类重写,所有向上转型之后,只能调用到父类原有的静态方法
  1. package com.test;
  2. import com.animal.Animal;
  3. import com.animal.Cat;
  4. public class Test {
  5. public static void main(String[] args) {
  6. Animal one = new Animal();
  7. Animal two = new Cat();
  8. two.setAge(2); // setAge是父类派生的方法,可以调用
  9. two.eat(); // eat是子类重写的方法,可以调用,且执行的是子类重写后的处理
  10. // two.setWeight(17); // 报错,无法调用子类独有的方法
  11. }
  12. }

向下转型

  1. 意思是子类引用指向父类实例
  2. 需要进行强制类型转换
  3. 可以调用子类特有的方法
  4. 跟用子类直接实例化子类对象没有什么区别
  1. package com.test;
  2. import com.animal.Animal;
  3. import com.animal.Cat;
  4. public class Test {
  5. public static void main(String[] args) {
  6. Animal one = new Cat();
  7. Cat two = (Cat) one;
  8. two.eat(); // eat是子类重写的方法,可以调用,且执行的是子类重写后的处理
  9. two.setWeight(12.0); // setWeight是子类特有的方法,可以调用
  10. System.out.println(two.getWeight()); // getWeight是子类特有的方法,可以调用
  11. // 下面这样是不行的,会编译报错
  12. // Cat three = (Cat) new Animal();
  13. }
  14. }

instanceof

  1. 判断对象是否是某个类的实例,如果是返回true,反之返回false
  2. 如果 A instanceof B == ture,那么A instanceof B的父类,结果也是 true
  3. 常用于强制类型转换之前的类型判断

image.png

  1. package com.imooc.test;
  2. import com.imooc.animal.Animal;
  3. import com.imooc.animal.Cat;
  4. public class Test {
  5. public static void main(String[] args) {
  6. Animal one = new Cat();
  7. if (one instanceof Cat) {
  8. Cat two = (Cat) one;
  9. two.eat();
  10. two.setWeight(12.0);
  11. System.out.println(two.getWeight());
  12. }
  13. // 条件成立
  14. if (one instanceof Animal) {
  15. System.out.println("Animal");
  16. }
  17. // 条件成立
  18. if (one instanceof Object) {
  19. System.out.println("Object");
  20. }
  21. }
  22. }

抽象类和抽象方法

我们先来看一种场景,以下代码编译是不会出错的,因为父类的确有eat这个方法,但是这样做没有实际意义。
一般来说,子类(比如Cat)会重写eat方法,所以调用子类的eat方法才有实际意义。

  1. // 定义父类 Animal
  2. public class Animal {
  3. private String name;
  4. public Animal() {
  5. super();
  6. }
  7. public String getName() {
  8. return name;
  9. }
  10. public void setName(String name) {
  11. this.name = name;
  12. }
  13. public void eat() {
  14. System.out.println("动物,会吃东西");
  15. }
  16. }
  17. // 使用Animal类进行实例化
  18. Animal pet = new Animal("花花",2);
  19. pet.eat();

怎么才能在编码阶段就提示这种没有意义的做法呢,这就要用到抽象类里了,请继续往下看。

作用:Java中使用抽象类,可以限制实例化

应用场景:
某个父类只是知道其子类应该包含怎样的方法,但无法准确知道这些子类如何实现这些方法

使用规则:

  1. 使用abstract关键字定义抽象类
  2. 抽象类不能直接实例化,只能被继承,可以通过向上转型完成对象实例
  3. 使用sbstract关键字定义抽象方法,不需要具体实现
  4. 包含抽象方法的类是抽象类
  5. 抽象类中可以没有抽象方法
  6. static final private不能与abstract并存
  1. // 定义抽象父类 Animal
  2. public abstract class Animal {
  3. private String name;
  4. public Animal() {
  5. super();
  6. }
  7. public String getName() {
  8. return name;
  9. }
  10. public void setName(String name) {
  11. this.name = name;
  12. }
  13. public abstract void eat(); // 抽象方法
  14. }
  15. // 使用Animal类进行实例化
  16. // Animal pet = new Animal("花花",2); => 这样是会报错的
  17. Animal pet = new Cat("花花",2); => 这样是这正确的用法

接口

有些类不能抽取出一个公共的父类,但是它们之间又具有一些相同的行为,怎么才能使它们关联起来?
在java中,根据行为建立它们之间的联系,就需要用到接口。

eclipse创建接口的方法:New => Interface
#一般接口的名字以I开头
image.png

比如:我们来看看怎样根据行为建立手机,智能手表,照相机之间的联系
智能手机拥有的行为:发短信,打电话,拍照
照相机拥有的行为 :拍照

拍照接口:IPhoto.java

  1. package com.dev;
  2. /**
  3. * 具有照相能力的接口
  4. * @author 57681
  5. *
  6. */
  7. public interface IPhoto {
  8. public void photo();
  9. }

照相机类:Camera.java

  1. package com.dev;
  2. public class Camera implements IPhoto{
  3. @Override
  4. public void photo() {
  5. System.out.println("照相机可以拍照");
  6. }
  7. // public void photo() {
  8. // System.out.println("照相机可以拍照");
  9. // }
  10. }

智能手机类:SmartPhone.java

  1. package com.dev;
  2. public class SmartPhone implements IPhoto {
  3. public void message() {
  4. System.out.println("智能手机可以发短信");
  5. }
  6. public void call() {
  7. System.out.println("智能手机可以打电话");
  8. }
  9. // public void photo() {
  10. // System.out.println("智能手机可以拍照");
  11. // }
  12. @Override
  13. public void photo() {
  14. System.out.println("智能手机可以拍照");
  15. }
  16. }

测试接口

  1. package com.imooc;
  2. import com.dev.Camera;
  3. import com.dev.IPhoto;
  4. import com.dev.SmartPhone;
  5. public class PhotoTest {
  6. public static void main(String[] args) {
  7. IPhoto ip = new Camera();
  8. ip.photo(); // 照相机可以拍照
  9. ip = new SmartPhone();
  10. ip.photo(); // 智能手机可以拍照
  11. }
  12. }

接口成员(抽象方法&常量)

  1. 接口定义了某一批类所需要遵守的规范
  2. 接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,它只规定这些类里必须提供某些方法

接口:INet

  1. package com.dev;
  2. // 接口访问修饰符:public 默认
  3. public interface INet {
  4. // 接口类中的抽象方法可以不写abstract关键字
  5. // 方法的访问修饰符默认public
  6. void network();
  7. void connection();
  8. // 接口中可以包含常量,默认public static final
  9. int TEMP = 20;
  10. }

电脑类:Compute
#必须实现接口中所有的抽象方法

  1. package com.dev;
  2. public class Compute implements INet {
  3. @Override
  4. public void network() {
  5. System.out.println("network");
  6. }
  7. @Override
  8. public void connection() {
  9. System.out.println("connection");
  10. }
  11. }

测试接口

  1. package com.imooc;
  2. import com.dev.Compute;
  3. import com.dev.INet;
  4. public class NetTest {
  5. public static void main(String[] args) {
  6. INet net = new Compute();
  7. net.network(); // network
  8. net.connection(); // connection
  9. System.out.println(net.TEMP); // 20
  10. }
  11. }

接口成员(默认方法&静态方法)

接口:INet

  1. package com.dev;
  2. // 接口访问修饰符:public 默认
  3. public interface INet {
  4. // default:默认方法 可以带方法体 jdk1.8后新增
  5. // 可以在实现类中重写,并可以通过接口的引用调用
  6. default void start() {
  7. System.out.println("我是接口中的默认方法");
  8. }
  9. // static:静态方法 可以带方法体 jdk1.8后新增
  10. // 不可以在实现类中重写,可以通过接口名调用
  11. static void stop() {
  12. System.out.println("我是接口中的静态方法");
  13. }
  14. }

电脑类:Compute

  1. package com.dev;
  2. public class Compute implements INet {
  3. @Override
  4. public void start() {
  5. INet.super.start(); // 调用接口中默认的方法
  6. System.out.println("custom");
  7. }
  8. }

测试接口

  1. package com.imooc;
  2. import com.dev.Compute;
  3. import com.dev.INet;
  4. public class NetTest {
  5. public static void main(String[] args) {
  6. INet net = new Compute();
  7. net.start(); // 我是接口中的默认方法 custome
  8. INet.stop(); // 我是接口中的静态方法
  9. System.out.println(net.TEMP); // 20
  10. }
  11. }

多接口中重名默认方法的解决方案

接口INet和接口IPhoto同时拥有默认方法start

接口INet

  1. package com.dev;
  2. // 接口访问修饰符:public 默认
  3. public interface INet {
  4. default void start() {
  5. System.out.println("我是接口中的默认方法");
  6. }
  7. }

接口IPhoto

  1. package com.dev;
  2. /**
  3. * 具有照相能力的接口
  4. * @author 57681
  5. *
  6. */
  7. public interface IPhoto {
  8. default void start() {
  9. System.out.println("我是IPhoto接口中的默认方法");
  10. }
  11. }

电脑类:Compute
#实施接口INet和接口IPhoto

  1. package com.dev;
  2. public class Compute implements INet, IPhoto {
  3. // 这里如果不定义自己的start方法,是会报错的
  4. public void start() {
  5. System.out.println("Compute中的默认方法");
  6. }
  7. }

注意:比如Compute类继承了一个父类,然后父类中有start方法,那么即使不定义自己的start方法,也是不会报错的。实例对象调用start方法的话,执行的就是父类的start方法。

测试接口

  1. package com.imooc;
  2. import com.dev.Compute;
  3. import com.dev.INet;
  4. import com.dev.IPhoto;
  5. public class NetTest {
  6. public static void main(String[] args) {
  7. INet net = new Compute();
  8. net.start(); // Compute中的默认方法
  9. IPhoto photo = new Compute();
  10. photo.start(); // Compute中的默认方法
  11. }
  12. }

多接口中重名常量的解决方案

测试结果:出错
原因分析:接口One和接口Two中有相同的常量x,FinalTest类中不知道该使用哪个,所以会出错

  1. package com.imooc;
  2. interface One{
  3. static int x = 11;
  4. }
  5. interface Two{
  6. static int x = 22;
  7. }
  8. class Three{
  9. public static final int x = 33;
  10. }
  11. public class FinalTest implements One, Two {
  12. public void test() {
  13. System.out.println(x);
  14. }
  15. public static void main(String[] args) {
  16. new FinalTest().test();
  17. }
  18. }

测试结果:出错
原因分析:跟重名方法不同,即使父类中存在重名常量,也不会使用父类的常量。在这里父类不好使。

  1. package com.imooc;
  2. interface One{
  3. static int x = 11;
  4. }
  5. interface Two{
  6. static int x = 22;
  7. }
  8. class Three{
  9. public static final int x = 33;
  10. }
  11. public class FinalTest extends Three implements One, Two {
  12. public void test() {
  13. System.out.println(x);
  14. }
  15. public static void main(String[] args) {
  16. new FinalTest().test();
  17. }
  18. }

测试结果:正确

  1. package com.imooc;
  2. interface One{
  3. static int x = 11;
  4. }
  5. interface Two{
  6. static int x = 22;
  7. }
  8. class Three{
  9. public static final int x = 33;
  10. }
  11. public class FinalTest extends Three implements One, Two {
  12. public static final int x = 44;
  13. public void test() {
  14. System.out.println(x);
  15. }
  16. public static void main(String[] args) {
  17. new FinalTest().test();
  18. }
  19. }

接口的继承

IFather1.java

  1. package com.dev;
  2. public interface IFather1 {
  3. void run();
  4. }

IFather2.java

  1. package com.dev;
  2. public interface IFather2 {
  3. void eat();
  4. }

ISon.java:继承IFather1,IFather2
#子类只能继承一个父类,但是接口可以继承多个接口

  1. package com.dev;
  2. public interface ISon extends IFather1, IFather2 {
  3. void fly();
  4. }

Demo类:实现接口ISon,需要重写所有的方法

  1. package com.dev;
  2. public class Demo implements ISon {
  3. @Override
  4. public void run() {
  5. // TODO Auto-generated method stub
  6. }
  7. @Override
  8. public void eat() {
  9. // TODO Auto-generated method stub
  10. }
  11. @Override
  12. public void fly() {
  13. // TODO Auto-generated method stub
  14. }
  15. }

内部类

内部类的定义:

  • 在java中,可以将一个类定义在另一个类的里面或者一个方法里面,这样的类成为内部类
  • 与之对应,包含内部类的类被成为外部类

内部类的好处:
因为内部类隐藏在外部类之内,更好的实现了数据隐藏。

内部类的分类:
成员内部类,静态内部类,方法内部类,匿名内部类

成员内部类(普通内部类)

Person.java

  1. package com.people;
  2. // 外部类
  3. public class Person {
  4. public int age;
  5. public Heart getHeart() {
  6. new Heart().temp = 12;
  7. // temp; 无法识别
  8. return new Heart();
  9. }
  10. public void eat() {
  11. System.out.println("人会吃东西");
  12. }
  13. // 成员内部类
  14. // 1. 内部类在外部使用时,无法直接实例化,需要借由外部类信息才能完成实例化
  15. // 2. 内部类的访问修饰符,可以任意,但是访问范围会受到影响
  16. // 3. 内部类可以直接访问外部类的成员,如果出现同名属性,优先访问内部类中定义的
  17. // 4. 可以使用外部类.this.成员的方式,访问外部类中同名的信息
  18. // 5. 外部类访问内部类信息,需要通过内部类实例,无法直接访问
  19. // 6. 内部类编译后.class文件命名:外部类$内部类.class
  20. public class Heart {
  21. int age = 13;
  22. int temp = 22;
  23. public void eat() {
  24. System.out.println("测试eat()");
  25. }
  26. public String beat() {
  27. Person.this.eat();
  28. // return age + "心脏在跳动";
  29. return Person.this.age + "岁的心脏在跳动";
  30. }
  31. }
  32. }

PeopleTest.java

  1. package com.people;
  2. public class PeopleTest {
  3. public static void main(String[] args) {
  4. Person lili = new Person();
  5. lili.age = 12;
  6. // 获取内部类对象实例,方式1:new 外部类.new 内部类
  7. Person.Heart myHeart = new Person().new Heart();
  8. System.out.println(myHeart.beat());
  9. // 获取内部类对象实例,方式2:外部类对象.new 内部类
  10. myHeart = lili.new Heart();
  11. System.out.println(myHeart.beat());
  12. // 获取内部类对象实例,方式3:外部类对象.获取方法
  13. myHeart = lili.getHeart();
  14. System.out.println(myHeart.beat());
  15. }
  16. }

静态内部类

Person.java

  1. package com.people;
  2. // 外部类
  3. public class Person {
  4. int age;
  5. public Heart getHeart() {
  6. new Heart().temp = 12;
  7. return new Heart();
  8. }
  9. public void eat() {
  10. System.out.println("人会吃东西");
  11. }
  12. // 成员内部类
  13. // 1. 静态内部类中,只能直接访问外部类的静态方法,如果需要调用非静态方法,可以通过对象实例
  14. // 2. 静态内部类对象实例时,可以不依赖于外部类对象
  15. // 3. 可以通过外部类.内部类.静态成员的方式,访问内部类中的静态成员
  16. // 4. 当内部类属性与外部类属性同名时,默认直接调用内部类中的成员
  17. // 5. 如果需要访问外部类的静态属性,则可以通过 外部类.属性的方式
  18. // 5. 如果需要访问外部类的非静态属性,则可以通过 new 外部类().属性的方式
  19. static class Heart {
  20. public static int age = 13;
  21. int temp = 22;
  22. public static void say() {
  23. System.out.println("hello");
  24. }
  25. public String beat() {
  26. new Person().eat();
  27. return new Person().age + "岁的心脏在跳动";
  28. }
  29. }
  30. }

PeopleTest.java

  1. package com.people;
  2. public class PeopleTest {
  3. public static void main(String[] args) {
  4. Person lili = new Person();
  5. lili.age = 12;
  6. // 获取静态内部类对象实例
  7. Person.Heart myHeart = new Person.Heart();
  8. System.out.println(myHeart.beat());
  9. Person.Heart.say();
  10. }
  11. }

方法内部类(局部内部类)

Person.java

  1. package com.people;
  2. // 外部类
  3. public class Person {
  4. int age;
  5. // 方法内部类
  6. // 1. 定义在方法内部,作用范围也在方法内
  7. // 2. 和方法内部成员使用规则一样,class前面不可以添加public,private,protected,static
  8. // 3. 类中不能包含静态成员
  9. // 4. 类中可以包含final,abstract修饰的成员
  10. public Object getHeart() {
  11. class Heart {
  12. public int age = 13;
  13. public void say() {
  14. System.out.println("hello");
  15. }
  16. public String beat() {
  17. new Person().eat();
  18. return new Person().age + "岁的心脏在跳动";
  19. }
  20. }
  21. return new Heart().beat();
  22. }
  23. public void eat() {
  24. System.out.println("人会吃东西");
  25. }
  26. }

PeopleTest.java

  1. package com.people;
  2. public class PeopleTest {
  3. public static void main(String[] args) {
  4. Person lili = new Person();
  5. lili.age = 12;
  6. System.out.println(lili.getHeart());
  7. }
  8. }

匿名内部类

将类的定义与类的创建,放到一起完成
用匿名内部类可以简化抽象类和接口实现的操作

适用场景:

  • 只用到类的一个实例
  • 类在定义后马上用到
  • 给类命名并不会导致代码更容易被理解

例子:根据传入的不同的人的类型,调用对应的read方法
Person.java

  1. package com.anonymous;
  2. public abstract class Person {
  3. private String name;
  4. public Person() {}
  5. public String getName() {
  6. return name;
  7. }
  8. public void setName(String name) {
  9. this.name = name;
  10. }
  11. public abstract void read();
  12. }

Man.java

  1. package com.anonymous;
  2. public class Man extends Person {
  3. @Override
  4. public void read() {
  5. // TODO Auto-generated method stub
  6. System.out.println("男生喜欢看科幻类书籍");
  7. }
  8. }

Woman.java

  1. package com.anonymous;
  2. public class Woman extends Person {
  3. @Override
  4. public void read() {
  5. // TODO Auto-generated method stub
  6. System.out.println("女生喜欢言情小说");
  7. }
  8. }

PersonTest.java
#方案1:

  1. package com.anonymous;
  2. public class PersonTest {
  3. public void getRead(Man man) {
  4. man.read();
  5. }
  6. public void getRead(Woman woman) {
  7. woman.read();
  8. }
  9. public static void main(String[] args) {
  10. PersonTest test = new PersonTest();
  11. Man one = new Man();
  12. Woman two = new Woman();
  13. test.getRead(one);
  14. test.getRead(two);
  15. }
  16. }

方案2

  1. package com.anonymous;
  2. public class PersonTest {
  3. public void getRead(Person person) {
  4. person.read();
  5. }
  6. public static void main(String[] args) {
  7. PersonTest test = new PersonTest();
  8. Man one = new Man();
  9. Woman two = new Woman();
  10. test.getRead(one);
  11. test.getRead(two);
  12. }
  13. }

方案3(匿名内部类)

  • 匿名内部类没有类型名称,实例对象名称
  • 编译后的文件命名:外部类$数字.class
  • 无法使用private,public,protected,abstract,static修饰
  • 无法编写构造方法,可以添加构造代码块
  • 不能出现静态成员
  • 匿名内部类可以实现接口也可以继承父类,但是不可兼得 ```java package com.anonymous;

public class PersonTest {

  1. public void getRead(Person person) {
  2. person.read();
  3. }
  4. public static void main(String[] args) {
  5. PersonTest test = new PersonTest();
  6. test.getRead(new Person() {
  7. @Override
  8. public void read() {
  9. // TODO Auto-generated method stub
  10. System.out.println("男生喜欢看科幻类书籍");
  11. }
  12. });
  13. test.getRead(new Person() {
  14. @Override
  15. public void read() {
  16. // TODO Auto-generated method stub
  17. System.out.println("女生喜欢看科幻类书籍");
  18. }
  19. });
  20. }

}

  1. <a name="os3TJ"></a>
  2. ## 知识巩固
  3. 问题:请写出程序输出结果<br />答案:1,4,2,3,5,6<br />分析:<br />//1. 静态优先<br />//2. 父类优先<br />//3. 非静态块优先于构造函数
  4. ```java
  5. package com.imooc.interview;
  6. //请写出程序输出结果
  7. //1. 静态优先
  8. //2. 父类优先
  9. //3. 非静态块优先于构造函数
  10. public class ExecutionSequence {
  11. public static void main(String[] args) {
  12. new GeneralClass();
  13. }
  14. }
  15. class ParentClass{
  16. static {
  17. System.out.println("①我是父类静态块");
  18. }
  19. {
  20. System.out.println("②我是父类非静态块");
  21. }
  22. public ParentClass(){
  23. System.out.println("③我是父类构造函数");
  24. }
  25. }
  26. class GeneralClass extends ParentClass{
  27. static{
  28. System.out.println("④我是子类静态块");
  29. }
  30. {
  31. System.out.println("⑤我是子类非静态块");
  32. }
  33. public GeneralClass(){
  34. System.out.println("⑥我是子类构造函数");
  35. }
  36. }