Java核心特性解析:封装、继承与多态详解

发表时间: 2024-06-15 05:34

1、封装

1.1 定义

隐藏对象内部的属性和实现细节,仅对外公开接口来和对象进行交互。

例如对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器,USB插孔等,让用户来和计算机进行交互,完成日常事务。但实际上电脑真正工作的却是CPU、显卡、内存等一些硬件元件,而这些元件并不直接对用户开放。

1.2 作用

  • 不需要关注对象内部细节,只需关注对象暴露出来的接口,简化和对象交互
  • 只能通过对象暴露出的接口,进行对对象的访问,规范了对象访问的方式
  • 对对象内部数据结构进行保护

1.3 Java封装的实现

Java中主要通过类和权限修饰符来实现封装,类可以将数据和操作数据的方法结合在一起,更符合人类对事物的认知,权限修饰符用来控制,外部对类内部数据的访问。

权限范围:

权限使用:

  • 类只能被public和default进行修饰
  • 类的成员变量和成员方法可以被public、protected、default、private进行修饰

【注】当父类和子类不在同一个包内,子类对于父类内被protected修饰的成员,只能通过super进行访问。

2、继承

2.1 定义

抽取对多个类的共同属性和行为到某个类,其他的类进行继承,可以实现对这些属性和行为的复用,无需重复定义。如果子类需要扩展可以重写父类方法,或者添加新的属性和方法。

2.2 作用

代码的复用

2.3 继承实现

通用extends关键字进行继承

public class Animal{    protected String name;    protected void eat(){            }} /**  * Dog继承了Animal的name和eat()  */public class Dog extends Animal{    protected String name;    protected void eat(){            }}

【注】

  • 子类会将父类中的成员变量或者成员方法继承到子类中去
  • 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了

2.4 继承原则说明

2.4.1 构造器调用

调用子类构造方法产生子类对象之前,默认调用父类的构造方法先产生父类对象而后产生子类对象

2.4.2 Java中的单继承局限

Java中不允许多重继承,但允许多层继承

Java中一个类只能使用extends继承一个父类,不能同时extends继承多个父类

如下继承语法时错误

public class A{}public class B{}//这种语法时错误的public class C extends A,B{}

2.4.3 super关键字

2.4.3.1 super访问属性

super.属性名 访问的时父类的属性

1)当子类有属性和父类同名时,访问的是子类属性,输出的是子类属性值

public class Parent {    protected String name = "爸爸";}class Son extends Parent{    protected String name="儿子";    public void printInfo(){        System.out.println(super.name); //输出“爸爸”        System.out.println(name);  //输出“儿子”    }    public static void main(String[] args) {        Son son = new Son();        son.printInfo();    }}

输出结果如下图:

2)当子类没有属性和父类同名时,访问的是父类的属性,输出的是父类属性值

public class Parent {    protected String name = "爸爸";}class Son extends Parent{        //protected String name="儿子";  //注释掉看输出的效果    public void printInfo(){        System.out.println(super.name); //输出“爸爸”        System.out.println(name);  //也是输出“爸爸”    }    public static void main(String[] args) {        Son son = new Son();        son.printInfo();    }}

输出结果如下图:

2.4.3.2 super调用构造器

1)父类有默认的无参构造器,子类构造器会默认去调用父类的无参构造器

public class Parent {    public Parent(){        System.out.println("爸爸1");    }    public Parent(String name){        System.out.println(name);    }}class Son extends Parent{    public Son(){        System.out.println("儿子");    }    public static void main(String[] args) {        Son son = new Son();    }}

输出结果如下图:

2)父类没有默认的无参构造器,子类构造器需要手动去调用父类的其他构造器

父类注释默认无参构造器后报错:

父类注释掉默认的无参构造器后,子类构造器内部去调用父类其他构造器,编译通过

2.4.3.3 super访问普通方法

super访问普通方法,逻辑同访问属性一致,这里不再赘述,可参考2.4.3.1

2.4.3.4 总结

  • this表示当前对象的成员变量或方法的调用
  • super表示父类对象的成员变量与方法的调用
  • 当父类中不存在无参构造,则子类构造方法的首行必须使用super(参数)显式调用父类中的有参构造。

2.5 final

1)修饰变量或字段,表示常量(即不能修改)

final int a = 10;a = 20; // 编译出错

2)修饰类表示此类不能被继承

final public class Animal {...}public class Bird extends Animal {...}// 编译出错Error:(3, 27) java: 无法从最终com.bit.Animal进行继承

我们平时使用的 String 字符串类, 就是用 final 修饰的, 不能被继承

3)修饰方法,表示方法不能被重写

3、多态

3.1 定义

当同一种类型的对象,去做同一种行为的时候,所体现的效果不同。

例如打印机有彩色打印机和黑白打印机,它们都有”打印文件“的行为,但是打印的文件颜色效果不同,这就是多态的一种体现。

3.2 多态的条件

  • 必须发生继承
  • 子类要重写父类方法
  • 通过父类的引用调用重写的方法

多态在代码中体现

/** * 打印机 */public abstract class Printer {    protected void print(){        System.out.println("打印出未知颜色的文件");    }}/** * 黑白打印机 */class BlackWhitePrinter extends Printer{    @Override    protected void print() {        System.out.println("打印出黑白的文件");    }}/** * 彩色打印机 */class ColorfulPrinter extends Printer{    @Override    protected void print() {        System.out.println("打印出彩色的文件");    }}class Runner{    public static void main(String[] args) {        Printer pr1 = new BlackWhitePrinter();        Printer pr2 = new ColorfulPrinter();        //pr1和pr2都是用Printer类型的引用进行引用,打印出效果不同,体现了多态。        pr1.print();        pr2.print();    }}

上述代码,基类为Printer,BlackWhitePrinter和ColorfulPrinter都继承了Printer,并重写了Printer的print(),调用的时候,BlackWhitePrinter和ColorfulPrinter的对象都是用Printer类型的变量进行引用,调用了同一个方法,但是产生的效果不同,这是多态的体现。

输出结果如下:

3.3 重写

3.3.1 重写的定义

  • 重写是子类对父类非静态、非private修饰,非final修饰,非构造方法等的实现过程 进行重新编写, 返回值和形参都不能改变
  • 重写的好处在于子类可以根据需要,定义特定 于自己的行为。 也就是说子类能够根据需要实现父类的方法。

3.3.2重写的规则

  • 子类在重写父类的方法时,一般必须与父类方法原型一致: 返回值类型 方法名 (参数列表) 要完全一致
  • 被重写的方法返回值类型可以不同,但是必须是具有父子关系的
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类方法被public修饰,则子类中重写该方 法就不能声明为 protected
  • 父类被static、private修饰的方法、构造方法都不能被重写。
  • 重写的方法, 可以使用 @Override 注解来显式指定. 有了这个注解能帮我们进行一些合法性校验. 例如不小心 将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法 构成重写.

3.3.3重写和重载的区别

3.4 向上和向下转型

3.4.1 向上转型

定义

向上转型:实际就是创建一个子类对象,将其当成父类对象来使用。

语法格式:父类类型 对象名 = new 子类类型()

Animal animal = new Cat("元宝",2);

使用场景

  • 直接赋值
  • 方法传参
  • 方法返回
public class TestAnimal {    // 2. 方法传参:形参为父类型引用,可以接收任意子类的对象    public static void eatFood(Animal a){        a.eat();    }    // 3. 作返回值:返回任意子类对象    public static Animal buyAnimal(String var){        if("狗".equals(var) ){            return new Dog("狗狗",1);        }else if("猫" .equals(var)){            return new Cat("猫猫", 1);        }else{            return null;        }    }    public static void main(String[] args) {        Animal cat = new Cat("元宝",2); // 1. 直接赋值:子类对象赋值给父类对象        Dog dog = new Dog("小七", 1);        eatFood(cat);        eatFood(dog);        Animal animal = buyAnimal("狗");        animal.eat();        animal = buyAnimal("猫");        animal.eat();    }}

【注】

  • 向上转型的优点:让代码实现更简单灵活。
  • 向上转型的缺陷:不能调用到子类特有的方法。

3.4.2 向下转型

将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的 方法,此时:将父类引用再还原为子类对象即可,即向下转换.

public class TestAnimal {    public static void main(String[] args) {        Cat cat = new Cat();        Dog dog = new Dog();        Animal animal = cat; //向上转型        animal.eat(); //输出“cat eat",当前实际类型为Cat        animal = dog; //指向dog对象        animal.eat(); //输出“dog eat",,当前实际类型为Dog       // cat = (Cat)animal;  //animal类型为Animal,实际类型为Dog,向下转型为Cat,虽然编译通过,但是运行时报错       // cat.mew();                dog = (Dog)animal; //animal引用类型为Animal,实际类型为Dog,向下转型为Dog,编译通过,运行也能通过        dog.bark();            }}class Animal{    protected void eat(){};}class Cat extends Animal{    @Override    protected void eat() {        System.out.println("cat eat");    }    public void mew(){        System.out.println("喵喵");    }}class Dog extends Animal{    @Override    protected void eat() {        System.out.println("dog eat");    }    public void bark(){        System.out.println("汪汪");    }}

向下转型用的比较少,而且不安全,万一转换失败,运行时就会抛异常。Java中为了提高向下转型的安全性,引入 了 instanceof ,如果该表达式为true,则可以安全转换。

public class TestAnimal {    public static void main(String[] args) {        Cat cat = new Cat();        Dog dog = new Dog();        Animal animal = cat; //向上转型        animal.eat(); //输出“cat eat",引用类型为Animal,当前实际类型为Cat        animal = dog; //指向dog对象        animal.eat(); //输出“dog eat",引用类型为Animal,当前实际类型为Dog        if (animal instanceof Cat) { //判断实际类型是否为Cat            cat = (Cat)animal;              cat.mew();        }        if (animal instanceof Dog) { //判断实际类型是否为Dog            dog = (Dog)animal;            dog.bark();        }    }}class Animal{    protected void eat(){};}class Cat extends Animal{    @Override    protected void eat() {        System.out.println("cat eat");    }    public void mew(){        System.out.println("喵喵");    }}class Dog extends Animal{    @Override    protected void eat() {        System.out.println("dog eat");    }    public void bark(){        System.out.println("汪汪");    }}