连夜爆肝【java基础奠基篇】——面向对象<上>(封装,继承,多态)
本文最后更新于74 天前,如有错误请发送邮件到guzhougongzuoshi@aliyun.com

面向对象

面向对象的思想

  • 封装性
    • 封装是面向对象的核心思想。具有两层含义:
    1. 把对象的属性和行为看成一个密不可分的整体,将两者组合在一起,即封装在对象中。
    2. 信息隐藏,将不想让外界知道的信息隐藏起来
  • 继承性
    • 继承性主要是描述类与类之间的关系。通过继承,可以在原有类的基础上对功能进行扩展。例如有一个汽车类的普通特性和功能。进一步再生产轿车类,而轿车类中不仅应该包含汽车类的特性和功能,还应该增加轿车特有的功能,这时,可以让轿车类继承汽车类,在轿车类中单独添加其独有的特性和方法就可以了。继承不仅增强了代码的复用性,提高了开发效率,还能降低程序产生错误的可能性,为程序的维护以及扩展提供了便利。
  • 多态性
    • 多态性是指在一个类中定义的属性和方法被其他类继承后,他们可以具有不同的数据类型或表现出不同行为,这使得同一个属性和方法在不同类中具有不同语义。例如,汽车和飞机同样是交通工具,汽车在陆地上行驶,而飞机在天空中飞行,所以不同的对象表现的行为是不一样的。多态的特性使程序更抽象、便捷,有助于开发人员设计程序时分组协同开发。

类与对象

在面向对象技术中,为了做到让程序对事物的描述与事物在现实中的形态保持一致,提出了两个概念:对象

在java中类和对象是最基本、最重要的单元。

  • 表示某类群体的一些基本特征抽象
  • 对象 表示一个个具体的事物
    例如,在现实生活中,学生这个群体就可以表示为一个类,而某个具体的学生就可以称为对象。一个具体的学生有自己的姓名和年龄等信息,这些信息在面向对象的概念中称为属性;学生可以看书和打篮球,看书和打篮球这些行为在类中就可以称为方法。
    总结来说:
  • 类用于描述多个对象的共同特征。是对象的模板。
  • 对象用于描述现实中的个体,是类的实例。

    对象是根据类创建的,一个类可以对应多个对象。

类的定义

在面向对象的思想中最核心的就是对象,创建对象的前提是定义一个类。类是java中一个重要的引用数据类型,也是组成java程序的基本要素,所有的java程序都是基于类的。
类是对象的抽象,用于描述一组对象的共同特征和行为。类中可以定义成员变量和成员方法。成员变量用于描述对象的特征,成员变量也被称作对象的属性;成员方法用于描述对象的行为,可简化为方法。

类的定义格式如下:

class 类名{
    成员变量;
    成员方法;
}

根据上述格式定义Student类,成员变量包括name,age,sex;成员方法包括read().

class Student{
    String name; //声明String类型的变量name
    int age;     //声明int类型的变量age
    String sex;  //声明String类型的变量sex
    void read(){
        System.out.println("大家好,我是"+name+",我在看书");
    }
}

以上代码中定义了String类。其中Student是类名,name,age,sex是成员变量,read()是成员方法,在成员方法read()中可以直接访问成员变量name,

注意一点:
局部变量与成员变量的不同

在java中,定义在类中的变量称为成员变量,定义在方法中的变量称为局部变量,如果在某个方法中定义的局部变量与成员变量同名,这种情况是允许的,此时,在方法中通过变量名访问的局部变量,而非成员变量

class Student{
    int age = 30;
    void read(){
        int age = 50;
        System.out.println("大家好,我"+age+"岁,我在看书。");
    }
}

上述代码中,在Student类的read()方法中有一条打印语句,打印了变量age, 此时打印的是局部变量age,也就是说当另一个程序调用read()方法时,输出的age值为50,而不是30。

对象的创建与使用

上面我们定义了Student类,想要使用一个类,则必须创建该类的对象。在java程序中可以使用new关键字创建对象,使用new关键字创建对象的具体格式:

类名 对象名 = null;
对象名 = new 类名();

上述格式中,创建对象分为声明对象实例化对象两步。也可以直接通过下面的方式创建对象:

类名 对象名 = new 类名();

例如,创建Student类的实例对象,示例代码如下:

Student stu = new Student();

上述代码中,

  • new Student()用于创建Student类的一个示例对象(称为Student对象)
  • Student stu声明了一个Student类的变量stu。
  • 运算符= 将新创建的实例对象地址赋值给变量stu
  • 变量stu 引用的对象简称为stu对象。
class Student{
    String name;
    void read(){
        System.out.println("大家好,我是"+name+",我在看书");
    }
}

public class Test{
    public static void main(String[] args){
        Student stu = new Student();
    }
}

上述代码在main()方法中实例化了Student对象,对象名为stu。使用new关键字创建的对象在堆内存中分配空间。

对象名stu保存在栈内存中,而对象的属性信息则保存在对应的堆内存中。

创建对象后,可以使用对象访问类的某个属性或方法,对象属性和方法的访问通过点(“.”)运算符实现,具体如下:

对象名.属性名
对象名.方法名

示例:

class Student{
    String name;
    void read(){
        System.out.println("大家好,我是"+name);
    }
}
public class Example{
    public static void main(String[] args){  
        Student stu1 = new student();
        Student stu2 = new student();
        stu1.name = "邵雅琪";
        stu1.read();
        stu2.name = "李涵";
        stu2.read();
    }
}

上述示例中,分别定义了Student类和Example类。Student类中声明了name属性和read()方法。Example类的main()方法中创建了两个Student对象————stu1和stu2。

stu1.name = "邵雅琪";
stu1.read();

以上代码为stu1对象的name属性赋值为邵雅琪,通过stu1对象调用read()方法;stu2同理。
stu1对象和stu2对象调用read()方法时,打印的name值不相同。这是因为stu1对象和stu2对象在系统内存中是两个完全独立的个体,它们分别拥有各自的name属性,对stu1对象的name属性进行赋值并不影响stu2对象的name属性的值。

对象的引用传递

类属于引用数据类型,引用数据类型的内存空间可以同时被多个栈内存引用。
示例:

class Student{
    String name;
    int age;
    void read(){
        System.out.println("大家好,我是"+name+",年龄"+age);
    }
}

class Example{
    public static void main(String[] args){
        Student stu1 = new student();
        Student stu2 = new student();
        stu2 = stu1;
        stu1.name = "李涵";
        stu1.age = 20;
        stu2.age = 50;
        stu1.read();
        stu2.read();
    }
}

运行结果:
大家好,我是李涵,年龄50
大家好,我是李涵,年龄50

以上示例分别定义了Student类和Example类。
Student类中声明了name属性、age属性和read()方法。
Example类的main()方法中创建了两个Student对象————stu1和stu2,程序中只对stu1对象进行了实例化,对stu2对象未进行实例化。

stu2 = stu1;

这串代码是stu1对象给stu2对象分配使用权;

stu1.name = "李涵";
stu1.age = 20;

这串代码是分别给stu1对象的name属性和age属性赋值;

stu2.age = 50;

这串代码是对stu2对象的age属性赋值;

stu1.read();
stu2.read();

这串代码分别通过stu1对象和stu2对象调用read()方法。
从输出结果来看,stu1和stu2对象输出的内容是一致的,这是因为stu2对象获得了stu1对象的堆内存空间的使用权。“stu1.age = 20;”这行代码对stu1对象的age属性赋值之后,“stu2.age = 50;”这行代码通过stu2对象对age属性值进行了修改。
实际上,所谓的引用传递,就是将一个堆内存空间的使用权分配给多个栈内存空间使用,每个栈内存空间都可以修改堆内存空间的内容。

一个栈内存空间只能指向一个堆内存空间。如果想要再指向其他堆内存空间,就必须先断开已有的指向,才能分配新的指向。

访问控制权限

java中,针对成员变量属性,java提供了4种访问控制权限,分别是privatedefaultprotectedpublic。(级别从低到高)

private

私有访问权限 用于修饰类的属性和方法,也可以修饰内部类。类的成员一旦使用了private关键字修饰,则该成员只能在本类中访问

default

默认访问权限 如果一个类的属性或方法没有任何访问权限声明,则该属性或方法就是默认访问权限,可以被本包中的其他类访问,但不能被其他包的类访问。

protected

受保护访问权限 如果一个类中的成员使用了protected关键字修饰,则只能被本包的子类访问。

public

公共访问权限 如果一个类中的成员使用了public关键字修饰,则该成员可以在所有类中被访问,不管是否在同一个包中。

访问范围privatedefaultprotectedpublic
同一个类YesYesYesYes
同一个包中的类noYesYesYes
不同包的子类nonoYesYes
全局范围nononoYes
public class Test{
    public int aa;
    protected boolean bb;
    void cc(){
        System.out.println("包访问权限");
    }
    //private权限的内部类,即私有类,只能在本类中访问
    private class InnerClass{

    }
}

上面的代码运行会报错❌
外部类的访问权限只能是public和default,所以Test类只能使用public修饰或者不写修饰符。局部成员是没有访问控制权限的。因为局部变量只能在其所在的作用域内起作用,不可能被其他类访问,如果在程序中对局部成员使用访问控制权限修饰符,编译器会报错。

封装性

封装是面向对象的核心思想,掌握封装对于学习java面向对象的内容十分重要。
封装是指将类的实现细节包装、隐藏起来的方法。
封装可以被认为是一道保护屏障,防止本类的代码和数据被外部类定义的代码随机访问。

class Student{
    String name;
    int age;
    void read(){
        System.out.println("大家好,我是"+name+",年龄"+age);
    }
}

public class Example{
    public static void main(String[] args){
        Student stu = new Student();
        stu.name ="张三";
        stu.age = -18;
        stu.read();
    }
}

在上述示例代码中,age属性赋值为-18岁,这在程序中是不会有任何问题的,因为int的值可以取负值;但在现实中,-18岁明显是一个不合适的年龄值。为了避免这种错误情况发生,在设计Student类时,应该对成员变量的访问做出一些限定,不允许外界随意访问,这就是实现类的封装。

如何实现封装

类的封装是指将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象的内部信息,而是通过该类提供的方法实现对内部信息的访问。
封装的具体实现过程是:

  • 在定义一个类时,将类中的属性私有化,即使用private关键字修饰类的属性。
  • 私有属性只能在它所在的类中被访问。
  • 如果外界想要访问私有属性,需要提供一些使用public修饰的公有方法,其中包括用于获取属性值的getXxx()方法(也称作getter方法)和设置属性值的setXxx()方法(也称作setter方法)。
class Student{
    private String name;
    private int age;
    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
    }
    public int getAge(){
        return age;
    }
    public void setAge(int age){
        if(age<0){
            System.out.println("您输入的年龄有误!");
        }else{
            this.age = age;
        }
    }
    public void read(){
        System.out.println("大家好,我是"+name+",年龄"+age);
    }
}

public class Example{
    public static void main(String[] args){  
        Student stu = new Student();
        stu.setName("李涵");
        stu.setAge("-18");
        stu.read();

    }
}

以上代码中,使用private关键字将name属性和age属性声明为私有变量,并对外界提供公有的访问方法,其中,getName()方法和getAge()方法用于获取name属性和age属性,setName()方法和getAge()方法用于设置name属性和age属性的值。

运行结果:
您输入的年龄有误
大家好,我是李涵,年龄:0

当调用setAge()方法传入了一个负数(-18)时,系统提示年龄输入有误,age显示为初始值0。
这是因为setAge()方法对参数age进行了判断,如果age的值小于0,会打印”您输入的年龄有误”,age会采用初始值0(在java中int类型的变量初始值为0).

构造方法

实例化一个对象后,如果要为这个对象中的属性赋值,则必须直接访问对象的属性或调用setter方法。
如果需要在实例化对象时为这个对象的属性赋值,可以通过构造方法实现。
构造方法(也称为构造器)是类的一个特殊成员方法,在类实例化对象时自动调用。

定义构造方法

构造方法是一个特殊的成员方法,在定义时,有以下几点需要注意:

  • 构造方法的名称必须与类名一致
  • 构造方法名称前不能有任何返回值类型的声明
  • 不能在构造方法中使用return返回一个值,但可以单独写return语句作为方法的结束。
class Student{
    public Student(){
        System.out.println("调用了无参构造方法");
    }
}

public class Example{
    public static void main(String[] args){
        System.out.println("声明对象");
        Student stu = null;
        System.out.println("实例化对象");
        stu = new Student();
    }
}

运行结果:

声明对象
实例化对象
调用了无参构造方法

当调用关键字new实例化对象时,程序调用了Student类的无参构造方法。
在一个类中除了可以定义无参构造方法外,还可以定义有参构造方法,通过有参构造方法可以实现对属性的赋值。

示例:

class Student{
    private String name;
    private int age;
    public Student(String n,int a){
        name = n;
        age = a;
        System.out.println("调用了有参构造方法");
    }
    public void read(){  
        System.out.println("我是:"+name+",年龄"+age);
    }
}

public class Example{
    public static void main(String[] args){
        Student stu = new Student("李涵",18);
        stu.read();
    }
}

Student类中声明了私有属性name和age, 并且定义了有参构造方法。
“Student stu = new Student(“李涵”,18)”实例化Student对象,并传入参数”李涵”和18,分别赋值给name 和 age, 该过程会调用有参构造方法

运行结果:

调用了有参构造方法
我是:李涵,年龄18

由结果可知:name属性已经被赋值为”李涵“,age属性被赋值为18

class Student{
    private String name;
    private int age;
    public Student(){
        System.out.println("调用了一个无参的构造方法")
    }
    public Student(String n){
        name = n;
        System.out.println("调用了一个参数的构造方法");
    }
    public Student(String n,int a){  
        name = n;
        age = a;
        System.out.println("调用了两个参数的构造方法");
    }
    public void read(){
        System.out.println("I am"+name+",my age is"+age);
    }
}

public class Example{
    public static void main(String[] args){
        Student stu1 = new Student("李涵");
        Student stu2 = new Student("秋",18);
        Student stu3 = new Student();
        stu1.read();
        stu2.read();
        stu3.read();

    }
}

运行结果:

调用了一个参数的构造方法
调用了两个参数的构造方法
调用了一个无参的构造方法
我是李涵,年龄:0
我是秋,年龄:18
我是null,年龄:0

在上述示例中,Student类中定义了一个无参构造方法和两个有参构造方法。
在main()方法中,根据实例化对象时传入参数个数不同

  • stu1对象调用了只有一个参数的构造方法
  • stu2对象调用了有两个参数的构造方法
  • stu3对象调用了无参的构造方法

默认构造方法:
在java中的每个类都至少有一个构造方法。
如果在一个类中没有定义构造方法,系统会自动为这个类创建一个默认的构造方法,这个默认的构造方法没有参数,方法体中没有任何代码,所以java中默认的构造方法在程序运行时什么也不做。
下面的程序中,Student类的两种写法效果时完全一样的。
第一种写法:
class Student{ }
第二种写法:
class Student{
public Student(){}
}

由于系统提供的默认构造方法往往不能满足需求,因此,通常需要程序员自己在类中定义构造方法,一旦为类定义了构造方法,系统就不再提供默认的构造方法了
示例:

class Student{
    int age;
    public Student(int n){
        age = n;
    }
}

上面的Student类中定义了一个有参构造方法,这时系统不再提供默认的构造方法。

public class Example{
    public static void main(String[] args){
        Student stu = new Student();
    }
}

运行上述代码会报错:

java: 无法将类 Student06中的构造器 Student06应用到给定类型;
需要: int
找到: 没有参数
原因: 实际参数列表和形式参数列表长度不同

this关键字

当成员变量与局部变量发生重名问题时,需要使用this关键字分辨成员变量与局部变量。
java中的this关键字语法比较灵活,其作用主要有以下3个:

  • 调用本类中的属性
  • 调用构造方法
  • 调用成员方法

使用this关键字调用本类中的属性

Student类定义中的变量age表年龄,而构造方法中表示年龄的参数是a,这样的程序可读性很差。这时需要对一个类中表示年龄的变量进行统一命名,例如都声明成age。但是这样会导致成员变量和局部变量的命名冲突。

class Student{
    private String name;
    private int age;
    public Student(String name,int age){
        name = name;
        age = age;
    }
    public String read(){
        return "我是:"+name+",年龄"+age;
    }
}

public class Example{
    public static void main(String[] args){
        Student stu = new Student("李涵",18);
        System.out.println(stu.read());
    }
}

运行结果:

我是null,年龄:0

根据运行结果可知,stu对象名称为null,年龄为0,这表明构造方法中的赋值并没有成功,这是因为构造方法参数名称与对象成员变量名称相同,编译器无法确定哪个名称是当前对象的属性。
为了解决这个问题,java提供了关键字this指代当前对象,通过this可以访问当前对象的成员。

class Student{
    private String name;
    private int age;
    public Student(String name,int age){
        this.name = name;
        this.age = age;
    }
    public String read(){
        return "我是:"+name+",年龄"+age;
    }
}

public class Example{
    public static void main(String[] args){
        Student stu = new Student("李涵",18);
        System.out.println(stu.read());
    }
}

运行结果:

我是李涵,年龄:18

根据运行结果成功调用构造方法完成了stu对象的初始化。
这是因为在构造方法中,使用this关键字明确表示了类中的两个属性this.name和this.age;所以在进行赋值操作时不会产生歧义。

使用this关键字调用成员变量

class Student{
    public void openMouth(){
        ...
    }
    public void read(){  

        this.openMouth();
    }
}

上述代码中,在read()方法中使用this关键字调用了openMonth()方法。
需要注意的是此处的this关键字也可以省略不写

使用this关键字调用构造方法

构造方法在实例化对象时被java虚拟机自动调用。
在程序中不能像调用其他成员方法一样调用构造方法,但可以在一个构造方法中使用this(参数1,参数2)的形式调用其他的构造方法。

class Student{
    private String name;
    private int age;
    public Student(){  
        System.out.println("调用了无参构造方法");
    }
    public Student(String name,int age){
        this();
        this.name = name;
        this.age = age;
    }
    public String read(){
        return "我是:"+name+",年龄"+age;
    }
}

public class Example{
    public static void main(String[] args){
        Student stu = new Student("李涵",18);
        System.out.println(stu.read());
    }
}

Student类定义了一个无参构造函数和一个有参构造方法,并在有参构造方法中使用this()的形式调用本类中的无参构造方法。

运行结果:

调用了无参构造方法
我是李涵,年龄18

使用this调用类的构造方法时,应注意以下三点:

  • 只能在构造方法中使用this调用其他的构造方法,不能在成员变量中通过this调用构造方法。
  • 构造方法中,使用this调用其他构造方法的语句必须位于第一行,且只能出现一次。
    举例: public Student(String name){ System.out.println("有参构造法方法被调用了"); this(name); //❌❌❌不在第一行,编译错误 } 在 Java 中,构造函数中的 this() 调用必须是构造函数的第一条语句。也就是说,this(name) 必须放在构造函数体的第一行。如果不这样做,编译器会抛出错误,因为 Java 不允许在构造函数中有其他代码(如打印语句)在调用 this() 之前。
    正确代码如下✅: public class Student { public Student(String name) { this(name, 18); // 调用另一个构造函数 } public Student(String name, int age) { System.out.println("有参构造法方法被调用了"); // 初始化逻辑 } }
  • 不能在一个类的两个构造方法中使用this互相调用。下面写法是错误的❌:
  class Student{  

      public Student(){
          this("张三");
          System.out.println("有参构造方法被调用");
      }
      public Student(String name){  

          this();
          System.out.println("无参构造方法被调用")
      }
  }

错误原因:这种调用方式会导致死循环:无参构造函数调用有参构造函数,而有参构造函数又调用无参构造函数,二者形成了一个循环。由于有参构造函数调用无参构造函数,无参构造函数又调用了有参构造函数,所以这会导致无限递归,从而导致栈溢出错误(StackOverflowError)。


代码块

简单来说,就是用{}括起来的一段代码。
根据位置及声明关键字的不同,代码块可以分为四种:

  • 普通代码块
  • 构造块
  • 静态代码块
  • 同步代码块

普通代码块

普通代码块就是直接在方法或语句中定义的代码块

public class Test{
    public static void main(String[] args){
        {
            int age = 18;
            System.out.println("这是普通代码块。age:"+age);
        }
        int age = 30;
        System.out.println("age:"+age);
    }
}

在上述代码中,每一对{}括起来的代码都是都称为一个代码块。
Test是一个大的代码块,在Test中包含了main()代码块,在main()方法中又定义了一个局部代码块,局部代码块对main()方法1进行了”分割“,起到了限定作用域的作用
上述代码中,局部代码块定义了变量age,main()方法代码块中也定义了变量age,但由于两个变量处在不同的代码块,作用域不同,因此互不影响。

构造块

构造块是直接在类中定义的代码块。

public class Student09 {
    String name;
    {
        System.out.println("我是构造快");
    }

    //构造方法
        public Student09(){
            System.out.println("我是Student类的构造方法");
        }
}

public class Example09 {
    public static void main(String[] args){
        Student09 stu1 = new Student09();
        Student09 stu2 = new Student09();
    }
}

运行结果:

我是构造快
我是Student类的构造方法
我是构造快
我是Student类的构造方法

其中:

{
    System.out.println("我是构造块");
}

这些代码定义的代码块与构造方法、成员属性同级,这样的代码就是构造块
由此可得两点结论:

  • 在实例化Student类对象stu1、stu2时,构造快先于构造方法执行(这里和构造快写在前面还是后面没有关系)
  • 每当实例化一个Student类对象时,都会在执行构造方法之前进行构造快。

static 关键字

static关键字,用于修饰类的成员,如成员变量、成员方法以及代码块等,被static修饰的成员具备一些特殊性

静态属性

如果在java程序中使用static修饰属性,则该属性称为静态属性(也称作全局属性)。静态属性可以使用类名直接访问,访问格式:

类名.属性名
class Student{
    String name;
    int age;
    String school = " A 大学";
    public Student(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void info(){
        System.out.println("姓名:"+this.name+",年龄"+this.age+",学校:"+school);

    }
}

public class Example{
    public static void main(String[] args){
        Student stu1 = new Student("李涵",23);
        Student stu2 = new Student("雅琪",25);
        Student stu3 = new Student("盈盈",20);
        stu1.info();
        stu2.info();
        stu3.info();
        //修饰stu1对象的school的值
        stu1.school = "B 大学";
        System.out.println("修改stu1学校对象的学校信息为B大学后");
        stu1.info();
        stu2.info();
        stu3.info();
    }
}

Student类中定义了name属性,age属性和school属性,还定义了有参构造方法和info()方法,并在info()方法中输出了name属性,age属性和school属性的值。

运行结果:

姓名:李涵,年龄23,学校A 大学
姓名:雅琪,年龄25,学校A 大学
姓名:盈盈,年龄20,学校A 大学
修改stu1学校对象的学校信息为B大学后
姓名:李涵,年龄23,学校B 大学
姓名:雅琪,年龄25,学校A 大学
姓名:盈盈,年龄20,学校A 大学

李涵的学校信息由A大学修改为B大学,而雅琪和盈盈的学校信息没有变化,表明非静态属性是对象所有的,改变当前对象的属性值,不影响其他对象的属性值。
下面考虑一种情况:

假设A大学改名为B大学,而此时Student类已经产生了10万个学生对象,那么意味着,如果要修改这些学生对象的学校信息,则要把这10万个学生对象中的school属性全部修改一遍,共修改10万次,这样肯定是非常麻烦的。
那么为了解决这样的问题,可以使用static关键词修饰school属性,将其变为公共属性。这样,school属性就被分配一块内存空间,被school类的所有对象共享,只要某个对象进行了一次修改,全部学生对象的school属性值都会发生变化。

class Student{
    String name;
    int age;
    static String school = "A 大学";
    public Student(String name, int age){
        this.name = name;
        this.age = age;
    }
    public void info(){
        System.out.println("姓名:"+this.name+",年龄:"+this.age+",学校"+school);
    }
}

public class Example{
    public static void main(String[] args){
        Student stu1 = new Student("李涵",23);
        Student stu1 = new Student("雅琪",25);
        Student stu1 = new Student("盈盈",20);
        stu1.info();
        stu2.info();
        stu3.info();
        //修改stu1对象的school值
        stu1.school = "B 大学";
        System.out.println("修改stu1学校对象的学校信息为B大学后");
        stu1.info();
        stu2.info();
        stu3.info();

    }
}

运行结果:

姓名:李涵,年龄23,学校A 大学
姓名:雅琪,年龄25,学校A 大学
姓名:盈盈,年龄20,学校A 大学
修改stu1学校对象的学校信息为B大学后
姓名:李涵,年龄23,学校B 大学
姓名:雅琪,年龄25,学校B 大学
姓名:盈盈,年龄20,学校B 大学

虽然只修改了stu1对象的school属性,但是stu2对象和stu3对象的school属性值也随之变化,说明**使用static关键字声明的属性是所有对象共享的。

static 不能修饰局部变量
static 关键字只能修饰成员变量,不能修饰局部变量,否则编译器会报错:

 public class Student{
     public void study(){
         static int num= 10;  //❌❌❌报错,这行代码是非法的
     }
 }

### 静态方法
如果想要使用类中的成员方法,就需要先将这个类实例化。
而在实际开发时,开发人员有时希望在不创建对象的情况下,通过类名就可以直接调用某个方法,这时就需要使用静态方法,要实现静态方法,只需要在成员方法前加上static关键字。

类名.方法

实例对象名.方法
public class Student11 {
    private String name;
    private int age;
    private static String school = "A 大学";
    public Student11(String name,int age){
        this.name = name;
        this.age = age;
    }
    public void info(){
        System.out.println("姓名:"+this.name+",年龄:"+age+",学校:"+school);
    }
    public static String getSchool(){
        return school;
    }
    public static void setSchool(String s){
        school = s;
    }

}

public class Example11 {
    public static void main(String[] args) {
        Student11 stu1 = new Student11("李涵", 15);
        Student11 stu2 = new Student11("秦", 20);
        Student11 stu3 = new Student11("秋", 25);
        System.out.println("---修改前---");
        stu1.info();
        stu2.info();
        stu3.info();
        System.out.println("---修改后---");
        Student11.setSchool("b大学");
        stu1.info();
        stu2.info();
        stu3.info();
    }
}

运行结果:

—修改前—
姓名:李涵,年龄:15,学校:A 大学
姓名:秦,年龄:20,学校:A 大学
姓名:秋,年龄:25,学校:A 大学
—修改后—
姓名:李涵,年龄:15,学校:b大学
姓名:秦,年龄:20,学校:b大学
姓名:秋,年龄:25,学校:b大学

Student类将所有的属性都使用private 关键字进行封装。想要更改属性值,就必须使用setter方法。
由于school属性是用static关键词修饰的,所以可以直接使用类名调用school属性的setter方法。
在main()方法中,”Student11.setSchool(“b大学”);”这行代码直接使用类名Student对静态方法setSchool()进行调用,将静态属性school重新赋值为”B大学”。

static 与非 static 的区别
  • static 方法和属性:
    • 属于类:static 方法和属性属于类本身,而不是类的任何特定实例。因此,你可以使用类名直接访问它们,例如 Student11.setSchool(“b大学”)。
    • 共享性:静态属性在所有实例中共享,即无论你创建多少个 Student11 实例,school 的值都是共享的。
  • 非 static 方法和属性:
  • 属于实例:非 static 方法和属性属于类的实例。你必须先创建一个对象实例,才能通过该实例访问这些方法或属性。
  • 实例独立性:每个实例都有自己的非 static 属性,彼此之间不共享。

静态代码块

用static关键词修饰的代码块就是静态代码块。
当类被加载时,静态代码块就会执行,由于类只加载一次,所以静态代码块只执行一次。
在程序中,通常用于使用静态代码块对类的成员变量进行初始化。

public class Student12 {
    String name;
    {
        System.out.println("我是构造代码块");
    }
    static {
        System.out.println("我是静态代码块");
    }
    public Student12(){

        System.out.println("我是student类的构造方法");
    }

}

public class Example12 {
    public static void main(String[] args){
        Student12 stu1 =new Student12();
        Student12 stu2 = new Student12();
        Student12 stu3 = new Student12();
    }
}

运行结果:

我是静态代码块
我是构造代码块
我是student类的构造方法
我是构造代码块
我是student类的构造方法
我是构造代码块
我是student类的构造方法

由上可知:
代码块的执行顺序为:静态代码块–>构造代码块–>构造方法
static修饰的代码块会随着class文件一同加载,属于优先级最高的代码块
要注意:上例中main()方法中创建了3个Student对象,但在3次实例化对象过程中,静态代码块中的内容只输出了一次,这时因为静态代码块在类第一次使用时才会被加载,并且只会被加载一次。

感谢您的阅读
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇