类变量/静态变量
类变量的内存布局
-
静态变量是被所有对象共享的
-
static 类变量在类加载的时候就生成了
-
static变量保存在Class实例的尾部, Class对象确实在堆中。
-
在方法区的静态域中
什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的象去修改它时,修改的也是同一个变量。
如何定义类变量
- 访问修饰符 static 数据类型 变量名
如何访问类变量
- 类名.类变量名
package com.hspedu.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);
}
}
class A {
//类变量
//类变量的访问,必须遵守 相关的访问权限.
public static String name = "韩顺平教育";
//普通属性/普通成员变量/非静态属性/非静态成员变量/实例变量
private int num = 10;
}
类变量使用注意事项和细节
- 什么时候需要用类变量
- 当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如:定义学生类,统计所有学生共交多少钱。Student(name,static fee)
-
类变量与实例变量(普通属性)区别是类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
-
加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
-
类变量可以通过类名.类变量名或者对象名.类变量名来访问,但java设计者推荐我们使用类名.类变量名方式访问。前提是满足访问修饰符的访问权限和范围
-
实例变量不能通过类名.类变量名方式访问。
-
类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量了
-
类变量的生命周期是随类的加载开始,随着类消亡而销毁。
类方法
类方法也叫静态方法
形式如下:
访问修饰符 static 数据返回类型 方法名(){}
package com.hspedu.static_;
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);
}
}
类方法经典的使用场景
-
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提高开发效率
-
比如:工具类中的方法utils
-
Math类、Arrays类、Collections集合类看下源码:
-
小结
-
在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使明了,比如打印一维数组,冒泡排序,完成某个计算任务等
类方法使用注意事项和细节讨论
-
类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区,类方法中无this的参数,普通方法中隐含着this的参数
-
类方法可以通过类名调用,也可以通过对象名调用。
-
普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用。
-
类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以。
-
类方法(静态方法)中只能访问静态变量或静态方法。
-
普通成员方法,既可以访问普通变量(方法),也可以访问静态变量(方法)。 小结:静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员 (必须遵守访问权限)
package com.hspedu.static_;
public class StaticMethodDetail {
public static void main(String[] args) {
D.hi();//ok
//非静态方法,不能通过类名调用
//D.say();, 错误,需要先创建对象,再调用
new D().say();//可以
}
}
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(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();
}
}
main方法
深入理解main方法
解释main方法的形式:
public static void main (String[] args)
-
main方法是java虚拟机调用
-
java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是public
-
java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
-
该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所 运行的类的参数,案例演示,接收参数.
-
java 执行的程序(Hello.java) 参数1 参数2 参数3
特别提醒
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性。
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
在idea中向main方法传递参数
代码块
代码化块又称为初始化块,属于类中的成员[即是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{}包围起来。 但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象类显式调用,而是加载类时,或创建对象时隐式调用。
基本语法
[修饰符]{ 代码 };
注意:
-
修饰符可选,要写的话,也只能写static
-
代码块分为两类,使用static修饰的叫静态代码块,没有static修饰的,叫普通/非静态代码块。
-
逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
-
;号可以写上,也可以省略。
代码块的好处
-
相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
-
场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
package com.hspedu.codeblock_;
public class CodeBlock01 {
public static void main(String[] args) {
Movie movie = new Movie("你好,李焕英");
System.out.println("===============");
Movie movie2 = 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;
}
}
使用细节
-
static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
-
类什么时候被加载[重要背]
1. 创建对象实例时(new) 2. 创建子类对象实例,父类也会被加载 3. 使用类的静态成员时(静态属性,静态方法) 4. 子类使用静态成员时 加载子类时,必须加载完他的父类 -
普通的代码块,在创建对象实例时,会被隐式的调用。 被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。普通代码块,在new对象时,被调用,而且是被创建一个对象,就调用一次,与类的加载无关,可以简单理解,代码块是构造器的补充
小结
-
static代码块是类加载时,执行,只会执行一次
-
普通代码块是在创建对象时调用的,创建一次,调用一次
-
创建一个对象时,在一个类调用顺序是:(重点,难点)
1. 调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用) 2. 调用普通代码块和普通属性的初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按定义顺序调用) 3. 调用构造方法。 -
构造方法(构造器)的最前面其实隐含了super()和调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕 ,因此是优先于构造器和普通代码块执行的
-
我们看一下创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
1. 父类的静态代码块和静态属性(优先级一样,按定义顺序执行) 2. 子类的静态代码块和静态属性(优先级一样,按定义顺序执行) 3. 父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行) 3. 父类的构造方法 4. 子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行) 5. 子类的构造方法 -
静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员。
new B02();
-
先加载类信息 父类 子类
-
创建对象 从子类的构造器开始
package com.hspedu.codeblock_;
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 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的一个静态代码块..");//(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
}
}
单例设计模式
什么是设计模式
-
静态方法和属性的经典使用
-
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式。设计模式就像是经典的棋谱,不同的棋局,我们用不同的棋谱,免去我们自己再思 考和摸索
什么是单例模式
单例(单个的实例)
-
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并旦该类只提供一个取得其对象实例的方法)
-
单例模式有两种方式:1)饿汉式2)懒汉式
实现
- 构造器私有化
- 类的内部创建对象
- 向外暴露一个静态的公共方法。
- 代码实现
1. 饿汉式
package com.hspedu.single_;
public class SingleTon01 {
public static void main(String[] args) {
// GirlFriend xh = new GirlFriend("小红");
// GirlFriend xb = new GirlFriend("小白");
//通过方法可以获取对象
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
GirlFriend instance2 = GirlFriend.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//T
//System.out.println(GirlFriend.n1);
//...
}
}
//有一个类, GirlFriend
//只能有一个女朋友
class GirlFriend {
private String name;
//public static int n1 = 100;
//为了能够在静态方法中,返回 gf对象,需要将其修饰为static
//对象常是重量級的对象, 饿汉可能造成创建了对象,但是沒有使用.
private static GirlFriend gf = new GirlFriend("小红红");
//如何保障我们只能创建一个 GirlFriend 对象
//步骤[单例模式-饿汉式]
//1. 将构造器私有化
//2. 在类的内部直接创建对象(该对象是static)
//3. 提供一个公共的static方法,返回 gf对象
private GirlFriend(String name) {
System.out.println("構造器被調用.");
this.name = name;
}
public static GirlFriend getInstance() {
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name='" + name + '\'' +
'}';
}
}
2. 懒汉式
package com.hspedu.single_;
/**
* 演示懶漢式的單例模式
*/
public class SingleTon02 {
public static void main(String[] args) {
//new Cat("大黃");
//System.out.println(Cat.n1);
Cat instance = Cat.getInstance();
System.out.println(instance);
//再次調用getInstance
Cat instance2 = Cat.getInstance();
System.out.println(instance2);
System.out.println(instance == instance2);//T
}
}
//希望在程序運行過程中,只能創建一個Cat對象
//使用單例模式
class Cat {
private String name;
public static int n1 = 999;
private static Cat cat ; //默認是null
//步驟
//1.仍然構造器私有化
//2.定義一個static靜態屬性對象
//3.提供一個public的static方法,可以返回一個Cat對象
//4.懶漢式,只有當用戶使用getInstance時,才返回cat對象, 後面再次調用時,會返回上次創建的cat對象
// 從而保證了單例
private Cat(String name) {
System.out.println("構造器調用...");
this.name = name;
}
public static Cat getInstance() {
if(cat == null) {//如果還沒有創建cat對象
cat = new Cat("小可愛");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name='" + name + '\'' +
'}';
}
}
3.饿汉式VS懒汉式
-
二者最主要的区别在于创建对象的时机不同:饿汉式是在类加载就创建了对象实例,而懒汉式是在使用时才创建。
-
饿汉式不存在线程安全问题,懒汉式存在线程安全问题。(后面学习线程后,会完善一把)
-
饿汉式存在浪费资源的可能。因为如果程序员一个对象实例都没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题。
-
在我们javaSE标准类中,java.lang.Runtimei就是经典的单例模式。
final关键字
final中文意思:最后的,最终的. final可以修饰类、属性、方法和局部变量. 在某些情况下,程序员可能有以下需求,就会使用到fina:
-
当不希望类被继承时,可以用final修饰
-
当不希望父类的某个方法被子类覆盖/重写(override)时,可以用final关键字修饰。
-
当不希望类的的某个属性的值被修改,可以用final修饰.
-
当不希望某个局部变量被修改,可以使用final修饰
细节
-
final修饰的属性又叫常量,一般用XX_XX_XX来命名
-
final修饰的属性在定义时,必须赋初值,并且以后不能再修改,赋值可以在如下位置之一【选择一个位置赋初值即可】:
1 . 定义时:如public final double TAX_RATE=O.08; 2. 在构造器中 3. 在代码块中。 -
如果final修饰的属性是静态的,则初始化的位置只能是
1. 定义时 2. 在静态代码块不能在构造器中赋值。 -
final类不能继承,但是可以实例化对象。
-
如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
package com.hspedu.final_;
public class FinalDetail01 {
public static void main(String[] args) {
CC cc = new CC();
new EE().cal();
}
}
class AA {
/*
1. 定义时:如 public final double TAX_RATE=0.08;
2. 在构造器中
3. 在代码块中
*/
public final double TAX_RATE = 0.08;//1.定义时赋值
public final double TAX_RATE2 ;
public final double TAX_RATE3 ;
public AA() {//构造器中赋值
TAX_RATE2 = 1.1;
}
{//在代码块赋值
TAX_RATE3 = 8.8;
}
}
class BB {
/*
如果final修饰的属性是静态的,则初始化的位置只能是
1 定义时 2 在静态代码块 不能在构造器中赋值。
*/
public static final double TAX_RATE = 99.9;
public static final double TAX_RATE2 ;
static {
TAX_RATE2 = 3.3;
}
}
//final类不能继承,但是可以实例化对象
final class CC { }
//如果类不是final类,但是含有final方法,则该方法虽然不能重写,但是可以被继承
//即,仍然遵守继承的机制.
class DD {
public final void cal() {
System.out.println("cal()方法");
}
}
class EE extends DD { }
-
一般来说,如果一个类已经是final类了,就没有必要再将方法修饰成final方法
-
final不能修饰构造方法(即构造器)
-
final和static往往搭配使用,效率更高,不会导致类加载底层编译器做了优化处理。
-
包装类(Integer,Double,Float,Boolean等都是final),String也是final类。不能被继承
抽象类
当父类的一些方法不能确定时,可以用abstract关键字来修饰该方法,这个方法就是抽象方法,用abstract来修饰该类就是抽象类。
package com.hspedu.abstract_;
public class Abstract01 {
public static void main(String[] args) {
}
}
abstract class Animal {
private String name;
public Animal(String name) {
this.name = name;
}
//思考:这里eat 这里你实现了,其实没有什么意义
//即: 父类方法不确定性的问题
//===> 考虑将该方法设计为抽象(abstract)方法
//===> 所谓抽象方法就是没有实现的方法
//===> 所谓没有实现就是指,没有方法体
//===> 当一个类中存在抽象方法时,需要将该类声明为abstract类
//===> 一般来说,抽象类会被继承,有其子类来实现抽象方法.
// public void eat() {
// System.out.println("这是一个动物,但是不知道吃什么..");
// }
public abstract void eat() ;
}
抽象类的介绍
-
用abstract关键字来修饰一个类时,这个类就叫抽象类 访问修饰符abstract类名{ }
-
用abstract关键字来修饰一个方法时,这个方法就是抽象方法 访问修饰符abstract返回类型方法名(参数列表);//没有方法体
-
抽象类的价值更多作用是在于设计,是设计者设计好后,让子类继承并实现 抽象类0
-
抽象类,是考官比较爱问的知识点,在框架和设计模式使用较多
细节
-
抽象类不能被实例化
-
抽象类不一定要包含abstract方法。也就是说,抽象类可以没有abstract方法
-
一旦类包含了abstract方法,则这个类必须声明为abstract
-
abstract只能修饰类和方法,不能修饰属性和其它的。
-
抽象类可以有任意成员抽象类还是类,比如:非抽象方法、构造器、静态属性等等
-
抽象方法不能有主体,即不能实现如图所示
-
如果一个类继承了抽象类,则它必须实现抽象类的所有抽象方法,除非它自己也声明为abstract类
-
抽象方法不能使用private、final和static来修饰,因为这些关键字都是和重写相违背的。
抽象类的最佳实现模式--模板设计模式