三大特性 | 我的日常分享

三大特性

三大特性

一、封装

1.1 封装的必要性:

在对象的外部为对象的属性赋值,可能存在非法数据的录入。

案例1:

在main函数中对age赋值时,赋的值为1000,在逻辑上没有问题,但是却不符合实际。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestEncapsulation{
public static void main(String[] args){
Student s1 = new Student();
s1.name = "tom";
s1.age = 1000;
s1.sex = "男";
s1.score = 100D;
}
}
class Student{
String name;
int age;
String sex;
double score;
}

1.2 什么是封装

  • 概念:尽可能隐藏对象的内部实现细节,控制对象的修改及访问的权限。
  • 访问修饰符:private(可将属性修饰为私有,仅本类可见)

案例2:使用private修饰的属性,无法直接通过属性名进行访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestEncapsulation{
public static void main(String[] args){
Student s1 = new Student();
s1.name = "tom";
s1.age = 1000;//编译错误
s1.sex = "男";
s1.score = 100D;
}
}
class Student{
String name;
private int age;
String sex;
double score;
}

运行结果:编译错误

image-20220203220701161

1.3 公共访问方法

当属性被private修饰时,无法直接通过属性名进行访问,可通过定义一个public修饰的公共方法对属性进行操作。

命名规范:

  • 赋值:setXXX();使用方法参数进行赋值
  • 取值:getXXX();使用方法返回值实现取值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestEncapsulation{
public static void main(String[] args){
Student s1 = new Student();
s1.name = "tom";
//s1.age = 1000;
s1.setAge(1000);
s1.sex = "男";
s1.score = 100D;
}
}
class Student{
String name;
private int age;
String sex;
double score;
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
}

问题:还是没有解决非法数据的录入。

1.4 过滤非法数据

我们可以在公共的访问方法内部,添加逻辑判断,进而过滤掉非法数据,以保证数据安全。

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
public class TestEncapsulation{
public static void main(String[] args){
Student s1 = new Student();
s1.name = "tom";
//s1.age = 1000;
s1.setAge(1000);
s1.sex = "男";
s1.score = 100D;
System.out.println("年龄为:"+s1.getAge());
}
}
class Student{
String name;
private int age;
String sex;
double score;
public void setAge(int age){
if(age>=0 && age <=120){
//符合范围则赋值
this.age = age;
}else{
//不符合返回则赋值为-1
this.age = -1;
}
}
public int getAge(){
return this.age;
}
}

运行结果:

当不符合范围时,

image-20220203221729298

当符合范围时,

image-20220203221751694

1.5 总结

image-20220203222156158

二、继承

继承就是子类继承父类的特性和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。

2.1 生活中的继承

​ 儿子继承父亲的遗产等等。

image-20220204133953547

兔子、牛、羊属于食草动物;狮子、老虎属于食肉动物。

食草动物与食肉动物都属于动物类。

所以继承需要符合的关系是:something is a xx,父类更通用,子类更具体。

2.2 父类的选择

  • 现实生活中,很多类别之间都存在继承关机,满足“is a”的关系。我们需要选择合适的父类。
  • 狗是一种动物,狗是一种生物,狗是一种物质。
  • 多个类别都可作为狗的父类,需要从中选择出最合适的父类

image-20220204134841960

  • 功能越精细,重合点越多,越接近直接父类
  • 功能越粗略,重合点越少,越接近Object类(万物皆对象的概念)

2.3 父类的抽象

可根据程序需要使用到的多个具体类,进行共性抽取,进而定义父类

image-20220204152358268

Dog.java狗狗类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Dog {
//品种
String breed;
//年龄
int age;
//性别
String sex;
//毛色
String furColor;

//吃
void eat(){
System.out.println("吃饭...");
}
//睡
public void sleep(){
System.out.println("睡觉...");
}
//跑
public void run(){
System.out.println("跑...");
}
}

Bird.java鸟类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Bird {
//品种
String breed;
//年龄
int age;
//性别
String sex;
//毛色
String furColor;

//吃
void eat(){
System.out.println("吃饭...");
}
//睡
public void sleep(){
System.out.println("睡觉...");
}
//飞
public void fly(){
System.out.println("飞...");
}
}

Fish.java鱼类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Fish {
//品种
String breed;
//年龄
int age;
//性别
String sex;

//吃
void eat(){
System.out.println("吃饭...");
}
//睡
public void sleep(){
System.out.println("睡觉...");
}
//游
public void swim(){
System.out.println("游...");
}
}

Snake.java蛇类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Snake {
//年龄
int age;
//性别
String sex;

//吃
void eat(){
System.out.println("吃饭...");
}
//睡
public void sleep(){
System.out.println("睡觉...");
}
//爬
public void crawl(){
System.out.println("爬...");
}
}

以上定义了狗狗类、鸟类、鱼类、蛇类,但是可以发现其中重复的代码有很多,这时我们可以想到复用。

抽取出父类Animal.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Animal {
//年龄
int age;
//性别
String sex;

//吃
void eat(){
System.out.println("吃饭...");
}
//睡
public void sleep(){
System.out.println("睡觉...");
}
}

其它四个动物类可修改为:

Dog.java狗狗类

1
2
3
4
5
6
7
8
9
10
11
public class Dog extends Animal{
//品种
String breed;
//毛色
String furColor;

//跑
public void run(){
System.out.println("跑...");
}
}

Bird.java鸟类

1
2
3
4
5
6
7
8
9
10
11
public class Bird extends Animal{
//品种
String breed;
//毛色
String furColor;

//飞
public void fly(){
System.out.println("飞...");
}
}

Fish.java鱼类

1
2
3
4
5
6
7
8
9
public class Fish extends Animal{
//品种
String breed;

//游
public void swim(){
System.out.println("游...");
}
}

Snake.java蛇类

1
2
3
4
5
6
public class Snake extends Animal{
//爬
public void crawl(){
System.out.println("爬...");
}
}

TestInheritance.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestInheritance {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.breed = "哈士奇";
dog1.age = 2;
dog1.furColor = "黑";
dog1.sex = "公";

dog1.eat();
dog1.run();
dog1.sleep();
}
}

运行结果:

虽然狗狗类中没有定义属性年龄、性别,方法吃、睡,但是由于继承了父类Animal,依然存在这些属性方法可以访问。

image-20220204152133515

2.4 继承的语法

1
2
3
4
5
class 父类 {
}

class 子类 extends 父类 {
}
  • 应用:产生继承关系之后,子类可以使用父类中的属性和方法,也可定义子类独有的属性和方法。
  • 好处:提高了代码的复用性和可扩展性。

2.5 继承的特点

在Java中继承为单继承一个类只能有一个直接父类,但是可以多级继承,属性和方法逐级叠加。

单继承

image-20220204153300956

1
2
3
4
5
6
public class A{

}
public class B extends A{

}

多重继承

image-20220204153359689

1
2
3
4
5
6
7
8
9
public class A{

}
public class B extends A{

}
public class C extends B{

}

不同类继承同一个类

image-20220204153550469

1
2
3
4
5
6
7
8
9
public class A{

}
public class B extends A{

}
public class C extends A{

}

多继承(不支持)

image-20220204154023435

1
2
3
4
5
6
7
8
9
public class A{

}
public class B{

}
public class C extends A,B{

}

需要注意的是 Java 不支持多继承,但支持多重继承。

  • 子类拥有父类非 private 的属性、方法。
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法。

2.6 不可继承

  • 构造方法:类中的构造方法,只负责创建本类对象,不可被继承。
  • private修饰的属性和方法:访问修饰符的一种,仅本类可见。
  • 父子类不在同一个package中时,default修饰的属性和方法不可被继承。

2.7 访问修饰符

本类 同包 非同包子类 其它
private X X X
default X X
protected X
public

下面案例以学生类Student.java示范

1
2
3
4
5
6
public class Student {
public String name;
protected int age;
String sex;
private double score;
}

案例1:被public修饰的属性或方法

  1. public修饰的属性在本类中可以被访问。
1
2
3
4
5
6
7
8
public class Student {
public String name;

public void testName(){
//在本类中可以访问public修饰的name属性
System.out.println(name+" "+this.name);
}
}
  1. public修饰的属性可以被同包(同包其它类)中被访问。

Student.java

1
2
3
4
package testModifier;
public class Student {
public String name;
}

Test1.java

1
2
3
4
5
6
7
8
9
package testModifier;
public class Test1 {
public static void main(String[] args) {
Student stu = new Student();
//public修饰的name可在同包其它类中被访问
stu.name = "小明";
System.out.println("stu.name="+stu.name);
}
}

运行结果:

image-20220204214308734

  1. public修饰的属性可在非同包子类中被访问

Student.java

1
2
3
4
package testModifier;
public class Student {
public String name;
}

S1.java

1
2
3
4
5
6
7
8
9
10
package demo4;
import testModifier.Student;

public class S1 extends Student {
//public修饰的name可在非同包子类中被访问
public void testName1(){
System.out.println(name+" "+this.name);
}
}

  1. public修饰的属性可在其它(非同包非子类)中被访问

Student.java

1
2
3
4
package testModifier;
public class Student {
public String name;
}

Test1.java

1
2
3
4
5
6
7
8
9
10
11
package demo4;

import testModifier.Student;
public class Test1 {
public static void main(String[] args) {
Student stu = new Student();
//public修饰的name可在非同包非子类中被访问
stu.name = "小明";
System.out.println("stu.name="+stu.name);
}
}

案例2:被protected修饰的属性或方法

  1. protected修饰的属性可在本类中被访问
1
2
3
4
5
6
7
8
public class Student {
protected int age;

public void testAge(){
//protected修饰的age属性可在本类中被访问
System.out.println(age+" "+this.age);
}
}
  1. protected修饰的属性可在同包(同包其它类)中被访问

Student.java

1
2
3
4
5
package testModifier;

public class Student {
protected int age;
}

Test1.java

1
2
3
4
5
6
7
8
9
10
11
package testModifier;

public class Test1 {
public static void main(String[] args) {
Student stu = new Student();
//protected修饰的age可在同包其它类中被访问
stu.age = 18;
System.out.println("stu.age="+stu.age);
}
}

运行结果:

image-20220204221609059

  1. protected修饰的属性可在非同包子类中被访问

Student.java

1
2
3
4
5
package testModifier;

public class Student {
protected int age;
}

S1.java

1
2
3
4
5
6
7
8
9
package demo4;

import testModifier.Student;
public class S1 extends Student {
//protected修饰的age可在非同包子类中被访问
public void testAge1(){
System.out.println(age+" "+this.age);
}
}
  1. protected修饰的属性可在非同包非子类中被访问

Student.java

1
2
3
4
5
package testModifier;

public class Student {
protected int age;
}

Test1.java

1
2
3
4
5
6
7
8
9
package demo4;

import testModifier.Student;
public class Test1 {
public static void main(String[] args) {
Student stu = new Student();
stu.age = 18;//protected修饰的age不可在非同包非子类中被访问
}
}

运行结果:编译报错

image-20220204222419871

案例3:被default修饰的属性或方法

属性或方法前不加任何修饰符,则为default修饰。

  1. default修饰的属性可在本类中被访问
1
2
3
4
5
6
7
8
public class Student {
String sex;

public void testSex(){
//default修饰的sex属性可在本类中被访问
System.out.println(sex+" "+this.sex);
}
}
  1. default修饰的属性可在同包(同包非子类或同包子类)中被访问

Student.java

1
2
3
4
5
package testModifier;

public class Student {
String sex;
}

Test1.java

1
2
3
4
5
6
7
8
9
10
package testModifier;

public class Test1 {
public static void main(String[] args) {
Student stu = new Student();
//default修饰的属性可在同包中被访问
stu.sex = "男";
System.out.println("stu.sex="+stu.sex);
}
}

运行结果:

image-20220204223545016

  1. default修饰的属性可在非同包子类中被访问

Student.java

1
2
3
4
5
package testModifier;

public class Student {
String sex;
}

S1.java

1
2
3
4
5
6
7
8
9
package demo4;
import testModifier.Student;

public class S1 extends Student {
//default修饰的属性不可在非同包子类中被访问
public void testSex1(){
System.out.println(sex+" "+this.sex);
}
}

运行结果:

image-20220204223940218

  1. default修饰的属性可在其它(非同包非子类)中被访问

Student.java

1
2
3
4
5
package testModifier;

public class Student {
String sex;
}

Test1.java

1
2
3
4
5
6
7
8
9
10
package demo4;
import testModifier.Student;

public class Test1 {
public static void main(String[] args) {
Student stu = new Student();
//default修饰的属性不可在非同包非子类中被访问
stu.sex = "男";
}
}

运行结果:

image-20220204224149167

案例4:被private修饰的属性或方法

  1. private修饰的属性可在本类中被访问
1
2
3
4
5
6
7
8
public class Student {
private double score;

public void testScore(){
//private修饰的score属性可在本类中被访问
System.out.println(score+" "+this.score);
}
}
  1. private修饰的属性可在同包(同包子类、同包非子类)中被访问

Student.java

1
2
3
4
5
package testModifier;

public class Student {
private double score;
}

S1.java

1
2
3
4
5
6
7
8
package testModifier;

public class S1 extends Student{
//private修饰的属性不可在同包子类中被访问
public void testScore1(){
System.out.println(score+" "+this.score);
}
}

运行结果:

image-20220204224807016

  1. private修饰的属性可在非同包子类中被访问

Student.java

1
2
3
4
5
package testModifier;

public class Student {
private double score;
}

S1.java

1
2
3
4
5
6
7
8
9
package demo4;
import testModifier.Student;

public class S1 extends Student {
//private修饰的属性不可在非同包子类中被访问
public void testScore1(){
System.out.println(score+" "+this.score);
}
}

运行结果:

image-20220204225146813

  1. private修饰的属性可在其它(非同包非子类)中被访问

Student.java

1
2
3
4
5
package testModifier;

public class Student {
private double score;
}

Test1.java

1
2
3
4
5
6
7
8
9
10
package demo4;
import testModifier.Student;

public class Test1 {
public static void main(String[] args) {
Student stu = new Student();
//private修饰的属性不可在非同包非子类中被访问
stu.score = 92.8D;
}
}

运行结果:

image-20220204225318271

观察表格,从public到private,权限逐渐严格,如果同包子类都无法访问则同包非子类也无法访问,如果非同包子类无法访问则非同包非子类也无法访问。即某一级别无法访问,则低于这一级别的都将无法访问。

2.8 继承的方法重写/覆盖(override)

当父类中的方法无法满足需求时,我们可在子类中定义与父类方法一致的方法,根据需要编写需求。

重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,抛出 IOException 异常或者 IOException 的子类异常。

  • 方法重写的规则:
    1. 方法名称、参数列表、返回值类型必须与父类相同
    2. 访问修饰符可与父类相同或比父类更宽泛。例如,父类方法为protected修饰,则子类重写的方法可为protected或public,不能为private或default

当子类重写父类方法后,调用时优先执行子类重写后的方法。

  • 注意事项:
    1. 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
    2. 父类的成员方法只能被它的子类重写。
    3. 声明为 final 的方法不能被重写。
    4. 声明为 static 的方法不能被重写,但是能够被再次声明。
    5. 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private (因为未被继承)和 final 的方法。
    6. 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。其中default与private原因是因为未被继承。
    7. 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
    8. 构造方法不能被重写。

案例1:

Animal.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Animal {
//年龄
int age;
//性别
String sex;

//吃
public void eat(){
System.out.println("吃饭...");
}
//睡
public void sleep(){
System.out.println("睡觉...");
}
}

Dog.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Dog extends Animal {
//品种
String breed;
//毛色
String furColor;

//跑
public void run(){
System.out.println("跑...");
}

@Override//@Override注解作用:检查方法是否符合重写规则,不符合则报错
public void eat() {
System.out.println("狗狗吃饭....");
}
}

TestDog.java

1
2
3
4
5
6
public class TestDog {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.eat();
}
}

运行结果:

执行的是子类中重写的eat方法。

image-20220205000252993

案例2:声明为 final 的方法不能被重写。

Animal.java

1
2
3
4
5
6
public class Animal {
//睡
public final void sleep(){
System.out.println("睡觉...");
}
}

Dog.java

1
2
3
4
5
6
public class Dog extends Animal {
@Override
public final void sleep(){
System.out.println("狗狗睡觉...");
}
}

运行结果:编译报错

image-20220205000824272

案例3:声明为 static 的方法不能被重写,但是能够被再次声明。

Animal.java

1
2
3
4
5
6
public class Animal {
//睡
public static void sleep(){
System.out.println("睡觉...");
}
}

Dog.java

1
2
3
4
5
6
public class Dog extends Animal {
@Override
public static void sleep(){
System.out.println("狗狗睡觉...");
}
}

编译报错:

image-20220205001622316

如果去掉@Override

Dog.java

1
2
3
4
5
6
public class Dog extends Animal {
//@Override
public static void sleep(){
System.out.println("狗狗睡觉...");
}
}

TestDog.java

1
2
3
4
5
public class TestDog {
public static void main(String[] args) {
Dog.sleep();
}
}

编译运行成功,且执行的是子类中重写定义的方法。

image-20220205001827801

案例4:子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。

Animal.java

1
2
3
4
5
6
public class Animal {
//呼吸
private void breath(){
System.out.println("呼吸...");
}
}

Dog.java

1
2
3
4
5
6
public class Dog extends Animal {
@Override
private void breath(){
System.out.println("狗狗呼吸...");
}
}

运行结果:

image-20220205002432437

如果去掉@Override,则编译运行成功,因为breath方法为private修饰,所以也不会被继承到子类中,去掉@Override相当于子类自己定义了自己的breath方法。

Dog.java

1
2
3
4
5
6
public class Dog extends Animal {
//@Override
private void breath(){
System.out.println("狗狗呼吸...");
}
}

案例5:子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

Animal.java

1
2
3
4
5
6
7
8
package demo5;

public class Animal {
//动
void move(){
System.out.println("动...");
}
}

Dog.java

1
2
3
4
5
6
7
8
9
10
package demo6;

import demo5.Animal;
public class Dog extends Animal {
@Override
void move(){

}
}

运行结果:

image-20220205003112499

这是因为,在非同包子类中default修饰的方法不会被继承。

只有被继承的方法才能被重写。

2.9 super关键字

  • super访问父类方法

当需要在子类中调用父类被重写方法时,this关键字则调用的是在子类中重写的方法,这时则需要使用super关键字调用。

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
class Animal{
public void eat(){
System.out.println("吃...");
}
public void move(){
System.out.println("动...");
}
public void breath(){
System.out.println("呼吸...");
}
}

class Dog extends Animal{
@Override
public void eat() {
//调用父类的eat方法
super.eat();
//使用this调用父类呼吸方法(因为父类的move方法被继承,子类中的与父类中的一致)
this.move();
//使用super调用父类呼吸方法
super.move();
System.out.println("狗狗吃饭...");
}
}
public class TestDog {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.eat();
}
}

运行结果:

image-20220205153234067

  • super访问父类属性

父子类的同名属性不存在重写的关系,两块空间同时存在,则子类遮蔽父类属性,需要使用不同的前缀进行访问。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Animal{
public String sex = "公";
public int age = 4;
}

class Dog extends Animal{
public String sex = "母";
public void test1(){
System.out.println("sex="+sex+" "+"this.sex="+this.sex+" "+"super.sex="+super.sex);
System.out.println("age="+age+" "+"this.age="+this.age+" "+"super.age="+super.age);
}
}
public class TestDog {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.test1();
}
}

运行结果:

image-20220205154130343

2.10 继承中对象的创建

  • 在具有继承关系的对象创建中,构建子类对象会先构建父类对象
  • 由父类的共性内容,叠加子类独有内容,组合成完整的子类对象。
1
2
3
4
5
6
7
8
9
class Father{
int a;
int b;
public void m1(){}
}
class Son extends Father{
int c;
public void m2(){}
}

子类Son拥有的属性方法有int a、int b、int c、m1()、m2()。

2.11 继承后的对象构建过程

构建子类对象时,先构建父类对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A{
A(){
System.out.println("A无参构造方法");
}
}
class B extends A{
B(){
System.out.println("B无参构造方法");
}
}
class C extends B{
C(){
System.out.println("C无参构造方法");
}
}
public class TestSuper {
public static void main(String[] args) {
C c1 = new C();
}
}

运行结果:

首先执行A的构造方法,再执行B的构造方法,然后再执行C的构造方法。

image-20220205161150373

2.12 super调用父类构造方法

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
class A{
int a;
int b;
A(){
System.out.println("A无参构造方法");
}
A(int a,int b){
this.a = a;
this.b = b;
}
}

class B extends A{
int c;
B(){
System.out.println("B无参构造方法");
}
B(int a,int b,int c){
this.a = a;
this.b = b;
this.c = c;
}
}

public class TestSuper {
public static void main(String[] args) {
B b1 = new B(10,20,30);
System.out.println(b1.a+" "+b1.b+" "+b1.c);
}
}

运行结果:

image-20220205162916744

类B中,构造方法B(int a,int b,int c)可修改为

1
2
3
4
B(int a,int b,int c){
super(a,b);
this.c = c;
}

运行结果:

A的无参构造方法不会被调用。

image-20220205163143413

案例1:

如果上面代码中B类中代码为这样

1
2
3
4
5
6
7
8
9
10
11
class B extends A{
int c;
B(){
System.out.println("B无参构造方法");
}
B(int a,int b,int c){
super(a,b);
this.a = 100;
this.c = c;
}
}

运行结果:

这时a的值为100,这是因为通过调用super将父类的a值赋值为10后,继承过来后,通过this.a=100;又将值设置为了100;

image-20220205163559583

案例2:如果B类中代码为这样

1
2
3
4
5
6
7
8
9
10
11
class B extends A{
int a = 999;
int c;
B(){
System.out.println("B无参构造方法");
}
B(int a,int b,int c){
super(a,b);
this.c = c;
}
}

运行结果:

这时a的值为999,这是因为通过调用super将父类的a值赋值为10后,继承过来后,重新声明属性a的默认值为999

image-20220205163810395

案例3:如果B类中代码为这样

1
2
3
4
5
6
7
8
9
10
11
12
class B extends A{
int a = 999;
int c;
B(){
System.out.println("B无参构造方法");
}
B(int a,int b,int c){
super(a,b);
this.a = 500;
this.c = c;
}
}

运行结果:

这时a的值为500,这是因为通过调用super将父类的a值赋值为10后,继承过来后,重新声明属性a的默认值为999,然后通过this.a = 500,又将值赋值为了500

image-20220205164004151

如果子类中构造方法没有手动调用super,则默认调用super()父类的无参构造方法。

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
package demo10;

/**
* @author :贾小宇
* @program: project
* @create :2022-02-05 16:06
* @description
*/
class A{
A(){
super();//默认会调用
System.out.println("A无参构造方法");
}
}

class B extends A{
B(){
super();//默认会调用
System.out.println("B无参构造方法");
}
B(int a){
System.out.println("B的带int参数 构造方法");
}
}

class C extends B{
C(){
//super();//默认会调用
super(10);//手动调用super后,将不会调用默认的super()
System.out.println("C无参构造方法");
}
}

public class TestSuper {
public static void main(String[] args) {
C c1 = new C();
}
}

运行结果:

由于C类中手动调用了B(int a)的构造方法,则不会调用默认的构造方法,在B类中调用的则是默认的构造方法,如果不写super(),仍然会调用父类的无参构造方法。

image-20220205164539052

super():表示调用父类无参构造方法,如果没有书写,则隐式存在于构造方法的首行。

this与super

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
class A{
A(){
super();//默认会调用
System.out.println("A无参构造方法");
}
}

class B extends A{
B(){
super();//默认会调用
System.out.println("B无参构造方法");
}
B(int a){
System.out.println("B的带int参数 构造方法");
}
}

class C extends B{
C(){
//super();//默认会调用
super(10);//手动调用super后,将不会调用默认的super()
System.out.println("C无参构造方法");
}
C(int a){
this();
System.out.println("C的带int有参构造方法");
}
}

public class TestSuper {
public static void main(String[] args) {
C c1 = new C(20);
}
}

运行结果:

在main函数中创建C对象时,调用的是C的带参构造方法,在C的带参构造方法中调用了this(),C的无参构造方法,在C的无参构造方法中,通过super(10),调用了父类B中的带参构造方法。

image-20220205165205073

注意:

  1. this或super使用在构造方法当中,都要求在首行,且只能使用一个。当子类构造方法中使用了this()或this(实参),即不可再同时书写super()或super(实参),这是因为this调用的本类构造方法中一定会调用一次父类中的构造方法,不允许重复调用构造方法。

  2. 如果子类构造方法中,没有书写super()或super(实参),则默认提供super()

  3. super关键字必须在构造方法的首行

  4. 同一个子类构造方法中,super、this不可同时存在

三、多态

3.1 生活中的多态

  • 现实中,比如我们按下F1键,在word下弹出的是word帮助,在windows下弹出的是Windows帮助和支持。同一个事件发生在不同的对象上会产生不同的结果。
  • 生活中,不同任务看待同一个对象的视角不同,关注点也不相同,比如小明在父母看了是儿子,在老师看来是学生,在同学朋友看来是同学朋友。

3.2 程序中的多态

父类引用指向子类对象,从而产生多种形态。

Animal a = new Dog();

  • 二者具有直接或间接的继承关系,父类引用可指向子类对象,即形成多态。
  • 父类引用仅可调用父类所声明的属性和方法,不能调用子类独有的属性和方法。

案例1:

Animal.java

1
2
3
4
5
6
7
8
9
10
11
public class Animal {
String sex;
int age;

public void breath(){
System.out.println("呼吸...");
}
public void eat(){
System.out.println("吃...");
}
}

Dog.java

1
2
3
4
5
6
7
8
9
10
11
public class Dog extends Animal{
String breed;
String color;

public void run(){
System.out.println("跑...");
}
public void bark(){
System.out.println("吠...");
}
}

TestPolymorphism.java

1
2
3
4
5
6
7
8
9
public class TestPolymorphism {
public static void main(String[] args) {
Animal a = new Dog();
a.age = 2;
a.sex = "公";
a.eat();
a.breath();
}
}

运行结果:

Animal a = new Dog();可以这样理解,1、Dog 也是 Animal 2、相当于类型转换小类型Dog向大类型Animal自动类型转换,比如short s=1;int n=s;在面向对象中叫向上转型

image-20220205213217615

仅能访问父类声明的sex、age属性和eat、breath方法,无法访问到Dog类独有的breed、color属性和run、bark方法。

image-20220205213023140

3.3 多态中方法重写/覆盖

  • 思考:如果子类中重写了父类中的方法,以父类类型引用调用此方法时,执行的是父类中的方法还是子类中的方法呢?

    答:如果子类重写了父类中的方法,以父类类型引用调用此方法时,执行的是子类中重写的方法,否则执行父类中的方法。

Animal.java

1
2
3
4
5
6
7
8
9
10
11
public class Animal {
String sex;
int age;

public void breath(){
System.out.println("呼吸...");
}
public void eat(){
System.out.println("吃...");
}
}

Bird.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Bird extends Animal{
public String color;

public void chirp(){
System.out.println("叫...");
}

@Override
public void eat() {
System.out.println("鸟吃...");
}
}

TestBird.java

1
2
3
4
5
6
7
8
9
public class TestBird {
public static void main(String[] args) {
Animal a = new Bird();
a.sex = "母";
a.age = 1;
a.breath();
a.eat();
}
}

运行结果:

通过Animal引用调用eat方法执行的是子类中重写的eat方法。

image-20220205215413295

思考:如果子类中存在于父类中一样的属性,以父类类型引用调用此属性时,执行的是父类中的属性还是子类中的属性呢?

答:执行的是父类中的属性。

Animal.java

1
2
3
4
5
6
7
8
9
10
11
public class Animal {
String sex;
int age;

public void breath(){
System.out.println("呼吸...");
}
public void eat(){
System.out.println("吃...");
}
}

Bird.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Bird extends Animal{
String sex = "abc";
public String color;

public void chirp(){
System.out.println("叫...");
}

@Override
public void eat() {
System.out.println("鸟吃...");
}
}

TestBird.java

1
2
3
4
5
6
7
8
public class TestBird {
public static void main(String[] args) {
Animal a = new Bird();
System.out.println(a.sex);
a.sex = "母";
System.out.println(a.sex);
}
}

运行结果:

通过运行结果开始为null而不是abc可以说明通过父类引用调用的属性,如果子类中存在相同的属性仍然调用的是父类的。

image-20220205215739098

3.4 向上转型(装箱)

  • 概念:当父类引用指向子类对象时,称为向上转型
1
2
父类类型 变量名 = new 子类类型(); 
//如:Animal a = new Dog();

案例1:

1
Animal a = (Animal) new Bird();

image-20220205222159821

翻译:把“new Bird0”换成“Animal”是多余的(Casting ‘new Bird0’ to ‘Animal’ is redundant )

向上转型后,仅可调用目标类型中所声明的属性和方法。

3.5 向下转型(拆箱)

  • 概念:将父类引用中的真实子类对象强转回子类本身类型,或父类类型向子类类型向下转换的过程,称为向下转型。

注意:只有向上转型后,才能向下转型。

1
2
子类类型 变量名 = (子类类型) 父类变量名; 
//如:Cat c =(Cat) a;

案例:只有向上转型后才能向下转型。

1
2
Bird b = new Bird();
Dog dog1 = (Dog) b;

image-20220205222956560

3.6 类型转换异常

1
2
3
4
5
6
7
8
9
10
11
public class TestTransf {
public static void main(String[] args) {
Animal a = new Dog();
a.breath();
Dog d = (Dog) a;
d.bark();
//转型类型不匹配
Bird b = (Bird) a;
b.chirp();
}
}

运行结果:

编译不会报错,运行抛出异常。

image-20220205223725573

解决方案:

向下转型时,如果父类引用中的子类对象类型与目标类型不匹配时,编译不会报错,当程序运行时,会抛出类型转换异常。

使用instanceof关键字可解决。

3.7 instanceof关键字

  • 作用:判断引用中的对象的真实类型。

  • 语法:

    1
    2
    父类引用 instanceof 类型
    //返回boolean类型结果

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class TestTransf {
public static void main(String[] args) {
Animal a = new Dog();
//如果引用中对象真实类型为Bird
if(a instanceof Bird){
Bird b = (Bird) a;
b.chirp();
}else if (a instanceof Dog){//如果引用中对象真实类型为Dog
Dog d = (Dog) a;
d.bark();
}
}
}

运行结果:

如果不是Bird,则不会向Bird转型,不会报错。

image-20220205224556864

3.8 多态的应用

1. 场景1:使用父类作为方法形参实现多态,使方法参数的类型更加宽泛。

案例1:小明有三个宠物狗、猫、鸟,需要给它们喂食。

Animal.java

1
2
3
4
5
public class Animal {
public void eat(){
System.out.println("吃...");
}
}

Dog.java

1
2
3
4
5
6
public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗狗开始吃...");
}
}

Cat.java

1
2
3
4
5
6
public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫开始吃...");
}
}

Bird.java

1
2
3
4
5
6
public class Bird extends Animal{
@Override
public void eat() {
System.out.println("鸟开始吃...");
}
}

Master.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Master {
String name;

//喂狗
public void feed(Dog dog){
System.out.println(name+"喂宠物");
//狗狗开始吃
dog.eat();
}
//喂猫
public void feed(Cat cat){
System.out.println(name+"喂宠物");
//猫猫开始吃
cat.eat();
}
//喂鸟
public void feed(Bird bird){
System.out.println(name+"喂宠物");
//鸟鸟开始吃
bird.eat();
}
}

TestMaster.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestMaster {
public static void main(String[] args) {
Master xiaoming = new Master();
xiaoming.name = "小明";
//小明养的三个宠物
Dog dog = new Dog();
Cat cat = new Cat();
Bird bird = new Bird();
//小明给三个宠物喂食
xiaoming.feed(dog);
xiaoming.feed(cat);
xiaoming.feed(bird);
}
}

运行结果:

小明喂宠物的时候虽然调用的是同一个函数,但是由于宠物不同,需要重载定义三个一样的feed方法。

image-20220206141444006

案例2:案例1的改进

仅仅需要改动Master

Master.java

1
2
3
4
5
6
7
8
9
10
public class Master {
String name;

//喂动物
public void feed(Animal a){
System.out.println(name+"喂宠物");
//动物开始吃
a.eat();
}
}

运行结果:

运行结果依然是一样的。形参为Animal,实参为Dog、Cat、Bird,自动向上转型。

image-20220206141909428

2. 场景2:使用父类作为方法返回值实现多态,使方法可以返回不同子类对象。

案例1:购买动物

Animal.java

1
2
3
4
5
public class Animal {
public void eat(){
System.out.println("吃...");
}
}

Dog.java

1
2
3
4
5
6
7
8
9
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗狗开始吃...");
}
public void bark(){
System.out.println("狗吠...");
}
}

Cat.java

1
2
3
4
5
6
7
8
9
public class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫开始吃...");
}
public void say(){
System.out.println("猫叫...");
}
}

Bird.java

1
2
3
4
5
6
7
8
9
public class Bird extends Animal {
@Override
public void eat() {
System.out.println("鸟开始吃...");
}
public void chips(){
System.out.println("鸟叫...");
}
}

Master.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Master {
String name;
//购买宠物
public Animal buy(int type){
if(type == 1){
//买一只休狗
return new Dog();
}else if(type == 2){
//买一只休猫
return new Cat();
}else if(type == 3){
//买一只鸟
return new Bird();
}else{
return null;
}
}
}

TestMaster.java

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
public class TestMaster {
public static void main(String[] args) {
System.out.println("**********欢迎来到宠物店***********");
System.out.println("***请选择购买的宠物1.狗 2.猫 3.鸟***");
Scanner input = new Scanner(System.in);
int type = input.nextInt();

Master xiaoming = new Master();
xiaoming.name = "小明";
//小明购买宠物
Animal a = null;
a = xiaoming.buy(type);
if(a != null){
System.out.println("购买成功");
//动物吃
a.eat();
//向下转型执行动物特有的方法 instanceof 判断原始类
if(a instanceof Dog){
//狗吠
((Dog) a).bark();
}else if (a instanceof Cat){
//猫叫
Cat cat = (Cat) a;
cat.say();
}else if(a instanceof Bird){
//鸟叫
((Bird) a).chips();
}
}else{
System.out.println("购买失败");
}
}
}

运行结果:

GIF