文章目录
1、类变量
1.1、提出问题
有一群小孩在玩堆雪人,不时有新的小孩加入,请问如何知道现在共有多少人在玩?
1.1.1、传统方式解决
public class ChildGame_ {
public static void main(String[] args) {
// 定义一个变量 count, 统计有多少小孩加入了游戏
int count = 0;
Child child1 = new Child("张三");
child1.joinGame();
count++;
Child child2 = new Child("李四");
child2.joinGame();
count++;
Child child3 = new Child("王五");
child3.joinGame();
count++;
System.out.println("共有" + count + "个小孩在游戏中...");
}
}
class Child {
private String name;
public Child(String name) {
this.name = name;
}
public void joinGame() {
System.out.println(name + "加入了游戏...");
}
}
1.1.2、类变量方式解决
public class ChildGame_ {
public static void main(String[] args) {
Child child1 = new Child("张三");
child1.joinGame();
Child child2 = new Child("李四");
child2.joinGame();
Child child3 = new Child("王五");
child3.joinGame();
System.out.println("共有" + Child.count + "个小孩在游戏中...");
}
}
class Child {
private String name;
// 定义一个变量 count, 是一个类变量(静态变量) static 静态
// 该变量最大的特点就是会被 Child 类的所有的对象实例共享
public static int count = 0;
public Child(String name) {
this.name = name;
}
public void joinGame() {
System.out.println(name + "加入了游戏...");
count++;
}
}
1.2、类变量内存布局
1.3、类变量定义
类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量
定义语法:
访问修饰符 static 数据类型 变量名; 【推荐】
static 访问修饰符 数据类型 变量名;
1.4、如何访问类变量
类名.类变量名 【推荐】
对象名.类变量名 【静态变量的访问修饰符的访问权限和范围和普通属性是一样的】
package visit_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);
// System.out.println(a.num); // 报错
}
}
class A {
// 类变量
// 类变量的访问, 必须遵守相关的访问权限
public static String name = "张三";
// 普通属性/普通成员变量/非静态属性/非静态成员变量/实例变量
private int num = 10;
}
1.5、注意事项和细节讨论
- 什么时候需要类变量
当我们需要让某个类的所有对象都共享一个变量时,就可以考虑使用类变量(静态变量):比如定义学生类,统计所有学生共交了多少钱- 类变量与实例变量(普通属性)的区别
类变量是该类的所有对象共享的,而实例变量是每个对象独享的- 加上static称为类变量或静态变量,否则称为实例变量/普通变量/非静态变量
- 类变量可以通过 类名.类变量名 或者 对象.类变量名 来访问,但java设计者推荐我们使用 类名.类变量方式访问【前提是 满足访问修饰符的访问权限和范围】
- 实例变量不能通过 类名.类变量名 方式访问
- 类变量是在类加载时就初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量
- 类变量的生命周期是随类的加载开始,随着类消亡而销毁
public class StaticDetail {
public static void main(String[] args) {
B b = new B();
// System.out.println(B.n1); // 报错
System.out.println(B.n2);
// 静态变量是类加载的时候, 就创建了, 所以我们没有创建对象实例
// 也可以通过类名.类变量名来访问
System.out.println(C.address);
}
}
class B {
public int n1 = 100;
public static int n2 = 200;
}
class C {
public static String address = "北京";
}
2、类方法
2.1、类方法定义
类方法也叫静态方法
形式如下:
访问修饰符 static 数据返回类型 方法名() {} 【推荐】
static 访问修饰符 数据返回类型 方法名() {}
2.2、类方法的调用
使用方式:
类名.类方法名 或者 对象名.类方法名 【前提是满足访问修饰符的访问权限和范围】
package StaticMethod;
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);
}
}
2.3、经典使用场景
当方法中不涉及到任何和对象相关的成员,则可以将方法设计成静态方法,提交开发效率
比如:工具类中的方法 utils
Math类、Arrays类、Collections 集合类
小结:
在程序员实际开发,往往会将一些通用的方法,设计成静态方法,这样我们不需要创建对象就可以使用了,比如打印一维数组,冒泡排序,完成某个计算任务等
2.4、注意事项和细节讨论
- 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:
类方法中无this的参数
普通方法中隐含着this的参数- 类方法可以通过类名调用,也可以通过对象名调用
- 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名(参数),不能通过类名调用
- 类方法中不允许使用和对象有关的关键字,比如this和super,普通方法(成员方法)可以
- 类方法(静态方法)中只能访问静态变量 或者 静态方法
- 普通成员方法,既可以访问非静态成员,也可以访问静态成员
小结:静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员(必须遵守访问权限)
public class StaticMethodDetail {
public static void main(String[] args) {
D.hi(); // ok
// 非静态方法, 不能通过类名调用
// D.say(); // 错误, 需要先创建对象, 再调用
new D().say(); // ok
}
}
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(n1); // 报错
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();
}
}
3、深入理解 main 方法
3.1、说明
- 在 main()方法中,我们可以直接调用 main 方法所在类的静态方法或静态属性
- 但是,不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员
package main01;
public class Main01 {
// 静态的变量/属性
private static String name = "hello world";
// 非静态的变量/属性
private int n1 = 10000;
// 静态方法
public static void hi() {
System.out.println("Main01 的 hi 方法");
}
// 非静态方法
public void cry() {
System.out.println("Main01 的 cry 方法");
}
public static void main(String[] args) {
// 可以直接使用 name
// 1. 静态方法 main 可以访问本类的静态成员
System.out.println("name=" + name);
hi();
// 2. 静态方法 main 不可以访问本类的非静态成员
// System.out.println("n1=" + n1); // 报错
// cry(); // 报错
// 3. 静态方法 main 要访问本类的非静态成员, 需要先创建对象, 再调用即可
Main01 main01 = new Main01();
System.out.println(main01.n1);
main01.cry();
}
}
3.2、给main方法传递参数
public class TestMain {
public static void main(String[] args) {
for (int i=0; i<args.length; i++) {
System.out.print(args[i] + "\t");
}
}
}
在ide中如何传递参数
4、代码块
4.1、定义
4.2、基本语法
[修饰符] {
代码
};
- 修饰符可选,要写的话,也只能写static
- 代码块分为两类,使用static 修饰的叫静态代码块,没有static修饰的,叫普通代码块/非静态代码块
- 逻辑语句可以为任何逻辑语句(输入、输出、方法调用、循环、判断等)
- ;号可以写上,也可以省略
4.3、代码块的优势
- 相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作
- 场景:如果多个构造器中都有重复的语句,可以抽取到初始化块中,提高代码的重用性
4.4、代码实现
package CodeBlock01;
public class CodeBlock01 {
public static void main(String[] args) {
new Movie("扬名立万");
System.out.println("========");
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;
}
}
4.5、注意事项和细节讨论
- static代码块也叫静态代码块,作用就是对类进行初始化,而且它随着类的加载而执行,并且只会执行一次,如果是普通代码块,每创建一个对象就执行
- 类什么时候被加载【重要!!!】
(1)创建对象实例时(new)
(2)创建子类对象实例,父类也会被加载
(3)使用类的静态成员时(静态属性,静态方法)
【 A 类 extends B类 的静态块 】- 普通的代码块,在创建对象实例时,会被隐式的调用
被创建一次,就会调用一次
如果只是使用类的静态成员时,普通代码块并不会执行- 小结:
(1)static代码块是类加载时执行,只会执行一次
(2)普通代码块是在创建对象时调用的,创建一次,调用一次
(3)类加载的三种情况- 创建一个对象时,在一个类的调用顺序
(1)调用静态代码块和静态属性初始化(注意:静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态变量初始化,则按他们定义的顺序调用)
(2)调用普通代码块和普通属性初始化(注意:普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按他们定义的顺序调用)
(3)调用构造方法- 构造器的最前面其实隐含了 super() 和 调用普通代码块,静态相关的代码块,属性初始化,在类加载时,就执行完毕,因此是优先于构造器和普通代码块执行的
- 创建一个子类对象时(继承关系),他们的静态代码块,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
(1)父类的静态代码块和静态属性(优先级一样,按定义顺序执行)
(2)子类的静态代码块和静态属性(优先级一样,按定义顺序执行)
(3)父类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
(4)父类的构造方法
(5)子类的普通代码块和普通属性初始化(优先级一样,按定义顺序执行)
(6)子类的构造方法- 静态代码块只能直接调用静态成员(静态属性和静态方法),普通代码块可以调用任意成员
第 1 - 4点的代码说明
package CodeBlockDetail01;
public class CodeBlockDetail01 {
public static void main(String[] args) {
// 类被加载的情况举例
// 1. 创建对象实例时(new)
AA aa = new AA();
/*
BB 的静态代码 1 被执行...
AA 的静态代码 1 被执行...
*/
// 2. 创建子类对象实例, 父类也会被加载, 而且, 父类先被加载, 子类后被加载
AA aa2 = new AA();
/*
static 代码块, 是在类加载时, 执行的, 而且只会执行一次
由于代码第七行已经执行过, 不会重复执行
*/
// 3. 使用类的静态成员时(静态属性, 静态方法)
System.out.println(Cat.n1);
/*
Animal 的静态代码 1 被执行...
Cat 的静态代码 1 被执行...
999
*/
DD dd = new DD();
/*
DD 的静态代码 1 被执行...
DD 的普通代码块...
*/
// 普通的代码块, 在创建对象实例时, 会被隐式的调用
// 被创建一次, 就会调用一次
DD dd1 = new DD(); // DD类中的静态代码块在28行已经执行过, 所以静态代码块的内容不会输出
/*
DD 的普通代码块...
*/
// 如果只是使用类的静态成员时, 普通代码块并不会执行
System.out.println(DD.n1); // 8888, 静态模块块一定会执行, 由于该类的静态代码块在28行已经执行过, 所以静态代码块的内容不会输出
/*
8888
*/
}
}
class DD {
public static int n1 = 8888; // 静态属性
// 静态代码块
static {
System.out.println("DD 的静态代码 1 被执行...");
}
// 普通代码块, 在 new 对象时, 被调用, 而且是每创建一个对象, 就调用一次
// 可以这样简单的理解, 普通代码块是构造器的补充
{
System.out.println("DD 的普通代码块...");
}
}
class Animal {
// 静态代码块
static {
System.out.println("Animal 的静态代码 1 被执行...");
}
}
class Cat extends Animal {
public static int n1 = 999; // 静态属性
// 静态代码块
static {
System.out.println("Cat 的静态代码 1 被执行...");
}
}
class BB {
// 静态代码块
static {
System.out.println("BB 的静态代码 1 被执行...");
}
}
class AA extends BB {
// 静态代码块
static {
System.out.println("AA 的静态代码 1 被执行...");
}
}
第 5 点的代码说明
package CodeBlockDetail01;
public class CodeBlockDetail02 {
public static void main(String[] args) {
A a = new A();
/*
1. A 静态代码块 01
2. getN1 被调用...
3. A 普通代码块 01
4. getN2 被调用...
5. A() 构造器被调用
*/
}
}
class A {
{ // 普通代码块
System.out.println("A 普通代码块 01");
}
private int n2 = getN2(); // 普通属性的初始化
static { // 静态代码块
System.out.println("A 静态代码块 01");
}
// 静态属性的初始化
private static int n1 = getN1();
public static int getN1() {
System.out.println("getN1 被调用...");
return 100;
}
public int getN2() { // 普通方法/非静态方法
System.out.println("getN2 被调用...");
return 200;
}
// 无参构造器
public A() {
System.out.println("A() 构造器被调用");
}
}
第 6 点的代码说明
package CodeBlockDetail01;
public class CodeBlockDetail03 {
public static void main(String[] args) {
new BBB();
/*
1. AAA 的普通代码块
2. AAA() 构造器被调用....
3. BBB 的普通代码块...
4. BBB() 构造器被调用....
*/
}
}
class AAA { // 父类 Object
{
System.out.println("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() 构造器被调用....");
}
}
第 7 - 8点的代码说明
package CodeBlockDetail01;
public class CodeBlockDetail04 {
public static void main(String[] args) {
// (1) 进行类的加载
// 1.1 先加载 父类 A02
// 1.2 再加载 B02
// (2) 创建对象
// 2.1 从子类的构造器开始
// new B02();
/*
1. getVal01
2. A02 的一个静态代码块..
3. getVal03
4. B02 的一个静态代码块..
5. A02 的第一个普通代码块..
6. getVal02
7. A02 的构造器
8. getVal04
9. B02 的第一个普通代码块..
10. B02 的构造器
*/
new C02();
}
}
class A02 { // 父类
private static int n1 = getVal01();
static {
System.out.println("A02 的一个静态代码块..");
}
{
System.out.println("A02 的第一个普通代码块..");
}
public int n3 = getVal02(); // 普通属性的初始化
public static int getVal01() {
System.out.println("getVal01");
return 10;
}
public int getVal02() {
System.out.println("getVal02");
return 10;
}
public A02() { // 构造器
// 隐藏
// super()
// 普通代码和普通属性的初始化......
System.out.println("A02 的构造器");
}
}
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 的一个静态代码块..");
}
public int n5 = getVal04();
{
System.out.println("B02 的第一个普通代码块..");
}
public static int getVal03() {
System.out.println("getVal03");
return 10;
}
public int getVal04() {
System.out.println("getVal04");
return 10;
}
public B02() { // 构造器
// 隐藏了
// super()
// 普通代码块和普通属性的初始化...
System.out.println("B02 的构造器");
// TODO Auto-generated constructor stub
}
}
package CodeBlockDetail01;
class Test {
Sample sam1 = new Sample("sam1 成员初始化");
static Sample sam = new Sample("静态成员 sam 初始化 ");
static {
System.out.println("static 块执行");
if (sam == null) System.out.println("sam is null");
}
Test() // 构造器
{
System.out.println("Test 默认构造函数被调用");
}
//主方法
public static void main(String str[]) {
Test a = new Test(); // 无参构造器
/*
1. 静态成员 sam 初始化
2. static 块执行
3. sam1 成员初始化
4. Test 默认构造函数被调用
*/
}
}
class Sample {
Sample(String s) {
System.out.println(s);
}
Sample() {
System.out.println("Sample 默认构造函数被调用");
}
}