JavaSE——面向对象9:static、final关键字、代码块、单例模式

目录

一、类变量(静态变量)

(一)问题引入

(二)类变量快速入门

(三)类变量的内存布局

(四)类变量的定义与访问

1.类变量定义

2.类变量的定义语法

3.访问类变量

(五)类变量使用注意事项和细节

二、类方法(静态方法)

(一)类方法基本介绍

(二)类方法使用场景

(三)注意事项和使用细节

三、理解main方法语法

四、代码块

(一)基本介绍

(二)基本语法

(三)代码块的作用

(四)注意事项与使用细节

(五)练习题

1.题目1

2.题目2

五、static在设计模式的经典应用——单例模式

(一)饿汉式

(二)懒汉式

(三)饿汉式VS懒汉式区别

六、final关键字

(一)基本介绍

(二)使用注意事项和细节讨论 

(三)练习题

1.题目1

2.题目2


一、类变量(静态变量)

(一)问题引入

        现在有一个需求:有一群孩子在玩耍,不断有新的孩子加入,请问如何知道现在共有多少孩子在玩?按照以往的思路,代码实现如下:

public class ChildGame {
    public static void main(String[] args) {
        int count = 0;
        Child child1 = new Child("张三");
        child1.join();
        count++;

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

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

        System.out.println("共有" + count + "个人加入了游戏");
    }
}

class Child {
    private String name;

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

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

运行结果:

从代码中我么可以看出,count这个变量是一个独立于对象的变量,外界可能无法访问,没有使用到OOP。因此,针对这个问题,可以用类变量/静态变量来解决、

(二)类变量快速入门

思考:如果设计一个int count表示总人数,在创建一个对象时,就把count加1,并且count是所有对象共享的就ok了。使用类变量解决上述问题如下:

public class ChildGame {
    public static void main(String[] args) {
//        int count = 0;
        Child child1 = new Child("张三");
        child1.join();
//        count++;
        child1.count++;

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

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

        // 类变量可以通过类名来访问
        System.out.println("共有" + Child.count + "个人加入了游戏");

        System.out.println("child1.count==" + child1.count);
        System.out.println("child2.count==" + child2.count);
        System.out.println("child3.count==" + child3.count);
    }
}

class Child {
    // 定义一个变量count,该变量是一个类变量(静态变量)  
    // static修饰的就是静态的
    public static int count = 0;

    private String name;

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

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

(三)类变量的内存布局

static修饰的变量成为类变量/静态变量,该变量在不同的JDK版本中,存放的位置不同:

  • Java 6及之前的版本:JVM使用了方法区来存储静态变量。
  • Java 8及之后的版本:静态变量被存储在中。

但是不管static变量在哪里,我们需要记住的是:

  • static变量是同一个类中所有对象共享
  • static变量在类加载的时候就生成了

(四)类变量的定义与访问

1.类变量定义

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

2.类变量的定义语法

  • 访问修饰符 static 数据类型 变量名;  [推荐]
  • static 访问修饰符 数据类型 变量名;  [不推荐]

3.访问类变量

  • 类名.类变量名 [推荐]
  • 或者 对象名.类变量名 [不推荐]
  • 注意:静态变量的访问修饰符的访问权限和范围 和 普通属性时一样的!
public class VisitStatic {
    public static void main(String[] args) {
        // 类变量是随着类的加载而创建的,所以即使没有创建对象实例也可以访问
        System.out.println(A.name); // 张三

        A a = new A();
        System.out.println(a.name); // 张三
    }
}

class A {
    public static String name = "张三";
    
    // private修饰的类变量外界无法访问
    // private static String name = "张三";
}

(五)类变量使用注意事项和细节

  1. 什么时候需要使用类变量:当我们需要让某个类的所有对象都共享同一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。Student(name,static double fee)
  2. 类变量与实例变量(普通属性)的区别:类变量是该类的所有对象共享的,而普通属性是每个对象独享的。
  3. 加上static的变量称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
  4. 类变量可以通过 类名.类变量名 或者 对象名.类变量名  来访问,但是推荐使用 类名.类变量名。[前提是:满足访问修饰符的访问权限和范围]
  5. 实例变量不能通过 类名.类变量名 方式访问。
  6. 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了。
  7. 类变量的生命周期是随类的加载开始,随着类消亡而销毁。

二、类方法(静态方法)

(一)类方法基本介绍

类方法也叫静态方法,形式如下:

  • 访问修饰符 static 数据返回类型 方法名(){}  [推荐]
  • static 访问修饰符 数据返回类型 方法名(){}

类方法的调用:类名.方法名        注意要满足访问修饰符的访问权限和范围

类方法调用代码示例:

public class StaticDetail {
    public static void main(String[] args) {
        Stu tom = new Stu("tom");
        Stu.payFee(100);
        Stu marry = new Stu("marry");
        Stu.payFee(200);

        Stu.showFee(); // 总学费:300.0
    }
}

class Stu {
    private String name;

    private static double fee;

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

    public static void payFee(double fee) {
        Stu.fee += fee;
    }

    public static void showFee() {
        System.out.println("总学费:" + Stu.fee);
    }
}

(二)类方法使用场景

        当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率。比如:工具类中的方法utils:Math类、Arrays类、Collections集合类等。

        开发过程中,如果我们希望不创建对象实例,也可以调用某个方法(即当做工具来使用),这时,把方法做成静态方法是非常合适的。,比如打印一堆数组,冒泡排序,完成某个计算任务等。

(三)注意事项和使用细节

  1. 类方法和普通方法,都是随着类的加载而加载,将结构信息存储在方法区,类方法中不能使用this和super关键字,普通方法中可以;
  2. 类方法可以通过类名调用,也可以通过对象名调用
  3. 普通方法和对象有关,需要通过对象名调用(对象名.方法名(参数)),不能通过类名调用。
  4. 类方法(静态方法)中只能访问静态变量或静态方法
  5. 普通成员方法,既可以访问普通成员(属性/方法),也可以访问静态成员(静态成员属性和静态成员方法)。如下图。

小结:

  • 静态方法,只能访问静态成员;
  • 非静态方法,可以访问所有成员;
  • 在编写代码时,仍然要遵守访问权限规则。

小练习:判断下面代码的输出结果:

public class StaticMethodDetail {
    public static void main(String[] args) {
        new Test().count(); // count=9
        new Test().count(); // count=10
        System.out.println(Test.count); // 11
    }
}

class Test {
    static int count = 9;

    public void count() {
        System.out.println("count=" + (count++));
    }
}

三、理解main方法语法

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

  1. main方法是由虚拟机调用的
  2. Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
  3. Java虚拟机在执行main()方法时,不必创建对象,所以该方法必须是static
  4. 该方法接收String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数
  5. 命令行形式: java 执行的程序 参数1 参数2 参数3
  6. 在IDEA中动态传值:

特别提示:

  1. 在main()方法中,可以直接调用main方法所在类的静态成员(静态变量和静态方法)
  2. 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。 

四、代码块

(一)基本介绍

        代码块又称为初始化块,属于类中的成员(即 是类的一部分),类似于方法,在方法体中,通将逻辑语句封装过{}包围起来。

        但和方法不同,代码块没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是在加载类时,或创建对象时隐式调用。

(二)基本语法

[修饰符]{
    代码
};

注意:

  1. 修饰符可选,要么不写,要么只能写static
  2. 代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通代码块
  3. 代码块中可以写任何逻辑语句(输入、输出、方法调用、循环、判断等)
  4. ;号可以写,也可以不写 

(三)代码块的作用

  1. 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作。
  2. 如果多个构造器中都有重复的语句,可以将构造器中重复的语句,抽取到代码块中,代码块会先于构造器加载,并且每创建一个对象,都会先加载代码块的内容,然后加载构造器的内容。
  3. 代码块的调用顺序优先于构造器。
public class CodeBlock01 {
    public static void main(String[] args) {
        Movie movie1 = new Movie("灰姑娘");
        System.out.println("------------------------");
        Movie movie2 = new Movie("泰坦尼克号", 55);
        System.out.println("------------------------");
        Movie movie3 = new Movie("蝙蝠侠", 75, "蒂姆·伯顿");
    }
}

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) {
        System.out.println("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;
    }
}

运行结果:

(四)注意事项与使用细节

1.static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。

如果是普通代码块,每创建一个对象,就执行一次。

        

2.类在什么时候会被加载

(1)创建对象实例时(new)        见图一

(2)创建子类对象实例,先加载父类,后加载子类        见图二

(3)使用类的静态成员时(静态属性,静态方法)        见图三

图一:创建对象实例时会加载类 

图二:创建子类对象时,会先加载父类,后加载子类 

图三:试用类的静态成员时,先加载静态代码块,然后按照调用顺序分别加载静态属性和静态方法。

图四:当父子类都有静态代码块时,先加载父类的静态代码块,然后加载子类的静态代码块,并且只会执行一次,最后按照main方法中的调用顺序加载。 

3.普通的代码块,在创建对象实例时,会被隐式调用:对象被创建一次,就会调用一次。

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

图六:创建对象时,最先加载一次静态代码块,然后创建几次对象,就加载几次普通代码块。 

图七:类加载的时候,不会执行普通代码块

小结:

(1)静态代码块在类加载的时候执行,且只执行一次;

(2)普通代码块在创建对象时调用的,创建一次,加载一次;

(3)类加载的3种情况

4.创建一个对象时,在一个类中的调用顺序:

(1)调用静态代码块和静态属性初始化

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

下面的代码中,静态成员按照编写顺序执行,但是代码中最先调用的是getN1()方法,所以getN1()会先执行,再执行静态代码块:

如果将静态代码块放在最前面,那么就先执行静态代码块中的内容: 

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

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

(3)调用构造方法

public class CodeBlockDetail02 {
    public static void main(String[] args) {
        new A();
        System.out.println("------------------------");
        new A(8);
    }
}

class A {
    {
        System.out.println("普通代码块被调用...");
    }

    private static int n1 = getN1();

    private int n2 = getN2();

    static {
        System.out.println("A 静态代码块被调用...");
    }

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

    public int getN2() {
        System.out.println("getN2()被调用...");
        return 200;
    }

    public A() {
        System.out.println("执行无参构造器...");
    }

    public A(int n2) {
        System.out.println("执行有参构造器...");
        this.n2 = n2;
    }
}

5.构造器的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕。因此是优先于构造器和普通代码块执行的。

6.创建一个子类对象时(有继承关系),他们的静态代码块、静态属性初始化、普通代码块、普通属性初始化、构造方法的调用顺序如下:

(1)父类的静态代码块和静态属性(优先级一样,按定义顺序执行)

(2)子类的静态代码块和静态属性(优先级一样,按定义顺序执行)

(3)父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)

(4)父类的构造方法

(5)子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)

(6)子类的构造方法

public class CodeBlockDetail04 {
    public static void main(String[] args) {
        //老师说明
        //(1) 进行类的加载
        //1.1 先加载 父类 A02 1.2 再加载 B02
        //(2) 创建对象
        //2.1 从子类的构造器开始
        new B02();//对象

//        new C02();
    }
}

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

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

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

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

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

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

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

}

class B02 extends A02 { //

    private static int n3 = getVal03();

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

    public int n5 = getVal04();

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

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

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

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

7.静态代码块只能调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。

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);
        //m1();//错误
        m2();
    }

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

(五)练习题

1.题目1

2.题目2

五、static在设计模式的经典应用——单例模式

        设计模式:在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。

        单例模式:所谓的单例设计模式,就是采取一定的方法,保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。比如我们开发中有一个类是重量级的,非常耗用资源,但是实际上我们只需要一个对象,就可以使用单例设计模式。

        简言之,就是只能通过一个方法,创建唯一一个对象。

(一)饿汉式

为什么叫“饿汉式”:不管你是否需要,先提供一个(很着急~)

饿汉式的弊端:可能创建了一个对象,但是没有使用。

饿汉式实现步骤:

  1. 为了防止在外面传参new多个对象,将构造器私有化
  2. 在类的内部直接创建私有的静态对象(注意:这里的创建对象私有化必须要有一个static修饰,否则下面的公共静态方法无法调用!!!)
  3. 提供一个公共的静态方法返回这个对象

代码示例: 

public class SingleTon01 {
    public static void main(String[] args) {
        // 原来可以new多个对象
        // GirlFriend aa = new GirlFriend("aa");
        // GirlFriend bb = new GirlFriend("bb");

        // 直接通过方法可以获取对象
        Cat cat = Cat.getInstance();
        System.out.println(cat);

        Cat cat1 = Cat.getInstance();
        System.out.println(cat1);

        // 两个对象是同一个对象,即,无论调用多少次,都只有一个对象
        // 因为静态调用的只会在内存中加载一次,这样就保证了不会有更多的对象
        System.out.println(cat == cat1); // true
    }
}

class Cat {
    private String name;

    // 1.为了防止在外面传参new多个对象,将构造器私有化
    private Cat(String name) {
        this.name = name;
    }

    // 2.在类的内部直接创建私有的静态对象
    // 注意:这里的创建对象私有化必须要有一个static修饰,否则下面的公共静态方法无法调用!!!
    private static Cat cat = new Cat("cc");

    // 3.提供一个公共的静态方法返回这个对象
    // 注意:如果下面的getInstance()方法不用static修饰,外部就可以创建Cat对象了
    public static Cat getInstance() {
        return cat;
    }

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

(二)懒汉式

为什么叫“懒汉式”:直到用了才提供对象(不着急~)

懒汉式实现步骤:

  1. 构造器私有化,防止外部new对象
  2. 定义一个static静态属性对象
  3. 提供一个public static 对象 getInstance()方法,可以返回一个对象
  4. 只有当用户使用getInstance()方法时,才会返回该对象,后面再次调用时,会返回上次创建的对象,从而保证了单例,且相比饿汉式来说,更节约资源。

代码实现:

public class SingleTon02 {
    public static void main(String[] args) {
        Fish fish1 = Fish.getInstance();
        Fish fish2 = Fish.getInstance();

        System.out.println(fish1);
        System.out.println(fish2);
        System.out.println(fish1 == fish2); // true
    }
}

// 希望在程序运行中,只能创建一个Fish对象
class Fish {
    private String name;

    private Fish(String name) {
        this.name = name;
    }

    private static Fish fish;

    public static Fish getInstance() {
        // 如果还没有创建Cat对象,就提供一个
        if (fish == null) {
            fish = new Fish("黄花鱼");
        }
        return fish;
    }

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

(三)饿汉式VS懒汉式区别

  1. 二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载时就创建了对象实例,而懒汉式则是在使用时才创建。
  2. 饿汉式不存在线程安全问题,懒汉式存在线程安全问题:即当多个线程同时读取getInstance()方法时,可能同时判断对象==null。从而导致多个线程同时执行对象的创建。
  3. 饿汉式存在资源浪费的可能。因为如果一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在对象先创建而没有使用的这个问题。
  4. 在JavaSE标准类中,java.lang.Runtime就是经典的单例模式。代码如下:

六、final关键字

(一)基本介绍

final意为最后的、最终的。final可以修饰类、属性、方法和局部变量。

使用场景:

  1. 当不希望类被继承时,可以用final修饰;
  2. 当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰;
  3. 当不希望类的某个属性的值被修改,可以用final修饰;
  4. 当不希望某个局部变量被修改,可以使用final修饰。

final修饰的类不能被继承:

final修饰的方法不能被重写:

final修饰的属性不能被修改:

final修饰的局部变量不能被修改:

(二)使用注意事项和细节讨论 

(1)final修饰的属性又叫常量,一般用XX_XX_XX来命名。字母全部大写,不同的单词之间用_间隔,例如:TAX_RATE;

(2)final修饰的属性在定义时,必须赋初始值,并且以后不能再修改,赋值可以在如下位置之一:[选择一个位置赋初值即可]:

  • 定义时:如public final double TAX_RATE=0.08;
  • 在构造器中
  • 在代码块中
class AA {
    // 1.定义时直接赋值
    public final double TAX_RATE = 0.08;

    // 2.构造器中赋值
    public final double TAX_RATE2;

    public AA() {
        TAX_RATE2 = 0.01;
    }

    // 3.代码块中赋值
    public final double TAX_RATE3;

    {
        TAX_RATE3 = 1.1;
    }
}

(3)如果final修饰的属性是静态的,则初始化的位置只能是a.定义时;b.在静态代码块中;不能在构造器中赋值。

(4)final类不能继承,但是可以实例化对象。

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

(6)一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法。

(7)final不能修饰构造方法

(8)final和static往往搭配使用,效率更高,不会导致类加载,底层编译器做了优化处理

例如,当一个变量被static修饰时,调用该静态变量之前,会加载静态代码块中的内容,因为类被加载了(见图十)。 但是当这个变量被static和final同时修饰的时候,静态代码块就不会被加载(见图十一)。

图十                                                                图十一

(9)包装类:Integer、Double、Float、Boolean等都是final修饰的;String、System也是final类。

(三)练习题

1.题目1

用3种方式给PI赋值,并求出圆的面积:

public class FinalExercise01 {
    public static void main(String[] args) {
        Circle circle = new Circle(5.0);
        System.out.println(circle.ares()); // 78.5
    }
}

class Circle {
    private double radius;
    //1.定义时赋值
    private final double PI = 3.14;

    // 2.构造器中赋值
    private final double PI2;

    public Circle(double radius) {
        this.radius = radius;
        PI2 = 3.14;
    }

    public Circle() {
        PI2 = 3.14;
    }

    // 3.代码块中赋值
    private final double PI3;

    {
        PI3 = 3.14;
    }

    public double ares() {
        return PI * radius * radius;
    }
}

2.题目2

判断下面代码是否有错误

class Something {
    public int addOne(final int x) {
//        ++x;  // 错误,原因是不能修改 final x的值
        // 下面的代码是可以的,因为基于它的当前值计算了一个新的值
        // 没有给x赋予一个新的值
        return x + 1;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值