Java面向对象之类变量、类方法、深入理解 main 方法及代码块

1、类变量

1.1、提出问题

有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?

1.1.1、传统方式解决
public class ChildGame_ {
    public static void main(String[] args) {
        // 定义一个变量 count, 统计有多少小孩加入了游戏
        int count = 0;
        Child child1 = new Child("张三");
        child1.joinGame();
        count++;

        Child child2 = new Child("李四");
        child2.joinGame();
        count++;

        Child child3 = new Child("王五");
        child3.joinGame();
        count++;

        System.out.println("共有" + count + "个小孩在游戏中...");
    }
}

class Child {
    private String name;

    public Child(String name) {
        this.name = name;
    }

    public void joinGame() {
        System.out.println(name + "加入了游戏...");
    }
}

在这里插入图片描述

1.1.2、类变量方式解决
public class ChildGame_ {
    public static void main(String[] args) {

        Child child1 = new Child("张三");
        child1.joinGame();

        Child child2 = new Child("李四");
        child2.joinGame();

        Child child3 = new Child("王五");
        child3.joinGame();

        System.out.println("共有" + Child.count + "个小孩在游戏中...");
    }
}

class Child {
    private String name;

    // 定义一个变量 count, 是一个类变量(静态变量) static 静态
    // 该变量最大的特点就是会被 Child 类的所有的对象实例共享
    public static int count = 0;

    public Child(String name) {
        this.name = name;
    }

    public void joinGame() {
        System.out.println(name + "加入了游戏...");
        count++;
    }
}
1.2、类变量内存布局

在这里插入图片描述
在这里插入图片描述

1.3、类变量定义

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量

定义语法:
访问修饰符 static 数据类型 变量名; 【推荐】
static 访问修饰符 数据类型 变量名; 
1.4、如何访问类变量
类名.类变量名 【推荐】
对象名.类变量名 【静态变量的访问修饰符的访问权限和范围和普通属性是一样的】
package visit_static;

public class VisitStatic {
    public static void main(String[] args) {
        // 类名.类变量名
        // 说明: 类变量是随着类的加载而创建, 所以即使没有创建对象实例也可以访问
        System.out.println(A.name);
        A a = new A();
        // 通过对象名.类变量名
        System.out.println("a.name=" + a.name);

        // System.out.println(a.num);  // 报错
    }
}

class A {
    // 类变量
    // 类变量的访问, 必须遵守相关的访问权限
    public static String name = "张三";
    // 普通属性/普通成员变量/非静态属性/非静态成员变量/实例变量
    private int num = 10;
}
1.5、注意事项和细节讨论
  1. 什么时候需要类变量
    当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如定义学生类,统计所有学生共交了多少钱
  2. 类变量与实例变量(普通属性)的区别
    类变量是该类的所有对象共享的,而实例变量是每个对象独享的
  3. 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
  4. 类变量可以通过 类名.类变量名 或者 对象.类变量名 来访问,但java设计者推荐我们使用 类名.类变量方式访问【前提是 满足访问修饰符的访问权限和范围】
  5. 实例变量不能通过 类名.类变量名 方式访问
  6. 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量
  7. 类变量的生命周期是随类的加载开始,随着类消亡而销毁
public class StaticDetail {
    public static void main(String[] args) {
        B b = new B();
        // System.out.println(B.n1);  // 报错
        System.out.println(B.n2);
        // 静态变量是类加载的时候, 就创建了, 所以我们没有创建对象实例
        // 也可以通过类名.类变量名来访问
        System.out.println(C.address);
    }

}

class B {
    public int n1 = 100;
    public static int n2 = 200;
}

class C {
    public static String address = "北京";
}

2、类方法

2.1、类方法定义
类方法也叫静态方法
形式如下:
访问修饰符 static 数据返回类型 方法名() {} 【推荐】
static 访问修饰符 数据返回类型 方法名() {} 
2.2、类方法的调用
使用方式:
类名.类方法名 或者 对象名.类方法名 【前提是满足访问修饰符的访问权限和范围】
package StaticMethod;

public class StaticMethod {
    public static void main(String[] args) {
        // 创建 2 个学生对象, 交学费
        Stu tom = new Stu("tom");
        // tom.payFee(100);
        Stu.payFee(100);
        Stu mary = new Stu("mary");
        // mary.payFee(200);
        Stu.payFee(200);
        // 输出当前收到的总学费
        Stu.showFee();  // 300
        // 如果我们希望不创建实例, 也可以调用某个方法(即当做工具来使用)
        // 这时, 把方法做成静态方法时非常合适
        System.out.println("9 开平方的结果是=" + Math.sqrt(9));
        System.out.println(MyTools.calSum(10, 30));
    }
}

// 开发自己的工具类时, 可以将方法做成静态的, 方便调用
class MyTools {
    // 求出两个数的和
    public static double calSum(double n1, double n2) {
        return n1 + n2;
    }
    // 可以写出很多这样的工具方法...
}

class Stu {
    private String name;  // 普通成员
    // 定义一个静态变量, 来累积学生的学费
    private static double fee = 0;

    public Stu(String name) {
        this.name = name;
    }

    // 说明
    // 1. 当方法使用了 static 修饰后, 该方法就是静态方法
    // 2. 静态方法就可以访问静态属性/变量
    public static void payFee(double fee) {
        Stu.fee += fee;  // 累积
    }

    public static void showFee() {
        System.out.println("总学费有:" + Stu.fee);
    }
}
2.3、经典使用场景

当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提交开发效率
比如:工具类中的方法 utils
Math类、Arrays类、Collections 集合类
小结:
在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等

2.4、注意事项和细节讨论
  1. 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
    类方法中无this的参数
    普通方法中隐含着this的参数
  2. 类方法可以通过类名调用,也可以通过对象名调用
  3. 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
  4. 类方法中不允许使用和对象有关的关键字,比如this和super,普通方法(成员方法)可以
  5. 类方法(静态方法)中只能访问静态变量 或者 静态方法
  6. 普通成员方法,既可以访问非静态成员,也可以访问静态成员

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

public class StaticMethodDetail {
    public static void main(String[] args) {
        D.hi(); // ok
        // 非静态方法, 不能通过类名调用
        // D.say();  // 错误, 需要先创建对象, 再调用
        new D().say();  // ok
    }
}

class D {
    private int n1 = 100;
    private static int n2 = 200;

    public void say() {  // 非静态方法, 普通方法
    }

    public static void hi() {  // 静态方法, 类方法
        // 类方法中不允许使用和对象有关的关键字
        // 比如 this 和 super 普通方法(成员方法)可以
        // System.out.println(this.n1);  // 报错
    }

    // 类方法(静态方法)中 只能访问 静态变量 或静态方法
    // 静态方法只能访问静态成员.
    public static void hello() {
        // System.out.println(n1);  // 报错
        System.out.println(n2);
        System.out.println(D.n2);

        // System.out.println(this.n2);  // 报错 不能使用
        hi();  // OK
        // say();  // 错误
    }

    //普通成员方法, 既可以访问 非静态成员, 也可以访问静态成
    //小结: 非静态方法可以访问 静态成员和非静态成员
    public void ok() {
        // 非静态成员
        System.out.println(n1);
        say();
        // 静态成员
        System.out.println(n2);
        hello();
    }
}

3、深入理解 main 方法

在这里插入图片描述

3.1、说明
  1. 在 main()方法中,我们可以直接调用 main 方法所在类的静态方法或静态属性
  2. 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
package main01;

public class Main01 {
    // 静态的变量/属性
    private static String name = "hello world";

    // 非静态的变量/属性
    private int n1 = 10000;

    // 静态方法
    public static void hi() {
        System.out.println("Main01 的 hi 方法");
    }

    // 非静态方法
    public void cry() {
        System.out.println("Main01 的 cry 方法");
    }

    public static void main(String[] args) {
        // 可以直接使用 name
        // 1. 静态方法 main 可以访问本类的静态成员
        System.out.println("name=" + name);
        hi();
        // 2. 静态方法 main 不可以访问本类的非静态成员
        // System.out.println("n1=" + n1);  // 报错
        // cry();  // 报错
        // 3. 静态方法 main 要访问本类的非静态成员, 需要先创建对象, 再调用即可
        Main01 main01 = new Main01();
        System.out.println(main01.n1);
        main01.cry();
    }
}
3.2、给main方法传递参数
public class TestMain {
	public static void main(String[] args) {
		for (int i=0; i<args.length; i++) {
			System.out.print(args[i] + "\t");
		}
	}
}

在这里插入图片描述
在ide中如何传递参数
在这里插入图片描述
在这里插入图片描述

4、代码块

4.1、定义

在这里插入图片描述

4.2、基本语法
[修饰符] {
	代码
};
  1. 修饰符可选,要写的话,也只能写static
  2. 代码块分为两类,使用static 修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块
  3. 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
  4. ;号可以写上,也可以省略
4.3、代码块的优势
  1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
  2. 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
4.4、代码实现
package CodeBlock01;

public class CodeBlock01 {
    public static void main(String[] args) {
        new Movie("扬名立万");
        System.out.println("========");
        new Movie("唐探 3", 100, "陈思诚");
    }
}

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

    // 3个构造器 -> 重载
    // (1) 下面的三个构造器都有相同的语句
    // (2) 这样代码看起来比较冗余
    // (3) 这时我们可以把相同的语句, 放入到一个代码块中即可
    // (4) 这样当我们不管调用哪个构造器, 创建对象, 都会先调用代码块的内容
    // (5) 代码块调用的顺序优先于构造器
    {
        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) {
        System.out.println("Movie(String name, double price, String director) 被调用...");
        this.name = name;
        this.price = price;
        this.director = director;
    }
}
4.5、注意事项和细节讨论
  1. static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次,如果是普通代码块,每创建一个对象就执行
  2. 类什么时候被加载【重要!!!】
    (1)创建对象实例时(new)
    (2)创建子类对象实例,父类也会被加载
    (3)使用类的静态成员时(静态属性,静态方法)
    【 A 类 extends B类 的静态块 】
  3. 普通的代码块,在创建对象实例时,会被隐式的调用
    被创建一次,就会调用一次
    如果只是使用类的静态成员时,普通代码块并不会执行
  4. 小结:
    (1)static代码块是类加载时执行,只会执行一次
    (2)普通代码块是在创建对象时调用的,创建一次,调用一次
    (3)类加载的三种情况
  5. 创建一个对象时,在一个类的调用顺序
    (1)调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
    (2)调用普通代码块和普通属性初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按他们定义的顺序调用)
    (3)调用构造方法
  6. 构造器的最前面其实隐含了 super() 和 调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
  7. 创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
    (1)父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    (2)子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
    (3)父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    (4)父类的构造方法
    (5)子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
    (6)子类的构造方法
  8. 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员

第 1 - 4点的代码说明

package CodeBlockDetail01;

public class CodeBlockDetail01 {
    public static void main(String[] args) {
        // 类被加载的情况举例
        // 1. 创建对象实例时(new)
        AA aa = new AA();
        /*
        BB 的静态代码 1 被执行...
        AA 的静态代码 1 被执行...
        */

        // 2. 创建子类对象实例, 父类也会被加载, 而且, 父类先被加载, 子类后被加载
        AA aa2 = new AA();
        /*
        static 代码块, 是在类加载时, 执行的, 而且只会执行一次
        由于代码第七行已经执行过, 不会重复执行
        */

        // 3. 使用类的静态成员时(静态属性, 静态方法)
        System.out.println(Cat.n1);
        /*
        Animal 的静态代码 1 被执行...
        Cat 的静态代码 1 被执行...
        999
        */

        DD dd = new DD();
        /*
        DD 的静态代码 1 被执行...
        DD 的普通代码块...
        */

        // 普通的代码块, 在创建对象实例时, 会被隐式的调用
        // 被创建一次, 就会调用一次
        DD dd1 = new DD();  // DD类中的静态代码块在28行已经执行过, 所以静态代码块的内容不会输出
        /*
        DD 的普通代码块...
        */

        // 如果只是使用类的静态成员时, 普通代码块并不会执行
        System.out.println(DD.n1);  // 8888, 静态模块块一定会执行, 由于该类的静态代码块在28行已经执行过, 所以静态代码块的内容不会输出
        /*
        8888
        */
    }
}

class DD {
    public static int n1 = 8888;  // 静态属性

    // 静态代码块
    static {
        System.out.println("DD 的静态代码 1 被执行...");
    }

    // 普通代码块, 在 new 对象时, 被调用, 而且是每创建一个对象, 就调用一次
    // 可以这样简单的理解, 普通代码块是构造器的补充
    {
        System.out.println("DD 的普通代码块...");
    }
}

class Animal {
    // 静态代码块
    static {
        System.out.println("Animal 的静态代码 1 被执行...");
    }
}

class Cat extends Animal {
    public static int n1 = 999;  // 静态属性

    // 静态代码块
    static {
        System.out.println("Cat 的静态代码 1 被执行...");
    }
}

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

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

第 5 点的代码说明

package CodeBlockDetail01;

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        A a = new A();
        /*
        1. A 静态代码块 01
        2. getN1 被调用...
        3. A 普通代码块 01
        4. getN2 被调用...
        5. A() 构造器被调用
        */
    }
}

class A {
    {  // 普通代码块
        System.out.println("A 普通代码块 01");
    }

    private int n2 = getN2();  // 普通属性的初始化

    static {  // 静态代码块
        System.out.println("A 静态代码块 01");
    }

    // 静态属性的初始化
    private static int n1 = getN1();

    public static int getN1() {
        System.out.println("getN1 被调用...");
        return 100;
    }

    public int getN2() {  // 普通方法/非静态方法
        System.out.println("getN2 被调用...");
        return 200;
    }

    // 无参构造器
    public A() {
        System.out.println("A() 构造器被调用");
    }
}

第 6 点的代码说明

package CodeBlockDetail01;

public class CodeBlockDetail03 {
    public static void main(String[] args) {
        new BBB();
        /*
        1. AAA 的普通代码块
        2. AAA() 构造器被调用....
        3. BBB 的普通代码块...
        4. BBB() 构造器被调用....
        */
    }
}

class AAA {  // 父类 Object
    {
        System.out.println("AAA 的普通代码块");
    }

    public AAA() {
        // (1) super()
        // (2) 调用本类的普通代码块
        System.out.println("AAA() 构造器被调用....");
    }
}

class BBB extends AAA {
    {
        System.out.println("BBB 的普通代码块...");
    }

    public BBB() {
        // (1)super()
        // (2)调用本类的普通代码块
        System.out.println("BBB() 构造器被调用....");
    }
}

第 7 - 8点的代码说明

package CodeBlockDetail01;

public class CodeBlockDetail04 {
    public static void main(String[] args) {
        // (1) 进行类的加载
        // 1.1 先加载 父类 A02
        // 1.2 再加载 B02
        // (2) 创建对象
        // 2.1 从子类的构造器开始
        // new B02();
        /*
        1. getVal01
        2. A02 的一个静态代码块..
        3. getVal03
        4. B02 的一个静态代码块..
        5. A02 的第一个普通代码块..
        6. getVal02
        7. A02 的构造器
        8. getVal04
        9. B02 的第一个普通代码块..
        10. B02 的构造器
        */

        new C02();
    }
}

class A02 {  // 父类
    private static int n1 = getVal01();

    static {
        System.out.println("A02 的一个静态代码块..");
    }

    {
        System.out.println("A02 的第一个普通代码块..");
    }

    public int n3 = getVal02();  // 普通属性的初始化

    public static int getVal01() {
        System.out.println("getVal01");
        return 10;
    }

    public int getVal02() {
        System.out.println("getVal02");
        return 10;
    }

    public A02() {  // 构造器
        // 隐藏
        // super()
        // 普通代码和普通属性的初始化......
        System.out.println("A02 的构造器");
    }
}

class C02 {
    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();
    }

    {
        // 普通代码块, 可以使用任意成员
        System.out.println(n1);
        System.out.println(n2);  // ok
        m1();
        m2();
    }
}

class B02 extends A02 {
    private static int n3 = getVal03();

    static {
        System.out.println("B02 的一个静态代码块..");
    }

    public int n5 = getVal04();

    {
        System.out.println("B02 的第一个普通代码块..");
    }

    public static int getVal03() {
        System.out.println("getVal03");
        return 10;
    }

    public int getVal04() {
        System.out.println("getVal04");
        return 10;
    }

    public B02() {  // 构造器
        // 隐藏了
        // super()
        // 普通代码块和普通属性的初始化...
        System.out.println("B02 的构造器");
        // TODO Auto-generated constructor stub
    }
}
package CodeBlockDetail01;

class Test {
    Sample sam1 = new Sample("sam1 成员初始化");
    static Sample sam = new Sample("静态成员 sam 初始化 ");

    static {
        System.out.println("static 块执行");
        if (sam == null) System.out.println("sam is null");
    }

    Test()  // 构造器
    {
        System.out.println("Test 默认构造函数被调用");
    }

    //主方法
    public static void main(String str[]) {
        Test a = new Test();  // 无参构造器
        /*
        1. 静态成员 sam 初始化
        2. static 块执行
        3. sam1 成员初始化
        4. Test 默认构造函数被调用
        */
    }
}

class Sample {
    Sample(String s) {
        System.out.println(s);
    }

    Sample() {
        System.out.println("Sample 默认构造函数被调用");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值