Java基础四——面向对象思想

1.1 什么是面向对象

        面向对象思想就是不断的创建对象(属性与行为的封装,让二者作为整体参与程序执行,使用对象,指挥对象做事情。(在已有对象的情况下,直接使用对象,而不再去考虑对象的内部构造

        面向对象的开发来讲也分为三个过程:OOA(面向对象分析)、OOD(面向对象设计)、OOP(面向对象编程)

1.2 了解对象的内部结构

1.2.1 成员变量和成员方法

        成员变量:访问修饰符修饰符(作用范围) 类型(存储结构) 属性名称(引用)=初始值(实际物理值);

       访问修饰符:可以使用四种不同的访问修饰符中的一种,包括public(公共的),protected(受保护的),无修饰符和 private(私有的)。public 访问修饰符表示属性可以从任何其它代码调用。private 表示属性只可以由该类中的其它方法来调用。       

        修饰符:是对属性特性的描述,例如:static、final 等等。

        类型:属性的数据类型,可以是任意的类型。

        属性名称:任何合法标识符

        初始值:赋值给属性的初始值。如果不设置,那么会自动进行初始化,基本类型使用缺省值,对象类型自动初始化为 null。

        成员变量有两种:一种是被static关键字修饰的变量,叫类变量或者静态变量(在类加载时初始化);另一种没有static修饰,为实例变量(在对象创建时初始化)

        二者在程序运行时的区别,实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了(直接在方法区读取数据)。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用

        成员方法:访问修饰符 修饰符 返回值类型 方法名称 (参数列表) throws 异常列表 {方法体}

        修饰符:是对方法特性的描述,例如:static、final、abstract、synchronized 等等。

        返回值类型:表示方法返回值的类型。如果方法不返回任何值,它必须声明为 void(空)。 Java 技术对返回值是很严格的,例如,如果声明某方法返回一个int值,那么方法必   须从所有可能的返回路径中返回一个int值(只能在等待返回该 int 值的上下文中被调用。)

        方法名称:可以是任何合法标识符,并带有用已经使用的名称为基础的某些限制 条件。

        参数列表:允许将参数值传递到方法中。列举的元素由逗号分开,而每一个元素包含一个类型和一个标识符。

throws 异常列表:子句导致一个运行时错误(异常)被报告到调用的方法中,以便以合适的方式处理它。异常在后面的课程中介绍。

    花括号内是方法体,即方法的具体语句序列。

    和变量一样分为实例方法和静态方法

1.2.2 构造方法

        目的:主要用来给对象的数据进行初始化

        格式:访问修饰符类名() { }

1.2.3 代码块

        1、局部代码块

            在局部代码块(方法体)中定义的变量属于局部变量,随方法的调用创建,随方法的出栈而消失

        2、构造代码块

            优先于构造方法执行(等同于将其放置在构造方法体中的首行),随构造方法执行而执行

        3、静态代码块

            只随类的加载而加载,无论创建多少次对象,只执行一次

        创建对象时代码执行顺序:

        静态代码块(父类到子类)--构造代码块(父类)--构造方法(父类)--构造代码块(子类)--构造方法(子类)--方法(自然顺序)

1.2.4 访问修饰符

public                   package(default)                 protected              private

本类                                                                                      

子类                                                               

同包                                

全局             

1.2.5 this和supper

        this:本类对象的引用,本类对象的构造器

        supper:父类对象的引用,父类对象的构造器

1.2.6 重写(覆盖)和重载

        重写:等同于对重写对象方法(非静态方法)的引用,修饰符,返回类型,方法名和参数一样

        重载:等同定义新的方法,只不过方法名和原来一样,但参数必须不一样(参数位置无关)

1.3 面对对象的特性

1.3.1 封装

        隐藏不需要对外公开的属性和方法,以减少对外耦合(通常将成员变量private,提供对应的getXxx()/setXxx()方法)

        而外部只能通过方法来控制成员变量的操作,可提高了代码的安全性;同时把代码用方法进行封装,提高了代码的复用性

1.3.2 继承

        多个类有共同的成员变量和成员方法,抽取到另外一个类中(父类),在让多个类去继承这个父类

特点:

    1.子类继承父类的成员变量

      当子类继承了某个类之后,便可以使用父类中的成员变量,但是并不是完全继承父类的所有成员变量。具体的原则如下:

        1)能够访问父类的publicprotected成员变量;不能够访问父类的private成员变量(可以通过反射强制获取)

        2)对于父类的包访问权限成员变量,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;

        3)对于子类可以继承的父类成员变量,如果在子类中出现了同名称的成员变量,则会发生隐藏现象,子类的成员变量会屏蔽掉父类的同名成员变量。如果要在子类中访问父类中同名成员变量,需要使用super关键字来进行引用。

    2.子类继承父类的方法

        1能够访问父类的publicprotected成员方法;不能够访问父类的private成员方法(但是能继承);

        2)对于父类的包访问权限成员方法,如果子类和父类在同一个包下,则子类能够继承;否则,子类不能够继承;

        3)对于子类可以继承的父类成员方法,如果在子类中出现了同名称的成员方法,则称为覆盖,即子类的成员方法会覆盖掉父类的同名成员方法(但是父类的成员方法如果是静态,子类也必须是静态才能屏蔽,不然编译错误,反之亦然)。如果要在子类中访问父类中同名成员方法,需要使用super关键字来进行引用。

        注意:隐藏和覆盖是不同的。隐藏是针对成员变量和静态方法的,而覆盖是针对普通方法的。

    3.构造器

        子类是不能够继承父类的构造器,但是要注意的是,如果父类是有参构造,那么子类必须有同类型参数构造,如果父类是无参构造,那么子类任意构造都可。

1.3.3 多态

        父类的引用指向子类对象:父类类型  引用名称= new 子类类型

        父类类型  引用名称= (父类类型 引用名称)new 子类类型

    A:多态成员变量

        当子父类中出现同名的成员变量时,多态调用该变量时:

        编译时期:参考的是引用型变量所属的类中是否有被调用的成员变量。没有,编译失败。

        运行时期:也是调用引用型变量所属的类中的成员变量。

        简单记:编译和运行都参考等号的左边。

    B:多态成员方法

        编译时期:参考引用变量所属的类,如果没有类中没有调用的方法,编译失败。

        运行时期:参考引用变量所指的对象所属的类,并运行对象所属类中的成员方法。

        简而言之:编译看左边(父类是否拥有该方法),运行看右边(静态都看左边)   

多态的转型分为向上转型与向下转型两种:

        A:向上转型

           当有子类对象赋值给一个父类引用时,便是向上转型,多态本身就是向 上转型的过程。

           使用格式:父类类型  变量名 = new 子类类型();

            如:Person p = new Student();

        B:向下转型

           一个已经向上转型的子类对象可以使用强制类型转换的格式,将父类引用转为子类引用,这个过程是向下转型。如果是直接创建父类对象,是无法向下转型的

        使用格式:子类类型 变量名 = (子类类型) 父类类型的变量;

        如:Student stu = (Student) p;  //变量p 实际上指向Student对象

        *执行Object[] o ={“java”,”java”};String[] s = (String[])o;报错

        转化异常:Java虚拟机的转化机制无法判断数组内部元素的类型,故不可强制为Object

1.4 对象的分类

1.4.1 类(抽象类)

        如果一个类含有抽象方法,则称这个类为抽象类(不包含抽象方法,但被abstract修饰的也是抽象类,但是没有意义),抽象类必须在类前用abstract关键字修饰。因为抽象类中含有无具体实现的方法,所以不能用抽象类创建对象。

        抽象类和普通类的主要有三点区别:

        1)抽象方法必须为public或者protected(因为如果为private,则不能被子类继承,       子类便无法实现该方法),缺省情况下默认为public

        2)抽象类有构造方法,但不能用来创建对象;

        3)如果一个类继承于一个抽象类,则子类必须实现父类的抽象方法。如果子类没       有实现父类的抽象方法,则必须将子类也定义为为abstract类。

1.4.2 内部类

        为什么需要内部类:

        无论该内部类的外围类实现或者继承了什么都不会对它造成影响

        成员内部类,定义在外部类中的成员位置。与类中的成员变量相似,可通过外部类对象进行访问

        A:定义格式

        class 外部类 {

               修饰符 class 内部类 {

                  //其他代码

        }

       }

        B:访问方式

        1.  非静态情况:Outer.Inner I = new Outer().new Inner();

        2.  静态:Outer.Inner I = new Outer .Inner();

局部内部类,定义在外部类方法中的局部位置。与访问方法中的局部变量相似,可通过调用方法进行访问

        A:定义格式

        class 外部类 {

               修饰符返回值类型 方法名(参数) {

        class 内部类 {

        //其他代码

        }

        }

    }

        B:访问方式

        在外部类方法中,创建内部类对象,进行访问

    匿名内部类

        A:作用:匿名内部类是创建某个类型子类对象的快捷方式

        B:格式:

        new 父类或接口(){

               //进行方法重写(抽象方法必须重写

    };

1.4.3 接口

        接口,英文称作interface,在软件工程中,接口泛指供别人调用的方法或者函数。从这里,我们可以体会到Java语言设计者的初衷,接口是对行为的抽象,属于整个程序的规范。

        接口中可以含有变量和方法。但是要注意,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如privateprotectedstatic final等修饰会报编译错误),并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加抽象,并且一般情况下不在接口中定义变量。

        接口与抽象类的区别:

        1.语法层面上的区别

            1)抽象类可以提供成员方法的实现细节,而接口中只能存在public abstract 方法;

            2)抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型的;

            3)接口中不能含有静态代码块以及普通方法,而抽象类可以有静态代码块和静态方法;

            4)一个类只能继承一个抽象类,而一个类却可以实现多个接口。

        2.设计层面上的区别

            1)抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将飞行这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将飞行设计为一个接口Fly,包含方法fly( ),然后AirplaneBird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个 "是不是"的关系,而接口实现则是 "有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。

            2)设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt Bppt Cppt Bppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt Bppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动。

1.4.4 枚举

        枚举是一种规范它规范了参数的形式,这样就可以不用考虑类型的不匹配并且显式的替代了int型参数可能带来的模糊概念 枚举像一个类,又像一个数组。

        Enum作为Sun全新引进的一个关键字,看起来很象是特殊的class, 它也可以有自己的变量,可以定义自己的方法,可以实现一个或者多个接口。当我们在声明一个enum类型时,我们应该注意到enum类型有如下的一些特征。

        1. 它不能有public的构造函数,这样做可以保证客户代码没有办法新建一个enum的实例。

        2. 所有枚举值都是public , static , final的。注意这一点只是针对于枚举值,我们可以和在普通类里面定义 变量一样定义其它任何类型的非枚举变量,这些变量可以用任何你想用的修饰符。

        3 . Enum默认实现了java.lang.Comparable接口。

        4.Enum覆载了了toString方法,因此我们如果调用Color.Blue.toString()默认返回字符串”Blue”.

        5.Enum提供了一个valueOf方法,这个方法和toString方法是相对应的。调用valueOf(“Blue”)将返回Color.Blue.因此我们在自己重写toString方法的时候就要注意到这一点,一把来说应该相对应地重写valueOf方法。

        6.Enum还提供了values方法,这个方法使你能够方便的遍历所有的枚举值。

        7.Enum还有一个oridinal的方法,这个方法返回枚举值在枚举类种的顺序,这个顺序根据枚举值声明的顺序而定,这里Color.Red.ordinal()返回0。

完整实例如下:

enum Color {

       red("红色"), bule("蓝色");          //定义枚举实例(利用构造方法传递参数)

       private String color;                     //定义成员变量

       private Color(Stringcolor) {         //定义构造方法,只能用private

              this.color = color;

       }

       public String toString() {              //定义或重写所需的方法

              return "你选择的颜色是" + color;

       }

}

1.5 类的加载

1.5.1 什么时候会加载类

    使用到类中的内容时加载:有三种情况

        1.创建对象:newStaticCode();

        2.使用类中的静态成员:StaticCode.num=9; StaticCode.show();

        3.在命令行中运行:javaStaticCodeDemo

1.5.2 类所有内容加载顺序和内存中的存放位置

       利用语句进行分析:

 创建对象 Person p=new Person("zhangsan",20);

    该句话所做的事情:

        1.在栈内存中,开辟main函数的空间,建立main函数的变量 p。

        2.加载类文件:因为new要用到Person.class,所以要先从硬盘中找到Person.class类文件,并加载到内存中。

加载类文件时,除了非静态成员变量(对象的特有属性)不会被加载,其它的都会被加载

        记住:加载,是将类文件中的一行行内容存放到了内存当中,并不会执行任何语句。---->加载时期,即使有输出语句也不会执行。

        静态成员变量(类变量)----->方法区的静态部分

        静态方法                  ----->方法区的静态部分

        静态代码块              ----->方法区的静态部分

        非静态方法(包括构造函数)----->方法区的非静态部分 

        构造代码块              ----->方法区的静态部分

注意:

    在Person.class文件加载时,静态方法和非静态方法都会加载到方法区中,只不过要调用到非静态方法时需要先实例化一个对象,对象才能调用非静态方法。如果让类中所有的非静态方法都随着对象的实例化而建立一次,那么会大量消耗内存资源,所以才会让所有对象共享这些非静态方法,然后用this关键字指向调用非静态方法的对象。

        3.执行类中的静态代码块:如果有的话,对Person.class类进行初始化。

        4.开辟空间:在堆内存中开辟空间,分配内存地址。

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

        6.显示初始化:对属性进行显示初始化。

        7.构造代码块:执行类中的构造代码块,对对象进行构造代码块初始化。

        8.构造函数初始化:对对象进行对应的构造函数初始化。

        9.将内存地址赋值给栈内存中的变量p

    执行普通方法 p.setName("lisi");

        1.在栈内存中开辟setName方法的空间,里面有:对象的引用this,临时变量name

        2.将p的值赋值给this,this就指向了堆中调用该方法的对象。

        3.将"lisi"赋值给临时变量name。

        4.将临时变量的值赋值给this的name。

     执行静态方法.Person.showCountry();

        1.在栈内存中,开辟showCountry()方法的空间,里面有:类名的引用Person。

        2.Person指向方法区中Person类的静态方法区的地址。

        3.调用静态方法区中的country,并输出。

注意:要想使用类中的成员,必须调用。通过什么调用?有:类名、this、super

1.5.3 静态代码块、构造代码块和构造函数的区别

        静态代码块:用于给类初始化,类加载时就会被加载执行,只加载一次。

        构造代码块:用于给对象初始化的。只要建立对象该部分就会被执行,且优先于构造函数。

        构造函数:  给对应对象初始化的,建立对象时,选择相应的构造函数初始化对象。

        创建对象时,三者被加载执行顺序:静态代码块--->构造代码块--->构造函数

实例

    父类属性引用对象:

package com.homework.labmanager.labmanager.utils;

import java.io.*;

/**
 * @author sunyiran
 * @date 2018-07-09
 * @purpose
 */
public class F implements Cloneable, Serializable {

    public F() {
        System.out.println("F实例化");
    }
}

    子类属性引用对象

package com.homework.labmanager.labmanager.utils;

/**
 * @author sunyiran
 * @date 2018-07-10
 * @purpose
 */
public class K {
    public K() {
        System.out.println("K实例化");
    }
}

    父类对象

package com.homework.labmanager.labmanager.utils;

/**
 * @author sunyiran
 * @date 2018-07-10
 * @purpose
 */
public class Father {
    F f = new F();
    static {
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("父类普通代码块");
    }
    Father() {
        System.out.println("父类实例化");
    }
    static F f1 = new F();
}

    子类对象

package com.homework.labmanager.labmanager.utils;

/**
 * @author sunyiran
 * @date 2018-07-09
 * @purpose
 */
public class Kid  extends  Father{
    K k = new K();
    static {
        System.out.println("子类静态代码块");
    }
    {
        System.out.println("子类普通代码块");
    }
    Kid() {
        System.out.println("子类实例化");
    }
    static K k1 = new K();

    public static void main(String[] args) {
        Kid k = new Kid();
    }
}

    最终结果

父类静态代码块
F实例化//父类静态属性
子类静态代码块
K实例化//子类静态属性
F实例化//父类普通属性
父类普通代码块
父类实例化//父类构造函数
K实例化//子类普通属性
子类普通代码块
子类实例化//子类构造函数

总结:

    第一阶段:父类静态属性,与静态代码块,按自然顺序执行

    第二阶段:子类静态属性,与静态代码块,按自然顺序执行

    第三阶段:父类普通属性,父类普通代码块,按自然顺序执行

    第四阶段:父类构造函数执行

    第五阶段:子类普通属性,子类普通代码块,按自然顺序执行

    第六阶段:子类构造函数执行

           


猜你喜欢

转载自blog.csdn.net/qq_35813653/article/details/80979068