文章目录
- 一、设计模式
-
- [1.1 设计模式概述](#1.1 设计模式概述)
- [1.2 单例设计模式](#1.2 单例设计模式)
- [1.3 单例设计模式的应用场景](#1.3 单例设计模式的应用场景)
- [1.4 单例设计模式的实现方式](#1.4 单例设计模式的实现方式)
- 二、继承
-
- [2.1 继承快速入门](#2.1 继承快速入门)
- [2.2 为什么要使用继承](#2.2 为什么要使用继承)
- [2.3 继承的注意事项](#2.3 继承的注意事项)
-
- [2.3.1 权限修饰符](#2.3.1 权限修饰符)
- [2.3.2 单继承、Object](#2.3.2 单继承、Object)
- [2.3.3 方法重写](#2.3.3 方法重写)
- [2.3.4 子类中访问成员的特点](#2.3.4 子类中访问成员的特点)
- [2.3.5 子类中访问构造器的特点](#2.3.5 子类中访问构造器的特点)
- 总结
详细的java学习路径和教程请看我写的另一篇java小白到大牛的快速直通车

一、设计模式
1.1 设计模式概述
设计模式其实就是某个具体问题的最优解法,设计模式有20多种,对应20多种软件开发中会遇到的问题
我们学习设计模式时应该带着问题去学习:
- 某种设计模式具体时解决什么问题呢
- 这种设计模式该如何写呢
1.2 单例设计模式
单例设计模式就是确保一个类只有一个对象
写法:
- 将类的构造器私有化
- 定义一个类变量记住类的一个对象
- 定义一个类方法,返回类的对象
代码表示如下
我们在测试类中多次调用getObject方法并输出对象的地址
结果如下:
可以看到,多次调用返回的对象地址都相同,保证了类只有一个对象
1.3 单例设计模式的应用场景
单例设计模式常用语接受当前环境下状态,比如咱们电脑中的任务管理器
无论我们开几次任务管理器,它也只会开一个,在Java中有这么一个API,叫RunTime,我们点开他的源码查看会发现它就是单例设计模式
因为他的对象表示当前的运行环境,所以不需要每次都创建对象
1.4 单例设计模式的实现方式
单例实际模式实现方式有很多种,这里只给大家介绍2种方式
- 饿汉式单例:拿对象时,对象早就创建好了
- 懒汉式单例:拿对象时,才开始创建对象
饿汉式单例:就是上面我们介绍的那种
类变量会随着类的加载而分配内存,所以上图当我们加载类时就直接会创建对象,所以为饿汉单例。
懒汉式单例:
只有我们调用方法时才会对变量进行初始化,并判断对象是否初始化,如未初始化则代表第一次调用,则对其进行初始化,如已经初始化则直接返回对象
二、继承
2.1 继承快速入门
面向对象编程之所以能够能够被广大开发者认可,有一个非常重要的原因,是因为它有三大特征,继承、封装和多态。封装我们在前面的文章已经讲过了,接下来我们讲一下继承。
继承:
- java中提供了一个关键字extends,用这个关键字,可以让一个类和另一个类建立起父子关系
继承的特点:
- 子类能继承父类的非私有变量(成员变量、成员方法)
继承后对象的创建:
- 子类的对象是由子类、父类共同完成的
接下来,我们演示一下使用继承来编写代码,注意观察继承的特点。
我们先写一个父类A
java
public class A{
//公开的成员
public int i;
public void print1(){
System.out.println("===print1===");
}
//私有的成员
private int j;
private void print2(){
System.out.println("===print2===");
}
}
然后,写一个B类,让B类继承A类。在继承A类的同时,B类中新增一个方法print3
java
public class B extends A{
public void print3(){
//由于i和print1是属于父类A的公有成员,在子类中可以直接被使用
System.out.println(i); //正确
print1(); //正确
//由于j和print2是属于父类A的私有成员,在子类中不可以被使用
System.out.println(j); //错误
print2();
}
}
接下来,我们再演示一下,创建B类对象,能否调用父类A的成员。再写一个测试类
java
public class Test{
public static void main(String[] args){
B b = new B();
//父类公有成员,子类对象是可以调用的
System.out.println(i); //正确
b.print1();
//父类私有成员,子类对象时不可以调用的
System.out.println(j); //错误
b.print2(); //错误
}
}
为了让大家对继承有更深入的认识,我们来看看继承的内存原理。
这里我们只需要关注一点:子类对象实际上是由子、父类两张设计图共同创建出来的。
所以,在子类对象的空间中,既有本类的成员,也有父类的成员。但是子类只能调用父类公有的成员。
2.2 为什么要使用继承
我们通过一个案例来讲一下继承的好处
现在我们有这样一个需求:我们要编写员工管理系统,员工有老师和咨询师,老师的数据有:姓名、技能,咨询师的数据有姓名、解答问题人数
我们会发现,2个类中存在着大量的重复代码,这个时候,我们可以将重复代码都提到父类中,然后让其他类继承父类就可以了,这样可以提高代码的复用性。改造后的代码如下:
接下来使用继承来完成上面的案例,这里只演示People类和Teacher类,然后大家尝试自己完成Consultant类。
- 先写一个父类 People,用来设计Teacher和Consultant公有的成员。
java
public class People{
private String name;
public String getName(){
return name;
}
public void setName(String name){
this.name=name;
}
}
- 再写两个子类Teacher继承People类,同时在子类中加上自己特有的成员。
java
public class Teacher extends People{
private String skill; //技能
public String getSkill(){
return skill;
}
public void setSkill(String skill){
this.skill=skill;
}
public void printInfo(){
System.out.println(getName()+"具备的技能:"+skill);
}
}
- 最后再写一个测试类,再测试类中创建Teacher、Consultant对象,并调用方法。
java
public class Test {
public static void main(String[] args) {
Teacher t = new Teacher();
t.setName("剁椒");
t.setSkill("Java、Spring");
System.out.println(t.getName());
System.out.println(t.getSkill());
t.printInfo();
}
}
执行代码,打印结果如下:
2.3 继承的注意事项
2.3.1 权限修饰符
以前文章使用的代码中我们有用到两个权限修饰符,一个是public(公有的)、一个是private(私有的),实际上还有两个权限修饰符,一个是protected(受保护的)、一个是缺省的(不写任何修饰符)。
接下来我们就学习一下这四个权限修饰符的访问范围。
下面我们用代码演示一下,在本类中可以访问到哪些权限修饰的方法。
java
public class Fu {
// 1、私有:只能在本类中访问
private void privateMethod(){
System.out.println("==private==");
}
// 2、缺省:本类,同一个包下的类
void method(){
System.out.println("==缺省==");
}
// 3、protected: 本类,同一个包下的类,任意包下的子类
protected void protectedMethod(){
System.out.println("==protected==");
}
// 4、public: 本类,同一个包下的类,任意包下的子类,任意包下的任意类
public void publicMethod(){
System.out.println("==public==");
}
public void test(){
//在本类中,所有权限都可以被访问到
privateMethod(); //正确
method(); //正确
protectedMethod(); //正确
publicMethod(); //正确
}
}
接下来,在和Fu类同一个包下,创建一个测试类Demo,演示同一个包下可以访问到哪些权限修饰的方法。
java
public class Demo {
public static void main(String[] args) {
Fu f = new Fu();
// f.privateMethod(); //私有方法无法使用
f.method();
f.protectedMethod();
f.publicMethod();
}
}
接下来,在另一个包下创建一个Fu类的子类,演示不同包下的子类中可以访问哪些权限修饰的方法。
java
public class Zi extends Fu {
//在不同包下的子类中,只能访问到public、protected修饰的方法
public void test(){
// privateMethod(); // 报错
// method(); // 报错
protectedMethod(); //正确
publicMethod(); //正确
}
}
接下来,在和Fu类不同的包下,创建一个测试类Demo2,演示一下不同包的无关类,能访问到哪些权限修饰的方法;
java
public class Demo2 {
public static void main(String[] args) {
Fu f = new Fu();
// f.privateMethod(); // 报错
// f.method(); //报错
// f.protecedMethod();//报错
f.publicMethod(); //正确
Zi zi = new Zi();
// zi.protectedMethod();
}
}
2.3.2 单继承、Object
上面我们写的代码中,都是一个子类继承一个父类,那么一个子类可以继承多个父类吗?
Java语言只支持单继承,不支持多继承,但是可以多层继承 。就像家族里儿子、爸爸和爷爷的关系一样:一个儿子只能有一个爸爸,不能有多个爸爸,但是爸爸也是有爸爸的。
Object类是所有类的祖宗类,所有的类都直接或者间接的继承Object类
java
public class Test {
public static void main(String[] args) {
// 目标:掌握继承的两个注意事项事项。
// 1、Java是单继承的:一个类只能继承一个直接父类;
// 2、Object类是Java中所有类的祖宗类。
A a = new A();
B b = new B();
ArrayList list = new ArrayList();
list.add("java");
System.out.println(list.toString());
}
}
class A {} //默认添加的extends Object{}
class B extends A{}
// class C extends B , A{} // 报错
class D extends B{}
2.3.3 方法重写
学习完继承之后,在继承的基础之上还有一个很重要的现象需要给大家说一下。
叫做方法重写。为了让大家能够掌握方法重写,我们先认识什么是方法重写,再说一下方法的应用场景。
什么是方法重写
当子类觉得父类方法不好用,或者无法满足父类需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆盖父类的这个方法,这就是方法重写。
注意:重写后,方法的访问遵循就近原则。下面我们看一个代码演示
写一个A类作为父类,定义两个方法print1和print2
java
public class A {
public void print1(){
System.out.println("重写前1");
}
public void print2(int a, int b){
System.out.println("重写前2");
}
}
再写一个B类作为A类的子类,重写print1和print2方法。
java
public class B extends A{
// 方法重写
@Override // 安全,可读性好
public void print1(){
System.out.println("重写后1");
}
// 方法重写
@Override
public void print2(int a, int b){
System.out.println("重写后2");
}
}
接下来,在测试类中创建B类对象,调用方法
java
public class Test {
public static void main(String[] args) {
// 目标:认识方法重写,掌握方法重写的常见应用场景。
B b = new B();
b.print1();
b.print2(2, 3);
}
}
执行代码,我们发现真正执行的是B类中的print1和print2方法
知道什么是方法重写之后,还有一些注意事项:
- 重写的方法上面,可以加一个注解@Override,用于标注这个方法是复写的父类方法,增加重写的安全性和可读性
- 子类复写父类方法时,访问权限必须大于或者等于 父类方法的权限
public > protected > 缺省 - 重写的方法返回值类型,必须与被重写的方法返回值类型一样,或者范围更小
- 私有方法、静态方法不能被重写,如果重写会报错。
方法重写的应用场景
学习完方法重写之后,接下来,我们还需要大家掌握方法重写,在实际中的应用场景。方法重写的应用场景之一就是:子类重写Object的toString()方法,以便返回对象的内容。
比如:有一个Student类,这个类会默认继承Object类。
java
public class Student extends Object{
private String name;
private int age;
public Student() {
}
public Student(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;
}
}
在Object类中有一个toString()方法,直接通过Student对象调用Object的toString()方法,会得到对象的地址值
java
public class Test {
public static void main(String[] args) {
Student s = new Student("剁椒", 18);
// System.out.println(s.toString());
System.out.println(s);
}
}

此时我们不想返回地址值而是内容,那就可以在Student类中重新写一个toSting()方法,用于返回对象的属性值。
java
public class Student extends Object{
private String name;
private int age;
public Student() {
}
public Student(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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
重新运行测试类,结果如下
2.3.4 子类中访问成员的特点
刚才我们已经学习了继承,我们发现继承至少涉及到两个类,而每一个类中都可能有各自的成员(成员变量、成员方法),就有可能出现子类和父类有相同成员的情况,那么在子类中访问其他成员有什么特点呢?
在子类中访问其他成员(成员变量、成员方法),是依据
就近原则的
定义一个父类,代码如下
java
public class F {
String name = "父类名字";
public void print() {
System.out.println("==父类中print方法执行==");
}
}
再定义一个子类,代码如下。有同名的name成员变量和局部变量,有同名的print成员方法;
java
public class Z extends F{
String name = "子类名字";
public void showName(){
String name = "子类局部名字";
System.out.println(name); //子类局部名字
System.out.println(this.name); //子类名字
System.out.println(super.name); //父类名字
}
@Override
public void print(){
System.out.println("==子类中print方法执行==");
}
public void showMethod(){
print(); //就近原则,子类print方法执行
super.print(); //父类中的print方法执行
}
}
运行结果如下
可以发现,如果子类和父类出现同名变量或者方法,优先使用子类的;此时如果一定要在子类中使用父类的成员,可以加this或者super进行区分。
this调用本类的成员,super调用父类的成员
2.3.5 子类中访问构造器的特点
子类在运行构造器前都会先调用父类构造器,在执行自己
执行顺序,如下图①②③步骤执行
如果父类中没有无参构造,则子类会报错(因为子类的构造器默认添加super()方法),所以我们需要给父类添加无参构造,如果不想添加无参构造,则可以给父类添加有参构造,子类手动添加super(参数)调用父类的有参构造
子类访问构造器的应用场景
- 一般用于快速地给子类独有的成员和父类共有的成员快速赋值
当我们想调用自己本身的构造器时,可以使用this调用
什么时候会调用本身构造器呢,这里通过一个案例大家就明白了
我们现在有个学生管理系统
java
class Student{
private String name; //学生姓名
private int age; //学生年龄
private String schoolName; //学生学校名称
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
此时我们创建学生对象并赋值
java
public class Test {
Student s = new Student("剁椒",18,"清华");
}
现在我们有个需求时如果学生不填学校名称,则默认北大
这时代码可以这样写,在Student中添加name和age的构造器
java
public Student(String name, int age) {
this.name = name;
this.age = age;
this.schoolName = "北大";
}
这样就满足了我们的需求,但是我们发现一个问题,就是重复的代码太多
这个时候我们就可以让2个参数的构造器直接调用3个参数的构造器,以减少代码重复,这里我们使用this关键字
最后我们对this和super的用法在总结一下
访问本类成员:
this.成员变量 //访问本类成员变量
this.成员方法 //调用本类成员方法
this() //调用本类空参数构造器
this(参数) //调用本类有参数构造器
访问父类成员:
super.成员变量 //访问父类成员变量
super.成员方法 //调用父类成员方法
super() //调用父类空参数构造器
super(参数) //调用父类有参数构造器
注意:this和super访问构造方法,不能同时在一个方法中出现,否则会报错
总结
以上就是今天要讲的内容,本文介绍了单例设计模式和继承及相关注意事项,通过本篇文章我们能掌握设计模式的设计思想,继承的优势和使用形式等