Java的面向对象细节,你都知道吗?

目录

面向对象简介

何为“面向对象”的编程思想?

1.何为“思想”和“编程思想”?

先问你个问题:你想做个怎样的人?
可能你会回答:我想做个好人, 孝敬父母, 尊重长辈, 关爱亲朋……你看, 这就是思想。 这是你做人的思想, 或者说, 是你做人的原则。做人有做人的原则, 编程也有编程的原则。 这些编程的原则呢, 就是编程思想。

2.面向过程(POP) 与 面向对象(OOP)

二者都是一种思想,面向对象是相对于面向过程而言的。

面向过程, 强调的是功能行为,以函数为最小单位,考虑怎么做。

面向对象,将功能封装进对象, 强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。

面向对象: Object Oriented Programming
面向过程: Procedure Oriented Programming

3.面向对象与面向过程的实例

面向过程
1.打开冰箱
2.把大象装进冰箱
3.把冰箱门关住
面向对象
{
    打开(冰箱) {
    	冰箱.开门();
    }
    操作(大象){
    	大象.进入(冰箱);
    }
    关闭(冰箱){
    	冰箱.关门();
    }
}
冰箱{
    开门(){ }
    关门(){ }
}
大象{
	进入(冰箱){ }
}

面向对象的思想概述

  • 程序员从面向过程的执行者转化成了面向对象的指挥者
  • 面向对象分析方法分析问题的思路和步骤:
    • 根据问题需要,选择问题所针对的现实世界中的实体。
    • 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
    • 把抽象的实体用计算机语言进行描述, 形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
    • 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。

面向对象的两个要素

  1. 面向对象程序设计的重点是类的设计
  2. 设计类,就是设计类的成员。

面向对象的三大特征

封装性

问题引入

当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其他制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行限制条件的添加。(比如:setLegs())同时,我们需要避免用户再使用"对象.属性"的方式对属性进行赋值。则需要将属性声明为私有的(private).

–>此时,针对于属性就体现了封装性。

封装性的思想

为什么需要封装?封装的作用和含义?

我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
我要开车, …

使用者对类内部定义的属性(对象的成员变量)的直接操作会导致数据的错误、混乱或安全性问题。所以应该使用封装,将信息隐藏.

我们程序设计追求“高内聚,低耦合”。

高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;

低耦合 : 仅对外暴露少量的方法用于使用。

封装性的设计思想

隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说, 把该隐藏的隐藏起来,该暴露的暴露出来。 这就是封装性的设计思想。

封装性的体现

Java中通过将数据声明为私有的(private), 再提供公共的(public)方法:getXxx()setXxx()实现对该属性的操作, 以实现下述目的:

  1. 隐藏一个类中不需要对外提供的实现细节;
  2. 使用者只能通过事先定制好的方法来访问数据, 可以方便地加入控制逻辑,
    限制对属性的不合理操作;
  3. 便于修改, 增强代码的可维护性;

封装性的体现,需要权限修饰符来配合。

四种访问权限修饰符

Java权限修饰符public、 protected、 (缺省)、 private置于类的成员定义前,用来限定对象对该类成员的访问权限。

  1. Java规定的4种权限(从小到大排列):private、缺省、protected 、public

  2. 4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类

  3. 具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类

  4. 修饰类的话,只能使用:缺省、public

修饰符 类内部 同一个包 不同包的子类 同一个工程
private yes
default(缺省) yes yes
protected yes yes yes
public yes yes yes yes
注意:

对于class的权限修饰只可以用public和default(缺省)。

  • public类可以在任意地方被访问。
  • default类只可以被同一个包内部的类访问。

总结封装性

Java提供了4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性的大小。

继承性

为什么要有继承?

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。

此处的多个类称为子类(派生类), 单独的这个类称为父类(基类或超类)。 可以理解为:“子类 is a 父类”

语法规则

class Subclass extends SuperClass{ }

作用

  1. 继承的出现减少了代码冗余,提高了代码的复用性。

  2. 继承的出现,更有利于功能的扩展。

  3. 继承的出现让类与类之间产生了关系,提供了多态的前提。

继承的体现

  1. 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。

  2. 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。子类和父类的关系,不同于子集和集合的关系。

继承的规则

  1. 一个类可以被多个子类继承。

  2. Java中类的单继承性:一个类只能有一个父类

  3. 子父类是相对的概念。

  4. 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类

  5. 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法

  6. 子类不能直接访问父类中私有的(private)的成员变量和方法。

  7. Java只支持单继承和多层继承, 不允许多重继承

  • 一个子类只能有一个父类

  • 一个父类可以派生出多个子类

    class SubDemo extends Demo{ } //ok
    class SubDemo extends Demo1,Demo2...//error
    
示例

image-20200530203541927

image-20200530203605521

注意事项

  1. 不 要仅为了获取其他类中某个功能而去继承

  2. 在Java 中,继承的关键字用的是“extends”,即子类不是父类的子集,而是对父类的“扩展” 。

  3. 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类

  4. 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类。意味着,所有的java类具有java.lang.Object类声明的功能。

子类对象实例化的全过程

  1. 从结果上来看:(继承性)
  • 子类继承父类以后,就获取了父类中声明的属性或方法。

  • 创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。

  1. 从过程上来看:
  • 当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,…

  • 直到调用了java.lang.Object类中空参的构造器为止。

  • 正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。

注意:

虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。

执行过程图示

image-20200530215521479

代码
class Creature {
    public Creature() {
    	System.out.println("Creature无参数的构造器");
    }
}

class Animal extends Creature {
    public Animal(String name) {
    	System.out.println("Animal带一个参数的构造器,该动物的name为" + name);
    }
    
    public Animal(String name, int age) {
        this(name);
        System.out.println("Animal带两个参数的构造器,其age为" + age);
    }
}

public class Wolf extends Animal {
    public Wolf() {
        super("灰太狼", 3);
        System.out.println("Wolf无参数的构造器");
    }
    
    public static void main(String[] args) {
    	new Wolf();
    }
}

image-20200530215741126

image-20200530215756048

思考

1.为什么super(…)和this(…)调用语句不能同时在一个构造器中出现?
2.为什么super(…)或this(…)调用语句只能作为构造器中的第一句出现?

继承成员变量和继承方法的区别

子类继承父类:

  • 若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
  • 对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量

多态性

如何理解多态性?

可以理解为一个事物的多种形态。

何为多态性?

对象的多态性:父类的引用指向子类的对象(或子类的对象赋给父类的引用)

  • 可以直接应用在抽象类和接口上

多态性的作用

提高了代码的通用性,常称作接口重用

多态性的使用

虚拟方法调用

有了对象的多态性以后,我们在编译期,只能调用父类中声明的方法,但在运行期,我们实际执行的是子类重写父类的方法。

总结:编译,看左边;运行,看右边。

多态性的注意事项

Java引用变量有两个类型: 编译时类型和运行时类型。 编译时类型由声明该变量时使用的类型决定, 运行时类型由实际赋给该变量的对象决定。 简称: 编译时, 看左边;运行时, 看右边。

  • 若编译时类型和运行时类型不一致, 就出现了对象的多态性(Polymorphism)

  • 多态情况下, “看左边” : 看的是父类的引用(父类中不具备子类特有的方法);“看右边” : 看的是子类的对象(实际运行的是子类重写父类的方法)

  • 对象的多态 —在Java中,子类的对象可以替代父类的对象使用

    • 一个变量只能有一种确定的数据类型

    • 一个引用类型变量可能指向(引用)多种不同类型的对象

      Person p = new Student();
      Object o = new Person();//Object类型的变量o, 指向Person类型的对象
      o = new Student(); //Object类型的变量o, 指向Student类型的对象
      
  • 子类可看做是特殊的父类, 所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。

  • 一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法

    Student m = new Student();
    m.school = “pku”; //合法,Student类有school成员变量
    Person e = new Student();
    e.school = “pku”; //非法,Person类没有school成员变量
    

    属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。

  • 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)

多态性的使用前提

① 类的继承或者实现关系 ② 方法的重写 ③向上转型

多态性示例

方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法

public class Test {
    public void method(Person e) {
        // ……
        e.getInfo();
    }
    public static void main(Stirng args[]) {
        Test t = new Test();
        Student m = new Student();
        t.method(m); // 子类的对象m传送给父类类型的参数e
    }
}

虚拟方法调用(Virtual Method Invocation)

正常的方法调用

Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();

虚拟方法调用(多态情况下)

子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。

Person e = new Student();
e.getInfo(); //调用Student类的getInfo()方法

编译时类型和运行时类型

编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。 ——动态绑定

图示

image-20200601084747489

前提

Person类中定义了welcome()方法,各个子类重写了welcome()。

执行:

多态的情况下,调用对象的welcome()方法,实际执行的是子类重写的方法。

方法的重载与重写与多态性(方法的重载是多态性的一种体现?NO)*

从编译和运行的角度看:
重载,是指允许存在多个同名方法,而这些方法的参数不同。 编译器根据方法不同的参数表, 对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。 它们的调用地址在编译期就绑定了。 Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法

所以: 对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定” ;

对于多态,只有等到方法调用的那一刻, 解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”

引用一句Bruce Eckel的话: “不要犯傻,如果它不是晚绑定, 它就不是多态。

所以说,方法的重载并不是多态性的一种体现

成员变量、成员方法与多态性

成员方法:

  • 编译时:要查看引用变量所声明的类中是否有所调用的方法。
  • 运行时: 调用实际new的对象所属的类中的重写方法。

成员变量:

  • 不具备多态性,只看引用变量所声明的类。

多态是编译时行为还是运行时行为?

多态是运行时行为

证明:

class Animal  {
 
	protected void eat() {
		System.out.println("animal eat food");
	}
}

class Cat  extends Animal  {
 
	protected void eat() {
		System.out.println("cat eat fish");
	}
}

class Dog  extends Animal  {
 
	public void eat() {
		System.out.println("Dog eat bone");

	}

}

class Sheep  extends Animal  {
	
	public void eat() {
		System.out.println("Sheep eat grass");
	}
 
}

public class InterviewTest {

	public static Animal  getInstance(int key) {
		switch (key) {
		case 0:
			return new Cat ();
		case 1:
			return new Dog ();
		default:
			return new Sheep ();
		}

	}

	public static void main(String[] args) {
		int key = new Random().nextInt(3);
		System.out.println(key);
		Animal  animal = getInstance(key);
		animal.eat();
	}

}

多态性常遇见的问题

编译时通过,运行时不通过
  1. p3的引用类型是子类Woman,不能强制转换为Man
Person p3 = new Woman();
Man m3 = (Man)p3;
  1. p4的引用类型是Person,不能强制转换为Man
Person p4 = new Person();
Man m4 = (Man)p4;

编译不通过

不能将父类的引用赋给子类,否则编译不能通过

Man m5 = new Woman();

编译通过,运行时也通过

可以将子类的引用赋给更高层次的父类,在强制向下转型,则可以成功

Object obj = new Woman();
Person p = (Person)obj;

猜你喜欢

转载自blog.csdn.net/qq_42937522/article/details/106562741