文章目录
Demo08-static、单例、代码块、继承
1.static
1.1static静态关键字
static是什么:
- static是静态的意思,可以修饰成员变量和成员方法
- static修饰成员变量表示该成员变量只在内存中存储一份,可以被共享访问、修改
1.1.1static修饰成员变量
成员变量可以分为2类:
- 静态成员变量(有static修饰,属于类而不再属于具体的对象,在内存中加载一次),访问方式:类名.静态成员变量(推荐.如果是在该类中访问自己类的静态成员变量,可以省略类名.) 对象名.静态成员变量(不推荐.访问流程:根据对象名找到对象,再根据对象找到静态成员变量)
- 实例成员变量(无static修饰,归属于对象),访问方式:对象名.实例成员变量
静态成员变量的内存机制:
静态成员变量会随着类的加载在内存中加载一次,加载完成后会在堆内存中开辟一块区域存放该类的静态变量,然后将加载后的静态成员变量放到开辟的这块区域中
1.1.2static修饰成员方法
成员方法可以分为2类:
- 静态成员方法(有static修饰,属于类而不再属于具体的对象),可以用类名访问也可以用对象访问,建议用类名访问(如果是在该类中访问自己类的静态成员变量,可以省略类名.)
- 实例成员方法(无static修饰,归属于对象),只能用对象访问
表示对象自己的行为,且方法中需要访问实例成员的,则该方法必须申明成实例方法
如果该方法是以执行一个共用功能为目的,则可以申明成静态方法
静态成员方法的内存机制及运行原理:
1.类加载时会将静态成员方法(如main和getMax)加载到方法区
2.将方法区的main方法提到栈内存运行
3.main方法运行过程中遇到了一行代码是调用getMax方法,那么就将方法区的getMax方法提到栈内存运行
实例成员方法的内存机制及运行原理:
1.类加载时会将静态成员方法(如main和getMax)加载到方法区
2.将方法区的main方法提到栈内存运行
3.main方法运行过程中遇到了一行代码是创建Student对象的,那么就会在堆内存创建该对象,同时将该对象的实例方法study加载到方法区,并且堆内存中的该对象有方法引用指向方法区的实例方法和静态方法.最后将地址赋值给栈内存中的变量student
4.继续运行main方法遇到了一行代码student.study(),此时根据地址找到堆内存的对象,再根据该对象中的方法引用找到方法区的方法study并将其提到栈内存运行,运行完成后将方法study退出栈内存
5.继续运行main方法遇到了一行代码student.getMax(),这里不能像使用类名调用静态方法那样(直接在方法区找到该方法然后提到栈内存运行)而是应该像调用实例方法那样,先根据地址找到对象,再根据对象的方法引用找到方法区的该方法
1.1.3static访问注意事项:
- 静态方法只能访问静态成员(成员变量和成员方法),不可以直接访问实例成员(成员变量和成员方法)可以间接访问:在静态方法中new一个对象然后通过对象访问该对象的实例成员
- 实例方法可以访问静态成员(成员变量和成员方法),也可以访问实例成员(成员变量和成员方法)
- 静态方法中不可以出现this关键字,因为this代表的是当前对象,而静态方法并不是使用对象调用的,而是使用类名调用的
1.2static应用知识
1.2.1工具类
工具类中都是一些静态方法,每个方法都是以完成一个共用的功能为目的,这个类用来给系统开发人员共同使用的
为什么工具类中的方法不用实例方法做:
- 实例方法需要创建对象调用
- 此时用对象只是为了调用方法,这样只会浪费内存
工具类定义时的要求: 由于工具类里面都是静态方法,直接用类名即可访问,因此工具类无需创建对象,建议将工具类的构造器进行私有,这样显得很专业,嘿嘿~~~
1.2.2代码块
代码块概述
- 代码块是类的五大成分之一(成员变量,构造器,方法,代码块,内部类),定义在类中方法外
- 在java类下,使用{}括起来的代码被称为代码块
代码块分为
- 静态代码块
- 格式: static{}
- 特点: 需要通过关键字static修饰,随着类的加载而加载,并且自动触发,只执行一次
- 使用场景: 在类加载的时候做一些静态数据初始化的操作,以便后续使用
- 构造代码块(了解,几乎不用)
- 格式: {}
- 特点: 每次创建对象,调用构造器执行时,都会执行该代码块中的代码,并且在构造器执行前执行
- 使用场景: 初始化实例资源
1.2.3单例设计模式
设计模式:
1.什么是设计模式:开发中经常遇到一些问题,一个问题通常有n种解法的,但其中肯定有一种解法是最优的,这个最优的解法被人总结出来了,称之为设计模式
2.设计模式有20多种,对应20多种软件开发中会遇到的问题
3.学设计模式主要是学2点:
- 第一:这种模式用来解决什么问题
- 第二:遇到这种问题了,该模式是怎么写的,他是如何解决这个问题的
单例设计模式:
- 可以保证系统中,应用该模式的这个类永远只有一个实例,即一个类永远只能创建一个对象
- 例如任务管理器对象我们只需要一个就可以解决问题了,这样可以节省内存空间
单例的实现方式很多:恶汉单例模式,懒汉单例模式…
饿汉单例设计模式:
- 在用类获取对象时,对象已经提前为你创建好了
设计步骤:
1.定义一个类,把构造器私有
2.定义一个静态变量并存储一个对象
public class SingleInstance {
public static SingleInstance instance = new SingleInstance();
private SingleInstance(){
}
}
public class Test1 {
public static void main(String[] args) {
SingleInstance s1 = SingleInstance.instance;
SingleInstance s2 = SingleInstance.instance;
System.out.println(s1 == s2);//true
}
}
懒汉单例设计模式
- 在真正需要该对象的时候,才去创建一个对象(延迟加载对象)
设计步骤:
1.定义一个类,把构造器私有
2.定义一个静态变量用来存储一个对象,但此时为null
3.提供一个返回单例对象的方法
public class SingleInstance2 {
//注意懒汉单例设计模式要把instance私有化,不能暴露给外部
private static SingleInstance2 instance;
public static SingleInstance2 getInstance() {
if(instance == null){
// 第一次来拿对象 :此时需要创建对象
instance = new SingleInstance2();
}
return instance;
}
private SingleInstance2(){
}
}
public class Test2 {
public static void main(String[] args) {
SingleInstance2 s1 = SingleInstance2.getInstance();
SingleInstance2 s2 = SingleInstance2.getInstance();
System.out.println(s1 == s2);
}
}
2.继承
2.1认识继承
什么是继承:
- 继承就是java允许我们用extends关键字,让一个类和另一个类建立起一种父子关系
继承的好处:
- 提高代码复用性,减少代码冗余,增强类的功能扩展性
继承的格式:
- 子类 extends 父类
继承后子类的特点:
- 子类继承父类,子类可以得到父类的属性和行为,子类可以使用
- java中子类更强大
继承设计规范:
- 子类们相同特征(共性属性,共性方法)放在父类中定义,子类独有的属性和行为应该定义在子类自己里面(如果子类的独有属性,行为定义在父类中,会导致其他子类也会得到这些属性和行为,这不符合面向对象逻辑)
继承的内存机制:
- new Student()时会在堆内存中创建对象,该对象有两个空间,一个是父类空间,一个是子类空间
2.2继承的特点
- 子类可以继承父类的属性和行为,但是子类不能继承父类的构造器
- 子类可以继承父类私有成员,只是不能直接访问
- 子类不可以继承父类的静态成员,但是子类可以直接使用父类的静态成员
- java是单继承模式,一个类只能继承一个直接父类
- java不支持多继承,但是支持多层继承
- java中所有的类都是Object类的子类
1.java不支持多继承是因为如果继承的多个父类有相同的方法,应该调用哪个呢,这就引起了语法冲突
2.为什么子类不可以继承父类的静态成员,但是子类可以直接使用父类的静态成员:老师认为这是一种共享方式并非继承,Student继承了People那么就理所当然可以用子类.静态成员变量或子类.静态成员方法调用父类的静态成员,如果说子类可以继承父类的静态成员,那么子类对象就应该能从父类得到静态成员,但因为静态成员在内存中只有一份,所以子类创建的对象并没有真的从父类得到静态成员,所以这种方式只能算是共享而不能算是继承
java中的所有类,要么直接继承Object,要么默认继承Object,要么间接继承Object,Object是祖宗类
2.3访问特点
1.在子类方法中访问成员变量:就近原则,先找方法内部有没有该局部变量,没有的话找子类里面有没有,没有的话找父类里面有没有,如果父类也没有就报错
2.在子类方法中访问成员方法:就近原则,先找子类里面有没有,没有的话找父类里面有没有,如果父类也没有就报错
3.如果子父类中出现了重名的成员(成员变量,成员方法),此时如果一定要在子类中使用父类的成员:super.父类成员变量/父类成员方法
题外话:如果一个类中有变量public String name = “1”,并且该类中的方法study()中有局部变量String name = “2”(注意这里的String,我一直以为如果类中有该变量,那么方法内部就不能再声明了,而是应该更改,也就是只能name = “2”,这是错误的),在该方法内默认是调用局部变量name,如果想要调用类中的变量name,那么应该this.name
public class Test {
public static void main(String[] args) {
Dog d = new Dog();
d.run(); // 子类有,就用子类的
d.lookDoor(); // 子类有,就用子类的
d.showName(); // 子类有,就用子类的
}
}
class Animal{
public String name = "动物名";
public void run(){
System.out.println("动物可以跑~~");
}
}
class Dog extends Animal{
public String name = "狗名";
public void lookDoor(){
System.out.println("狗可以看门~~");
}
public void showName(){
String name = "局部名";
System.out.println(name);//局部的name
System.out.println(this.name); // 当前子类对象的name
System.out.println(super.name); // 找父类的name
super.run(); // 找父类的方法
run(); // 子类的run
this.run(); // 子类的run
}
public void run(){
System.out.println("狗跑的贼快~~~");
}
}
2.4方法重写
@Override重写注解
- 修饰方法,表明这是一个重写方法,作为重写是否正确的校验注解,**如果方法有@Override修饰并且不报错,表明重写正确.**如果方法的返回值或方法名或参数列表不同,那么在编译阶段就会出现错误提示,代码更安全
- 别人读我写的代码一眼就看出来这是个重写方法,是不是更优雅了呢~~~
方法重写的注意事项:
- 重写方法的返回值,方法名,参数列表必须和被重写方法的一致
- 私有方法不能被重写
- 子类重写父类方法时,访问的权限必须大于或者等于父类(实际开发中建议等于)
- 子类不能重写父类的静态方法,因为重写方法的前提是该方法能够过继到子类对象,而静态方法不能过继到子类对象
public class Test {
public static void main(String[] args) {
NewPhone hw = new NewPhone();
hw.call();
}
}
class Phone{
public void call(){
System.out.println("打电话~");
}
}
class NewPhone extends Phone{
@Override
public void call(){
super.call(); // 先用它爸爸的基本功能
System.out.println("开始视频通话~~");
}
}
2.5子类构造器
2.5.1子类的无参构造器
子类中所有的构造器默认都会先访问父类中无参构造器,再执行自己
为什么要这样呢:因为子类在初始化的时候,有可能会使用到父类中的数据,如果父类没有完成初始化,子类将无法使用父类的数据
怎么调用父类构造起的呢:子类构造器的第一行语句默认都是:super(),不写也存在
2.5.2子类的有参构造器
我们通常会在子类的有参构造器中手动用super(…)调用父类的有参构造器以便初始化继承自父类的数据,一定要手动写上super(…)否则子类的有参构造器就会自动调用父类的无参构造器,就无法实现初始化了
如果父类只有有参构造器而没有无参构造器,那么就需要在子类的所有构造器中手动调用父类的有参构造器,否则就会报错,因为子类构造器默认是调动父类无参构造器的.所以建议父类一定要写上无参构造器
public class Test {
public static void main(String[] args) {
Teacher t = new Teacher("mxy");
System.out.println(t.getName());
}
}
class People {
private String name;
public People(){
}
public People(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class Teacher extends People{
public Teacher(){
}
public Teacher(String name){
super(name);
}
}
2.6this和super总结
this代表本类对象的引用,super代表父类存储空间的标识
关键字 | 访问成员变量 | 访问成员方法 | 访问构造方法 |
---|---|---|---|
this | this.成员变量 访问本类成员变量 在子类的方法中使用 | this.成员方法(…) 访问本类成员方法 在子类的方法中使用 | this(…) 访问本类其他构造器 在构造器中使用 |
super | super.成员变量 访问父类成员变量 在子类的方法中使用 | super.成员方法(…) 访问父类成员方法 在子类的方法中使用 | super(…) 访问父类构造器 在子类的构造器中使用 |
以前没遇见过this(…)访问本类其他构造器,下面用一个实例理解一下
public class Test {
public static void main(String[] args) {
Student s2 = new Student("mxy");
System.out.println(s2.getName());
System.out.println(s2.getSchoolName());
}
}
class Student {
private String name;
private String schoolName;
public Student() {
}
public Student(String name) {
// 借用本类兄弟构造器
this(name, "中国大学~");
}
public Student(String name, String schoolName) {
// 这里有默认的super();先初始化父类,再初始化自己。
this.name = name;
this.schoolName = schoolName;
}
public String getName() {
return name;
}
public String getSchoolName() {
return schoolName;
}
}
this(…)和super(…)使用注意点:
-
this(…)和super(…)都只能放在构造器的第一行
-
因为this(…)和super(…)都只能放在构造器的第一行,所以两者不能共存在同一个构造器中
-
子类通过this(…)去调用本类的其他构造器,本类其他构造器会通过super去调用父类的构造器,所以最终还是会调用父类构造器的