# static

回顾:

public class Demo {
    public static void main(String[] args) {
        Student student = new Student("f", 18);

        String name = student.getName();
        int age = student.getAge();

        System.out.println("名字:" + name + " 年龄:" + age);
    }
}
public class Student {
    private String name;
    private int age;

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

# static 修饰成员变量

假设电影院上映了电影速度与激情 10,一场电影准备卖出 100 张票,2 个窗口负责卖这 100 张票。

public class Window {
    public String name;
    public int ticketNumber;
}

上述代码的内存图:
image.png
对于普通的成员变量来说,每个对象都有一份成员变量。每当创建一个对象,对象中都包含 ticketNumber 成员变量,每个窗口都是卖自己的 100 张票,不符合要求,应该卖相同的 100 张票。

# static 修饰成员变量作用

static 修饰的变量在内存中只有一份,被这个类的所有对象共享。

public class Window {
    public String name;
    public static int ticketNumber;
}

image.png

# static 修饰成员变量如何调用

  • 类名.静态成员变量(推荐)
  • 对象名.静态成员变量(不推荐)
public class Demo {
    public static void main(String[] args) {
        Window.ticketCount = 100;

        Window window1 = new Window("一号窗口");
        window1.sellTicket();

        Window window2 = new Window("二号窗口");
        window2.sellTicket();

        System.out.println(Window.ticketCount);
    }
}
public class Window {
    String name;
    static int ticketCount;

    public Window(String name) {
        this.name = name;
    }
    public void sellTicket() {
        ticketCount--;
    }
}

# static 修饰成员变量的场景说明

  • 如果某个成员变量希望被共享,则定义成静态成员,只有一份。例如:学校饮水机。
  • 如果某个成员变量是属于每个对象的,而且每个对象的该数据的值不同则定义成实例成员变量。

# static 修饰变量内存图

public class Window {
    public String name;
    public static int ticketNumber;
    // 省略其他
}

public class Demo02 {
    public static void main(String[] args) {
        Window.ticketNumber = 100;

        Window w1 = new Window();
        w1.name = "窗口一";
        System.out.println("剩余票数" + w1.ticketNumber);
        Window w2 = new Window();
        w2.name = "窗口二";
        System.out.println("剩余票数" + w2.ticketNumber);
		w1.ticketNumber--;
        System.out.println("剩余票数" + w1.ticketNumber);
        System.out.println("剩余票数" + w2.ticketNumber);
        System.out.println("剩余票数" + Window.ticketNumber);
    }
}

image.png
由上图可以看出 static 修饰的成员变量存在静态区,只有一份。

# static 修饰方法

# static 修饰方法格式

修饰符 static 返回值类型 方法名() {
    ...
}

# 静态成员方法调用

  • 类名.静态方法名(); (推荐)
  • 对象名.静态方法名(); (不推荐)

# static 修饰方法缺点

  • 同一个类中,静态方法只能访问静态修饰的成员 (静态只能访问静态)
  • 静态方法中是没有 this 关键字
public class Demo {
    public static void main(String[] args) {
        Student student = new Student();

        student.name = "x";
        Student.schoolName = "方头小学";

        Student.show2();
    }
}
public class Student {
    String name;
    public static String schoolName;

    public String getName() {
        return name;
    }

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

    public void show1() {}
    public static void show2() {
//        静态方法只能访问静态的成员变量成员方法;静态方法中是没有this关键字
//        System.out.println(this);
//        show1();
//        System.out.println(name);

        System.out.println(schoolName);
    }

}

# static 修饰成员方法的场景说明

如果该方法是以执行一个通用功能为目的,且不会使用到普通成员变量,可以考虑声明成静态方法。如果该方法是需要访问对象的实例成员的,则该方法必须声明成实例方法。

# static 使用-工具类设计

在各种软件中,通常需要在很多业务处使用手机验证码进行安全验证。如果登录和注册等多处地方都存在验证码逻辑,就会导致同一个功能多处开发,会出现代码重复度过高。

# 工具类概述

对于一些应用程序中都有的共性的功能,可以将这些功能进行抽取成静态方法,独立封装成工具类,以便其他方法调用。

# 工具类好处

  • 方便使用
  • 提高了代码复用

# 为什么不用实例方法做工具类

实例方法需要创建对象调用这些功能,会浪费内存。

# 扩展

工具类建议将构造器私有

/*
目标:static使用-工具类设计

编写生成验证码的工具类
    1.先使用成员方法
    2.再使用静态成员方法
 */
public class Demo {
    public static void main(String[] args) {
        String code1 = CodeUtils.getCode();
        System.out.println(code1);

        String code2 = CodeUtils.getCode();
        System.out.println(code2);

        String code3 = CodeUtils.getCode();
        System.out.println(code3);
    }
}
import java.util.Random;

public class CodeUtils {
    private CodeUtils() {
    }

    public static String getCode() {
        StringBuilder str = new StringBuilder();
        Random random = new Random();

        for (int i = 0; i < 4; i++) {
            int value = random.nextInt(10);
            str.append(value);
        }

        return str.toString();
    }
}

# 继承

# 继承概述、优点和格式

下面定义类存在的问题:子类的相同内容在每个子类中都需要写一遍,代码冗余。
image.png

# 什么是继承

生活中的继承先有爸爸,后有儿子,儿子继承爸爸的财产。

Java 中的继承:和生活中的继承类似,子类会自动拥有父类的内容。

# Java 中如何使用继承

把子类共性内容抽取到一个类中,子类继承父类。
image.png

# 继承的优点

  • 减少了代码冗余,提高了代码的复用性。
  • 有利于功能的扩展,子类继承父类就得到了父类的功能。

# 继承的格式

public class 子类名 extends 父类名 {}

# 什么时候使用继承?

继承体现的关系:is…a 的关系子类是父类的一种。例如:狗和动物。
image.png
上图中,狗是动物的一种,所以狗可以继承动物。但人不是狗,所以人不能继承狗。

# 继承练习

在王者荣耀游戏中,存在如下非玩家角色:龙、坦克。
角色属性和功能简要清单:

  • 龙(名称,攻击力,攻击,飞行)
  • 坦克(名称,攻击力,攻击,移动)

使用继承完成案例,步骤如下:

  1. 分析时,将子类的共性内容抽取到父类中。
  2. 写代码时,先写父类,后写子类。

image.png

public class Demo {
    public static void main(String[] args) {
        Dragon dragon = new Dragon("飞龙", 100);
        Tank tank = new Tank("小炮车", 50);

        dragon.fly();
        dragon.attack();

        tank.move();
        tank.attack();
    }
}
package demo06继承案例2_王者荣耀;

public class Role {
    String name;
    int attackValue;

    public void attack() {
        System.out.println(name + "开始攻击,攻击力为" + attackValue);
    }
}
public class Dragon extends Role {
    public Dragon(String name, int attackValue) {
        this.name = name;
        this.attackValue = attackValue;
    }

    public void fly() {
        System.out.println(name + "飞过来了");
    }
}
public class Tank extends Role {
    public Tank(String name, int attackValue) {
        this.name = name;
        this.attackValue = attackValue;
    }
    public void move() {
        System.out.println(name + "开过来了");
    }
}

# 子类无法使用的内容

  • 父类的构造器不会继承到子类中。因为构造器名称要和类名相同,而子类和父类类名不一样,父类的构造方法不能继承到子类中来使用。
  • 父类私有的内容子类不能使用。父类私有的内容子类会继承保存,由于 private 关键字导致这个变量只能在本类使用,子类不能使用。

# 子类内存图

image.png

# 继承的特点

  • Java 只支持单继承。子类只能继承一个直接父类,不能同时继承多个父类。

image.png
image.png

  • Java 支持多层继承。A 继承 B ,B 继承 C。一个类不写父类会自动继承 Object。

# 继承后成员变量访问特点

  • 在子类方法中访问一个变量满足:就近原则
    1. 先找子类局部变量
    2. 然后找子类成员变量
    3. 然后找父类成员变量
  • 如果子父类中,出现了重名的成员变量,会优先使用子类的,此时如果一定要使用父类的怎么办?可以通过 super 关键字,指定访问父类的成员。

# 方法重写

什么是方法重写?子类中对父类已有的方法重新写一遍。

方法重写的应用场景:当子类和父类有相同的功能,但父类的功能不满足子类的需求时,子类可以重写父类中的方法。

@Override 注解作用:@Override 是放在重写后的方法上,检验该方法是否是重写方法,加上该注解后如果重写错误,编译阶段会出现错误提示。建议重写方法加上@Override 注解,代码安全!

方法重写注意事项和要求:

  • 是子类和父类之间的事情
  • 方法名要相同
  • 参数列表要相同
  • 返回值类型要相同
  • 子类重写方法的权限要大于等于父类方法的权限(暂时了解 :private < 缺省 < protected < public)

# 继承中构造器的访问特点

继承中构造器的访问特点:子类中所有的构造器默认都会先访问父类中的无参构造器,再执行自己。

为什么要先执行父类构造器:因为子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化。

子类构造器如何调用到父类构造器:子类构造器的第一行语句默认都是:super(),不写也存在。

如果父类中没有无参数构造器,只有有参构造器,会出现什么现象呢?会报错。因为子类默认是调用父类无参构造器的。

如何解决?子类通过 super,手动调用父类的有参数构造器

super 调用父类构造器使用场景:子类构造器可以直接通过调用父类有参数构造器来初始化父类的数据。

继承后构造器的标准做法:

  • 子类无参调用父类无参构造
  • 子类满参构造调用父类满参构造

# this 与 super 总结

this:代表本类对象的引用。
super:代表父类存储空间的标识(可以理解为父类对象引用) 。

关键字 访问成员变量 访问成员方法 访问构造方法
this this.成员变量
访问本类成员变量
this.成员方法(…)
访问本类成员方法
this(…)
访问本类构器
super super.成员变量
访问父类成员变量
super.成员方法(…)
访问父类成员方法
super(…)
访问父类构造器

# 快捷键

4.fori
a.sout