前情提要:
书接上回,上篇博客详细介绍了继承,了解到了继承在Java中的作用以及如何去具体应用到实际情景。本篇博客将对多态展开详细介绍。
何为多态:
概念:同一个行为,在创建不同对象去进行的时候会产生不同的效果。
例如:
打印机有黑白和彩色两种,都能进行打印这个操作,但是执行操作之后的效果却是不同的
多态的实现条件:
1、必须在继承体系下
2、子类必须重写父类中的方法进行重写
3、通过父类的引用调用重写的方法
多态的体现:在代码运行的时候,不同类对象会调用对应类的方法
多态的优点和缺点:
优点:1、降低了代码的复杂度;2、代码扩展性强
缺点:代码的效率比较低(父类引用不能直接访问子类特有的成员变量和构造方法)
认识重写:
概念:对子类的方法保留方法名字、返回值类型、返回值、方法形参与父类一致,然后方法中的核心代码进行重新编写
重写的规则:
1、重写的方法在形参、名字、返回值类型上必须与父类中的方法一致
2、子类中重写的方法其访问权限不能比父类的方法低(例如父类中方法的修饰符是public,那么子类重写方法的时候就不能写成protected)
3、父类中被private、static修饰的方法不能被重写
4、重写的方法可以用@Override显示注释,可以帮助检测代码正确性
在IDEA中可以用快捷键alt+insert进行方法重写:
1、先按下alt+insert,点击Override Methods
选择要重写的类,然后点击ok
然后再对方法核心代码进行重写
代码实例:
创建一个父类people,在父类中创建构造方法
public class People {
String name ;
int age;
public People(String name,int age)
{
this.name = name;
this.age = age;
}
public void sleep()
{
System.out.println(name+"正在呼呼大睡....");
}
public void eat()
{
System.out.println(name+"正在吃大餐....");
}
}
接着创建两个子类进行继承,然后在子类中对父类的构造方法进行重写
public class Dad extends People{
public Dad(String name ,int age)
{
super(name,age);
}
@Override
public void sleep() {
super.sleep();
}
}
public class Son extends People {
public Son(String name,int age)
{
super(name, age);
}
@Override
public void sleep() {
super.sleep();
}
}
对子类进行实例化测试
public class Test {
public static void main(String[] args) {
Son s1 = new Son("大头儿子",5);
Dad d1 = new Dad("小头爸爸",40);
s1.sleep();
d1.sleep();
}
}
结果如下:
区别重写和重载:
二者区别一图解:
方法重载的概念以及用法详情移步博客:http://t.csdnimg.cn/P0ZOR
此处不做过多赘述。
转型:
重头戏!!!!这是多态最为重要的内容,也是实用性最强的!!
向上转型:
概念:创建一个子类对象,把该子类对象当做一个父类对象使用。
格式:父类 对象名 = new 子类();
优点:让代码实现更加灵活
缺点:不能调用子类中特有的方法和成员变量
代码实例:
父类:
public class People {
String name ;
int age;
public People(String name,int age)
{
this.name = name;
this.age = age;
}
public void sleep()
{
System.out.println(name+"正在呼呼大睡....");
}
public void eat()
{
System.out.println(name+"正在吃大餐....");
}
}
子类:
public class Son extends People {
double weight; //weight为子类中特有的成员变量
public Son(String name,int age)
{
super(name, age);
}
@Override
public void sleep() {
super.sleep();
}
public void play() //play()方法为子类中特有的方法
{
System.out.println(name+"正在玩游戏....");
}
}
实例化对象
public class Test {
public static void main(String[] args) {
People p1 = new Son("张三",20);
p1.sleep();
}
}
当我们在实例化对象之后,发现无法通过访问子类中特有的方法和成员变量
向下转型:
概念:将父类引用转化为子类对象就是向下转型。
在创建子类实例化对象的时候,因为该对象是一个父类引用对象,此时的实例化对象是无法访问子类中特有的成员变量和构造方法的。
而为了能够访问子类中特有的成员变量和构造方法,此时就需要将实例化对象进行向下转型。
下面用代码实例直观感受向下转型:
先写一个父类Aniaml
public class Animal {
String name ;
String color;
public Animal(String name,String color)
{
this.color = color;
this.name = name;
System.out.println(name+"的颜色是"+color);
}
public void eat()
{
System.out.println(name + "正在吃饭....");
}
}
再写一个子类Dog继承自父类Animal,在子类中写一个子类特有的构造方法
public class Dog extends Animal{
public Dog(String name ,String color)
{
super(name,color);
}
public void bark () //子类中特有的方法
{
System.out.println(name+"正在叫....");
}
}
再实例化一个父类引用的子类实例化对象:
public class Test {
public static void main(String[] args) {
Animal dog = new Dog("哈士奇","黑白"); //此处为向上转型
}
}
然后通过实例化对象访问子类Dog中特有的构造方法,发现子类特有的构造方法bark()访问不了
进行向下转型,再次尝试访问子类中特有的构造方法bark(),这次发现可以访问了
注意:子类实例化对象进行父类引用之后,想要进行向下转型最好是转型为原有的子类
例如:
上述例子的子类是Dog,实例化对象的父类引用是Animal,那么在进行转型的时候最好是将实例化对象转化为Dog,不要转化为其他的子类,否则会产生编译报错
用代码来感受这一点:
在上一个例子中再写一个Cat类继承父类Animal
public class Cat extends Animal{
public Cat(String name ,String color)
{
super(name,color);
}
public void play() //子类Cat中特有的构造方法
{
System.out.println(name+"正在玩耍....");
}
}
然后再将上一个例子的实例化对象照搬过来,对其进行向下转型,此时转型的子类Cat
public class Test {
public static void main(String[] args) {
Animal dog = new Dog("哈士奇","黑白"); //此处为向上转型
dog = (Cat)dog;
}
}
然后我们通过对象去访问对象中的构造方法,发现确实可以访问,但是编译器在编译的时候却抛出了异常
之所以抛出异常,是因为进行向上转型的子类与进行向下转型子类不同,代码不安全所以抛出了异常(本来是只狗,你向上转型成一个动物,然后进行向下转型之后又变成了一只猫,编译器肯定不干)
避免在实例化对象中调用重写的构造方法:
依旧是通过代码来直观感受:
先写一个父类A,重写父类的成员方法并且在成员方法中调用父类中的构造方法
public class A {
public A()
{
func();
}
public void func()
{
System.out.println("类A中的构造方法func()");
}
}
再写一个子类B,重写父类中的构造方法func(); 定义一个访问权限为private的成员变量num并且进行初始化,然后在子类的成员方法中调用已经重写过的func()
public class B extends A{
private int num = 10;
@Override
public void func() {
System.out.println("类B中重写过的方法func():"+num);
}
}
进行实例化引用
public class Test {
public static void main(String[] args) {
B b = new B();
}
}
附上运行结果
通过上例,我们可以分析:
1、构造对象b的时候会调用类A的构造方法
2、在实例化b的过程中,A中的成员方法调用的是在B中重写过的构造方法
3、B在进行实例化过程中,成员变量num处于未初始化的状态,如果成员变量num具有多态性,那么在B中重写构造方法的num就是10
总结:避免在实例化对象中调用重写过的构造方法,很容易出错
本篇博客到此就结束啦!看完请不要吝啬您的三连哦!!