JAVA面向对象三大特性详解

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

JAVA⾯向对象三⼤特性详解
⼀、封装
1、概念:
将类的某些信息隐藏在类内部,不允许外部程序直接访问,⽽是通过该类提供的⽅法来实现对隐藏信息的操作和访问。

2、好处:
只能通过规定的⽅法访问数据。

隐藏类的实例细节,⽅便修改和实现。

 
3、封装的实现步骤
需要注意:对封装的属性不⼀定要通过get/set⽅法,其他⽅法也可以对封装的属性进⾏操作。

当然最好使⽤get/set⽅法,⽐较标准。

A、访问修饰符
从表格可以看出从上到下封装性越来越差。

B、this关键字
 1.this关键字代表当前对象
this.属性操作当前对象的属性
this.⽅法调⽤当前对象的⽅法。

 2.封装对象的属性的时候,经常会使⽤this关键字。

 3.当getter和setter函数参数名和成员函数名重合的时候,可以使⽤this区别。

如:
C、Java 中的内部类
 内部类( Inner Class )就是定义在另外⼀个类⾥⾯的类。

与之对应,包含内部类的类被称为外部类。

 那么问题来了:那为什么要将⼀个类定义在另⼀个类⾥⾯呢?清清爽爽的独⽴的⼀个类多好啊!!
 答:内部类的主要作⽤如下:
1. 内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同⼀个包中的其他类访问该类。

3. 内部类所实现的功能使⽤外部类同样可以实现,只是有时使⽤内部类更⽅便。

内部类可分为以下⼏种:
成员内部类
静态内部类
⽅法内部类
匿名内部类
各个内部类的具体使⽤请转移到另⼀篇随⽂:
⼆、继承
1、继承的概念
 继承是类与类的⼀种关系,是⼀种“is a”的关系。

⽐如“狗”继承“动物”,这⾥动物类是狗类的⽗类或者基类,狗类是动物类的⼦类或者派⽣类。

如下图所⽰:
 注:java中的继承是单继承,即⼀个类只有⼀个⽗类。

2、继承的好处
 ⼦类拥有⽗类的所有属性和⽅法(除了private修饰的属性不能拥有)从⽽实现了实现代码的复⽤; 
3、语法规则,只要在⼦类加上extends关键字继承相应的⽗类就可以了:
A、⽅法的重写
 ⼦类如果对继承的⽗类的⽅法不满意(不适合),可以⾃⼰编写继承的⽅法,这种⽅式就称为⽅法的重写。

当调⽤⽅法时会优先调⽤⼦类的⽅法。

 重写要注意:
a、返回值类型
b、⽅法名
c、参数类型及个数
 都要与⽗类继承的⽅法相同,才叫⽅法的重写。

 重载和重写的区别:
⽅法重载:在同⼀个类中处理不同数据的多个相同⽅法名的多态⼿段。

⽅法重写:相对继承⽽⾔,⼦类中对⽗类已经存在的⽅法进⾏区别化的修改。

B、继承的初始化顺序
1、初始化⽗类再初始化⼦类
2、先执⾏初始化对象中属性,再执⾏构造⽅法中的初始化。

 基于上⾯两点,我们就知道实例化⼀个⼦类,java程序的执⾏顺序是:
 下⾯有个形象的图:
C、final关键字
 使⽤final关键字做标识有“最终的”含义。

1. final 修饰类,则该类不允许被继承。

2. final 修饰⽅法,则该⽅法不允许被覆盖(重写)。

3. final 修饰属性,则该类的该属性不会进⾏隐式的初始化,所以该final 属性的初始化属性必须有值,或在构造⽅法中赋值(但只能选其⼀,且必须选其⼀,因为没有默认值!),且初始化之后就不能改了,只能赋值⼀次。

4. final 修饰变量,则该变量的值只能赋⼀次值,在声明变量的时候才能赋值,即变为常量。

D、super关键字
 在对象的内部使⽤,可以代表⽗类对象。

1、访问⽗类的属性:super.age
2、访问⽗类的⽅法:super.eat()
 super的应⽤:
 ⾸先我们知道⼦类的构造的过程当中必须调⽤⽗类的构造⽅法。

其实这个过程已经隐式地使⽤了我们的super关键字。

 这是因为如果⼦类的构造⽅法中没有显⽰调⽤⽗类的构造⽅法,则系统默认调⽤⽗类⽆参的构造⽅法。

 那么如果⾃⼰⽤super关键字在⼦类⾥调⽤⽗类的构造⽅法,则必须在⼦类的构造⽅法中的第⼀⾏。

 要注意的是:如果⼦类构造⽅法中既没有显⽰调⽤⽗类的构造⽅法,⽽⽗类没有⽆参的构造⽅法,则编译出错。

(补充说明,虽然没有显⽰声明⽗类的⽆参的构造⽅法,系统会⾃动默认⽣成⼀个⽆参构造⽅法,但是,如果你声明了⼀个有参的构造⽅法,⽽没有声明⽆参的构造⽅法,这时系统不会动默认⽣成⼀个⽆参构造⽅法,此时称为⽗类有没有⽆参的构造⽅法。


E、Object类
 Object类是所有类的⽗类,如果⼀个类没有使⽤extends关键字明确标识继承另⼀个类,那么这个类默认继承Object类。

 Object类中的⽅法,适合所有⼦类!!!
 那么Object类中有什么主要的⽅法呢?
 1、toString()
a. 在Object类⾥⾯定义toString()⽅法的时候返回的对象的哈希code码(对象地址字符串)。

我们可以发现,如果我们直接⽤System.out.print(对象)输出⼀个对象,则运⾏结果输出的是对象的对象地址字符串,也称为哈希code码。

如:
哈希码是通过哈希算法⽣成的⼀个字符串,它是⽤来唯⼀区分我们对象的地址码,就像我们的⾝份证⼀样。


b. 可以通过重写toString()⽅法表⽰出对象的属性。

如果我们希望输出⼀个对象的时候,不是它的哈希码,⽽是它的各个属性值,那我们可以通过重写toString()⽅法表⽰出对象的属性。

 2、equals()
a、equals()----返回值是布尔类型。

b、默认的情况下,⽐较的是对象的引⽤是否指向同⼀块内存地址-------对象实例化时,即给对象分配内存空间,该内存空间的地址就是内存地址。

使⽤⽅法如:dog.equals(dog2);
c、如果是两个对象,但想判断两个对象的属性是否相同,则重写equals()⽅法。

 以Dog类为例,重写后的equals()⽅法如下(当然你可以根据⾃⼰想⽐较的属性来重写,这⾥我以age属性是否相同来重写equals()⽅法):
 上⾯有四个判断,它们的含义分别是:
1.判断地址是否相同----if (this == obj),相同则返回true
2.判断对象是否为空----if (obj == null),为空则返回false
3.getClass()可以得到类对象,判断类型是否⼀样-----if (getClass() != obj.getClass()),不⼀样则返回false
4.判断属性值是否⼀样----if (age != other.age),不⼀样返回false
5.如果地址相同,对象不为空,类型⼀样,属性值⼀样则返回true
 这⾥要注意的是,理解obj.getClass()得到的类对象和类的对象的区别,以下⽤图形表⽰:
 可以看到,对于类对象我们关⼼它属于哪个类,拥有什么属性和⽅法,⽐如我和你都是属于“⼈”这个类对象;⽽类的对象则是⼀个类的实例化的具体的⼀个对象。

⽐如我和你是两个不同的⼈。

三、多态
 ⾯向对象的最后⼀个特性就是多态,那么什么是多态呢?多态就是对象的多种形态。

 java⾥的多态主要表现在两个⽅⾯:
1.引⽤多态
⽗类的引⽤可以指向本类的对象;
⽗类的引⽤可以指向⼦类的对象;
这两句话是什么意思呢,让我们⽤代码来体验⼀下,⾸先我们创建⼀个⽗类Animal和⼀个⼦类Dog,在主函数⾥如下所⽰:
注意:我们不能使⽤⼀个⼦类的引⽤来指向⽗类的对象,如:。

给⼤家讲解⼀下:就以上⾯的例⼦来说,我们能说“狗是⼀种动物”,但是不能说“动物是⼀种狗”,狗和动物是⽗类和⼦类的继承关系,它们的从属是不能颠倒的。

当⽗类的引⽤指向⼦类的对象时,该对象将只是看成⼀种特殊的⽗类(⾥⾯有重写的⽅法和属性),反之,⼀个⼦类的引⽤来指向⽗类的对象是不可⾏的!!
2.⽅法多态
根据上述创建的两个对象:本类对象和⼦类对象,同样都是⽗类的引⽤,当我们指向不同的对象时,它们调⽤的⽅法也是多态的。

创建本类对象时,调⽤的⽅法为本类⽅法;
创建⼦类对象时,调⽤的⽅法为⼦类重写的⽅法或者继承的⽅法;
使⽤多态的时候要注意:如果我们在⼦类中编写⼀个独有的⽅法(没有继承⽗类的⽅法),此时就不能通过⽗类的引⽤创建的⼦类对象来调⽤该⽅法!!!
注意:继承是多态的基础。

A、引⽤类型转换 
 了解了多态的含义后,我们在⽇常使⽤多态的特性时经常需要进⾏引⽤类型转换。

 引⽤类型转换:
 1. 向上类型转换(隐式/⾃动类型转换),是⼩类型转换到⼤类型。

 就以上述的⽗类Animal和⼀个⼦类Dog来说明,当⽗类的引⽤可以指向⼦类的对象时,就是向上类型转换。

如:
 2. 向下类型转换(强制类型转换),是⼤类型转换到⼩类型(有风险,可能出现数据溢出)。

将上述代码再加上⼀⾏,我们再次将⽗类转换为⼦类引⽤,那么会出现错误,编译器不允许我们直接这么做,虽然我们知道这个⽗类引⽤指向的就是⼦类对象,但是编译器认为这种转换是存在风险的。

如:
那么我们该怎么解决这个问题呢,我们可以在animal前加上(Dog)来强制类型转换。

如:
但是如果⽗类引⽤没有指向该⼦类的对象,则不能向下类型转换,虽然编译器不会报错,但是运⾏的时候程序会出错,如:
其实这就是上⾯所说的⼦类的引⽤指向⽗类的对象,⽽强制转换类型也不能转换!!
还有⼀种情况是⽗类的引⽤指向其他⼦类的对象,则不能通过强制转为该⼦类的对象。

如:
这是因为我们在编译的时候进⾏了强制类型转换,编译时的类型是我们强制转换的类型,所以编译器不会报错,⽽当我们运⾏的时候,程序给animal开辟的是Dog类型的内存空间,这与Cat类型内存空间不匹配,所以⽆法正常转换。

这两种情况出错的本质是⼀样的,所以我们在使⽤强制类型转换的时候要特别注意这两种错误!!下⾯有个更安全的⽅式来实现向下类型转换。

3. instanceof运算符,来解决引⽤对象的类型,避免类型转换的安全性问题。

instanceof是Java的⼀个⼆元操作符,和==,>,<是同⼀类东东。

由于它是由字母组成的,所以也是Java的保留关键字。

它的作⽤是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。

我们来使⽤instanceof运算符来规避上⾯的错误,代码修改如下:
利⽤if语句和instanceof运算符来判断两个对象的类型是否⼀致。

补充说明:在⽐较⼀个对象是否和另⼀个对象属于同⼀个类实例的时候,我们通常可以采⽤instanceof和getClass两种⽅法通过两者是否相等来判断,但是两者在判断上⾯是有差别的。

Instanceof进⾏类型检查规则是:你属于该类吗?或者你属于该类的派⽣类吗?⽽通过getClass获得类型信息采⽤==来进⾏检查是否相等的操作是严格的判断,不会存在继承⽅⾯的考虑;
总结:在写程序的时候,如果要进⾏类型转换,我们最好使⽤instanceof运算符来判断它左边的对象是否是它右边的类的实例,再进⾏强制转换。

B、抽象类
 定义:抽象类前使⽤abstract关键字修饰,则该类为抽象类。

 使⽤抽象类要注意以下⼏点:
1. 抽象类是约束⼦类必须有什么⽅法,⽽并不关注⼦类如何实现这些⽅法。

2. 抽象类应⽤场景:
a. 在某些情况下,某个⽗类只是知道其⼦类应该包含怎样的⽅法,但⽆法准确知道这些⼦类如何实现这些⽅法(可实现动态多态)。

b. 从多个具有相同特征的类中抽象出⼀个抽象类,以这个抽象类作为⼦类的模板,从⽽避免⼦类设计的随意性。

3. 抽象类定义抽象⽅法,只有声明,不需要实现。

抽象⽅法没有⽅法体以分号结束,抽象⽅法必须⽤abstract关键字来修饰。

如:
4、包含抽象⽅法的类是抽象类。

抽象类中可以包含普通的⽅法,也可以没有抽象⽅法。

如:
5、抽象类不能直接创建,可以定义引⽤变量来指向⼦类对象,来实现抽象⽅法。

以上述的Telephone抽象类为例:
1 public abstract class Telephone {
2 public abstract void call();//抽象⽅法,⽅法体以分号结束,只有声明,不需要实现
3 public void message(){
4 System.out.println("我是抽象类的普通⽅法");
5 }//抽象类中包含普通的⽅法
6 }
1 public class Phone extends Telephone {
2
3 public void call() {//继承抽象类的⼦类必须重写抽象⽅法
4 // TODO Auto-generated method stub
5 System.out.println("我重写了抽象类的⽅法");
6 }
7
8 }
 以上是Telephone抽象类和⼦类Phone的定义,下⾯我们看main函数⾥:
运⾏结果(排错之后):
C、接⼝
 1、概念
接⼝可以理解为⼀种特殊的类,由全局常量和公共的抽象⽅法所组成。

也可理解为⼀个特殊的抽象类,因为它含有抽象⽅法。

如果说类是⼀种具体实现体,⽽接⼝定义了某⼀批类所需要遵守的规范,接⼝不关⼼这些类的内部数据,也不关⼼这些类⾥⽅法的实现细节,它只规定这些类⾥必须提供的某些⽅法。

(这⾥与抽象类相似)
 2.接⼝定义的基本语法
[修饰符] [abstract] interface 接⼝名 [extends⽗接⼝1,2....](多继承){
0…n常量 (public static final)
0…n 抽象⽅法(public abstract)
}
其中[ ]⾥的内容表⽰可选项,可以写也可以不写;接⼝中的属性都是常量,即使定义时不添加public static final 修饰符,系统也会⾃动加上;接⼝中的⽅法都是抽象⽅法,即使定义时不添加public abstract修饰符,系统也会⾃动加上。

 3.使⽤接⼝
⼀个类可以实现⼀个或多个接⼝,实现接⼝使⽤implements关键字。

java中⼀个类只能继承⼀个⽗类,是不够灵活的,通过实现多个接⼝可以补充。

继承⽗类实现接⼝的语法为:
[修饰符] class 类名 extends ⽗类 implements 接⼝1,接⼝2...{
类体部分//如果继承了抽象类,需要实现继承的抽象⽅法;要实现接⼝中的抽象⽅法
}
注意:如果要继承⽗类,继承⽗类必须在实现接⼝之前,即extends关键字必须在implements关键字前
补充说明:通常我们在命名⼀个接⼝时,经常以I开头,⽤来区分普通的类。

如:IPlayGame
以下我们来补充在上述抽象类中的例⼦,我们之前已经定义了⼀个抽象类Telephone和⼦类Phone,这⾥我们再创建⼀个IPlayGame的接⼝,然后在原来定义的两个类稍作修改,代码如下:
1 public interface IPlayGame {
2 public void paly();//abstract 关键字可以省略,系统会⾃动加上
3 public String name="游戏名字";//static final关键字可以省略,系统会⾃动加上
4 }
1 public class Phone extends Telephone implements IPlayGame{
2
3 public void call() {//继承抽象类的⼦类必须重写抽象⽅法
4 // TODO Auto-generated method stub
5 System.out.println("我重写了抽象类的⽅法");
6 }
7
8 @Override
9 public void paly() {
10 // TODO Auto-generated method stub
11 System.out.println("我重写了接⼝的⽅法");
12 }
13
14 }
1 public class train {
2
3
4 public static void main(String[] args) {
5 // TODO Auto-generated method stub
6 IPlayGame i=new Phone();//⽤接⼝的引⽤指向⼦类的对象
7 i.paly();//调⽤接⼝的⽅法
8 System.out.println();//输出接⼝的常量
9
10
11
13 }
 运⾏结果:
 4.接⼝和匿名内部类配合使⽤
接⼝在使⽤过程中还经常和匿名内部类配合使⽤。

匿名内部类就是没有没名字的内部类,多⽤于关注实现⽽不关注实现类的名称。

语法格式:
1 Interface i =new interface(){
2 Public void method{
3 System.out.println(“利⽤匿名内部类实现接⼝1”);
4 }
5 };
6 i.method();
还有⼀种写法:(直接把⽅法的调⽤写在匿名内部类的最后)
1 Interface i =new interface(){
2 Public void method{
3 System.out.println(“利⽤匿名内部类实现接⼝1”);
4 }
5 }.method();
四、抽象类和接⼝的区别
我们在多态的学习过程中认识到抽象类和接⼝都是实现java多态特性的关键部分,两者都包含抽象⽅法,只关注⽅法的声明⽽不关注⽅法的具体实现,那么这两者⼜有什么区别呢??我们在编写java程序的时候⼜该如何抉择呢?
参考博⽂:
(1)语法层⾯上的区别
 1.⼀个类只能继承⼀个抽象类,⽽⼀个类却可以实现多个接⼝。

 2.抽象类中的成员变量可以是各种类型的,⽽接⼝中的成员变量只能是public static final类型的;且必须给其初值,所以实现类中不能重新定义,也不能改变其值;抽象类中的变量默认是 friendly 型,其值可以在⼦类中重新定义,也可以重新赋值。

 3.抽象类中可以有⾮抽象⽅法,接⼝中则不能有⾮抽象⽅法。

 4.接⼝可以省略abstract 关键字,抽象类不能。

 5.接⼝中不能含有静态代码块以及静态⽅法,⽽抽象类可以有静态代码块和静态⽅法;
(2)设计层⾯上的区别
1)抽象类是对⼀种事物的抽象,即对类抽象,⽽接⼝是对⾏为的抽象。

抽象类是对整个类整体进⾏抽象,包括属性、⾏为,但是接⼝却是对类局部(⾏为)进⾏抽象。

举个简单的例⼦,飞机和鸟是不同类的事物,但是它们都有⼀个共性,就是都会飞。

那么在设计的时候,可以将飞机设计为⼀个类Airplane,将鸟设计为⼀个类Bird,但是不能将飞⾏这个特性也设计为类,因此它只是⼀个⾏为特性,并不是对⼀类事物的抽象描述。

此时可以将飞⾏设计为⼀个接⼝Fly,包含⽅法fly( ),然后Airplane和Bird分别根据⾃⼰的需要实现Fly这个接⼝。

然后⾄于有不同种类的飞机,⽐如战⽃机、民⽤飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。

从这⾥可以看出,继承是⼀个 "是不是"的关系,⽽接⼝实现则是 "有没有"的关系。

如果⼀个类继承了某个抽象类,则⼦类必定是抽象类的种类,⽽接⼝实现则是有没有、具备不具备的关系,⽐如鸟是否能飞(或者是否具备飞⾏这个特点),能飞⾏则可以实现这个接⼝,不能飞⾏就不实现这个接⼝。

2)设计层⾯不同,抽象类作为很多⼦类的⽗类,它是⼀种模板式设计。

⽽接⼝是⼀种⾏为规范,它是⼀种辐射式设计。

什么是模板式设计?最简单例⼦,⼤家都⽤过ppt⾥⾯的模板,如果⽤模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进⾏改动。

⽽辐射式设计,⽐如某个电梯都装了某种报警器,⼀旦要更新报警器,就必须全部更新。

也就是说对于抽象类,如果需要添加新的⽅法,可以直接在抽象类中添加具体的实现,⼦类可以不进⾏变更;⽽对于接⼝则不⾏,如果接⼝进⾏了变更,则所有实现这个接⼝的类都必须进⾏相应的改动。

下⾯看⼀个⽹上流传最⼴泛的例⼦:门和警报的例⼦:门都有open( )和close( )两个动作,此时我们可以定义通过抽象类和接⼝来定义这个抽象概念:
1 abstract class Door {
2 public abstract void open();
3 public abstract void close();
4 }
或者:
1 interface Door {
2 public abstract void open();
3 public abstract void close();
但是现在如果我们需要门具有报警alarm( )的功能,那么该如何实现?下⾯提供两种思路:
1)将这三个功能都放在抽象类⾥⾯,但是这样⼀来所有继承于这个抽象类的⼦类都具备了报警功能,但是有的门并不⼀定具备报警功能;
2)将这三个功能都放在接⼝⾥⾯,需要⽤到报警功能的类就需要实现这个接⼝中的open( )和close( ),也许这个类根本就不具备open( )和close( )这两个功能,⽐如⽕灾报警器。

从这⾥可以看出, Door的open() 、close()和alarm()根本就属于两个不同范畴内的⾏为,open()和close()属于门本⾝固有的⾏为特性,⽽alarm()属于延伸的附加⾏为。

因此最好的解决办法是单独将报警设计为⼀个接⼝,包含alarm()⾏为,Door设计为单独的⼀个抽象类,包含open和close两种⾏为。

再设计⼀个报警门继承Door类和实现Alarm接⼝。

1 interface Alram {
2 void alarm();
3 }
4
5 abstract class Door {
6 void open();
7 void close();
8 }
9
10 class AlarmDoor extends Door implements Alarm {
11 void oepn() {
12 //....
13 }
14 void close() {
15 //....
16 }
17 void alarm() {
18 //....
19 }
20 }
终于写完了这篇博⽂,java的这三⼤特性是⾯向对象编程的核⼼和基础,这⾥的概念的理解对实际编程有⾮常⼤的帮助,可能内容过于繁冗了,如果对内容有疑问请在下⾯留⾔或者私信我的邮箱,谢谢!!。

相关文档
最新文档