第九章
类变量与类方法
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的无参构造器");
}
}
输出:
B的static的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学习第一阶段