Java面向对象(高级)-- static关键字的使用

文章目录

  • 一、static关键字
    • (1)类属性、类方法的设计思想
    • (2) static关键字的说明
    • (3)static修饰属性
      • 1. 复习变量的分类
      • 2. 静态变量
        • 2.1 语法格式
        • 2.2 静态变量的特点
        • 2.3 举例
          • 2.3.1 举例1
          • 2.3.2 举例2
          • 2.3.3 举例3
        • 2.4 静态变量的存储位置演进
          • 2.4.1 jdk6
          • 2.4.2 jdk7
          • 2.4.3 jdk8
        • 2.5 对比静态变量与实例变量
        • 2.6 内存解析
          • 2.6.1 举例1
          • 2.6.2 举例2
    • (4)static修饰方法
      • 1. 语法格式
      • 2. 静态方法的特点
      • 3.重点注意事项
      • 4. 举例
        • 4.1 举例1
        • 4.2 举例2
        • 4.3 举例3
        • 4.4 举例4
        • 4.5 举例5
    • (5)开发中的注意事项
  • 二、练习
    • (1)练习1
    • (2)练习2
    • (3)练习3
    • (4)面试题

一、static关键字

回顾类中的实例变量(即非static的成员变量)

class Circle{
	private double radius;	//之前写的属性没有static声明-->实例变量(每个对象有一份)
	public Circle(double radius){
        this.radius=radius;
	}
	public double findArea(){
        return Math.PI*radius*radius;
    }
}

创建两个Circle对象:(当我们创建类的多个对象的时候,每个对象都各自一份,互不影响)

Circle c1=new Circle(2.0);	//c1.radius=2.0
Circle c2=new Circle(3.0);	//c2.radius=3.0

Circle类中的变量radius是一个实例变量(instance variable),它属于类的每一个对象,c1中的radius变化不会影响c2的radius,反之亦然。

如果想让一个成员变量(如radius)被类的所有实例所共享,就用static修饰即可,称为类变量(或类属性)!

实例变量:每个对象一份

类变量:所有对象共用一个,归类多有

(1)类属性、类方法的设计思想

当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用

我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份

例如,所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
Java面向对象(高级)-- static关键字的使用_第1张图片

此外,在类中声明的实例方法,在类的外面必须要先创建对象,才能调用。但是有些方法的调用者和当前类的对象无关,这样的方法通常被声明为类方法,由于不需要创建对象就可以调用类方法,从而简化了方法的调用。

这里的类变量(成员变量)、类方法,只需要使用static修饰即可。所以也称为静态变量静态方法

(2) static关键字的说明

static: 静态的

static 用来修饰的结构:属性、方法; 代码块、内部类;(构造器不能够用static修饰)

  • 使用范围:
    • 在Java类中,可用static修饰属性、方法、代码块、内部类
  • 被修饰后的成员具备以下特点:
    • 随着类的加载而加载
    • 优先于对象存在
    • 修饰的成员,被所有对象所共享
    • 访问权限允许时,可不创建对象,直接被类调用

(3)static修饰属性

1. 复习变量的分类

方式1:按照数据类型

  • 基本数据类型
  • 引用数据类型

方式2:按照类中声明的位置

  • 成员变量:直接声明在类内部,方法、构造器外面的
    • 按照是否使用static修饰进行分类:
      • 使用static修饰的成员变量:静态变量、类变量
      • 不使用static修饰的成员变量:非静态变量、实例变量
  • 局部变量:方法内、方法形参、构造器内、构造器形参、代码块内等。

2. 静态变量

2.1 语法格式

使用static修饰的成员变量就是静态变量(或类变量、类属性)

[修饰符] class{
	[其他修饰符] static 数据类型 变量名;
}
2.2 静态变量的特点
  • 静态变量的默认值规则和实例变量一样。
  • 静态变量值是所有对象共享。
  • 静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。
  • 如果权限修饰符允许,在其他类中可以通过“类名.静态变量”直接访问,也可以通过“对象.静态变量”的方式访问(但是更推荐使用类名.静态变量的方式)。
  • 静态变量的get/set方法也静态的,当局部变量与静态变量重名时,使用“类名.静态变量”进行区分。
2.3 举例
2.3.1 举例1
class Chinese{
    //实例变量
    String name;
    int age;
    //类变量
    static String nation;//国籍

    public Chinese() {
    }

    public Chinese(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Chinese{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", nation='" + nation + '\'' +
                '}';
    }
}
public class StaticTest {
    public static void main(String[] args) {
        Chinese c1 = new Chinese("康师傅",36);
        c1.nation = "中华人民共和国";

        Chinese c2 = new Chinese("老干妈",66);

        System.out.println(c1);
        System.out.println(c2);

        System.out.println(Chinese.nation);
    }
}

对应的内存结构:(以经典的JDK6内存解析为例,此时静态变量存储在方法区)

Java面向对象(高级)-- static关键字的使用_第2张图片


2.3.2 举例2
package com.atguigu.keyword;

public class Employee {
    private static int total;//这里私有化,在类的外面必须使用get/set方法的方式来访问静态变量
    static String company; //这里缺省权限修饰符,是为了方便类外以“类名.静态变量”的方式访问
    private int id;
    private String name;

    public Employee() {
        total++;
        id = total;//这里使用total静态变量的值为id属性赋值
    }

    public Employee(String name) {
        this();
        this.name = name;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static int getTotal() {
        return total;
    }

    public static void setTotal(int total) {
        Employee.total = total;
    }

    @Override
    public String toString() {
        return "Employee{company = " + company + ",id = " + id + " ,name=" + name +"}";
    }
}
package com.atguigu.keyword;

public class TestStaticVariable {
    public static void main(String[] args) {
        //静态变量total的默认值是0
        System.out.println("Employee.total = " + Employee.getTotal());

        Employee e1 = new Employee("张三");
        Employee e2 = new Employee("李四");
        System.out.println(e1);//静态变量company的默认值是null
        System.out.println(e2);//静态变量company的默认值是null
        System.out.println("Employee.total = " + Employee.getTotal());//静态变量total值是2

        Employee.company = "尚硅谷";
        System.out.println(e1);//静态变量company的值是尚硅谷
        System.out.println(e2);//静态变量company的值是尚硅谷

        //只要权限修饰符允许,虽然不推荐,但是也可以通过“对象.静态变量”的形式来访问
        e1.company = "超级尚硅谷";

        System.out.println(e1);//静态变量company的值是超级尚硅谷
        System.out.println(e2);//静态变量company的值是超级尚硅谷
    }
}

2.3.3 举例3
package yuyi01;

/**
 * ClassName: ChineseTest
 * Package: yuyi01
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/14 0014 10:00
 */
public class ChineseTest {
    public static void main(String[] args) {
        Chinese c1=new Chinese();
        c1.name="路飞";
        c1.age=18;

        Chinese c2=new Chinese();
        c2.name="索隆";
        c2.age=19;

        System.out.println(c1);
        System.out.println(c2);
    }
}

class Chinese{  //中国人
    //非静态变量/实例变量(每个 对象--实例 一份)
    String name;
    int age;

    //toString方法
    @Override
    public String toString() {
        return "Chinese{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
    }
}

这里造了两个对象c1与c2,对于name和age来说,它们各自有一份,所以这里打印的时候,它们的信息互相不影响。如下:

image.png

现在我们引入静态变量static String nation;,并通过c1给它赋值c1.nation="China";,如下:

package yuyi01;

public class ChineseTest {
    public static void main(String[] args) {
        Chinese c1=new Chinese();
        c1.name="路飞";
        c1.age=18;
        c1.nation="China";

        Chinese c2=new Chinese();
        c2.name="索隆";
        c2.age=19;

        /*System.out.println(c1);
        System.out.println(c2);*/

        System.out.println(c1.nation);	//China
        System.out.println(c2.nation);	//China
    }
}

class Chinese{  //中国人
    //非静态变量 / 实例变量(每个 对象--实例 一份)
    String name;
    int age;

    //静态变量 / 类变量
    static String nation;   //国籍

    //toString方法
    @Override
    public String toString() {
        return "Chinese{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
    }
}

上面代码输出结果如下:

image.png

可以看到,虽然c2没有给nation赋值,但是打印仍然有China。

因为nation在整个内存当中只有一份,通过任何一个对象去调用都会影响其他对象。

现在通过c2改变nation的值c2.nation="CHN";,如下:

package yuyi01;

public class ChineseTest {
    public static void main(String[] args) {
        Chinese c1=new Chinese();
        c1.name="路飞";
        c1.age=18;
        c1.nation="China";

        Chinese c2=new Chinese();
        c2.name="索隆";
        c2.age=19;

        c2.nation="CHN";
        System.out.println(c1.nation);	//CHN
        System.out.println(c2.nation);	//CHN
    }
}

class Chinese{  //中国人
    //非静态变量 / 实例变量(每个 对象--实例 一份)
    String name;
    int age;

    //静态变量 / 类变量
    static String nation;   //国籍

    //toString方法
    @Override
    public String toString() {
        return "Chinese{" +
        "name='" + name + '\'' +
        ", age=" + age +
        '}';
    }
}

输出结果为:

image.png

因为内存中nation只有一份,所以无论谁去改,都会导致别人调用的时候也会被改变。


若此时将nation的static修饰去掉String nation;,结果就会变化:

package yuyi01;

public class ChineseTest {
    public static void main(String[] args) {
        Chinese c1=new Chinese();
        c1.name="路飞";
        c1.age=18;
        c1.nation="China";

        Chinese c2=new Chinese();
        c2.name="索隆";
        c2.age=19;

        System.out.println(c1);
        System.out.println(c2);

        System.out.println(c1.nation);  //China
        System.out.println(c2.nation);  //null

        c2.nation="CHN";
        System.out.println(c1.nation);  //China
        System.out.println(c2.nation);  //CHN
    }
}

class Chinese{  //中国人
    //非静态变量 / 实例变量(每个 对象--实例 一份)
    String name;
    int age;

    String nation;   //国籍

    //静态变量 / 类变量
    //static String nation;   //国籍

    //toString方法
    @Override
    public String toString() {
        return "Chinese{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

输出结果:

Java面向对象(高级)-- static关键字的使用_第3张图片

静态变量:整个内存中就一份,被类的多个对象所共享。

2.4 静态变量的存储位置演进
2.4.1 jdk6

Java面向对象(高级)-- static关键字的使用_第4张图片

永久代

栈、堆、方法区的概念是JVM规范说的(Oracle公司),具体的有实打实的虚拟机,比如Oracle官方发布的HotSpot,如下:

Java面向对象(高级)-- static关键字的使用_第5张图片

这个虚拟机在实现这套规范的时候,将“方法区”叫做“永久代”。

在HotSpot里面,永久代就可以看作方法区的一个具体实现。

在JDK6中,静态变量放在永久代中

2.4.2 jdk7

Java面向对象(高级)-- static关键字的使用_第6张图片

jdk7的时候有个变化,将静态变量从方法区移到堆里面了。

因为在堆里面GC比较频繁,方法区很少去GC,会导致回收不及时。

2.4.3 jdk8

Java面向对象(高级)-- static关键字的使用_第7张图片

jdk8及以后,方法区换了一个时间,叫“元空间”,开始使用本地内存。

静态变量还是在堆里面。

2.5 对比静态变量与实例变量

静态变量:类中的属性使用static进行修饰。

对比静态变量与实例变量:

  • ① 个数

    • 静态变量:在内存空间中只有一份,被类的多个对象所共享。
    • 实例变量:类的每一个实例(或对象)都保存着一份实例变量
  • ② 内存位置

    • 静态变量:jdk6及之前:存放在方法区。 jdk7及之后:存放在堆空间
    • 实例变量:存放在堆空间的对象实体中
  • ③ 加载时机

    • 静态变量(类变量):随着类的加载而加载,由于类只会加载一次,所以静态变量也只有一份。
    • 实例变量:随着对象的创建而加载。每个对象拥有一份实例变量。

举例

package yuyi01;

public class ChineseTest {
    public static void main(String[] args) {
        System.out.println(Chinese.nation); //不造对象直接打印nation
    }
}

class Chinese{  //中国人
    //静态变量 / 类变量
    static String nation="中国";   //国籍
}

输出结果:

image.png

此时对象还没有创建,就已经有nation属性了,可见静态变量随着类的加载而加载,有了类就有了静态变量,可以去调用。

  • ④ 调用者

    • 静态变量:可以被类直接调用,也可以使用对象调用
    • 实例变量:只能使用对象进行调用。(类不能调用)
  • ⑤ 判断是否可以调用 —> 从生命周期的角度解释

类变量 实例变量
yes no
对象 yes yes
  • ⑥ 消亡时机
    • 静态变量:随着类的卸载而消亡。
    • 实例变量:随着对象的消亡而消亡。

Java面向对象(高级)-- static关键字的使用_第8张图片

2.6 内存解析
2.6.1 举例1

Java面向对象(高级)-- static关键字的使用_第9张图片

2.6.2 举例2

Java面向对象(高级)-- static关键字的使用_第10张图片

(4)static修饰方法

1. 语法格式

用static修饰的成员方法就是静态方法。(类方法、静态方法)

[修饰符] class{
	[其他修饰符] static 返回值类型 方法名(形参列表){
        方法体
    }
}

2. 静态方法的特点

  • 静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。
  • 只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。
  • 在static方法内部只能访问类的static修饰的属性或方法,不能访问类的非static的结构。
  • 静态方法可以被子类继承,但不能被子类重写。
  • 静态方法的调用都只看编译时类型。
  • 因为不需要实例就可以访问static方法,因此static方法内部不能有this,也不能有super。如果有重名问题,使用“类名.”进行区别。

3.重点注意事项

  • 随着类的加载而加载
  • 可以通过“类.静态方法”的方式,直接调用静态方法。
  • 静态方法内可以调用静态的属性或静态的方法。(属性和方法的前缀使用的是当前类,可以省略),不可以调用非静态的结构。(比如:属性、方法)
  • static修饰的方法内,不能使用this和super。
  • 补充:在类的非静态方法中,可以调用当前类中的静态结构(属性、方法)或非静态结构(属性、方法)。
类方法(静态方法) 实例方法(非静态方法)
yes no
对象 yes yes

4. 举例

4.1 举例1
package yuyi01;

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

        System.out.println(Chinese.nation); //不造对象直接打印nation
        Chinese.show(); //在造对象之前就可以调用

    }
}

class Chinese{  //中国人
    //非静态变量 / 实例变量(每个 对象--实例 一份)
    String name;
    int age;

    //静态变量 / 类变量
    static String nation="中国";   //国籍

    //静态方法
    public static void show(){  //此方法是静态方法,随着类的加载而加载
        System.out.println("我是一个中国人");
    }
}

输出结果:

image.png

可以通过“类.静态方法”的方式,直接调用静态方法。

4.2 举例2
public static void show(){  //此方法是静态方法,随着类的加载而加载
    System.out.println("我是一个中国人");

    //调用静态结构  -- 可以
    System.out.println("name= "+nation);
    method1();

    //调用非静态结构 -- 不可以
    System.out.println("name= "+name);  //这里的name就是this.name,this表示当前对象,通过类调用show方法可能还没有对象,所以不能调用name属性
    eat("饺子");  //这里的eat()相当于this.eat(),同样这时候可能还没有对象呢,所以不能调用

}

注意:

Java面向对象(高级)-- static关键字的使用_第11张图片

静态方法里面没有this之说,省略的是当前类

Java面向对象(高级)-- static关键字的使用_第12张图片

因此可以这样来写:

Java面向对象(高级)-- static关键字的使用_第13张图片

static修饰的方法内,不能使用this和super。(this和super都是基于当前对象去调用结构,但是static方法内还没有对象之说)

整体代码:

package yuyi01;

public class ChineseTest {
    public static void main(String[] args) {
        System.out.println(Chinese.nation); //不造对象直接打印nation
        Chinese.show(); //在造对象之前就可以调用

    }
}

class Chinese{  //中国人
    //非静态变量 / 实例变量(每个 对象--实例 一份)
    String name;
    int age;

    //静态变量 / 类变量
    static String nation="中国";   //国籍

    public void eat(String food){
        System.out.println("我喜欢吃"+food);
    }

    public static void method1(){
        System.out.println("我是静态的测试方法");
    }

    public static void show(){  //此方法是静态方法,随着类的加载而加载
        System.out.println("我是一个中国人");

        //调用静态结构  -- 可以
        System.out.println("name= "+nation);
        method1();

        //调用非静态结构 -- 不可以
        //System.out.println("name= "+name);  //这里的name就是this.name,this表示当前对象,通过类调用show方法可能还没有对象,所以不能调用name属性
        //eat("饺子");  //这里的eat()相当于this.eat(),同样这时候可能还没有对象呢,所以不能调用

    }
}

输出结果:

Java面向对象(高级)-- static关键字的使用_第14张图片

静态方法内可以调用静态的属性或静态的方法。(属性和方法的前缀使用的是当前类,可以省略),不可以调用非静态的结构。(比如:属性、方法)

4.3 举例3
package yuyi01;

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

        System.out.println(Chinese.nation); //不造对象直接打印nation
        Chinese.show(); //在造对象之前就可以调用(通过类调用静态方法)

        Chinese c1=new Chinese();
        c1.name="路飞";
        c1.age=18;
        c1.nation="China";

        c1.show();//通过对象调用静态方法
    }
}

class Chinese{  //中国人
    //非静态变量 / 实例变量(每个 对象--实例 一份)
    String name;
    int age;

    //静态变量 / 类变量
    static String nation="中国";   //国籍

	//非静态方法
    public void eat(String food){
        System.out.println("我喜欢吃"+food);
    }

    //静态方法
    public static void method1(){
        System.out.println("我是静态的测试方法");
    }

    public static void show(){  //此方法是静态方法,随着类的加载而加载
        System.out.println("我是一个中国人");

        //调用静态结构  -- 可以
        System.out.println("name= "+nation);
        method1();

        //调用非静态结构 -- 不可以
        //System.out.println("name= "+name);  //这里的name就是this.name,this表示当前对象,通过类调用show方法可能还没有对象,所以不能调用name属性
        //eat("饺子");  //这里的eat()相当于this.eat(),同样这时候可能还没有对象呢,所以不能调用

    }


}

可以通过类调用静态方法:Chinese.show();

也可以通过对象调用静态方法:c1.show();

Java面向对象(高级)-- static关键字的使用_第15张图片

输出结果:

Java面向对象(高级)-- static关键字的使用_第16张图片

4.4 举例4

若此时有一个普通的非静态方法method2()。

public void method2(){
    System.out.println("我是非静态测试方法");
    //调用非静态结构   -- 可以
    System.out.println("name= "+this.name); //this就是方法的调用者,这个调用者一定是一个对象
    this.eat("饺子");

    //调用静态结构    -- 可以
    System.out.println("name= "+nation);
    method1();
}

整体代码:

package yuyi01;

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

        System.out.println(Chinese.nation); //不造对象直接打印nation
        Chinese.show(); //在造对象之前就可以调用(通过类调用静态方法)

        Chinese c1=new Chinese();
        c1.name="路飞";
        c1.age=18;
        c1.nation="China";

        c1.show();//通过对象调用静态方法
    }
}

class Chinese{  //中国人
    //非静态变量 / 实例变量(每个 对象--实例 一份)
    String name;
    int age;

    //静态变量 / 类变量
    static String nation="中国";   //国籍

    //非静态方法
    public void eat(String food){
        System.out.println("我喜欢吃"+food);
    }

    public void method2(){
        System.out.println("我是非静态测试方法");
        //调用非静态结构   -- 可以
        System.out.println("name= "+this.name); //this就是方法的调用者,这个调用者一定是一个对象
        this.eat("饺子");

        //调用静态结构    -- 可以
        System.out.println("name= "+nation);
        method1();
    }


    //静态方法
    public static void method1(){
        System.out.println("我是静态的测试方法");
    }

    public static void show(){  //此方法是静态方法,随着类的加载而加载
        System.out.println("我是一个中国人");

        //调用静态结构  -- 可以
        System.out.println("name= "+nation);
        method1();

        //调用非静态结构 -- 不可以
        //System.out.println("name= "+name);  //这里的name就是this.name,this表示当前对象,通过类调用show方法可能还没有对象,所以不能调用name属性
        //eat("饺子");  //这里的eat()相当于this.eat(),同样这时候可能还没有对象呢,所以不能调用

    }

}

对象可以调用类方法、类变量,实例变量、实例方法。

4.5 举例5
package com.atguigu.keyword;

public class Father {
    public static void method(){
        System.out.println("Father.method");
    }

    public static void fun(){
        System.out.println("Father.fun");
    }
}
package com.atguigu.keyword;

public class Son extends Father{
//    @Override //尝试重写静态方法,加上@Override编译报错,去掉Override不报错,但是也不是重写
    public static void fun(){
        System.out.println("Son.fun");
    }
}
package com.atguigu.keyword;

public class TestStaticMethod {
    public static void main(String[] args) {
        Father.method();
        Son.method();//继承静态方法

        Father f = new Son();
        f.method();//执行Father类中的method
    }
}

(5)开发中的注意事项

①开发中,什么时候需要将属性声明为静态的?

  • 判断当前类的多个实例是否能共享此成员变量,且此成员变量的值是相同的
  • 开发中,常将一些常量声明是静态的。比如:Math类中的PI

②开发中,什么时候需要将方法声明为静态的?

  • 方法内操作的变量如果都是静态变量(而非实例变量)的话,则此方法建议声明为静态方法。
  • 开发中,常常将工具类中的方法,声明为静态方法。比如:Arrays类、Math类

【举例1】

Arrays工具类:

Java面向对象(高级)-- static关键字的使用_第17张图片

比如调用sort方法,直接拿Arrays.(类.)来调用,因为它是静态方法,可以直接调用。

若不是静态方法,不能直接拿类调用,就需要new一个Arrays对象调用,但我们会发现两个对象a1与a2调用Arrays方法也没有什么区别,因为主要的区别在于形参。既然如此,工具类拿哪个对象调用都没有什么区别,不妨就静态化用类来调用。

之前说面向对象的时候,通过对象调用属性和方法。现在还有一类方法,不需要用对象去调用,可以用类来调用,它就是静态的方法。

【举例2】

若一个类里面有一个静态的变量(比如static String nation=“中国”;),这个变量被私有化了,就要有get/set方法,此时对这个属性设置get/set方法,写成静态还是非静态呢?

设置为非静态的get/set方法:语法上可以,因为非静态的方法可以调用静态的属性。

但是一般都设置为静态的。(让它们生命周期一致,既然属性都被静态化了,同时又私有化了,那么设置get/set就自然而然用类去调用)

编译器自动生成的也是静态方法。

class Chinese{  //中国人
    //静态变量 / 类变量
    static String nation="中国";   //国籍

    public static String getNation() {
        return nation;
    }

    public static void setNation(String nation) {
        Chinese.nation = nation;
    }
}

【举例3】

在main方法里面不能直接调用非静态方法,需要先创建测试类ChineseTest的对象,再通过对象调用test()方法。

如下:

public class ChineseTest {
    public static void main(String[] args) {
        test();	//直接调用不被允许
    }

    public void test(){	//非静态方法
        System.out.println("我是static的测试方法");    
    }

}

若将test()方法声明为static就可以,因为静态里面可以调静态,不需要再造对象了,前面省略了“ChineseTest.”。

如下:

public class ChineseTest {
    public static void main(String[] args) {
        test();	//可以,静态里面可以调静态,ChineseTest.test()
    }

    public static void test(){	//非静态方法
        System.out.println("我是static的测试方法");    
    }

}

二、练习

(1)练习1

看下面一段代码,输出结果是?

package yuyi01;

/**
 * ClassName: CircleTest
 * Package: yuyi01
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/15 0015 16:33
 */
public class CircleTest {
    public static void main(String[] args) {
        Circle c1=new Circle(); //创建的第一个圆
        System.out.println(c1);

        Circle c2=new Circle(); //创建的第二个圆
        System.out.println(c2);
    }
}

//类-圆
class Circle{
    double radius;  //每个圆的半径不一样,不需要静态(实例变量)
    int id; //给每个圆一个编号(实例变量)

    //每个对象去调用total的值都一样,可以设置它为静态
    static int total;   //记录创建了几个对象,即Circle实例的个数,与具体圆没啥关系,用类调用就行,可以静态(静态变量)

    //id自动赋值,创建第一个圆的对象是1001,第二个就是1002...
    public Circle(){    //通过构造器造对象
        this.id=init;
        init++;
    }

    private int init=1001;  //初始值默认为1001 (不对外暴露),,自动给id赋值的基数


    //重写toString方法
    @Override
    public String toString() {
        return "Circle{" +
        "radius=" + radius +
        ", id=" + id +
        '}';
    }
}

输出结果:

image.png

①先来看init的问题。

可以看到,半径都是0.0,没有问题,因为在构造器里面本身就没有赋值。

但是id是怎么回事?明明init++了,为啥第二个圆的id还是1001?

Java面向对象(高级)-- static关键字的使用_第18张图片

这是因为init变量没有静态修饰,每个对象都有一份。

如图:
Java面向对象(高级)-- static关键字的使用_第19张图片

这就有悖于我们的初衷,希望在原有的init基础之上给id赋值,所以init不应该每个对象一份:private static int init=1001;

再次运行会发现第二个圆圈的id变成了1002,如下:

Java面向对象(高级)-- static关键字的使用_第20张图片

若再创建一个Circle对象,id会自动生成1003.

因为init在内存中只有一份,用它来体现自动编号。


②现在来看total的问题,创建了几个对象如何体现?

现在调用肯定不太靠谱,结果是0,如下:

Java面向对象(高级)-- static关键字的使用_第21张图片

可以在构造器里面实现,每次创建一个对象,就让total++。如下:

class Circle{
    double radius;  //每个圆的半径不一样,不需要静态(实例变量)
    int id; //给每个圆一个编号(实例变量)

    //每个对象去调用total的值都一样,可以设置它为静态
    static int total;   //记录创建了几个对象,即Circle实例的个数,与具体圆没啥关系,用类调用就行,可以静态(静态变量)

    //id自动赋值,创建第一个圆的对象是1001,第二个就是1002...
    public Circle(){    //通过构造器造对象
        this.id=init;
        init++;
        total++;
    }
	//...
}

输出可以看到结果为2(创建了两个对象):

Java面向对象(高级)-- static关键字的使用_第22张图片

若此时total不是静态变量,首先就不能用Circle.total来调用了,需要使用对象来调用。

比如此时用c1来调用,那么结果就成1了,先看代码:

package yuyi01;

public class CircleTest {
    public static void main(String[] args) {
        Circle c1=new Circle(); //创建的第一个圆
        System.out.println(c1);

        Circle c2=new Circle(); //创建的第二个圆
        System.out.println(c2);

        //System.out.println(Circle.total);   //total为非静态变量时不可以这样用类调用

        System.out.println(c1.total);
    }
}

//类-圆
class Circle{
    double radius;  //每个圆的半径不一样,不需要静态(实例变量)
    int id; //给每个圆一个编号(实例变量)

    //每个对象去调用total的值都一样,可以设置它为静态
    //static int total;   //记录创建了几个对象,即Circle实例的个数,与具体圆没啥关系,用类调用就行,可以静态(静态变量)
    int total;  //若total是非静态变量

    //id自动赋值,创建第一个圆的对象是1001,第二个就是1002...
    public Circle(){    //通过构造器造对象
        this.id=init;
        init++;
        total++;
    }

    private static int init=1001;  //初始值默认为1001 (不对外暴露),自动给id赋值的基数


    //重写toString方法
    @Override
    public String toString() {
        return "Circle{" +
        "radius=" + radius +
        ", id=" + id +
        '}';
    }
}

输出结果:

Java面向对象(高级)-- static关键字的使用_第23张图片

此时每个对象都有一份total,各自将自己的total从0改为了1。


③针对上面这个问题,若此时提供带参的构造器

再创建一个Circle对象,通过带参构造器创建。如下:

package yuyi01;

public class CircleTest {
    public static void main(String[] args) {
        Circle c1=new Circle(); //创建的第一个圆
        System.out.println(c1);

        Circle c2=new Circle(); //创建的第二个圆
        System.out.println(c2);

        Circle c3=new Circle(2.3);  //调用有参数的构造器
        System.out.println(c3);

        System.out.println(Circle.total);   //total为非静态变量时不可以这样用类调用

        //System.out.println(c1.total);
    }
}

//类-圆
class Circle{
    double radius;  //每个圆的半径不一样,不需要静态(实例变量)
    int id; //给每个圆一个编号(实例变量)

    //每个对象去调用total的值都一样,可以设置它为静态
    static int total;   //记录创建了几个对象,即Circle实例的个数,与具体圆没啥关系,用类调用就行,可以静态(静态变量)

    //int total;  //若total是非静态变量--不推荐这样

    //id自动赋值,创建第一个圆的对象是1001,第二个就是1002...
    public Circle(){    //通过构造器造对象
        this.id=init;
        init++;
        total++;
    }

    public Circle(double radius){
        this(); //将本类的空参构造器里面的东西拿过来
        this.radius=radius;
    }

    private static int init=1001;  //初始值默认为1001 (不对外暴露),自动给id赋值的基数


    //重写toString方法
    @Override
    public String toString() {
        return "Circle{" +
        "radius=" + radius +
        ", id=" + id +
        '}';
    }
}

输出结果:

Java面向对象(高级)-- static关键字的使用_第24张图片

(2)练习2

题目描述

编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,

定义封装这些属性的方法。账号要自动生成。

编写主类,使用银行账户类,输入、输出3个储户的上述信息。

考虑:哪些属性可以设计成static属性。

代码

【Account.java】

package yuyi02;

/**
 * ClassName: Account
 * Package: yuyi02
 * Description:
 *  编写一个类实现银行账户的概念,包含的属性有“帐号”、“密码”、“存款余额”、“利率”、“最小余额”,
 *  定义封装这些属性的方法。账号要自动生成。
 * @Author 雨翼轻尘
 * @Create 2023/11/15 0015 21:45
 */
public class Account {
    //属性
    private int id; //账号 (不需要静态,每个人账号不一致)
    private String password;    //密码 (不需要静态,每个人密码不一致)
    private double balance; //存款余额  (不需要静态,每个人存款余额不一致)
    private static double interestRate; //利率 (可以静态,每个人面对同样的银行,所以利率是一样的)
    private static double minBalance=1.0;   //最小余额 (可以静态,每个人面对同样的银行,所以最小余额是一样的)

    private static int init=1001;   //用于自动生成id的基数

    //构造器
    public Account() {
        this.id=init;
        init++;

        password="000000";  //默认密码
    }

    //只有非静态的属性,因为造对象,相当于设置对象的情况,所以这里只设置非静态的,静态直接拿类去调用即可,和构造器关系就不大了(编译器自动生成也没有提供静态的属性)
    public Account(String password, double balance) {   //题目要求id是自动生成,所以设置另外两个非静态即可
        this.id=init;
        init++;

        this.password = password;
        this.balance = balance;
    }

    //get/set方法
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public static double getInterestRate() {
        return interestRate;
    }

    public static void setInterestRate(double interestRate) {
        Account.interestRate = interestRate;
    }

    public static double getMinBalance() {
        return minBalance;
    }

    public static void setMinBalance(double minBalance) {
        Account.minBalance = minBalance;
    }

    //toString方法
    @Override
    public String toString() {
        return "Account{" +
        "id=" + id +
        ", password='" + password + '\'' +
        ", balance=" + balance +
        '}';
    }
}

【AccountTest.java】

package yuyi02;

/**
 * ClassName: AccountTest
 * Package: yuyi02
 * Description:
 *  编写主类,使用银行账户类,输入、输出3个储户的上述信息。
 * @Author 雨翼轻尘
 * @Create 2023/11/15 0015 22:45
 */
public class AccountTest {
    public static void main(String[] args) {
        Account acct1=new Account();
        System.out.println(acct1);

        Account acct2=new Account("123456",2000);
        System.out.println(acct2);

        Account.setInterestRate(0.0123);    //设置银行存款的利率
        Account.setMinBalance(10);  //设置银行最小存款额度
        System.out.println("银行存款的利率为: "+Account.getInterestRate());  //打印利率 (静态变量可以直接用类来调用)
        System.out.println("银行最小存款额度为: "+Account.getMinBalance());
    }

}

输出结果

Java面向对象(高级)-- static关键字的使用_第25张图片

(3)练习3

题目描述

自定义一个数组的工具类,封装常用的数组算法。

代码

【MyArrays.java】

package yuyi03;

/**
 * ClassName: MyArrays
 * Description:
 *      根据上一章数组中的常用算法操作,自定义一个操作int[]的工具类。
 *      涉及到的方法有:求最大值、最小值、总和、平均数、遍历数组、复制数组、数组反转、
 *              数组排序(默认从小到大排序)、查找等
 * @Author 雨翼轻尘
 * @Create 14:13
 * @Version 1.0
 */
public class MyArrays {

    /**
     * 获取int[]数组的最大值
     * @param arr 要获取最大值的数组
     * @return 数组的最大值
     */
    public static int getMax(int[] arr){
        int max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if(max < arr[i]){
                max = arr[i];
            }
        }
        return max;
    }

    /**
     * 获取int[]数组的最小值
     * @param arr 要获取最小值的数组
     * @return  数组的最小值
     */
    public static int getMin(int[] arr){
        int min = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if(min > arr[i]){
                min = arr[i];
            }
        }
        return min;
    }

    public static int getSum(int[] arr){
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
        }
        return sum;
    }

    public static int getAvg(int[] arr){

        return getSum(arr) / arr.length;
    }

    public static void print(int[] arr){ //[12,231,34]
        System.out.print("[");

        for (int i = 0; i < arr.length; i++) {
            if(i == 0){
                System.out.print(arr[i]);
            }else{
                System.out.print("," + arr[i]);
            }
        }

        System.out.println("]");
    }

    public static int[] copy(int[] arr){
        int[] newArr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            newArr[i] = arr[i];
        }
        return newArr;
    }

    public static void reverse(int[] arr){
        for(int i = 0,j = arr.length - 1;i < j;i++,j--){
            //交互arr[i] 与 arr[j]位置的元素
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }

    public static void sort(int[] arr){
        for(int j = 0;j < arr.length - 1;j++){
            for (int i = 0; i < arr.length - 1 - j; i++) {
                if(arr[i] > arr[i + 1]){
                    //交互arr[i] 和 arr[i + 1]
                    int temp = arr[i];
                    arr[i] = arr[i + 1];
                    arr[i + 1] = temp;
                }

            }
        }
    }

    /**
     * 使用线性查找的算法,查找指定的元素
     * @param arr 待查找的数组
     * @param target 要查找的元素
     * @return target元素在arr数组中的索引位置。若未找到,则返回-1
     */
    public static int linearSearch(int[] arr,int target){

        for(int i = 0;i < arr.length;i++){
            if(target == arr[i]){
                return i;
            }

        }

        //只要代码执行到此位置,一定是没找到
        return -1;

    }

}

【MyArraysTest.java】

package yuyi03;

/**
 * ClassName: MyArraysTest
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 14:16
 * @Version 1.0
 */
public class MyArraysTest {
    public static void main(String[] args) {

        //MyArrays arrs = new MyArrays();
        int[] arr = new int[]{34,56,223,2,56,24,56,67,778,45};

        //求最大值
        //System.out.println("最大值为:" + arrs.getMax(arr));
        System.out.println("最大值为:" + MyArrays.getMax(arr));

        //求平均值
        //System.out.println("平均值为:" + arrs.getAvg(arr));
        System.out.println("平均值为:" + MyArrays.getAvg(arr));

        //遍历
        //arrs.print(arr);
        MyArrays.print(arr);

        //查找
        //int index = arrs.linearSearch(arr,24);
        int index = MyArrays.linearSearch(arr,24);
        if(index >= 0){
            System.out.println("找到了,位置为:" + index);
        }else{
            System.out.println("未找到");
        }

        //排序
        //arrs.sort(arr);
        MyArrays.sort(arr);

        //遍历
        //arrs.print(arr);
        MyArrays.print(arr);

    }
}

输出结果

Java面向对象(高级)-- static关键字的使用_第26张图片

⚡注意

之前这些方法写的都是非静态的,这就意味着在调用的时候,就需要先造一个对象。

其实咱们造一个对象也没啥用,无论是哪个对象,调用这个方法也没什么区别,主要区别在于形参

这就是工具类,主要看形参不一样。(工具类里面的方法通常加上static)

(4)面试题

package yuyi03;

/**
 * ClassName: StaticTest
 * Package: yuyi03
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2023/11/16 0016 9:56
 */
public class StaticTest {
    public static void main(String[] args) {
        Order order = null;
        order.hello();
        System.out.println(order.count);
    }
}

class Order {
    public static int count = 1;    //静态变量

    public static void hello() {    //静态方法
        System.out.println("hello!");
    }
}

输出结果:

image.png

可以看到并没有空指针异常

静态方法和静态变量随着Order类的创建而创建。(静态不依赖于对象

创建对象时没有在堆空间里面给对象分配空间,没有加载成员变量和方法,静态变量和方法是加载了的。

order只是没有在堆空间中创建对象实体,但静态变量和方法和对象实体在堆空间中并不是存放在同一个位置。


PS:
终于进入面向对象高级部分了,知识点越来越多,要及时复习。
粉丝破5w了,在此纪念一下,快毕业了,也不知道以后敲博客的时间还会有多少,迷茫又有点惆怅,未来很长,我会慢慢去走,你呢?

你可能感兴趣的:(Java基础,java,面向对象(高级),static关键字)