Java的多态
多种形态,同一种方式在不同的场景中得到不同的结果.
- 编译时多态[少部分]
- 通过方法的重载实现
- 运行时多态[大部分]
- 决定了调用哪个类里边定义的方法
多态的必要条件:
- 满足继承关系
- 父类应用执行子类对象
设计:
Animal:
name
month
eat()
getName()
getMonth()
getWeight()
Cat: -> Animal
weight
eat()
run()
Cat()
Dog: -> Animal
sex
eat()
sleep()
Dog()
向上转型
Animal
package com.animal;
public class Animal {
private String name;
private int month;
public Animal() {
}
public Animal(String name, int month) {
this.setName(name);
this.setMonth(month);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public void eat(){
System.out.println("动物都有吃的能力");
}
}
Cat
package com.animal;
public class Cat extends Animal {
private double weight;
public Cat() {
}
public Cat(String name, int month, double weight) {
super(name, month);
this.setWeight(weight);
}
public void run() {
System.out.println("猫在跑~~~~");
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
@Override
public void eat() {
System.out.println("猫吃鱼~~~~");
}
public static void say(){
System.out.println("Cat say()");
}
}
Dog
package com.animal;
public class Dog extends Animal {
private String sex;
public Dog() {
}
public Dog(String name, int month, String sex) {
super(name, month);
this.setSex(sex);
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public void eat() {
System.out.println("狗吃肉~~~");
}
public void sleep() {
System.out.println("小狗睡觉~~~~");
}
public static void say() {
System.out.println("Dog say()");
}
}
AnimalTest
package com.test;
import com.animal.Animal;
import com.animal.Cat;
import com.animal.Dog;
public class AnimalTest1 {
public static void main(String[] args) {
Animal one = new Animal();
// 子类对象转型为父类对象<向上转型> 父类的引用执行子类实例
Animal two = new Cat();
Animal three = new Dog();
// 虽然都是Animal实例,但是调用的是相应类的eat()方法
one.eat();
two.eat();
three.eat();
one.say();
two.say();
three.say();
}
}
父类的的引用指向子类的实例对象,父类是小范围,子类对父类进行了扩展,one two three
三个实例都可以调用
父类Animal
的所有公开接口,但是不可以调用超出父类范围,子类特有的方法
注意: Animal
的静态方法say
在多态的情况下,调用的是Animal
的say()而不是相应子类的say
,静态方法无法展现多态
注意:父类中的 static
方法在子类中是不能被重写的,只能被继承调用,所以才会出现上述的结果,如果想用子类的静态方法,那么只能使用
向下转型的方式,来实现调用可以被继承的,重写的方法
向下转型
package com.test;
import com.animal.Animal;
import com.animal.Cat;
import com.animal.Dog;
public class AnimalTest2 {
// 向下转型,强制类型转换
// 子类引用指向父类实例,可以调用子类中继承父类的方法,也可以调用相应子类中的特有方法
public static void main(String[] args) {
Animal one = new Cat();
Animal two = new Dog();
Cat tmp_1 = (Cat) one;
Dog tmp_2 = (Dog) two;
tmp_1.eat();
tmp_1.getWeight();
one.eat();
tmp_2.eat();
tmp_2.getSex();
two.eat();
}
}
instanceof运算符,判断左侧的对象是否是右侧类的实例,返回Boolean
Cat: --> Animal:
Cat one = new Cat();
System.out.println(one instanceof Cat); --> true
System.out.println(one instanceof Animal); --> true
向下转型的例子,现在有Master类,master可以feed Animal
- 方案一 方法重载,重载多个feed方法
- 方案二 多态,向下转型
AnimalMaster类
package com.animal;
/**
* 动物的主人类
* 喂养猫咪的时候,吃完跑了
* 喂养狗的时候,吃完去睡觉
* <p>
* 空闲时间多,养狗
* 空闲时间少,养猫
*/
public class AnimalMaster {
// 通过类型判断 向下转型
public void feed(Animal obj) {
obj.eat();
if (obj instanceof Cat) {
Cat tmp = (Cat) obj;
tmp.run();
} else if (obj instanceof Dog) {
Dog tmp = (Dog) obj;
tmp.sleep();
}
}
// 向上转型
public Animal raiseAnimal(Boolean isManyTime) {
if (isManyTime) {
System.out.println("休闲时间重做,适合养狗");
return new Dog();
} else {
System.out.println("休闲时间少,适合养猫");
return new Cat();
}
}
}
AnimalMasterTest
package com.test;
import com.animal.Animal;
import com.animal.AnimalMaster;
import com.animal.Cat;
import com.animal.Dog;
public class AnimalMasterTest {
public static void main(String[] args) {
AnimalMaster people = new AnimalMaster();
Cat one = new Cat();
Dog two = new Dog();
// 向下转型
people.feed(one);
people.feed(two);
Boolean isManyTime = true;
// 向上转型
Animal am;
am = people.raiseAnimal(isManyTime);
System.out.println(am);
}
}
抽象类
在上述代码中Animal
类可以被实例化,但是并没有实际的意义,可以实例化猫,狗,不能说实例化一个动物
现在有一种解决方案,可以实现某个类只能被继承,而不能被实例化
abstract
关键子修饰的类就是抽象类,这样这个类就不能被实例化了.
public abstract class Animal{
//
}
Animal
不能被实例化,但是还是可以向上转型的 , 它的引用可以指向子类的实例
当我们需要某些类必须具有某些方法,而不关注子类实现这个方法的具体逻辑的时候,我们就可以将父类定义成为抽象类,子类去实现抽象类中定义的
方法,那么抽象类中只需要定义方法,而不需要去实现,这个时候,这种方法可以被定义成为抽象方法,抽象方法也是需要abstract
修饰而且没
有大括号方法体.
注意: 如果父类中定义了抽象方法,那么子类必须去重写实现这个方法,否则子类会报类型错误.
如果子类不需要实现方法,那么子类也必须抽象掉.
抽象类的价值:
不使用定义抽象类,定义抽象方法的方式也能实现整个项目的业务逻辑,
当整个项目变得巨大的时候,会随时提示编程人员,哪些方法是需要重写的.非常的友好,让代码更有意义.
曾经提过:static final private
修饰的方法是不能被子类重写的,那么abstract
修饰符就不能与前三者搭配使用,因为抽象方法必须要被子类重写.
问题
已知在Java中,一个类只能有一个父类[单继承]
- 如果我们需要某个子类要兼容多个类的特征,如何做?
- 多个不同的类,要具有某些相同的特征,如何做?
接口
接口 interface
案例:描述手机发展史
- 一代
- 打电话
- 二代
- 增加发短信
- 三代
- 增加看视频
- 增加听音乐
- 四代
- 增加玩游戏
- 增加上网
- 增加拍照
现在有: 电脑 智能手表 数码相机 需要拥有上述手机相同功能的部分
无法抽象出一个公共的父类,因为他们之间的功能具有交叉性
- 接口定义了某一批类所需要遵守的规范
- 接口不关心这些类的内部数据,也不关心这些类里方法的实现细节,只规定这些类必须提供某些方法
IPhoto 定义拍照的接口
package com.phone;
/**
* 具有照相能力的接口
*/
public interface IPhoto {
// 拍照的方法 接口中的抽象方法可以不添加abstract
public void photo();
}
第四代手机
package com.phone;
/**
* 四代手机
*/
public class FourGenerationsPhone extends ThreeGenerationsPhone implements IPhoto {
public FourGenerationsPhone() {
}
public FourGenerationsPhone(String brand, int price) {
super(brand, price);
}
/**
* 拍照功能
*/
@Override
public void photo() {
System.out.println("手机可以拍照");
}
/**
* 上网功能
*/
public void network() {
System.out.println("手机可以上网");
}
/**
* 玩游戏功能
*/
public void game() {
System.out.println("手机可以玩游戏");
}
}
数码相机
package com.phone;
public class Camera implements IPhoto{
@Override
public void photo() {
System.out.println("相机可以拍照");
}
}
第四代手机和数码相机都使用了拍照的接口
PhoneTest
package com.test;
import com.phone.Camera;
import com.phone.FourGenerationsPhone;
import com.phone.IPhoto;
public class PhoneTest {
public static void main(String[] args) {
IPhoto iPhoto1 = new FourGenerationsPhone("三星",2000);
IPhoto iPhoto2 = new Camera();
iPhoto1.photo();
iPhoto2.photo();
}
}
通过接口实例化的对象只能访问接口中定义的公共内容
上网功能的接口
package com.phone;
public interface INetWork {
// 接口中的抽象方法可以不写abstract关键字
public void network();
}
接口中的方法都是public
方法,常量默认是public static final
.
接口中除了public
方法外,还有default static
方法.后两者是携带方法体的,这两种方法可以在使用接口的类中,不重写.
默认方法可以通过接口实例去调用,但是接口静态方法需要通过接口去调用.
如果某些接口中的方法没必要然使用接口的类去实现,那么就可以选择默认方法或者接口静态方法去定义这些方法.<jdk1.8+>
默认方法是可以被重写的,重写的时候就不需要default
修饰符了,而是使用public
接口名.super.方法名();
表示调用接口原定义的方法,可以被扩展
接口中的接口静态方法,不可以在使用类中重写,只能通过接口名去调用
多接口中相同名的方法冲突的解决方法,在实用类中定义自己的方法(重写)
- network接口中有
default void connection(){}
- game接口中有
default void connection(){}
Computer使用了上述两个接口,那么需要重写connection
方法来实现自己的方法
如果Computer中从父类继承了一个connection
方法,使用的两个接口中也都具有connection
方法,那么都会使用类继承下来的connection
方法
经过子类重写后,子类有了自己的方法,那么会调用自己的connection
方法.
多个接口的使用需要有,
隔开
多借口中的冲突命名常量,在使用的时候需要使用相应的接口名去准确调用常量,否则无法识别使用的是哪一个接口的常量
如果从父类中继承了同名常量,也会因为无法分辨报错,这与继承方法不同,继承方法会以继承方法为首,我们只有在子类中定义
冲突的常量(属性),定义自己的数据,那么就会以自己定义的数据为主.
接口相关代码:
public interface ICall {
public void call();
}
public interface IGame {
void game();
default void connection() {
System.out.println("游戏建立连接");
}
}
public interface IMessage {
public void message();
}
public interface IMusic {
public void music();
}
public interface INetWork {
// 接口中的抽象方法可以不写abstract关键字
void network();
String PORT = "8080";
String HOST = "127.0.0.1";
default void connection() {
System.out.println("接口默认连接");
}
static void stop() {
System.out.println("接口静态方法");
}
}
/**
* 具有照相能力的接口
*/
public interface IPhoto {
// 拍照的方法
public void photo();
}
public interface IVideo {
public void video();
}
手机
public class OriginalPhone implements ICall {
public OriginalPhone() {
}
public OriginalPhone(String brand, int price) {
this.setBrand(brand);
this.setPrice(price);
}
private String brand;
private int price;
public void call() {
System.out.println("手机能打电话");
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
}
public class SecondGenerationPhone extends OriginalPhone implements IMessage {
public SecondGenerationPhone() {
}
public SecondGenerationPhone(String brand, int price) {
super(brand, price);
}
/**
* 发短信的功能
*/
public void message() {
System.out.println("我可以发短信");
}
}
public class ThreeGenerationsPhone extends SecondGenerationPhone implements IMusic, IVideo {
public ThreeGenerationsPhone() {
}
public ThreeGenerationsPhone(String brand, int price) {
super(brand, price);
}
/**
* 增加看视频的功能
*/
@Override
public void video() {
System.out.println("我可以看视频");
}
/**
* 增加听音乐的功能
*/
@Override
public void music() {
System.out.println("我可以听音乐");
}
}
public class FourGenerationsPhone extends ThreeGenerationsPhone implements IPhoto, INetWork, IGame {
public FourGenerationsPhone() {
}
public FourGenerationsPhone(String brand, int price) {
super(brand, price);
}
/**
* 拍照功能
*/
@Override
public void photo() {
System.out.println("手机可以拍照");
}
/**
* 上网功能
*/
@Override
public void network() {
System.out.println("手机可以上网");
}
@Override
public void connection() {
System.out.println("手机连接到" + INetWork.HOST + ":" + INetWork.PORT);
}
/**
* 玩游戏功能
*/
public void game() {
System.out.println("手机可以玩游戏");
}
}
电脑
public class Computer implements INetWork, IVideo, IMusic, IGame {
public Computer() {
}
@Override
public void network() {
System.out.println("电脑可以上网");
}
@Override
public void connection() {
System.out.println("电脑连接到" + INetWork.HOST + ":" + INetWork.PORT);
}
@Override
public void music() {
System.out.println("电脑可以听音乐");
}
@Override
public void video() {
System.out.println("电脑可以看视频");
}
@Override
public void game() {
System.out.println("电脑可以玩游戏");
}
}
数码相机
public class Camera implements IPhoto{
@Override
public void photo() {
System.out.println("相机可以拍照");
}
}
智能手表
public class SmartWatch implements IMessage, ICall {
@Override
public void call() {
System.out.println("智能手表打电话");
}
@Override
public void message() {
System.out.println("智能手表发短信");
}
}
内部类
Java中,可以将一个类定义到另一个类的内部,或者一个方法体里面,
这样的类称为内部类,包含内部类的类,称为外部类
例如:
public class People {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Heart getHeart() {
return new Heart();
}
public class Heart {
public String beat() {
return "The heart is beating";
}
}
}
内部类体现更好的封装,这样内部类的信息在外部就无法被获取到,保护内部类的信息
内部类:
- 成员内部类
- 静态内部类
- 方法内部类
- 匿名内部类
成员内部类<普通内部类>
上述的People-Heart就是成员内部类,
如果内部类Heart
是public
的情况下,可以这样得到Heart的实例
import com.internalClass.People;
public class PeopleTest {
public static void main(String[] args) {
People p1 = new People();
p1.setAge(12);
p1.setName("小明");
// 方式一
People.Heart heart = new People().new Heart();
System.out.println(heart.beat());
// 方式二
heart = p1.new Heart();
System.out.println(heart.beat());
// 方式三
heart = p1.getHeart();
System.out.println(heart.beat());
}
}
内部类的访问修饰符可以任意,但是需要注意访问范围,默认不写范围不能出包,public可以跨包,private不可以出包
内部类可以直接使用外部类中定义的时间和方法
- 当以
People.Heart heart = new People().new Heart();
方式去获取内部类的时候,外部类的某些属性哈没有被初始化,那么内部类只能获取外部类中默认的属性 - 当以
heart = p1.new Heart();
方式去获取内部类,而且在获取内部类之前对外部类的实例p1的属性进行了初始化,那么内部类就可以获取外部类准确的属性 - 当内部类中有和外部类同名的属性或者方法的时候,内部类的实例会选择使用自己的属性和方法,如果非要访问外部的,那么
People.this.age
获取准确外部类的数据 - 如果外部类想获取内部类的数据,那么只能通过内部类的实例获取,无法直接获取.
静态内部类
成员内部类使用public修饰的,现在换为static修饰,那么这个内部类就是静态内部类
静态内部类可以直接使用外部类的静态数据,注意要使用静态的方式去调用,如果需要调用外部类的其他属性或者方法,需要通过对象实例去调用
public class Out{
public String name = "张三";
public static String SN = "SN0001";
public static void stsay(){
System.out.print("static hello");
}
public void say(){
System.out.print("hello");
}
static class inner{
public static int age = 10;
public void doit () {
Out.stsay();
System.out.println(Out.SN); //静态调用
new Out().say();
System.out.println(new Out().name); //其他调用
}
}
}
获取静态内部类的实例的方法是:
Out.Inner myinner = new Out.Inner();
获取静态内部类的静态成员
Out.Inner.age
方法内部类
定义在方法里的内部类(局部内部类),按照方法中成员变量的方式来使用
- 局部变量的使用范围仅仅在块内
- 方法内不能定义静态成员
- class 关键字前面 不能使用
public private protected static
- 内部类的成员或者方法均不能使用static但是可以使用final,也可以使用抽象方法抽象类,一般没人去抽象
方法内部类往往会直接调用内部类实例化后的某个方法,而不是直接返回内部类的实例,也就是说,方法内部类会实例化内部类后调用内部类的方法
public class Outer{
public String getInner(){
class Inner{
public int age = 10;
private String message = "hello Inner";
public String innerSay(){
return "Innersay:"+this.message;
}
}
return new Inner().innerSay();
}
}
匿名内部类
没有名字的内部类,其他的内部类都是通过class
声明类new ClassName();
获取实例,当对某些类使用仅仅是去实例化一次,而且类名有没有
都毫无意义的时候,就可使用匿名内部类.
将定义类和实例类放在一起,简化代码和操作
案例:
abstract Person 具有 say() 抽象方法
Man extends Person say(){...}
Woman extends Person say(){...}
一个方法实现传入不同对象调用各自的say()
1. 重载<略>
2. 多态
3. 匿名内部类
2.多态
abstract Person
package com.internalClass;
public abstract class Person {
private String name = "张三";
public void setName(String Name) {
this.name = Name;
}
public String getName() {
return this.name;
}
public Person() {
}
public abstract void say();
}
Man
package com.internalClass;
public class Man extends Person {
private String gender = "男";
public Man() {
}
@Override
public void say() {
System.out.println("(" + this.gender + ")My name is " + this.getName());
}
}
Woman
package com.internalClass;
public class Woman extends Person {
private String gender = "女";
public Woman() {
}
@Override
public void say() {
System.out.println("(" + this.gender + ")My name is " + this.getName());
}
}
test
package com.test;
import com.internalClass.Man;
import com.internalClass.Person;
import com.internalClass.Woman;
public class PersonTest {
// 通过传入男人或者女人调取相应的say方法
// 1. 重载方案
// 2. 多态方案
public void getSay(Person obj) {
obj.say();
}
public static void main(String[] args) {
PersonTest pt = new PersonTest();
Man m1 = new Man();
Woman w1 = new Woman();
m1.setName("猪坚强");
w1.setName("王漂亮");
pt.getSay(m1);
pt.getSay(w1);
}
}
3.匿名内部类
abstract Person
public abstract class Person {
private String name = "张三";
public void setName(String Name) {
this.name = Name;
}
public String getName() {
return this.name;
}
public Person() {
}
public abstract void say();
}
不需要Man 或者 Woman类
直接操作PersonTest
public class PersonTest {
// 通过传入男人或者女人调取相应的say方法
public void getSay(Person obj) {
obj.say();
}
public static void main(String[] args) {
PersonTest pt = new PersonTest();
pt.getSay(
new Person() {
@Override
public void say() {
System.out.println("男人的say");
}
}
);
pt.getSay(
new Person() {
@Override
public void say() {
System.out.println("女人的say");
}
}
);
}
}
如果上述匿名使用的方法只是使用一次,就不必要去创建子类,省内存,省资源,但是缺点就是,如果使用多次的话,无法提高代码复用性
匿名内部类一般是用来直接实现某个抽象方法的,所以无法使用public private protected static abstract 修饰符去定义类,
也无法在匿名内部类中编写构造方法(没类名怎么写?)但是可以使用构造代码块,无法在匿名内部类中定义静态属性或方法.
抽象内部类可以:
1.Person抽象类say抽象方法
2.IMessage接口中的say方法
- 可以通过抽象内部类直接实现Person抽象类中的say方法的重写调用
- 可以通过抽象内部类直接实现IMessage接口中say方法的重写和调用
但是同一个函数中,二者只能选其一,分别定义在两个函数中互不影响
public void getSay(Person obj) {
obj.say();
}
public void getISay(IMessage api) {
api.say();
}
来源:CSDN
作者:北海骆驼
链接:https://blog.csdn.net/qq_40015566/article/details/89167339