这次的博客写的会比较长,因为虽然只学习了多态这一个知识但是他所涉及的内容是比较多、比较杂的,也会增加很多例子来辅助结论,希望大家能喜欢。
1. 分清覆盖与重载
虽然大家觉得覆盖和重载没有什么可说的,但是当我们遇到有些棘手的多态问题,它和覆盖与重载往往有着联系。我们在此权当复习了。
范围:
覆盖(重写):发生在不同类中,一般是用于子类在继承父类时,重写(重新实现)父类中的方法。
重载:发生在同一类中,一般是用于在一个类内实现若干重载的方法。
判断标准:先说重载再说覆盖
重载:相同的方法名、不同的参数列表。和访问权限、返回类型、抛出的异常无关
覆盖(重写)的标准比较复杂:
1、方法名、参数列表必须完全相同
2、重写方法的访问修饰符一定要大于被重写方法的访问修饰符(public>protected>default>private)。
也就是说子类访问修饰符权限大于等于父类的权限
3、重写对返回值、抛出异常都有关系,这一点和重载有很大区别。
重写的方法的返回值必须和被重写的方法的返回一致;
重写的方法所抛出的异常必须和被重写方法的所抛出的异常一致,或者是其子类。也就是说子类抛出的异常类和父类抛出的异常类符合is-a关系
4、特别注意:
被重写的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重写。
静态方法不能被重写为非静态的方法(会编译出错)。
关于标准的几条疑惑:
1、为什么子类重写的方法的访问权限要大于等于父类的被重写的方法的访问权限?
package com.Java1;
public class Demo1 {
public static void main(String[] args) {
Father f = new Child();
f.print();
}
}
//下面为带继承关系的其他包com.Java2中两个类;
package com.Java2;
public class Father{
public void print(){
System.out.println("This is the Father");
}
}
package com.Java2;
public class Child extends Father{
protected void print(){ //protected权限修饰符只允许被同个包com.java2中的类访问也就无法被com.java1中的访问到,即使是用父类引用来调用。
System.out.println("This is the Child");
}
}
父类public方法在main方法中可以被调用,可是由于子类protected把子类权限降低导致子类在main方法中不可以被调用。继承来的东西用不了!
2、为什么静态方法不能被重写?
java静态方法能否重写?JAVA静态方法形式上可以重写,但从本质上来说不是JAVA的重写。因为静态方法只与类相关,不与具体实现相关,声明的是什么类,则引用相应类的静态方法(本来静态无需声明,可以直接引用
static方法不是后期绑定的,它在编译期就绑定了,换句话说,这个方法不会进行多态的判断。只与声明的类有关。
2. 我们要对子类到底有什么有清晰的认识。
自己构思了例子和模型为了便于学习与记忆
class Person
{
private String hobby="swim";
private String name="Person";//父类私有的属性
public int age=53;//父类共有属性
Person()
{
System.out.println("父类无参构造方法");
}
public void speak()
{
System.out.println("父类公共方法");
}
private void run()
{
System.out.println("父类私有方法");
}
public void hobby()
{
System.out.println(hobby);
}
}
class Student extends Person
{
String name="son";//子类私有的属性
public int age=27;//子类共有属性
Student()
{
System.out.println("子类无参构造方法");
}
public void run()
{
System.out.println("子类私有方法");
}
public void speak()
{
System.out.println("子类公共方法");
}
}
public class duotai {
public static void main(String[] args) {
Student s=new Student();
}
}
创建子类模型第一步:分析父类有什么
父类内容 | 方法或者属性 |
---|---|
父类私有成员 | hobby/name |
父类私有方法 | run |
父类非私有成员 | age |
父类非私有方法 | speak/hobby |
第二步:私有的都放在一起,是在不可见区域之中的
可见区域(子类中的属性和方法) | 不可见区域(其中的内容都是父类私有的) |
---|---|
hobby/name | |
run |
第三步:非私有的看看有没有覆盖的,如果覆盖就用覆盖的,如果没有就用父类的
可见区域(子类中的属性和方法) | 不可见区域(其中的内容都是父类私有的) |
---|---|
age属性被覆盖 | hobby/name |
speak方法被覆盖 | run |
hobby方法被覆盖 |
第四步:把子类特有的添加其中,注意在考虑是不是自己特有的时候要忽视不可见区域中的属性和方法:如果子类和父类属性名称或者方法名称相同,但是父类的是私有的,那么该属性和方法是子类特有的,并不是覆盖的
可见区域(子类中的属性和方法) | 不可见区域(其中的内容都是父类私有的) |
---|---|
age属性被覆盖 | hobby/name |
speak方法被覆盖 | run |
hobby方法被覆盖 | |
name属性子类特有的 | |
run方法子类特有的 |
想学好多态的第一步:分清子类中到底有什么我们已经说完了。
3. 多态
讲多态之前先简单的讲一下静态绑定和动态绑定都和什么有关。
属性是静态绑定的(编译期就能确定)。
static、final、private修饰的方法和构造函数是静态绑定的(编译期就能确定)。
其余的方法是动态绑定的(运行期才能确定)。
多态其实针对的是方法,和属性无关。
下面结合例子说明一下
4. 例子
从网上找的例子
例子1:
public class Wine {
public void fun1(){
System.out.println("Wine 的Fun.....");
fun2();
}
public void fun2(){
System.out.println("Wine 的Fun2...");
}
}
public class JNC extends Wine{
/**
* @desc 子类重写父类方法
* 父类中不存在该方法,向上转型后,父类是不能引用该方法的
* @param a
* @return void
*/
public void fun1(String a){
System.out.println("JNC 的 Fun1...");
fun2();
}
/**
* 子类重写父类方法
* 指向子类的父类引用调用fun2时,必定是调用该方法
*/
public void fun2(){
System.out.println("JNC 的Fun2...");
}
}
public class Test {
public static void main(String[] args) {
Wine a = new JNC();
a.fun1();
}
}
-------------------------------------------------
Output:
Wine 的Fun.....
JNC 的Fun2...
分析如下:
创建子类模型第一步:分析父类有什么
父类内容 | 方法或者属性 |
---|---|
父类私有成员 | 无 |
父类私有方法 | 无 |
父类非私有成员 | 无 |
父类非私有方法 | fun1/fun2 |
第二步:私有的都放在一起,是在不可见区域之中的
可见区域(子类中的属性和方法) | 不可见区域(其中的内容都是父类私有的) |
---|---|
无 | |
无 |
第三步:非私有的看看有没有覆盖的,如果覆盖就用覆盖的,如果没有就用父类的
可见区域(子类中的属性和方法) | 不可见区域(其中的内容都是父类私有的) |
---|---|
fun2方法被覆盖 | 无 |
fun1()(父类的无参方法) | 无 |
PS:重载和覆盖都和方法体无关,具体和什么相关请看上文
第四步:把子类特有的添加其中,注意在考虑是不是自己特有的时候要忽视不可见区域中的属性和方法:如果子类和父类属性名称或者方法名称相同,但是父类的是私有的,那么该属性和方法是子类特有的,并不是覆盖的
可见区域(子类中的属性和方法) | 不可见区域(其中的内容都是父类私有的) |
---|---|
fun2方法被覆盖 | 无 |
fun1()(父类的无参方法) | 无 |
fun1(String a)(子类的含参fun1(String a)方法,和父类的fun1()是重载) | 无 |
分析过子类都有什么之后,再来看看这道题,a.fun1();
子类调用无参的fun1所以先输出
Wine 的Fun…..
然后fun1()中调用了fun2(),这个fun2()也是子类的。
JNC 的Fun2…
例子2:
class Wine {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Wine(){
}
public String drink(){
return "喝的是 " + getName();
}
/**
* 重写toString()
*/
public String toString(){
return null;
}
}
class JNC extends Wine{
public JNC(){
setName("JNC");
}
/**
* 重写父类方法,实现多态
*/
public String drink(){
return "喝的是 " + getName();
}
/**
* 重写toString()
*/
public String toString(){
return "Wine : " + getName();
}
}
class JGJ extends Wine{
public JGJ(){
setName("JGJ");
}
/**
* 重写父类方法,实现多态
*/
public String drink(){
return "喝的是 " + getName();
}
/**
* 重写toString()
*/
public String toString(){
return "Wine : " + getName();
}
}
public class Test {
public static void main(String[] args) {
//定义父类数组
Wine[] wines = new Wine[2];
//定义两个子类
JNC jnc = new JNC();
JGJ jgj = new JGJ();
//父类引用子类对象
wines[0] = jnc;
wines[1] = jgj;
for(int i = 0 ; i < 2 ; i++){
System.out.println(wines[i].toString() + "--" + wines[i].drink());
}
System.out.println("-------------------------------");
}
}
OUTPUT:
Wine : JNC--喝的是 JNC
Wine : JGJ--喝的是 JGJ
-------------------------------
创建子类模型第一步:分析父类有什么
父类内容 | 方法或者属性 |
---|---|
父类私有成员 | name |
父类私有方法 | 无 |
父类非私有成员 | 无 |
父类非私有方法 | getName/setName/drink/toString |
第二步:私有的都放在一起,是在不可见区域之中的
可见区域(子类中的属性和方法) | 不可见区域(其中的内容都是父类私有的) |
---|---|
name | |
无 |
第三步:非私有的看看有没有覆盖的,如果覆盖就用覆盖的,如果没有就用父类的
可见区域(子类中的属性和方法) | 可见区域(子类中的属性和方法) | 不可见区域(其中的内容都是父类私有的) | |
---|---|---|---|
JGJ子类 | JNC子类 | 父类 | |
getName()方法被继承 | getName()方法被继承 | name | |
setName()方法被继承 | setName()方法被继承 | ||
drink方法被覆盖 | drink方法被覆盖 | ||
toString()方法被覆盖 | toString()方法被覆盖 |
第四步和第三步的结果是相同的
main函数里面就是两个多态,没有什么需要特别说明的。
例子3:
在例子3之前我先讲两个原则,按照这两个原则分析往往能避免一些错误。
多态能调用那些方法,由引用类型决定,具体执行情况,由实际内存对象类型决定。也就是说指向子类对象的父类引用调用的方法*必须是父类拥有的(声明的),子类特有的(父类没有声明)不行,具体的方法实现要看子类。
例程如下:
public class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
public class B extends A{
public String show(B obj){
return ("B and B");
}
public String show(A obj){
return ("B and A");
}
}
public class C extends B{
}
public class D extends B{
}
public class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b));
System.out.println("2--" + a1.show(c));
System.out.println("3--" + a1.show(d));
System.out.println("4--" + a2.show(b));
System.out.println("5--" + a2.show(c));
System.out.println("6--" + a2.show(d));
System.out.println("7--" + b.show(b));
System.out.println("8--" + b.show(c));
System.out.println("9--" + b.show(d));
}
}
运行结果:
[java] view plain copy
1--A and A
2--A and A
3--A and D
4--B and A
5--B and A
6--A and D
7--B and B
8--B and B
9--A and D
这次就不按照之前的四步了,因为之前的四步是不熟的情况下用的,熟练之后就能很快写出子类模型了。
孙子D | 孙子C | 爸爸B | 爷爷A |
---|---|---|---|
show(D)继承而来 | show(D)继承而来 | show(D)继承而来 | show(D) |
show(A)继承而来 | show(A)继承而来 | show(A)覆盖而来 | show(A) |
show(B)继承而来 | show(B)继承而来 | show(B)爸爸B特有 |
知道自己有什么方法很重要,知道自己方法从哪里来的也很重要
a1是指向A类对象的引用A(跟多态没关系)
a2是指向B类对象的引用B(这个符合多态三条件之中的向上转型)
b,c,d都和多态没关系。
- a1.show(b)你找a1的方法,里面没有show(b),但是有show(A),b是B类对象同时也是A的子类,那么 show(A)方法就是可以用的。所以输出Aand A
- a1.show(c)你找a1的方法,里面没有show(c),但是有show(A),c是C类对象同时也是A的孙子类,那么 show(A)方法就是可以用的。所以输出A and A
- a1.show(d)你找a1的方法,里面有show(d)。所以输出A and D
- a2.show(b)你先找a1里面有没有这个方法,没有show(b),但是有show(A),b是B类对象同时也是A的子类,那么 show(A)方法就是可以用的(编译期做的事)。此时注意运行时候方法是看子类的,调用子类show(A)。所以输出B and A
- a2.show(c))你先找a1里面有没有这个方法,没有show(c),但是有show(A),c是C类对象同时也是A的孙子类,那么 show(A)方法就是可以用的(编译期做的事)。此时注意运行时候方法是看子类的,调用子类show(A)。所以输出B and A
- a2.show(d))你先找a1里面有没有这个方法,有show(d),那么 show(D)方法就是可以用的(编译期做的事)。此时注意运行时候方法是看子类的,调用子类show(D)(继承A类而来)。所以输出A and D
- b.show(b)你找b里面有没有这个方法,有show(b)。所以输出B and B
- b.show(c)你找b里面有没有这个方法,有show(b),调用这个show(b)比show(a)更适合一点。所以输出B and B
- b.show(d)你找b里面有没有这个方法,有show(d)。所以输出A and D