面向对象编程之继承、多态、封装、抽象类、接口、包

面向对象编程之继承、多态、封装、抽象类、接口、包

    • 继承
        • 类的继承格式
        • 为什么要继承
        • 继承的特点
        • 继承的种类及关键字
        • 访问权限
    • 多态
        • 向上转型
        • 动态绑定
        • 方法重写
        • 重写和重载的区别比较
        • 多态的优点
    • 封装
        • 实现Java的封装
        • 封装的优点
    • 抽象类
        • 抽象类及其实现
        • 抽象方法
        • 抽象类使用的注意事项
    • 接口
        • 接口的声明
        • 接口的特性
        • 抽象类和接口的区别
        • 接口与类的区别
        • 包的语法格式和导入
        • 包的作用
    • 欢迎指正

继承

继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

类的继承格式

在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下:

class 父类 {
     
}
 
class 子类 extends 父类 {
     
}

为什么要继承

从这下面两段代码可以看出来,代码存在重复了,导致后果就是代码量大且臃肿,而且维护性不高(维护性主要是后期需要修改的时候,就需要修改很多的代码,容易出错)

/**
 1. user:ypc;
 2. date:2021-04-20;
 3. time: 17:13;
 */
public class Dog {
     
    private String name;
    private int id;
    public Dog(String myName, int  myid) {
     
        name = myName;
        id = myid;
    }
    public void eat(){
     
        System.out.println(name+"正在吃");
    }
    public void sleep(){
     
        System.out.println(name+"正在睡");
    }
    public void introduction() {
     
        System.out.println("大家好!我是"         + id + "号" + name + ".");
    }
}
/**
 4. user:ypc;
 5. date:2021-04-20;
 6. time: 17:14;
 */
public class Cat {
     
    private String name;
    private int id;
    public Cat(String myName, int  myid) {
     
        name = myName;
        id = myid;
    }
    public void eat(){
     
        System.out.println(name+"正在吃");
    }
    public void sleep(){
     
        System.out.println(name+"正在睡");
    }
    public void introduction() {
     
        System.out.println("大家好!我是"         + id + "号" + name + ".");
    }
}

所以要从根本上解决这两段代码的问题,就需要继承,将两段代码中相同的部分提取出来组成 一个父类:

public class Animal {
      
    private String name;  
    private int id; 
    public Animal(String myName, int myid) {
      
        name = myName; 
        id = myid;
    } 
    public void eat(){
      
        System.out.println(name+"正在吃"); 
    }
    public void sleep(){
     
        System.out.println(name+"正在睡");
    }
    public void introduction() {
      
        System.out.println("大家好!我是"         + id + "号" + name + "."); 
    } 
}

这个Animal类就可以作为一个父类,然后狗类和猫类继承这个类之后,就具有父类当中的属性和方法,子类就不会存在重复的代码,维护性也提高,代码也更加简洁,提高代码的复用性(复用性主要是可以多次使用,不用再多次写同样的代码) 继承之后的代码:

public class Dog extends Animal {
      
    public Dog (String myName, int myid) {
      
        super(myName, myid); 
    } 
}
public class Cat extends Animal {
      
    public Cat (String myName, int myid) {
      
        super(myName, myid); 
    } 
}

继承的特点

使用 extends 指定父类.
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性
子类会继承父类的所有 public 的字段和方法.
对于父类的 private 的字段和方法, 子类中是无法访问的.
子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。

继承的种类及关键字

种类
面向对象编程之继承、多态、封装、抽象类、接口、包_第1张图片
关键字
final 关键字
final 关键字也能修饰类, 此时表示被修饰的类就不能被继承.
父类没有final修饰子类可以正常继承
面向对象编程之继承、多态、封装、抽象类、接口、包_第2张图片
有final关键字修饰父类子类不可以继承,编译会报错
面向对象编程之继承、多态、封装、抽象类、接口、包_第3张图片

super 关键字
super关键字:
1.super()调用父类的构造方法,必需放在第一行
面向对象编程之继承、多态、封装、抽象类、接口、包_第4张图片
2.super.父类的成员变量,用super来调用父类的成员变量
面向对象编程之继承、多态、封装、抽象类、接口、包_第5张图片
3.调用父类的成员函数 super.func()
面向对象编程之继承、多态、封装、抽象类、接口、包_第6张图片

this关键字
指向自己的引用。
面向对象编程之继承、多态、封装、抽象类、接口、包_第7张图片

extends关键字
在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
多继承编译会报错
面向对象编程之继承、多态、封装、抽象类、接口、包_第8张图片

访问权限

private 修饰的方法和变量只可以在自己的类中访问,子类也不可以访问。
default 就是不加private,protected,public修饰限定符修饰的方法或变量,在同一个包的同一类和不同类可以访问default方法和变量
protected相比于default,在不同包的子类可以访问protected方法或变量,但这个类必需是它的父类。
public可以在任一个类中访问变量和方法。
可以在任何一个类中使用public类
不能用private和protected来修饰类
访问权限从高到低排列顺序是:
public protected default private
面向对象编程之继承、多态、封装、抽象类、接口、包_第9张图片

多态

向上转型

向上转型,JAVA中的一种调用方式。向上转型是对A的对象的方法的扩充,即A的对象可访问B从A中继承来的和B“重写”A的方法。
例如:

public  class Animal {
     
    public  String name;
    public  int age;
    public Animal(String name, int  age) {
     
        this.age = age;
        this.name = name;
    }
    public void eat() {
     
        System.out.println(this.name+"在吃东西");
    }
    public void sleep() {
     
        System.out.println(this.name+"在睡觉");
        }
}
class Pig extends Animal{
     
    public Pig(String name, int  age){
     
        super( name, age);
    }
    void funcPig(){
     
    }
   public  void eat(){
     
       System.out.println(this.name+"吃桃子");
    }
     void func(){
     
         System.out.println(super.age);
         this.eat();//调用自己的eat()fangfa
         super.eat();//调用父类的eat()方法
         this.funcPig();
     }

}

class Test{
     
    public static void main(String[] args) {
     
        /*
        正常使用
         */
        Animal animal1 = new Animal("gougou",12);
        animal1.eat();
        Pig pig  = new Pig("zhuzhu",8);
        pig.eat();
        /*
        向上转型
         */
        Animal animal =new Pig("bajie",78);
        animal.eat();
    }


}

运行结果为:
面向对象编程之继承、多态、封装、抽象类、接口、包_第10张图片

动态绑定

在 Java 中, 调用某个类的方法, 究竟执行了哪段代码 (是父类方法的代码还是子类方法的代码) , 要看究竟这个引
用指向的是父类对象还是子类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定.
上面的Animal animal =new Pig(“bajie”,78);
animal.eat();就发生了动态绑定
构造方法也可以发生动态绑定(也叫运行时绑定)
动态绑定的条件:
一定有向上转型
子类重写了父类的方法,发生了方法的重写
通过父类的引用来调用被重写的方法

方法重写

子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override)
比如刚才的eat()方法,在Pig子类中重写了父类Animal的eat()方法

public  class Animal {
     
    public  String name;
    public  int age;
    public Animal(String name, int  age) {
     
        this.age = age;
        this.name = name;
    }
    public void eat() {
     
        System.out.println(this.name+"在吃东西");
    }
    public void sleep() {
     
        System.out.println(this.name+"在睡觉");
        }
}
class Pig extends Animal{
     
    public Pig(String name, int  age){
     
        super( name, age);
    }
    void funcPig(){
     
    }
   public  void eat(){
     //重写父类的eat()方法
       System.out.println(this.name+"吃桃子");
    }
     void func(){
     
         System.out.println(super.age);
         this.eat();//调用自己的eat()fangfa
         super.eat();//调用父类的eat()方法
         this.funcPig();
     }

}

class Test{
     
    public static void main(String[] args) {
     
        /*
        正常使用
         */
        Animal animal1 = new Animal("gougou",12);
        animal1.eat();
        Pig pig  = new Pig("zhuzhu",8);
        pig.eat();
        /*
        向上转型
         */
        Animal animal =new Pig("bajie",78);
        animal.eat();
    }


}

针对重写的方法, 可以使用 @Override 注解来显式指定.
面向对象编程之继承、多态、封装、抽象类、接口、包_第11张图片
如果不重写eat()方法编译就会报错
面向对象编程之继承、多态、封装、抽象类、接口、包_第12张图片

重写和重载的区别比较

重写
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。
重写规则:

参数列表与被重写方法的参数列表必须完全相同。

返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

父类的成员方法只能被它的子类重写。

声明为 final 的方法不能被重写。

声明为 static 的方法不能被重写,但是能够被再次声明。

子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。

构造方法不能被重写。

如果不能继承一个类,则不能重写该类的方法
重载
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
重载规则:

被重载的方法必须改变参数列表(参数个数或类型不一样);
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
方法能够在同一个类中或者在一个子类中被重载。
无法以返回值类型作为重载函数的区分标准。
面向对象编程之继承、多态、封装、抽象类、接口、包_第13张图片

多态的优点

消除类型之间的耦合关系
可替换性
可扩充性
接口性
灵活性
简化性
多态存在的三个必要条件
1.继承
2.重写
3.父类引用指向子类对象:Parent p = new Child();
多态的实现方式
方式一:重写:
参见上文
方式二:接口
java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。
参见下文
方式三:抽象类和抽象方法
参见下文

封装

在面向对象程式设计方法中,封装(英语:Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
要访问该类的代码和数据,必须通过严格的接口控制。
封装最主要的功能在于我们能修改自己的实现代码,而不用修改那些调用我们代码的程序片段。
适当的封装可以让程式码更容易理解与维护,也加强了程式码的安全性。

实现Java的封装

  1. 修改属性的可见性来限制对属性的访问(一般限制为private)如:
/**
 * user:ypc;
 * date:2021-04-20;
 * time: 19:15;
 */
public class Person {
     
    private String name;
    private int age;
}

这段代码中,将 name 和 age 属性设置为私有的,只能本类才能访问.如此就对信息进行了隐藏。
2. 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问如:get\set方法(idea快捷键alt+Ins插入)

/**
 * user:ypc;
 * date:2021-04-20;
 * time: 19:15;
 */
public class Person {
     
    private String name;
    private int age;

    public String getName() {
     
        return name;
    }

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

    public int getAge() {
     
        return age;
    }

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

面向对象编程之继承、多态、封装、抽象类、接口、包_第14张图片
3.实例化

/**
 * user:ypc;
 * date:2021-04-20;
 * time: 19:15;
 */
public class Person {
     
    private String name;
    private int age;
    public String getName() {
     
        return name;
    }
    public void setName(String name) {
     
        this.name = name;
    }
    public int getAge() {
     
        return age;
    }
    public void setAge(int age) {
     
        this.age = age;
    }
}
class TestDemo{
     
    public static void main(String[] args) {
     
        Person person = new Person();
        person.setAge(20);
        person.setName("jack");
        System.out.println(person.getName());
        System.out.println(person.getAge());

    }
}

结果
面向对象编程之继承、多态、封装、抽象类、接口、包_第15张图片

封装的优点

良好的封装能够减少耦合。

类内部的结构可以自由修改。

可以对成员变量进行更精确的控制。

隐藏信息,实现细节

抽象类

抽象类及其实现

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
例如:Person就是一个抽象类

public abstract class Person {
     
    private String name;
    private int age;
    public String getName() {
     
        return name;
    }
    public void setName(String name) {
     
        this.name = name;
    }
    public int getAge() {
     
        return age;
    }
    public void setAge(int age) {
     
        this.age = age;
    }
    public void Work(){
     }
}
class Teacher extends Person{
     
    public void Work(){
     
        System.out.println(this.getName()+"的工作是教书");
    }
}
class Student extends  Person{
     
    public void Work(){
     
        System.out.println(this.getName()+"的工作学习");
    }
}
class TestDemo{
     
    public static void main(String[] args) {
     
     //   Person person = new Person();不能实例化否则编译就会报错
        person.setAge(20);
        person.setName("jack");
        System.out.println(person.getName());
        System.out.println(person.getAge());

    }
}

面向对象编程之继承、多态、封装、抽象类、接口、包_第16张图片

抽象方法

如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。
如:Pereon类中的Work()方法
面向对象编程之继承、多态、封装、抽象类、接口、包_第17张图片
抽象方法必需被重写否则就会报错

/**
 * user:ypc;
 * date:2021-04-20;
 * time: 19:15;
 */
public abstract class Person {
     
    private String name;
    private int age;
    public String getName() {
     
        return name;
    }
    public void setName(String name) {
     
        this.name = name;
    }
    public int getAge() {
     
        return age;
    }
    public void setAge(int age) {
     
        this.age = age;
    }
   // public void Work(){}
    public abstract  void Work();
}
class Teacher extends Person{
     //抽象方法必需被重写否则就会报错
    public void Work(){
     
        System.out.println(this.getName()+"的工作是教书");
    }
}
class Student extends  Person{
     
    public void Work(){
     
        System.out.println(this.getName()+"的工作是学习");
    }
}
class TestDemo{
     
    public static void main(String[] args) {
     
        Person person = new Student();
        person.setName("Steven");
        person.Work();
//        Person person = new Person();
//        person.setAge(20);
//        person.setName("jack");
//        System.out.println(person.getName());
//        System.out.println(person.getAge());

    }
}

面向对象编程之继承、多态、封装、抽象类、接口、包_第18张图片

抽象类使用的注意事项

如果一个类包含抽象方法,那么该类必须是抽象类。
任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。
抽象类可以发生向上转型,可以被继承,可以动态绑定
抽象类不能被实例化
抽象类被继承时方法一定要重写
抽象类存在的意义就是为了被继承
抽象方法不能是private
抽象类可以有非抽象方法

接口

接口是抽象类的更进一步. 抽象类中还可以包含非抽象方法, 和字段. 而接口中包含的方法都是抽象方法, 字段只能包含
静态常量.
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象

接口的声明

[可见度] interface 接口名称 [extends 其他的接口名] {
// 声明变量
// 抽象方法
}
例如:IShape就是一个接口

interface IShape {
     
    void draw();
}
class Rect implements  IShape{
     
    public void draw(){
     
        System.out.println("⬜");
    }
}
class Cycle implements IShape {
     
    public void draw() {
     
        System.out.println("○");
    }
}
 class TestDemo2 {
     
    public static void main(String[] args) {
     
        IShape shape = new Rect();
        shape.draw();
    }
}

接口的特性

接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
接口中的方法都是公有的。
也就是说interface IShape{}中的方法默认都是从public abstrat的,并且不能有具体实现
变量默认都是public abstrat final的
接口可以向上转型,可以发生运行时绑定即动态绑定
接口中的方法可以具体实现但必须是这样的:default void function(){…}
一个类可以实现多个接口
接口的实现就是为了满足继承
接口可以拓展
一个类可以实现一个接口也可以继承一个类
如下:
面向对象编程之继承、多态、封装、抽象类、接口、包_第19张图片

class A{
     

}
interface IShape {
     
    void draw();
}
/*
一个类可以实现多个接口
接口的实现就是为了满足继承
接口可以拓展
一个类可以实现一个接口也可以继承一个类
 */
interface IShape2 extends IShape {
     
    void draw();
}
class Rect extends A implements  IShape,IShape2{
     
    public void draw(){
     
        System.out.println("⬜");
    }
}
class Cycle implements IShape {
     
    public void draw() {
     
        System.out.println("○");
    }
}
 class TestDemo2 {
     
    public static void main(String[] args) {
     
        IShape shape = new Rect();
        shape.draw();
    }
}

抽象类和接口的区别

抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
一个类只能继承一个抽象类,而一个类却可以实现多个接口。

接口与类的区别

接口不能用于实例化对象。
接口没有构造方法。
接口中所有的方法必须是抽象方法。
接口不能包含成员变量,除了 static 和 final 变量。
接口不是被类继承了,而是要被类实现。
接口支持多继承

为了更好地组织类,Java 提供了包机制,用于区别类名的命名空间。

包的语法格式和导入

包的语法格式
package pkg1[.pkg2[.pkg3…]];
包的导入
import package1[.package2…].(classname|*);
例如:Demo6导入了B包中的Demo5类并在主函数中实列化了Demo5类,使用了Demo5类的func函数
同时也继承了A接口(不能实现A接口(不能使用implements接口),只能继承)
面向对象编程之继承、多态、封装、抽象类、接口、包_第20张图片

面向对象编程之继承、多态、封装、抽象类、接口、包_第21张图片
包B中的Demo5类的代码

package B;

/**
 * user:ypc;
 * date:2021-04-20;
 * time: 20:46;
 */
interface A{
     
    public void test();
}
public class Demo5 {
     
    public void func(){
     
        System.out.println("导入了B包中的Demo5类的func()方法");
    }
}

包外类Demo6的代码

import B.Demo5;
/**
 * user:ypc;
 * date:2021-04-20;
 * time: 20:46;
 */
class B extends A {
     
    public void test(){
     
        System.out.println("继承了B包中的A接口的test方法");
    }
}
public class Demo6 {
     
    public static void main(String[] args) {
     
        B b = new B();
        b.test();
        Demo5 demo5 = new Demo5();
        demo5.func();

    }

}

包的作用

把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。

如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。

包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

欢迎指正

你可能感兴趣的:(Java,抽象类,多态,java,设计模式,封装)