面向对象(上)
一、类和对象
面向对象有两个基本概念:类和对象,对象也被称为实例,类是某一批对象的抽象,对象是具体存在的实体。
类最常见的三种成员
- 构造器
- 成员变量
- 方法
定义顺序没有任何影响,成员之间可以相互调用
注意: **static修饰的成员不能访问没有static修饰的成员。**很好理解,static修饰的成员属于这个类的成员,如果还没有创建对象,但是调用了含有访问没有static修饰的成员的类方法,就会出现错误。
构造器是一个类创建对象的根本途径,如果一个类没有构造器,这个类无法创建实例。因此Java提供了一个功能:如果没有为一个类编写构造器,Java会默认为该类提供一个无参数的构造器。但是如果编写了一个有参数的构造器,则不会再有默认的无参数构造器。
static修饰字
static修饰字是一个特殊的关键字,可以用来修饰方法变量等,它修饰的成员表明这个成员属于这个类本身,不属于任何对象,因此把static修饰的成员变量和方法叫做类变量和类方法,把没有用static修饰的成员变量和方法叫做实例变量和方法。
对象的this调用
Java提供了一个this关键词,this关键词总是指向调用该方法的对象,根据出现位置不同,this作为对象默认调用有两种情况
- 构造器中引用该构造器正在初始化的对象
- 在方法中调用该方法的对象
this可以代表任何对象,当this出现在某个方法体中时,它代表的对象是不确定的,但可以确定的是,它所代表的只能是当前类的实例,只有当这个方法被真正调用的时候,才能确定this代表的是哪个对象,谁在调用这个对象,this就代表谁。
但注意,static修饰的方法不能用this引用。由前面可知,静态成员不能访问非静态成员,所以静态方法不能访问普通成员,如果必须访问的话,则只能在静态方法中定义一个对象,通过这个对象来进行访问。
大部分时候访问类成员时可以不用this关键词,但如果出现方法中一个局部变量与成员变量同名,又需要访问这个成员变量,则需要加上this。
注意
Java允许某个类的实例来调用类方法,但这是不应该的,类方法属于这个类,而不属于任何对象,所以不要用对象去调用类方法,而应该直接用类名来调用类方法。
方法详解
方法必须定义在类里,所有方法在逻辑上,要么属于这个类本身,要么属于对象。
Java语言是静态的,一个类定义完成后,只要不重新编译这个类文件,该类和该类对象所拥有的方法是固定的,不会改变。
执行方法时,必须使用类或者对象作为调用者,即所有的方法都必须使用“类.方法”或者“对象.方法”的形式进行调用。
需要指出:同一类的方法调用另一个方法时,如果被调方法是普通方法,默认使用this作为调用者,如果是类方法,默认使用类作为调用者。
方法参数传递机制
Java里方法传递参数方式只有一种,值传递。即如果是基本类型变量,则会将实际参数复制一份传递给方法形参,原来变量不会出现任何改动,如果是引用变量,则变量保存的是实际数据的内存地址,将会把地址传递给方法形参,实际就是C语言中的指针。
Java允许定义形参变量可变的参数,如果在定义方法时,在最后一个形参类型后增加三点,则表明该形参可以接受多个参数值。
例子
1 |
|
方法重载
Java允许同一个类定义多个同名方法,只要形参列表不同就行,这被称为方法重载。
注意:方法重载只要求方法名相同,形参列表不同,至于其他部分,没有要求。
不推荐在重载形参个数可变的方法。
成员变量和局部变量
成员变量是在类里定义的变量,局部变量是在方法里定义的变量,其中static修饰的变量是类变量。
类变量的生存域与类的生存范围相同,实例变量则从该类实例创建起存在,直到系统销毁这个实例。类变量和实例变量统称为成员变量。
成员变量不需要显式初始化,系统在创建这个变量时会进行默认初始化,int,double,float等数值类型默认初始化为0,布尔类型默认初始化为false。
局部变量则根据定义形式不同,分为三种
- 形参:在定义方法时定义的变量,在整个方法内有效
- 方法局部变量:在方法体内定义的变量,作用域是从创建的地方开始,到该方法结束
- 代码块局部变量:在代码块中定义的局部变量,作用域从定义地方开始到代码块结束
局部变量除了形参之外,都需要显式初始化,否则不能访问。
隐藏和封装
封装是指不允许外部程序直接访问对象内部信息,而应该通过该类提供的方法间接访问。
为了实现良好封装,考虑以下两个方面
- 将对象的成员变量和实现细节隐藏起来,不允许外部直接访问
- 把方法暴露出来,让方法来控制这下成员变量进行安全访问和操作
使用访问控制符来实现控制访问
Java提供了3个控制符:public, private, protected
Java的访问控制级别由小到大依次为
private----->default---->protected---->public
default表示没有访问符修饰
访问控制级别表
权限 | private | default | protected | public |
同一个类 | 可以 | 可以 | 可以 | 可以 |
同一个包 | 可以 | 可以 | 可以 | |
子类中 | 可以 | 可以 | ||
全局范围 | 可以 |
但对于一个外部类(相对于内类而言的类,没有子类)而言,只能有public和默认来修饰,其他修饰符没有意义。
关于控制访问符的使用,几条基本原则
- 类里的绝大部分变量应该用private修饰,有一些方法辅助实现该类的其他方法,被称为工具方法,也应该用private修饰。
- 如果某个类做其他类的父类,该类里包含的大部分方法仅希望被子类重写,不希望被外界直接调用,用protected修饰
- 希望暴露出来给其他类自由调用的方法应该用public修饰。
深入构造器
构造器是创建Java对象的重要途径,但这个对象不是完全由构造器负责创建,事实上,当程序调用构造器时,系统会先为该对象分配内存空间,并未这个对象默认初始化,这个对象产生了,这些操作是在构造器执行前完成的。
也就是说,当系统执行构造器之前,构造器已经创建了一个对象,只是这个对象还不能被外界程序访问,只能在构造器中通过this来引用,当构造器执行结束后,这个对象被作为构造器的返回值返回,赋给一个引用类型的变量。
### 构造器重载
同一个类里有多个构造器,但形参列表不同,被称为构造器重载。
如果系统包含了多个构造器,其中一个构造器B的执行体完全包含另一个构造器A的执行体,那么可以在B中调用A,使用this关键词来调用相应构造器。
类的继承
继承通过extends来实现。
子类可以获得父类的所有成员变量,方法和内部类,但不能获得父类的构造器。
如果父类的成员变量是private修饰的,则子类虽然继承得到了这些变量,但不能直接访问,需通过父类提供的方法来间接访问。
重写父类方法
子类与父类同名方法的现象被称为方法重写,或者方法覆盖。
重写要遵循“两同两小一大”原则:“两同”即方法名相同,形参列表形同,“两小”指子类方法返回值比父类方法返回值更小或者相等,子类方法声明抛出的异常比父类方法生命抛出的异常更小或者相等,“一大”指子类方法访问权限比父类方法访问权限更大或者相等。
需要指出:覆盖方法和被覆盖的方法要么都是类方法,要么都是实例方法。
super限定
如果需要在子类方法中调用父类中被覆盖的实例方法,可以使用super限定来调用父类被覆盖的实例。
例如父类中有方法fly(),子类可以使用super.fly()来调用父类fly方法。
super用于限定该对象调用它从父类继承得到的实例变量或方法,super也不能出现在static修饰的方法里。
当然如果子类没有与父类同名的成员变量可以直接使用名字访问子类从父类继承得到的成员变量,如果同名则应该使用super.a来访问从父类继承得到的a变量。
如果在构造器中使用super,即直接用super()
即可调用父类构造器,super限定该构造器初始化的是该对象从父类继承得到的实例变量,而不是该类自己定义的实例变量。
使用父类构造器必须出现在子类构造器的第一行,不管是否通过super调用父类构造器,子类构造器总会调用父类构造器一次,即如果子类构造器执行体没有super和this调用,系统会在执行子类构造器之前,隐式调用父类无参数的构造器。
当执行父类构造器时,系统会再上溯执行父类的父类的构造器,依次类推,创建任何Java对象,最先执行的总是Object类的构造器。
## 多态
Java引用变量有两个类型:编译时类型和运行时类型。
编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给的变量对象决定。如果两个类型不一致,就有可能出现多态。
Java允许把一个子类对象赋给父类引用变量,无需任何类型转换,这个称为向上转型,由系统自动完成。
当运行时,调用父类的引用变量方法时,方法行为体现出子类方法的行为特征,一个父类可能有多个子类,这就有可能出现同一个类型的变量调用同一个方法时,呈现出不同的行为特征,即多态。
但与方法不同,对象的实例变量不具有多态性,如果子类和父类中有相同名称的成员变量,当使用父类引用变量输出该变量时,输出的就是父类的成员变量。
注意:引用变量在编译阶段只能调用编译时类型的方法,但运行时执行运行时类型具有的方法,因此写代码时,引用变量只能调用声明该变量时所用类里的方法,例如Object p = new Person();
,这个p只能调用Object类里的方法,不能调用Person类里的方法。
### 引用变量的强制类型转换
如果需要让引用变量调用运行时类型的方法,则必须把它强制类型转换为运行时类型,使用类型转换运算符,(type)variable。
需要注意:
- 基本类型之间转换只能在数值类型之间进行,数值类型和布尔类型不能相互转换
- 引用类型之间转换只能在具有继承关系的两个类型之间进行,没有任何继承关系的类型,无法进行强制转换。
在强制类型转换之前,使用instanceof运算符进行判断是否可以成功转换,可以使程序更加健壮。
instanceof运算符的前一个操作数是引用类型变量,后一个通常是一个类(也可以是接口),它用于判断前面的对象是否属于后面的类,或者其子类、实现类的实例,是返回true,否则false
使用时注意:前面操作数编译时类型要么与后面类相同,要么与后面的类具有继承关系,否则会编译错误
继承和组合
继承是实现类复用的重要手段,但继承最大的坏处是破坏了封装。而采用组合实现类复用可以提供更好的封装性。
使用继承需要注意的几点
- 尽量隐藏父类的内部数据,把所有成员变量设为private类型,不要让子类直接访问父类的成员变量
- 不要让子类随意访问修改父类的方法,父类中的工具方法应该使用private控制符修饰,如果父类方法需要被外部类调用,但又不希望子类重写,可以使用final修饰符修饰
- 尽量不要在父类构造器中调用将被子类重写的方法。
如果不想让某个类被继承,可以使用final修饰这个类,也可以使用private修饰这个类的所有构造器,从而使子类无法调用该类构造器,也就无法继承该类。
利用组合来实现类复用实际就是在新类中创建一个需要复用的类的引用变量,这样就可以使用旧类的成员变量和方法。