第5章 面向对象(上)

    Java是面向对象的程序设计语言,Java语言提供了定义类、成员变量、方法等最基本的功能。类可被认为是一种自定义的数据类型,可以使用类来定义的变量都是引用变量,它们将会引用到类的对象。类用于描述客观世界里某一类对象的共同特征,而对象则是类的具体存在,Java程序使用类的构造器来创建该类对象。

    Java也支持面向对象的三大特征:封装、继承和多态,Java提供了private、protected和public三个访问控制修饰符类实现良好的封装,提供了extends关键字来让子类继承父类,子类继承父类就可以继承到父类的成员变量和方法,如果访问控制允许,子类实例可以直接调用父类里定义方法。使用继承关系来实现复用时,子类对象可以直接赋给父类变量,这个变量具有多态性,编程更加灵活。

       构造器用来对类实例进行初始化操作,构造器支持重载,如果多个重载的构造器里包含了相同的初始化代码,则可以把这些初始化代码放置在普通初始化块里完成,初始化块总在构造器执行之前被调用。

1:类和对象

       面向对象的程序设计过程中有两个重要的概念:类(class)和对象(object,也称为实例),类是某一批对象的抽象,可以把类理解成某种概念;对象才是一个具体存在的实体。

    1:类

    Java定义类的简单语法:

修饰符 class 类名
{
	成员变量……;
	各种方法……;
}

    修饰符可以是public、final、abstract,或者完全省略这三个修饰符。类名只要是一个合法的标识符即可,对于一个类来说,可以包含三种最常见的成员:构造器、成员变量和方法,三个成员都可以定义零个或多个,static修饰的成员不能访问没有static修饰的成员,成员变量用于定义该类或该类的实例所包含的状态数据。方法则用于定义该类或该类的实例的行为特征或功能实现。构造器用于构造该类的实例,如果在类中没有写构造器,则系统会为该类提供有一个默认的构造器。

    Java定义成员变量的简单语法:

修饰符 类型 成员变量名 [=默认值]; 

    (1)修饰符:修饰符可以省略,也可以是public、protected、private、static、final其中public、protected、private最多出现其中一个,他们可以与final、static组合起来修饰成员变量。

    (2)类型:可以是Java语言允许任何数据类型,包括引用类型。

    (3)成员变量名:只要是一个合法标识符即可。

    (4)默认值:定义成员变量还可以指定一个可选的默认值,也可以不指定。

    Java定义方法的简单语法:

修饰符 方法返回值类型 方法名(参数列表){
	方法体;
}

    (1)修饰符:修饰符可以省略,也可以是public、protected、private、static、final,abstract其中public、protected、private最多出现其中一个,final,abstract最多出现其中一个,他们可以与static组合起来修饰方法。

    (2)方法返回值类型:可以是Java语言允许任何数据类型,包括引用类型。如果有返回值需要跟return配合使用,如果没有返回值,则必须使用void来声明没有返回值。

    (3)方法名:只要是一个合法标识符即可

    (4)参数列表:形参列表用于定义该方法可以接受的参数,参数可以是零个或多个,调用方法时必须传入对应的参数值,谁调用方法,谁负责为形参赋值。

    Java定义构造器的简单方法:

修饰符 构造器名(形参列表)
{
	构造器执行体;
}

    (1)修饰符:修饰符可以省略,也可以是public、protected、private其中之一。

    (2)构造器名:必须与类名相同。

    (3)形参列表:形参列表用于定义该方法可以接受的参数,参数可以是零个或多个,调用方法时必须传入对应的参数值,谁调用方法,谁负责为形参赋值。

       构造器既不能定义返回值类型,也不能使用void声明构造器没有返回值。

    2:对象的产生和使用

        创建对象的根本途径是构造器,通过new关键字来调用某个类的构造器即可创建这个类的实例。

         创建对象之后,接下来即可使用该对象了,Java的对象大致有两个作用:①访问对象的实例变量;②调用对象的方法。如果访问权限允许,类的定义方法和成员变量都可以通过类和实例来调用。static修饰的方法和成员变量,既可以通过类来调用,也可以通过实例来调用;没有使用static修饰的普通方法和 成员变量,可通过实例 来调用。

    3:对象的this引用

         Java提供了一个this关键字,this关键字总是指向调用该方法的对象。根据this出现位置的不同,this作为对象的默认引用有两种情形:①构造器中引用该构造器正在初始化的对象;②在方法中调用该方法的对象。

        this关键字最大的作用就是让类中的方法,访问该类里的另一个方法或实例变量。

public class Person {
		public void speak(){
			System.out.println("我是谁?我在哪儿?");
		}
		public void run() {
			this.speak();
		}
}

        Java也允许对象的一个成员直接调用另一个成员,可以省略this前缀。虽然可以省略this前缀,但是实际上这个this依然存在。

       当静态方法需要调用另一个非静态方法时不能用this关键字,this无法引用有效对象,程序会出现编译错误。

        Java编程时不要使用对象去调用static修饰的成员变量、方法,而是用该使用类去调用static修饰的成员变量、方法!如果需要在静态方法中访问另一个普通方法,则只能重新创建一个对象。

        大部分时候普通方法访问其他方法、成员变量是无需使用this前缀,但如果方法里有一个局部变量和成员变量同名,但程序有需要在该方法里访问这个被覆盖的成员变量,则必须使用this前缀。除此之外,this引用也可以用于构造器中作为默认引用,由于构造器是直接使用new关键字来调用,而不是使用对象来调用的,所以this在构造器中代表该构造器正在初始化的对象。

public class text1 {
	public int age;
	public text1() {
		int age=0;
		this.age=11;
	}
	public static void main(String[] args) {
		text1 t=new text1();
		System.out.println(t.age);
	}  

2:方法详解

       方法是类或对象的行为的特征的抽象,方法是类或对象最重要的组成部分。Java里的方法不能独立存在,必须定义在类里。方法在逻辑上要么属于类,要么属于对象。

    1:方法的所属性

        如果需要定义方法,则只能在类体内定义,不能独立定义一个方法。一旦将一个方法定义在某个类的类体内,如果这个方法使用了static修饰,则这个方法属于这个类,否则这个方法属于这个类的实例。

        Java语言里方法的所属性主要体现在如下几个方面:①方法不能独立定义,方法只能在类体内定义;②从逻辑意义上看,方法要么属于该类本身,要么属于该类的一个对象;③永远不能独立执行方法,执行方法必须使用类或者实例作为调用者。 

    2:方法重载

        Java允许同一个类里定义多个同名方法,只要形参列表不同就行。如果同一个类中包含了两个或两个以上方法的方法名相同,但形参列表不同,则被称为方法重载。
        从上面介绍可以看出,在Java程序中确定一个方法需要三个要素:①调用者,也就是方法的所属者,既可以是类,也可以是对象;②方法名:方法的标识。③形参列表,当调用方法时,系统将会根据传入的实参列表匹配。
        方法重载的要求就是两同一不同:同一个类中方法名相同,参数列表不同。在Java里不能使用方法返回值类型作为区分方法重载的依据。

    3:成员变量和局部变量

        在Java语言中,根据定义变量的位置不同,可以将变量分为两大类:成员变量和局部变量。

        成员变量指的是再类里定义的变量,局部变量指的是在方法里定义的变量,不管是成员变量还是局部变量,都应该遵守相同的命名规则。成员变量被分为类变量和实例变量两种,定义成员变量时没有static修饰的就是实例变量。有static修饰的就是类变量。局部变量根据定义形式的不同,又可分为如下三类:①形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效;②方法局部变量:在方法体内的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效;③代码块局部变量:在代码块中定义的局部变量,它的作用域是从定义该变量的地方生效,到该代码块结束时失效。

4:隐藏和封装

    1:理解封装

        封装(Encapsulation)是面向对象的三大特征之一,它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问。

        封装是面向对象编程语言对客观世界的模拟,在客观世界里,对象的状态信息都被隐藏在对象内部,外界无法直接操作和修改。对一个类或对象实现良好的封装,可以实现以下目的:①隐藏类的实现细节;②让使用者只能通过事先预定的方法来访问数据,从而可以在该方面里加入控制逻辑,限制对成员变量的不合理访问;③可进行数据检查,从而有利于保证对象信息的完整性;④便于修改,提高代码的可维护性。为了实现良好的封装,需要从两方面考虑:①将对象的成员变量和实现细节隐藏起来,不允许外部直接访问;②把方法暴露出来,让方法来控制对这些成员变量进行安全的访问和操作。因此,封装实际上有两个方面的含义:把该隐藏的隐藏起来,把该暴露的暴露出来。

    2:使用访问控制符

        Java提供了3个访问控制符:private、protected和public,分别代表了3个访问控制符级别,访问级别依次由小到大。

        private(当前类访问权限):如果类里的一个成员变量使用private访问控制符来修饰,则这个成员变量只能在当前类的内部被访问。

        protected(子类访问权限):如果一个成员使用protected访问控制符修饰,那么这个成员既可以被同一个包中的其他类访问,也可以被不同包中的子类访问,在通常情况下,如果使用protected来修饰一个方法,通常希望其子类来重写这个方法。

        public(公共访问权限):这是一个最宽松的访问级别,如果一个成员或者一个外部类使用public访问修饰符修饰,那么这个成员或外部类就可以被所有类访问,不管访问类和被访问类是否处于一个包内,是否具有父子继承关系。

        关于访问控制修饰符的使用,存在如下几条基本原则:①:类里的绝大部分成员变量都应该使用private修饰,还有一些static修饰的、类似全局变量的成员变量,才可以考虑使用public修饰。除此之外,有些方法只用于辅助实现该类的其他方法,这些方法被称为工具方法,工具方法也应该使用private修饰;②如果某个类主要用做其他类的父类。该类里包含的大部分方法可能仅希望被其子类重写,而不想被外界直接调用,则应该使用protected修饰这些方法;③希望暴露出来给其他类自由调用的方法应该使用public修饰。

5深入构造器

        构造器是一个特殊的方法,这个特殊方法用于创建实例时执行初始化。构造器是创建Java对象的重要途径,通过new关键字调用构造器时,构造器也确实返回了该类的对象,但这个对象并不是完全由构造器负责创建的,当程序员没有创建构造器时,系统会自动创建一个无参构造器。一旦程序员提供了自定义构造器,系统就不会提供默认的构造器。

        如果用户希望该类保留无参数的构造器,或者希望有多个初始化过程,则可以为该类提供多个构造器。如果有个类提供了多个构造器,就形成了构造器的重载。因为构造器主要用于被其他方法调用,用以返回该类的实例,因而通常吧构造器设置成public访问权限,从而允许系统中任何位置的类来创建该类的对象。

        1:构造器的重载

             同一个类里具有多个构造器,多个构造器的形参列表不同,即被称为构造器的重载。构造器重载的要求是构造器名相同,参数列表不同
       构造器不能被直接被调用,构造器必须使用new关键字来调用。但一旦使用new关键字来调用构造器,将会导致系统重新创建一个对象。

6:类的继承

    继承是面向对象的三大特征之一,也是实现软件复用的重要手段。Java的继承具有单继承的特点,每个子类只有一个直接父类。

    1:继承的特点

        Java的继承是通过extends关键字来实现的,实现继承的类被称为子类,被继承的类称为父类。每个子类只能有一个父类。

        Java里子类继承父类的语法格式是:  

修饰符 class 子类名 extends 父类名{
	类定义部分;
}

        子类扩展了父类,将可以获得父类的全部成员变量和方法。从子类角度来看,子类扩展了父类,但从父类来看,父类派生了子类。

    2:重写父类的方法

        子类包含于父类同名方法的现象被称为方法重写(Override),也被称为方法覆盖。方法重写要遵循“两同两小一大”规则,即方法名相同、参数列表相同;两小指的是子类的方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常更小或相等;一大指的是子类方法的访问权限应比父类方法的访问权限更大或相等。尤其需要指出的是,覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法,不能一个是类方法,一个是实例方法。

        当子类覆盖了父类方法后,子类的对象将无法访问父类被覆盖的方法,但可以在子类方法中调用父类中被覆盖的方法。如果需要在子类方法中调用父类中被覆盖的方法,则可以使用super或者父类类名作为调用者来调用父类中覆盖的方法。如果父类方法具有private访问权限,则该方法对其子类是隐藏的,因此其子类无法访问该方法,也就是无法重写该方法。如果子类中依然写了这个方法,那么这个不是重写,而是新定义了一个新方法。

    3:super限定

         如果需要在子类方法中调用父类被覆盖的实例方法,则可以使用super限定来调用父类被覆盖的实例方法。super用于限定该对象调用它从父类继承得到的实例变量或方法。为了在子类方法中访问父类定义的、被隐藏的实例变量,或为了在子类方法中调用父类中定义的、被覆盖的方法,可以通过super.作为限定类调用这些实例变量和实例方法。

    4:调用父类构造器

        子类不会获得父类的构造器,但子类构造器里可以调用父类构造器的初始化代码,在子类构造器中调用父类构造使用super调用来完成。创建任何对象总是从该类所在继承书最顶层类的构造器开始执行,然后依次向下执行,最后才执行本类的构造器。

7:多态

        Java引用变量有两种类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用该类型决定,运行时类型由实际赋给该变量的对象决定,如果编译时类型和运行时类型不一致,就可能出现所谓的多态。

    1:多态性

        相同类型的变量,调用同一个方法时呈现出多种不同的行为特征,这就是多态。

        引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。因此,编写Java代码时,引用变量只能调用声明该变量时所用类里包含的方法。

        通过引用变量类来访问其包含的实例变量时,系统总是试图访问它编译时类型所定义的成员变量,而不是它运行时类型所定义的成员变量。

    2:引用变量的强制类型转换

        编写Java程序时,引用变量只能调用它编译时类型的方法,而不能调用它运行时类型方法,即是它实际所引用的对象确实包含该方法。如果需要让这个引用类型调用它运行时类型方法,则必须把它强制类型转换成运行时类型,强制类型转换需要借助于类型强制转换运算符(小括号)。

        强制转换不是万能的,当进行强制类型转换书需要注意的是:①基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整数类型、字符类和浮点型。但数值类型和布尔类型时间不能进行类型转换。②引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行,否则将会在运行时引发异常。

        考虑到进行强制类型转换时可能出现异常,因此进行类型转换之前应先通过instanceof运算符来判断是否可以成功转换。例子如下:

if (Object类型变量 instanceof String) {
			String 变量=(String)Object类型变量;
		}

    3:instanceof运算符

        instanceof运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类,它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是,则返回true,否则返回false。在使用instanceof运算符时需要注意:instanceof运算符前面操作数的编译时类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误。

        instanceof运算符的作用是:在进行强制类型转换之前,首先判断前一个对象是否是后一个类的实例,是否可以转换成功,从而保证代码更加健壮。

8:继承与组合

    继承是实现类复用的重要手段,但继承带来了一个最大的坏处:破坏封装。相比之下,组合也是实现类的复用的重要方式,而采用组合方式类实现类的复用则提供更好的封装性。

    1:使用继承的注意点

        子类扩展父类时,子类可以从父类继承得到成员变量和方法,如果访问权限允许,子类可以直接访问父类的成员变量和方法,相当于子类可以直接复用父类的成员变量和方法,确实非常方便。继承带来高度复用的同时,也带来了一个严重问题:继承严重的破坏了父类的封装性,,介绍封装时提到:每个类都应该封装它的内部信息和实现细节,而只暴露必要的方法给其他类使用。但在继承关系中,子类可以直接访问父类的成员变量和方法,从而造成子类和父类的严重耦合。从这个角度来看,子类可以直接访问父类的成员变量和方法,并可以改变父类方法的实现细节,从而导致子类可以恶意篡改父类的方法。

        为了保证父类有良好的封装性,不会被子类随意更改,设计父类通常应该遵循如下规则。

        ①尽量隐藏父类的内部数据。尽量吧父类的所有成员变量都设置成private访问类型,不要让子类直接访问父类的成员变量。

        ②不要让子类可以随意访问、修改父类的方法。父类中那些仅为辅助其他的工具方法,应该适应private修饰符修饰,让子类无法访问该方法;如果父类中的方法需要被外部类调用,则必须以public修饰,但又不希望子类重写该方法,可以使用final修饰符类修饰该方法;如果希望父类的某个方法被子类重写,但不希望被其他类自由访问,则可以protected类修饰该方法。

        ③尽量不要在父类构造器中调用将要被子类重写方法。

        派生新的子类需要具备以下两个条件之一:①子类需要增加额外属性,而不仅仅是属性值的改变;②子类需要额外增加自己独有的行为方式。

    2:利用组合实现复用

        如果需要复用一个类,除了把这个类当成基类来继承之外,还可以吧该类当成另一个类的组合成分,从而允许新类直接复用该类的public方法。

        大部分时候,继承关系中多个子类里抽象出共有父类的过程,类似于组合关系中多个整体类里提取被组合类的过程;继承关系中从父类派生子类的过程,则类似于组合关系中把被组合类组合到整体类的过程。总之,继承要表达的是一种(is-a)的关系,而组合表达的是(has-a)的关系。

9:初始化块

        Java使用构造器来对单个 对象今次那个初始化操作,使用构造器先完成整个Java对象的状态初始化,然后将Java对象返回给程序,从而让该Java对象的信息更加完整。与构造器作用非常类似的是初始化块,它也可以对Java对象进行初始化操作。

    1:使用初始化块

        初始化块是Java类里可出现的第四种成员,一个类里可以有多个初始化块,相同类型的初始化块之间有顺序:前面定义的初始化块先执行,后面定义的初始化块后执行。语法格式如下:

修饰符 {
	初始化块执行代码;
}

        修饰符只能是static,使用static修饰的初始化块被称为静态初始化块,还可以不用修饰符修饰。

    2:初始化块和构造器

        从某种程度上看,初始化块是构造器的补充,初始化块总是在构造器执行之前执行。与构造器不同的是,初始化块是一段固定执行的代码,它不能接受任何参数。因此初始化块对同一个类的所有对象所进行的初始化处理完全相同。


猜你喜欢

转载自blog.csdn.net/Hot_snail/article/details/79681122