当前位置:首页>Java>Java面向对象>Java面向对象

Java面向对象

作者:微学网发布时间:2019-09-03 19:20:41

什么是面向对象

面向对象是一种编程思想,而不是一种方法或标准。它指导我们从现实世界中事物的客观存在出发,进行编程。

“面向对象”(英语:Object Oriented,简称OO)是一种以事物为中心的编程思想。

面向对象程序设计(英语:Object-oriented programming,缩写:OOP),是一种程序开发的方法。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。

面向对象是相对于面向过程的(c则是一个典型的面向过程的语言),站在面向对象的角度去看问题,你则是对象的动作的指挥者。如果站在面向过程的角度去看问题,你则是动作的执行者。

什么是面向过程

强调的是功能行为,面向过程”是一种以过程为中心的编程思想。“面向过程”他们不支持丰富的“面向对象”特性(比如继承、多态),就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。面向过程在这一系列工作的执行中,强调的是工作的执行。

面向过程到面向对象的转变

面对简单的软件程序,我们往往不需要考虑过多的设计,任何一个小的功能单元或模块,都可以按照一定的步骤去实现,这种 step by step 的方式即为面向过程。

随着程序的复杂性提升,我们无法按步骤去描述并实现编码,所以面向过程已无法适应。加之考虑到代码的可读及复用性等,便有了对象的概念,以及面向对象的编程思想。
当然,还有面向组件、面向服务等设计思想,不在此讨论。

面向对象和面向过程的区别

要知道,二者并不是非此即彼,而是相辅相成的。

面向对象思想要求我们从宏观上进行程序设计,抽象出一个个“对象”。而面向过程,则在微观上对“对象”内部进行具体的实现。

可以看出,面向对象最终还是离不开面向过程。

举例来说,需要编程实现绘制圆形、三角形、矩形。

面向过程实现:

drawCircle... //一坨绘制相关的代码,下同
drawTriangle
drawRect

面向对象实现:

//抽象出画笔对象
class Painter {
    drawCircle(){
        //上述面向过程实现,下同
    }

    drawTriangle(){}

    drawRect(){}
}

//面向对象,使用画笔绘制
1. painter.drawCircle() //此处只需要调用对象的方法,下同
1. painter.drawTriangle()
2. painter.drawRect()

示例1 - 买电脑

1:面向过程
    1:查资料
    2:电脑城砍价
    3:被黑
    4:痛苦归来

2:面向对象
    1:找对象。老师
    2:老师.砍价
    3:老师.检测电脑
    4:电脑成功购买

示例2 - 吃饭

1:面向过程
    1:自己动手做
    2:买菜
    3:洗菜
    4:煮饭炒菜
    5:很难吃,浪费时间

2:面向对象
    1:找专业对象
    2:餐馆.点餐
    3:餐馆,做饭
    4:饭好吃,节约时间,精力

示例3 - 找对象

1:求介绍,相亲,找现成的对象。(面向对象的思想先找有的对象,直接拿来使用)
2:不满意,没有对象,自己造一个。(sun没有提供,自己造对象)

什么是对象

Everything is Object,在 Java 语言的世界中,万事万物皆对象。上面提到“从现实世界中客观存在的事物出发”,这里客观存在的事物,我们称之为“对象”。

在程序中,对象是程序的基本构成单元,是客观存在的事物的体现。一个对象通常由一组属性和对这组属性进行操作的若干服务(方法)构成。

面向对象的特征

说到面向对象,就不得不说其三大特性:封装继承多态
下面简单说说自己的理解吧,就不再贴相关示例了,网上有很多优秀的例子。

封装

封装是面向对象最基础的特性。在前面介绍对象时,提到“一个对象通常由一组属性和对这组属性进行操作的若干服务(方法)构成”,这里的构成体现的就是封装性。

封装,是指对外隐藏对象内部的属性和实现细节,只提供相应的接口和方法进行交互。一方面,外部只需专注于对象所提供的能力,而不用关心内部实现细节;另一方面,避免了外部随意修改或访问内部属性和方法,从而提升了程序的健壮性;同时,封装能提升代码的复用率。

封装是把对象的属性和操作结合为一个独立的整体,隐藏对象内部操作的实现,用户只需要通过其对外提供的方法来访问该对象,无需知道其内部实现细节。

优点

  • 隐藏内部实现细节,提供公共访问方式

  • 类内部可以自由修改不影响其调用者

  • 减少耦合度,提高安全性

继承

继承指一个对象从另一个对象中获得属性和方法的过程,继承者称为子类,被继承者称为父类。继承使得子类对象拥有父类对象的全部属性与方法(非私有)。

关键点:

  • 子类也可以被其他类继承成为父类;

  • 父类可以拥有多个子类,但一个子类只能有一个父类。这一点与现实中父亲与孩子的关系是一致的;

  • Java 中任何类都是 java.lang.Object 的直接或间接子类;

  • 子类不能继承父类中访问权限为 private 的成员变量和方法;

  • 子类可以重写(override)父类的方法;

  • 子类可以通过 super 关键字访问父类的成员变量、方法和构造器;

  • final 声明的类或方法,不能被继承或重写;

实现方式

继承是一个对象获取另一个对象属性的过程,关键字为extends和implements。

1). IS-A关系(一个对象所属于另一个对象):
方式一. 用extends来实现继承:

public class Animal {
    public void eat() {
        System.out.println("Animal eating...");
    }
}

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

public class Dog extends Mammal {
    public void eat() {
        System.out.println("Dog eating...");
    }
}

方式二. 用implements来实现继承:

public interface Animal {
    void eat();
}

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

public class Dog extends Mammal {
    public void eat() {
        System.out.println("Dog eating...");
    }
}

无论方式一还是方式二,我们都可以用instanceof关键字检查得出:Mammal是一个Animal(哺乳动物也是动物);Dog既是一个Mammal,也是一个Animal(狗既是哺乳动物也是动物)。

public class Test {  
     /**
       * instanceof关键字检查代码
      */
    public static void main(String[] args) {
        Mammal m = new Mammal();
        Dog d = new Dog();
        System.out.print(m instanceof Animal);
        System.out.print(d instanceof Mammal);
        System.out.print(d instanceof Animal);
    }
}

输出结果:
true true true

2). HAS-A关系(一个对象含有另一个对象的一些属性):

public class Car{}

public class Speed{}

public class Benz extends Car{
    private Speed sp; 
}

Benz含有Spend属性,但Benz不是Spend

多态

多态是在继承的基础上实现的,多态实现的三个必要条件:

  • 继承

  • 方法重写(override)

  • 父类引用指向子类对象,例如:Human human = new Man(),这里 Man extends Human。

当使用多态方式调用方法时,会依据以下规则:

  • 首先检查父类中是否有该方法,如果没有,则编译错误;

  • 若子类重写了该方法,调用子类的重写方法;

  • 若子类没有重写该方法,则调用父类的方法;

多态的优点:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

多态的实现

实现多态的三个必要条件:继承、重写、父类引用指向子类对象。

1). 向上转型:
我们实例化一个Dog对象可以用 Dog d = new Dog(); 我们也可以用 Animal d = new Dog();,后者就是向上转型(父引用指向子对象),上面两种方式创建出来的对象d,调用d.eat();输出的结果都是Dog eating…,这就体现出了java得多态。向上转型创建的对象会遗失掉和父类不同的方法和变量(不能用来调用子类特有的方法和变量)。

2). 举例说明:

class A {
    public String show(D obj) {
        return ("A and D");
    }

    public String show(A obj) {
        return ("A and A");
    }
}

class B extends A {
    public String show(B obj) {
        return ("B and B");
    }

    public String show(A obj) {
        return ("B and A");
    }
}

class C extends B {}

class D extends B {}

class Test {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();
        System.out.println(a1.show(b)); //  ①
        System.out.println(a1.show(c)); //  ②
        System.out.println(a1.show(d)); //  ③
        System.out.println(a2.show(b)); //  ④
        System.out.println(a2.show(c)); //  ⑤
        System.out.println(a2.show(d)); //  ⑥
        System.out.println(b.show(b));  //  ⑦
        System.out.println(b.show(c));  //  ⑧
        System.out.println(b.show(d));  //  ⑨
    }
}

输出结果:
A and A    //  ①
A and A    //  ②
A and D    //  ③
B and A    //  ④
B and A    //  ⑤
A and D    //  ⑥
B and B    //  ⑦
B and B    //  ⑧
A and D    //  ⑨

前三个比较简单不容易出错,看看下面几个:

  • ④:a2是A的引用指向B对象,向上转型创建的对象会遗失掉和父类不同的方法和变量,所以a2只能调用show(D obj)、show(A obj)两个方法,所以a2.show(b)应该调用show(A obj)方法,B中重写了该方法,所以运行时JVM会调用B类中重写的show(A obj)方法,所以输出B and A;

  • ⑤: 原理同④;

  • ⑥:a2.show(d)应该调用show(D obj)方法,B中没有重写该方法所以调用的为A类中的该方法,所以输出为A and D;

  • ⑦⑧⑨:b为B类对象,可调用A类中show(D obj)、B类中show(B obj)、B类中show(A obj)方法,所以输出如上。

面向对象的五大基本原则

  • 单一职责原则SRP(Single Responsibility Principle)
    类的功能要单一,不能包罗万象,跟杂货铺似的。

  • 开放封闭原则OCP(Open-Close Principle)
    一个模块对于拓展是开放的,对于修改是封闭的,想要增加功能热烈欢迎,想要修改,哼,一万个不乐意。

  • 里式替换原则LSP(the Liskov Substitution Principle LSP)
    子类可以替换父类出现在父类能够出现的任何地方。比如你能代表你爸去你姥姥家干活。哈哈~~

  • 依赖倒置原则DIP(the Dependency Inversion Principle DIP)
    高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。抽象不应该依赖于具体实现,具体实现应该依赖于抽象。就是你出国要说你是中国人,而不能说你是哪个村子的。比如说中国人是抽象的,下面有具体的xx省,xx市,xx县。你要依赖的是抽象的中国人,而不是你是xx村的。

  • 接口分离原则ISP(the Interface Segregation Principle ISP)
    设计时采用多个与特定客户类有关的接口比采用一个通用的接口要好。就比如一个手机拥有打电话,看视频,玩游戏等功能,把这几个功能拆分成不同的接口,比在一个接口里要好的多。

使用Java来描述事物

案例

通过Java语言定义一个汽车类,并生产出汽车,有颜色,轮胎个数, 有运行的功能。

分析

如何描述现实世界中的事物,描述该事物的属性和行为,汽车具有颜色和轮胎数的属性,具备运行的行为。

如何使用Java语言进行转换?

根据对应关系:
属性:类中的成员变量
行为:类中的成员函数
那么定义Java类就是定义一个类的成员。汽车类具备的成员是:颜色,轮胎数,运行方法。

Car类定义流程:

  • 使用class 关键字 定义类,
    • class空格 类名。类名就是标识符,命名规则,单词的首字母大写,多个单词的首字母大写。注意:不是规定,但是最好遵守
    • 类名后紧跟一对{}表示类的开始和结束。
  • 汽车有轮胎数 int num;
    • 不需要给num初始化值,因为汽车轮胎数不确定,有4,有6,有8。
  • 有颜色 String color
    • 什么使用String 例如定义颜色”红色”是字符串类型。
    • 也不需要初始化值。
  • 跑的行为(方法、函数) void run(){}
    • 方法中执行输出语句。syso(“跑啦。。。。”);
public class Car {

String color;// 成员变量
    int num; // 成员变量

    // 成员函数
    void run() {
        System.out.println(color + "的车,轮胎数:" + num + "个,跑起来了");
    }
}

Java类

Java类的定义

我们常说“物以类聚”,表达“ 同类的东西聚在一起”。其实 Java 中的类也可以这么去理解:“物”即客观存在的“对象”,同类物体的特征描述,即为“类”。

上面是我的个人理解,用术语概括就是:类是客观事物的抽象,或者说是对现实生活中事物的一种描述(属性和行为)。

类可以看成是创建Java对象的模板

Java类的定义语法

[public][abstract|final]class<class_name>[extends<class_name>]
[implements<interface_name>]
{
    //定义属性部分
    <property_type><property1>;
    <property_type><property2>;
    <property_type><property3>;

    //定义方法部分
    function1();
    function2();
    function3();

}

上述语法中各关键字的描述如下。

  • public:表示“共有”的意思。如果使用 public 修饰,则可以被其他类和程序访问。每个 Java 程序的主类都必须是 public 类,作为公共工具供其他类和程序使用的类应定义为 public 类。

  • abstract:如果类被 abstract 修饰,则该类为抽象类,抽象类不能被实例化,但抽象类中可以有抽象方法(使用 abstract 修饰的方法)和具体方法(没有使用 abstract 修饰的方法)。继承该抽象类的所有子类都必须实现该抽象 类中的所有抽象方法(除非子类也是 抽象类)。

  • final:如果类被 final 修饰,则不允许被继承。

  • class:声明类的关键字。

  • class_name:类的名称。

  • extends:表示继承其他类。

  • implements:表示实现某些接口。

  • property_type:表示成员变量的类型。

  • property:表示成员变量名称。

  • function():表示成员方法名称。

注意:类名应该以下划线()或字母开头,最好以字母开头;第一个字母最好大写,如果类名由多个单词组成,则每个单词的首字母最好都大写;类名不能为 Java 中的关键字,例如 boolean、this、int 等;类名不能包含任何嵌入的空格或点号以及除了下划线()和美元符号($)字符之外的特殊字符。

示例

通过下面一个简单的类来理解下Java中类的定义:

public class Dog{
  String breed;
  int age;
  String color;
  void barking(){
  }

  void hungry(){
  }

  void sleeping(){
  }
}

对象与类的关系

类进行实例化可生成对象,所以,类的具体表现或者实例就是对象,而对象的抽象或者总概括就是类。

类的构成

一个类可以包含以下类型变量:

局部变量

在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。

成员变量

成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。

类变量

类变量也声明在类中,方法体之外,但必须声明为static类型。
一个类可以拥有多个方法,在上面的例子中:barking()、hungry()和sleeping()都是Dog类的方法。

定义类的步骤

创建一个新的类,就是创建一个新的数据类型。实例化一个类,就是得到类的一个对象。因此,对象就是一组变量和相关方法的集合,其中变量表明对象的状态和属性,方法表明对象所具有的行为。定义一个类的步骤如下所述。

声明类

编写类的最外层框架,声明一个名称为 Person 的类。

public class Person
{
    //类的主体
}

编写类的属性

类中的数据和方法统称为类成员。其中,类的属性就是类的数据成员。通过在类的主体中定义变量来描述类所具有的特征(属性),这里声明的变量称为类的成员变量。

编写类的方法

类的方法描述了类所具有的行为,是类的方法成员。可以简单地把方法理解为独立完成某个功能的单元模块。

示例

下面来定义一个简单的 Person 类。

public class Person
{
    private String name;    // 姓名
    private int age;    // 年龄
    public void teli()
    {   //定义说话的方法
        System.out.println(name+"今年"+age+"岁!");
    }
}

如上述代码,在 Person 类中首先定义了两个属性,分别为 name 和 age,然后定义了一个名称为 tell() 的方法。

构造方法

每个类都有构造方法。如果没有显式地为类定义构造方法,Java编译器将会为该类提供一个默认构造方法。

在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。

下面是一个构造方法示例:

public class Puppy{
    public Puppy(){
    }

    public Puppy(String name){
        // 这个构造器仅有一个参数:name
    }
}

Java对象

创建对象

对象是根据类创建的。在Java中,使用关键字new来创建一个新的对象。创建对象需要以下三步:

  • 声明:声明一个对象,包括对象名称和对象类型。

  • 实例化:使用关键字new来创建一个对象。

  • 初始化:使用new创建对象时,会调用构造方法初始化对象。

下面是一个创建对象的例子:

public class Puppy{
   public Puppy(String name){
      //这个构造器仅有一个参数:name
      System.out.println("小狗的名字是 : " + name ); 
   }
   public static void main(String[] args){
      // 下面的语句将创建一个Puppy对象
      Puppy myPuppy = new Puppy( "tommy" );
   }
}

编译并运行上面的程序,会打印出下面的结果:

小狗的名字是 : tommy

访问实例变量和方法

通过已创建的对象来访问成员变量和成员方法,如下所示:

/* 实例化对象 */
Object referenceVariable = new Constructor();
/* 访问类中的变量 */
referenceVariable.variableName;
/* 访问类中的方法 */
referenceVariable.methodName();

创建对象的示例

下面的例子展示如何访问实例变量和调用成员方法:

public class Puppy{
   int puppyAge;
   public Puppy(String name){
      // 这个构造器仅有一个参数:name
      System.out.println("小狗的名字是 : " + name ); 
   }

   public void setAge( int age ){
       puppyAge = age;
   }

   public int getAge( ){
       System.out.println("小狗的年龄为 : " + puppyAge ); 
       return puppyAge;
   }

   public static void main(String[] args){
      /* 创建对象 */
      Puppy myPuppy = new Puppy( "tommy" );
      /* 通过方法来设定age */
      myPuppy.setAge( 2 );
      /* 调用另一个方法获取age */
      myPuppy.getAge( );
      /*你也可以像下面这样访问成员变量 */
      System.out.println("变量值 : " + myPuppy.puppyAge ); 
   }
}

编译并运行上面的程序,产生如下结果:

小狗的名字是 : tommy
小狗的年龄为 : 2
变量值 : 2

Java包

包主要用来对类和接口进行分类。当开发Java程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。

Import语句

在Java中,如果给出一个完整的限定名,包括包名、类名,那么Java编译器就可以很容易地定位到源代码或者类。Import语句就是用来提供一个合理的路径,使得编译器可以找到某个类。

例如,下面的命令行将会命令编译器载入java_installation/java/io路径下的所有类

面向对象示例

在该例子中,我们创建两个类:Employee 和 EmployeeTest。

首先打开文本编辑器,把下面的代码粘贴进去。注意将文件保存为 Employee.java。

Employee类有四个成员变量:name、age、designation和salary。该类显式声明了一个构造方法,该方法只有一个参数。

import java.io.*;

public class Employee{
   String name;
   int age;
   String designation;
   double salary;
   // Employee 类的构造器
   public Employee(String name){
      this.name = name;
   }
   // 设置age的值
   public void empAge(int empAge){
      age =  empAge;
   }
   /* 设置designation的值*/
   public void empDesignation(String empDesig){
      designation = empDesig;
   }
   /* 设置salary的值*/
   public void empSalary(double empSalary){
      salary = empSalary;
   }
   /* 打印信息 */
   public void printEmployee(){
      System.out.println("名字:"+ name );
      System.out.println("年龄:" + age );
      System.out.println("职位:" + designation );
      System.out.println("薪水:" + salary);
   }
}

程序都是从main方法开始执行。为了能运行这个程序,必须包含main方法并且创建一个实例对象。

下面给出EmployeeTest类,该类实例化2个 Employee 类的实例,并调用方法设置变量的值。

将下面的代码保存在 EmployeeTest.java文件中。

import java.io.*;
public class EmployeeTest{

   public static void main(String[] args){
      /* 使用构造器创建两个对象 */
      Employee empOne = new Employee("RUNOOB1");
      Employee empTwo = new Employee("RUNOOB2");

      // 调用这两个对象的成员方法
      empOne.empAge(26);
      empOne.empDesignation("高级程序员");
      empOne.empSalary(1000);
      empOne.printEmployee();

      empTwo.empAge(21);
      empTwo.empDesignation("菜鸟程序员");
      empTwo.empSalary(500);
      empTwo.printEmployee();
   }
}

编译这两个文件并且运行 EmployeeTest 类,可以看到如下结果:

$ javac EmployeeTest.java
$ java EmployeeTest 
名字:RUNOOB1
年龄:26
职位:高级程序员
薪水:1000.0
名字:RUNOOB2
年龄:21
职位:菜鸟程序员
薪水:500.0