Java 中面向对象主要有以下两种主要表现:
- 方法的重载与覆写
- 对象的多态性
一、重写和重载
具体内容可参考:重写和重载
重载与重写是 Java 多态性的不同表现
- 重写是父类与子类之间多态性的表现,在运行时起作用(动态多态性,譬如实现动态绑定)
- 而重载是一个类中多态性的表现,在编译时起作用(静态多态性,譬如实现静态绑定)。
1. 重载
重载(Overload)是让类以统一的方式处理不同类型数据的一种手段,实质表现就是多个具有不同的参数个数或者类型的同名函数(返回值类型可随意,不能以返回类型作为重载函数的区分标准)
同时存在于同一个类中,是一个类中多态性的一种表现(调用方法时通过传递不同参数个数和参数类型来决定具体使用哪个方法的多态性)。
重载规则:必须具有不同的参数列表; 可以有不同的返回类型;可以有不同的访问修饰符;可以抛出不同的异常
2. 重写
重写(Override)是父类与子类之间的多态性,实质是对父类的函数进行重新定义,如果在子类中定义某方法与其父类有相同的名称和参数则该方法被重写,不过子类函数的访问修饰权限不能小于父类的;
若子类中的方法与父类中的某一方法具有相同的方法名、返回类型和参数表,则新方法将覆盖原有的方法,如需父类中原有的方法则可使用 super 关键字。
重写规则:
- 参数列表必须完全与被重写的方法相同,否则不能称其为重写;
- 返回类型必须一直与被重写的方法相同,否则不能称其为重写;
- 访问修饰符的限制一定要大于等于被重写方法的访问修饰符;
- 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常,譬如父类方法声明了一个检查异常 IOException,在重写这个方法时就不能抛出 Exception,只能抛出 IOException 的子类异常,可以抛出非检查异常。
3. Java 构造方法能否被重写和重载?
重写是子类方法重写父类的方法,重写的方法名不变,而类的构造方法名必须与类名一致,假设父类的构造方法如果能够被子类重写则子类类名必须与父类类名一致才行,所以 Java 的构造方法是不能被重写的。
而重载是针对同一个的,所以构造方法可以被重载。
4. 扩展
比较结果:
public class Demo {
public boolean equals( Demo other) {
System.out.println("use Demo equals." );
return true;
}
public static void main(String[] args) {
Object o1 =new Demo ();
Object o2 =new Demo ();
Demo o3 =new Demo ();
Demo o4 =new Demo ();
if (o1.equals(o2)) {
System.out.println("o1 is equal with o2.");
}
if(o3.equals(o4)) {
System.out.println("o3 is equal with o4.");
}
}
}
结果:
use Demo equals.
o3 is equal with o4.
- 1
- 2
因为 Demo 类中的 public boolean equals(Demo other)
方法并没有重写 Object 类中的 public boolean equals(Object obj)
方法,原因是其违背了参数规则,其中一个是 Demo 类型而另一个是 Object 类型,因此这两个方法是重载关系(发生在编译时)而不是重写关系;
故当调用 o1.equals(o2)
时,o2 是 Object 类型参数,实际上调用了 Object 类中的 public boolean equals(Object obj)
方法,因为在编译时 o1 和 o2 都是 Object 类型,而Object 类的 equals 方法是通过比较内存地址才返回 false;
当调用 o3.equals(o4)
时,实际上调用了 Demo 类中的 equals(Demo other)
方法,因为在编译时 o3 和 o4 都是 Demo 类型的,所以才有上面的打印。
对象的多态性主要分为以下两种类型:
(1)向上转型:子类对象 -> 父类对象
(2)向下转型:父类对象 -> 子类对象
对于向上转型,程序会自动完成,对于向下转型,必须明确地指明要转型的子类类型
对象向上转型:父类 父类对象 = 子类实例;
对象向下转型:子类 子类对象 = (子类)父类实例;
- 1
- 2
二、向上转型
class A{
public void fun1(){
System.out.println("A --> public void fun1(){}");
}
public void fun2(){
this.fun1();
}
}
class B extends A{
public void fun1(){//覆写父类中的fun1()方法
System.out.println("B --> public void fun1(){}");
}
public void fun3(){//子类自己定义的方法
System.out.println("B --> public void fun3(){}");
}
}
public class Test{
public static void main(String[] args) {
B b = new B();//定义子类实例化对象
A a = b;//发生了向上转型的关系,子类 --> 父类
a.fun1();//此方法被子类覆写过
}
}
- 23
- 24
- 25
以上程序就是一个对象向上转型的关系,如果对象发生了向上转型,所调用的方法一定是被子类覆写过的方法。
但是此时的对象 a 是无法调用 B 类中的 fun3() 方法的,因为此方法只在子类定义,而没有在父类中定义,如果要想调用子类自己的方法,则肯定要使用子类实例,所以此时可以将对象进行向下转型。
三、向下转型
class A{
public void fun1(){
System.out.println("A --> public void fun1(){}");
}
public void fun2(){
this.fun1();
}
}
class B extends A{
public void fun1(){//覆写父类中的fun1()方法
System.out.println("B --> public void fun1(){}");
}
public void fun3(){//子类自己定义的方法
System.out.println("B --> public void fun3(){}");
}
}
public class Test{
public static void main(String[] args) {
A a = new B();//发生了向上转型的关系,子类 --> 父类
B b = (B)a;//此时发生了向下转型
b.fun1();//调用方法被覆写的方法
b.fun2();//调用父类的方法
b.fun3();//调用子类自己定义的方法
}
}
- 23
- 24
- 25
- 26
- 27
可以看出想要调用子类自己的方法,一定只能使用子类声明对象,另外,在子类中调用了父类中的 fun2() 方法,fun2() 方法要调用 fun1() 方法,但此时 fun1() 方法已经被子类所覆写,所以此时调用的方法是被子类覆写过的方法
需要注意的是在进行对象的向下转型前,必须首先发生对象向上转型,否则将出现对象转换异常
四、多态的用处
class A{
public void fun1(){
System.out.println("A --> public void fun1(){}");
}
public void fun2(){
this.fun1();
}
}
class B extends A{
public void fun1(){//覆写父类中的fun1()方法
System.out.println("B --> public void fun1(){}");
}
public void fun3(){//子类自己定义的方法
System.out.println("B --> public void fun3(){}");
}
}
class C extends A{
public void fun1(){//覆写父类中的 fun1() 方法
System.out.println("C --> public void fun3(){}");
}
public void fun5(){//子类自己定义的方法
System.out.println("C --> public void fun3(){}");
}
}
public class Test{
public static void main(String[] args) {
fun(new B());//传递B类实例,产生向上转型
fun(new C());// 传递C类实例,产生向上转型
}
public static void fun(A a){//接收父类对象
a.fun1();
}
}
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
此处由于在 fun()
方法中使用了对象的多态性,所以可以接收任何子类对象,这样无论子类如何增加,fun()
方法都不用做任何的改变,因为一旦发生对象的向上转型后,调用的方法一定是被子类覆写过的方法
对象向上转型的最大的特点在于其可以通过父类对象自动接收 子类实例。而在实际的项目开发中,就可以利用这一原则实现方法接收或 返回参数类型的统一
对象 向上 实例化后 ,如果想调用子类自己扩充的方法时 就必须强制性将其转换为指定的子类类型。
对象向下转型 有意义的时候 的前提 是 对象 向上转型了 这样才可以获得 已经变动的数据值 要不然 对象向下转型 和 普通对象实例化 没有区别