首页 > 代码库 > 黑马程序员——面向对象(一)概述、类与对象、继承、抽象类、接口、多态、内部类

黑马程序员——面向对象(一)概述、类与对象、继承、抽象类、接口、多态、内部类

 

一、面向对象概述

  1、面向对象:是一个很抽象的概念,它相对面向过程而言,是一种程序设计的思想。

  2、面向对象的特点:

  * 符合人们思考习惯的思想

  * 可以将复杂的事情简单化

  * 将程序员从执行者转换成了指挥者

  * 完成需求时:只需要寻找具备所需功能的对象或者创建所需功能的对象即可,简化开发效率并提高对象的复用性。

  3、面向对象的三个特征:封装(Encapsulation)、继承(Inheritance)、多态(Polymorphism)。

二、类与对象

  (一)类与对象之间的关系

  1、类的定义:类是一组事物共有特征和功能的描述。类是对于一组事物的总体描述。Java用类class来描述对象的属性和行为。

  2、对象:对象是类的实例,

  3、类的定义格式:

  [修饰符] class类名{

            属性;

            构造方法;

            一般方法;

      }

  (二)成员变量和局部变量的区别

  成员变量:
  * 成员变量定义在类中,在整个类中都可以被访问。
  * 成员变量随着对象的建立而建立,存在于对象所在的堆内存中。
  * 成员变量有默认初始化值。

  局部变量:
  * 局部变量只定义在局部范围内,如:函数内,语句内等。
  * 局部变量存在于栈内存中。
  * 作用的范围结束,变量空间会自动释放。
  * 局部变量没有默认初始化值。

  (三)构造函数

  1、特点:

  (1)函数名与类名相同。

  (2)无返回值。

  2、作用:给对象进行初始化。对象一建立就会调用与之对应的构造函数。

  3、构造函数的细节:

  当一个类中没有定义构造函数时,系统就会默认给该类加入一个空参数的构造函数。当在类中自定义了构造函数后,默认的构造函数就没有了。

  4、构造函数和一般函数的区别?

  (1)写法上不同。

  (2)在运行上也有不同:构造函数式在对象一建立就运行,给对象初始化。而一般方法是对象调用时才执行,是给对象添加对象具备的功能。

  (3)一个对象建立,构造函数只运行一次。而一般方法可以被该对象调用多次。

  5、什么时候定义构造函数?

  当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中。

  6、构造代码块

   System.out.println(“person code run”); } 

  作用:给对象进行初始化。对象一建立就运行,而且优先于构造函数执行。

  “构造函数”与“构造代码块”的区别总结:(1)优先级不同,构造代码块优先于构造函数;(2)初始化对象不同。构造代码块中定义的是不同对象共性的初始化内容,是给所有对象进行统一初始化。而构造函数是给对应的对象初始化。

  (四)this关键字

  1、作用:指向当前类的属性和方法。

  2、说明:在类定义中,如果“局部变量”和“成员变量”同名,则在将“局部变量”赋给“成员变量”时,可以使用this关键字,例如使用方式可以使:this.name = name 。

  3、问题:this看上去好像是为了区分变量同名,即区分局部变量和成员变量。this为什么可以解决这种区分?它到底代表什么呢?

    解答:this代表本类的对象,代表它所在函数所属的对象的引用。因为this持有本类对象的引用,所以this单独使用代表当前对象。

  4、补充:this也可以使用在构造函数中调用其他的构造函数。在构造函数中,用this调用其他构造函数,必须写在第一行,因为初始化要先执行。。

  5this关键字的基本应用(Java代码)

  需求:this关键字用于“构造函数”间调用

1 class Person{2        Person (String name){3              this.name = name;4        }5        Person( String name , int age ){6              this( name );   //等价于 this.name = name,即调用上面的构造函数7           this.age = age;8        }9 }

  6、关于this及构造函数的总结:

  (1)用于区分同名变量的情况,说的成员和局部同名的时候;

  (2)用于构造函数间调用,this语句只能定义在构造函数的第一行。

  (3)构造函数之间相互调用,初始化动作先执行。初始化动作中的初始化要先执行;构造函数自身的初始化,最后执行。

  (4)注意:构造杉树之间的相互调用初始化,会陷入死循环。

  (五)static关键字

  1static关键字说明

  static是一个修饰符,用于修饰成员(成员变量和成员函数)。当类成员被static修饰后,就多了一个调用方式,除了可以被对象调用外,还可以直接通过类名调用。格式:类名.静态成员。

  2static的特点

  (1)随着类的加载而加载,随着类的消失而消失,其生命周期最长。

  (2)优先于对象存在。(静态是先存在。对象是后存在)

  (3)被所有对象所共享。

  (4)可以直接通过类名所调用。

  (5)static修饰的数据是共享数据。由static修饰的变量称为“静态变量”或“类变量”。可被对象调用的非静态变量称为“成员变量”或“实例变量”。

  3、实例变量和类变量(静态变量)的区别

  (1)存放位置:类变量随着类的加载而存在于“方法区”中。实例变量随着对象的建立而存在“堆内存”中。

  (2)生命周期:类变量生命周期最长,随着类的消失而消失。实例变量生命周期随着对象的消失而消失。

  4static使用注意事项

  (1)static方法只能访问“静态成员”。非static方法既可以访问static成员,也可以访问非静态成员。

  (2)static方法中,不可以定义this,super等关键字。因为静态优先于对象存在。

  (3)main函数是静态的。

  5static的利弊

  利:对对象共享数据进行单独空间的存储,节省空间;可以直接被类名调用。

  弊:生命周期过长。访问出现局限性。(静态虽好,只能访问静态)。

  6main函数是静态的

  (1)主函数是一个特殊的函数,是程序的入口,可以被JVM调用。

  (2)主函数定义:public:代表着该函数访问权限是最大的。static:代表主函数随着类的加载就已经存在了。void:主函数没有具体的返回值。main:不是关键字,但是一个特殊的单词,可以被JVM识别。String[] arr:函数的参数,是一个字符串数组。

  (3)主函数是固定格式的:被JVM识别。JVM在调用主函数时,传入的是new String[0]。可以将main函数的参数args输出查看。

  7、什么时候使用static

  (1)从两方面下手: 即“静态变量”和“静态函数”。

  (2)什么时候定义静态变量(类变量)呢?当对象中出现共享数据时,该数据被静态所修饰。对象中的特有数据要定义成非静态,存在于堆内存中。

  (3)什么时候定义静态函数呢?当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以定义成静态的。

  8Java帮助文档的制作(Javadoc

  在类文件ArrayToolDemo.java文件中,有文档注释如下:

  /**

    这是一个可以对数组进行操作的工具类,提供了……功能。

    @ author xxx

    @ version v1.0

    @ param xxx

    @ return xxx

  */

  在dos命令行中输入命令:javadoc -d myhelp -author-version ArrayToolDemo.java,则Java中的javadoc工具就会帮我们完成帮助文档(也称API文档)的制作。

  9、静态代码块

  (1)定义格式:static { 静态代码块中的语句 }

  (2)特点:随着类的加载而执行,只执行一次。并优先先于main函数。用于对类进行初始化。

  10、对象的初始化过程

  定义一个对象,都做了什么事情?

  (1)把类名.class文件加载到内存中。

  (2)执行该类中的static代码块,如果有得话,给该类进行初始化。

  (3)在堆内存中开辟空间,分配内存地址给对象。

  (4)在堆内存中建立对象的特有属性,并进行默认初始化。

  (5)对属性进行显示初始化。

  (6)对对象进行构造代码块初始化。

  (7)对对象进行对应构造函数初始化。

  (8)将内存地址赋给栈内存中的对象名变量。

  (六)单例设计模式

  1、设计模式:解决某一问题最行之有效的方法。java有23种设计模式。

  2、单例设计模式:解决一个类在内存中只存在一个对象的问题。

  3、问题:如何保证对象的唯一呢?

  (1)为避免其他程序过多建立该类对象,先禁止其他程序建立该类对象。

  (2)为了让其他的程序可以访问到该类对象,只好在本类中自定义一个对象。

  (3)为方便其他程序对自定义对象的访问,可以对外提供一些访问方式。

  上述三步如何通过代码实现来体现呢?

  (1)将构造函数用private私有化。

  (2)在类中创建一个本类对象。

  (3)提供一个方法可以获取该类对象。

  注解:对事物该怎么描述,还怎么描述。当需要将该事物的对象保证在内存中唯一时,就加上以上三步。

  4、饿汉式、懒汉式

  (1)单例设计模式一:饿汉式

class Single{    private Single(){}    private static Single s = new Single();    public static Single getInstance(){        return s;    }}

  注释说明:饿汉式的特点是先初始化对象。如:Single类一进内存,就已经创建好了对象。在实际开发中,出于安全考虑,建议使用此方式。 

  (2)单例设计模式二:懒汉式(又称:延时加载)

 1 class Single 2 { 3      private static Single s=null; 4      private Single(){}  5      public static Single getInstance() 6      { 7           If(s==null) 8                s=new Single(); 9           return s;10      }11 }

  注释说明:是方法被调用时,才进行初始化。如:以上代码中,Single类进内存,对象还没有存在,只有调用了getInstance()方法时,才创建对象。在多线程同时调用时,懒汉式易出问题。 

三、继承(extends

  (一)继承的概述

  继承是面向对象的一个重要特征。当多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承抽取共同属性的类即可。这时,多个类可以称为子类,单独的这个类被称为父类或超类。

  当类与类之间就存在了继承的关系,子类可以直接访问父类中的非私有的属性和行为。在代码中通过extends关键字表示继承关系。

  继承的定义格式:格式:[修饰符] class SubClass extends SuperClass{}

  (二)继承特点

  1、提高了代码的复用性。

  2、让类之间产生关系,催生了类的多态特性。

  3、类与类之间的所属关系是is-a

  4、java语言只支持单继承,不支持多继承(因为多继承带来安全隐患,当多个父类中定义了相同功能,当功能内容不同时,子类对象不确定要运行哪一个)

  5、Java保留了“多继承”机制,用“接口(interface)”完成“多实现”,实现接口的关键字implements

  6、Java支持“多层继承”,也就是一个继承体系。

  (三)子父类中成员变量

  继承关系出现后,类成员包括:变量,函数,构造函数。

  1、变量

  如果子类中出现非私有的同名成员变量时,子类要访问本类中的变量,用this。子类要访问父类中的同名变量,用super。

  super的使用和this的使用几乎一致,且两者都存在于方法区中。this表示本来对象的引用。super表示父类对象的引用。

  2、子父类中函数的特点——覆盖(重写)

  当子父类出现同名函数时,当子类对象调用该函数,会运行子类函数的内容。如同父类的函数被覆盖一样。这种情况是函数的另一个特性:重写(覆盖)。

  当子类继承父类,沿袭了父类的功能到子类中。但子类虽具备该功能与父类不同,这时,没有必要定义新功能,而是使用覆盖特性,保留父类的功能定义,并重写功能内容。子类同时具有父类方法中的内容时,可以用super.方法()。

  注意:(1)子类覆盖父类,必须保证子类权限大于父类权限,才可以覆盖,否则编译失败。(2)静态只能覆盖静态。(3)父类中的私有方法不能被覆盖。

  重载(Overload):只看同名函数的参数列表。

  重写(Override):子父类方法要一模一样。

  3、构造函数

  在对子类对象进行初始化时,父类构造函数也会运行。那是因为子类的每一个构造函数默认第一行有一条隐式的语句super()。super():会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super()。

  问题:为什么子类实例化一定要访问父类中的构造函数?

  因为父类中的数据子类可以直接获取,所以子类在建立对象时,需要先查看父类是如何对这些数据进行初始化的。所以子类在对象初始化时,要先访问一下父类中的构造函数。

  如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式来指定。在上面的示例中已经有了很好的体现。

  注:super语句一定定义在子类构造函数中的第一行。

  构造函数结论:

  ① 子类的所有构造函数,默认都会访问父类中空参数的构造函数。因为子类的每一个构造函数内的第一行都有一句隐式super()。

  ② 当父类中没有空参数的构造函数时,子类必须手动通过supe语句形式来指定要访问父类中的构造函数。

  ③ 当然,子类的构造函数第一行也可以手动指定this语句形式来访问本类中的构造函数。子类中至少会有一个构造函数会访问父类中的构造函数。

  知识点:为什么this()和super()不能在同一个构造函数中?因为它们都位于够咱函数的第一行,且两不能在同一行。为什么不能再同一行?因为初始化动作要先做,而在子类构造函数中必有一个this语句或者super语句。

  (四)final关键字

  final作为一个修饰符。具有以下特点:

  1、可以修饰类、函数、变量。

  2、被final修饰的类不可以被继承,这样就可以避免被继承、被子类复写功能,打破“封装性”。

  3、被final修饰的方法不可以被覆写。

  4、被final修饰的变量是一个常量,只能赋值一次,既可以修饰“成员变量”,又可以修饰“局部变量”。(作为常量:所有的字母都大写;多个单词,用下划线连接)。

  5、内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。

四、抽象类(abstract class

  (一)定义

  1、Java中可以定义没有方法体的方法,该方法的具体实现由子类完成,该方法称为抽象方法,包含抽象方法的类就是抽象类。

  2、抽象类的由来

  多个对象都具备相同的功能,但是功能具体内容有所不同,那么在抽取过程中,只抽取了功能定义,并未抽取功能主体,那么只有功能声明,没有功能主体的方法称为抽象方法。

  (二)抽象的特点

  1、抽象方法一定定义在抽象类中。

  2、抽象类和抽象方法必须用abstract关键字来修饰。

  3、 抽象类不可以被实例化,即不可以用new创建对象。

  4、抽象类中的抽象方法被使用,必须有子类覆写后,建立子类对象进行调用。如果子类只覆盖了部分抽象方法,那么子类还是一个抽象类。

  5、抽象方法只有方法声明,没有方法体,定义在抽象类中。

  注:抽象类中可以有非抽象的方法。

  6、抽象类及抽象方法的定义格式,示例代码如下:

1 abstract class Demo{2     abstract void show();    //抽象方法3     abstract void inof();        //抽象方法4     void turn(){}    //一般方法5 }

  (三)抽象类的一些细节问题

  1、抽象类中有构造函数吗?

  答:有,用于给子类对象进行初始化。

  2、抽象类可以不定义抽象方法吗?

  答:可以,但很少见,目的是不让该类创建对象。AWT的适配器对象就是这种类。

  3、抽象关键字不可以和那些关键字共存?

  答:private(因为方法要被覆盖)、static(不用创建对象)、final(覆盖还是不覆盖?)

  4、抽象类和一般类的异同点:

  相同点:抽象类和一般类都是用来描述事物的,都在内部定义成员。

  不同点:(1)一般类有足够的信息描述事物。抽象了描述事物的信息有可能不足。(2)一般类中不能定义抽象方法,只能定义非抽象方法。抽象类中既可以定义抽象方法,又可以定义非抽象方法。(3)一般类可以被实例化。抽象方法不可以被实例化。

  5、抽象类一定是个父类吗?是的。

  (四)抽象类应用示例代码

    /* 需求:假如我们在开发一个系统时需要对员工进行建模,员工包含 3 个属性:姓名、工号以及工资。经理也是员工,除了含有员工的属性外,另为还有一个奖金属性。请使用继承的思想设计出员工类和经理类。要求类中提供必要的方法进行属性访问。 

    分析:员工类:name id pay。经理类:继承了员工,并有自己特有的bonus。*/ 

 1 //员工类,也是父类  2 abstract class Employee{ 3       private String name;//姓名 4      private String id;  //工号 5      private double pay;  //工资 6  7      //自定义构造函数初始化  8      Employee(String name,String id,double pay){  9             this.name = name;10             this.id = id; 11             this.pay = pay;12       }13       public abstract void work();//抽象的工作方法14 }15 16 //经理类,继承员工类17 class Manager extends Employee{18       private int bonus;//特有的奖金属性19       Manager(String name,String id,double pay,int bonus) {//子类的构造方法20       super(name,id,pay);//调用超类中的构造器21       this.bonus = bonus;22       }23       public void work(){//经理类的工作方法内容24       System.out.println("manager work");25       }26 }27 28 //普通员工类,继承员工类29 class Pro extends Employee{30       Pro(String name,String id,double pay){31       super(name,id,pay);32       }33       public void work(){        //普通员工类的工作方法内容34       System.out.println("pro work");35       }36 }37 38 class  AbstractDemo{39       public static void main(String[] args){40       new Manager("manager","001",10000,2000).work();41       new Pro("pro","020",5000).work(); 42       }43 }

五、接口interface

  (一)接口的概述

  1、接口定义

  接口可以被认为是一个特殊的抽象类。当抽象类中的方法都是抽象的,那么该类可以通过接口的形式来表示。接口使用interface来表示,子类中用implements实现。格式为:

  interface 接口名{}

  子类名 implements接口名{}

  2、格式特点

  (1)接口中常见定义:常量,抽象方法。

  (2)接口中的成员都有固定修饰符。常量:public static final。方法:public abstract

  (3)接口中的成员都是public的。

  在使用中,常量可以不写publicstatic final,方法可以不写public abstract,编译时Java会自动添加这些修饰符,因为这是固定的格式修饰符。但为了方便阅读,通常我们都写上。

  (二)接口的特点

  1、接口是对外暴露的规则。

  2、接口是程序的功能扩展。

  3、接口的出现降低耦合性。

  4、接口可以由类多实现。这也是对多继承不支持的转换形式。java支持多实现。

  5、类与接口之间是“实现关系”,而且类可以继承一个类的同时实现多个接口。

  6、接口之间可以有继承关系,而且可以多继承,因为接口没有方法体。

  注:(1)接口不可以创建对象的,因为有抽象方法。需要被子类实现(implements),子类对接口中的抽象方法全都覆盖后,子类才可以进行实例化。否则子类是一个抽象类。(2)实现多个接口时,接口中不可以有返回不同类型的同名抽象函数。这样子类实现时将不能复写。

  (三)接口与抽象类的

  相同点:都是不断向上抽取而来。

  不同点:(1)抽象类需要被继承,只能是单继承。接口需要被实现,可以使多实现。(2)抽象类中可以定义抽象方法和非抽象方法,子类继承后可以直接使用非抽象方法。接口只能是定义抽象方法,必须由子类去实现。(3)继承关系是is-a。实现关系是like。继承体系是共性内容;实现关系是额外功能。(4)抽象类中可以私有变量或方法。接口中的常量和方法都是public修饰的权限。

  (四)接口的应用示例

  /*需求:笔记本电脑使用。为了扩展笔记本的功能,但日后出现什么功能设备不知道。 

  分析:定义一个规则,只要日后出现的设备都符合这个规则就可以了。规则在java中就是接口。*/

 1 interface USB// 暴露的规则。 2 { 3     public void open(); 4     public void close(); 5 } 6 class BookPC 7 { 8     public static void main(String[] args) 9     {10         useUSB(new UPan());//功能扩展了。11         useUSB(new UsbMouse());12     }13     //使用规则。14     public static void useUSB(USB u)//接口类型的引用,用于接收(指向)接口的子类对象。//USB u= new UPan();15     {16         if(u!=null)17         {18             u.open();19             u.close();20         }21     }22 }23 //一年后。------------------------------24 //实现规则。25 //这些设备和电脑的耦合性降低了。26 class UPan implements USB27 {28     public void open()29     {30         System.out.println("upan open");31     }32     public void close()33     {34         System.out.println("upan close");35     }36 }37 class UsbMouse implements USB38 {39     public void open()40     {41         System.out.println("UsbMouse open");42     }43     public void close()44     {45         System.out.println("UsbMouse close");46     }47 }

 

六、多态

  (一)多态的概述

  1、对多态的理解:指同一个实体同时具有多种形式,也可以理解为事物存在的多种体现形态。例如动物中猫和狗。猫这个对象对应的类型是猫类型,如:猫 cat1 = new 猫()。同时猫也是动物的一种,因此也可以把猫称为动物。动物 cat1 = new猫()。那么动物就是从猫和狗等具体事物中抽取出来的父类型。

  2、多态在代码中的体现:父类或接口的引用指向其子类对象。如Animal a = new Dog()。

  3、多态的好处:提高代码的扩展性,前期定义的代码可以使用后期的内容。

  4、多态的弊端

  (1)前期定义的内容,不能使用后期特有内容。

  (2)只能使用父类中的引用访问父类中的成员。也就是说使用了多态,父类型的引用在使用功能时,不能直接调用子类中的特有方法。如Animal a = new Cat(); 这代码就是多态的体现,假设子类Cat中有特有的抓老鼠功能,父类型的 a就不能直接调用。这上面的代码中,可以理解为Cat类型提升了,向上转型。如果此时父类的引用想要调用Cat中特有的方法,就需要强制将父类的引用转成子类类型,向下转型。如:Catc = (Cat)a;

注:如果父类可以创建对象,如Animal a = new Animal()。此时就不能向下转型,Cat c = (Cat)a这样的代码就变得不容许,编译时会报错。所以千万不能出现这样的操作,就是将父类对象转成子类类型。我们所说的能够转换指的是父类引用指向自己的子类对象时,该引用可以被提升,也可以被强制转换。多态始终都是子类对象在做着变化。

  5、多态的前提

  (1)类与类之间必须有关系,要么继承,要么实现。

  (2)存在覆盖。父类中有方法被子类重写。

  6、多态的类型判断

  instanceof用于判断对象的具体类型。

  (二)多态中成员的特点

  1、多态中成员变量的特点

  无论编译和运行,都参考左边(引用变量所属的类)。如:多态中的父类引用调用成员变量时,如果父类和子类有同名的成员变量,那么被调用的是父类中的成员变量。

  2、多态中非静态成员函数的特点

  在编译时期:参考引用型变量所属的类中是否有调用成员函数。如果有,编译通过,如果没有编译失败。

  在运行时期:参阅对象所属的类中是否有调用的方法。这就是说,如果父类中有一个非抽象的方法,而子类继承后又将其覆写,在多态运行时,父类的引用调用这个同名函数时,被运行的将是父类中的方法。

  简单总结就是:成员函数在多态调用时,编译时看左边,运行时看右边。

  3、多态中静态成员函数的特点

  无论编译和运行,都参考左边。也就是父类引用在调用静态同名函数时,被调用的是父类中的静态函数。这是因为,当类一被加载,静态函数就随类绑定在了内存中。此时,不需要创建对象,就可以使用类名直接调用。

  注:类在方法区中的分配,分为静态区和非静态区,而关键字this和super在非静态区。

  (三)多态应用的代码示例

  /* 电脑的运行实例。电脑的运行由主板控制,假设主板只是提供电脑运行,但是没有上网,听歌等功能。而上网、听歌需要硬件的支持。而现在主板上没有网卡和声卡,这时可以定义一个规则,叫PCI,只要符合这个规则的网卡和声卡都可以在主板上使用,这样就降低了主板和网卡、声卡之间的耦合性。用程序体现。*/ 

 1 // 接口PCI 2 interface PCI{ 3       void open(); 4       void close(); 5 } 6  7 //网卡实现接口 8 class NetCard implements PCI{ 9       public void open(){10             System.out.println("NetCard_open");11       }12       public void close(){13             System.out.println("NetCard_close");14       }15 }16 17 //声卡实现接口18 class SoundCard implements PCI{19       public void open(){20             System.out.println("SoundCard_open");21       }22       public void close(){23             System.out.println("SoundCard_close");24       }25 }26 27 class Mainboard{28       //电脑运行29       public static void run(){30             System.out.println("Mainboard_run");31       }32       //使用扩展功能33       public static void usePCI(PCI p) {//接口型引用指向自己的子类对象。//PCI p = new NetCard()34             if(!(p==null)){35                   p.open();36                   p.close();37             }38       }39 }40 41 class Demo{42       public static void main(String[] args) {43             Mainboard m =new Mainboard();44             //电脑运行45             m.run();46             //m.usePCI(null);47             //电脑上网48             m.usePCI(new NetCard());49             //电脑听歌50             m.usePCI(new SoundCard());51       }52 }

七、内部类

 

 

黑马程序员——面向对象(一)概述、类与对象、继承、抽象类、接口、多态、内部类