封装、继承、多态

1.封装

1.1 概念

举个例子解释,我们使用计算机时,并不关心内部核心部件,而是只需要知道如何开机,如何通过键盘鼠标与计算机进行交互即可.因此厂家在出厂计算机时,在外部套上壳子将内部实现细节隐藏起来,仅仅对外提供开机等,让用户可以与计算机进行交互即可.

封装:

将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,进对外公开接口来和对象进行交互.

1.2 访问限定符

java中主要通过类和访问权限来实现封装

No范围privatedefaultprotectedpublic
1同一包中的同一类
2同一包中的不同类
3不同包中的子类
4不同包中的非子类

private:只有自己知道

default:什么都不写时的默认权限,对于自己家族中不是秘密

protected:主要用在继承中

public:谁都可以知道

一般情况下成员变量设置为private,成员方法设置为public

1.3 封装扩展之包

1.3.1 包的概念

在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组,称为软件包.

在Java中也引入了包,包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式.

1.3.2 导入包中的类

方法一:

public class Test {
  public static void main(String[] args) {
    java.util.Date date = new java.util.Date();
    System.out.println(date.getTime());
  }
}

方法二:(较为简易)

import java.util.Date;
public class Test {
  public static void main(String[] args) {
  Date date = new Date();
  System.out.println(date.getTime());
  }
}

那么看这个:

import java.util.Arrays;
import java.util.Date;

public class Test1 {
    public static void main(String[] args) {
        Date date = new Date();
        Arrays.sort();
    }
}

此时有两个java.util 中的类,我们就可以用 import java.util.*

import java.util.*;

public class Test1 {
    public static void main(String[] args) {
        Date date = new Date();
        Arrays.sort();
    }
}

不是导入util下的所有类,作用是用到哪个类,导入哪个类

更建议显式的指定要导入的类名. 否则还是容易出现冲突,比如:

import java.util.*;
import java.sql.*;

public class Test {
    public static void main(String[] args) {
    // util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
    //java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
    Date date = new Date();
    System.out.println(date.getTime());
    }
}

此时在这种情况下需要使用完整的类名

import java.util.*;
import java.sql.*;

public class Test {
    public static void main(String[] args) {
    java.util.Date date = new java.util.Date();
    System.out.println(date.getTime());
    }
}

所以*非必要不要用

可以使用import static导入包中静态的方法和字段。(不建议)

import static java.lang.Math.*;
public class Test {
    public static void main(String[] args) {
    double x = 30;
    double y = 40;
    // 静态导入的方式写起来更方便一些.
    //代码上看起来简短一点
    // double result = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
    double result = sqrt(pow(x, 2) + pow(y, 2));
    System.out.println(result);
    }
}

1.3.3 自定义包

包其实就是Java当中组织类的一种方式

基本规则

  • 在文件的最上方加上一个 package 语句指定该代码在哪个包中.
  • 包名需要尽量指定成唯一的名字, 通常会用公司的域名的颠倒形式,比如:com.bit,包名一定要全部小写
  • 包名要和代码路径相匹配. 例如创建 com.bit.demo1 的包, 那么会存在一个对应的路径 com/bit/demo1 来存储代码.
  • 如果一个类没有 package 语句, 则该类被放到一个默认包中.

1.3.4 包的访问权限控制举例

 

 

详见0318第六,七次测试

生成封装方法

public class Test2 {
    public static void main(String[] args) {
        Student student = new Student();
        student.setName("小笨猪");
        student.setAge(19);
        System.out.println(student.getName());
        System.out.println(student.getAge());
    }
}
class Student{
    private String name;
    private int age;

    public Student(){
        System.out.println("不含参数的构造方法");
    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("含有两个参数的构造方法");
    }

    public void setName(String name){
        this.name = name;
    }

    public void setAge(int age){
        this.age = age;
    }

    public String getName(){
        return this.name;
    }

    public int getAge(){
        return this.age;
    }

    public void print(){
        System.out.println("姓名:"+this.name+" 年龄: "+this.age);
    }
}

那么我们要一个一个去收到设置属性嘛,当然不是,idea会帮我们生成 

2. 继承

2.1 概念

比如说,猫和狗都是动物,而它们的属性和行为存在大量重复,那么就可以将这些共性抽取,实现代码复用,这就是继承.

看下图所示:

其中,继承之后,子类可以服用父类中成员,子类在实现时只需要关心自己新增的成员即可.

2.2 语法

表示类之间的继承关系,借助extends关键字

 

子类继承父类之后,必须要新添加自己特有的成员,体现出与父类的不同.

2.3 父类成员访问 

2.3.1 子类中访问父类的成员变量

2.3.1.1 子类和父类不存在同名成员变量
public class Base {
    int a;
    int b;
}

public class Derived extends Base{
    int c;
    public void method(){
        a = 10;//访问从父类中继承下来的a
        b = 10;//访问从父类中继承下来的b
        c = 30;//访问子类自己的c
    }
}
2.3.1.2 子类和父类成员变量同名
public class Base {
    int a;
    int b;
}


public class Derived extends Base{
    int a; //与父类中成员a相同,且类型相同
    char b; //类型不同
    public void method(){
        a = 10;//访问子类自己的a
        b = 10;//访问子类自己的b
        c = 30;//子类父类都没有,编译失败
    }
}

注:(就近原则)

①如果访问的成员变量子类中有,优先访问自己的成员变量

②如果访问的成员变量子类中无,则访问从父类继承下来的,如果父类也没有,编译失败

③ 如果访问的成员变量与父类中成员变量同名,则优先访问自己的

2.3.2 子类中访问父类的成员方法

2.3.2.1 成员方法名字不同
public class Base {
     public void methodA(){
         System.out.println("Base中的methodA()");
     }
}
 
public class Derived extends Base{
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
    
    public void methodC(){
        methodB();         // 访问子类自己的methodB()
        methodA();         // 访问父类继承的methodA()
        // methodD();      // 编译失败,在整个继承体系中没有发现方法methodD()
    }
 }
2.3.2.2 成员方法名字相同
public class Base {
     public void methodA(){
         System.out.println("Base中的methodA()");
     }
 
     public void methodB(){
         System.out.println("Base中的methodB()");
     }
 }
 
public class Derived extends Base{
    public void methodA(int a) {
        System.out.println("Derived中的method(int)方法");
    }
 
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
 
    public void methodC(){
        methodA();      // 没有传参,访问父类中的methodA()
        methodA(20);    // 传递int参数,访问子类中的methodA(int)
        methodB();      // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
    }
 }

注:

  • ①通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到 则访问,否则编译报错.
  • ②通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用 方法适传递的参数选择合适的方法访问,如果没有则报错. 

2.4 super关键字

当子类和父类中存在相同名称的成员,如果要在子类方法中访问父类同名成员时,直接访问是无法做到的,此时Java提供了super关键字.

主要作用:在子类方法中访问父类的成员.

public class Base {
    int a;
    int b;
    public void methodA(){
       System.out.println("Base中的methodA()");
    }
 
    public void methodB(){
       System.out.println("Base中的methodB()");
    }

}
public class Derived extends Base{
    int a;    // 与父类中成员变量同名且类型相同
    char b;   // 与父类中成员变量同名但类型不同
    // 与父类中methodA()构成重载
    public void methodA(int a) {
        System.out.println("Derived中的method()方法");
    }
 
    // 与基类中methodB()构成重写
    public void methodB(){
        System.out.println("Derived中的methodB()方法");
    }
 
    public void methodC(){
        // 对于同名的成员变量,直接访问时,访问的都是子类的
        a = 100;   // 等价于: this.a = 100;
        b = 101;   // 等价于: this.b = 101;
        // 注意:this是当前对象的引用
 
        // 访问父类的成员变量时,需要借助super关键字
        // super是获取到子类对象中从基类继承下来的部分
        super.a = 200;
        super.b = 201;
 
 
        // 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
        methodA();      // 没有传参,访问父类中的methodA()
        methodA(20);    // 传递int参数,访问子类中的methodA(int)
 
        // 如果在子类中要访问重写的基类方法,则需要借助super关键字
        methodB();      // 直接访问,则永远访问到的都是子类中的methodA(),基类的无法访问到
        super.methodB(); // 访问基类的methodB()
    }
 }

注:

①只能在非静态方法中使用

②在子类方法中,访问父类的成员变量和方法

2.5 子类构造方法 

 子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法

public class Base1 {
    public Base1() {
        System.out.println("Base1()");
    }
}
public class Derived1 extends Base1{
    public Derived1() {
        System.out.println("Derived2()");
    }
    // super();   // 注意子类构造方法中默认会调用基类的无参构造方法:super(),
    // 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句,
    // 并且只能出现一次

    public static void main(String[] args) {
        Derived1 derived1 = new Derived1();

    }
}

在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整.

注:

1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法.

2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败.

3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句.

4. super(...)只能在子类构造方法中出现一次,并且不能和this同时出现.

2.6 super和this

 

2.7 继承方式

 Java中支持:

 不支持:

2.8 final关键字

final关键可以用来修饰变量、成员方法以及类

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

②修饰类:表示此类不能被继承

2.9 继承与组合

能用组合尽量用组合.

比如:

汽车和其轮胎、发动机、方向盘、车载系统等的关系应该是组合,汽车是由这些部件组成的.

3.多态

3.1 概念 

多态即多种形态.

即就是去完成某个行为,不同的对象去完成时会产生出不同的状态.

3.2 多态实现条件

① 必须在继承体系下

② 子类必须要对父类中方法进行重写

③ 通过父类的引用调用重写的方法

public class Animal {
    String name;
    int age;
    public Animal(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void eat(){
        System.out.println(name + "吃饭");
    }
}


public class Cat extends Animal{
    public Cat(String name, int age){
        super(name, age);
    }
    @Override
    public void eat(){
        System.out.println(name+"吃鱼~~~");
    }
}

public class Dog extends Animal {
    public Dog(String name, int age){
        super(name, age);
    }
    @Override
    public void eat(){
        System.out.println(name+"吃骨头~~~");
    }
}

public class TestAnimal {
    public static void eat(Animal a){
        a.eat();
    }
    public static void main(String[] args) {
        Cat cat = new Cat("xx",2);
        Dog dog = new Dog("mm",3);
        eat(cat);
        eat(dog);
    }
}

解释:

当类的调用者在编写 eat 这个方法的时候, 参数类型为 Animal (父类), 此时在该方法内部并不知道, 也不关注当前的 a 引用指向的是哪个类型(哪个子类)的实例. 此时a这个引用调用eat方法可能会有多种不同的表现(和 a 引用的实例相关), 这种行为就称为多态. 

3.3 重写

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

3.3.1 方法重写的规则

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

3.3.2  重写和重载的区别

即:方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现.

3.4 向上转移和向下转型

3.4.1 向上转型

实际就是创建一个子类对象,将其当成父类对象来使用.

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

优点:让代码实现更简单灵活.

缺陷:不能调用到子类特有的方法.

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.1 向下转型

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

public class TestAnimal {
    public static void main(String[] args) {
        Cat cat = new Cat("元宝",2);
        Dog dog = new Dog("小七", 1);
 
        // 向上转型
        Animal animal = cat;
        animal.eat();
        animal = dog;
        animal.eat();
 
        // 编译失败,编译时编译器将animal当成Animal对象处理
        // 而Animal类中没有bark方法,因此编译失败
        // animal.bark();
 
        // 向上转型
        // 程序可以通过编程,但运行时抛出异常---因为:animal实际指向的是狗
        // 现在要强制还原为猫,无法正常还原,运行时抛出:ClassCastException
        cat = (Cat)animal;
        cat.mew();
 
        // animal本来指向的就是狗,因此将animal还原为狗也是安全的   
        dog = (Dog)animal;
        dog.bark();
    }
 }

3.5 多态的优缺点

用下面代码为例:

class Shape {
    //属性....
    public void draw() {
        System.out.println("画图形!");
    }
 }
 class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("♦");
    }
 }
 class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("●");
    }
 }
 class Flower extends Shape{
    @Override
    public void draw() {
        System.out.println("❀");
    }
 }

3.5.1 好处

①能够降低代码的 "圈复杂度", 避免使用大量的 if - else

 如果要打印多个形状,不基于多态:

public static void drawShapes() {
    Rect rect = new Rect();
    Cycle cycle = new Cycle();
    Flower flower = new Flower();
    String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
    
    for (String shape : shapes) {
        if (shape.equals("cycle")) {
            cycle.draw();
        } else if (shape.equals("rect")) {
            rect.draw();
        } else if (shape.equals("flower")) {
            flower.draw();
        }
    }
 }

如果基于多态:

public static void drawShapes() {
    // 我们创建了一个 Shape 对象的数组. 
    Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), 
                      new Rect(), new Flower()};
    for (Shape shape : shapes) {
        shape.draw();
    }
 }

② 可扩展能力更强

如果要新增一种新的形状, 使用多态的方式代码改动成本也比较低.

class Triangle extends Shape {
    @Override
    public void draw() {
        System.out.println("△");
    }
 }

3.5.2 缺陷

①属性没有多态性

当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性.

②构造方法没有多态性.

3.6 避免在构造方法中调用重写的方法

 class B {
    public B() {
        // do nothing
        func();
    }
 
    public void func() {
        System.out.println("B.func()");
    }
 }
 
class D extends B {
    private int num = 1;
    @Override
    public void func() {
        System.out.println("D.func() " + num);
    }
 }
 
public class Test {
    public static void main(String[] args) {
        D d = new D();
    }
 }
 
// 执行结果
D.func() 0

所以说,尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题. 

  • 49
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值