面向对象进阶1(static、单例、代码块、继承)
# 面向对象进阶1(static、单例、代码块、继承)
# 1. static 关键字
# 1.1 static 是什么、修饰成员变量和成员方法的用法
# 1.1.1 static 是什么
static 是静态的意思,可用来修饰成员变量和成员方法
static 修饰成员变量表示该成员变量只在内存中只存储一份,可以被共享访问、修改。
# 1.1.2 成员变量
成员变量可以分为2类:
静态成员变量
有 static 修饰,属于类,内存中加载一次
常表示如在线人数信息、等需要被共享的信息,可以被共享访问
示例代码
public class User { // 静态成员变量 public static String onlineNumber= 161; }
1
2
3
4访问格式:推荐使用
类名.静态成员变量
,不推荐对象.静态成员变量
实例成员变量
- 无static修饰,存在于每个对象中
- 常表示姓名name、年龄age、等属于每个对象的信息。属于每个对象,且每个对象的信息不同
- 访问格式:
对象.实例成员变量
# 1.1.3 成员方法
成员方法可以分为2类:
- 静态成员方法
- 有 static 修饰,属于类
- 使用场景:如果该方法是以执行一个共用功能为目的,则可以申明成静态方法
- 访问:建议用类名访问,也可以用对象访问。
类名.静态成员方法
- 实例成员方法
- 无 static 修饰,属于对象
- 使用场景:表示对象自己的行为的,且方法中需要访问实例成员的,则该方法必须申明成实例方法
- 访问:只能用对象触发访问
# 1.2 static 修饰成员变量的内存原理
在加载一个类时,会先将此类加载到方法区,同时会在堆内存中开辟一块此类的静态变量区
-> 然后将 main 方法加载进栈内存运行
注:此时内存中还没有 name、age 实例成员变量,因为他们是属于对象的
-> 在创建对象时,才在堆内存中开辟一个新的空间出来。
-> 有新的对象创建时,在堆内存中开辟另一个空间表示此对象。
提示
同一个类中访问静态成员变量时,类名可以省略不写。
# 1.3 static 修饰成员方法的内存原理
在初始加载类时,便会将该类的静态方法同时加载到内存的方法区中,但实例成员方法此时还不会加载进来。 执行图中的 getMax 方法时,将该方法提到栈内存中运行,输出结果之后进行下一步。
-> 创建一个对象之后,在堆内存中开辟空间,并将实例成员方法 study 加载入方法区,在对象中指向对相应静态方法和该实例方法的引用。
# 1.4 static 访问的注意事项
静态方法只能访问静态的成员,不可以直接访问实例成员。
- 只是不能直接在一个静态方法中访问实例成员变量,但可以先在此方法中先创建一个实例成员变量,然后访问其变量。
- 同时也不能直接访问实例成员方法。
实例方法可以访问静态的成员,也可以访问实例成员。
静态方法中是不可以出现 this 关键字的。
- 因为 this 代表当前对象,但是静态方法可以不通过对象来调用。
实例方法/变量 ∈ 对象,静态方法/变量 ∈ 类。 静态成员变量一般设为 public ,因为它全局只有一份。
# 2. static 应用知识
# 2.1 工具类
# 2.1.1 什么是工具类
工具类中定义的都是一些静态方法,每个方法都是以完成一个共用的功能为目的。
比如:一个系统的很多部分都需要生成验证码,此时可以开发一个生成验证码的静态方法。
# 2.1.2 工具类的好处
- 调用方便
- 提高了代码复用(一次编写,处处可用)
# 2.1.3 为什么工具类中的方法不用实例方法做?
实例方法需要创建对象调用,此时用对象只是为了调用方法,这样只会浪费内存。
# 2.1.4 工具类定义时的其他要求(锦上添花)
- 工具类中都是静态方法,直接用类名访问即可。
- 工具类无需创建对象,建议将工具类的构造器进行私有化。(用 private 阉割掉)
# 2.2 代码块
# 2.2.1 代码块概述
代码块是类的5大成分之一(成员变量、构造器,方法,代码块,内部类),定义在类中方法外。
在Java类下,使用 { } 括起来的代码被称为代码块 。
# 2.2.2 代码块分类
静态代码块
格式:
static{}
特点:需要通过 static 关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
作用:若要在启动系统时对数据进行初始化,建议使用静态代码块完成数据的初始化操作,代码优雅
使用场景:在类加载的时候做一些静态数据初始化的操作,以便后续使用。比如,数据库的连接可以在 static { } 中执行,进行静态资源的初始化。
代码示例:
public class StaticDemo { public static void main(String[] args) { System.out.println("----main方法执行----"); } /** 静态代码块:有static修饰,属于类,与类一起优先加载一次,自动触发执行 */ static { System.out.println("----静态代码块被触发执行了----"); } } // 以上代码中先触发静态代码块,然后执行main方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
构造代码块(了解,用的少)
- 也可以叫实例代码块
- 格式:{}
- 特点:每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
- 使用场景:初始化实例资源。
# 2.2.3 案例——斗地主游戏
需求:
在启动游戏房间的时候,应该提前准备好54张牌,后续才可以直接使用这些牌数据。
分析:
①该房间只需要一副牌。
②定义一个静态的 ArrayList 集合存储 54 张牌对象,静态的集合只会加载一份。
③在启动游戏房间前,应该将 54 张牌初始化好
④当系统启动的同时需要准备好 54 张牌数据,此时可以用静态代码块完成。
总结:
需要提前准备数据,并且是只需要一份这个数据,因此使用 static 修饰的时候,需要用到静态代码块。
# 2.3 设计模式:单例
# 2.3.1 什么是设计模式
开发中经常遇到一些问题,一个问题通常有n种解法的,但其中有一种解法是最优的,这个最优的解法被总结出来,称之为设计模式。
设计模式有20多种,对应20多种软件开发中会遇到的问题,学设计模式主要是学2点:
- 第一:这种模式用来解决什么问题。
- 第二:遇到这种问题了,该模式是怎么写的,他是如何解决这个问题的。
# 2.3.2 单例模式
可以保证系统中,应用该模式的这个类永远只有一个实例,即保证一个类永远只能创建一个对象。例如任务管理器对象我们只需要一个就可以解决问题了,这样可以节省内存空间。
单例的实现方式有很多,如饿汉单例模式、懒汉单例模式,……
# 2.3.3 饿汉单例模式
在用类获取对象的时候,对象已经提前为你创建好了。
设计步骤:
定义一个类,把构造器私有
定义一个静态变量存储一个对象
代码示例:
/** a、定义一个单例类 */ public class SingleInstance { /** c.定义一个静态变量存储一个对象即可:属于类,与类一起加载一次 */ public static SingleInstance instance = new SingleInstance(); /** b.单例必须私有构造器*/ private SingleInstance(){ System.out.println("创建了一个对象"); } } /** 别的类中的使用:直接获取该单例的对象 */ SingleInstance s1 = SingleInstance.instance; // 当有多条语句创建s1、s2、s3...时,他们都是相等的,是同一个对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 2.3.4 懒汉单例模式
在真正需要该对象的时候,才去创建一个对象(延迟加载对象)。
设计步骤:
- 定义一个类,把构造器私有
- 定义一个静态变量存储一个对象
- 提供一个返回单例对象的方法
代码示例:
/** 定义一个单例类 */ class SingleInstance{ /** 定义一个静态变量存储一个对象即可:属于类,与类一起加载一次 */ private static SingleInstance instance ; // 默认值:null /** 单例必须私有构造器*/ private SingleInstance(){ } /** 必须提供一个方法返回一个单例对象 */ public static SingleInstance getInstance(){ if(instance == null){ // 第一次来拿对象:此时需要创建对象 instance = new SingleInstance(); } return instance; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19⚠️ 上述代码中,line 4 中用 private 修饰对象,此时无法从该类外部使用
SingleInstance s1 = SingleInstance.instance
获取对象了,只能使用SingleInstance s1 = SingleInstance.getInstance()
获取。否则可能会拿到 null 值,不符合需求。
# 2.3.5 饿汉单例模式 VS 懒汉单例模式
懒汉单例模式节省内存,在不需要创建对象时,不会在栈内存中创建内存。
饿汉单例模式更快些,当需要对象时,已经在内存中了。
# 4. 面向对象三大特征之二:继承
# 4.1 继承概述
关键字:extends,建立两个类之间的父子关系
基本使用:
public class Student extends People {}
。 Student 称为子类(派生类),People 称为父类(基类 或超类)。好处:子类继承父类后,可以直接使用父类公共的属性和方法了。因此,用好这个技术可以很好的我们提高代码的复用性
# 4.2 继承的设计规范
子类们相同特征(共性属性,共性方法)放在父类中定义,子类独有的的属性和行为应该定义在子类自己里面。
# 4.3 继承的内存运行原理
创建一个子类对象时,实际会在堆内存中分为父类空间和子类空间两部分;对外是一个对象,对内会分为两部分,整个代表一个子类对象。
# 4.4 继承的特点
子类可以继承父类的属性和行为,但是子类不能继承父类的构造器。
- 子类有自己的构造器,父类构造器用于初始化父类对象。
Java是单继承模式:一个类只能继承一个直接父类。
- 为啥?反证,当多个爸爸中让你复习不一样的内容,那你就不知道该复习哪门啦,出现了二义性
Java不支持多继承、但是支持多层继承。
- 多层继承:儿子 -> 爸爸 -> 爷爷
- 基于就近原则使用父类中的成员变量和成员方法
Java中所有的类都是 Object 类的子类。
- 要么直接继承了Object , 要么默认继承了Object , 要么间接继承了Object
- Object是祖宗类。
一些小注意点
子类是否可以继承父类的私有成员?
子类可以继承父类的私有成员,但不能直接访问。如,大头儿子继承了小头爸爸的保险柜,但他没有密码,打不开。
子类是否可以继承父类的静态成员?
有争议的知识点。
子类可以直接使用父类的静态成员(共享)。
但黑马认为:子类不能继承父类的静态成员。(共享并非继承)。如,爸爸的车给儿子开,这只是共享,不是继承,车没有给儿子。
# 4.5 继承之后的特点
# 4.5.1 成员变量、成员方法的访问特点
- 在子类方法中访问成员(成员变量、成员方法)满足:就近原则
- 子类局部范围找 → 子类成员范围找 → 父类成员范围找,如果父类范围还没有找到则报错。
- 在子类中使用父类的重名成员怎么办?
- 子类父类出现重名成员,优先使用子类的
- 可通过 super 关键字,指定访问父类的成员,格式:
super.父类成员变量/方法
# 4.5.2 继承后:方法重写
方法重写的基本知识:
- 什么是方法重写?
- 在继承体系中,子类出现了和父类中一模一样的方法声明,称子类的这个方法是重写的方法。
- 方法重写的应用场景
- 子类需要父类的功能,但父类的该功能不完全满足自己的需求时
- 案例:
- 旧手机的功能只能是基本的打电话,发信息
- 新手机的功能需要能够:基本的打电话下支持视频通话。基本的发信息下支持发送语音和图片
@override 注解:
- @Override 是放在重写后的方法上,作为重写是否正确的校验注解。
- 加上该注解后如果重写错误,编译阶段会出现错误提示。
- 建议重写方法都加 @Override 注解,代码安全,优雅!
方法重写注意事项和要求:
- 重写方法的名称、形参列表必须与被重写方法的名称和参数列表一致(申明必须一样,访问权限也一般都是一样的)。
- 申明不变,重新实现
- 私有方法不能被重写。
- 子类重写父类方法时,访问权限必须大于或者等于父类 (private < 缺省 < protected < public)
- 子类不能重写父类的静态方法,如果重写会报错的。
- 原因:子类本身就没有继承到父类的静态方法,只是共享,因此无法覆盖它。调用中也是父类的静态方法用 “父类名.父类静态方法名” 调用,子类的用子类对应的方法调用,不会互相影响。
# 4.5.3 继承后:子类构造器的特点
子类继承父类后构造器的特点:
- 子类中所有的构造器默认都会先访问父类的**无参构造器**,再执行自己。
原因:
- 子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据。
- 子类初始化之前,一定要调用父类构造器先完成父类数据空间的初始化。
怎么调用父类构造器的?
- 子类构造器的第一行语句默认都是:
super()
,不写也存在。
# 4.5.4 继承后:子类构造器访问父类有参构造器
super调用父类有参数构造器的作用:
- 初始化继承自父类的数据。
- 即,Teacher 类继承了 People 类,在 Teacher 中初始化 People 定义好的 name,age
如果父类中没有无参数构造器,只有有参构造器,会出现什么现象呢?
会报错。因为子类默认是调用父类无参构造器的。
但子类中如果只调用了父类的有参构造器,是不会报错的。
解决:子类构造器中通过 super(…),手动调用父类的有参数构造器
建议:将无参构造器全都写出来
# 4.6 this、super 使用总结
this:代表本类对象的引用;
super:代表父类存储空间的标识。
示例代码
/** 后台创建对象封装数据的时候如果用户没有输入学校,则默认使用“黑马培训中心”;如果用户输入了学校则使用用户输入的学校信息 */ public class Student { private String schoolName; private String name; public Student(String name){ this(name , “黑马培训中心”); // 调用两个参数的构造器 } public Student(String name , String schoolName ){ this.name = name; this.schoolName = schoolName; } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
this(...) 和 super(...) 使用注意点
子类通过 this (...)去调用本类的其他构造器(如有参构造器),本类其他构造器会通过 super 去手动调用父类的构造器,最终还是会调用父类构造器的。
this(…) super(…) 都只能放在构造器的第一行,所以二者不能共存在同一个构造器中。
- 用 this() 构造器时,可能会调用兄弟构造器,兄弟构造器也会调用他爸 super(),若自己也调用super(),会造成调用两次 super()