java面向对象基础(中)
## 本章学习目标
- 理解封装的意义
- 知道public、protected、缺省、private几种权限修饰符的区别
- 知道标准Javabean的要求
- 掌握对象数组的声明、初始化、遍历
- 掌握增强for循环的使用
- 理解继承的意义
- 掌握用extends实现子类继承父类
- 知道Java子类继承父类的要求
- 掌握方法重写的概念和要求
- 掌握toString、hashCode和equals方法的重写
- 掌握关键字final的使用
- 了解native关键字
- 掌握用abstract声明抽象类和抽象方法
- 掌握用interface声明接口
- 掌握用implements实现接口
- 掌握接口与抽象类的区别
## 6.1 封装
### 6.1.1 封装概述
为什么需要封装?
* 我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
* 我们使用的电脑,内部有CPU、硬盘、键盘、鼠标等等,每一个部件通过某种连接方式一起工作,但是各个部件之间又是独立的。
* 现实生活中,每一个个体与个体之间是有边界的,每一个团体与团体之间是有边界的,而同一个个体、团体内部的信息是互通的,只是对外有所隐瞒。
面向对象编程语言是对客观世界的模拟,客观世界里每一个事物的内部信息都是隐藏在对象内部的,外界无法直接操作和修改,只能通过指定的方式进行访问和修改。封装可以被认为是一个保护屏障,防止该类的代码和数据被其他类随意访问。适当的封装可以让代码更容易理解与维护,也加强了代码的安全性。
随着我们系统越来越复杂,类会越来越多,那么类之间的访问边界必须把握好,面向对象的开发原则要遵循“高内聚、低耦合”,而“高内聚,低耦合”的体现之一:
* 高内聚:类的内部数据操作细节自己完成,不允许外部干涉;
* 低耦合:仅对外暴露少量的方法用于使用
隐藏对象内部的复杂性,只对外公开简单和可控的访问方式,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
```java
package com.atguigu.oop.encapsulation;
public class Circle {
private double radius;
public void setRadius(double radius) {
if(radius < 0){
return;
}
this.radius = radius;
}
public double getRadius() {
return radius;
}
}
```
```java
package com.atguigu.oop.encapsulation;
public class TestCircle {
public static void main(String[] args) {
double r1 = -1.5;
double r2 = 2.5;
Circle c1 = new Circle();
Circle c2 = new Circle();
// c1.radius = r1;
// c2.radius = r2;
/*
if(r1>0){
c1.radius = r1;
}
if(r2 > 0){
c2.radius = r2;
}*/
c1.setRadius(r1);
c2.setRadius(r2);
System.out.println(c1.getRadius());
System.out.println(c2.getRadius());
}
}
```
### 6.1.2 几种权限修饰符
如何实现封装呢?实现封装就是指控制类或成员的可见性范围,这就需要依赖访问控制修饰符(也称为权限修饰符)来控制。
| 修饰符 | 本类 | 本包 | 其他包子类 | 其他包非子类 |
| --------- | ---- | ---- | ---------- | ------------ |
| private | √ | × | × | × |
| 缺省 | √ | √ | × | × |
| protected | √ | √ | √ | × |
| public | √ | √ | √ | √ |
外部类:public和缺省
成员变量、成员方法、构造器、成员内部类:public,protected,缺省,private
### 6.1.3 成员变量/属性私有化问题
**<span style="color:red">成员变量(field)私有化</span>之后,提供标准的<span style="color:red">get/set</span>方法,我们把这种成员变量也称为<span style="color:red">属性(property)</span>。**
> 或者可以说只要能通过get/set操作的就是事物的属性,哪怕它没有对应的成员变量。
1、成员变量封装的目的
* 隐藏类的实现细节
* 让使用者只能通过事先预定的方法来访问数据,从而可以在该方法里面加入控制逻辑,限制对成员变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。
* 便于修改,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。
2、实现步骤
(1)使用 `private` 修饰成员变量
```java
private 数据类型 变量名 ;
```
代码如下:
```java
public class Person {
private String name;
private int age;
private boolean marry;
}
```
(2)提供 `getXxx`方法 / `setXxx` 方法,可以访问成员变量,代码如下:
静态变量的get/set方法也静态的,当局部变量与静态变量重名时,使用“类名.静态变量”进行区分。
```java
public class Person {
private String name;
private int age;
private boolean marry;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setMarry(boolean marry){
this.marry = marry;
}
public boolean isMarry(){
return marry;
}
}
```
IDEA自动生成get/set方法模板
- 大部分键盘模式按Alt + Insert键。
- 部分键盘模式需要按Alt + Insert + Fn键。
- Mac电脑快捷键需要单独设置
![image-20211229171605642](images/image-20211229171605642.png)
![image-20211229171757032](images/image-20211229171757032.png)
(3)测试
```java
package com.atguigu.encapsulation;
public class TestPerson {
public static void main(String[] args) {
Person p = new Person();
//实例变量私有化,跨类是无法直接使用的
/* p.name = "张三";
p.age = 23;
p.marry = true;*/
p.setName("张三");
System.out.println("p.name = " + p.getName());
p.setAge(23);
System.out.println("p.age = " + p.getAge());
p.setMarry(true);
System.out.println("p.marry = " + p.isMarry());
}
}
```
### 6.1.4 标准JavaBean
`JavaBean` 是 Java语言编写类的一种标准规范。符合`JavaBean` 的类,要求:
(1)类必须是公共的和具体的(非抽象的,关于抽象请看6.5小节)
(2)成员变量私有化,并提供用来操作成员变量的`set` 和`get` 方法
(3)必须有无参构造
(4)提供有参构造(可选)
(5)建议重写toString方法、equals和hashCode方法等(关于重写请看6.3小节)
```java
public class ClassName{
//成员变量
//构造方法
//无参构造方法【必须】
//有参构造方法【建议】
//getXxx()
//setXxx()
//其他成员方法
}
```
编写符合`JavaBean` 规范的类,以学生类为例,标准代码如下:
```java
public class Student {
// 成员变量
private String name;
private int age;
// 构造方法
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// get/set成员方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
//其他成员方法列表
public String getInfo(){
return "姓名:" + name + ",年龄:" + age;
}
}
```
测试类,代码如下:
```java
public class TestStudent {
public static void main(String[] args) {
// 无参构造使用
Student s = new Student();
s.setName("柳岩");
s.setAge(18);
System.out.println(s.getName() + "---" + s.getAge());
System.out.println(s.getInfo());
// 带参构造使用
Student s2 = new Student("赵丽颖", 18);
System.out.println(s2.getName() + "---" + s2.getAge());
System.out.println(s2.getInfo());
}
}
```
## 6.2 对象数组
数组是用来存储一组数据的容器,一组基本数据类型的数据可以用数组装,那么一组对象也可以使用数组来装。
即数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组。
> 注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。
### 6.2.1 对象数组的声明和使用
案例:
(1)定义矩形类,包含长、宽属性,area()求面积方法,perimeter()求周长方法,String getInfo()返回圆对象的详细信息的方法
(2)在测试类中创建长度为5的Rectangle[]数组,用来装3个矩形对象,并给3个矩形对象的长分别赋值为10,20,30,宽分别赋值为5,15,25,遍历输出
```java
package com.atguigu.test08.array;
public class Rectangle {
double length;
double width;
double area(){//面积
return length * width;
}
double perimeter(){//周长
return 2 * (length + width);
}
String getInfo(){
return "长:" + length +
",宽:" + width +
",面积:" + area() + //直接调用本类的另一个实例方法
",周长:" + perimeter();
}
}
```
```java
package com.atguigu.test08.array;
public class ObjectArrayTest {
public static void main(String[] args) {
//声明并创建一个长度为3的矩形对象数组
Rectangle[] array = new Rectangle[3];
//创建3个矩形对象,并为对象的实例变量赋值,
//3个矩形对象的长分别是10,20,30
//3个矩形对象的宽分别是5,15,25
//调用矩形对象的getInfo()返回对象信息后输出
for (int i = 0; i < array.length; i++) {
//创建矩形对象
array[i] = new Rectangle();
//为矩形对象的成员变量赋值
array[i].length = (i+1) * 10;
array[i].width = (2*i+1) * 5;
//获取并输出对象对象的信息
System.out.println(array[i].getInfo());
}
}
}
```
### 6.2.2 对象数组的内存图分析
对象数组中数组元素存储的是元素对象的首地址。
![image-20211228153827819](images/image-20211228153827819-17028727226681.png)
### 6.2.3 增强for循环
增强for循环是一种语法糖,即在遍历数组中,表面上是一种新语法,但是编译后仍然是我们学过的普通for循环。
```java
for(元素类型 元素名 : 数组名){
}
```
注意:增强for循环只能用于查看元素,或修改元素属性值,但不能替换元素。增强for循环遍历数组是没有下标的。
```java
package com.atguigu.test08.array;
public class TestForeach {
public static void main(String[] args) {
//声明并创建一个长度为3的矩形对象数组
Rectangle[] array = new Rectangle[3];
array[0] = new Rectangle(10,5);
array[1] = new Rectangle(20,15);
array[2] = new Rectangle(30,25);
for(Rectangle r : array){
System.out.println(r.getInfo());
}
}
}
```
普通for循环与增强for循环在遍历数组时的区别:
| | 普通for循环 | 增强for循环 |
| ------------------------ | ------------ | ----------- |
| 是否需要指定下标信息 | 是 | 否 |
| 是否可以替换元素 | 是 | 否 |
| 是否可以遍历数组部分元素 | 是 | 否 |
| 遍历查看元素信息时 | 稍微复杂一点 | 更简洁 |
## 6.3 继承
### 6.3.1 继承的概述
1、生活中的继承
* 财产:富二代
* 样貌:如图所示:
<img src="images/继承2.jpg" style="zoom:50%;" />
继承有延续(下一代延续上一代的基因、财富)、扩展(下一代和上一代又有所不同)的意思。
社会的进步就是源于知识、财富、经验得以继承,又可以不断的翻新。
2、Java中的继承
如图所示:
<img src="images/猫狗继承1.jpg" style="zoom:50%;" />
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类中无需再定义这些属性和行为,只需要和抽取出来的类构成某种关系。如图所示:
<img src="images/猫狗继承2.jpg" style="zoom: 50%;" />
其中,多个类可以称为**子类**,也叫**派生类**;多个类抽取出来的这个类称为**父类**、**超类(superclass)**或者**基类**。
继承描述的是事物之间的所属关系,这种关系是:`is-a` 的关系。例如,图中猫属于动物,狗也属于动物。可见,父类更通用或更一般,子类更具体。我们通过继承,可以使多种事物之间形成一种关系体系。
3、继承的好处
* 提高**代码的复用性**。
* 提高**代码的扩展性**。
* 表示类与类之间的is-a关系
### 6.3.2 继承的语法格式
通过 `extends` 关键字,可以声明一个子类继承另外一个父类,定义格式如下:
```java
【修饰符】 class 父类 {
...
}
【修饰符】 class 子类 extends 父类 {
...
}
```
1、父类
```java
package com.atguigu.inherited.grammar;
/*
* 定义动物类Animal,做为父类
*/
public class Animal {
// 定义name属性
String name;
// 定义age属性
int age;
// 定义动物的吃东西方法
public void eat() {
System.out.println(age + "岁的" + name + "在吃东西");
}
}
```
2、子类
```java
package com.atguigu.inherited.grammar;
/*
* 定义猫类Cat 继承 动物类Animal
*/
public class Cat extends Animal {
int count;//记录每只猫抓的老鼠数量
// 定义一个猫抓老鼠的方法catchMouse
public void catchMouse() {
count++;
System.out.println("抓老鼠,已经抓了" + count + "只老鼠");
}
}
```
3、测试类
```java
package com.atguigu.inherited.grammar;
public class TestCat {
public static void main(String[] args) {
// 创建一个猫类对象
Cat cat = new Cat();
// 为该猫类对象的name属性进行赋值
cat.name = "Tom";
// 为该猫类对象的age属性进行赋值
cat.age = 2;
// 调用该猫继承来的eat()方法
cat.eat();
// 调用该猫的catchMouse()方法
cat.catchMouse();
cat.catchMouse();
cat.catchMouse();
//调用该猫的eat()方法
cat.eat();
}
}
```
### 6.3.3 IDEA中如何查看继承关系
1、子类和父类是一种相对的概念
例如:B类对于A来说是子类,但是对于C类来说是父类
2、查看继承关系快捷键
例如:选择A类名,按Ctrl + H就会显示A类的继承树。
![image-20211230090701383](images/image-20211230090701383.png):A类的父类和子类
![image-20211230090719430](images/image-20211230090719430.png):A类的父类
![image-20211230090732532](images/image-20211230090732532.png):A类的所有子类
例如:在类继承目录树中选中某个类,比如C类,按Ctrl+ Alt+U就会用图形化方式显示C类的继承祖宗
<img src="images/image-20211229180113255.png" alt="image-20211229180113255" style="zoom: 67%;" />
### 6.3.4 继承的特点
1、每一个类有一个默认的父类java.lang.Object类,它也是所有类的根父类
<img src="images/image-20220616110817804.png" alt="image-20220616110817804" style="zoom: 67%;" />
2、子类会继承父类所有的实例变量和实例方法
从类的定义来看,类是一类具有相同特性的事物的抽象描述。父类是所有子类共同特征的抽象描述。而实例变量和实例方法就是事物的特征,那么父类中声明的实例变量和实例方法代表子类事物也有这个特征。
- 当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存。
- 当子类对象调用方法时,编译器会先在子类模板中看该类是否有这个方法,如果没找到,会看它的父类甚至父类的父类是否声明了这个方法,遵循从下往上找的顺序,找到了就停止,一直到根父类都没有找到,就会报编译错误。
==所以继承意味着子类的对象除了看子类的类模板还要看父类的类模板。==
![image-20211230090255997](images/image-20211230090255997.png)
3、Java只支持单继承,不支持多重继承
```java
public class A{}
class B extends A{}
//一个类只能有一个父类,不可以有多个直接父类。
class C extends B{} //ok
class C extends A,B... //error
```
4、Java支持多层继承(继承体系)
```java
class A{}
class B extends A{}
class C extends B{}
```
> 顶层父类是Object类。所有的类默认继承Object,作为父类。
5、一个父类可以同时拥有多个子类
```java
class A{}
class B extends A{}
class D extends A{}
class E extends A{}
```
### 6.3.5 继承时权限修饰符限制问题
权限修饰符:public,protected,缺省,private
| 修饰符 | 本类 | 本包(包含子类和非子类) | 其他包子类 | 其他包非子类 |
| --------- | ---- | ------------------------- | ----------------------------------------- | ------------ |
| private | √ | × | × | × |
| 缺省 | √ | √(本包子类非子类都可见) | × | × |
| protected | √ | √(本包子类非子类都可见) | √(其他包仅限于子类中可见,直接使用方式) | × |
| public | √ | √ | √ | √ |
外部类:public和缺省
成员变量、成员方法等:public,protected,缺省,private
1、外部类要跨包使用必须是public,否则仅限于本包使用
(1)外部类的权限修饰符如果缺省,本包使用没问题
![image-20211230093627763](images/image-20211230093627763.png)
(2)外部类的权限修饰符如果缺省,跨包使用有问题
![image-20211230094236974](images/image-20211230094236974.png)
2、父类成员变量私有化(private)
子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。如图所示:
![](images/继承私有成员1.jpg)
父类代码:
```java
package com.atguigu.inherited.modifier;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo(){
return "姓名:" + name + ",年龄:" + age;
}
}
```
子类代码:
```java
package com.atguigu.inherited.modifier;
public class Student extends Person {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getInfo(){
// return "姓名:" + name + ",年龄:" + age;
//在子类中不能直接使用父类私有的name和age
return "姓名:" + getName() + ",年龄:" + getAge();
}
}
```
测试类代码:
```java
package com.atguigu.inherited.modifier;
public class TestStudent {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
student.setAge(23);
student.setScore(89);
System.out.println(student.getInfo());
}
}
```
IDEA在Debug模式下查看学生对象信息:
![image-20211230101938382](images/image-20211230101938382.png)
3、成员的权限修饰符问题
(1)本包下使用:成员的权限修饰符可以是public、protected、缺省
![image-20211230095320646](images/image-20211230095320646.png)
(2)跨包使用时,如果类的权限修饰符缺省,成员权限修饰符>类的权限修饰符也没有意义
![image-20211230100219840](images/image-20211230100219840.png)
(3)跨包下使用:要求严格
![image-20211230095817784](images/image-20211230095817784.png)
注意:跨包时父类中protected修饰的成员,仅限于在子类中访问,且是子类对象自己访问。
### 6.3.6 继承时构造器问题
子类继承父类时,不会继承父类的构造器。必须通过super()或super(实参列表)的方式调用父类的构造器。
- super();:子类构造器中一定会调用父类的构造器,默认调用父类的无参构造,super();可以省略。
- super(实参列表);:如果父类没有无参构造或者有无参构造但是子类就是想要调用父类的有参构造,则必须使用super(实参列表);的语句。
- super()和super(实参列表)都只能出现在子类构造器的首行。
```java
package com.atguigu.constructor;
public class Employee {
private String name;
private int age;
private double salary;
public Employee() {
System.out.println("父类Employee无参构造");
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
System.out.println("父类Employee有参构造");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getInfo(){
return "姓名:" + name + ",年龄:" + age +",薪资:" + salary;
}
}
```
```java
package com.atguigu.constructor;
public class Manager extends Employee{
private double bonusRate;
public Manager() {
super();//可以省略
}
public Manager(String name, int age, double salary, double bonusRate) {
super(name, age, salary);//调用父类的有参构造
this.bonusRate = bonusRate;
}
public double getBonusRate() {
return bonusRate;
}
public void setBonusRate(double bonusRate) {
this.bonusRate = bonusRate;
}
public String getInfo() {
return "姓名:" + getName() + ",年龄:" + getAge() +",薪资:" + getSalary() +",奖金比例:" + bonusRate;
}
}
```
![image-20211231112340813](images/image-20211231112340813.png)
```java
package com.atguigu.constructor;
public class TestEmployee {
public static void main(String[] args) {
Manager m1 = new Manager();
System.out.println(m1.getInfo());
Manager m2 = new Manager("张三",23,20000,0.1);
System.out.println(m2.getInfo());
}
}
```
形式一:
```java
class A{
}
class B extends A{
}
class Test1{
public static void main(String[] args){
B b = new B();
//A类和B类都是默认有一个无参构造,B类的默认无参构造中还会默认调用A类的默认无参构造
//但是因为都是默认的,没有打印语句,看不出来
}
}
```
形式二:
```java
class A{
A(){
System.out.println("A类无参构造器");
}
}
class B extends A{
}
class Test2{
public static void main(String[] args){
B b = new B();
//A类显示声明一个无参构造,
//B类默认有一个无参构造,
//B类的默认无参构造中会默认调用A类的无参构造
//可以看到会输出“A类无参构造器"
}
}
```
形式三:
```java
class A{
A(){
System.out.println("A类无参构造器");
}
}
class B extends A{
B(){
System.out.println("B类无参构造器");
}
}
class Test3{
public static void main(String[] args){
B b = new B();
//A类显示声明一个无参构造,
//B类显示声明一个无参构造,
//B类的无参构造中虽然没有写super(),但是仍然会默认调用A类的无参构造
//可以看到会输出“A类无参构造器"和"B类无参构造器")
}
}
```
形式四:
```java
class A{
A(){
System.out.println("A类无参构造器");
}
}
class B extends A{
B(){
super();
System.out.println("B类无参构造器");
}
}
class Test4{
public static void main(String[] args){
B b = new B();
//A类显示声明一个无参构造,
//B类显示声明一个无参构造,
//B类的无参构造中明确写了super(),表示调用A类的无参构造
//可以看到会输出“A类无参构造器"和"B类无参构造器")
}
}
```
形式五:
```java
class A{
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(){
System.out.println("B类无参构造器");
}
}
class Test5{
public static void main(String[] args){
B b = new B();
//A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
//B类显示声明一个无参构造,
//B类的无参构造没有写super(...),表示默认调用A类的无参构造
//编译报错,因为A类没有无参构造
}
}
```
![image-20200227141228450](images/image-20200227141228450.png)
![image-20200227141051954](images/image-20200227141051954.png)
形式六:
```java
class A{
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(){
super();
System.out.println("B类无参构造器");
}
}
class Test6{
public static void main(String[] args){
B b = new B();
//A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
//B类显示声明一个无参构造,
//B类的无参构造明确写super(),表示调用A类的无参构造
//编译报错,因为A类没有无参构造
}
}
```
![image-20200303183542807](images/image-20200303183542807.png)
形式七:
```java
class A{
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(int a){
super(a);
System.out.println("B类有参构造器");
}
}
class Test7{
public static void main(String[] args){
B b = new B(10);
//A类显示声明一个有参构造,没有写无参构造,那么A类就没有无参构造了
//B类显示声明一个有参构造,
//B类的有参构造明确写super(a),表示调用A类的有参构造
//会打印“A类有参构造器"和"B类有参构造器"
}
}
```
形式八:
```java
class A{
A(){
System.out.println("A类无参构造器");
}
A(int a){
System.out.println("A类有参构造器");
}
}
class B extends A{
B(){
super();//可以省略,调用父类的无参构造
System.out.println("B类无参构造器");
}
B(int a){
super(a);//调用父类有参构造
System.out.println("B类有参构造器");
}
}
class Test8{
public static void main(String[] args){
B b1 = new B();
B b2 = new B(10);
}
}
```
### 6.3.7 方法重写(Override)
我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)
1、方法重写
```java
package com.atguigu.inherited.override;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo(){
return "姓名:" + name + ",年龄:" + age;
}
}
```
```java
package com.atguigu.inherited.override;
public class Student extends Person {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
//方法的重写
public String getInfo(){
// return "姓名:" + name + ",年龄:" + age;
//在子类中不能直接使用父类私有的name和age
return "姓名:" + getName() + ",年龄:" + getAge() + ",成绩:" + score;
}
}
```
```java
package com.atguigu.inherited.override;
public class TestStudent {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
student.setAge(23);
student.setScore(89);
System.out.println(student.getInfo());
}
}
```
2、在子类中如何调用父类被重写的方法
在子类中可以通过super关键字调用父类被重写的方法
```java
super.被重写方法(【实参列表】)
```
示例代码:
```java
package com.atguigu.inherited.override;
public class Student extends Person {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
//方法的重写
public String getInfo(){
// return "姓名:" + name + ",年龄:" + age;
//在子类中不能直接使用父类私有的name和age
// return "姓名:" + getName() + ",年龄:" + getAge() + ",成绩:" + score;
return super.getInfo() + ",成绩:" + score;
}
}
```
3、IDEA重写方法快捷键:Ctrl + O
![image-20211230104547719](images/image-20211230104547719.png)
```java
package com.atguigu.inherited.override;
public class Student extends Person {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String getInfo() {
return super.getInfo() +",成绩:" + score;
}
}
```
> @Override:写在方法上面,用来检测是不是满足重写方法的要求。这个注解就算不写,只要满足要求,也是正确的方法覆盖重写。建议保留,这样编译器可以帮助我们检查格式,另外也可以让阅读源代码的程序员清晰的知道这是一个重写的方法。
4、重写方法的要求
(1)必须保证父子类之间重写方法的名称相同。
(2)必须保证父子类之间重写方法的参数列表也完全相同。
(3)子类方法的返回值类型必须【小于等于】父类方法的返回值类型(小于其实就是是它的子类,例如:Student < Person)。
> 注意:如果返回值类型是基本数据类型和void,那么必须是相同
(4)子类方法的权限必须【大于等于】父类方法的权限修饰符。
> 注意:public > protected > 缺省 > private
>
> 父类私有方法不能重写
>
> 跨包的父类缺省的方法也不能重写
5、方法的重载和方法的重写
方法的重载:方法名相同,形参列表不同。不看返回值类型。
方法的重写:见上面。
(1)同一个类中
```java
package com.atguigu.inherited.method;
public class TestOverload {
public int max(int a, int b){
return a > b ? a : b;
}
public double max(double a, double b){
return a > b ? a : b;
}
public int max(int a, int b,int c){
return max(max(a,b),c);
}
}
```
(2)父子类中
```java
package com.atguigu.inherited.method;
public class TestOverloadOverride {
public static void main(String[] args) {
Son s = new Son();
s.method(1);//只有一个形式的method方法
Daughter d = new Daughter();
d.method(1);
d.method(1,2);//有两个形式的method方法
}
}
class Father{
public void method(int i){
System.out.println("Father.method");
}
}
class Son extends Father{
public void method(int i){//重写
System.out.println("Son.method");
}
}
class Daughter extends Father{
public void method(int i,int j){//重载
System.out.println("Daughter.method");
}
}
```
### 6.3.8 Object根父类
#### 1、Object根父类
**API(Application Programming Interface)**,应用程序编程接口。Java API是一本程序员的`字典` ,是JDK中提供给我们使用的类的说明文档。所以我们可以通过查询API的方式,来学习Java提供的类,并得知如何使用它们。在API文档中是无法得知这些类具体是如何实现的,如果要查看具体实现代码,那么我们需要查看**src源码**。
类 `java.lang.Object`是类层次结构的根类,即所有类的父类。每个类都使用 `Object` 作为超类。所有对象(包括数组)都实现这个类的方法。
* 如果一个类没有特别指定父类,那么默认则继承自Object类。例如:
```java
public class MyClass /*extends Object*/ {
// ...
}
```
- 所有对象(包括数组)都实现这个类的方法。而且很多方法子类都会重写,通过子类对象调用方法后执行的是重写后的方法。
#### 2、Object类的方法
Object类有11个方法,先介绍如下3个:
(1)public String toString():建议子类重写。没有重写的话,默认返回的是 对象的运行时类型 @ 对象的哈希值的十六进制值。所有对象的toString方法,在打印对象时,或者对象与字符串进行拼接时,都会自动调用。
(2)public boolean equals(Object obj):用于比较两个对象是否“相等”。
- 绝对相等:同一个对象,地址值完全相同。没有重写,继承Object里面的equals,默认就是比较地址值,等价于==比较。
- 相对相等(逻辑相等):比较两个对象的内容。这个时候需要重写。核心类库中大部分类都重写了equals方法,例如:String类。
(3)public int hashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表的性能。 暂时可以认为它相当于这个对象的身份证号码。如果设计的好,那么对象的哈希值相同的概率就会降低。y=f(x)。不同的x,都可能得到相同的y。后面学习哈希表时,再来演示哈希值真正的作用是什么。
```java
package com.atguigu.object;
import java.util.Objects;
public class Student{
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
//重写hashCode,最快捷的方式 Alt + Insert
@Override
public boolean equals(Object o) {
if (this == o) {//如果当前对象的地址值与o对象的地址值相同,直接返回true
return true;
}
//(1)非空对象与null比较
//(2)getClass() != o.getClass()
// this.getClass() != o.getClass() 两个对象的类型不同
if (o == null || getClass() != o.getClass()){
return false;
}
//向下转型
//这里为什么没有instanceof判断呢?
//因为如果o对象的运行时类型不是Student类型,上一个if就回去了
//this对象的类型肯定是Student类型。
Student student = (Student) o;
//为什么要向下转型?
//如果不向下转型,o的编译时类型是Object,只能调用Object里面定义的成员。
//必须向下转型,才能调用o对象的Student类声明的成员
return score == student.score && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, score);
//根据某个规则,把对象的所有属性值 合起来算出一个int值
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
```
#### 3、native关键字
native:本地的,原生的
native只能修饰方法,表示这个方法的方法体代码不是用Java语言实现的,而是由C/C++语言编写的。但是对于Java程序员来说,可以当做Java的方法一样去正常调用它,或者子类重写它。
![image-20240731152128788](images/image-20240731152128788.png)
### 6.3.9 final关键字
final:最终的,不可更改的
final修饰类:表示这个类不能被继承,没有子类
final修饰方法:表示这个方法不能被子类重写
final修饰某个变量(成员变量或局部变量),表示它的值就不能被修改,称为常量。
- 其中static final的常量名建议使用大写字母,其余的常量名通常和变量名的命名规范一样。
> 如果某个成员变量用final修饰后,没有set方法,并且必须初始化(可以显式赋值、或在初始化块赋值、实例变量还可以在构造器中赋值)
```java
final class Eunuch{//太监类
}
class Son extends Eunuch{//错误
}
```
```java
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//错误
System.out.println("son");
}
}
```
```java
package com.atguigu.keyword.finals;
public class TestFinal {
public static void main(String[] args){
final int min = 0;
final int max = 100;
MyDate m1 = new MyDate();
System.out.println(m1.getInfo());
MyDate m2 = new MyDate(2022,2,14);
System.out.println(m2.getInfo());
System.out.println(Math.PI)
}
}
class MyDate{
//没有set方法,必须有显示赋值的代码
private final int year;
private final int month;
private final int day;
public MyDate(){
year = 1970;
month = 1;
day = 1;
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public int getMonth() {
return month;
}
public int getDay() {
return day;
}
public String getInfo(){
return year + "年" + month + "月" + day + "日";
}
}
```
## 6.4 抽象类
### 6.4.1 由来
抽象:即不具体、或无法具体
例如:当我们声明一个几何图形类:圆、矩形、三角形类等,发现这些类都有共同特征:求面积、求周长、获取图形详细信息。那么这些共同特征应该抽取到一个公共父类中。但是这些方法在父类中又**无法给出具体的实现**,而是应该交给子类各自具体实现。那么父类在声明这些方法时,**就只有方法签名,没有方法体**,我们把没有方法体的方法称为**抽象方法**。Java语法规定,包含抽象方法的类必须是**抽象类**。
### 6.4.2 语法格式
* **抽象方法**:被abstract修饰没有方法体的方法。
* **抽象类**:被abstract修饰的类。
抽象类的语法格式
```java
【权限修饰符】 abstract class 类名{
}
【权限修饰符】 abstract class 类名 extends 父类{
}
```
抽象方法的语法格式
```java
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
```
> 注意:抽象方法没有方法体
代码举例:
```java
public abstract class Animal {
public abstract void eat();
}
```
```java
public class Cat extends Animal {
public void run (){
System.out.println("小猫吃鱼和猫粮");
}
}
```
```java
public class CatTest {
public static void main(String[] args) {
// 创建子类对象
Cat c = new Cat();
// 调用eat方法
c.eat();
}
}
```
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做**实现方法**。
### 6.4.3 注意事项
关于抽象类的使用,以下为语法上要注意的细节,虽然条目较多,但若理解了抽象的本质,无需死记硬背。
1. 抽象类**不能创建对象**,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
> 理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
2. 抽象类中,也有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
> 理解:子类的构造方法中,有默认的super()或手动的super(实参列表),需要访问父类构造方法。
3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
> 理解:未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。
4. 抽象类的子类,必须重写抽象父类中**所有的**抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
> 理解:假设不重写所有抽象方法,则类中可能包含抽象方法。那么创建对象后,调用抽象的方法,没有意义。
## 6.5 接口
### 6.5.1 为什么要使用接口?
多态的使用前提必须是“继承”。而类继承有如下问题:
(1)类继承有单继承限制
(2)类继承表示的是事物之间is-a的关系,但是is-a的关系要求太严格了。
为了解决这两个问题,引入了接口,接口支持:
(1)多实现
(2)实现类和接口是is-like-a关。只要A类想要B接口声明的行为功能,就可以让A类实现B接口,不用考虑逻辑关系。
```java
Bird is a Animal. 鸟是一种动物。
Plane is not a Animal. 飞机不是一种动物。
Plane is a Vehicle. 飞机是一种交通工具。
Bird is like a Flyable。 鸟具有飞的能力。或 鸟会飞。
Plane is like a Flyable。 飞机具有飞的功能。或飞机会飞。
is-a解决的是:是不是的问题
is-like-a解决的是:要不要的问题
```
生活中的USB接口等思想,也是接口的思想。
<img src="images/image-20220616183122869.png" alt="image-20220616183122869" style="zoom:50%;" />
### 6.5.2 定义和使用格式
接口的定义,它与定义类方式相似,但是使用 `interface` 关键字。它也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
> 引用数据类型:数组,类,枚举,接口,注解。
1、接口的声明格式
```java
【修饰符】 interface 接口名{
//接口的成员列表:
// 公共的静态常量
// 公共的抽象方法
// 公共的默认方法(JDK1.8以上)
// 公共的静态方法(JDK1.8以上)
// 私有方法(JDK1.9以上)
}
```
2、接口的成员说明
接口定义的是多个类共同的公共行为规范,这些行为规范是与外部交流的通道,这就意味着接口里通常是定义一组公共方法。
在JDK8之前,接口中只允许出现:
(1)公共的静态的常量:其中public static final可以省略
(2)公共的抽象的方法:其中public abstract可以省略
> 理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
在JDK1.8时,接口中允许声明默认方法和静态方法:
(3)公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
(4)公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
在JDK1.9时,接口又增加了:
(5)私有方法:其中private不可以省略
(6)除此之外,接口中不能有其他成员,没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化。
3、示例代码
```java
package com.atguigu.interfacetype;
public interface Flyable {
long MAX_SPEED = 299792458;//光速:299792458米/秒, 省略public static final
void fly();//省略public abstract
static void start(){//省略public
System.out.println("start");
}
default void end(){//省略public
System.out.println("end");
}
private void show(){
System.out.println("cool!");
}
}
```
4、其他说明
为什么接口中只能声明公共的静态的常量?(面试题)
因为接口是标准规范,那么在规范中需要声明一些底线边界值,当实现者在实现这些规范时,不能去随意修改和触碰这些底线,否则就有“危险”。
例如:USB1.0规范中规定最大传输速率是1.5Mbps,最大输出电流是5V/500mA
USB3.0规范中规定最大传输速率是5Gbps(500MB/s),最大输出电流是5V/900mA
例如:尚硅谷学生行为规范中规定学员,早上8:25之前进班,晚上21:30之后离开等等。
为什么JDK1.8之后要允许接口定义静态方法和默认方法呢?因为它违反了接口作为一个抽象标准定义的概念。
**静态方法**:因为之前的标准类库设计中,有很多Collection/Colletions或者Path/Paths这样成对的接口和类,后面的类中都是静态方法,而这些静态方法都是为前面的接口服务的,那么这样设计一对API,不如把静态方法直接定义到接口中使用和维护更方便。
**默认方法**:(1)我们要在已有的老版接口中提供新方法时,如果添加抽象方法,就会涉及到原来使用这些接口的类就会有问题,那么为了保持与旧版本代码的兼容性,只能允许在接口中定义默认方法实现。比如:Java8中对Collection、List、Comparator等接口提供了丰富的默认方法。(2)当我们接口的某个抽象方法,在很多实现类中的实现代码是一样的,此时将这个抽象方法设计为默认方法更为合适,那么实现类就可以选择重写,也可以选择不重写。
为什么JDK1.9之后要允许接口定义私有的方法呢?
因为JDK1.8增加的静态方法和默认方法都是有方法体的,多个方法之间就可能存在“重复的冗余”代码,这些代码可以抽取出来“内部共用”。
### 6.5.3 接口的使用
#### 1、使用接口的静态成员
接口不能直接创建对象,但是可以通过接口名直接调用接口的静态方法和静态常量。
```java
package com.atguigu.interfacetype;
public class TestFlyable {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);//调用接口的静态常量
Flyable.start();//调用接口的静态方法
}
}
```
#### 2、类实现接口(implements)
接口**不能创建对象**,但是可以被类实现(`implements` ,类似于被继承)。
类与接口的关系为实现关系,即**类实现接口**,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类似继承,格式相仿,只是关键字不同,实现使用 ` implements`关键字。
```java
【修饰符】 class 实现类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口{
// 重写接口中抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
```
注意:
1. 如果接口的实现类是非抽象类,那么必须==重写接口中所有抽象方法==。
2. 默认方法可以选择保留,也可以重写。
> 重写时,default单词就不要再写了,它只用于在接口中表示默认方法,到类中就没有默认方法的概念了
3. **接口中的静态方法不能被继承也不能被重写**
示例代码:
```java
package com.atguigu.interfacetype;
public class Animal {
public void eat(){
System.out.println("吃东西");
}
}
```
```java
package com.atguigu.interfacetype;
public class Bird extends Animal implements Flyable{
//重写父接口的抽象方法,【必选】
@Override
public void fly() {
System.out.println("我要飞的更高~~~");
}
//重写父接口的默认方法,【可选】
@Override
public void end() {
System.out.println("轻轻落在树枝上~~~");
}
}
```
#### 3、使用接口的非静态方法
* 对于接口的静态方法,直接使用“接口名.”进行调用即可
* 也只能使用“接口名."进行调用,不能通过实现类的对象进行调用
* 对于接口的抽象方法、默认方法,只能通过实现类对象才可以调用
* 接口不能直接创建对象,只能创建实现类的对象
```java
package com.atguigu.interfacetype;
public class TestBirdFlyable {
public static void main(String[] args) {
Bird bird = new Bird();
Flyable.start();//调用接口的静态方法,只能通过 接口名.
//必须依赖于实现类的对象
bird.fly();//调用接口的抽象方法
bird.end();//调用接口的默认方法
bird.eat();//调用父类继承的方法
}
}
```
#### 4、接口的多实现(implements)
之前学过,在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接口的**多实现**。并且,一个类能继承一个父类,同时实现多个接口。
实现格式:
```java
【修饰符】 class 实现类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
【修饰符】 class 实现类 extends 父类 implements 接口1,接口2,接口3。。。{
// 重写接口中所有抽象方法【必须】,当然如果实现类是抽象类,那么可以不重写
// 重写接口中默认方法【可选】
}
```
> 接口中,有多个抽象方法时,实现类必须重写所有抽象方法。**如果抽象方法有重名的,只需要重写一次**。
定义多个接口:
```java
package com.atguigu.interfacetype;
public interface Jumpable {
void jump();
}
```
```java
package com.atguigu.interfacetype;
public interface Runnable {
void jump();
void run();
}
```
定义实现类:
```java
package com.atguigu.interfacetype;
public class Bird implements Flyable,Jumpable,Runnable{
//重写父接口的抽象方法,【必选】
@Override
public void fly() {
System.out.println("我要飞的更高~~~");
}
//重写父接口的默认方法,【可选】
@Override
public void end() {
System.out.println("轻轻落在树枝上~~~");
}
@Override
public void jump() {
System.out.println("我会跳跳~~~");
}
@Override
public void run() {
System.out.println("我会跑~~");
}
}
```
测试类
```java
package com.atguigu.interfacetype;
public class TestBird {
public static void main(String[] args) {
Bird bird = new Bird();
bird.fly();//调用Flyable接口的抽象方法
bird.jump();//调用Jumpable接口的抽象方法
bird.run();//调用Runnable接口的抽象方法
}
}
```
#### 5、接口的多继承 (extends)
一个接口能继承另一个或者多个接口,接口的继承也使用 `extends` 关键字,子接口继承父接口的方法。
定义父接口:
```java
package com.atguigu.interfacetype;
public interface A {
void a();
}
```
```java
package com.atguigu.interfacetype;
public interface B {
void b();
}
```
定义子接口:
```java
package com.atguigu.interfacetype;
public interface C extends A,B {
void c();
}
```
定义子接口的实现类:
```java
package com.atguigu.interfacetype;
public class D implements C {
@Override
public void c() {
System.out.println("重写C接口的抽象方法c");
}
@Override
public void a() {
System.out.println("重写C接口的抽象方法a");
}
@Override
public void b() {
System.out.println("重写C接口的抽象方法b");
}
}
```
>所有父接口的抽象方法都有重写。
>
>方法签名相同的抽象方法只需要实现一次。
### 6.5.4 接口的特点总结
- 接口本身不能创建对象,只能创建接口的实现类对象,接口类型的变量可以与实现类对象构成多态引用。
- 声明接口用interface,接口的成员声明有限制:(1)公共的静态常量(2)公共的抽象方法(3)公共的默认方法(4)公共的静态方法(5)私有方法(JDK1.9以上)
- 类可以实现接口,关键字是implements,而且支持多实现。如果实现类不是抽象类,就必须实现接口中所有的抽象方法。如果实现类既要继承父类又要实现父接口,那么继承(extends)在前,实现(implements)在后。
- 接口可以继承接口,关键字是extends,而且支持多继承。
- 接口的默认方法可以选择重写或不重写。如果有冲突问题,另行处理。子类重写父接口的默认方法,要去掉default,子接口重写父接口的默认方法,不要去掉default。
- 接口的静态方法不能被继承,也不能被重写。接口的静态方法只能通过“接口名.静态方法名”进行调用。
### 6.5.5 成员冲突问题
#### 1、默认方法两种冲突情况
(1)亲爹优先原则
当一个类,既继承一个父类,又实现若干个接口时,父类中的成员方法与接口中的抽象方法重名,子类就近选择执行父类的成员方法。代码如下:
定义接口:
```java
package com.atguigu.interfacetype;
public interface Friend {
default void date(){//约会
System.out.println("吃喝玩乐");
}
}
```
定义父类:
```java
package com.atguigu.interfacetype;
public class Father {
public void date(){//约会
System.out.println("爸爸约吃饭");
}
}
```
定义子类:
```java
package com.atguigu.interfacetype;
public class Son extends Father implements Friend {
@Override
public void date() {
//(1)不重写默认保留父类的
//(2)调用父类被重写的
// super.date();
//(3)保留父接口的
// Friend.super.date();
//(4)完全重写
System.out.println("学Java");
}
}
```
定义测试类:
```java
package com.atguigu.interfacetype;
public class TestSon {
public static void main(String[] args) {
Son s = new Son();
s.date();
}
}
```
(2)左右为难
- 当一个类同时实现了多个父接口,而多个父接口中包含方法签名相同的默认方法时,怎么办呢?
![](images/选择困难.jpg)
无论你多难抉择,最终都是要做出选择的。
声明接口:
```java
package com.atguigu.interfacetype;
public interface BoyFriend {
default void date(){//约会
System.out.println("神秘约会");
}
}
```
选择保留其中一个,通过“接口名.super.方法名"的方法选择保留哪个接口的默认方法。
```java
package com.atguigu.interfacetype;
public class Girl implements Friend,BoyFriend{
@Override
public void date() {
//(1)保留其中一个父接口的
// Friend.super.date();
// BoyFriend.super.date();
//(2)完全重写
System.out.println("学Java");
}
}
```
测试类
```java
package com.atguigu.interfacetype;
public class TestGirl {
public static void main(String[] args) {
Girl girl = new Girl();
girl.date();
}
}
```
- 当一个子接口同时继承了多个接口,而多个父接口中包含方法签名相同的默认方法时,怎么办呢?
另一个父接口:
```java
package com.atguigu.interfacetype;
public interface Usb2 {
//静态常量
long MAX_SPEED = 60*1024*1024;//60MB/s
//抽象方法
void in();
void out();
//默认方法
public default void start(){
System.out.println("开始");
}
public default void stop(){
System.out.println("结束");
}
//静态方法
public static void show(){
System.out.println("USB 2.0可以高速地进行读写操作");
}
}
```
子接口:
```java
package com.atguigu.interfacetype;
public interface Usb extends Usb2,Usb3 {
@Override
default void start() {
System.out.println("Usb.start");
}
@Override
default void stop() {
System.out.println("Usb.stop");
}
}
```
> 小贴士:
>
> 子接口重写默认方法时,default关键字可以保留。
>
> 子类重写默认方法时,default关键字不可以保留。
#### 2、变量冲突问题
- 当子类继承父类又实现父接口,而父类中存在与父接口常量同名的成员变量,并且该成员变量名在子类中仍然可见。
- 当子类同时继承多个父接口,而多个父接口存在相同同名常量。
此时在子类中想要引用父类或父接口的同名的常量或成员变量时,就会有冲突问题。
父类和父接口:
```java
package com.atguigu.interfacetype;
public class SuperClass {
int x = 1;
}
```
```java
package com.atguigu.interfacetype;
public interface SuperInterface {
int x = 2;
int y = 2;
}
```
```java
package com.atguigu.interfacetype;
public interface MotherInterface {
int x = 3;
}
```
子类:
```java
package com.atguigu.interfacetype;
public class SubClass extends SuperClass implements SuperInterface,MotherInterface {
public void method(){
// System.out.println("x = " + x);//模糊不清
System.out.println("super.x = " + super.x);
System.out.println("SuperInterface.x = " + SuperInterface.x);
System.out.println("MotherInterface.x = " + MotherInterface.x);
System.out.println("y = " + y);//没有重名问题,可以直接访问
}
}
```