Java 类详解 9章

本文详细介绍了Java中的类变量(静态变量)和类方法(静态方法)的概念、使用场景及细节,包括它们的内存分配、访问方式和生命周期。此外,讲解了main方法的语法和执行顺序。同时,深入探讨了单例设计模式,包括饿汉式和懒汉式的实现,并分析了final关键字的作用和用法,以及在不同场景下的实践应用。最后,通过代码示例展示了final关键字在常量定义和单例模式中的应用。
摘要由CSDN通过智能技术生成

第九章

类变量与类方法

9.1 类变量

9.1.1 类变量的内存刨析:

在这里插入图片描述
static变量保存在class实例的尾部,
JDK7以上的版本,静态域储存于定义类型的Class对象中,Class对象如同队中其他对象一样,存在于GC堆中。
基本共识:
1、static变量是同一个类所有对象的共享
2、static类变量,在类加载的时候就生成了。

9.1.2类变量介绍

类变量概念:
类变量也叫做静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都i是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量,从上面的内存刨析图可以看出。

类变量定义:
访问修饰符 static 数据类型 变量名;{推荐使用}
static 访问修饰符 数据类型 变量名;

类变量的访问:
类名.类变量名{推荐使用}
对象名.类变量名 【静态变量的访问修饰符的访问权限和范围 与 普通属性一样的】

public class ChildGame {
    public static void main(String[] args) {
        //类名.类变量名
        //因为static是随着类的加载而创建的,所以即使没有创建对象实例也可以用类名去访问
        System.out.println(A.name);
        //对象名.类变量名
        A a = new A();
        System.out.println(a.name);
    }
}
class A{
    //类变量的访问也要遵循访问修饰符的访问权限
    //如果name的访问修饰符是privat,则在上面的类中的访问方法就会报错
    public static String name = "猪猪";
}

9.1.3 类变量的细节

1、什么时候使用类变量
当我们需要让某个类的所有对象都共享一个变量的时候,可以考虑使用类变量(静态变量),比如:定义学生类,统计所有学生交多少钱的时候。Student(name,static fee)

2、类变量与实例变量(普通变量)的区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的

3、加上static称为类变量或者静态变量,否则称为实例变量/普通变量/非静态变量

4、类变量的访问可以通过: 类名.类变量名【推荐使用】 或者 对象名.类变量名

5、实例变量不能通过 类名.类变量名 方式访问

6、类变量是在类加载的时候就初始化了,也就是说,即使没有创建对象,只要是类以及加载了,就可以使用类变量。

7、类变量的生命周期是随类的加载开始,随着类的消亡而销毁

public class ChildGame {
    public static void main(String[] args) {
        //类名.类变量名
        //因为static是随着类的加载而创建的,所以即使没有创建对象实例也可以用类名去访问
        System.out.println(A.name);
        //因为age是实例变量所以不能用 类名.类变量名访问,所以会报错
        System.out.println(A.age);//错的
        //对象名.类变量名
        A a = new A();
        System.out.println(a.name);
    }
}
class A{
    //类变量的访问也要遵循访问修饰符的访问权限
    //如果name的访问修饰符是privat,则在上面的类中的访问方法就会报错
    public static String name = "猪猪";
    //实例变量/普通变量/
    public int age = 10;
}

9.2类方法

9.2.1 类方法基本介绍

类方法又叫做静态方法

类方法定义形式:
访问修饰符 static 数据返回类型 方法名(){ }【推荐使用】
static 访问修饰符 数据返回类型 方法名(){ }

类方法的调用:
类名.类方法名 或者 对象名.类方法名

public class StaticMethod {
    public static void main(String[] args) {
        //创建2个学生对象,交学费
        Student tom = new Student("tom");
        tom.payFee(100);//用对象名.类方法名 调用
        //Student.payFee(100);//用类名.类方法名 调用

        Student jack = new Student("jack");
        jack.payFee(200);
        //Student.payFee(200);

        Student.showFee();//用类名去调用显示费用的方法,因为fee是定义的一个类变量,
        //所以每一次调用的时候,调用的都是同一个地址的类变量,所以每一次调用,值都会变化
    }
}
class Student{
    private String name;//普通成员
    //定义一个静态变量,来累积学生的学费
    private static double fee = 0;

    public Student(String name) {
        this.name = name;
    }
    //1、当方法使用static的时候,表示该方法就是静态方法
    //2、静态放啊发可以访问静态变量
    public static void payFee(double fee){
        Student.fee += fee;//累积到静态变量fee中
    }
    public static void showFee(){
        System.out.println("总学费="+Student.fee);
    }
}

9.2.2 类方法使用场景

1、当方法中不涉及任何与对象相关的成员,则可以将方法设计成静态方法,提高开发效率。比如工具类中的方法 utils、Math类、Arrays类等等:以下是其Math源码:
在这里插入图片描述
2、当程序员自己开发中,通常会将一些通用的方法,去设计成静态方法,这样我们不需要创建对象就可以直接使用的工具方法,例如打印一维数组、冒泡排序,完成某个计算任务等

        System.out.println(Student.sum(20,30));
    }
}
class Student{//获取两个数的和的方法,将其定义成一个static方法,然后可以直接调用
    public static double sum(double n1,double n2){
        return n1+n2;
    }
9.2.3 类方法的细节

1、类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区
注:类方法 中没有this的参数; 普通放啊发中隐含有this参数

2、类方法 可以通过类名调用,也可以通过对象名调用

3、普通方法和对象有关,需要通过对象名调用,比如: 对象名.方法名(参数),不能通过类名调用

4、类方法中不允许使用和对象有关的关键字,比如this和super,普通方法(成员方法)可以
在这里插入图片描述

5、类方法(静态方法)中 只能访问 静态变量 或者静态变量

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

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

9.3 main语法

9.3.1main语法说明

main方法的形式:
public static void main(String[ ] args){ }
注:main方法是由虚拟机去调用的
解释:
1、public 是因为当Java虚拟机要去调用main()方法的时候,必须要由访问权限,所以必须是public公共的

2、static 是因为 当Java虚拟机去调用的时候,是不需要创建对象的,所以将其定义成一个静态方法,可以直接被Java虚拟机调用

3、main()方法接收的是String类型的数组参数,该数组中保存执行Java命令时,传递给所运行的类的参数

4、String[ ] 数组的参数值,是在 Java 执行程序的时候所传进去的 在命令行中
例: Java 执行程序 参数1 参数2 参数3

在这里插入图片描述

9.3.2 main方法特别说明

1、在main()方法中。我们可以直接调用main方法所在类的静态方法或者静态属性
2、但是,不能直接访问该类中的非静态成员,必须要创建一个对象后,才可以通过对象去访问类中的非静态成员。毕竟 main()方法是一个静态方法
在这里插入图片描述
代码:

public class Main01 {
    //静态变量/成员
    private static String name = "小王";
    //静态方法
    public static void hi(){
        System.out.println("我是静态方法hi");
    }
    //普通变量/成员
    private int age = 21;
    //普通方法
    public void ok(){
        System.out.println("我是普通方法ok");
    }
    public static void main(String[] args) {
        //因为main方法时静态的,所以可以访问静态变量、静态方法
        System.out.println("name= "+name);
        hi();

        //访问普通变量
       // System.out.println("age = "+age);//错误
        //访问普通方法
      //  ok();//错误
        //因为main()方法是静态方法,所以不能直接访问该类的非静态成员
        //所以直接会报错,要访问必须创建一个类对象,用对象去调用访问
        Main01 main01 = new Main01();
        System.out.println("age= "+main01.age);
        main01.ok();
    }
}

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

9.3.3 main方法动态传参(idea)

在 9.3.1 中的 4 那里,用的是命令行去传参的,而在平时的工作或者开发过程中都是用工具(idea、eclipse等),所以我们看一下在idea中,如何去传参
首先先输入输出的代码:

public class Main02 {
    public static void main(String[] args) {
        for (int i = 0; i < args.length ; i++) {
            System.out.println("arg["+ i +"]= " + args[i]);
        }
    }
}

当直接运行这段代码的时候,当然不会输出任何值,传参方式如下:
在这里插入图片描述
在这里插入图片描述
输出:
在这里插入图片描述

9.4 代码块

9.4.1 代码块基本介绍

基本概念:
代码化块又称为初始化块,属于类中的成员【即 是类的一部分】,类似于方法,将逻辑语句封装在方法体中,通过{ }包装起来,
但是,和方法又不同,代码块没有方法名,没有返回,没有参数,只有方法体(也就是 只有方法体的方法代码块),而且不用通过对象或者类去显式调用,而是在加载类的时候,或者创建对象的时候去隐式调用的

基本语法:
[修饰符] {
代码
};

注:
1、修饰符 是可以选择的 , 要写的话,也只能写static

2、代码块分为两类,使用static 修饰的叫静态代码块,没有static修饰的是普通代码块/非静态代码块

3、逻辑语句可以为任何的逻辑语句(输入、输出、方法调用、循环、判断等)

4、 ;号可以写也可以省略

9.4.2代码块的使用

1、相当于另外一种形式的构造器(对构造器的补充机制),可以作初始化的操作

2、使用场景:如果多个构造器中都由重复的语句,可以抽取到初始化块中,提高代码的重用性。

案例:
创建一个电影类,属性如下:创建三个构造器,当创建一个对象的时候,无论使用那个构造器都会输出以下三句话:
System.out.println(“电影名字:”+name);
System.out.println(“电影价格:”+price);
System.out.println(“电影导演:”+actor);

第一种方法:
直接在每一个构造器里面去输出三句话。

public class Block01 {
    public static void main(String[] args) {
        Movie movie = new Movie("反贪5");
    }
}

class Movie{
    private String name;
    private int price;
    private String actor;

    public Movie(String name) {
        System.out.println("电影名字:"+name);
        System.out.println("电影价格:"+price);
        System.out.println("电影导演:"+actor);
        this.name = name;
    }

    public Movie(String name, int price) {
        System.out.println("电影名字:"+name);
        System.out.println("电影价格:"+price);
        System.out.println("电影导演:"+actor);
        this.name = name;
        this.price = price;
    }

    public Movie(String name, int price, String actor) {
        System.out.println("电影名字:"+name);
        System.out.println("电影价格:"+price);
        System.out.println("电影导演:"+actor);
        this.name = name;
        this.price = price;
        this.actor = actor;
    }
}

这种方法可以在每一个构造器中去输出这三句话,但是随着构造器越多,代码的复杂度就月大,看起来就很不清晰。

第二种方法:
鉴于避免以上的代码冗余,所以我们可以将其共有部分放在代码块中,当创建对象的时候,就会自动的调用代码块。

public class Block01 {
    public static void main(String[] args) {
        Movie movie = new Movie("反贪5");
        Movie movie1 = new Movie("你好,李焕英", 40, "weizhi");
    }
}

class Movie{
    private String name;
    private int price;
    private String actor;
        
    {
        System.out.println("电影屏幕打开");
        System.out.println("播放广告");
        System.out.println("开始播放电影");
    };
 
    public Movie(String name) {
        System.out.println("Movie(String name)构造器被调用");
        this.name = name;
        System.out.println(toString());
        System.out.println("========================");
    }

    public Movie(String name, int price) {
        System.out.println("Movie(String name, int price)构造器被调用");
        this.name = name;
        this.price = price;
        System.out.println(toString());
    }

    public Movie(String name, int price, String actor) {
        System.out.println("Movie(String name, int price, String actor)构造器被调用");
        this.name = name;
        this.price = price;
        this.actor = actor;
        System.out.println(toString());
    }

    @Override
    public String toString() {
        return "Movie{" +
                "name='" + name + '\'' +
                ", price=" + price +
                ", actor='" + actor + '\'' +
                '}';
    }
}
输出:

电影屏幕打开
播放广告
开始播放电影
Movie(String name)构造器被调用
Movie{name='反贪5', price=0, actor='null'}
========================
电影屏幕打开
播放广告
开始播放电影
Movie(String name, int price, String actor)构造器被调用
Movie{name='你好,李焕英', price=40, actor='weizhi'}

由这个方法我们可以看出,当一个对象被创建的时候,代码块将会被执行,由输出我们可以看出,代码块的内容被优先于调用,

通过两种方法的比较,我们可以看出,使用代码块,可以提高代码的复用性,以及可以减少程序的复杂度和清晰度。

9.4.3 注意事项和细节!!

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

2、类什么时候被加载[重要]!!!!!!

①创建对象实例时(new 一个对象的时候)

②创建子类对象实例时,父类也会被加载(由下面案例可知,先加载父类再加载子类)

③使用类的静态成员时(静态属性,静态方法)

(通过输出语句来判断类是否被加载,以下是 static 代码块演示):

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

        //1.创建实例对象时(new)
        AA aa = new AA();//执行一次
        //2.创建子类对象实例时,父类也会被加载(先加载父类,再加载子类,所以先输出BB,再输出AA)
        AA aa2 = new AA();//因为static代码块只会被执行一次,所以在这里不会再被执行
        //3.使用类的静态成员时(静态属性,静态方法)
        System.out.println(Cat.age);//(先加载父类,再加载子类)
    }
}
class Animal{
    static {
        System.out.println("Animal的静态代码块1被执行");
    }
}
class Cat extens Animal{
    public static int age = 3;
    static {
        System.out.println("Cat的静态代码块1被执行");
    }
}
class AA extends BB{
    //静态代码块
    static {
        System.out.println("AA的静态代码块1被执行");
    }
}
class BB{
    static {
        System.out.println("BB的静态代码块1被执行");
    }
}
输出:

BB的静态代码块1被执行
AA的静态代码块1被执行
Animal的静态代码块1被执行
Cat的静态代码块1被执行
3

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

public class Block02 {
    public static void main(String[] args) {
        DD dd = new DD();
        DD dd1 = new DD();//静态代码块只执行一次,普通的创建一个对象就执行一次
    }
}
class DD{
    public static int n1 = 222;
    static {
        //静态代码块
        System.out.println("DD的静态代码块1被执行");
    }
    {
        //普通代码块
        System.out.println("DD的普通代码块被执行");
    }
}
输出:

DD的静态代码块1被执行
DD的普通代码块被执行
DD的普通代码块被执行

如果是只使用类的静态成员,则普通代码块不会被执行(因为普通代码块是构造器的补充,如果构造器被调用(new 一个对象),则普通代码块会被执行)

public class Block02 {
    public static void main(String[] args) {
        System.out.println(DD.n1);//静态代码块一定会被输出,
        // 普通代码块不会执行,创建对象了(new一个对象)才能被执行,每创建一个对象就会被调用一次
    }
}
class DD{
    public static int n1 = 222;
    static {
        //静态代码块
        System.out.println("DD的静态代码块1被执行");
    }
    {
        //普通代码块
        System.out.println("DD的普通代码块被执行");
    }
}
输出:
DD的静态代码块1被执行
222

注:
1)、static代码块是类加载时,执行,只会被执行一次
2)、普通代码块是在创建对象时调用的,创建一次,调用一次
3)、类加载的三种情况,要记住

4、创建一个对象时,在一个类中的调用顺序(重点、难点!!!)

调用静态代码块和静态属性初始化(注:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)主要是因为是类加载时被调用

public class Block03 {
    public static void main(String[] args) {
        A a = new A();//调用顺序:(1)getN1被调用(2)A 静态代码块01,
        //static的优先级一样,所以按照定义顺序去调用
    }
}
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

调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果由多个普通代码块和多个普通属性初始化,则按定义顺序调用)是用对象去加载

public class Block03 {
    public static void main(String[] args) {
        A a = new A();//调用顺序:(1)getN2被调用(2)A 的普通代码块01
        //普通代码块的优先级一样,所以按照定义顺序去调用
    }
}
class A{
    private int n2 = getN2();//普通属性初始化
    {
        System.out.println("A 的普通代码块01");
    }
    public int getN2(){//普通方法/非静态方法
        System.out.println("getN2被调用");
        return 200;
    }
}
输出:
getN2被调用
A 的普通代码块01

最后再调用构造器(综合代码)

public class Block03 {
    public static void main(String[] args) {
        A a = new A();//调用顺序:(1)getN1被调用(2)A 静态代码块01(3)getN2被调用(4)A 的普通代码块01(5)A的无参构造器被调用
        //static的优先级一样,所以按照定义顺序去调用
    }
}
class A{
    //构造器,最后被调用
    public A() {
        System.out.println("A的无参构造器被调用");
    }

    //普通属性初始化
    private int n2 = getN2();
    //普通代码块
    {
        System.out.println("A 的普通代码块01");
    }
    public int getN2(){//普通方法/非静态方法
        System.out.println("getN2被调用");
        return 200;
    }
    //静态属性初始化
    private static int n1=getN1();
    static {//静态代码块
        System.out.println("A 静态代码块01");
    }
    public static int getN1(){
        System.out.println("getN1被调用");
        return 100;
    }
}
输出:
getN1被调用
A 静态代码块01
getN2被调用
A 的普通代码块01
A的无参构造器被调用

总结:当在 “一个类” 中创建一个对象实例的时候,调用顺序是:静态(按定义顺序)>非静态(按定义顺序)>构造器,静态是与类

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

class A{
public A(){
//这里由隐藏的执行要求
//(1)super()
//(2)调用本类的普通代码块
}
}

public class Block04 {
    public static void main(String[] args) {
        new B();
    }
}
class C{
    {
        System.out.println("C的普通代码块");
    }
    public C(){
        //这里其实是隐藏了两个方法的
        //(1)super();
        //(2)调用本类的普通代码块
        System.out.println("C的无参构造器");
    }
}
class B extends C{
    {//普通代码块
        System.out.println("B的普通代码块");
    }
    private static String n1 =getN1();//静态属性初始化
    public static String getN1(){
        System.out.println("B的static的n1被调用");//静态代码块在对象加载的时候就被调用,所以先输出
        return n1;
    }
    public B(){
        //这里其实是隐藏了两个方法的
        //(1)super();
        //(2)调用本类的普通代码块
        System.out.println("B的无参构造器");
    }
}
输出:
Bstatic的n1被调用
C的普通代码块
C的无参构造器
B的普通代码块
B的无参构造器

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

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

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

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

④父类的构造器

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

⑥子类的构造器

例:分析下面代码的输出

public class Block05 {
    public static void main(String[] args) {
        new B02();//先加载父类A02 再加载子类B02 static静态方法是在类加载的时候就调用的
    }
}
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() {//构造器
        //隐藏了
        //(1)super();
        //(2)本类的普通代码块和普通属性的初始化
        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(){
        //隐藏了
        //(1)super();
        //(2)本类的普通代码块和普通属性的初始化
        System.out.println("B02的构造器");//(10)
    }
}
输出:
getVal01
A02的一个静态代码块..
getVal03
B02的一个静态代码块..
A02的一个普通代码块..
getVal02
A02的构造器
getVal04
B02的一个普通代码块..
B02的构造器

7、静态代码块只能调用静态成员(静态属性和静态方法)

9.4.4练习题

A01:分析下列代码

ic class Block06 {
    public static void main(String[] args) {
        System.out.println("total="+A01.total);//首先先加载A01,所以A01的static被调用
        System.out.println("total="+A01.total);//因为static只会被调用一次,所以第二次现在不会调用,在第一次的时候total以及被赋值了
    }
}
class A01{
    public static int total;
    static {
        total = 100;//100返回上去(2)(3)
        System.out.println("in static block!");//(1)
    }
}
输出:
in static block!
total=100
total=100

第二条语句只输出total的值,因为static只会被调用一次,所以第二次现在不会调用,在第一次的时候total以及被赋值了

A02:分析下列代码的输出

public class Block07 {
    public static void main(String[] args) {
        Test test = new Test();//1、类加载
    }
}
class Test{
    Sample sam1 =  new Sample("sam1成员初始化");//9、
    static Sample sam = new Sample("静态成员sam初始化");//2、
    static {
        System.out.println("static块执行");//5、
        if (sam == null){//6、在2那句sam已经被赋值了,所以sam不为null
            System.out.println("sam is null");
        }
    }
    Test(){//7、
        //隐含了super(),父类是object类,所以没有输出,不考虑
        //8、隐含了普通方法
        System.out.println("Test默认构造函数被调用");//12
    }
}
class Sample{
    Sample(String s){//3、//10、
        System.out.println(s);//4、//11、
    }
    Sample(){
        System.out.println("Sample默认构造器被调用");
    }
}
输出:

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

9.3单例设计模式

9.3.1设计模式概念

设计模式是在大量的实践中总结和理论化之后优选的代码结构、编码风格、以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋谱、不同的棋局,我们用不同的棋谱,免去我们自己再去思考和摸索。

9.3.2 单例模式

1.单例模式,就是采取一定的方法保证再整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法

2.单例模式有两种:①饿汉式 ②懒汉式

9.3.3 单例模式的应用实例
9.3.3.1 饿汉式

步骤:
① 将构造器私有化,防止用户直接new一个对象
② 在类的内部去创建对象
③ 向外暴露一个静态的公共方法
④ 代码实现

例:要求:创建一个GirlFriend类,并且要求只能有唯一一个对象

//要求:创建一个GirlFriend类,并且要求只能有唯一一个对象
public class SingleTon01 {
    public static void main(String[] args) {
        //用类去调用一个对象
        System.out.println(GirlFriend.getInstance());
        //用方法去获取对象,判断是否是同一个对象
        GirlFriend gf1 = GirlFriend.getInstance();
        GirlFriend gf2 = GirlFriend.getInstance();
        System.out.println(gf1 == gf2);//true
    }
}

class GirlFriend{
    private String name;
    // [
    // [单例模式----饿汉式 ]
    //步骤① 将构造器私有化,可有避免用户直接去new一个对象,那么就不是只有一个对象了
    private GirlFriend(String name) {
        this.name = name;
    }
    //步骤② 在类的内部创建一个对象,为了保证能够使用,其对象将其设置为static静态的
    private static GirlFriend gf = new GirlFriend("江仔");
    //步骤③ 提供一个公共的static方法,去返回 gf对象
    public static GirlFriend getInstance(){
        return gf;
    }
    //用toString方法去显示对象

    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + '\'' +
                '}';
    }
}
输出:
GirlFriend{name='江仔'}
true

之所以叫做饿汉式,是因为一个对象没有被用,就已经随着类的加载被加载了,饿汉式可能造成创建了对象,到那时对象没有被使用

9.3.3.2 懒汉式

步骤:
1、构造器私有化
2、定义一个static静态属性
3、提供一个公共static方法,去返回一个Cat对象
4、只有当用户去使用getInstance()方法的时候,才会去返回一个cat对象, 后面继续调用的话,还是会返回上一次创建的cat对象,这样就保证了单例性

例:在程序的运行中,只能创建一个Cat对象

public class SingleTon02 {
    public static void main(String[] args) {
        //用类去调用其方法去创建对象
        System.out.println(Cat.getInstance());
        //用方法去创建对象
        Cat cat1 = Cat.getInstance();
        Cat cat2 = Cat.getInstance();
        System.out.println(cat1 == cat2);
    }
}
//在程序的运行中,只能创建一个Cat对象
//使用单例模式
class Cat{
    private String name;
    //步骤① 构造器私有化
    private Cat(String name) {
        this.name = name;
    }
    //步骤②定义一个static静态属性
    private static Cat cat;//没有初始化的时候,默认是null,当然也可以赋值为null
    //步骤③ 提供一个公共static方法,去返回一个Cat对象
    //步骤④ 只有当用户去使用getInstance()方法的时候,才会去返回一个cat对象,
    //       后面继续调用的话,还是会返回上一次创建的cat对象,这样就保证了单例性
    public static Cat getInstance(){
        if (cat == null){//如果还没有创建Cat对象就创建一个
            cat = new Cat("江仔");//将其步骤2定义的属性去创建其对象
        }
        return cat;
    }

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

懒汉式,主要就是用户要使用这个对象的时候才去创建这么一个对象去使用,不会造成资源浪费。

9.3.3.3 饿汉式、懒汉式区别

1、创建对象的时机不同:饿汉式是在类加载的时候就已经创建了对象实例,而懒汉式是在使用的时候才创建的

2、饿汉式不存在线程安全的问题,懒汉式存在线程问题

3、饿汉式存在浪费资源的可能性,因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是在使用的时候才创建,就不会存在这个问题

4、在我们javaSE标准类中,java.lang.Runtime就是经典的单例模式
Runtime源码如下:

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

    /** Don't let anyone else instantiate this class */
    private Runtime() {}
9.4 final关键字
9.4.1 final关键字概念

final可以修饰类、属性、方法和局部变量

应用场景:
1、在当一个类不希望被继承的时候,可以用final关键字修饰
在这里插入图片描述
2、当不希望类的某一个方法被子类重写或者覆盖的时候,可以用final关键字修饰
在这里插入图片描述
3、当不希望类的某个属性的值不被修改时,可以用final关键字修饰
在这里插入图片描述
4、当不希望某个局部变量被修改的时候,可以用final关键字修饰
在这里插入图片描述

9.4.2 final关键字细节

1、final修饰的属性又叫做常量,一般用 XX_XX_XX 命名,也就是命名时大写,且用下划线隔开
例: TAX_RATE = 10;

2、final修饰的属性在定义时,必须要赋初值,并且不能够再修改,赋值的位置以下之一:
定义时去赋值
②在构造器中去赋值
③在代码块中去赋值
在这里插入图片描述
3、如果final修饰的属性事静态的,则初始化的位置只能是在以下之一:

① 定义时
②在静态的代码块中
在这里插入图片描述
因为要遵循static关键字的语法使用规则,所以static的属性只能在static的方法中去使用赋值。

4、final类不能继承,但是可以实例化对象
在这里插入图片描述
5、如果类不是final类,但是含有final方法,则该方法虽然不能被重写,但是可以被继承
在这里插入图片描述
5、一般来说,如果一个类已经时final类了,就没有必要去再把方法修饰 成final方法
(因为类已经被final修饰了,那就整个类都不会被诶继承了,那么其他类自然也就不能够去重写类方法了,所以方法再用final修饰一次,有一点多余了)

6、final不能去修饰构造器

7、final关键字和static往往搭配使用,效率更高,不会导致类的加载(static是在确保不用创建对象的情况下,只要类被加载的,就可以去调用方法,两者结合的话,就不会导致类加载)

public class Final03 {
    public static void main(String[] args) {
        System.out.println(AAA.num);
        System.out.println(BBB.num1);
    }
}
class AAA{
    public static int num = 10;
    static {//用来测试会不会被类加载
        System.out.println("AAA的静态代码块被调用...");
    }
}
class BBB{
      public final static int num1 = 20;
    static {//用来测试会不会被类加载
        System.out.println("AAA的final 静态代码块被调用...");
    }
}
输出:
AAA的静态代码块被调用...
10
20

8、包装类(Integer、Double、String等都是final类,所以是不能被继承的)
源码:
在这里插入图片描述

9.4.3 final关键字练习题

/A01 编写一个程序,能够计算圆形的面积,要求圆周率为3.14,赋值的位置3个方式都写,并且时用对象去调用的/

package com.xioawang.final_;
/*编写一个程序,能够计算圆形的面积,要求圆周率为3.14,赋值的位置3个方式都写*/
public class FinalText1 {
    public static void main(String[] args) {
        Circle circle = new Circle();
        System.out.println(circle.are());

    }
}
class Circle{
    private double radius = 2;//半径先写定死,可以更改的
    //1、定义属性时去赋初值,并且通过对象去调用
    private final double PI ;//= 3.14;
    //2、构造器中去赋初值,并通过对象去调用
    public Circle() {
       // this.PI = 3.14;
    }
    //3、代码块中赋值
    {
        PI = 3.14;
    }
    public  double are(){
        return radius*radius*PI;
    }
}
输出:
12.56

/A02 编写一个程序,能够计算圆形的面积,要求圆周率为3.14,赋值的位置3个方式都写,并且时用类加载去调用/

public class FinalText2 {
    public static void main(String[] args) {
        System.out.println(Circle1.are());
    }
}
class Circle1{
    private static double radius = 2;
    private final static double PI ;
    static {
        PI=3.14;
    }
    public static double are(){
        return radius*radius*PI;
    }
}
输出:
12.56

类的篇幅就在这结束了吖,这是整个Java学习中的第九章哦,觉得还不错的可以查看我完整的Java笔记哦:
Java学习第二阶段(仍然在继续更新中~~~~)
Java学习第一阶段

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值