Java基础-面向对象概述
本章重点针对面向对象的三大特征:继承、封装、多态进行详细的讲解。另外还包括抽象类、接口、内部类等概念。很多概念对于初学者来说,更多的是先进性语法性质的了解。
1. 面向对象-继承:
1. 继承的实现:
继承通过如下格式实现:
class 子类名 extends 父类名 [implements <接口名>]{
}
- 继承让我们更加容易实现类的扩展。比如:我们定义了人类,在定义Boy类就只需要扩展人类即可。实现了代码的重用。子类再保留父类的基本属性与行为的基础之上,可以增加新的属性和行为,或者修改父类的属性与行为。
- 从英文字面意思理解,extends的意思是“扩展”。子类是父类的扩展。现实世界中的继承无处不在。父类派生子类,子类继承父类,子类也可以在派生子类。这样就形成了层次结构。
2. 继承使用要点:
- 父类也称作为超类、基类、派生类等。
- Java中只有单继承,没有C++那样的多继承,多继承会引起混乱,使得继承链过于复杂,可能会导致二义性,系统难以维护。
- java中类没有多继承、接口有多继承(实现)。
- 子类继承父类,可以得到父类的全部属性和方法(除了父类的构造方法),但不见得可以直接访问(比如:父类私有的属性和方法).
- 如果定义一个类时,没有调用extends,则他的父类是:java.lang.Object;
- Object类是java预定义的所有类的父类,包含了所有java的公共属性,其中定义的属性和方法均可被任何类所使用、继承和修改。
- 子类继承父类时遵循普遍性原则和特殊性原则:
- 普遍性原则:指子类继承父类中已有的成员变量和方法
- 特殊性原则:指子类可以增加父类中没有的方法和变量,或者修改父类中已有的变量和方法。
3. super关键字的用法:
super是直接父类对象的引用,可以通过super来访问父类中被子类覆盖的方法或属性。
- 子类在继承父类时,可能会出现变量隐藏、方法覆盖等现象。
变量隐藏指子类的成员变量与父类的成员变量同名,此时父类的成员变量被隐藏。
方法覆盖指的是子类的方法名与父类的方法名同名,方法的返回值类型、入口参数的数目、类型、顺序均相同,只是方法实现的功能不同,此时弗雷德方法被覆盖。
如果子类需要调用或访问父类被隐藏的变量或被覆盖的方法,可以使用super关键字实现。
【注意】:但是创建子类对象的时候并不会创建一个父类对象,因为加入我创建子类的同时会自动创建父类对象的话,复杂的继承链会让堆内存立刻Overflow。所以说父类对象只是一个标识符,并非指向父类对象,而且this.hashCode方法和super.hashCode方法指向的都是同一块内存。super其实是一种用来访问父类成员的一种标识符而已。当继承中出现了变量隐藏和方法覆盖的时候,super就是一种访问父类属性和调用父类方法的一种手段。
- super关键字除了可以调用父类被隐藏的变量和被覆盖的方法之外,还可显式调用父类构造方法。
综上所述:super关键字的三个情况:
1.用来访问父类中被覆盖的方法
2.用来访问父类中被隐藏的成员变量
3.用来调用父类的构造方法
4. 子类对象的构造:
调用构造方法遵循以下几条规则:
-
创建对象时调用该类的父类构造方法。调用父类的构造方法很简单,只要在类的构造方法的方法体中,将第一条写为super语句即可。super可以调用父类的任何一个带入口参数或不带入口参数的构造方法。
-
如果类的构造方法中第一条没有用super来到用的构造方法,则编译器也会默认用super()调用父类的无参构造方法。
-
如果某个类的构造方法的第一条语句是用this来调用本类的另外一个构造方法,那么java系统就不会默认用这个构造方法调用父类的构造方法。(但是一定会调用父类的构造方法)
-
如果父类中定义了有参构造方法,则java系统不在提供默认的无参构造方法,因此在子类的构造方法中一定需要显式通过super调用父类有参构造方法。
5. 对象类型转化:
-
如同基本数据类型之间的类型转换,对象也可以在一定范围内进行类型转换。
由于子类拥有父类的方法和属性,因此java中的子类对象可以向上转换为父类对象(也成为上转型对象)。允许将子类的实例赋值给父类的引用,也允许一个父类的引用指向子类对象。
SuperClass superClass = new SubClass();//父类的引用指向了子类对象
-
但是反过来,一个父类对象的类型未必可以向下转换成子类对象。因为子类具有的信息,父类未必包含,这种转换是不安全的。
只有当父类引用实际上指向一个子类对象时,才可以进行这种转换。
SubClass subClass = new SuperClass();//错误!!!
-
类型转换的某些问题:
- 上转型对象不能操作子类新增加的成员变量和成员方法。
- 上转型对象可以代替子类对象调用子类重写的实例方法。
- 上转型对象可以调用子类继承的成员变量和隐藏的成员变量。
-
对象转换不仅只发生在对象赋值的情况下,也会发生在方法调用的参数传递的情况下。如果一个方法的形式参数定义的是父类对象,那么调用这个方法时,可以使用子类对象作为实际参数。
6. instanceof运算符:
instanceof是二元运算符,左边是对象,右边是类:当对象是右边的类或子类所创建的对象时,返回true;否则,返回false。
7. 方法的重写(Override):
子类通过重写父类的方法,可以用自身的行为去替换父类的行为。
方法的重写需要符合下面三个重点:
- “==”:方法名、参数列表相同。
- “<=”:返回值类型和声明异常类型,子类小于等于父类。
- “>=”:访问权限,子类大于等于父类。
8. toString方法的重写和equals方法的重写:
toString方法:
目的是为了打印对象的内容,可根据自己的格式进行重写。
equals方法:
- "=="和equals方法的区别:
- "=="代表比较双方是否相同,如果是基本类型,则表示值相等。如果是引用类型则表示地址相等,即是否为同一个对象。
- equals方法是由Object类中定义,提供定义"对象内容相等"的逻辑,即:内容相等即可。
- 重写equals方法
- equals在Object类中默认为:equals方法和==的作用一样,即看两个对象的HashCode是否相同。我们可以根据自己的需求重写equals方法。
- JDK提供的一些类,如String、Date、包装类,重写了Object的equals方法,调用这些类的equals方法,x.equals(y),当x和y所引用的对象是同一类对象且内容属性相等时(不一定是相同对象),返回true,否则返回false。
2. 面向对象-封装:
面向对象程序设计的一个特性就是封装,将实体特征的属性隐藏起来,对象与外界仅通过公共方法进行交流,这样可以提高程序的可靠性、安全性、改善程序的可维护性。数据的隐藏与开放对外的接口可以通过访问权限控制符来实现,权限控制符可以用来设置类、成员变量、成员方法等的访问权限。
1. 封装的实现-使用访问控制符:
Java提供public、protected、default、private四种访问控制符,在类、成员变量、成员方法的前面均可以使用访问控制符关键字,没有显式使用的为default控制类型。
- private:表示私有,只有自己类可以访问。
- private只能修饰成员变量和成员方法
- default:表示没有修饰符修饰,只有同一个包的类能访问。
- 默认权限,当类、接口、成员变量和成员方法没有设置访问控制符时,默认为dufault。
- protected:表示可以被同一个包的类以及其他包中的子类访问。
- 可以用来修饰成员变量和成员方法,不可以修饰类和接口。
- public:表示可以被该项目中的所有包中的所有类访问。
- 可以用来修饰类、接口、成员变量和成员方法。为了保证数据的隐藏性和安全性,不建议把所有的成员变量或方法全部设置为public,通常只将公共类或公共接口的成员方法指定为public
2. 封装的使用细节:
类的属性的处理:
- 一般使用private访问权限
- 提供相应的get/set方法来访问相关属性,这些方法通常都是public修饰的,以提供对属性的赋值与读取操作(注意:boolean类型的变量的get方法时is开头!)。
- 一些只用于本类的辅助性方法可以用private修饰,希望其它类调用的使用public修饰。
getInofo和setInfo:
处于对系统设计的安全性考虑,一般将类的成员属性定义为private形式保护起来,而将类的成员方法定义为public形式对外公开,这是类封装特性的一个体现。一般来说,类中应提供相应的get方法(得到private成员变量的值)和set方法(修改private成员变量的值),以便其它类操作该类的private成员变量。
3. 面向对象-多态:
多态指的是:同一个方法调用,由于对象不同可能会有不同的行为。现实生活中,同一个方法,具体实现会完全不同。即:不同类的对象收到相同的信息时,得到不同的结果。
1. 静态多态性和动态多态性:
静态多态性:
指定义在一个类或一个函数中的同名函数,他们根据参数列表(类型以及个数)区别语义和执行的功能。
动态多态性/运行时多态:
指定义在一个类层次的不同类中的重载函数,他们具有相同的函数原型,需要根据指针指向的对象所在类来区别语义,它通过动态联编实现。
2. 多态的使用要点:
- 多态指的是方法的多态,不是属性的多态(多态与属性无关)。
- 多态的存在要有三个必要条件(主要是运行时多态):
- 继承
- 方法重写
- 父类引用指向子类对象
- 父类引用指向子类对象后,用该父类引用调用子类重写的方法,此时多态就出现了。
4. 非访问控制符:
1. static:
1. 修饰类变量:
如果一个数据需要被所有对象共享使用的时候,那么即可使用static修饰该成员变量。
访问方式:
-
可以使用对象进行访问。 格式:对象.静态属性名
-
可以使用类名进行访问。 格式:类名.静态属性名
推荐使用类名进行访问。 -
静态的成员变量可以使用类名或者是对象进行访问,不需要创建对象就可以访问类变量。
-
非静态的成员变量只能使用对象进行访问,不能使用类名直接访问。
-
千万不要为了方便而是用static修饰一个成员变量,只有这个成员变量的数据是需要被共享的时候才使用static修饰。
1. 修饰类方法:
用于修饰类方法(或静态方法),与类变量类似,类方法不需要通过创建对象来使用,可以直接通过类来访问,类方法也不允许被重载。
- 非静态的方法只能使用对象调用,不能使用类名调用。
- 静态方法可以直接访问静态的成员,但是不能直接访问非静态的成员。
- 因静态方法可以使用类名调用,而这个时候对象可能还没有存在在内存中,这时候非静态的数据也就不存在在内存中。
- 非静态方法可以直接访问静态以及非静态的成员。
- 因非静态方法必须要由对象调用,如果对象存在了,静态数据以及非静态数据早就存在内存中了。
- 静态函数不能出现this以及super关键字。
- 因静态方法可以使用类名直接调用,而这个时候可能还没有对象存在,this又要代表当前对象。
2. final:
final关键字修饰变量:
被他修饰的变量不可改变。一旦赋了初值,就不能被重新赋初值。
final int MAX_SPEED = 120;
final关键字修饰方法:
该方法不可被子类重写。但是可以被重载。
final void study(){}
final关键字修饰类:
该类不能被继承,即final类不可能有子类。
final class A{}
- Java API中有许多类定义为final类,这些类通常是由固定作用、用来完成某种标准功能的类,如Math类、String类、Integer类等。
- 容易理解,abstract和final修饰符不能同时修饰一个类,但是可以各自与其他修饰符合用。当一个以上的修饰符修饰类时,这些修饰符之间以空格分开,写在关键字class之前,修饰符之间的先后排列次序对类的性质没有任何影响。
- final类中的所有成员变量和方法都认为是final的。
3. 其他修饰符:native、volatile、synchronized等。
5. 抽象方法和抽象类:
1. 抽象方法:
使用abstract修饰的方法,没有方法体,只有声明。定义的是一种规范,就是告诉子类必须要给抽象方法提供具体的实现。
父类中不能有方法体,但是子类必须实现抽象方法。
声明抽象方法的语法如下:
abstract <方法返回值类型> <方法名>([参数列表]);
2. 抽象类:
包含抽象方法的类就是抽象类,通过abstract方法定义规范,然后要求子类必须定义具体实现。通过抽象类,我们就可以做到严格限制子类的设计,使子类之间更加通用。
1. 抽象类的定义:
抽象类需要使用abstract来修饰,定义语法为:
abstract class <类名> [extends <父类>] [implements <接口名>]{
<类主体>
}
- 抽象类与抽象方法。抽象方法属于一种不完整的方法,只含有声明部分,没有方法主体。
- 包含抽象方法的类一定是抽象类,但是抽象类不一定必须包含抽象方法,也可以包含普通方法。也就是说,即使不包含任何抽象方法,也可以将一个类声明为抽象类。
- 抽象类可以包含普通属性、方法和构造方法。但是构造方法不能用来new实例,只能用来被子类调用。
- 抽象类不能被实例化。抽象类不能使用new关键字创建实例化对象。
- 抽象类只能用来被继承,抽象方法必须被子类实现。
2. 抽象类的继承:
如果一个类A需要继承抽象类,则该类必须实现抽象类中定义的所有抽象方法。否则,该类也必须修饰为抽象类。也就是说,抽象类的子类如果仅仅实现父类的部分抽象方法,子类也必须声明为抽象类。
6. 接口:
接口(interface)是Java所提供的另一种重要结构。接口是一种特殊的类,但接口与类存在这本质上的区别。类有成员变量和成员方法,而接口却只有常量和抽象方法,也就是说,接口的成员变量必须初始化,同时接口中的所有方法必须声明为abstract方法。
1. 接口的定义:
接口是一种比抽象类还要彻底的一种类(但是与类有着本质区别),接口通过interface来定义,一般形式如下:
[接口修饰符] interface <接口名> [extends [父类接口列表]]{
接口体
}
- 接口修饰符:接口修饰符为接口访问权限,有public和default默认两种类型。
-
public指任意类均可以使用这个接口。
-
default指只有与该接口定义在同一包中的类才可以访问这个接口,而其他包中的类无权访问该接口。
- 父类接口列表:
一个接口可以继承其他接口,通过关键字extends来实现,其语法与类相同。被继承的类接口被称为父类接口,接口可以实现多继承,当有多个父类接口时,用逗号","分隔。
- 接口体:
接口中的属性只能是常量,总是public static final修饰,不写也会默认是静态常量。属性在定义的时候必须用常量初始化。
接口中的方法只能是public abstract。省略的话,也是抽象方法。
2. 接口的实现:
- 子类通过implements来实现接口中的规范。一个类可以同时实现多个接口,接口之间用“,”分隔。
- 接口不能创建实例,但是可用于声明引用变量类型。
- 一个类实现了接口,必须实现接口中所有的方法,并且这些方法只能是public的。如果一个接口继承了父接口,则必须实现该接口及其父接口的所有方法。
- JDK1.7之前,接口只能包含静态常量、抽象方法,不能有普通属性、构造方法、普通方法。JDK1.8之后,接口中包含普通的静态方法。
3. 接口与多重继承:
-
与类一样,接口可以使用extends子句生成子接口。原来的接口叫做父接口/基本接口,拓展出的接口叫子接口/派生接口。派生接口不仅保留了父接口的成员,同时也可以加入新的成员以满足实际问题的需要。
-
与类不同的是,一个接口可以拓展多个接口,继承他们所有的属性和方法,而一个类只能拓展一个类。显然,接口不能拓展类,接口的方法必须全是抽象的。
-
interface A extends B{...}
- 这条语句表示定义了接口A并继承了接口B,使A成为B的子接口,并且具有B的所有属性。接口继承的过程中可能会出现以下情况:
- 方法重名:
- 如果两个方法一样,只保留一个。如果两个方法有不同的参数(类型或个数),那么子接口中包括两个方法,方法被重载;若两个方法仅在返回值上不同,出现错误。
- 常量重名:
- 两个重名常量全部保留,并使用原来的接口名作为前缀。
7. 内部类:
在Java中内部类分为:成员内部类(静态内部类、非静态内部类)、匿名内部类、局部内部类。
1. 成员内部类
可以使用private、default、protected、public任意进行修饰。类文件:外部类$内部类.class
1. 非静态内部类(外部类里使用非静态内部类和平时使用其他类没什么不同)
- 非静态内部类必须寄存在一个外部类对象里。因此,如果有一个非静态内部类对象,那么一定存在对应的外部类对象。非静态内部类对象单独属于外部类的某个对象。
- 非静态内部类可以直接访问外部类的成员,但是外部类不能直接访问非静态内部类成员。
- 非静态内部类不能有静态方法、静态属性和静态初始化块。
- 非静态内部类中相当于是一个外部类的成员变量,必须要有外部类的成员才能有内部类成员,所以说不可能有静态的方法属性和初始化块。
- 外部类的静态方法、静态代码块不能访问非静态内部类,包括不能使用非静态内部类定义变量、创建实例。
- 成员变量访问要点:
- 内部类里方法的局部变量:变量名
- 内部类的成员变量:this.变量名
- 外部类属性:外部类.this.变量名
2. 静态内部类:
定义方式:
static class ClassName{
//类体
}
使用要点:
- 当一个静态内部类对象存在,并不一定存在对应的外部类对象。因此,静态内部类的实例方法不能直接访问外部类的实例方法。
- 静态内部类看做外部类的一个静态成员。因此,外部类的方法中可以通过:“静态内部类.名字”的方式访问静态内部类的静态成员,通过new静态内部类()访问静态内部类的实例。
2. 匿名类:
使用类创建对象时,Java允许把类体与对象的创建组合在一起。
也就是说,类创建对象时,除了构造方法还有类体,此类体被称为匿名类。如果该类中的方法或属性不会被重复利用,则设计匿名类会比较简单。
匿名类由于无名可用,所以不可能用匿名类声明对象,但是可以直接用匿名类创建一个对象。
匿名内部类:
适合那种只需要一次的类。比如:键盘监听操作等等。
语法:
new 父类构造器(实参列表)\实现接口(){
//匿名内部类类体
}
- 匿名内部类没有访问修饰符。
- 匿名内部类没有构造方法。因为他连名字都没有,也就没有构造方法。
3. 局部内部类:
还有一种内部类,他是定义在方法内部的,作用域只限于本方法,成为局部内部类。
局部内部类的使用主要是用来解决比较复杂的问题,像创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类。
局部内部类和成员内部类一样被编译,只是他的作用域发生了改变,他只能在该方法中被使用,出了该方法就会失效。
局部内部类在实际开发中应用很少。
8. 泛型类:
JDK5之后支持泛型,泛型是对Java语言的类型系统的一种扩展,支持创建可以按类型进行参数化的类。
泛型的本质是参数化类型,也就是说,所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
1. 泛型类声明:
首先,在一对尖括号(<>)中声明类型变量,以逗号间隔变量名列表。在类的实例变量和方法中,可以在任何的地方使用这些类型变量。泛型类的声明格式如下:
class 泛型类名<泛型列表>{
类体
}
泛型类声明时并不指明泛型列表是什么类型的数据,可以是任何对象或接口,但不能是基本类型数据。泛型列表中的泛型可以作为类的成员变量的类型、方法的类型以及局部变量的类型。
泛型类的类体与普通类的类体完全相似,有成员变量和方法构成。例如:
class Chorous<E,F>{
void makeChorous(E person,F instruments){
instruments.toString();
person.toString();
}
}
其中<E,F>是泛型列表,E和F可理解为一种类参数,声明对象或创建实例时用具体的类名替代。
2. 使用泛型类声明对象:
使用泛型类声明对象时,必须指定类中使用泛型的具体类名。例如:
Chorous<Student,Button> model = new Chorous<Student,Button>();
3. 泛型接口:
与类相同,Java也支持泛型接口,泛型接口的定义格式如下:
interface 泛型接口名<泛型列表>{
接口体
}
Java泛型的主要目的是可以建立具有类型安全的数据结构,如:链表、散列表等数据结构。使用泛型类建立数据结构时,不必进行强制类型转换。