Java面向对象进阶:封装、继承、多态、抽象类、接口、内部类与分层开发实战。

封装

封装的概念

面向对象有三大特性:封装、继承、多态。

需要让用户知道的才暴露出来,不需要让用户知道的全部隐藏起来,这就是封装。

更加官方的语言来说 封装就是指隐藏对象的内部实现细节,对外仅提供公共的访问方式

封装优点

  1. 提高了代码的安全性
  2. 提高代码的复用性。
  3. 使用时,只需要了解使用方式,不需要了解内部实现细节。

封装原则

  1. 对于不需要对外提供的内容都隐藏起来
  2. 把成员变量都隐藏,提供公共的方法对外访问。

访问修饰符

Java中使用访问修饰符来控制遍历、方法、类等的访问权限,就是控制哪些需要封装,哪些需要暴露

public:表示公开的,作用范围是当前类+当前包+其他包(整个项目可见,也叫作项目可见性)

protected:表示受保护的,作用范围是当前类+当前包+其他包中的子类(子类可见性)

无修饰符:表示默认的。作用范围是当前类+当前包(包可见性)

private:表示私有的。作用范围是当前类(类可见性)

  1. 类中的变量和方法访问权限有4种:public、protected、无修饰符、private
  2. 类的访问权限只有两种:public和default(内部类也是4个)
  3. 访问修饰符不能修饰局部变量

成员变量的封装

为什么要对成员变量进行封装?

如果让使用者直接给成员变量进行赋值,那么这个变量的取值范围将变得不可空,比如年龄可能被赋值为500岁,这样的操作是极不合理的。

1
2
3
4
5
6
7
8
public class Demo2 {
public static void main(String[] args) {
Person person = new Person();
person.name = "张三";
person.age = 500;
person.show();
}
}

因此我们需要对成员变量的赋值、取值进行一定的范围控制,比如年龄不能为负数,也不能超过150岁,为了能够实现对变量合法性的控制,我们就可以使用封装的思想。

实现方式:使用private修饰成员变量,提供get/set方法分别用于对成员变量的取值/赋值

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

public class Demo2 {
public static void main(String[] args) {
Person person = new Person();
// person.name = "张三";
// person.age = 500;
person.setName("张三");
person.setAge(160);
person.show();
}
}

class Person {
private String name;
private int age;

public String getName() {
return this.name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return this.age;
}

public void setAge(int age) {
if(age > 150) {
age = 150;
}else if(age < 0) {
age = 0;
}
this.age = age;
}

public void show() {
System.out.println("姓名:" + this.name + ",年龄:" + this.age);
}
}


总结

  1. 成员变量的封装要使用private修饰符修饰成员变量
  2. 提供的get/set方法分别用于取值和赋值,并且要使用public修饰,这一对方法也称为getter/setter,它们往往有固定的写法,getXxx和setXxx(boolean类型的get方法比较特殊,是isXxx)
  3. 以后的开发中,成员变量、静态方法都需要使用修饰符来修饰,一般都是public,有时候某些方法只是本类中的辅助性方法,也可以使用private修饰

注意:前面我们提到了一个概念:属性。其实get/set方法才是真正的属性。而String xxx只能叫做变量。

比如,有name成员变量而没有getName和setName,就认为没有name属性

而有getName和setName,但没有name变量,也认为有name属性。

有getName而没有setName,就认为属性是只读的 mybatis

JavaBean

JavaBean是一种Java语言写成的可重用组件,这种类一般都有成员变量,并且成员变量都是私有的,并提供了get/set方法(一般也称之为实体类)

随堂练习

  1. 编写 Java 程序_用于显示人的姓名和年龄。定义一个人类Person。 该类中应该有两个私有属性: 姓名 (name) 和年龄 (age) 。定义构造方法用来初始化数据成员。再定义显示(display()) 方法将姓名和年龄打印出来。在 main 方法中创建人类的实例然后将信息显示。
    Person类

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

    public class Person2 {
    private String name;
    private int age;

    public Person2() {
    }

    public Person2(String name, int age) {
    this.name = name;
    this.age = age;
    }

    public void display() {
    System.out.println("姓名:" + this.name + ",年龄:" + this.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;
    }
    }

    测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package encapsulation;

    /**
    * 编写 Java 程序_用于显示人的姓名和年龄。
    * 定义一个人类Person。 该类中应该有两个私有属性:
    * 姓名 (name) 和年龄 (age) 。
    * 定义构造方法用来初始化数据成员。再定义显示(display())
    * 方法将姓名和年龄打印出来。在 main 方法中创建人类的实例
    * 然后将信息显示。
    */
    public class Demo3 {
    public static void main(String[] args) {
    Person2 person = new Person2();
    person.setName("张三");
    person.setAge(23);
    person.display();
    }
    }
  2. 以面向对象的思想,编写自定义类描述IT从业者。设定属性包括:姓名,年龄,技术方向,工作年限, 工作单位和职务;方法包括:工作
    要求:
    设置属性的私有访问权限,通过公有的get,set方法实现对属性的访问
    限定IT从业人员必须年满15岁,无效信息需提示,并设置默认年龄为15。
    限定“技术方向”是只读属性
    工作方法通过输入参数,接收工作单位和职务,输出个人工作信息
    编写测试类,测试IT从业者类的对象及相关方法(测试数据信息自定义)
    ItWorker类

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

    public class ItWorker {

    private String name;
    private int age;
    private String direction;
    private int year;
    private String company;
    private String position;

    public ItWorker() {
    }

    public ItWorker(String direction) {
    this.direction = direction;
    }

    public void work(String company, String position) {
    System.out.println("姓名:" + this.name);
    System.out.println("年龄:" + this.age);
    System.out.println("技术方向:" + this.direction);
    System.out.println("工作年限:" + this.year);
    System.out.println("工作单位:" + company);
    System.out.println("职务:" + position);
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    if(age < 15) {
    System.out.println("年龄小于15岁,已默认重置为15");
    age = 15;
    }
    this.age = age;
    }

    public String getDirection() {
    return direction;
    }

    public int getYear() {
    return year;
    }

    public void setYear(int year) {
    this.year = year;
    }

    public String getCompany() {
    return company;
    }

    public void setCompany(String company) {
    this.company = company;
    }

    public String getPosition() {
    return position;
    }

    public void setPosition(String position) {
    this.position = position;
    }
    }

    测试类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package encapsulation;

    /**
    * 以面向对象的思想,编写自定义类描述IT从业者。
    * 设定属性包括:姓名,年龄,技术方向,工作年限,
    * 工作单位和职务;方法包括:工作
    * 要求:
    * 设置属性的私有访问权限,通过公有的get,set方法实现对属性的访问
    * 限定IT从业人员必须年满15岁,无效信息需提示,并设置默认年龄为15。
    * 限定“技术方向”是只读属性
    * 工作方法通过输入参数,接收工作单位和职务,输出个人工作信息
    * 编写测试类,测试IT从业者类的对象及相关方法(测试数据信息自定义)
    */
    public class Demo4 {
    public static void main(String[] args) {
    ItWorker itWorker = new ItWorker("Java开发");
    itWorker.setName("稽哥");
    itWorker.setAge(18);
    itWorker.setYear(20);
    itWorker.work("雷欧教育", "Java讲师");
    }
    }

继承

什么是继承

现实生活中继承一般指的是子女继承父辈的遗产,在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关系体系。类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类称之为子类(派生类),现有类称之为父类(基类、超类)。子类拥有父类所有的属性和方法

继承的使用

继承是面向对象最显著的一个特征,是从已有的类派生出一个新的类,新的类拥有父类所有的属性和行为,并且可以扩展出新的能力

1
2
3
4
5
6
7
class 父类 {

}

class 子类 extends 父类 {

}

代码示例

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

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 void eat() {
System.out.println("吃饭中....");
}

public void show() {
System.out.println("姓名:" + this.name + ",年龄:" + this.age);
}

}
package extendsstudy;

public class Teacher extends Person {

private String title;

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public void teach() {
System.out.println("授课");
}
}
package extendsstudy;

public class Demo1Extends {
public static void main(String[] args) {
Person person = new Person();
person.setName("张三");
person.setAge(23);
person.eat();
person.show();
Teacher teacher = new Teacher();
teacher.setName("李四");
teacher.setAge(24);
teacher.setTitle("教导主任");
teacher.eat();
teacher.teach();
teacher.show();
System.out.println(teacher.getTitle());
}
}

继承的使用要点

使用继承的好处

  1. 继承的出现提高了代码的复用性,提高了软件的开发效率
  2. 继承让类与类之间产生了关系,为多态提供了前提

继承的注意点

  1. java只支持单继承,一个类只能有一个直接父类,但是可以有多层继承(间接地继承)
  2. 如果定义了一个类,这个类没有使用extends,它的默认父类是 java.lang.Object

方法的重写

当父类的方法不能满足子类的需求时,我们可以在子类中重写父类的方法,重写也称为复写或者覆盖。

要点

  1. 要求重写方法的方法名和形参列表必须一模一样
  2. 要求子类重写方法的修饰符权限必须大于等于父类被重写方法的修饰符权限,private的方法不能被重写,static的方法不能被重写
  3. 返回值类型和异常类型,子类小于等于父类。
    1. 如果返回值类型是基本数据类型、字符串类型、void,要求子类重写方法的返回值类型必须与父类方法保持一致
    2. 如果是其他引用数据类型,那么子类重写方法的返回值类型和父类的方法返回值类型要么保持一致,要么是父类方法返回值类型的子类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package extendsstudy;

public class Teacher extends Person {

private String title;

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public void teach() {
System.out.println("授课");
}

public void show() {
System.out.println("姓名:" + this.getName() + ",年龄:" + this.getAge() + ",职位:" + this.title);
}

}

注意:如果子类重写了父类的方法,通过子类对象调用该方法时,调用的就是被重写后的方法

方法重写和重载的区别

名字:重载(overload),重写(override)

范围:重载发生在同一个类中,重写发生在继承关系中

定义:重载要求方法名相同,参数列表不同,重写要求方法名相同,参数列表相同

​ 重载对访问权限没有要求,重写要求访问权限子类大于等于父类

​ 重载对返回值类型和异常类型没有要求,重写要求返回值类型和异常类型子类小于等于父类

当一个类中存在大量的方法时,开发者可能无法快速的定位到哪些方法是独有的,哪些方法是重写的,此时可以在被重写的方法上打上 @Override 注解,这个注解的作用是标识一下这个方法是重写的方法,如果不是,则编译不通过

随堂练习

  1. 编写应用程序,创建类的对象,分别设置圆的半径、圆柱体的高,计算并分别显示圆半径、圆面积、圆周长,圆柱体的体积
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
package extendsstudy.test;

public class Circle {

private double r;

public Circle() {
}

public Circle(double r) {
this.r = r;
}

public double getR() {
return r;
}

public void setR(double r) {
this.r = r;
}

public double calcArea() {
return Constant.PI * r * r;
}

public double calcPerimeter() {
return Constant.PI * r * 2;
}

public void show() {
System.out.println("半径为" + this.r + "的圆" +
"周长为:" + calcPerimeter() + "," +
"面积为:" + calcArea());
}


}
package extendsstudy.test;

public class Cylinder extends Circle{

private double height;

public Cylinder() {
}

public double getHeight() {
return height;
}

public void setHeight(double height) {
this.height = height;
}

public double calcVolume() {
return calcArea() * this.height;
}

@Override
public void show() {
System.out.println("半径为" + getR() + ",高为" + this.height + "的圆柱体," +
"底圆周长为:" + calcPerimeter() + "," +
"底面积为:" + calcArea() + "," +
"体积为:" + calcVolume());
}
}
package extendsstudy.test;

public class Test1 {
public static void main(String[] args) {
Circle circle = new Circle(3);
circle.show();
System.out.println("===============");
Cylinder cylinder = new Cylinder();
cylinder.setR(2);
cylinder.setHeight(3);
cylinder.show();
}
}

  1. 某公司要开发新游戏,请用面向对象的思想,设计游戏中的蛇怪和蜈蚣精

    设定

    1. 蛇怪类:

    属性包括:怪物名字,生命值,攻击力

    方法包括:攻击,移动(曲线移动),补血(当生命值<10时,可以补加20生命值)

    1. 蜈蚣精类:

    属性包括:怪物名字,生命值,攻击力

    方法包括:攻击,移动(飞行移动)

    要求

    1. 分析蛇怪和蜈蚣精的公共成员,提取出父类—怪物类

    2. 利用继承机制,实现蛇怪类和蜈蚣精类

    3. 攻击方法,描述攻击状态。内容包括怪物名字,生命值,攻击力

    4. 编写测试类,分别测试蛇怪和蜈蚣精的对象及相关方法

    定义名为mon的包存怪物类,蛇怪类,蜈蚣精类和测试类

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
package extendsstudy.test;

public class Monster {

private String name;
private int hp;
private int atk;

public void show() {
System.out.println("========");
System.out.println("= "+this.name+" =");
System.out.println("= hp:"+this.hp+" =" );
System.out.println("= atk:"+this.atk+" =" );
System.out.println("========");
}

public Monster() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getHp() {
return hp;
}

public void setHp(int hp) {
this.hp = hp;
}

public int getAtk() {
return atk;
}

public void setAtk(int atk) {
this.atk = atk;
}

public void attack() {
System.out.println("怪物攻击");
}

public void move() {
System.out.println("怪物移动");
}
}
package extendsstudy.test;

public class Centipede extends Monster{

@Override
public void attack() {
this.show();
System.out.println(getName()+"使用了普通攻击......");
}

@Override
public void move() {
this.show();
System.out.println(getName()+"使用了飞行术");
}

}
package extendsstudy.test;

public class Snake extends Monster {

@Override
public void attack() {
this.show();
System.out.println(getName()+"使用了石化术......");
}

@Override
public void move() {
this.show();
System.out.println(getName()+"使用蛇皮走位miss了你的攻击");
}

public void healHp() {
this.show();
System.out.println(getName()+"使用了大蛇补血术,hp+20");
this.setHp(this.getHp()+20);
this.show();
}

}
package extendsstudy.test;

public class Test2 {
public static void main(String[] args) {
Snake snake = new Snake();
snake.setHp(50);
snake.setAtk(20);
snake.setName("八岐大蛇");
snake.attack();
snake.move();
snake.healHp();
System.out.println("==============");
Centipede centipede = new Centipede();
centipede.setName("小蜈蚣精");
centipede.setHp(80);
centipede.setAtk(30);
centipede.attack();
centipede.move();
}
}

  1. 请用面向对象的思想,设计自定义类描述演员和运动员的信息

    设定

    1. 演员类:

    属性包括:姓名,年龄,性别,毕业院校,代表作

    方法包括:自我介绍

    1. 运动员类:

    属性包括:姓名,年龄,性别,运动项目,历史最好成绩

    方法包括:自我介始

    要求

    1. 分析演员和运动员的公共成员,提取出父类—人类

    2. 利用继承机制,实现演员类和运动员类

    3. 编写测试类,分别测试人类,演员类和运动员类对象及相关方法

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
package extendsstudy.test;

public class Person {

private String name;
private String gender;
private int age;

public Person() {
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getGender() {
return gender;
}

public void setGender(String gender) {
this.gender = gender;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public void show() {
System.out.println("大家好,我是" + this.name + "," +
"性别:" + this.getGender() +
"年龄:" + this.getAge());
}
}
package extendsstudy.test;

public class Actors extends Person{

private String school;
private String works;

public Actors() {
}

public String getSchool() {
return school;
}

public void setSchool(String school) {
this.school = school;
}

public String getWorks() {
return works;
}

public void setWorks(String works) {
this.works = works;
}

@Override
public void show() {
super.show();
System.out.println("毕业于:" + this.school + "," +
"代表作是:" + this.works);
}
}
package extendsstudy.test;

public class Sports extends Person{
private String sport;
private double bestScore;

public Sports() {
}

public String getSport() {
return sport;
}

public void setSport(String sport) {
this.sport = sport;
}

public double getBestScore() {
return bestScore;
}

public void setBestScore(double bestScore) {
this.bestScore = bestScore;
}

@Override
public void show() {
super.show();
System.out.println("运动项目:" + this.sport +
",历史最好成绩:" + this.bestScore);
}
}
package extendsstudy.test;

public class Test3 {
public static void main(String[] args) {
Actors actors = new Actors();
actors.setName("菜旭坤");
actors.setAge(23);
actors.setGender("男");
actors.setSchool("雷欧教育");
actors.setWorks("唱跳rap篮球");
actors.show();
System.out.println("===================");
Sports sports = new Sports();
sports.setName("马龙");
sports.setAge(30);
sports.setGender("男");
sports.setSport("乒乓球");
sports.setBestScore(100);
sports.show();
}
}

super关键字

super关键字可以理解成父类对象的引用,事实上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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package superstudy;

public class Person {

String name = "张三";

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}


public void study() {
System.out.println("父类的学习方法");
}
}
package superstudy;

public class Student extends Person{

@Override
public void study() {
super.study();
System.out.println("子类的study方法");
}

public void show() {
super.study();
System.out.println("子类的方法:"+super.name);
}

}

package superstudy;

public class Demo1Super {
public static void main(String[] args) {
Student student = new Student();
student.show();
System.out.println("===========");
student.study();
}
}

子类可以继承父类的成员变量,当调用时就可以使用super关键字进行调用(前提是成员变量能够被子类访问)

子类可以继承父类的成员方法,当调用成员方法时可以使用super关键字进行调用。在继承关系中,如果不使用super就调用不到父类中被重写的方法,此时就需要使用super关键字

super关键字调用父类构造方法

子类继承父类,子类的构造方法必须调用父类的构造方法,如果子类中的构造方法没有显式的调用父类构造方法,那么则默认调用父类的无参构造方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package superstudy;

public class Student extends Person{

public Student() {
super("张三");
System.out.println("Student的无参构造被调用了");
}

@Override
public void study() {
super.study();
System.out.println("子类的study方法");
}

public void show() {
super.study();
System.out.println("子类的方法:"+super.name);
}

}

如果使用super关键字主动地调用父类构造方法,那么super必须在构造方法的第一条语句

细节

super关键字用于调用父类的某一个构造方法,this关键字用来调用本类的某一个构造方法,二者都要求必须放到构造方法的第一条语句,所以二者不可以同时出现在一个构造方法中

创建对象内存分析

  1. 成员变量是否存在重写
  2. 创建子类对象时是否创建了父类对象
  3. super关键字和this关键字的区别

1.继承关系下的创建对象内存分析

  1. 成员变量不存在重写
  2. 创建子类对象时没有创建父类对象,而是在子类对象中有一块区域是从父类继承过来的,属于父类的存储空间
  3. 二者实际上都是同一个对象的引用,只不过引用的位置不同。this关键字是从子类的存储空间开始引用,而super关键字是从父类的存储空间开始引用

final关键字

继承的出现提高了代码的复用性和扩展性,方便了开发,但有的时候,我们并不想让子类什么都能继承,或者有些类中的方法是固定的,不想让子类进行改动,这个时候就可以使用final关键字。

  1. final修饰类不可以被继承,但是可以继承其他类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package finalstudy;

    public class Person {
    }
    package finalstudy;

    public final class Student extends Person{
    }
    package finalstudy;

    public class SmallStudent extends Student{
    }

  2. final修饰的方法不能被重写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package finalstudy;

    public class Student extends Person{
    @Override
    public void method1() {}
    @Override
    public void method2() {}
    }
    package finalstudy;

    public class Student extends Person{
    @Override
    public void method1() {}
    @Override
    public void method2() {}
    }

  3. final修饰的变量称之为常量,这些变量只能被赋值一次。final修饰成员变量和静态变量必须要显式的赋初值

    1
    2
    3
    4
    5
    6
    7
    8
    package finalstudy;

    public final class Constant {

    private Constant() {}

    public static final double PI = 3.14;
    }
  4. final修饰引用数据类型变量,变量的地址不能改变,但是堆内存中对象的属性可以修改

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

    public class Demo1Final {
    public static void main(String[] args) {
    final SmallStudent student = new SmallStudent("迪丽热巴", 23);
    student.show();
    student.setName("古力娜扎");
    student.show();
    student = new SmallStudent("马尔扎哈", 20);
    }
    }

Object类

Object类是Java中所有类的父类(数组也继承了Object)。如果一个类没有使用extends继承指定的类,那么这个类默认就继承了Object

toString方法

toString的作用是将一个对象按一定的规则转换成一个字符串的表示形式,默认的表示形式是 对象类名@哈希码

一般情况下,toString方法默认的执行结果并不符合我们的使用场景,此时就需要重写这个方法

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

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;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

输出语句输出一个对象,默认输出的就是它的toString方法。

equals方法

当进行比较时,== 比较的是栈内存中的值,如果是基本数据类型,就是直接比较字面值,如果是引用数据类型,则是比较地址值。

Object类中的 equals 方法,默认也是用 == 进行比较的,比较的也是地址的值

一般情况下,Object默认提供的实现方式并不满足我们的应用场景,所以我们需要将其进行重写,一般重写成 比较两个对象中指定的属性是否相同

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

public class Person {
private String name;
private int age;

public Person() {
}

public Person(String name, int age) {
this.name = name;
this.age = 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;
}

@Override
public boolean equals(Object obj) {
Person p1 = (Person) obj;
return this.name.equals(p1.name) && this.age == p1.age;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

hashCode方法

hashCode方法用于返回该对象的哈希码值。

我们认为,两个对象相同,那么二者的哈希码一定相同,但是两个对象的哈希码相同,对象不一定相同

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

import java.util.Objects;

public class Person {
private String name;
private int age;

public Person() {
}

public Person(String name, int age) {
this.name = name;
this.age = 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;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

多态

什么是多态

多态指的是同一个方法调用,由于对象不同可能会有不同的行为

多态的必要条件

  1. 继承
  2. 子类重写父类方法
  3. 父类引用指向子类对象

多态的适用场合

  1. 使用父类做方法的形参,实参可以是任意子类类型
  2. 使用父类做方法的返回值,返回值可以是任意的子类对象

快速上手

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

public class Animal {

public void eat() {
System.out.println("动物吃饭");
}

}
package polymorphism;

public class Cat extends Animal{
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}
package polymorphism;

public class Dog extends Animal{
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
package polymorphism;

public class Bird extends Animal{
@Override
public void eat() {
System.out.println("鸟吃小米");
}
}
package polymorphism;

public class Demo1 {
public static void main(String[] args) {
// 编译看左边,运行看右边
Animal cat = new Cat();
Animal dog = new Dog();
cat.eat();
dog.eat();
System.out.println("========");
feedAnimal(new Animal());
feedAnimal(new Cat());
feedAnimal(new Dog());
feedAnimal(new Bird());
}

public static void feedAnimal(Animal animal) {
animal.eat();
}

}

引用数据类型转换

引用数据类型转换主要分为向上转型和向下转型

向上转型:父类引用指向子类对象,属于自动类型转换

​ 格式:父类类型 变量名 = 子类对象

向下转型:子类引用指向父类对象,属于强制类型转换

​ 格式:子类类型 变量名 = (自类类型) 父类对象;

instanceof关键字

该关键字的作用是判断某一个对象是否是某个类或者它的子类,如果是则返回true,否则返回false

如果想要使用向下转型,就需要保证这个转换过程一定不要出错,即在转换之前先使用 instanceof 关键字进行判断

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

public class Demo2 {
public static void main(String[] args) {
feedAnimal(new Cat());
feedAnimal(new Dog());
feedAnimal(new Bird());
System.out.println("=========");
invoke(new Cat());
invoke(new SmallCat());
invoke(new Dog());
invoke(new Bird());
}

/**
* 向下转型(子类引用指向父类对象)
* 在转换时必须要保证将要转换的类型与源对象保持一致,或者右边是左边的子类对象
* 如果转换的类型不匹配,就会报 ClassCastException
* @param animal
*/
public static void invoke(Animal animal) {
if(animal instanceof Cat) {
Cat cat = (Cat) animal;
cat.catchMouse();
}else if(animal instanceof Dog) {
Dog dog = (Dog) animal;
dog.lookDoor();
}else if(animal instanceof Bird) {
Bird bird = (Bird) animal;
bird.fly();
}
else {
System.out.println("您传入的并不是一个动物");
}
}

/**
* Animal animal = new Cat()
* Animal animal = new Dog()
* 向上转型(父类引用指向子类对象,无法调用子类独有的方法)
* @param animal
*/
public static void feedAnimal(Animal animal) {
animal.eat();
}

}

优缺点

  • 向上转型
    • 优点:隐藏了子类类型,提高了代码的扩展性。多态本身就是向上转型的过程
    • 缺点:只能使用父类共性的内容,不能调用子类特有的方法
  • 向下转型
    • 优点:可以调用子类特有的方法
    • 缺点:向下转型有风险,容易发生ClassCastException异常。此外代码的扩展性低、侵入性高

多态中的成员变量和成员方法

成员变量

编译和运行都看左边。因为成员变量不存在重写和覆盖

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

public class Animal {

public String name = "小黄";

public void eat() {
System.out.println("动物吃饭");
}

}
package polymorphism;

public class Dog extends Animal{

public String name = "小黑";

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

public void lookDoor() {
System.out.println("狗看大门");
}

}
package polymorphism;

public class Demo3 {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog.name);
Animal animal = new Dog();
System.out.println(animal.name);
}
}

成员方法

编译看左边,运行看右边。

因为方法存在重写机制,调用一个方法时需要确定调用者是哪个对象,就去执行哪个对象中的对应方法,如果在这个对象对应的类中找不到,就去找父类。而编译看左边的机制是为了确定被调用的方法在这个类以及这个类所有的子类中都拥有。

随堂练习

  1. 编写程序实现乐手(Musician)弹奏乐器(Instrument)。乐手可以弹奏不同的乐器从而发出不同的声音。可以弹奏的乐器包括二胡(Erhu)、钢琴(Piano)和小提琴(Violin)
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
package polymorphism.test;

public class Test1 {
public static void main(String[] args) {
Musician musician = new Musician();
musician.playInstrument(new Piano());
musician.playInstrument(new Violin());
}
}

class Musician {
public void playInstrument(Instrument instrument) {
instrument.song();
}
}

class Instrument {
public void song() {
System.out.println("乐器发出声音");
}
}

class Erhu extends Instrument {
@Override
public void song() {
System.out.println("二胡发出了声音");
}
}

class Piano extends Instrument {
@Override
public void song() {
System.out.println("钢琴发出了声音");
}
}
class Violin extends Instrument {
@Override
public void song() {
System.out.println("小提琴发出了声音");
}
}
  1. 编写程序实现比萨制作。需求说明编写程序,接收用户输入的信息,选择需要制作的比萨。可供选择的比萨有:培根比萨和海鲜比萨。
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
package polymorphism.test;

import java.util.Scanner;

public class Test2 {
public static void main(String[] args) {
System.out.println("请选择需要制作的披萨:1.培根披萨,2.海鲜披萨");
Scanner scanner = new Scanner(System.in);
int type = scanner.nextInt();
Pizza pizza = buildPizza(type);
pizza.show();
}

public static Pizza buildPizza(int type) {
if(type == 1) {
return new PacoPizza();
}else {
return new FishPizza();
}
}

}

class FishPizza extends Pizza {
public FishPizza() {
super(6, 30);
}

@Override
public void show() {
System.out.println("海鲜披萨");
super.show();
}
}

class PacoPizza extends Pizza {
public PacoPizza() {
super(8, 20);
}

@Override
public void show() {
System.out.println("培根披萨");
super.show();
}
}

class Pizza {
private int size;
private double price;

public Pizza() {
}

public Pizza(int size, double price) {
this.size = size;
this.price = price;
}

public void show() {
System.out.println("尺寸:" + this.size + ",价格:" + this.price);
}

public int getSize() {
return size;
}

public void setSize(int size) {
this.size = size;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}
}
  1. 编写程序实现软料购买:编写程序,接收用户输入的信息,选择购买的饮料。可供选择的饮料有:咖啡、矿泉水和可乐。其中,购买咖啡时可以选择:加糖、加奶还是什么都不加。购买可乐时可以选择:买可口可乐还是百事可乐。
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
package polymorphism.test;

import java.util.Scanner;

public class Test3 {
static Scanner scanner = new Scanner(System.in);

public static void main(String[] args) {
System.out.println("请选择购买的饮料,1.矿泉水,2.咖啡,3.可乐");
int type = scanner.nextInt();
Drink drink = buyDrink(type);
drink.show();
}

public static Drink buyDrink(int type) {
if(type == 1) {
return new Water();
}else if(type == 2) {
System.out.println("1.加奶,2.加糖,3.什么都不加");
int search = scanner.nextInt();
if(search == 1) {
return new Coffee("加奶");
}else if(search == 2) {
return new Coffee("加糖");
}else {
return new Coffee("什么都不加");
}
}else {
System.out.println("1.可口可乐,2.百事可乐");
int search = scanner.nextInt();
if(search == 1) {
return new Cola("可口");
}else {
return new Cola("百事");
}
}
}

}

class Cola extends Drink {
private String brand;

public Cola(String brand) {
super(3, 300);
this.brand = brand;
}

@Override
public void show() {
System.out.println(brand + "可乐");
super.show();
}

public String getBrand() {
return brand;
}

public void setBrand(String brand) {
this.brand = brand;
}
}

class Coffee extends Drink {
private String other;
public Coffee(String other) {
super(50, 100);
this.other = other;
}

@Override
public void show() {
System.out.println("咖啡" + this.other);
super.show();
}
}

class Water extends Drink {
public Water() {
super(2, 500);
}

@Override
public void show() {
System.out.println("矿泉水");
super.show();
}
}

class Drink {
private double price;
private int ml;

public Drink(double price, int ml) {
this.price = price;
this.ml = ml;
}

public void show() {
System.out.println(this.ml + "毫升,售价" + this.price);
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

public int getMl() {
return ml;
}

public void setMl(int ml) {
this.ml = ml;
}
}

抽象类

抽象类的概述

抽象类是一个抽象的类。

当我们编写一个类时,往往会给这个类定义一些方法,但是有的时候我们可能并不能知道这些方法具体的实现方式,应当交给子类去实现。比如计算一个图形的面积,当无法确定图形是哪一种时,计算面积这个方法也不知道如何实现,只有确定了是什么图形时,才能确定计算方法。

此时图形类中的计算面积方法,就称之为 抽象方法,而图形类则称之为 抽象类

抽象类的使用

抽象类的特点

  1. 抽象类使用 abstract 进行修饰,不能被实例化,但是可以使用多态
  2. 抽象类中可以定义成员变量和静态变量,也可以定义构造方法,为成员变量进行赋值
  3. 抽象类中可以定义普通的方法、静态方法、抽象方法(抽象方法没有也可以)
  4. 抽象类一定是一个父类,需要子类重写父类的抽象方法然后创建对象进行使用

抽象方法的特点

  1. 抽象方法使用 abstract 进行修饰
  2. 抽象方法只有声明,没有方法体
  3. 子类要么实现父类的抽象方法,要么子类也是一个抽象类,否则编译失败
  4. 有抽象方法的类一定是抽象类,抽象类则不一定有抽象方法。

抽象类语法

1
2
3
权限修饰符 abstract class 类名 {

}

抽象方法语法

1
权限修饰符 abstract 返回值类型 方法名(参数列表);

代码案例

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

public abstract class Graph {

private String name;

public Graph(String name) {
this.name = name;
}

public abstract double calcArea();

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}
package abstractstudy;

import extendsstudy.test.Constant;

public class Circle extends Graph{
private double r;

public Circle(double r) {
super("圆形");
this.r = r;
}

@Override
public double calcArea() {
return Constant.PI * this.r * this.r;
}
}
package abstractstudy;

public class Square extends Graph{

private double width;
private double height;

public Square(double width, double height) {
super("矩形");
this.width = width;
this.height = height;
}

@Override
public double calcArea() {
return this.width * this.height;
}

public double getWidth() {
return width;
}

public void setWidth(double width) {
this.width = width;
}

public double getHeight() {
return height;
}

public void setHeight(double height) {
this.height = height;
}
}
package abstractstudy;

public class Demo1Abstract {
public static void main(String[] args) {
Graph square = new Square(3, 4);
System.out.println(square.getName()+"面积为:"+square.calcArea());
Graph circle = new Circle(3);
System.out.println(circle.getName()+"面积为:" + circle.calcArea());
}
}



抽象类在使用过程中更像是一个模板,这个模板主流程是确定的,但是流程中部分节点是不确定实现方式的,将这些节点交给子类进行实现,从而将整个流程操作完毕

随堂练习

  1. 编写程序描述运营商发送短信操作。不同的运营商发送短信的逻辑都不一样,但相同的是在发送前后都会记录一次发送日志。
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
package abstractstudy.test;

public abstract class Operator {

public abstract void sendSms();

public void sendSmsFlow() {
System.out.println("记录发送前日志......");
sendSms();
System.out.println("记录发送后日志......");
}


}
package abstractstudy.test;

public class YidongOperator extends Operator{
@Override
public void sendSms() {
System.out.println("记录短信内容");
System.out.println("发送短信到指定手机号");
System.out.println("扣除话费");
}
}
package abstractstudy.test;

public class LiantongOperator extends Operator {

@Override
public void sendSms() {
System.out.println("扣除话费");
System.out.println("发送短信");
System.out.println("保存短信内容");
}
}
package abstractstudy.test;

public class Test1 {
public static void main(String[] args) {
Operator yidong = new YidongOperator();
yidong.sendSmsFlow();
System.out.println("==============");
Operator liantong = new LiantongOperator();
liantong.sendSmsFlow();
}
}

接口

接口的概述

当一个抽象类中的方法全都是抽象方法的时候,该抽象类可以用另外一种形式来表示,就是接口 interface

接口是比抽象类还要抽象的类,一般有两种应用场景

  1. 对一些类进行功能上的扩展,使其拥有本类以外的能力。一台电脑是否有鼠标、声卡等设备并不影响它是一台电脑的事实,而我们可以为其扩展上一个声卡,让其拥有发出声音的能力,为其扩展上一个键盘,让其拥有打字的能力
  2. 作为一种规范,为子类(实现类)提供一种功能上的约束

从接口的实现角度来看,接口定义了可以向外部提供的服务。从接口的调用者角度来看,接口定义了实现者能够提供哪些服务。

我们在开发时有一种思想叫做 面向接口编程,我们可以在开发一个系统之前,将要设计的系统之间的模块之间的接口定义好,就相当于定义了个系统的设计大纲,剩下的就是添砖加瓦的实现了,当这些大纲全部实现完毕后,系统也就开发完毕了

接口的定义

定义接口使用的关键字不是class,而是 interface 来修饰

1
2
3
修饰符 interface 接口名 [extends 父接口1, 父接口2] {

}

接口中定义的变量全部默认都用 public static final 修饰,都是全局静态常量

接口中的方法,在JDK1.8以前,全部都是抽象方法,默认有 public abstract 修饰。JDK1.8以后,接口中还可以包含静态方法和default修饰的成员方法,默认都是用 public 修饰的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package interfacestudy;

public interface MyInterface {

String name = "张三";

void method1();

static void method2() {

}

default void method3() {

}

}

接口中不能有构造方法,因为接口不能被实例化,此外接口中没有需要被初始化的成员变量,并且接口是可以多实现的,这就意味着如果接口有了构造方法,那么它的实现类使用 super 关键字调用父类构造方法时,可能会不知道调用的是哪一个父类的构造方法

接口的继承

接口之间允许有继承关系,并且可以是多继承。接口之间发生继承后,可以将父接口的抽象方法重写成default方法,也可以不重写,但不能重写成static方法。建议不要重写方法

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

public interface MyInter2 {
void method2();
void method3();
}
package interfacestudy;

public interface MyInter1 {
void method1();
}
package interfacestudy;

public interface MyInterface extends MyInter1, MyInter2{

String name = "张三";

void method1();

static void method4() {

}

@Override
default void method3() {

}

}

接口的实现

类对接口的“继承”一般不叫做继承,而叫做 实现,同时重写接口中的抽象方法也称之为 实现抽象方法

1
2
3
修饰符 class 实现类 extends 父类 implements 接口1, 接口2 {

}

在类实现接口后,该类中所有的抽象方法都会被继承过来,此时该类就需要重写这些抽象方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package finalstudy;

import interfacestudy.Flyable;
import interfacestudy.MyInter1;

public class Bird implements Flyable, MyInter1 {
@Override
public void fly() {
System.out.println("鸟正在飞.....");
}

@Override
public void method1() {

}
}

接口同样适用于多态

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

public class Demo1 {
public static void main(String[] args) {
Flyable bird = new Bird();
bird.fly();
}
}

类实现接口是允许多实现的,当实现一个接口,就需要重写这个接口中所有的抽象方法,抽象类除外。当一个抽象类实现一个接口时,可以选择性的重写其中的抽象方法

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

public abstract class Animal implements MyInter2 {
@Override
public void method2() {

}
}

接口和抽象类的区别

抽象级别,接口 > 抽象类 > 普通类。

接口:吃东西可以定义成一个接口,然后狗、猫、人都需要吃东西,所以就可以去实现吃东西这个接口,让其拥有吃东西这个行为。接口还可以作为一种规范的制定,一种约束

抽象类:人有姓名和年龄,有吃饭行为,狗也有姓名和年龄,也有吃饭行为,我们可以将相同的行为定义在一个抽象类中,然后然这些类去继承这个抽象类。此外抽象类还可以作为一种模板

接口中的默认方法和静态方法

JDK8之后接口也允许有默认的实现方法和静态方法,这些方法在使用时与抽象类没有什么区别。

默认方法使用default关键字去进行定义

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

public interface Flyable {

void fly();

default void glide() {
System.out.println("鸟正在低空滑翔");
}

}

注意,尽管接口中有了default方法,但接口依然是不能进行实例化的,如果想调用default方法,应当交给它的实现类进行操作

接口中的静态方法定义方式与类中没有区别,使用方式也没有区别

接口中的静态方法在调用时只能使用 接口名.方法名 进行调用,不能使用对象进行调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package interfacestudy;

public interface Flyable {

void fly();

default void glide() {
System.out.println("鸟正在低空滑翔");
}

static void method() {
System.out.println("接口中的默认方法");
}

}

面向接口编程

面向接口编程是面向对象编程的一部分。

软件设计过程中需求是可能多变的,或者说需求是非常多的,在开发的过程中如果想一出是一出,那么在编写程序的过程中很容易陷入被动,这样的话就可能会延期项目的交付,我们必须围绕着某种东西开展,才能以静制动。

我们回想写项目计划书、毕业论文时,我们一般会先列出目录,再列出大纲,之后按照目录或者大纲来进行。而接口就是这种“大纲”,我们可以在写代码之前先定义好接口,把某个模块需要的功能全部定义在这个接口中,之后再创建这个接口的实现类,将这些抽象方法一个一个的进行实现,当方法全部实现完毕后,这个系统就开发完毕了。

此外,抽象类相较于接口太过于笨重了,在使用抽象类的过程中难免会出现耦合性过高的情况,而接口则比较轻量,接口可以编写出低耦合性的代码,提高了整个系统的可扩展性和可维护性。

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

public interface FlowerManager {

void save(Flower flower);

void update(Flower flower);

Flower find(int id);

Flower[] findAll();

void delete(int id);

}
package interfacestudy;

public class FlowerManagerImpl implements FlowerManager{
@Override
public void save(Flower flower) {

}

@Override
public void update(Flower flower) {

}

@Override
public Flower find(int id) {
return null;
}

@Override
public Flower[] findAll() {
return new Flower[0];
}

@Override
public void delete(int id) {

}
}

随堂练习

  1. 编写程序描述兔子、青蛙、鱼,拥有属性:名字、颜色、类别(哺乳类、非哺乳类),方法:吃饭,发出叫声。其中,青蛙和鱼会游泳
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
package interfacestudy.test;

public abstract class Animal {

private String name;
private String color;
private String type;

public Animal() {
}

public Animal(String name, String color, String type) {
this.name = name;
this.color = color;
this.type = type;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getColor() {
return color;
}

public void setColor(String color) {
this.color = color;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}

public abstract void eat();

public abstract void call();


}
package interfacestudy.test;

public class Rabbit extends Animal{
@Override
public void eat() {
System.out.println("兔子吃胡萝卜");
}

@Override
public void call() {
System.out.println("兔子唧唧的叫");
}
}
package interfacestudy.test;

public class Frog extends Animal implements Swim{
@Override
public void eat() {
System.out.println("青蛙吃虫子");
}

@Override
public void call() {
System.out.println("青蛙呱呱的叫");
}

@Override
public void swim() {
System.out.println("青蛙用腿游泳");
}
}
package interfacestudy.test;

public class Fish extends Animal implements Swim{
@Override
public void eat() {
System.out.println("大鱼吃小鱼,小鱼吃虾米");
}

@Override
public void call() {
System.out.println("鱼不会叫");
}

@Override
public void swim() {
System.out.println("鱼用尾巴游泳");
}
}
package interfacestudy.test;

public class Test1 {
public static void main(String[] args) {
Animal rabbit = new Rabbit();
rabbit.eat();
rabbit.call();
System.out.println("============");
Animal frog = new Frog();
frog.eat();
frog.call();
Swim swimFrog = (Swim) frog;
swimFrog.swim();
System.out.println("==============");
Animal fish = new Fish();
fish.eat();
fish.call();
}
}

  1. 实现不同符合PCI规范的适配器。PCI是一种规范,所有实现了该规范的适配器,必如显卡、声卡、网卡都可以安装到PCI插槽上并工作。模拟实现该功能
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
package interfacestudy.test;

public interface PCI {

void transfer();

}
package interfacestudy.test;

public class GraphCard implements PCI{
@Override
public void transfer() {
System.out.println("显卡显示图像");
}
}
package interfacestudy.test;

public class SoundCard implements PCI{
@Override
public void transfer() {
System.out.println("声卡输出声音");
}
}
package interfacestudy.test;

public class NetCard implements PCI{
@Override
public void transfer() {
System.out.println("网卡连接网络");
}
}
package interfacestudy.test;

public class Test2 {
public static void main(String[] args) {
PCI graphCard = new GraphCard();
graphCard.transfer();
PCI soundCard = new SoundCard();
soundCard.transfer();
PCI netCard = new NetCard();
netCard.transfer();
}
}

  1. 编写程序描述影视歌三栖艺人。三栖艺人可以演电视剧、电影、唱歌
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
package interfacestudy.test;

public class Artists implements Tv, Movie,Sing {

private String name;
private int age;

public Artists() {
}

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;
}

@Override
public void movie() {
System.out.println(this.name+"在拍电影");
}

@Override
public void sing() {
System.out.println(this.name+"在唱歌");
}

@Override
public void tv() {
System.out.println(this.name+"在拍电视剧");
}
}
package interfacestudy.test;

public interface Tv {
void tv();
}
package interfacestudy.test;

public interface Movie {
void movie();
}
package interfacestudy.test;

public interface Sing {
void sing();
}
package interfacestudy.test;

public class Test3 {
public static void main(String[] args) {
Artists artists = new Artists();
artists.setName("菜旭坤");
artists.setAge(18);
artists.movie();
artists.tv();
artists.sing();
}
}

内部类

什么是内部类

将类写在其他类的内部,可以写在其他类的成员位置或者局部位置,这时写在其他类内部的类就称为内部类,其他类也称为外部类。

在描述事物时,如果一个事物内部还包含其他事物,并且还不太希望这个类能被其他类所访问到,这个时候可以使用内部类

对于一个名为OuterClass的外部类和一个InnerClass的内部类,在编译成功后,会出现两个class文件:OuterClass.classOuterClass$InnerClass.class

当想要访问内部类时,需要借助外部类

内部类分为:成员内部类,静态内部类,局部内部类,匿名内部类

成员内部类

成员内部类,是定义在外部类中的成员变量位置,与成员变量和成员方法平级,可以通过外部类对象进行访问

1
2
3
4
5
6
7
8
9
class 外部类 {
成员变量1;
成员变量2;
成员方法1;
成员方法2;
修饰符 class 内部类 {

}
}

代码示例

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

public class OuterClass1 {

String sex = "男";
InnerClass1 class1;

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public InnerClass1 getClass1() {
return class1;
}

public void setClass1(InnerClass1 class1) {
this.class1 = class1;
}

public void show() {
InnerClass1 class1 = new InnerClass1();
class1.setName("小明");
System.out.println(class1.getName());
class1.test();
}

public class InnerClass1 {
String name;

public InnerClass1() {
}

public InnerClass1(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void test() {
System.out.println("内部类访问外部类成员变量:"+OuterClass1.this.sex);
}
}

}
package innerclass;

public class Demo1 {
public static void main(String[] args) {
OuterClass1 outerClass1 = new OuterClass1();
// 就像成员变量和成员方法一样,要用对象.去调用成员内部类
OuterClass1.InnerClass1 class1 = outerClass1.new InnerClass1();
outerClass1.setClass1(class1);
outerClass1.show();
class1.test();
}
}

静态内部类

静态内部类语法和成员内部类非常相似,都是定义在与成员变量和静态变量平级的位置,只是多了个static关键字。

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

public class OuterClass2 {

String sex = "女";

InnerClass2 innerClass2;

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public InnerClass2 getInnerClass2() {
return innerClass2;
}

public void setInnerClass2(InnerClass2 innerClass2) {
this.innerClass2 = innerClass2;
}

static class InnerClass2 {
String name;

public InnerClass2() {
}

public InnerClass2(String name) {
this.name = name;
}

public void show() {
System.out.println("静态内部类的show方法");
}
}
}
package innerclass;

public class Demo2 {
public static void main(String[] args) {
OuterClass2 outerClass2 = new OuterClass2();
outerClass2.setInnerClass2(new OuterClass2.InnerClass2());
}
}

局部内部类

局部内部类是定义在方法中的局部位置,跟局部变量很类似。

1
2
3
4
5
6
7
class 外部类 {
修饰符 返回值类型 方法名(参数列表) {
class 内部类 {

}
}
}

代码示例

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

public class Demo3 {
public static void main(String[] args) {
class InnerClass3 {
String name;

// JDK8以前必须加final
public static String NAME = "123213";

public InnerClass3() {
}

public InnerClass3(String name) {
this.name = name;
}

public void show() {
System.out.println("局部内部类中的show");
}
}
InnerClass3 innerClass3 = new InnerClass3("小明");
System.out.println(innerClass3.name);
innerClass3.show();
System.out.println(InnerClass3.NAME);
}
}

匿名内部类

匿名内部类就是没有名字的内部类,也是局部内部类的一种,语法简单,但有局限性,它要求内部类必须继承一个父类或者实现一个接口,是创建某个类型子类对象的快捷方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package innerclass;

import interfacestudy.Flyable;

public class Demo4 {

public static void main(String[] args) {
Flyable flyable = new Flyable() {
@Override
public void fly() {
System.out.println("匿名内部类fly");
}
};
flyable.fly();
}

}

注意,这里并不是创建了Flyable的对象,接口是不能创建对象,这里只是创建了一个Flyable的实现类,并去new它的对象

鲜花销售系统

学习目标

  1. 体会数组的作用

  2. 找到分层开发的感觉

  3. 收获分层开发的兴趣

  4. 作出效果,找到自信

  5. 学会调试错误

  6. 掌握数组的增删改查方法

项目需求

  1. 使用分层开发:表示层(测试类)、业务层(鲜花管理类)、实体层(鲜花类)

  2. 使用数组的相关方法实现鲜花销售系统的查询订单、修改订单、删除订单、添加订单功能。

  3. 查询订单之前需要对数组进行初始化操作

  4. 修改订单时,需要先判断订单是否存在,存在才可以修改

  5. 删除订单时,需要先判断订单是否存在,存在才可删除,询问用户是否确认删除,确认后再删除订单信息,删除成功后,显示目前最新的订单信息

需求分析

分层开发

前后端分离

将功能模块分解成表示层、业务层、数据层三层,实现高内聚低耦合。

表示层:也叫作页面层,只负责页面的展示、与用户的交互

业务层:也叫作服务层,只负责业务逻辑的处理

数据层:也叫作持久层,只负责数据的存取

代码编写

Q1:需要哪些类

A1:表示层 FlowerManager;业务层 FlowerService 接口,FlowerServiceImpl 实现类;持久层 DataBase ,实体类 Flower

Q2:每一个类分别有哪些功能

A2:

  • Flower 类:负责描述鲜花订单这个实体,有编号、鲜花名称、销售数量、价格、销售日期、销售员、备注这些成员变量,并提供get、set方法

  • DataBase类:负责存储数据,提供一个静态对象数组,并且在静态代码块中对数据进行初始化。提供数据的操作方法:存储数据、删除数据、修改数据、查询数据,这些方法只对数据进行操作,也就意味着,调用这些方法时,这些方法就默认传过来的参数是合法的

  • FlowerService类:负责功能的定义,添加订单、修改订单、删除订单、查询订单

  • FlowerManager类:负责页面的展示。主要是菜单的展示,以及选择菜单之后与用户的交互

代码实现

Flower类

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

public class Flower {

private String id;

private String name;
private int saleCount;
private double price;
private String date;
private String saleUser;
private String remark;

public Flower() {
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getSaleCount() {
return saleCount;
}

public void setSaleCount(int saleCount) {
this.saleCount = saleCount;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

public String getDate() {
return date;
}

public void setDate(String date) {
this.date = date;
}

public String getSaleUser() {
return saleUser;
}

public void setSaleUser(String saleUser) {
this.saleUser = saleUser;
}

public String getRemark() {
return remark;
}

public void setRemark(String remark) {
this.remark = remark;
}
}

DataBase类

  • 数据如何存储?使用静态变量
  • 数据如何初始化?使用静态代码块
  • 操作数据相关方法
    • save
      • 在数组下一个位置进行添加(此时需要有一个变量记录当前添加到了数组的哪个位置)
      • 如果数组已满,扩容数组,新数组扩容成原数组的1.5倍,拷贝元素
      • 更新下一个待存储位置
    • update
      • 遍历数组,匹配到id相同的数据的位置
      • 替换元素
    • delete
      • 遍历数组,匹配到id相同的数据的位置
      • 删除元素
      • 移动数组元素
    • getById
      • 遍历数组,匹配到id相同的数据的位置
      • 返回对应位置的元素
      • 没查询到,返回null
    • findAll
      • System.arraycopy

具体功能

dao层代码

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
package flowermanager.dao;

import flowermanager.pojo.Flower;

public class DataBase {

public static Flower[] flowers;
private static int nextIndex = 0;


static {
flowers = new Flower[8];
flowers[0] = new Flower("1", "康乃馨", 10, 10, "2023-01-02", "稽哥", "");
flowers[1] = new Flower("2", "玫瑰花", 99, 20, "2023-05-20", "雷哥", "");
nextIndex = 2;
}

public void save(Flower flower) {
if(nextIndex >= flowers.length) {
Flower[] newArr = new Flower[(int) (nextIndex*1.5)];
System.arraycopy(flowers, 0, newArr, 0, nextIndex);
flowers = newArr;
}
flowers[nextIndex] = flower;
nextIndex++;
}

public void update(Flower flower) {
for (int i = 0; i < nextIndex; i++) {
if(flower.getId().equals(flowers[i].getId())) {
flowers[i] = flower;
break;
}
}
}

public void delete(String id) {
for (int i = 0; i < nextIndex; i++) {
if(flowers[i].getId().equals(id)) {
flowers[i] = null;
// 移动数组元素
System.arraycopy(flowers, i+1, flowers, i, flowers.length-i-1);
nextIndex--;
break;
}
}
}

public Flower getById(String id) {
for (int i = 0; i < nextIndex; i++) {
Flower flower = flowers[i];
if(flower.getId().equals(id)) {
return flower;
}
}
return null;
}

public Flower[] findAll() {
Flower[] newArr = new Flower[nextIndex];
System.arraycopy(flowers, 0, newArr, 0, nextIndex);
return newArr;
}
}

服务层

FlowerService接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package flowermanager.service;

import flowermanager.pojo.Flower;

public interface FlowerService {
void save(Flower flower);

void update(Flower flower);

void delete(String id);

Flower getById(String id);

Flower[] findAll();
}

FlowerServiceImpl实现类

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
package flowermanager.service.impl;

import flowermanager.dao.DataBase;
import flowermanager.pojo.Flower;
import flowermanager.service.FlowerService;

public class FlowerServiceImpl implements FlowerService {

private DataBase dataBase = new DataBase();


@Override
public void save(Flower flower) {
dataBase.save(flower);
}

@Override
public void update(Flower flower) {
// 判断订单是否存在
Flower order = dataBase.getById(flower.getId());
if(order == null) {
System.out.println("订单不存在");
}else {
dataBase.update(flower);
}
}

@Override
public void delete(String id) {
Flower flower = dataBase.getById(id);
if(flower == null) {
System.out.println("订单不存在");
}else {
dataBase.delete(id);
}
}

@Override
public Flower getById(String id) {
return dataBase.getById(id);
}

@Override
public Flower[] findAll() {
return dataBase.findAll();
}
}

页面层

  • 删除订单

    • 提示用户输入要删除的id
    • 调用删除方法进行删除
    • 重新展示列表
  • 添加订单

    • 提醒用户输入订单信息
      • 在输入完id之后,要校验id是否存在,如果存在,提示该订单已存在,请勿重复录入
    • 直接调用订单添加方法
    • 重新展示列表
  • 修改订单

    • 提示用户输入id
      • 如果id不存在,题型订单号不存在
    • 输入订单信息
    • 调用修改方法
    • 重新展示列表
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
package flowermanager.view;

import flowermanager.pojo.Flower;
import flowermanager.service.FlowerService;
import flowermanager.service.impl.FlowerServiceImpl;

import java.util.Scanner;

public class FlowerManager {

private static FlowerService flowerService = new FlowerServiceImpl();

private static Scanner scanner = new Scanner(System.in);

public static void main(String[] args) {
while (true) {
showMenu();
checkMenu();
System.out.println();
}
}

private static void showMenu() {
System.out.println("======欢迎光临七彩鲜花销售系统======");
System.out.println("1.添加订单");
System.out.println("2.修改订单");
System.out.println("3.删除订单");
System.out.println("4.订单列表");
System.out.println("5.退出系统");
System.out.println("================================");
}

private static void checkMenu() {
System.out.println("请选择菜单:");
int menu = scanner.nextInt();
if(menu == 1) {
saveOrder();
}else if(menu == 2) {
updateOrder();
}else if(menu == 3) {
deleteOrder();
}else if(menu == 4) {
showOrderList();
}else if(menu == 5) {
System.out.println("退出系统");
System.exit(0);
}else {
System.out.println("您选择的菜单有误");
}
}

private static void updateOrder() {
System.out.print("请输入编号:");
String id = scanner.next();
Flower flower = flowerService.getById(id);
if(flower == null) {
System.out.println("订单号不存在,请检查后再录入");
}else {
System.out.print("请输入鲜花名称:");
flower.setName(scanner.next());
System.out.print("请输入销量:");
flower.setSaleCount(scanner.nextInt());
System.out.print("请输入价格:");
flower.setPrice(scanner.nextDouble());
System.out.print("请输入销售日期:");
flower.setDate(scanner.next());
System.out.print("请输入销售员:");
flower.setSaleUser(scanner.next());
System.out.print("请输入备注:");
flower.setRemark(scanner.next());
flowerService.update(flower);
System.out.println("修改成功");
}
showOrderList();
}

private static void saveOrder() {
System.out.print("请输入编号:");
String id = scanner.next();
Flower flower = flowerService.getById(id);
if(flower != null) {
System.out.println("订单号已存在,请勿重复录入");
}else {
flower = new Flower();
flower.setId(id);
System.out.print("请输入鲜花名称:");
flower.setName(scanner.next());
System.out.print("请输入销量:");
flower.setSaleCount(scanner.nextInt());
System.out.print("请输入价格:");
flower.setPrice(scanner.nextDouble());
System.out.print("请输入销售日期:");
flower.setDate(scanner.next());
System.out.print("请输入销售员:");
flower.setSaleUser(scanner.next());
System.out.print("请输入备注:");
flower.setRemark(scanner.next());
flowerService.save(flower);
System.out.println("添加成功");
}
showOrderList();
}

private static void deleteOrder() {
System.out.println("请输入待删除的订单id:");
String id = scanner.next();
flowerService.delete(id);
showOrderList();
}

private static void showOrderList() {
Flower[] flowers = flowerService.findAll();
System.out.println("编号\t鲜花名称\t销售数量\t价格\t\t销售日期\t\t销售员\t备注");
for (Flower flower : flowers) {
System.out.println(flower.getId()+"\t"+flower.getName()+"\t" +
flower.getSaleCount()+"\t\t"+flower.getPrice()+"\t"+
flower.getDate()+"\t"+flower.getSaleUser()+"\t"+
flower.getRemark());
}
}

}