JAVA笔记(十三):类变量和类方法、main方法深入理解、代码块、单例设计模式、final关键字、抽象类抽象方法

类变量和类方法

1、基本介绍

  1. 类变量 (静态变量)可以被所有实例共享
    在这里插入图片描述
    实例child1和child2都共享count空间
    静态空间在堆还是在方法区里,取决于jdk版本

jdk8以前,在方法区静态域里
jdk8以后,在堆里

但是不管static变量在哪里,总有这样的共识:
①static变量是同一个类所有对象共享的
②static类变量,在类加载的时候就生成了

  1. 如何访问类变量
    类名.类变量名
    或者 对象名.类变量名

  2. 什么时候需要用类变量
    当所有对象都需要共享一个变量时,就可以考虑使用类变量

  3. 类变量与实例变量的区别
    类变量是该类的所有对象共享的,而实例变量是每个对象独享的。

  4. 加上static成为类变量或静态变量,否则称为实例变量/普通变量/非静态变量

  5. 静态变量在类加载时就初始化
    即使没有创建对象,只要类加载了,就可以使用类变量了

  6. 类变量的生命周期随着类的加载开始,随着类消亡而销毁
    对象销毁了,静态变量仍然存在

注意:类变量的访问,必须遵守相关的访问权限

比如 private static,无法在本类外访问

2、静态方法

  1. 当方法使用了static修饰,该方法就是静态方法
  2. 静态方法可以调用静态变量
  3. 什么时候适合用类方法
    如果我们希望不创建实例,也可以调用某个方法(即当作工具来使用),则适合用静态方法
  4. 类方法和普通方法都是随着类的加载而加载,将信息结构存储在方法区:
  • 类方法中无this的参数(类方法不用创建对象)
  • 普通方法中隐含着this的参数
  1. 类方法中不允许使用和对象有关的关键字,比如this和super

  2. 类方法(静态方法)只能访问静态变量或静态方法

  3. 普通成员方法,既可以访问普通变量(方法),也可以访问静态变量(方法)

小结:静态方法,只能访问静态的成员,非静态方法,可以访问静态成员和非静态成员(必须遵守访问权限)

3、作业

作业1

public class Test {
    public static void main(String[] args){
        System.out.println("total=" + Person.getTotalPerson()); //0,因为此时还没有创建对象,没创建对象就不会调用构造器
        Person p1 = new Person(); //创建对象,调用了构造器
        System.out.println("total=" + Person.getTotalPerson()); //1
    }
}

class Person {
    private int id;
    private static int total = 0;
    public static int getTotalPerson(){
        //id++; //错误,静态方法不能访问非静态变量
        return total;
    }
    public Person(){ //构造器也是非静态方法
        total++;
        id = total;
    }
}

作业2

public class Test {
    public static void main(String[] args){
        Person.setTotalPerson(3);
        new Person();
        System.out.println("total=" + Person.getTotal()); //4
    }
}

class Person {
    private int id;
    private static int total = 0;
    public static int getTotal(){
        return total;
    }
    public static void setTotalPerson(int total){
        this.total = total; //错误,static方法中不能用this
        Person.total = total;
    }
    public Person(){ //构造器也是非静态方法
        total++;
        id = total;
    }
}

main方法深入理解

main方法的形式:public static void main(String[] args){}

1、为什么是public

main方法是虚拟机调用 (java虚拟机和main方法不在同一个包)==》解释为什么是public

Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public

2、为什么是static

java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static

3、怎么理解String[] args

main方法接收String类型的数组参数,该数组中保存执行java命令时传递给运行的类的参数

args如何传参
1)命令行:

java 运行的程序(类名) 参数1 参数2 参数3
在这里插入图片描述
2) idea中
右上角运行模块倒三角 -> Edit Configurations -> Program arguments ->
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4、 注意事项

  1. 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
  2. 不能直接访问该类中的非静态成员,必须创建该类的实例对象,再去调用

代码块

1、基本介绍

代码块又称初始化块,属于类中的成员。

没有方法名,没有返回,没有参数,只有方法体。

不用通过对象或类显示调用,而是加载类时,或创建对象时隐式调用。(静态代码块在类加载时就会调用一次,且仅调用一次)(普通代码块在创建对象时就会调用一次,可调多次)

2、基本语法

[修饰符]{
代码
};

3、注意

  1. 修饰符可写可不写。如果写,只能写static
  2. 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块
  3. 代码块内容可以为任意逻辑语句(输入、输出、方法调用、循环、判断等)
  4. ;号可以写上也可以省略

4、对代码块的理解

  1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化操作
  2. 如果多个构造器都有重复的语句,则可以整合到代码块中,提高复用性
public class Test {
    public static void main(String[] args) {
        Movie aa = new Movie("aa");
        Movie bb = new Movie("bb", 30);
        Movie cc = new Movie("cc", 40, "Leo");
    }
}

class Movie {
    private String name;
    private double price;
    private String director;

    //下面三个构造器都有相同的语句,冗余
    //可以把相同的语句,放入到一个代码块中
    //这样,当我们不管调用哪个构造器,创建一个新对象,就会调用一次代码块中的内容
    public Movie(String name) {
        System.out.println("电影屏幕打开...");
        System.out.println("广告开始...");
        System.out.println("电影正式开始");
        this.name = name;
    }

    public Movie(String name, double price) {
        System.out.println("电影屏幕打开...");
        System.out.println("广告开始...");
        System.out.println("电影正式开始");
        this.name = name;
        this.price = price;
    }

    public Movie(String name, double price, String director) {
        System.out.println("电影屏幕打开...");
        System.out.println("广告开始...");
        System.out.println("电影正式开始");
        this.name = name;
        this.price = price;
        this.director = director;
    }
}

整合后:

public class Test {
    public static void main(String[] args) {
        Movie aa = new Movie("aa");
        Movie bb = new Movie("bb", 30);
        Movie cc = new Movie("cc", 40, "Leo");
    }
}

class Movie {
    private String name;
    private double price;
    private String director;

    {
        System.out.println("电影屏幕打开...");
        System.out.println("广告开始...");
        System.out.println("电影正式开始");  
    };
    //下面三个构造器都有相同的语句,冗余
    public Movie(String name) {
    System.out.println("构造器Movie(String name)被调用");
        this.name = name;
    }

    public Movie(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public Movie(String name, double price, String director) {
        this.name = name;
        this.price = price;
        this.director = director;
    }
}

  1. 代码块的调用优先于构造器
    在这里插入图片描述

5、使用细节(类加载)⭐⭐⭐⭐⭐

1) static代码块只执行一次,普通代码块可执行多次

static代码块也叫静态代码块。作用就是对类进行初始化,随着类加载而执行。并且只会执行一次。如果是普通代码块,每创建一个对象,就执行一次。

2) 类在什么时候被加载⭐⭐⭐

① 创建对象实例时(new一个对象)
② 创建子类对象实例,父类也会被加载(且父类先加载)
③ 使用类的静态成员时(静态属性,静态方法)(如果static和final一起用,则类不会被加载)

public class Test {
    public static void main(String[] args) {
        //类什么时候被加载
        //1. 创建对象实例
        AA aa = new AA();
        System.out.println("================");

        //2. 创建子类实例,父类也被加载,并且先加载父类再加载子类
        CC cc = new CC();
        System.out.println("================");

        //3.使用类的静态成员(属性方法)时
        System.out.println(DD.n1);
        System.out.println("================");

        //4.所有静态代码块只执行一次
        System.out.println(BB.n1);
    }
}

class AA {
    //静态代码块
    static {
        System.out.println("AA的静态代码块被执行");
    }
}
class BB {
    public static int n1 = 999; //静态属性
    static {
        System.out.println("BB的静态代码块被执行");
    }
}

class CC extends BB{
    static {
        System.out.println("CC的静态代码块被执行");
    }
}

class DD {
    public static int n1 = 999; //静态属性
    static {
        System.out.println("DD的静态代码块被执行");
    }
}

输出:
在这里插入图片描述

3) 普通代码块,在创建对象实例时被调用

创建对象时普通代码块会被隐式调用。被创建一次就会调用一次。

如果只是使用类的静态成员时,普通代码块不会执行。

**普通代码块与类创建无关**!只有在创建对象实例(new)才会调用普通代码块
public class Test {
    public static void main(String[] args) {
        //创建多个对象
        //创建对象时,同时进行类加载。原先没调用过静态代码块,这里先调用静态代码块
        DD dd = new DD();
        DD dd1 = new DD();
        System.out.println("================");
        //使用类的静态成员(属性方法)时,没有创建对象,不会调用普通代码块
        System.out.println(DD.n1);
    }
}

class DD {
    public static int n1 = 999; //静态属性
    static {
        System.out.println("DD的静态代码块被执行");
    };

    {
        System.out.println("DD的普通代码块被执行");
    };
}

输出:
在这里插入图片描述
小结:
① static代码块是类加载时执行,只会执行一次
② 普通代码块是在创建对象时调用,创建一次,调用一次
③ 类加载的3种情况(new、子父类、调用静态成员)

4) 创建一个对象时,在一个类的调用顺序⭐⭐⭐⭐⭐⭐

调用静态代码块和静态属性初始化。
静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用

优先级一样,谁在前面,谁先调用
public class Test {
    public static void main(String[] args) {
        A a = new A();
    }
}

class A {
    private static int n1 = getN1();
    static {
        System.out.println("A静态代码块01");
    }
    public static int getN1() {
        System.out.println("getN1被调用...");
        return 100;
    }
}
//输出:
getN1被调用...
A静态代码块01

n1 和static优先级一样,n1在前面,所以n1先调用

调用普通代码块和普通属性的初始化

普通代码块和普通属性初始化调用的优先级一样,如果多个普通代码块和多个普通属性初始化,则按定义顺序调用

调用构造方法

package A;

public class Test {
    public static void main(String[] args) {
        A a = new A();
    }
}

class A {
    private int n2 = getN2();
    private static int n1 = getN1();
    public A(){
        System.out.println("A()构造器01");
    }

    {
        System.out.println("A普通代码块01");
    }
    static {
        System.out.println("A静态代码块01");
    }
    public static int getN1() {
        System.out.println("getN1被调用...");
        return 100;
    }
    public static int getN2() {
        System.out.println("getN2被调用...");
        return 200;
    }
}

输出:
在这里插入图片描述

顺序:static属性或代码块 -> 普通属性或代码块 -> 构造器

5) 构造器的最前面隐含了super()和、调用普通代码块和初始化普通属性

所以普通代码块优先于构造器执行

而静态static相关属性和代码块,在类加载时就执行完毕,所以优先于普通代码块和构造器
public A(){
	//这里有隐藏的两个执行要求:
	//(1)super(); //在没有this和其他super构造器时默认
	//(2)调用本类普通代码块,初始化普通属性
	System.out.println("A构造器被调用")
}

6) 创建一个子类对象实例时,调用顺序⭐⭐⭐⭐⭐

在这里插入图片描述
核心:父类构造器优先于子类普通代码块(父类122)
原理:

  1. 先类加载(先加载父类再加载子类)

  2. 然后创建对象,先创建子类对象,调用子类构造器,但构造器中隐含调用父类构造器

  3. 父类构造器,需要先调用本类普通代码块,初始化普通属性

  4. 接着才是执行本类构造器里剩余的内容

  5. 此时父类构造器执行完毕,返回子类构造器,重复上面34两步

package A;

public class Test {
    public static void main(String[] args) {
        //1 先进行类加载:先加载父类,后加载子类
        //1.1 n1getVal01 1.2 A静态代码块 1.3 n3getVal03 1.4 B静态代码块
        //2 再创建对象:从子类构造器开始->super()->super()中同样隐藏了super()和普通代码块
        B b = new B();
    }
}

class A {
    private static int n1 = getVal01();
    static {
        System.out.println("A的一个静态代码块");//(2)
    }
    {
        System.out.println("A的普通代码块"); //(5.4)=>(5)
    }
    public int n2 = getVal02(); //(6.1)
    public static int getVal01(){
        System.out.println("getVal01"); //(1)
        return 10;
    }
    public int getVal02(){
        System.out.println("getVal02");//(6.2)=>(6)
        return 10;
    }
    public A(){
        //隐藏了
        //super() (5.2)
        //本类普通属性及代码块 (5.3)
        System.out.println("A的构造器");//(7)
    }
}

class B extends A{
    private static int n3 = getVal03();
    static {
        System.out.println("B的一个静态代码块");//(4)
    }
    {
        System.out.println("B的普通代码块");//(8.2)=>(8)
    }
    public int n4 = getVal04();//(9.1)
    public static int getVal03(){
        System.out.println("getVal03");//(3)
        return 10;
    }
    public int getVal04(){
        System.out.println("getVal04");//(9.2)=>(9)
        return 10;
    }
    public B(){
        //隐藏了
        //super() (5.1)
        //本类普通属性及代码块(8.1)
        System.out.println("B的构造器");//(10)
    }
}

输出:
在这里插入图片描述

  1. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用静态也可以调用普通成员
package A;

public class Test {
    public static void main(String[] args) {

    }
}

class C {
    private int n1 = 100;
    private static int n2 = 200;
    private void m1(){}
    private static void m2(){}

    static {
        System.out.println(n1); //报错
        System.out.println(n2); //ok
        m1();//报错
        m2();//ok
    }

    {
        System.out.println(n1); //ok
        System.out.println(n2); //ok
        m1();//ok
        m2();//ok
    }
}

6、 课堂练习

在这里插入图片描述
输出:

静态成员sam初始化
static块执行
sam1成员初始化
Test默认构造器被调用

单例设计模式

1、什么是单例模式

采取一定方法,保证在整个软件系统中,对某个类只能存在一个对象实例。并且该类只提供一个取得其对象实例的方法

2、饿汉式

  1. 构造器私有化 =》 防止直接new一个对象
  2. 类的内部创建对象
  3. 向外暴露一个静态的公共方法 getInstance
public class Test{
    public static void main(String[] args) {
    //  GirlFriend xm = new GirlFriend("小美");
        GirlFriend instance = GirlFriend.getInstance();
        System.out.println(instance);
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);
    }
}

//类,GirlFriend
//只能有一个女朋友
class GirlFriend {
    private String name;
    //内部创建一个类(为了能够使用static方法,这里必须加上static修饰符)
    private static GirlFriend gf = new GirlFriend("小美美");
    //构造器私有化
    private GirlFriend(String name) {
        this.name = name;
    }
    //提供公共的static方法,返回gf对象(方法必须静态,因为不能通过创建对象来调用方法)
    public static GirlFriend getInstance(){
        return gf;
    }

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

饿汉式特点:在还没有使用对象时,对象就已经创建好了

3、懒汉式

  1. 构造器私有化 => 防止直接new一个对象
  2. 定义一个static静态属性对象
  3. 提供一个public的static方法,可以返回一个GirlFriend对象
public class Test{
    public static void main(String[] args) {
        GirlFriend gf = GirlFriend.getInstance();
        System.out.println(gf);
        //后续再调用getInstance方法时,不会再重新创建一个对象(即gf != null)、
        GirlFriend gf2 = GirlFriend.getInstance();
        System.out.println(gf2);
        
        System.out.println(gf == gf2); //true
    }
}

//类,GirlFriend
//只能有一个女朋友
class GirlFriend {
    private String name;
    //2. 定义一个static静态属性对象
    private static GirlFriend gf;
    //1. 构造器私有化
    private GirlFriend(String name) {
        this.name = name;
    }
    //提供公共的static方法,可以返回一个GirlFriend对象
    public static GirlFriend getInstance(){
        if(gf == null) {//
            gf = new GirlFriend("小美");
        }
        return gf;
    }

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

懒汉式特点:在使用对象时才会创建对象,后面再次调用时,会返回上次创建的对象

4、饿汉式vs懒汉式

在这里插入图片描述

final关键字

1、final关键字的特点:

  1. final修饰的类不能被继承
  2. 父类某个方法被final修饰,不能被子类重写
  3. final修饰的类属性,其值不能被修改
  4. final修饰的局部变量不能被修改

2、final使用注意事项

  1. final修饰的属性又叫常量,一般用XX_XX_XX来命名

  2. final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一
    ① 定义时:如public final double TAX_RATE = 0.08
    ② 在构造器中
    ③ 在代码块中

  3. 如果final修饰的属性是静态的,则初始化的位置只能是
    ① 定义时
    ② 在静态代码块
    (不能在构造器中赋值)(因为构造器在创建对象时才会被调用,而类加载比创建对象更先一步)

class A {
    public static final double TAX_RATE = 0.08;
    public static final double TAX_RATE2;

    static {
        TAX_RATE2 = 0.33;
    }
}
  1. final类不能继承,但是可以实例化对象

  2. 如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承

  3. 一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法了(无法继承,自然也就无法重写)

  4. final和static往往搭配使用,效率更高。不会导致类加载(单独使用static,调用静态属性或方法时,类会被加载)

public class Test{
    public static void main(String[] args) {
        System.out.println(A.i);
    }
}

class A {
    public static final int i = 1;
    static {
        System.out.println("类被加载");
    }
}

//输出:
1
  1. 包装类(Integer、Double、 Float、Boolean等都是final),String也是final类

3、练习

  1. 判断正误
public int addOne(final int x){ //true
	++x;  //错,不能修改final修饰的值
	return x + 1;  //true,没有修改x的值
}

抽象类

1、抽象类引入

在父类中定义一些没有实现的方法,由子类去实现

2、抽象类的使用

用abstract修饰父类中不确定的方法 =》 这个方法就是抽象方法

当一个类中存在抽象方法,其类必须用abstract修饰(变成抽象类)

抽象方法没有方法体

3、注意事项

  1. 抽象类不能实例化(抽象类是一个结构和功能“不完整”的类,如果强行创建对象,不能实现相关功能)
  2. 抽象类不一定要包含abstract方法,即抽象类可以没有抽象方法
  3. abstract只能修饰类和方法,不能修饰属性和其他的
  4. 抽象类可以有任意类成员(抽象类的本质还是一个类)
  5. 抽象方法不能有主体,即不能实现
  6. 如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
abstract class A {
   public abstract void hi();
}
abstract class B extends A {
    @Override
    public void hi(){ //重写父类抽象方法,即实现这个抽象方法
        
    }
}
  1. 抽象方法不能使用private、final和static来修饰,因为这些关键词都是和重写相违背的
  • private :没有机会重写
  • final:子类不能重写父类的该方法
  • static:static修饰的方法可以直接被类所调用,而abstract修饰的抽象方法没有实现(无方法体),所以不能被调用

4、抽象类实践:模板设计

要求:

  1. 有多个类,完成不同的人物job
  2. 统计得到各自完成任务的时间

Test.java(包含AA、BB子类和main方法)

public class Test{
    public static void main(String[] args) {
        AA aa = new AA();
        aa.calculateTime();

        BB bb = new BB();
        bb.calculateTime();
    }
}

class AA extends Template{

    //任务:1+2+...+1000000
    @Override
    public void job(){
        long num = 0;
        for(long i = 1 ; i <= 1000000 ; i++){
            num += i;
        }
    }

    @Override
    public String getClassName(){
        return "AA";
    }
}

class BB extends Template{
    //任务:1*2*...*10000
    @Override
    public void job(){
        long num = 0;
        for(long i = 1 ; i <= 1000000 ; i++){
            num *= i;
        }
    }

    @Override
    public String getClassName(){
        return "BB";
    }
}

Template.java(包含统一的内容)

abstract public class Template {
    public abstract void job(); //抽象方法
    public abstract String getClassName();
    
    public void calculateTime(){ //实现方法,调用了job抽象方法
        //得到开始时间
        long start = System.currentTimeMillis();

        job(); //动态绑定机制,执行到此时,会从子类开始调用

        //得到结束时间
        long end = System.currentTimeMillis();
        System.out.println(getClassName() + " 执行时间:" + (end - start));
    }
}

输出:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值