Java语言基础-08-面向对象三大特性

204 阅读9分钟

我们都知道,面向对象的三大特性:封装、继承、多态。具体说来,它们到低是啥意思呢,下面简单概括一下。

封装:高内聚、低耦合

  1. 将属性私有化,然后提供公共的getter和setter方法
  2. 将方法私有化,只对内使用,对外不暴露

继承:表达类与类之间的关系

多态:父类指针指向子类对象

访问修饰符

  • private:当前本类内被访问,子类都不行
  • public:不限制访问
  • protected:本类、子类、同包可以访问。跨包非子类不允许访问
  • 默认:本类,同包可以访问。其他不可以。

可以用以上修饰符来修饰类、类的内部结构(属性、方法)。

封装

类的封装

隐藏对象的信息,同时留出可控的访问接口,实现高内聚低耦合的效果。

public class Cat{
  //私有属性
  private String name;
  
  //setter方法
  public void setName(String name){
    this.name = name;//再加上其他规则逻辑判断
  }
  //getter方法
  public String getName(){
    return this.name;
  }
}

包封装

管理java文件,解决同名文件冲突问题
同一个包内文件名必须不同(类名),不同包内文件名可以相同(类名)

//声明包
package 包名
//导入包
import 包名
import com.hellomeng.study.*; //加载com.hellomeng.study包下的所有类(不包括子包)
import com.hellomeng.study.Java;//加载com.hellomeng.study包下的Java类

//还可以不在程序开头导入包,直接在代码中写全包名加类名的方式使用类

当然,利用IDE,你并不需要package 包名 这样创建包。在你创建文件夹时,这一切工具都替你自动完成了。打开IEDA,随便找一个java文件。在头部你都能看到上述信息。

代码块/初始化块

{}括起来的一段代码,一般用于初始化类或对象

public class Cat{
      String name;
      public void eat(){
            System.out.println("吃")
      }
      {
        //构造代码块,在对象被创建时调用,优先于构造方法执行
        //如果是多个构造代码块,按照先后顺序执行
      }
}

当代码块定义在方法中时,称之为普通代码块

  public void eat(){
        System.out.println("吃");
        {
          //普通代码块,按照方法内部执行顺序执行
        }
  }

static修饰的代码块称之为静态代码块

static {
	//随着 类的加载 而加载并自动执行(静态方法是随着类的加载而加载)
}
{
	//非静态代码块随着 每个 对象的创建而加载并执行
}

下面说一下从类被加载,到创建对象,代码块、构造函数执行顺序

⚠️
静态代码块【类加载的时候执行一次】==》非静态/初始化代码块【对象创建时执行】==》构造函数 【创建对象时执行、父类先执行,子类后执行】

继承

表达的是一种类与类之间的关系。
使用已经存在的类作为基础创建新的类。
但是子类不能选择性的继承父类,一旦继承(包括直接父类和间接父类),就要继承所有的属性和方法(私有的 属性或方法,子类虽然继承了,但是不能访问)。

注意:子类无法直接访问父类中的私有属性或方法(可通过公共的getter或setter方法调用)
注意:父类的构造方法是不能被继承的 继承语法:

class 子类 extends 父类{
  
}

java中的继承只能是单继承,一个子类只能有一个父类。但是存在间接继承的关系,子类-》父类-》父类的父类-》。。。。

方法重写

在子类中。方法名,返回值类型,参数类型,参数个数,参数顺序都与父类完全一致,就构成了方法重写。

注意区分重写和重载。直白但不严谨的理解是:重载是同一类的两个“同名方法”。重写是覆盖了父类的“同名方法”。另外,方法重写是运行期特性,表现为多态。

  • 属性也是可以”重写“的

  • 子类重写父类时,访问修饰符可以修改,但是权限必须大于等于父类的访问权限(private<默认<protected<public)

  • 子类重写父类方法时,返回值类型要和父类保持一致。但如果返回值是A类时,子类重写了这个方法的返回值,可以是A类或A类的子类。

  • 子类不能重写父类中private权限的方法

  • 子类重写的方法抛出的异常类型,要求不大于父类方法中异常的类型。

  • 子类和父类同名同参(要重写)的方法,要么都是非static的(构成重写),要么都是static的(不构成重写)。否则会报错(父类static,子类非static,不允许)

子类对象实例化过程

要先把类加载进来,才能根据类实例化对象,这样就好理解了。

  • 类的加载:
    先加载父类的静态成员,再加载子类的静态成员
  • 子类对象实例化的过程中
    父类静态代码块->子类静态代码块->父类构造代码块->父类构造方法->子类构造代码块->子类构造方法

子类对象的实例化过程中,如果不显示指定,默认调用的是父类的无参构造。所以,无论用不用,都建议在创建一个类的时候,把无参构造显示声明出来。

子类构造中,如果用super显示调用父类构造的时候。如:super(参数)。这行代码要放在子类构造方法的第一行。

在构造方法中调用其他构造方法时。this(参数),super(参数),不能同时出现。

构造子类的时候,一定会直接或间接的调用父类的构造器,直到Object。但是,只会创建你想创建的这个子类的一个对象。虽然调用了父类的构造器,但并不会创建多个父类对象。

多态

  1. 何为多态?
    可以理解为一个事物的多种形态。具体指允许不同类的对象对用一消息做出不同的响应
  2. 为什么多态?
    可以让“接口”变得更加通用
public void test(Animal animal){
	animal.eat()
}

假设 Animal 有两个子类 分别是 Gou 和 Mao。他们都重写了eat()方法。
此时,这个animal参数,你传的是狗,下面eat方法执行的就是狗的eat。你传mao,执行的就是mao的eat。如果没有多态性。你要进行方法重载,即分别写两个test方法。参数分别为猫,和狗。

  1. 怎么多态?

    父类引用指向子类对象(重点)

⚠️:对象的多态性只适用于方法,不适用于属性。
(指向子类对象的父类指针调用一个子父类都有的属性,实际调用的是父类的。

向上转型

说的是父类指针指向子类对象

Animal * animal = new Animal();
Animal * cat = new Cat();
Animal * dog = new Dog();
aninal.eat() //动物吃东西
cat.eat() //猫吃鱼
dog.eat()//狗吃肉

父类指针 可以调用父类在子类中被重写的方法,或者父类派生下去的方法(子类继承的方法)。但无法调用子类特有的方法(因为对象(指针、引用)类型是父类)(如果非要调用子类特有的部分,只能强转子类)

编译看父类(类型),运行看子类(实例)。属性除外,都看父类
⚠️:由于父类和子类之间的同名静态方法不构成重写,所以,父类指针指向子类对象时,当调用静态方法时,实际调用的是父类中的

向下转型(强制转换)

父类指针指向子类对象,根据多态性,可以调用父类中的属性和方法。其中方法部分,实际运行时会执行子类的方法。但是,父类指针无法调用子类特有的对象和方法。除非把父类类型强制转换为子类类型。
也就是:

子类引用指向父类实例(必须强制转换)

Cat cat = (Cat)animal;

这种强制转换,一定要做好逻辑判断,否则会引起运行时错误。
做什么样的逻辑判断呢,就是判断 对象是否满足某个类的实例特征。比如。animal是否满足Cat的特性。满足才能强转。

instanceof

对象 instanceof//对象是否满足某个类的实例特征
cat instanceof Cat;//true
cat instanceof Animal;//true
cat instanceof Car;//false

Object

是所有类的直接或间接父类(包括数组)
Java中,所有类,都可以使用Object中的成员或方法。

Object类中的常用方法

  • equals判断两个对象的引用是否一致

在其他类中不一定是这个意思。比如在String中,就重写了equals方法,判断的时两个字符串的值是否相等

  • toString打印出一个对象的字符串表现形式

一般在子类中也会对其重写

接口

不同的类型,不满足继承关系。可以抽象出某些功能放到接口中。或者可以把接口理解为一组规范,任何人都可以对接口进行实现,从而具备接口的功能,但必须满足规范。

一个类可以实现多个接口(遵守多个规范)

public interface 接口名{//接口名一般用I开头
	//抽象方法
	接口中声明的抽象方法,可以不写abstract关键字
	public void 方法名();	
}

使用接口时,在类的后面加implements

public class 类名 implements 接口名{
    public void 方法名(){
        //方法实现
    }
}

当一个类去实现接口时,需要实现接口中的所有抽象方法

接口中也可以定义常量

在接口方法声明前加上default,就成为默认方法,或者加上static,成为静态方法。
实现这个接口的类可以选择不实现默认方法或静态方法

default public void 方法名(){
	//默认方法体
}

区别:静态方法不能被实现类重写,只能接口自己用

调用接口中的默认方法:

A implements B{
	B.supper.默认方法
}

一个类可以实现多个接口,中间用, 号分隔。

当多个接口中有同名的默认方法时,实现接口的类要调用默认方法的时候不知道调用哪一个。此时只能重写默认方法。注意:是默认方法哦,带default的那个。

如果接口中的默认方法和父类中的方法同名,子类调用的时候,调用的是父类中的方法。

对于多个接口、父类中 存在重名的常量时,子类调用。要么指定接口接口名.常量。要么在子类中重写属于自己的常量。否则会报错。编译器不知道取哪个常量。

Java中接口也可以存在继承关系,并且接口可以多继承,用,号分隔

public interface 接口名 extends 父接口,父接口{
    ​
}