- 一:对于多态的理解
- 二:Java多态实现
- (1)重写(override)
- A:重写规则
- B:重写和重载的区别
- (2)向上转型和向下转型
- A:向上转型
- B:向下转型(基本不用)
- (3)实现多态
- 三:静态绑定与动态绑定
- 四:不要在构造方法中调用重写的方法
- 五:抽象类
- (1)一个例子
- (2)抽象类概念
- (3)抽象类语法及特性
多态:面向对象方法学中多态是指子类对象可以像父类对象那样使用,同样的消息既可以发送给父类对象也可以发送给子类对象。即在类等级的不同层次中可以共享一个方法的名字, 不同层次中的每个类各自按自己的需要来实现这个行为。通俗点理解就是指完成某个行为时,不同对象有着不同的完成方法
比如买票行为,学生和普通人都属于人,但是学生买票可以半价,而普通人是全票
Java多态实现:和C++类似,在Java中要实现多态,必须满足以下条件
- 继承是多态的前提,没有继承就没有多态
- 子类要对父类的方法完成重写
- 需要通过父类的引用调用重写的方法
重写(override):也即覆盖,是子类对父类的非静态、非private修饰、非final修饰和非构造方法等的实现过程进行重新编写,返回值和形参都不能改变
A:重写规则重写规则:
- 子类重写父类时与父类方法原型保持一致,也即修饰符 返回值类型 方法名(参数列表)需要完全一致(注意:JDK7以后,被重写的方法返回值类型可以不同,但是必须是具有父子关系的,这称之为协变)
- 访问权限不能比父类中被重写的方法的访问权限更低:例如,如果父类方法被public修饰,那么子类中重写该方法时不可以被声明为protected
- 父类被static、private、final所修饰以及构造方法都不能被重写
- 重写的方法可以使用@Override注解来显式指定。此注解可以帮助我们进行一些合法性检验
class Animal{
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eating(){
System.out.println(name + "吃饭");
}
}
class Dog extends Animal{
public Dog(String name, int age){
super(name, age);
}
@Override //重写父类
public void eating(){
System.out.println(name + "吃鱼");
}
}
class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override//重写父类
public void eating(){
System.out.println(name + "啃骨头");
}
}
B:重写和重载的区别
- 方法重载:是类的多态性表现
- 方法重写:是子类与父类的一种多态性表现
向上转型:使用父类的引用指向子类对象,语法格式为:父类类型 对象名 = new 子类类型()
- 向上转型是合理、安全的,因为大范围可以囊括小范围
- 优点:让代码实现更为简单、灵活
- 缺点:不能调用子类特有的方法
class Animal{
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eating(){
System.out.println(name + "吃饭");
}
}
class Dog extends Animal{
public Dog(String name, int age){
super(name, age);
}
@Override //重写父类
public void eating(){
System.out.println(name + "啃骨头");
}
}
class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override//重写父类
public void eating(){
System.out.println(name + "吃小鱼");
}
}
public class TestDemo {
public static void main(String[] args) {
Animal animal = new Dog("旺财", 3);//向上转型
}
}
向上转型一般会有用于以下场景
- 直接赋值:上面代码已经说过
- 方法传参:形参为父类引用,可以接受任意子类对象
- 方法返回:返回值为父类引用,此时可以返回任意子类对象
class Animal{
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eating(){
System.out.println(name + "吃饭");
}
}
class Dog extends Animal{
public Dog(String name, int age){
super(name, age);
}
@Override //重写父类
public void eating(){
System.out.println(name + "啃骨头");
}
}
class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override//重写父类
public void eating(){
System.out.println(name + "吃小鱼");
}
}
public class TestDemo {
//形参为父类引用,可以接受任意子类对象
public static void eatFood(Animal animal){
animal.eating();
}
public static void main(String[] args) {
Cat cat = new Cat("喵喵", 3);
eatFood(cat);
}
}
class Animal{
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eating(){
System.out.println(name + "吃饭");
}
}
class Dog extends Animal{
public Dog(String name, int age){
super(name, age);
}
@Override //重写父类
public void eating(){
System.out.println(name + "啃骨头");
}
}
class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override//重写父类
public void eating(){
System.out.println(name + "吃小鱼");
}
}
public class TestDemo {
//方法返回值为父类引用,此时可以返回任意子类对象
public static Animal buyAnAnimal(String animal_name){
if(animal_name.equals("狗")) {
return new Dog("旺财", 2);
}
else if(animal_name.equals("猫")) {
return new Cat("喵喵", 3);
}
else{
return null;
}
}
public static void main(String[] args) {
System.out.println("进入宠物店:请问你需要什么动物");
Scanner sc = new Scanner(System.in);
String animal_name = sc.nextLine();
Animal animal = buyAnAnimal(animal_name);
System.out.println("你得到了:" + animal.name);
sc.close();
}
}
B:向下转型(基本不用)
向下转型:使用子类的引用指向父类对象,但是向下转型很不安全,所以基本不会使用
如下是向上转型,Animal animal = new Dog("旺财", 2),所以animal是无法调用Dog的特有方法dog_method()的
class Animal{
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eating(){
System.out.println(name + "吃饭");
}
}
class Dog extends Animal{
public Dog(String name, int age){
super(name, age);
}
@Override //重写父类
public void eating(){
System.out.println(name + "啃骨头");
}
public void dog_method(){
System.out.println(name + "的特有方法");
}
}
class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override//重写父类
public void eating(){
System.out.println(name + "吃小鱼");
}
}
public class TestDemo {
public static void function(Animal animal){
animal.eating();
}
public static void main(String[] args) {
Animal animal = new Dog("旺财", 2);
animal.dog_mehtod();
}
}
但是我们可以向下转型,也即让一个子类的引用指向强转后的父类对象,此时便可以调用Dog的特有方法dog_method()了
public class TestDemo {
public static void function(Animal animal){
animal.eating();
}
public static void main(String[] args) {
Animal animal = new Dog("旺财", 2);
Dog dog = (Dog)animal;//向下转型
dog.dog_method();
}
}
向下转型很不安全,而且有时会有严重的歧义。 Java中为 了提高向下转型的安全性,引入了instanceof,如果该表达式为true,则可以安全转换
(3)实现多态讲到这里,其实已经完成了多态,所以日后我们就可以利用继承和转型来完成传哪个对象就调用哪个对象的方法的目的
class Animal{
String name;
int age;
public Animal(String name, int age){
this.name = name;
this.age = age;
}
public void eating(){
System.out.println(name + "吃饭");
}
}
class Dog extends Animal{
public Dog(String name, int age){
super(name, age);
}
@Override //重写父类
public void eating(){
System.out.println(name + "啃骨头");
}
}
class Cat extends Animal{
public Cat(String name, int age){
super(name, age);
}
@Override//重写父类
public void eating(){
System.out.println(name + "吃小鱼");
}
}
public class TestDemo {
public static void function(Animal animal){
animal.eating();
}
public static void main(String[] args) {
Dog dog = new Dog("旺财", 2);
Cat cat = new Cat("喵喵", 3);
function(dog);
function(cat);
}
}
三:静态绑定与动态绑定
- 静态绑定:也称为早绑定,是指在编译时根据用户所传递实参类型确定所调用的方法,最典型的例子就是方法重载
- 动态绑定:也称为晚绑定,是指在编译时不能确定该调用的方法,需要在程序运行确定,最典型的例子就是多态,在程序运行时才能拿到具体的类型
下面的代码中,B继承自A,func方法被重写。在构造B对象时会调用A的构造方法,在A的构造方法中调用了func方法,但此时是B对象,所以会调用到B的func,而B对象而没有完成构造,所以num处于未被初始化状态,为0
class A{
public A(){
func();
}
public void func(){
System.out.println("A的func()方法");
}
}
class B extends A{
private int num = 1;
@Override
public void func(){
System.out.println("B的func()方法,num="+num);
}
}
public class TestDemo{
public static void main(String[] args) {
B b = new B();
b.func();
}
}
五:抽象类
(1)一个例子
如下一个类Shape,其中有一个方法draw,作用是向屏幕打印某个具体的图形(没有具体实现),图形有很多种,这里给出了Rectangle、Triangle和Circle 三个类,分别用于描述矩形、三角形和圆形,它们各自内部也有一个方法draw,重写了父类的方法,形成多态
class Shape{
public void draw(){
System.out.println("画一个图形");
}
}
class Rectangle extends Shape{
@Override
public void draw(){
System.out.println("画一个矩形");
}
}
class Triangle extends Shape{
@Override
public void draw(){
System.out.println("画一个三角形");
}
}
class Circle extends Shape{
@Override
public void draw(){
System.out.println("画一个三角形");
}
}
public class TestDemo {
public static void drawing(Shape input){
input.draw();
}
public static void main(String[] args) {
Circle circle = new Circle();
drawing(circle);
}
}
对于父类的方法,它的实现是没有意义的,因为“画一个图形”这样的话太过抽象,无法具体去描述。所以,我们可以不用实现它,将其改造为抽象方法,需要在方法返回值前加入关键字abstract,当某个类拥有抽象方法后它就变成了抽象类,也需要在其前面加入关键字abstract进行修饰
- 注意:抽象方法就是C++中的纯虚函数
abstract class Shape{
abstract public void draw();//抽象方法
}
class Rectangle extends Shape{
@Override
public void draw(){
System.out.println("画一个矩形");
}
}
class Triangle extends Shape{
@Override
public void draw(){
System.out.println("画一个三角形");
}
}
class Circle extends Shape{
@Override
public void draw(){
System.out.println("画一个三角形");
}
}
public class TestDemo {
public static void drawing(Shape input){
input.draw();
}
public static void main(String[] args) {
Circle circle = new Circle();
drawing(circle);
}
}
(2)抽象类概念
抽象类:在面向对象方法学中,所有对象都是需要通过类来描绘的,但这不意味着所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就应该设计为抽象类
- 例如上例中Shape类中也存在draw方法,但是Shape它并代表某个具体图形,是没有办法来实现的
抽象类语法:在Java中,如果一个类被abstract修饰,就称它为抽象类,抽象类中被abstract修饰的方法称之为抽象方法,注意以下几点
①:抽象类无法进行实例化
②:抽象方法不能是private的
③:抽象类存在的意义是为了被继承,它所体现的是接口继承。也就是说我只需要你存在这样一个方法名,你的实现我是不关心的
④:抽象必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也将会是抽象类
⑤:抽象方法不能被final和static修饰



