-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Expand file tree
/
Copy pathencapsulation-inheritance-polymorphism.md
More file actions
1039 lines (753 loc) · 42.1 KB
/
encapsulation-inheritance-polymorphism.md
File metadata and controls
1039 lines (753 loc) · 42.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
---
title: 深入理解Java三大特性:封装、继承和多态
shortTitle: Java封装继承多态
description: 本文详细解析Java面向对象编程的三大核心特性:封装、继承和多态。文章将分别介绍这三个特性的概念、原理以及在实际开发中的应用场景,通过实例帮助读者更好地理解和掌握Java面向对象编程的基本技巧。
category:
- Java 核心
tag:
- 面向对象编程
head:
- - meta
- name: keywords
content: Java, 封装, 继承, 多态, 面向对象编程
---
在谈 Java 面向对象的时候,不得不提到面向对象的三大特征:[封装](https://javabetter.cn/oo/encapsulation.html)、[继承](https://javabetter.cn/oo/extends-bigsai.html)、[多态](https://javabetter.cn/oo/polymorphism.html)。三大特征紧密联系而又有区别,合理使用继承能大大减少重复代码,**提高代码复用性。**
### 1)封装
“三妹,准备好了没,我们来讲 Java 封装,算是 Java 的三大特征之一,理清楚了,对以后的编程有较大的帮助。”我对三妹说。
“好的,哥,准备好了。”三妹一边听我说,一边迅速地打开了 XMind,看来一边学习一边总结思维导图这个高效的学习方式三妹已经牢记在心了。
封装从字面上来理解就是包装的意思,专业点就是信息隐藏,**是指利用抽象将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体**。
数据被保护在类的内部,尽可能地隐藏内部的实现细节,只保留一些对外接口使之与外部发生联系。
其他对象只能通过已经授权的操作来与这个封装的对象进行交互。也就是说用户是无需知道对象内部的细节(当然也无从知道),但可以通过该对象对外的提供的接口来访问该对象。
使用封装有 4 大好处:
- 1、良好的封装能够减少耦合。
- 2、类内部的结构可以自由修改。
- 3、可以对成员进行更精确的控制。
- 4、隐藏信息,实现细节。
首先我们先来看两个类。
Husband.java
```java
public class Husband {
/*
* 对属性的封装
* 一个人的姓名、性别、年龄、妻子都是这个人的私有属性
*/
private String name ;
private String sex ;
private int age ;
private Wife wife;
/*
* setter()、getter()是该对象对外开发的接口
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setWife(Wife wife) {
this.wife = wife;
}
}
```
Wife.java
```java
public class Wife {
private String name;
private int age;
private String sex;
private Husband husband;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setAge(int age) {
this.age = age;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
public Husband getHusband() {
return husband;
}
}
```
可以看得出, Husband 类里面的 wife 属性是没有 `getter()`的,同时 Wife 类的 age 属性也是没有 `getter()`方法的。至于理由我想三妹你是懂的。
没有哪个女人愿意别人知道她的年龄。
所以封装把一个对象的属性私有化,同时提供一些可以被外界访问的属性的方法,如果不想被外界方法,我们大可不必提供方法给外界访问。
但是如果一个类没有提供给外界任何可以访问的方法,那么这个类也没有什么意义了。
比如我们将一个房子看做是一个对象,里面有漂亮的装饰,如沙发、电视剧、空调、茶桌等等都是该房子的私有属性,但是如果我们没有那些墙遮挡,是不是别人就会一览无余呢?没有一点儿隐私!
因为存在那个遮挡的墙,我们既能够有自己的隐私而且我们可以随意的更改里面的摆设而不会影响到外面的人。
但是如果没有门窗,一个包裹的严严实实的黑盒子,又有什么存在的意义呢?所以通过门窗别人也能够看到里面的风景。所以说门窗就是房子对象留给外界访问的接口。
通过这个我们还不能真正体会封装的好处。现在我们从程序的角度来分析封装带来的好处。如果我们不使用封装,那么该对象就没有 `setter()`和 `getter()`,那么 Husband 类应该这样写:
```java
public class Husband {
public String name ;
public String sex ;
public int age ;
public Wife wife;
}
```
我们应该这样来使用它:
```java
Husband husband = new Husband();
husband.age = 30;
husband.name = "张三";
husband.sex = "男"; //貌似有点儿多余
```
但是哪天如果我们需要修改 Husband,例如将 age 修改为 String 类型的呢?你只有一处使用了这个类还好,如果你有几十个甚至上百个这样地方,你是不是要改到崩溃。如果使用了封装,我们完全可以不需要做任何修改,只需要稍微改变下 Husband 类的 `setAge()`方法即可。
```java
public class Husband {
/*
* 对属性的封装
* 一个人的姓名、性别、年龄、妻子都是这个人的私有属性
*/
private String name ;
private String sex ;
private String age ; /* 改成 String类型的*/
private Wife wife;
public String getAge() {
return age;
}
public void setAge(int age) {
//转换即可
this.age = String.valueOf(age);
}
/** 省略其他属性的setter、getter **/
}
```
其他的地方依然这样引用( `husband.setAge(22)` )保持不变。
到了这里我们确实可以看出,**封装确实可以使我们更容易地修改类的内部实现,而无需修改使用了该类的代码**。
我们再看这个好处:**封装可以对成员变量进行更精确的控制**。
还是那个 Husband,一般来说我们在引用这个对象的时候是不容易出错的,但是有时你迷糊了,写成了这样:
```java
Husband husband = new Husband();
husband.age = 300;
```
也许你是因为粗心写成了这样,你发现了还好,如果没有发现那就麻烦大了,谁见过 300 岁的老妖怪啊!
但是使用封装我们就可以避免这个问题,我们对 age 的访问入口做一些控制(setter)如:
```java
public class Husband {
/*
* 对属性的封装
* 一个人的姓名、性别、年龄、妻子都是这个人的私有属性
*/
private String name ;
private String sex ;
private int age ; /* 改成 String类型的*/
private Wife wife;
public int getAge() {
return age;
}
public void setAge(int age) {
if(age > 120){
System.out.println("ERROR:error age input...."); //提示錯誤信息
}else{
this.age = age;
}
}
/** 省略其他属性的setter、getter **/
}
```
上面都是对 setter 方法的控制,其实通过封装我们也能够对对象的出口做出很好的控制。例如性别在数据库中一般都是以 1、0 的方式来存储的,但是在前台我们又不能展示 1、0,这里我们只需要在 `getter()`方法里面做一些转换即可。
```java
public String getSexName() {
if("0".equals(sex)){
sexName = "女";
}
else if("1".equals(sex)){
sexName = "男";
}
return sexName;
}
```
在使用的时候我们只需要使用 sexName 即可实现正确的性别显示。同理也可以用于针对不同的状态做出不同的操作。
```java
public String getCzHTML(){
if("1".equals(zt)){
czHTML = "<a href='javascript:void(0)' onclick='qy("+id+")'>启用</a>";
}
else{
czHTML = "<a href='javascript:void(0)' onclick='jy("+id+")'>禁用</a>";
}
return czHTML;
}
```
“好了,关于封装我们就暂时就聊这么多吧。”我喝了一口普洱茶后,对三妹说。
“好的,哥,我懂了。”
> 参考链接:[https://www.cnblogs.com/chenssy/p/3351835.html](https://www.cnblogs.com/chenssy/p/3351835.html),整理:沉默王二
### 2)继承
#### 01、什么是继承
**继承**(英语:inheritance)是面向对象软件技术中的一个概念。它使得**复用以前的代码非常容易。**
Java 语言是非常典型的面向对象的语言,在 Java 语言中**继承就是子类继承父类的属性和方法,使得子类对象(实例)具有父类的属性和方法,或子类从父类继承方法,使得子类具有父类相同的方法**。
我们来举个例子:动物有很多种,是一个比较大的概念。在动物的种类中,我们熟悉的有猫(Cat)、狗(Dog)等动物,它们都有动物的一般特征(比如能够吃东西,能够发出声音),不过又在细节上有区别(不同动物的吃的不同,叫声不一样)。
在 Java 语言中实现 Cat 和 Dog 等类的时候,就需要继承 Animal 这个类。继承之后 Cat、Dog 等具体动物类就是子类,Animal 类就是父类。

#### 02、为什么需要继承
三妹,你可能会问**为什么需要继承**?
如果仅仅只有两三个类,每个类的属性和方法很有限的情况下确实没必要实现继承,但事情并非如此,事实上一个系统中往往有很多个类并且有着很多相似之处,比如猫和狗同属动物,或者学生和老师同属人。各个类可能又有很多个相同的属性和方法,这样的话如果每个类都重新写不仅代码显得很乱,代码工作量也很大。
这时继承的优势就出来了:可以直接使用父类的属性和方法,自己也可以有自己新的属性和方法满足拓展,父类的方法如果自己有需求更改也可以重写。这样**使用继承不仅大大的减少了代码量,也使得代码结构更加清晰可见**。

所以这样从代码的层面上来看我们设计这个完整的 Animal 类是这样的:
```java
class Animal
{
public int id;
public String name;
public int age;
public int weight;
public Animal(int id, String name, int age, int weight) {
this.id = id;
this.name = name;
this.age = age;
this.weight = weight;
}
//这里省略get set方法
public void sayHello()
{
System.out.println("hello");
}
public void eat()
{
System.out.println("I'm eating");
}
public void sing()
{
System.out.println("sing");
}
}
```
而 Dog,Cat,Chicken 类可以这样设计:
```java
class Dog extends Animal//继承animal
{
public Dog(int id, String name, int age, int weight) {
super(id, name, age, weight);//调用父类构造方法
}
}
class Cat extends Animal{
public Cat(int id, String name, int age, int weight) {
super(id, name, age, weight);//调用父类构造方法
}
}
class Chicken extends Animal{
public Chicken(int id, String name, int age, int weight) {
super(id, name, age, weight);//调用父类构造方法
}
//鸡下蛋
public void layEggs()
{
System.out.println("我是老母鸡下蛋啦,咯哒咯!咯哒咯!");
}
}
```
各自的类继承 Animal 后可以直接使用 Animal 类的属性和方法而不需要重复编写,各个类如果有自己的方法也可很容易地拓展。
#### 03、继承的分类
继承分为单继承和多继承,Java 语言只支持类的单继承,但可以通过实现接口的方式达到多继承的目的。
##### 单继承

单继承,一个子类只有一个父类,如我们上面讲过的 Animal 类和它的子类。**单继承在类层次结构上比较清晰,但缺点是结构的丰富度有时不能满足使用需求**。
##### 多继承

多继承,一个子类有多个直接的父类。这样做的好处是子类拥有所有父类的特征,**子类的丰富度很高,但是缺点就是容易造成混乱**。下图为一个混乱的例子。

Java 虽然不支持多继承,但是 Java 有三种实现多继承效果的方式,**分别是**内部类、多层继承和实现接口。
[内部类](https://javabetter.cn/oo/inner-class.html)可以继承一个与外部类无关的类,保证了内部类的独立性,正是基于这一点,可以达到多继承的效果。
**多层继承:**子类继承父类,父类如果还继承其他的类,那么这就叫**多层继承**。这样子类就会拥有所有被继承类的属性和方法。

[实现接口](https://javabetter.cn/oo/interface.html)无疑是满足多继承使用需求的最好方式,一个类可以实现多个接口满足自己在丰富性和复杂环境的使用需求。
类和接口相比,**类就是一个实体,有属性和方法,而接口更倾向于一组方法**。举个例子,就拿斗罗大陆的唐三来看,他存在的继承关系可能是这样的:

#### 04、如何实现继承
##### **extends 关键字**
在 Java 中,类的继承是单一继承,也就是说一个子类只能拥有一个父类,所以**extends**只能继承一个类。其使用语法为:
```java
class 子类名 extends 父类名{}
```
例如 Dog 类继承 Animal 类,它是这样的:
```java
class Animal{} //定义Animal类
class Dog extends Animal{} //Dog类继承Animal类
```
子类继承父类后,就拥有父类的非私有的**属性和方法**。如果不明白,请看这个案例,在 IDEA 下创建一个项目,创建一个 test 类做测试,分别创建 Animal 类和 Dog 类,Animal 作为父类写一个 sayHello()方法,Dog 类继承 Animal 类之后就可以调用 sayHello()方法。具体代码为:
```java
class Animal {
public void sayHello()//父类的方法
{
System.out.println("hello,everybody");
}
}
class Dog extends Animal//继承animal
{ }
public class test {
public static void main(String[] args) {
Dog dog=new Dog();
dog.sayHello();
}
}
```
点击运行的时候 Dog 子类可以直接使用 Animal 父类的方法。

##### **implements 关键字**
使用 implements 关键字可以变相使 Java 拥有多继承的特性,使用范围为类实现接口的情况,一个类可以实现多个接口(接口与接口之间用逗号分开)。
我们来看一个案例,创建一个 test2 类做测试,分别创建 doA 接口和 doB 接口,doA 接口声明 sayHello()方法,doB 接口声明 eat()方法,创建 Cat2 类实现 doA 和 doB 接口,并且在类中需要重写 sayHello()方法和 eat()方法。具体代码为:
```java
interface doA{
void sayHello();
}
interface doB{
void eat();
//以下会报错 接口中的方法不能具体定义只能声明
//public void eat(){System.out.println("eating");}
}
class Cat2 implements doA,doB{
@Override//必须重写接口内的方法
public void sayHello() {
System.out.println("hello!");
}
@Override
public void eat() {
System.out.println("I'm eating");
}
}
public class test2 {
public static void main(String[] args) {
Cat2 cat=new Cat2();
cat.sayHello();
cat.eat();
}
}
```
Cat 类实现 doA 和 doB 接口的时候,需要实现其声明的方法,点击运行结果如下,这就是一个类实现接口的简单案例:

#### 05、继承的特点
继承的主要内容就是子类继承父类,并重写父类的方法。使用子类的属性或方法时候,首先要创建一个对象,而对象通过[构造方法](https://javabetter.cn/oo/construct.html)去创建,在构造方法中我们可能会调用子父类的一些属性和方法,所以就需要提前掌握 [this 和 super 关键字](https://javabetter.cn/oo/this-super.html)。
创建完这个对象之后,再调用**重写**父类后的方法,注意[重写和重载的区别](https://javabetter.cn/basic-extra-meal/override-overload.html)。
##### this 和 super 关键字
> [后面](https://javabetter.cn/oo/this-super.html)会详细讲,这里先来简单了解一下。
this 和 super 关键字是继承中**非常重要的知识点**,分别表示当前对象的引用和父类对象的引用,两者有很大相似又有一些区别。
**this 表示当前对象,是指向自己的引用。**
```java
this.属性 // 调用成员变量,要区别成员变量和局部变量
this.() // 调用本类的某个方法
this() // 表示调用本类构造方法
```
**super 表示父类对象,是指向父类的引用。**
```java
super.属性 // 表示父类对象中的成员变量
super.方法() // 表示父类对象中定义的方法
super() // 表示调用父类构造方法
```
##### 构造方法
[构造方法](https://javabetter.cn/oo/construct.html)是一种特殊的方法,**它是一个与类同名的方法**。在继承中**构造方法是一种比较特殊的方法**(比如不能继承),所以要了解和学习在继承中构造方法的规则和要求。
继承中的构造方法有以下几点需要注意:
**父类的构造方法不能被继承:**
因为构造方法语法是**与类同名**,而继承则不更改方法名,如果子类继承父类的构造方法,那明显与构造方法的语法冲突了。比如 Father 类的构造方法名为 Father(),Son 类如果继承 Father 类的构造方法 Father(),那就和构造方法定义:**构造方法与类同名**冲突了,所以在子类中不能继承父类的构造方法,但子类会调用父类的构造方法。
**子类的构造过程必须调用其父类的构造方法:**
Java 虚拟机**构造子类对象前会先构造父类对象,父类对象构造完成之后再来构造子类特有的属性,**这被称为**内存叠加**。而 Java 虚拟机构造父类对象会执行父类的构造方法,所以子类构造方法必须调用 super()即父类的构造方法。就比如一个简单的继承案例应该这么写:
```java
class A{
public String name;
public A() {//无参构造
}
public A (String name){//有参构造
}
}
class B extends A{
public B() {//无参构造
super();
}
public B(String name) {//有参构造
//super();
super(name);
}
}
```
**如果子类的构造方法中没有显示地调用父类构造方法,则系统默认调用父类无参数的构造方法。**
你可能有时候在写继承的时候子类并没有使用 super()调用,程序依然没问题,其实这样是为了节省代码,系统执行时会自动添加父类的无参构造方式,如果不信的话我们对上面的类稍作修改执行:

##### 方法重写(Override)
[方法重写](https://javabetter.cn/basic-extra-meal/Overriding.html)也就是子类中出现和父类中一模一样的方法(包括返回值类型,方法名,参数列表),它建立在继承的基础上。你可以理解为方法的**外壳不变,但是核心内容重写**。
在这里提供一个简单易懂的方法重写案例:
```java
class E1{
public void doA(int a){
System.out.println("这是父类的方法");
}
}
class E2 extends E1{
@Override
public void doA(int a) {
System.out.println("我重写父类方法,这是子类的方法");
}
}
```
其中`@Override` 注解显示声明该方法为注解方法,可以帮你检查重写方法的语法正确性,当然如果不加也是可以的,但建议加上。
##### 方法重载(Overload)
如果有两个方法的**方法名相同**,但参数不一致,那么可以说一个方法是另一个方法的[重载](https://javabetter.cn/basic-extra-meal/override-overload.html)。
重载可以通常理解为完成同一个事情的方法名相同,但是参数列表不同其他条件也可能不同。一个简单的方法重载的例子,类 E3 中的 add()方法就是一个重载方法。
```java
class E3{
public int add(int a,int b){
return a+b;
}
public double add(double a,double b) {
return a+b;
}
public int add(int a,int b,int c) {
return a+b+c;
}
}
```
#### 06、继承与修饰符
Java 修饰符的作用就是对类或类成员进行修饰或限制,每个修饰符都有自己的作用,而在继承中可能有些特殊修饰符使得被修饰的属性或方法不能被继承,或者继承需要一些其他的条件。
Java 语言提供了很多修饰符,修饰符用来定义类、方法或者变量,通常放在语句的最前端。主要分为以下两类:
- [访问权限修饰符](https://javabetter.cn/oo/access-control.html),也就是 public、private、protected 等
- 非访问修饰符,也就是 static、final、abstract 等
##### 访问修饰符
Java 子类重写继承的方法时,**不可以降低方法的访问权限**,**子类继承父类的访问修饰符作用域不能比父类小**,也就是更加开放,假如父类是 protected 修饰的,其子类只能是 protected 或者 public,绝对不能是 default(默认的访问范围)或者 private。所以在继承中需要重写的方法不能使用 private 修饰词修饰。
如果还是不太清楚可以看几个小案例就很容易搞懂,写一个 A1 类中用四种修饰词实现四个方法,用子类 A2 继承 A1,重写 A1 方法时候你就会发现父类私有方法不能重写,非私有方法重写使用的修饰符作用域不能变小(大于等于)。

正确的案例应该为:
```java
class A1 {
private void doA(){ }
void doB(){}//default
protected void doC(){}
public void doD(){}
}
class A2 extends A1{
@Override
public void doB() { }//继承子类重写的方法访问修饰符权限可扩大
@Override
protected void doC() { }//继承子类重写的方法访问修饰符权限可和父类一致
@Override
public void doD() { }//不可用protected或者default修饰
}
```
还要注意的是,**继承当中子类抛出的异常必须是父类抛出的异常或父类抛出异常的子异常**。下面的一个案例四种方法测试可以发现子类方法的异常不可大于父类对应方法抛出异常的范围。

正确的案例应该为:
```java
class B1{
public void doA() throws Exception{}
public void doB() throws Exception{}
public void doC() throws IOException{}
public void doD() throws IOException{}
}
class B2 extends B1{
//异常范围和父类可以一致
@Override
public void doA() throws Exception { }
//异常范围可以比父类更小
@Override
public void doB() throws IOException { }
//异常范围 不可以比父类范围更大
@Override
public void doC() throws IOException { }//不可抛出Exception等比IOException更大的异常
@Override
public void doD() throws IOException { }
}
```
##### 非访问修饰符
访问修饰符用来控制访问权限,而非访问修饰符每个都有各自的作用,下面针对 static、final、abstract 修饰符进行介绍。
[static 修饰符](https://javabetter.cn/oo/static.html)
static 翻译为“静态的”,能够与变量,方法和类一起使用,**称为静态变量,静态方法(也称为类变量、类方法)**。如果在一个类中使用 static 修饰变量或者方法的话,它们**可以直接通过类访问,不需要创建一个类的对象来访问成员。**
我们在设计类的时候可能会使用静态方法,有很多工具类比如`Math`,`Arrays`等类里面就写了很多静态方法。
可以看以下的案例证明上述规则:

源代码为:
```java
class C1{
public int a;
public C1(){}
// public static C1(){}// 构造方法不允许被声明为static
public static void doA() {}
public static void doB() {}
}
class C2 extends C1{
public static void doC()//静态方法中不存在当前对象,因而不能使用this和super。
{
//System.out.println(super.a);
}
public static void doA(){}//静态方法能被静态方法重写
// public void doB(){}//静态方法不能被非静态方法重写
}
```
[final 修饰符](https://javabetter.cn/oo/final.html)
final 变量:
- final 表示"最后的、最终的"含义,**变量一旦赋值后,不能被重新赋值**。被 final 修饰的实例变量必须显式指定初始值(即不能只声明)。final 修饰符通常和 static 修饰符一起使用来创建类常量。
final 方法:
- **父类中的 final 方法可以被子类继承,但是不能被子类重写**。声明 final 方法的主要目的是防止该方法的内容被修改。
final 类:
- **final 类不能被继承**,没有类能够继承 final 类的任何特性。
所以无论是变量、方法还是类被 final 修饰之后,都有代表最终、最后的意思。内容无法被修改。
[abstract 修饰符](https://javabetter.cn/oo/abstract.html)
abstract 英文名为“抽象的”,主要用来修饰类和方法,称为抽象类和抽象方法。
**抽象方法**:有很多不同类的方法是相似的,但是具体内容又不太一样,所以我们只能抽取他的声明,没有具体的方法体,即抽象方法可以表达概念但无法具体实现。
**抽象类**:**有抽象方法的类必须是抽象类**,抽象类可以表达概念但是无法构造实体的类。

比如我们可以这样设计一个 People 抽象类以及一个抽象方法,在子类中具体完成:
```java
abstract class People{
public abstract void sayHello();//抽象方法
}
class Chinese extends People{
@Override
public void sayHello() {//实现抽象方法
System.out.println("你好");
}
}
class Japanese extends People{
@Override
public void sayHello() {//实现抽象方法
System.out.println("口你七哇");
}
}
class American extends People{
@Override
public void sayHello() {//实现抽象方法
System.out.println("hello");
}
}
```
#### 07、Object 类和转型
提到 Java 继承,不得不提及所有类的根类:Object(java.lang.Object)类,如果一个类没有显式声明它的父类(即没有写 extends xx),那么默认这个类的父类就是 Object 类,任何类都可以使用 Object 类的方法,创建的类也可和 Object 进行向上、向下转型,所以 Object 类是掌握和理解继承所必须的知识点。
Java 向上和向下转型在 Java 中运用很多,也是建立在继承的基础上,所以 Java 转型也是掌握和理解继承所必须的知识点。
##### Object 类概述
1. Object 是类层次结构的**根类**,所有的类都隐式的继承自 Object 类。
2. Java 中,所有的对象都拥有 Object 的默认方法。
3. Object 类有一个[构造方法](https://javabetter.cn/oo/construct.html),并且是**无参构造方法**。
Object 是 Java 所有类的父类,是整个类继承结构的顶端,也是最抽象的一个类。
像 toString()、equals()、hashCode()、wait()、notify()、getClass()等都是 Object 的方法。你以后可能会经常碰到,但其中遇到更多的就是 toString()方法和 equals()方法,我们经常需要重写这两种方法满足我们的使用需求。
toString()方法表示返回该对象的字符串,由于各个对象构造不同所以需要重写,如果不重写的话默认返回`类名@hashCode`格式。
**如果重写 toString()方法后**直接调用 toString()方法就可以返回我们自定义的该类转成字符串类型的内容输出,而不需要每次都手动的拼凑成字符串内容输出,大大简化输出操作。
equals()方法主要比较两个对象是否相等,因为对象的相等不一定非要严格要求两个对象地址上的相同,有时内容上的相同我们就会认为它相等,比如 String 类就重写了 euqals()方法,通过[字符串的内容比较是否相等](https://javabetter.cn/string/equals.html)。

##### 向上转型
**向上转型** : 通过子类对象(小范围)实例化父类对象(大范围),这种属于自动转换。用一张图就能很好地表示向上转型的逻辑:

父类引用变量指向子类对象后,只能使用父类已声明的方法,但方法如果被重写会执行子类的方法,如果方法未被重写那么将执行父类的方法。
##### 向下转型
**向下转型** : 通过父类对象(大范围)实例化子类对象(小范围),在书写上父类对象需要加括号`()`强制转换为子类类型。但父类引用变量实际引用必须是子类对象才能成功转型,这里也用一张图就能很好表示向下转型的逻辑:

子类引用变量指向父类引用变量指向的对象后(一个 Son()对象),就完成向下转型,就可以调用一些子类特有而父类没有的方法 。
在这里写一个向上转型和向下转型的案例:
```java
Object object=new Integer(666);//向上转型
Integer i=(Integer)object;//向下转型Object->Integer,object的实质还是指向Integer
String str=(String)object;//错误的向下转型,虽然编译器不会报错但是运行会报错
```
#### 08、子父类初始化顺序
在 Java 继承中,父子类初始化先后顺序为:
1. 父类中静态成员变量和静态代码块
2. 子类中静态成员变量和静态代码块
3. 父类中普通成员变量和代码块,父类的构造方法
4. 子类中普通成员变量和代码块,子类的构造方法
总的来说,就是**静态>非静态,父类>子类,非构造方法>构造方法**。同一类别(例如普通变量和普通代码块)成员变量和代码块执行从前到后,需要注意逻辑。
这个也不难理解,静态变量也称类变量,可以看成一个全局变量,静态成员变量和静态代码块在类加载的时候就初始化,而非静态变量和代码块在对象创建的时候初始化。所以静态快于非静态初始化。
而在创建子类对象的时候需要先创建父类对象,所以父类优先于子类。
而在调用构造方法的时候,是对成员变量进行一些初始化操作,所以普通成员变量和代码块优于构造方法执行。
至于更深层次为什么这个顺序,就要更深入了解 JVM 执行流程啦。下面一个测试代码为:
```java
class Father{
public Father() {
System.out.println(++b1+"父类构造方法");
}//父类构造方法 第四
static int a1=0;//父类static 第一 注意顺序
static {
System.out.println(++a1+"父类static");
}
int b1=a1;//父类成员变量和代码块 第三
{
System.out.println(++b1+"父类代码块");
}
}
class Son extends Father{
public Son() {
System.out.println(++b2+"子类构造方法");
}//子类构造方法 第六
static {//子类static第二步
System.out.println(++a1+"子类static");
}
int b2=b1;//子类成员变量和代码块 第五
{
System.out.println(++b2 + "子类代码块");
}
}
public class test9 {
public static void main(String[] args) {
Son son=new Son();
}
}
```
执行结果:

### 3)多态
Java 的多态是指在面向对象编程中,同一个类的对象在不同情况下表现出来的不同行为和状态。
- 子类可以继承父类的字段和方法,子类对象可以直接使用父类中的方法和字段(私有的不行)。
- 子类可以重写从父类继承来的方法,使得子类对象调用这个方法时表现出不同的行为。
- 可以将子类对象赋给父类类型的引用,这样就可以通过父类类型的引用调用子类中重写的方法,实现多态。
多态的目的是为了提高代码的灵活性和可扩展性,使得代码更容易维护和扩展。
比如说,通过允许子类继承父类的方法并重写,增强了代码的复用性。
再比如说多态可以实现动态绑定,这意味着程序在运行时再确定对象的方法调用也不迟。
“光说理论很枯燥,我们再通过代码来具体地分析一下。”
#### 01、多态是什么
在我的印象里,西游记里的那段孙悟空和二郎神的精彩对战就能很好的解释“多态”这个词:一个孙悟空,能七十二变;一个二郎神,也能七十二变;他们都可以变成不同的形态,只需要悄悄地喊一声“变”。
Java 的多态是什么?其实就是一种能力——同一个行为具有不同的表现形式;换句话说就是,执行一段代码,Java 在运行时能根据对象类型的不同产生不同的结果。和孙悟空和二郎神都只需要喊一声“变”,然后就变了,并且每次变得还不一样;一个道理。
多态的前提条件有三个:
- 子类继承父类
- 子类重写父类的方法
- 父类引用指向子类的对象
多态的一个简单应用,来看程序清单 1-1:
```java
//子类继承父类
public class Wangxiaoer extends Wanger {
public void write() { // 子类重写父类方法
System.out.println("记住仇恨,表明我们要奋发图强的心智");
}
public static void main(String[] args) {
// 父类引用指向子类对象
Wanger[] wangers = { new Wanger(), new Wangxiaoer() };
for (Wanger wanger : wangers) {
// 对象是王二的时候输出:勿忘国耻
// 对象是王小二的时候输出:记住仇恨,表明我们要奋发图强的心智
wanger.write();
}
}
}
class Wanger {
public void write() {
System.out.println("勿忘国耻");
}
}
```
#### 02、多态与后期绑定
现在,我们来思考一个问题:程序清单 1-1 在执行 `wanger.write()` 时,由于编译器只有一个 Wanger 引用,它怎么知道究竟该调用父类 Wanger 的 `write()` 方法,还是子类 Wangxiaoer 的 `write()` 方法呢?
答案是在运行时根据对象的类型进行后期绑定,编译器在编译阶段并不知道对象的类型,但是 Java 的方法调用机制能找到正确的方法体,然后执行,得到正确的结果。
多态机制提供的一个重要的好处就是程序具有良好的扩展性。来看程序清单 2-1:
```java
//子类继承父类
public class Wangxiaoer extends Wanger {
public void write() { // 子类覆盖父类方法
System.out.println("记住仇恨,表明我们要奋发图强的心智");
}
public void eat() {
System.out.println("我不喜欢读书,我就喜欢吃");
}
public static void main(String[] args) {
// 父类引用指向子类对象
Wanger[] wangers = { new Wanger(), new Wangxiaoer() };
for (Wanger wanger : wangers) {
// 对象是王二的时候输出:勿忘国耻
// 对象是王小二的时候输出:记住仇恨,表明我们要奋发图强的心智
wanger.write();
}
}
}
class Wanger {
public void write() {
System.out.println("勿忘国耻");
}
public void read() {
System.out.println("每周读一本好书");
}
}
```
在程序清单 2-1 中,我们在 Wanger 类中增加了 `read()` 方法,在 Wangxiaoer 类中增加了 `eat()`方法,但这丝毫不会影响到 `write()` 方法的调用。
`write()` 方法忽略了周围代码发生的变化,依然正常运行。这让我想起了金庸《倚天屠龙记》里九阳真经的口诀:“他强由他强,清风拂山岗;他横由他横,明月照大江。”
多态的这个优秀的特性,让我们在修改代码的时候不必过于紧张,因为多态是一项让程序员“将改变的与未改变的分离开来”的重要特性。
#### 03、多态与构造方法
在构造方法中调用多态方法,会产生一个奇妙的结果,我们来看程序清单 3-1:
```java
public class Wangxiaosan extends Wangsan {
private int age = 3;
public Wangxiaosan(int age) {
this.age = age;
System.out.println("王小三的年龄:" + this.age);
}
public void write() { // 子类覆盖父类方法
System.out.println("我小三上幼儿园的年龄是:" + this.age);
}
public static void main(String[] args) {
new Wangxiaosan(4);
// 上幼儿园之前
// 我小三上幼儿园的年龄是:0
// 上幼儿园之后
// 王小三的年龄:4
}
}
class Wangsan {
Wangsan () {
System.out.println("上幼儿园之前");
write();
System.out.println("上幼儿园之后");
}
public void write() {
System.out.println("老子上幼儿园的年龄是3岁半");
}
}
```
从输出结果上看,是不是有点诧异?明明在创建 Wangxiaosan 对象的时候,年龄传递的是 4,但输出结果既不是“老子上幼儿园的年龄是 3 岁半”,也不是“我小三上幼儿园的年龄是:4”。
为什么?
因为在创建子类对象时,会先去调用父类的构造方法,而父类构造方法中又调用了被子类覆盖的多态方法,由于父类并不清楚子类对象中的字段值是什么,于是把 int 类型的属性暂时初始化为 0,然后再调用子类的构造方法(子类构造方法知道王小二的年龄是 4)。
#### 04、多态与向下转型
向下转型是指将父类引用强转为子类类型;这是不安全的,因为有的时候,父类引用指向的是父类对象,向下转型就会抛出 ClassCastException,表示类型转换失败;但如果父类引用指向的是子类对象,那么向下转型就是成功的。
来看程序清单 4-1:
```java
public class Wangxiaosi extends Wangsi {
public void write() {
System.out.println("记住仇恨,表明我们要奋发图强的心智");
}
public void eat() {
System.out.println("我不喜欢读书,我就喜欢吃");
}
public static void main(String[] args) {
Wangsi[] wangsis = { new Wangsi(), new Wangxiaosi() };
// wangsis[1]能够向下转型
((Wangxiaosi) wangsis[1]).write();