我们都知道多态是java的三大特性之一(封装/继承/多态),知道它很重要。简单来说,多态就是子类可以替换父类。下面我将通过一个应用场景说明怎么在代码中去使用多态。
我们先定义一个接口类Virus
public interface Virus {
void creat();
public void move() ;
}
1.当方法的参数类型是父类类型/接口类型,设定满足不同的条件,可以将不同的子类对象/实现类对象作为参数传递
应用场景:在以前火爆的小程序游戏《消灭病毒》中,我们尝试给不同种类的病毒不同的移动方式
这是普通病毒的移动方式,普普通通的斜着运动
class Odvirus implements Virus {
public void move() {
//向右移动
if(flag ==1) {
x += dx;
y += dy;
}
//向左移动
else if(flag==2){
x -= dx;
y += dy;
}
//判断圆是否碰到窗体的左边或右边,如果是,则计算交点的角度,并根据反射定律调整水平速度的方向和大小
if (x - r <= 0 || x + r >= 800) {
dx = -dx;
}
//判断圆是否碰到窗体的下边,如果是,则将圆的纵坐标设置为负半径,即从窗体上面重新下来
if (y + r >= 600) {
y = -r - 100;
}
}
}
这是追踪病毒,可以在靠近飞机时向飞机移动
public class TraceVirus extends Odvirus{
public Myplane myplane;
public TraceVirus(Graphics g,Myplane myplane) {
this.g=g;
this.myplane =myplane;
}
public void move(){
if(this.r+myplane.r+150>Math.sqrt((Math.abs(x-myplane.x)*Math.abs(x-myplane.x)+(Math.abs(y-myplane.y)*Math.abs(y-myplane.y))))) {
if (myplane.y > y&(myplane.x!=x)) {
double k = (myplane.y - y) / (myplane.x - x);
y += 5;
x += 5 / k;
System.out.println("JJJJ");
}
}
else {
super.move();
}
}
}
我们写一个消费者模型,一个线程用来生产,一个线程用来消费。即一个线程用来生成病毒,一个线程用来移动病毒。
生成病毒线程
public class ThreadCreate extends Thread{
public Vector<Virus> virus;
public Graphics g ;
public LoadImage loadImage = new LoadImage();
public Myplane myplane;
public Integer len =400;
public ThreadCreate(Vector<Virus> virus, Graphics g,Myplane myplane){
this.virus = virus;
this.g = g;
this.myplane = myplane;
}
@Override
public void run() {
while (true) {
if (myplane.issave==1|myplane.issave==3) {
Random ran = new Random();
int ram = ran.nextInt(3);
switch (ram) {
case 0 -> {
Odvirus x1 = new Odvirus(g);
x1.creat();
virus.add(x1);
}
case 1 -> {
HughVirus x2 = new HughVirus(g);
x2.creat();
virus.add(x2);
}
case 2->{
TraceVirus x3 = new TraceVirus(g,myplane);
x3.creat();
System.out.println("追踪病毒");
virus.add(x3);
}
}
len = len - 10;
if (len % 30 == 0) {
Strengthen fire_1 = new Strengthen(g,myplane);
fire_1.creat();
virus.add(fire_1);
}
}
try {
Thread.sleep(3000);
} catch (Exception ef) {
}
}
}
}
把生成的病毒加入到一个队列中,
public class ThreadDraw extends Thread {
private Vector<Virus> virus = null;
private Graphics g;
private Graphics g0;
public ThreadCreate tc;
private LoadImage loadImage = new LoadImage();
private BufferedImage buffer;
private Myplane myplane;
private int num=1;
private String page = "开始界面";
private int wudi = 30; //无敌时间
public ThreadDraw(Vector<Virus> virus, Graphics g, Graphics g0, BufferedImage buffer,Myplane myplane){
this.virus = virus;
this.g = g;
this.g0 = g0;
this.buffer = buffer;
this.myplane = myplane;
}
public void run() {
while (true) {
switch (page) {
case "开始界面" -> page_start();
case "游戏开始" -> page_game();
case "游戏结束" ->page_over();
}
g0.drawImage(buffer,0,0,null);
}
}
private void page_game(){
if (virus.size() > 0) {
for (Virus x : virus) {
x.move();
}
}
}
这个virus队列中有各种各样的病毒,把它作为形参,我就可以不改动这行代码就能实现之后所有生成的病毒可以按照自己类中写的move()方法来移动,这就是多态,是不是很方便?
2.当方法的返回值类型是父类类型/接口类型,设定满足不同的条件,可以将不同的子类对象/实现类对象作为返回值
通过这个思路我们可以看出生成病毒的线程的run()方法的代码不是很清晰,我们可以改成
public void run() {
while (true) {
Virus virus0 = creat();
if(virus0!=null) {
virus0.creat();
virus.add(virus0);
}
len = len - 10;
if (len % 30 == 0) {
Strengthen fire_1 = new Strengthen(g, myplane);
fire_1.creat();
virus.add(fire_1);
}
try {
Thread.sleep(3000);
} catch (Exception ef) {
}
}
}
private Virus creat() {
Virus virus0 = null;
if (myplane.issave == 1 | myplane.issave == 3) {
Random ran = new Random();
int ram = ran.nextInt(3);
switch (ram) {
case 0 -> {
virus0 = new Odvirus(g);
}
case 1 -> {
virus0 = new HughVirus(g);
}
case 2 -> {
virus0 = new TraceVirus(g, myplane);
System.out.println("追踪病毒");
}
}
}
return virus0;
}
}
以上我们可以总结出多态的好处和弊端
好处:提高了程序的拓展性
弊端:不能调用实现类的特有方法,但是可以通过向下转型来调用特有方法
补充的知识点:继承和组合的区别。
对于病毒来说,存在不同的移动方式,我们很自然的希望定义一个类Virus来描述所有病毒通用的移动行为move(),然后通过某种方式(继承/组合)来为不同的型号的病毒提供不同move()行为
上面的所有代码的move()是通过继承后重写的方式来实现。组合就是move()是通过赋予不同的行为实例来实现。具体的实现方式为:需要为Virus类定义一个引用,该引用类型是一个移动行为定义的抽象。
public class Virus{
protected AllMove movebehavior;
pubulic move(){
movebehavior.move;
}
public void setmovebehavior(AllMove movebehavior){
this.movebehavior = movebehavior;
}
}
AllMove是一个接口或者父类,有很多行为类继承了这个接口。
有句话叫组合优于继承。这是因为继承无法在运行时改变继承了父类的子类行为。而对象的组合由于是通过接口实现的,这样在复用的过程中不会打破其封装。任意一个对象都可以在运行时刻被替换成另外一个实现了相同接口且类型相同的对象。
那怎么根据需要动态地替换对象呢?下面给出示例代码。
public class ObjectReplaceExample {
private String name;
private int age;
private String address;
public ObjectReplaceExample(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setAddress(String address) {
this.address = address;
}
public void replaceObject(String newName, int newAge, String newAddress) {
// 创建一个新的对象并替换当前对象
ObjectReplaceExample newObject = new ObjectReplaceExample(newName, newAge, newAddress);
// 替换当前对象为新对象
this = newObject;
}
public static void main(String[] args) {
ObjectReplaceExample example = new ObjectReplaceExample("John", 30, "123 Main St");
System.out.println("Before replacement: " + example); // 输出:Before replacement: ObjectReplaceExample[name=John, age=30, address=123 Main St]
example.replaceObject("Jane", 25, "456 Elm St"); // 替换对象为新的对象"Jane"、25岁、"456 Elm St"
System.out.println("After replacement: " + example); // 输出:After replacement: Jane is 25 years old and lives at 456 Elm St.
}
}
在上面的示例中,我们定义了一个名为 ObjectReplaceExample
的类,它具有三个属性:name
、age
和 address
。我们还定义了三个方法 setName
、setAge
和 setAddress
,用于设置这些属性的值。然后,我们定义了一个名为 replaceObject
的方法,它接受三个参数:新对象的名称、新对象的年龄和新对象的地址。在 replaceObject
方法中,我们创建了一个新的 ObjectReplaceExample
对象,并将其赋值给当前对象。这样,我们就成功地动态地替换了对象。