多态和特殊类

一、多态

1、多态的概念

  • 多态主要指同一种事务表现出来的多种形态。

    饮料:可乐、雪碧、红牛、脉动.....

    宠物:猫、狗、鸟、小强、鱼......

    人:学生、教师、工人、保安.......

    图形:矩形、圆形、梯形、三角形

2、多态的语法格式

  • 父类类型 引用变量名 = new 子类类型();

    如:Shape sr = new Rect();

    ​ sr.show();

3、多态的特点

  • 当父类类型的引用指向子类类型的对象时,父类类型的引用可以直接调用父类独有的方法
  • 当父类类型的引用指向子类类型的对象时,父类类型的引用不可以直接调用子类独有的方法
  • 对于父子类都有的非静态方法来说,编译阶段调用父类版本,运行阶段调用子类重写的版本
  • 对于父子类都有的静态方法来说,编译和运行阶段都调用父类版本

4、引用数据类型之间的转换

  • 引用数据类型之间的转换方式有两种:自动类型转换和强制类型转换。
  • 自动类型转换主要指小类型想大类型转换,也就是子类向父类转换,也叫做向上转型。
  • 强制类型转换主要指大类型向小类型的转换,也就是父类转为子类,也叫做向下转型或者显示转型。
  • 引用数据类型之间的转换必须发生在父子类之间,否则编译报错。

5、引用数据类型之间转换的注意事项

  • 若强制的目标类型并不是该引用真正指向的数据类型时,则编译通过,运行阶段发生类型转换异常。

  • 为了避免上述错误的发生,应该在强转之前进行判断,格式如下:

    if(引用变量 instanceof 数据类型)

    判断引用变量指向的对象是否为后面的数据类型

package com;

public class Shape {

	//用private关键字私有化成员变量
	private int x;//声明私有成员变量x描述横坐标
	private int y;//声明私有成员变量y描述纵坐标
	
	//自定义构造方法进行合理值判断
	public Shape() {}
	public Shape(int x, int y) {
		
		setX(x);
		setY(y);
	}
	//提供get、set方法获取私有成员变量
	public int getX() {
		
		return x;
	}
	
	public void setX(int x) {
		
		this.x = x;
	}
	
	public int getY() {
		
		return y;
	}
	
	public void setY(int y) {
		
		this.y = y;
	}
	
	//自定义成员方法打印所有特征
	public void show() {
		
		System.out.println("横坐标是:" + getX() + ",纵坐标是:" + getY());
	}
	
	//自定义静态方法
	public static void test() {
		
		System.out.println("Shape类中的静态方法");
	}
}



package com;

public class Rect extends Shape {

	//用private关键字私有化成员变量
	private int length;
	private int width;
	
	//自定义构造方法进行合理值判断
	public Rect() {}
	public Rect(int x, int y, int length, int width) {
		
		super(x, y);
		setLength(length);
		setWidth(width);
	}
	//提供get、set方法进行合理值判断
	public int getLength() {
		
		return length;
	}
	
	public void setLength(int length) {
		
		if(length > 0) {
			
			this.length = length;
		}else {
			
			System.out.println("长度有误!");
		}
	}
	
	public int getWidth() {
		
		return width;
	}
	
	public void setWidth(int width) {
		
		if(width > 0) {
			
			this.width = width;
		}else {
			
			System.out.println("宽度有误!");
		}
	}
	
	//自定义成员方法打印所有特征
	@Override
	public void show() {
		
		super.show();
		System.out.println("长是:" + getLength() + "宽是:" + getWidth());
	}
	
	//自定义静态方法
	//@Ovrride Error:历史原因 静态方法不是真正意义上的重写
	public static void test() {
			
		System.out.println("Rect类中的静态方法");
	}
}



package com;

public class Circle extends Shape{

	private int ir;
	
	public Circle() {}
	public Circle(int x, int y, int ir) {
		
		super(x, y);
		setIr(ir);
	}
	
	public int getIr() {
		
		return ir;
	}
	
	public void setIr(int ir) {
		
		if(ir > 0) {
			
			this.ir = ir;
		}else {
			
			System.out.println("半径不合理!");
		}
	}
	
	public void show() {
		
		super.show();
		System.out.println("圆的半径是:" + getIr());
	}
}



package com;

public class ShapeRectTest {
	
	public static void main(String[] args) {
		
		//声明Shape类型的引用指向该类型的对象
		Shape s = new Shape(1, 2);
		//当Rect类中没有重写show方法时,下面调用shape类中的show方法
		//当Rect类中重写show方法时,下面调用shape类中的show方法
		s.show();
		
		//声明Rect类型的引用指向该类型的对象
		System.out.println("-----------------------");
		Rect r = new Rect(3, 4, 5, 6);
		//当Rect类中没有重写show方法时,下面调用shape类中的show方法
		//当Rect类中重写show方法时,下面调用Rect类中的show方法
		r.show();
		
		//声明Shape类型的引用指向Rect类型的对象
		System.out.println("-----------------------");
		//多态相当于从子类Rect类型到父类Shape类型的转换,小到大的转换,也就是自动类型转换
		Shape sr = new Rect(7, 8, 9, 10);
		//当Rect类中没有重写show方法时,下面调用shape类中的show方法
		//当Rect类中重写show方法时,下面代码在编译阶段调用shape类中show方法,在运行阶段调用Rect类中的show方法
		sr.show();
		
		System.out.println("-----------------------");
		//测试Shape类型的引用能否直接调用父类和子类独有的方法
		int ia = sr.getX();
		System.out.println("获取到的横坐标是:" + ia);
		//int ib = sr.getLength(); error Shape类中找不到getLength方法,也就是还是在Shape类中查找
		//调用静态方法
		sr.test();
		Shape.test();
		r.test();
		
		System.out.println("-----------------------");
		//使用父类类型的引用调用子类独有方法的方式
		int ib = ((Rect)sr).getLength();//强制类型转换 把Shape类型的引用强制转换为Rect类型
		System.out.println("获取到的长度是:" + ib);
		//希望将Shape类型强制转换为Circle类型,编译没有报错
		//Circle c1 = (Circle)sr;//编译ok,但运行阶段发生ClassCastException类型转换异常
		
		//在强制类型转换之前应该使用instanceof进行类型的判断
		//判断sr指向的堆区内存的对象是否为Circle
		if(sr instanceof Circle) {
			
			System.out.println("可以放心的转换");
			Circle c1 = (Circle)sr;
		}else {
			
			System.out.println("强转有风险");
		}
	}
}

/*
输出:
横坐标是:1,纵坐标是:2
-----------------------
横坐标是:3,纵坐标是:4
长是:5宽是:6
-----------------------
横坐标是:7,纵坐标是:8
长是:9宽是:10
-----------------------
获取到的横坐标是:7
Shape类中的静态方法
Shape类中的静态方法
Rect类中的静态方法
-----------------------
获取到的长度是:9
强转有风险

*/

6、多态的实际意义

  • 多态的实际意义在于屏蔽不同子类的差异性实现通用的编程带来的不同的效果。
package com;

public class ShapeTest {
 
	//自定义成员方法实现将参数指定矩形对象特征打印出来的行为,也就是绘制图形的行为
	/*public static void draw(Rect r) {
		
		r.show();
	}
	
	//自定义成员方法实现将指定圆形对象特征打印出来的行为
	public static void draw(Circle c) {
		
		c.show();
	}*/
	
	//自定义成员方法实现既能打印矩形对象又能打印圆形对象的特征,对象有参数传入 子类 is a 父类
	//Shape s = new Rect(1, 2, 3, 4); 父类类型的引用指向子类类型的对象,形成了多态
	//Shape s = new Circle(5, 6, 7);   多态
	//多态的使用场合之一:通过参数传递形成了多态
	public static void draw(Shape s) {
		
		//编译阶段调用父类的版本,运行阶段调用子类重写以后的版本
		s.show();
	}
	
	public static void main(String[] args) {
		
		ShapeTest.draw(new Rect(1, 2, 3, 4));
		System.out.println("-------------------");
		ShapeTest.draw(new Circle(5, 6, 7));
	}
}
/*
输出:
横坐标是:1,纵坐标是:2
长是:3宽是:4
-------------------
横坐标是:5,纵坐标是:6
圆的半径是:7
*/

二、抽象

1、抽象方法的概念

  • 抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,也就是没有方法体。

  • 具体格式如下:

    访问权限 abstract 返回值类型 方法名(形参列表);

    public absract void cry();

2、抽象类的概念

  • 抽象类主要指不能具体实例化的类并且使用abstract关键字修饰,也就是不能创建对象。(抽象类不能创建对象的意义:防止调用抽象方法)

3、抽象类和抽象方法的关系

  • 抽象类中可以有成员变量、构造方法、成员方法;
  • 抽象类中可以没有抽象方法,也可以有抽象方法
  • 拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类应该是具有抽象方法并且使用abstract关键字修饰的类。

4、抽象类的实际意义

  • 抽象类的实际意义不在于创建对象而在于被继承。
  • 当一个类继承抽象类后必须重写抽象方法,否则该类也变成抽象类,也就是抽象类对子类具有强制性和规范性,也就是模板设计模式。
package com;

public abstract class AbstractTest {
	
	private int cnt;
	
	public AbstractTest() {}
	public AbstractTest(int cnt) {
		
		setCnt(cnt);
	}
	public int getCnt() {
		
		return cnt;
	}
	
	public void setCnt(int cnt) {
		
		this.cnt = cnt;
	}
	
	//自定义抽象方法
	public abstract void show();
	
	public static void main(String[] args) {
		
		//AbstractTest at = new AbstractTest();
		//System.out.println("at.cnt = " + at.getCnt());
	}
}




package com;

public class SubAbstractTest extends AbstractTest {

	@Override
	public void show() {
		// TODO Auto-generated method stub
		System.out.println("其实我是被迫重写的,否则我也得编程抽象类!");
	}

	public static void main(String[] args) {
		
		SubAbstractTest sst = new SubAbstractTest();
		sst.show();
		
		System.out.println("-----------------------------------------");
		//声明AbstractTest类型的引用指向子类的对象,形成多态
		//多态的使用场合之二:直接在方法体中使用抽象类型的引用指向子类类型的对象
		AbstractTest at = new SubAbstractTest();
		//编译阶段调用父类版本,运行阶段调用子类版本
		at.show();
	}
	
}

5、开发经验分享

  • 在以后的开发中推荐使用多态的格式,此时父类类型引用直接调用的所有方法一定是父类中拥有的方法,若以后更换子类时,只需要将new关键字后面的子类类修改而其他地方无需改变就可以立即生效,从而提高了代码的可维护性和可扩展性。
  • 该方式的缺点就是:父类引用不能直接调用子类独有的方法,若调用则需要强制类型转换。

6、案例:

银行有 定期账户和活期账户。

继承自 账户类。

账户类中:

public class Account{

​ private double money;

​ public double getLixi(){}

}

package com;

public abstract class Account {
	
	private double money;
	//private double lixi;
	
	//自定义构造方法
	public Account() {}
	public Account(double money) {
		
		setMoney(money);
	}
	
	//提供公有的get、set方法获取私有成员变量
	public double getMoney() {
		
		return money;
	}
	
	public void setMoney(double money) {
		
		this.money = money;
	}
	
	//声明抽象方法计算利息并返回
	public abstract double getLixi();
	
	//public abstract void setLixi(double lixi);
	
	//自定义成员方法打印所有特征
	public void show() {
		
		System.out.println("账户余额是:" + getMoney());
	}
}


package com;

public class DAccount extends Account{

	private double lixi;
	
	public DAccount() {}
	public DAccount(double money) {
		
		super(money);
	}
	@Override
	public double getLixi() {
		// TODO Auto-generated method stub
		lixi = getMoney() * 0.001;
		return lixi;
	}
	
	/*public void setLixi(double lixi) {
		
		lixi = getMoney() * 0.001;
		this.lixi = lixi;
	}*/

	public void show() {
		
		super.show();
		System.out.println("定期利息是:" + getLixi());
	}
}


package com;

public class HAccount extends Account {

	//private double lixi;
	
	public HAccount() {}
	public HAccount( double money) {
		
		super(money);
	}
	
	@Override
	public double getLixi() {
		// TODO Auto-generated method stub
		double lixi;
		lixi = getMoney() * 0.0001;
		return lixi;
	}

	/*public void setLixi(double lixi) {
		
		lixi = getMoney() * 0.0001;
		this.lixi = lixi;
	}*/
	
	public void show() {
		
		super.show();
		System.out.println("活期的利息是:" + getLixi());
	}
}


package com;

public class AccountTest {

	public static void main(String[] args) {
		//声明ccount类型的引用指向子类类型的对象,形成多态
		Account a1 = new DAccount(50000);
		a1.show();
		
		System.out.println("---------------------");
		Account a2 = new HAccount(50000);
		a2.show();
	}
}

7、笔试考点

//private 和abstract关键字不能共同修饰一个方法
//private abstract double getLixi();

//final 和abstract关键字不能共同修饰一个方法
//public final abstract double getLixi();

//static 和abstract关键字不能共同修饰一个方法
//public static abstract double getLixi();

三、接口

1、接口的概念

  • 接口就是一种比抽象还抽象的类,体现在所有方法都是抽象方法

    package com;
    
    public interface InterfaceTest {
    
    	/*public static final*/ int CNT = 1;//接口里面只能有常量
    	//private void show(){}
    	
    	/*public abstract*/ void show(); //里面只能有抽象方法(新特性除外) 注释中的关键字可以省略,但建议写上
    }
    
    
  • 定义类的关键字是class,而定义接口的关键字是interface。

    如:金属接口、货币接口、黄金类

package com;

public interface Metal {

	//自定义抽象方法描述发光的行为
	public abstract void shine();
}


package com;

public interface Money {

	//自定义抽象方法描述购物行为
	
	public abstract void buy();
}


package com;

//使用implements关键字表示实现的关系,支持多实现
public class Gold implements Metal, Money {

	@Override
	public void shine() {
		
		System.out.println("发出了金黄色的光芒!");
	}
	
	@Override
	public void buy() {
		
		System.out.println("买了好多好吃的。。。。");
	}
	
	public static void main(String[] args) {
		
		//声明接口类型的引用指向实现类的对象
		Metal mt = new Gold();
		mt.shine();
		
		Money mn = new Gold();
		mn.buy();
	}
}

2、类与接口之间的关系

名称			关键字                       关系
类与类之间	   使用extends表达继承关系          支持单继承
类与接口之间	   使用implements表达实现关系       支持多实现
接口与接口之间	   使用extends表达继承关系          支持多继承
package com;

public interface Runner {

	//自定义抽象方法描述奔跑行为
	public abstract void run();
}


package com;

public interface Hunter extends Runner{
	
	//自定义抽象方法描述捕猎的行为
	public abstract void hunt();
}


package com;

public class Man implements Hunter {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println("追逐一只兔子。。。");
	}

	@Override
	public void hunt() {
		// TODO Auto-generated method stub
		System.out.println("用弓箭射杀兔子。。。。");
	}

	public static void main(String[] args) {
		
		//声明接口类型的引用指向实现类的对象,形成多态
		Runner runner = new Man();
		runner.run();
		
		Hunter hunter = new Man();
		hunter.hunt();
	}
	
}

3、抽象类和接口的主要区别(笔试题

  • 定义抽象类的关键字是abstract class,而定义接口的关键字是interface。
  • 继承抽象类的关键字是extends,而实现接口的关键字是implements。
  • 继承抽象类支持单继承,而实现接口支持多实现。
  • 抽象类中可以有构造方法,而接口中不可以有构造方法。
  • 抽象类中可以有成员变量,而接口中只可以有常量。
  • 抽象类中可以有成员方法,而接口中只可以有抽象方法。
  • 抽象类中增加方法时子类可以不用重写,而接口中增加方法时,实现类需要重写(Java8以前的版本)
  • 从Java8开始增加新特性,接口中允许出现非抽象方法和静态方法,但非抽象方法需要使用default关键字修饰。
  • 从Java9开始增加新特性,接口中允许出现私有方法。
package com;

public interface Hunter extends Runner{
	
	//自定义抽象方法描述捕猎的行为
	public abstract void hunt();
	
	//增加一个抽象方法
	//public abstract void show();
	
	//增加一个show方法
	private void show() {
		
		System.out.println("这里仅仅是接口中的默认功能,实现类可以自由选择是否重写!");
	}
	//增加一个非抽象方法
	public default void show1() {
	 	//System.out.println("尽可能避免重复代码的使用!");
	 	 show();
		 System.out.println("这里仅仅是接口中的默认功能,实现类可以自由选择是否重写!");
	}
	
	public default void show2() {
	 	//System.out.println("这里仅仅是接口中的默认功能,实现类可以自由选择是否重写!");
	 	show();
		System.out.println("这里仅仅是接口中的默认功能,实现类可以自由选择是否重写!");
}
	
	//增加静态方法 隶属于类层级,也就是接口层级
	public static void test() {
		
		System.out.println("这里是静态方法,可以直接通过接口名.的方式调用,省略对象的创建");
	}
}

文章内容输出来源:拉勾教育Java高薪训练营

本文来自博客园,作者:寒露凝珠,转载请注明原文链接:https://www.cnblogs.com/china-soldier/p/14889169.html

原文地址:https://www.cnblogs.com/china-soldier/p/14889169.html