JAVA学习
类变量和类方法、main方法
类变量
什么是类变量
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。
定义类变量
- 定义语法
访问修饰符 static 数据类型 变量名;【推荐】
static 访问修饰符 数据类型 变量名;
- 访问类变量
类名.类变量名【推荐】
或者 对象名.类变量名【静态变量的访问修饰符的访问权限和范围 和 普通属性是一样的。】
class Child { private String name; // 定义一个变量count,是一个类变量(静态变量)static 静态 // 该变量的最大特点是会被Child 类的所有对象实例共享 public static int count = 0; public Child(String name) { this.name = name; } }public class Test { public static void main(String[] args) { Child child01 = new Child("lili"); child01.count++; Child child02 = new Child("huahua"); child02.count++; System.out.println(child01.count); // 2 System.out.println(child02.count); // 2 System.out.println(Child.count); // 2 } }(1)static变量是同一个类所有对象共享
(2)static变量,在类加载的时候就生成了
类变量的使用细节
- 什么时候需要类变量:需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量
- 类变量与实例变量(普通属性)区别:类变量是该类的所有对象共享的,而实例变量是每个对象独享的。
- 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
- 类变量可以通过 类名.类变量名 或者 对象名.类变量名 来访问【要满足访问修饰符的访问权限和范围】
- 实例变量不能通过 类名.类变量名 方式访问
- 类变量是在类加载时就初始化了,也就是说,即使没有创建对象,只要类加载了,就可以使用类变量
- 类变量的生命周期是随着类的加载开始,随着类消亡而销毁
类方法
类方法基本介绍
类方法也叫静态方法
语法:
访问修饰符 static 数据返回类型 方法名() {...} 【推荐】
static 访问修饰符 数据返回类型 方法名() {...}
类方法的调用
使用方法:
类名.类方法名 或者 对象名.类方法名【前提是 满足访问修饰符的访问权限和范围】
public class StaticMethod { public static void main(String[] args) { Stu tom = new Stu("tom"); tom.payFee(100); Stu mary = new Stu("mary"); mary.payFee(200); // 输出当前所收取到的学费 Stu.showFee(); } } 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; //this.fee += fee; // 不能使用this } public static void showFee(){ System.out.println("总学费有:" + fee); } }
非静态成员(比如 name):属于【具体的对象】
- 类加载后,并不会为非静态成员分配内存;
- 只有当你
new Stu("张三")、new Stu("李四")创建对象实例时,才会为每个对象单独分配一份非静态成员的内存;- 每个学生对象有自己的
name,张三的 name 和李四的 name 互不干扰,这就是对象的独有属性。静态成员(比如 fee、payFee ()、showFee ()):属于【类本身】
- 当类第一次被加载时(还没创建任何对象),静态成员就会被初始化并分配唯一的一份内存,存放在 JVM 的静态区;
- 所有该类的对象,共享这一份静态成员(比如所有 Stu 对象的学费都累加到同一个 Stu.fee 里);
- 可以直接通过
类名.静态成员调用(比如Stu.payFee(1000)),无需创建对象。
原因 1:为什么静态方法不能用
this?
this关键字的唯一作用是:代表当前执行该方法 / 构造器的【具体对象实例】,简单说 ——this指向一个实实在在的对象。而静态方法的执行根本不依赖任何对象:
- 你可以直接调用
Stu.payFee(5000),此时没有创建任何 Stu 对象;- 既然没有对象,
this就没有任何指向的目标,Java 编译器自然会报错。一句话总结:
this是对象的 “代名词”,静态方法属于类、不关联对象,所以用不了this。
原因 2:为什么静态方法只能访问静态属性 / 变量?
这个原因是上面概念的直接推导,核心逻辑是:静态方法执行时,可能根本不存在任何对象,而非静态成员必须依附对象才能存在。
结合你的代码举例:
- 静态方法
payFee()属于 Stu 类,调用Stu.payFee(2000)时,没有任何 Stu 对象被创建;- 非静态变量
name是对象的独有属性,此时连对象都没有,name的内存都没分配,静态方法根本 “找不到”name;- 而静态变量
Stu.fee在类加载时就已经存在,无论有没有对象,静态方法都能直接访问到这唯一的一份内存。反过来想:非静态方法可以访问静态成员(比如在 Stu 里加一个普通方法,能直接用 fee),原因是 —— 非静态方法必须由对象调用(比如
stu1.xxx()),此时对象存在,类必然已经加载,静态成员一定存在。
类方法使用注意事项和细节讨论
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:类方法中无this参数
- 类方法可以通过类名调用,也可以通过对象名调用
- 普通方法和对象有关,需要通过对象名调用,不能通过类名调用。
- 类方法中不允许使用和对象有关的关键字,比如this和super。普通方法(成员方法)可以。
- 类方法(静态方法中)只能 访问静态变量或者静态方法
- 普通成员方法,可以访问 普通成员,即非静态成员,也可以访问静态成员。
总结:静态方法,只能访问静态的成员;非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)
main方法
深入理解main方法
解释main方法的形式:public static void main(String[] args){...}
- main方法是java虚拟机调用
- java虚拟机需要调用类的main()方法,所有该方法的访问权限必须是public
- java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
- 该方法接收String类型的数组参数,该数组中保存执行java命令时传递给所运行的类的参数
- java 执行的程序 参数1 参数2 参数3
特别提示
- 在main()方法中,我们可以直接调用main方法所在类的静态方法或静态属性
- 不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员。
代码块
基本介绍
代码化块又称为初始化块,属于类中的成员[即 是类的一部分],类似于方法,将逻辑语句封装在方法体中,通过{ }包围起来
但和方法不同,没有方法名,没有返回,没有参数,只有方法体,而且不用通过对象或类显式调用,而是加载类时,或创建对象时隐式调用。
基本语法
[修饰符]{
代码
};
说明注意:
- 修饰符可选,要写的话,也只能写static
- 代码块分为两类,使用static 修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块。
- 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
- ;可以写上,也可以省略
代码块的好处
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
- 场景:如果多个构造器中有重复的语句,可以抽取到初始化块中,提高代码的重用性。
public class CodeBlock01 { public static void main(String[] args) { Movie movie = new Movie("喜羊羊与灰太狼"); } } class Movie{ private String name; private double price; private String director; //这三个语句原本是这两个构造器中都包含的语句 // 但是看起来太过冗余 // 把相同的语句,放入到同一个代码块中 // 当不管调用哪个构造器,创建对象,都会先调用代码块的内容 { System.out.println("A"); System.out.println("B"); System.out.println("C"); } public Movie(String name){ System.out.println("Movie(String name)构造器被调用!"); this.name = name; } 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代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每创建一个对象,就执行。
- 类什么时候被加载[重要!]
- 创建对象实例时(new)
// 定义一个简单的类 class Person { // 静态代码块,用来直观看到类被加载的时机 static { System.out.println("Person类被加载了!"); } String name; public Person(String name) { this.name = name; } } public class ClassLoadDemo { public static void main(String[] args) { System.out.println("准备创建Person实例"); // 这行代码会触发Person类的加载 Person p = new Person("张三"); } }只有执行
new Person()时,Person 类才被加载,而不是程序一开始就加载。- 创建子类对象实例,父类也会被加载
// 父类 class Animal { static { System.out.println("Animal父类被加载了!"); } } // 子类 class Cat extends Animal { static { System.out.println("Cat子类被加载了!"); } } public class ClassLoadDemo2 { public static void main(String[] args) { System.out.println("准备创建Cat实例"); // 创建子类实例,触发父类+子类的加载 Cat cat = new Cat(); } }父类的加载是子类实例化的前提,哪怕只用到子类,父类也会被加载。
- 使用类的静态成员时(静态属性,静态方法)
class MathUtil { static { System.out.println("MathUtil类被加载了!"); } // 静态属性 public static final double PI = 3.1415926; // 静态方法 public static int add(int a, int b) { return a + b; } } public class ClassLoadDemo3 { public static void main(String[] args) { System.out.println("准备访问静态属性"); // 访问静态属性,触发类加载 System.out.println(MathUtil.PI); System.out.println("准备调用静态方法"); // 调用静态方法(此时类已加载,不会重复加载) System.out.println(MathUtil.add(1, 2)); } }关键点:类只会被加载一次,后续再访问静态成员,不会重复加载。
- 普通代码块,在创建对象实例时,会被隐式的调用。如果被创建一次,就会调用一次。如果只是使用类的静态成员时,普通代码块并不会执行。
- 创建一个对象时,在一个类 调用顺序是
- 先调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按照他们定义的顺序执行)
- 后调用普通代码块和普通属性初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通变量初始化,则按照他们定义的顺序执行)
- 最后调用构造方法
public class CodeBlockDetail02 { public static void main(String[] args) { A a = new A(); // 输出结果:(1)getN1被调用...(2)A 静态代码块01... // (3)getN2被调用...(4)A 的普通代码块01... // (5) A 的构造器被调用 } } class A{ public A(){ System.out.println("A 的构造器被调用..."); } private int n2 = getN2(); //普通属性/非静态属性的初始化 { // 普通代码块 System.out.println("A 的普通代码块01..."); } private static int n1 = getN1(); static{ System.out.println("A 静态代码块01..."); } public static int getN1(){ System.out.println("getN1被调用..."); return 100; } public int getN2(){ System.out.println("getN2被调用..."); return 200; } }- 构造器 的最前面隐含了super()和调用普通代码块,在类加载时,就执行完毕静态相关的代码块以及属性初始化,因此是优先于构造器和普通代码块执行的
public class CodeBlockDetail03 { public static void main(String[] args) { BBB b = new BBB(); // 运行结果:(1)AAA() 构造器被调用...(2)BBB 的普通代码块...(3)BBB() 构造器被调用... } } class 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() 构造器被调用..."); } }- 创建一个子类时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
- 父类静态代码块和静态属性(优先级一样,按照定义顺序执行)
- 子类静态代码块和静态属性(优先级一样,按照定义顺序执行)
- 父类普通代码块和普通属性
- 父类的构造方法
- 子类普通代码块和普通属性
- 子类的构造方法
- 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
小结:
- 静态代码块(
static{}):只执行一次,在类第一次被加载时执行(无论创建多少个对象,都只执行一次);- 普通代码块是在创建对象时调用的,创建一次,调用一次
- 类被加载的三种情况!
- 先静态后普通,代码块以及属性初始化看顺序,最后执行构造器
- 父类static代码块 > 本类静态代码块 > 父类的普通代码块 > 父类构造器> 本类的普通代码块> 本类构造器
静态方法和属性的经典使用
设计模式
设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。
单例模式
- 所谓的单例设计模式,单个实例,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法
- 单例模式有两种方式:1)饿汉式 2)懒汉式
单例模式应用实例
步骤如下:
1)构造器私有化 ---> 防止直接new
2)类的内部创建对象
3)向外暴露一个静态的公共方法 ----> getInstance
饿汉式
没有用上那个实例,但是还是创建了。可能造成创建了对象,但是没有使用,导致资源浪费
public class SingleTon01 {
public static void main(String[] args) {
// 通过方法可以获取对象
GirlFriend instance = GirlFriend.getInstance();
System.out.println(instance);
GirlFriend instance1 = GirlFriend.getInstance();
System.out.println(instance1);
System.out.println(instance1 == instance); // true
}
}
// 类 GirlFriend
// 只能有一个女朋友
class GirlFriend{
private String name;
// 为了能够在静态方法中,返回gf对象,需要将其修饰为static
private static GirlFriend gf = new GirlFriend("小红");
// 保证我们只能创建一个girlfriend
// 步骤:[单例模式-饿汉式]
// 1. 将构造器私有化
// 2. 在类的内部直接创建对象(该对象是static)
// 3. 提供一个公共的static 方法,返回gf对象
private GirlFriend(String name) {
this.name = name;
}
public static GirlFriend getInstance(){
return gf;
}
@Override
public String toString() {
return "GirlFriend{" +
"name = '" + name + '\'' +
"}";
}
}
懒汉式
使用时,再按照需要创建实例
// 懒汉式的单例模式
public class SingleTon02 {
public static void main(String[] args){
Cat instance = Cat.getInstance();
System.out.println(instance);
Cat instance1 = Cat.getInstance();
System.out.println(instance1);
System.out.println(instance1==instance); // true 表明,没有新创建2个对象
}
}
// 希望在程序运行过程中,只能创建一个cat对象
// 使用单例模式
class Cat{
private String name;
// 步骤
// 1. 将构造器私有化
// 2. 定义一个静态属性对象
// 3. 提供一个公共的static方法,可以返回一个Cat对象
// 4. 懒汉式,只有当用户使用这个方法时,才返回cat对象,当后面再次调用时,会返回上次创建的对象,
// 从而保证了单例
private static Cat cat;
private Cat(String name){
this.name = name;
}
public static Cat getInstance(){
if (cat == null){// 如果还没有创建一个cat对象
cat = new Cat("小花");
}
return cat;
}
@Override
public String toString() {
return "Cat{" +
"name = '" + name + '\'' +
"}";
}
}
饿汉式VS懒汉式
- 二者最注意的区别在于创建对象的时机不同:饿汉式是在类加载时就创建了对象实例,而懒汉式是在使用时才创建
- 饿汉式不存在线程安全问题,懒汉式存在线程安全问题
- 饿汉式存在资源浪费的可能,如果一个对象实例没有使用,那么饿汉式创建的对象就浪费了,懒汉式是使用时才创建,就不存在这个问题
更多推荐




所有评论(0)