2

Java面向对象三大特征:封装、继承、多态

面向对象三大特征:封装、继承、多态

高内聚和低耦合

面向对象的最终目的是要构建强健、安全、高效的项目,也就是要实现项目的高内聚和低耦合:

  • 高内聚:把该模块的内部数据,功能细节隐藏在模块内部,不允许外界直接干预;只能通过公开的接口访问;
  • 低耦合:该模块只需要给外界暴露少量功能方法;模块之间相互依赖的程度不高;

封装

什么是封装

  1. 把对象的状态和行为看成一个统一的整体,将二者存放在一个独立的模块中,比如:类;
  2. 细节隐藏, 把不想对外公开的实现细节隐藏起来,使用private私有化使其私有化,向外暴露public方法,保证调用者安全访问,不会出现因为越界导致本不应该出现的问题出现;

封装的好处:

  1. 调用者能够正确、方便地使用系统功能,有效地防止调用者随意修改系统属性。
  2. 把特定的功能封装起来,提高功能的重用性。
  3. 降低功能组件之间的耦合性,即使一个模块的实现细节发生改变,只要保证对外暴露的接口或者方法保持不变,就不会影响到其他调用者。

访问权限修饰符

应封装的隐藏细节的理念,java提供了访问权限修饰符来控制调用者的访问权限,详情如下:

private:属于类访问权限,表示私有的,只能在当前类中访问,使用private修饰的类、方法、字段,在’离开当前类的作用域之后,调用者就不能直接访问。

(缺省):其实就是什么都不写,其属于包访问权限,表示包私有的,调用者的包必须和当前类(使用缺省修饰)的包相同才能访问。

protected:属于子类访问权限,表示受保护的,使用private修饰的类、方法、字段不仅同包中类可以访问,而且即使不同包,但是如果有继承关系,也可以访问。

public:表示全局的公共访问权限,使用private修饰的类、方法、字段,在当前项目中任何地方都可以访问。接口(interface)中的方法默认都是public的。

访问权限修饰符

一般情况下,类的字段都使用private修饰;封装了实现细节的方法,一般也使用private修饰,因为不希望调用者直接访问其实现细节,而是要通过公开的public方法间接调用。

// 公开给调用者访问的public方法
public void doWork(){
    methodA();
    methodB();
    methodC();
}
// 三个方法分别封装了部分实现细节
private methodA(){}
private methodB(){}
private methodC(){}

很少会使用(缺省),即使要使用,也仅仅是暴露给同包中的其他类访问;protected很多时候出现在继承关系中,父类只希望被子类访问的字段和方法时;

Java 中的继承

从面向对象的角度上说,继承是一种从一般到特殊的关系,是一种“is a”的关系,即子类是对父类的拓展,是一种特殊的父类,比如:狗是动物的一种特殊情况,狗属于动物;在这个例子中,动物是父类,狗是子类,狗继承了动物的特征和行为,并在动物的特征和行为的基础之上拓展自己的特征和行为,构成了狗这种特殊的动物。

所以可以基于父类并对其加以拓展,产生新的子类定义,这就是继承;子类可以通过继承获得父类原有的字段和方法,也可以增加父类所没有的字段和方法,更是可以覆写父类中的允许被子类覆盖的字段或者方法。

在Java中使用”extends”关键字来表示子类和父类之间的继承关系;在Java中,类之间的继承关系只允许单继承,不允许多继承,一个类只能有一个直接父类。

语法格式:

public class 子类类名 extends 父类类名{        
    编写自己特有的状态和行为
}

也就是说一个类只能有一个直接的父类,不能出现类A同时继承于类B和类C。即便不允许多继承,但是多重继承是被允许的。以下是一个多重继承的例子:

动物有胎生动物和卵生动物之分,胎生动物有老虎,老虎又分华南虎,东北虎,孟加拉虎等。在继承体系中可以这样来表示:

华南虎——》老虎——》胎生动物——》动物

多重继承

最终,华南虎通过多重继承获得了老虎、胎生动物、动物的特征和行为。

继承的优点:

  1. 有效的解决了代码的重用问题,使代码拓展更加灵活;
  2. 通过从继承关系,可以从始至终完整的体现出一个应用体系,逻辑更加清晰;
  3. 一般在开发中是先有多个自定义类,再从多个类中写共同的代码,抽象生成一个父类,然后子类都继承于它。使用web框架开发时,也会更多的使用继承来拓展框架的功能,以适应不同的业务需求。

继承体系

子类可以继承父类的哪些成员(根据访问修饰符来判断):

  1. 父类中使用protected、public修饰的成员;
  2. 父类和子类在同一个包中,父类中缺省修饰符的成员可以被子类继承;

不能继承的是:

  1. 父类中使用private修饰的成员;
  2. 父类的构造器,子类也不能继承,因为构造器必须和当前的类名相同,父类的构造器是父类的名称,子类的构造器是子类的名称;

方法重写和方法重载

方法重写(Override):从父类继承的方法(行为)不符合子类的功能需求,那此时子类就需要重新实现父类的方法,并重写方法体,以实现子类需求。

方法重写的原则:一同两小一大

一同

  • 方法签名必须相同。 方法签名= 方法名 + 方法的参数列表,参数类型、参数个数、参数顺序都必须一致

两小

  1. 子类方法的返回值类型和父类方法的返回类型相同或者是其子类,子类可以返回一个更加具体的子类.
  2. 子类方法声明抛出的异常类型和父类方法声明抛出的异常类型相同或者是其子类,子类方法中声明抛出的异常小于或等于父类方法声明抛出异常类型;子类方法可以同时声明抛出多个属于父类方法声明抛出异常类的子类(RuntimeException类型除外,RuntimeException是个特例);
// 父类
class SuperClass {  
    protected void work() throws Exception {}
}

// 子类
class SubClass extends SuperClass {  
    @Override  
    protected void work() throws NullPointerException, NumberFormatException {}
    
}

一大:

  • 子类方法的访问权限与父类方法访问权限相同或者更大;而private方法不能被子类所继承,也就不会被覆盖。

在重写方法父类方法时,使用@Override注解修饰方法,若是重写错误,就会报编译错误,是一大开发利器;这里需要注意的是只有方法会被重写,字段则没有重写一说。

方法重载(Overload): 在同一个类中,方法名字相同,但是因方法参数列表不同而又不同的实现,这样的机制称为方法重载,其实现原则是:两同一不同,返回值并不计入其中。

两同:相同类,方法名(只是方法名,不是方法签名)相同

一不同:方法参数列表不同,包括参数类型、参数个数、参数顺序都必须一致

protected void work() {}

protected int work(String str) {  return 0;}

方法重载(Overload)和方法重写(Override)两者只是名字上比较接近,其本身并没有关系。

this 关键字

this关键字表示当前对象,当前对象就是this所在的这个对象。this主要存在于两个位置:

  • 构造器中: 就表示当前正在创建的对象
  • 方法中: 表示方法的调用者对象

当一个对象创建之后,JVM会为对象分配一个引用该对象自身的引用:this。this的使用是为了:

  1. 解决成员变量和方法参数、局部变量之间的二义性,使用this可以显式指向成员变量;
  2. 同类中实例方法间相互调用,虽然此时可以省略不写,但还是建议不要省略,以提高代码的可读性。
  3. 将this作为参数传递给另一个方法;
  4. 将this作为方法的返回值(链式方法编程);
  5. 构造器重载的互相调用,this([参数])必须写在构造方法第一行;
  6. static不能和this一起使用;因为当字节码被加载进JVM的时候,static成员就已经存在了,但是此时对象很有可能还没有被创建,没有对象也就没有this。

super 关键字

在对象中this表示当前对象,而super则表示当前对象的父类对象。在子类初始化过程中会创建子类对象,但在创建子类对象之前,会先创建父类对象;也就是说调用子类构造器之前,在子类构造器中会先调用父类的构造器,如果没有显式的调用父类构造器,那么默认情况下会隐式的调用父类无参数构造器。

super关键字用于显式调用父类方法、构造器和字段;可以使用super解决子类隐藏了父类的字段情况;在子类方法中,调用父类被覆盖的方法;在子类构造器中,调用父类构造器。

class SuperClass {  
    public SuperClass() {}
}

class SubClass extends SuperClass {  
    public SubClass() {    
        super(); // 调用父类构造器  
    }
}

父类构造器的不可或缺性:

  1. 如果父类不存在可以被子类访问/调用的构造器,则子类就不可能存在。也就是必须要先有父类对象,而后才会有子类对象;
  2. 如果父类没有提供无参数构造器,子类就必须显式地通过super语句去调用父类带参数的构造器。子类对象在初始化过程中,必须先调用父类构造器,而后再调用子类构造器。

继承中的隐藏

上文中提到了隐藏的概念,继承中的隐藏表示会忽略一些特征和方法,比如静态字段和静态方法:

  1. 满足继承的访问权限下,隐藏父类静态方法:若子类定义的静态方法的签名和超类中的静态方法签名相同,那么此时就是隐藏父类方法。注意:仅仅是在子类存在和父类一模一样的静态方法的情况下。
  2. 满足继承的访问权限下,隐藏父类字段:若子类中定义的字段和父类中的字段名相同(忽略数据类型),此时是隐藏父类字段,但是可以通过super访问被隐藏的字段。
  3. 隐藏本类字段:若本类中的局部变量名和字段名相同,此时就是隐藏本类字段,可以通过this访问被隐藏的字段。

无论是this,还是super,都不能和static一起使用。

Object 类

在Java中除去Object类之外的每一个类都有一个直接或间接的父类:Object类。也就是说除去Object类之外的类都是Object类的直接子类或间接子类。

比如:

class Person {}

class Student extends Person{}

此时Student类的直接父类是Person,Person类的直接父类是Object类,Object类是Student类的间接父类。

Object类是Java的基类,Java中的类都是Object的直接或者间接的子类,Object本身是指对象的意思, 它是所有的对象都具有的共同的行为的抽象类,其他类都会直接或者间接继承于Object类,然后也就拥有了Object类中的方法。

class SuperClass {} 

等价于 

class SuperClass extends Object {}

Object类的常见方法:

  1. finalize() :当垃圾回收器确定不存在对该对象的更多引用时,也就是垃圾回收器会在回收对象之前,会先调用该方法;该方法我们一般不会调用。
  2. getClass() :返回当前对象的真实类型。
  3. hashCode(): 返回该对象的哈希码值,hashCode(哈希码值)决定了对象再哈希表中的存储位置,不同对象的存储位置是不一样的,所以hashCode也会是不一样的。
  4. equals(Object obj) :用当前对象(this)和参数obj做比较,在Object类中的equals方法,比较对象的内存地址。官方建议:每个类都应该覆盖equals方法,不要比较内存地址,而是去比较内容数据。
  5. toString():表示把一个对象转换为字符串,打印对象时,调用的就是对象的toString方法,默认情况下打印的是对象的十六进制的hashCode值。 官方建议:应该每个类都应该覆盖toString,返回我们真正关心的数据。

多态

通过上文,讲清楚了继承关系,继承关系是一种”is a”(是一种)的关系,也就是说子类是父类的一种特殊情况;既然子类是一种特殊的父类,我们是否可以认为子类对象就是父类类型的对象。

考虑以下的代码:

Animal d = new Dog(); //创建一只狗对象,类型是动物
Animal c = new Cat(); //创建一只猫对象,类型是动物

这个时候,多态就产生了。我们以下面的代码为例,详细解释什么是多态:

Animal a = new Dog();

在上例中,对象a具有两种类型:

  • 编译类型: 声明对象变量的类型,Animal;表示把对象看作是什么类型。
  • 运行类型: 对象的真实类型,Dog;运行类型--->对象的真实类型。

编译类型必须是运行类型的父类或与之相同,当编译类型和运行类型不同的时候,多态就产生了。所谓多态是指对象具有多种形态,对象可以存在不同的形式:

Animal a = null;
a = new Dog(); //a此时表示Dog类型的形态
a = new Cat(); //a此时表示Cat类型的形态

多态可以是类和类之间的继承关系,也可以是接口和实现类间的实现关系,一般情况下指的都是接口和实现类间的实现关系。

多态的特点:把子类对象赋给父类类型的变量,在运行时期会表现出具体的子类特征,比如父类类型的变量调用子类的方法。

多态的好处:通过一个例子呈现

需求:给饲养员提供一个喂养动物的方法,用于喂养动物。

  • 如果没有多态,针对于不同类型的动物,得提供不同的喂养方法。可拓展性差,方法重用性低,不优雅。
  • 存在多态:提供统一的喂养方法,大大减轻了饲养员的工作量。从上述例子,不难发现:当把不同的子类对象都当作父类类型来看待,可以屏蔽不同子类对象之间的实现差异,从而写出通用的代码达到通用编程,以适应需求的不断变化。

多态

数据类型转换

基本数据类型转换:大和小表示的是可存储的容量范围。

自动类型转换:把小类型的数据赋给大类型的变量:

  • byte b = 12; byte是1个字节
  • int i = b; int是4个字节

强制类型转换:把大类型的数据赋给小类型的变量。

  • short s = (short) i ;short是2个字节

引用类型的转换:引用类型的大和小,指的是父类和子类的关系。

自动类型转换: 把子类对象赋给父类变量(多态)。

Animal a = new Dog();
Object obj = new Dog();

强制类型转换:把父类类型对象赋给子类类型变量(对象的真实类型是子类类型)。

Animal a = new Dog();
Dog d = (Dog)a;

instanceof 运算符

instanceof 运算符: 判断该对象是否是某一个类的实例。

语法格式:

boolean b = 对象A instanceof 类B; 
// 判断 A对象是否是 B类的实例,如果是,返回true.

instanceof运算符:

  • 若对象是类的实例返回true。
  • 若对象是类的父类的实例也返回true。

组合关系(has a)

在继承关系中,子类可以继承到父类中部分的成员,那么此时子类是可以修改到父类的信息的,此时的继承关系破坏了封装,让子类拥有了本不该具有的功能。那么这时可以使用”包含关系”(has a)的组合关系。

可以这么理解组合关系:把另一个类当作属性来获取其特征和行为。

比如:需求是:我想拥有天子的权力;

  1. 方式1: 当太子,此时表现的是继承关系;
  2. 方式2: 学曹操挟天子以令诸侯,挟持天子,此时是组合/包含关系。

思考:如果A类想要得到B的功能行为,如若A类是B类的一种特殊情况,就应该采用继承来实现,否则使用组合方式。

class Cat {    
    public void eatMouse() {      
        System.out.println("猫吃老鼠"); 
    }    
}

class XiaoMing {    
    // 把cat类当作属性引入  
    private  Cat cat;   
    public XiaoMing() {      
        cat = new Cat();  
    }    
    
    public void killMouse() {          
        cat.eatMouse();  
    }  
}

完结。老夫虽不正经,但老夫一身的才华


老夫不正经
31 声望6 粉丝

老夫一身的才华