JavaSE-02笔记【封装~this和static】

文章目录

  • 1.封装(掌握)
    • 1.1 封装的理解
    • 1.2 不封装存在的问题
    • 1.3 怎么封装
    • 1.4 难点解惑
    • 1.5 练习
  • 2. this 和 static
    • 2.1 this(掌握)
      • 2.1.1 this是什么
      • 2.1.2 this 在实例方法中使用
      • 2.1.3 this访问实例变量
      • 2.1.4 this扩展①
      • 2.1.5 this扩展②
      • 2.1.6 this总结
      • 2.1.7 this在构造方法中使用
    • 2.2 static(掌握)
      • 2.2.1 static概述
      • 2.2.2 静态变量
        • 2.2.2.1 静态变量使用“引用”访问?
        • 2.2.2.2 静态方法使用“引用”访问?
      • 2.2.3 静态代码块
        • 2.2.3.1 语法格式与介绍
        • 2.2.3.2 静态代码块中访问静态变量
      • 2.2.4 静态方法
    • 2.3 难点解惑
    • 2.4 练习
      • 2.4.1 选择题
      • 2.4.2 判断下面代码的输出结果,并说明原因
      • 2.4.3 找出下面代码的错误,并说明为什么错了

1.封装(掌握)

1.1 封装的理解

封装从字面上来理解就是包装的意思,专业点就是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。 系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。也就是说用户是无需知道对象内部的细节,但可以通过该对象对外提供的接口来访问该对象
现实世界事物封装举例:

  1. “鼠标”,外部有一个壳,将内部的原件封装起来,至于鼠标内部的细节是什么,我们不需要关心,只需要知道鼠标对外提供了左键、右键、滚动滑轮这三个简单的操作。对于用户来说只要知道左键、右键、滚动滑轮都能完成什么功能就行了。为什么鼠标内部的原件要在外部包装一个“壳”呢?起码内部的原件是安全的。
  2. “数码相机”,外部也有一个壳,将内部复杂的结构包装起来,对
    外提供简单的按键,这样每个人都可以很快的学会照相了,因为它的按键很简单,另外照相机内部精密的原件也受到了壳儿的保护,不容易坏掉。

封装的好处:
封装之后就形成了独立实体,独立实体可以在不同的环境中重复使用,显然封装可以降低程序的耦合度,提高程序的扩展性,以及重用性或复用性,例如“鼠标”可以在 A 电脑上使用,也可以在 B 电脑上使用。另外封装可以隐藏内部实现细节,站在对象外部是看不到内部复杂结构的,对外只提供了简单的安全的操作入口,所以封装之后,实体更安全了。

1.2 不封装存在的问题

如下代码:

class MobilePhone {
	//电压:手机正常电压在 3~5V
	double voltage;
}
public class MobilePhoneTest {
	public static void main(String[] args) {
		MobilePhone phone = new MobilePhone();
		phone.voltage = 3.7;
		System.out.println("手机电压 = " + phone.voltage);
		phone.voltage = 100;
		System.out.println("手机电压 = " + phone.voltage);
	}
}

以上程序 MobilePhone 类未进行封装,其中的电压属性 voltage 对外暴露,在外部程序当中可以对 MobilePhone 对象的电压 voltage 属性进行随意访问,导致了它的不安全,例如手机的正常电压是 3~5V,但是以上程序已经将手机电压设置为 100V,这个时候显然是要出问题的,但这个程序编译以及运行仍然是正常的,没有出现任何问题,这是不对的。

1.3 怎么封装

为了保证内部数据的安全,这个时候就需要进行封装了,封装的第一步就是将应该隐藏的数据隐藏起来,起码在外部是无法随意访问这些数据的,可以使用 java 语言中的 private 修饰符,private 修饰的数据表示私有的,私有的数据只能在本类当中访问。

class MobilePhone {
	//电压:手机正常电压在 3~5V
	private double voltage;
}
public class MobilePhoneTest {
	public static void main(String[] args) {
		MobilePhone phone = new MobilePhone();
		phone.voltage = 3.7;
		System.out.println("手机电压 = " + phone.voltage);
		phone.voltage = 100;
		System.out.println("手机电压 = " + phone.voltage);
	}
}

直接编译报错:在这里插入图片描述
通过以上的测试,手机对象的电压属性确实受到了保护,在外部程序中无法访问了。但从
当前情况来看,voltage 属性有点儿过于安全了,一个对象的属性无法被外部程序访问,自然这
个数据就没有存在的价值了。所以这个时候就需要进入封装的第二步了:对外提供公开的访问入口,让外部程序统一通过这个入口去访问数据,我们可以在这个入口处设立关卡,进行安全
控制,这样对象内部的数据就安全了。

对于“一个”属性来说,我们对外应该提供几个访问入口呢?通常情况下我们访问对象的
某个属性,不外乎读取(get)和修改(set),所以对外提供的访问入口应该有两个,这两个
方法通常被称为 set 方法和 get 方法(请注意:set 和 get 方法访问的都是某个具体对象的属性,
不同的对象调用 get 方法获取的属性值不同,所以 set 和 get 方法必须有对象的存在才能调用,
这样的方法定义的时候不能使用 static 关键字修饰,被称为实例方法。实例方法必须使用“引
用”的方式调用。
还记得之前我们接触的方法都是被 static 修饰的,这些方法直接采用“类名”
的方式调用,而不需要创建对象,在这里显然是不行的)。请看以下代码:

class MobilePhone {
	//电压:手机正常电压在 3~5V
	private double voltage;
	public MobilePhone(){
	}
	public void setVoltage(double _voltage){
		if(_voltage < 3 || _voltage > 5){
		//当电压低于 3V或者高于 5V时抛出异常,程序则终止
		throw new RuntimeException("电压非法,请爱护手机!");
		}
		//程序如果能执行到此处说明以上并没有发生异常,电压值合法
		voltage = _voltage;
	}
	public double getVoltage(){
		return voltage;
	}
}
public class MobilePhoneTest {
	public static void main(String[] args) {
		MobilePhone phone = new MobilePhone();
		phone.setVoltage(3.7);
		System.out.println("手机电压 :" + phone.getVoltage());
		phone.setVoltage(100);
		System.out.println("手机电压 :" + phone.getVoltage());
	}
}

运行结果:
在这里插入图片描述
通过以上程序,可以看出 MobilePhone 的 voltage 属性不能在外部程序中随意访问了,只能调用 MobilePhone 的 setVoltage()方法来修改电压,调用 getVoltage()方法来读取电压,在setVoltage()方法中编写了安全控制代码,当电压低于 3V,或者高于 5V 的时候,程序抛出了异常,不允许修改电压值,程序结束了。只有合法的时候,才允许程序修改电压值。(后续可以通过学习异常机制对异常进行处理)

总之,在 java 语言中封装的步骤应该是这样的:

  1. 需要被保护的属性使用 private 进行修饰;
  2. 给这个私有的属性对外提供公开的 set 和 get 方法,其中 set 方法用来修改属性的值,get 方法用来读取属性的值。
    • set 和 get 方法在命名上也是有规范的: set 方法名是 set + 属性名(属性名首字母大写),get 方法名是 get + 属性名(属性名首字母大写)。
    • set 方法有一个参数,用来给属性赋值,set 方法没有返回值,一般在 set 方法内部编写安全控制程序,因为毕竟 set 方法是修改内部数据的。
    • get 方法不需要参数,返回值类型是该属性所属类型。
    • 【先记住】 set 方法和 get 方法都不带 static 关键字,不带 static 关键字的方法称为实例方法,这些方法调用的时候需要先创建对象,然后通过“引用”去调用这些方法,实例方法不能直接采用“类名”的方式调用。

示例代码:

class Product{
    private int no;
    private String name;
    private double price;
    public Product(){

    }

    public Product(int _no, String _name, double _price){
        no = _no;
        name = _name;
        price = _price;
    }

    public void setNo(int _no){
        no = _no;
    }
    public int getNo(){
        return no;
    }

    public String getName() {
        return name;
    }

    public void setName(String _name) {
        name = _name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double _price) {
        price = _price;
    }
}

public class ProductTest {
    public static void main(String[] args) {
        Product p1 = new Product(10000 , "小米 12" , 3599.0);
        System.out.println("商品编号:" + p1.getNo());
        System.out.println("商品名称:" + p1.getName());
        System.out.println("商品单价:" + p1.getPrice());
        p1.setNo(70000);
        p1.setName("小米 14");
        p1.setPrice(5999);
        System.out.println("商品编号:" + p1.getNo());
        System.out.println("商品名称:" + p1.getName());
        System.out.println("商品单价:" + p1.getPrice());
    }
}

运行结果:
在这里插入图片描述

补充:构造方法中给属性赋值是在创建对象的时候完成的,当对象创建完毕之后,属性可能还是会被修改的,后期要想修改属性的值,这个时候就必须调用 set 方法了。

1.4 难点解惑

理解:为什么在进行封装时,提供的 setter 和 getter 方法就不能添加 static 关键字了呢?
这块内容在后期学习 static 关键字之后自然就好理解了,先简单解释一下:带有 static 关键字的方法是静态方法,不需要创建对象,直接通过“类”来调用。对于没有 static 关键字的方法被称为实例方法,这些方法执行时要求必须先创建对象,然后通过“引用”的方式来调用。而对于封装来说,setter 和 getter 方法都是访问对象内部的属性,所以 setter 和 getter 方法在定义时不能添加 static 关键字。

1.5 练习

第一题:定义“人”类,“人”类包括这些属性:姓名、性别、年龄等。使用封装的方式编写
程序,要求对年龄进行安全控制,合法的年龄范围为[0~100],其他值表示不合法。

class People {
    private String name;
    private char sex;
    private int age;

    public People() {
    }

    public People(String _name, char _sex, int _age) {
        name = _name;
        sex = _sex;
        age = _age;
    }

    public String getName() {
        return name;
    }

    public void setName(String _name) {
        name = _name;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char _sex) {
        sex = _sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int _age) {
        if(_age < 0 || _age > 100){
            //当年龄不合法则抛出异常,程序则终止
            throw new RuntimeException("非法年龄!");
        }
        age = _age;
    }
}

public class PeopleTest{
    public static void main(String[] args) {
        People p1 = new People("李宗盛", '男', 23);
        System.out.println(p1.getName());
        System.out.println(p1.getSex());
        System.out.println(p1.getAge());
        p1.setAge(101);
    }
}

2. this 和 static

2.1 this(掌握)

2.1.1 this是什么

先看一段代码:

class Customer {
	private String name;
	public Customer(){
	}
	public Customer(String _name){
		name = _name;
	}
	public void setName(String _name){
		name = _name;
	}
	public String getName(){
		return name;
	}
}
public class CustomerTest {
	public static void main(String[] args) {
		Customer jack = new Customer("jack");
		Customer rose = new Customer("rose");
	}
}

以上代码的this内存结构图为:
在这里插入图片描述
this可以看做一个变量,它是一个引用,存储在 Java 虚拟机堆内存的对象内部,this这个引用保存了当前对象的内存地址指向自身,任何一个堆内存的 java 对象都有一个 this,也就是说创建 100 个 java 对象则分别对应 100 个this。通过以上的内存图,可以看出jack 引用保存的内存地址是0x1111,对应的this 引用保存的内存地址也是 0x1111,所以jack 引用this 引用是可以划等号的。也就是说访问对象的时候 jack.namethis.name 是一样的,都是访问该引用所指向对象的 name 属性。

this 指向当前对象,也可以说this代表当前对象this可以使用在实例方法中以及构造方法中,语法格式分别为this.xxthis(xx)

2.1.2 this 在实例方法中使用

this 不能出现在带有 static 的方法当中,如下:

public class ThisInStaticMethod {
	public static void main(String[] args) {
		ThisInStaticMethod.method();
	}
	public static void method(){
		System.out.println(this);
	}
}

编译报错:
在这里插入图片描述

通过以上的测试得知 this不能出现在 static的方法当中,原因如下:
首先static 的方法,在调用的时候是不需要创建对象的,直接采用“类名”的方式调用,也就是说static 方法执行的过程中是不需要“当前对象”参与的,所以 static 的方法中不能使用 this,因为 this 代表的就是“当前对象”。

之前的“封装”学习中,曾编写属性相关的 set 和 get 方法,set 和 get方法在声明的时候不允许带 static 关键字,我们把这样的方法叫做实例方法,与实例变量一样,实例变量和实例方法都是对象相关,必须有对象的存在,然后通过“引用”去访问。

将 set 和 get 方法设计为实例方法的原因: 因为 set 和 get 方法操作的是实例变量,“不同的对象”调用 get 方法最终得到的数据是不同的,例如 zhangsan 调用 getName()方法得到的名字是zhangsan,lisi 调用 getName()方法得到的名字是 lisi,显然 get 方法是一个对象级别的方法,不能直接采用“类名”调用,必须先创建对象,再通过“引用”去访问。

this 可以出现在实例方法当中,因为实例方法在执行的时候一定是对象去触发的,实例方法一定是对象才能去调用的,而 this 恰巧又代表“当前对象”,所以 “谁”去调用这个实例方法 this 就是“谁” 。请看以下代码:

class Customer {
	private String name;
	public Customer(){
	}
	public Customer(String _name){
		name = _name;
	}
	public void setName(String _name){
		name = _name;
	}
	public String getName(){
		return name;
	}
	public void shopping(){
		System.out.println("shopping() --> " + this);
	}
}
public class CustomerTest {
	public static void main(String[] args) {
		Customer jack = new Customer("jack");
		System.out.println("main() ---> " + jack);
		jack.shopping();
		System.out.println("====================");
		Customer rose = new Customer("rose");
		System.out.println("main() ---> " + rose);
		rose.shopping();
	}
}

运行结果:
在这里插入图片描述
分析一下:this代表当前对象,jack调用shopping()方法,this就是jack,rose调用shopping方法,this就是rose。

由上可知:this 可以使用在实例方法当中,它指向当前正在执行这个动作的对象。

2.1.3 this访问实例变量

实例变量怎么访问?正规的访问方式是采用引用.去访问。请看下面的代码:

class Customer {
	private String name;
	public Customer(){
	}
	public Customer(String _name){
		name = _name;
	}
	public void setName(String _name){
		name = _name;
	}
	public String getName(){
		return name;
	}
	public void shopping(){
		System.out.println(name + " is shopping!");
	}
}
public class CustomerTest {
	public static void main(String[] args) {
		Customer jack = new Customer("jack");
		jack.shopping();
		Customer rose = new Customer("rose");
		rose.shopping();
	}
}

运行结果:
在这里插入图片描述
将以上部分代码片段取出来进行分析:

class Customer {
	private String name; //实例变量
	...
	public void shopping(){
		//jack 调用 shopping,当前对象是 jack
		//rose 调用 shopping,当前对象是 rose
		//name 是实例变量,不用“引用”可以访问?(以上结果表示可以)
		System.out.println(name + " is shopping!");
		//正规的访问方式应该是“引用.name”,比如
		//System.out.println(jack.name + " is shopping!");
		//或者
		//System.out.println(rose.name + " is shopping!");
		//对不起,jack 和 rose 在 main 方法当中,在这里不可见,不能用
		//难道是这样???
		System.out.println(this.name + " is shopping!");
	}
}
public class CustomerTest {
	public static void main(String[] args) {
	Customer jack = new Customer("jack");
	jack.shopping();
	Customer rose = new Customer("rose");
	rose.shopping();
	}
}

完整修改代码:

class Customer {
	private String name;
	public Customer(){
	}
	public Customer(String _name){
		name = _name;
	}
	public void setName(String _name){
		name = _name;
	}
	public String getName(){
		return name;
	}
	public void shopping(){
		System.out.println(name + " is shopping!");
		System.out.println(this.name + " is shopping!");
	}
}
public class CustomerTest {
	public static void main(String[] args) {
		Customer jack = new Customer("jack");
		jack.shopping();
		System.out.println("=======================");
		Customer rose = new Customer("rose");
		rose.shopping();
	}
}

运行结果:
在这里插入图片描述
通 过 以 上 的 测 试 我 们 得 知 :
System.out.println(name + " is shopping!") 等价于System.out.println(this.name + " is shopping!")。也就是说在 shopping()这个“实例方法”当中直接访问“实例变量”name 就表示访问当前对象的name。换句话说 在实例方法中可以直接访问当前对象的实例变量,而"this."是可以省略的

"this."什么时候不能省略呢?以上name=_name这样的代码其实可以修改为更优雅的样子,如下:

public class Customer {
	private String name;
	public Customer(){
	}
	public Customer(String name){
		this.name = name;//这里的“this.”不能省略
	}
	public void setName(String name){
		this.name = name;//这里的“this.”不能省略
	}
	public String getName(){
		return name; //这里的“this.”可以省略
	}
	public void shopping(){
		//这里的“this.”可以省略
		System.out.println(name + " is shopping!");
	}
}

以上代码当中this.name = name,其中this.name表示实例变量name,等号右边的name是局部变量 name,此时如果省略"this.",则变成name = name,这两个 name 都是局部变量(java 遵守就近原则),和实例变量 name无关了,显然是不可以省略"this."的。

2.1.4 this扩展①

如下代码:

class Customer {
	private String name;
	public Customer(){
	}
	public Customer(String name){
		this.name = name;
	}
	public void setName(String name){
		this.name = name;
	}
	public String getName(){
		return name;
	}
	//实例方法
	public void shopping(){
		System.out.println(name + " is shopping!");
		System.out.println(name + " 选好商品了!");
		//pay()支付方法是实例方法,实例方法需要使用“引用”调用
		//那么这个“引用”是谁呢?
		//当前对象在购物,肯定是当前对象在支付,所以引用是this
		this.pay();
		//同样“this.”可以省略
		pay();
	}
	//实例方法
	public void pay(){
		System.out.println(name + "支付成功!");
	}
}
public class CustomerTest {
	public static void main(String[] args) {
		Customer jack = new Customer("jack");
		jack.shopping();
		System.out.println("=======================");
		Customer rose = new Customer("rose");
		rose.shopping();
	}
}

运行结果:
在这里插入图片描述
通过以上的测试,可以看出在一个实例方法当中可以直接去访问其它的实例方法,方法是对象的一种行为描述,实例方法中直接调用其它的实例方法,就表示“当前对象”完成了一系列行为动作。例如在实例方法 shopping()中调用另一个实例方法 pay(),这个过程就表示 jack 在选购商品,选好商品之后,完成支付环节,其中选购商品是一个动作,完成支付是另一个动作。

2.1.5 this扩展②

public class ThisTest {
	int i = 10;
	public static void main(String[] args) {
		System.out.println(i);
	}
}

编译报错:
在这里插入图片描述
原因分析:首先i变量是实例变量,实例变量要想访问必须先创建对象,然后通过“引用”去访问,main方法是 static 的方法,也就是说 main 方法是通过“类名”去调用的,在 main 方法中没有“当前对象”的概念,也就是说 main 方法中不能使用 this,所以编译报错了。可以修改如下:

public class ThisTest {
    int i = 10;
    public static void main(String[] args) {
	    //这肯定是不行的,因为 main 方法带有 static,不能用 this
		//System.out.println(this.i);
		//可以自己创建一个对象
        ThisTest thisTest = new ThisTest();
        //通过引用访问
        System.out.println(thisTest.i);
    }
}

运行结果:
在这里插入图片描述
结论:在 static 的方法中不能直接访问实例变量,要访问实例变量必须先自己创建一个对象,通过引用可以去访问,不能通过 this 访问,因为在 static 方法中是不能存在 this 的。其实这种设计也是有道理的,因为 static 的方法在执行的时候是采用“类名”去调用,没有对象的参与,自然也不会存在当前对象,所以 static 的方法执行过程中不存在 this

在 static 方法中能够直接访问实例方法吗?请看以下代码:

public class ThisTest {
	public static void main(String[] args) {
		doSome();
	}
	public void doSome(){
		System.out.println("do some...");
	}
}

编译报错:
在这里插入图片描述
原因分析:因为实例方法必须先创建对象,通过引用去调用,在以上的 main 方法中并没有创建对象,更没有 this。所以在 main 方法中无法直接访问实例方法。结论就是:在 static 的方法中不能直接访问实例方法。修改的话,同样是先创建对象:

public class ThisTest {
	public static void main(String[] args) {
		ThisTest t = new ThisTest();
		t.doSome();
	}
	public void doSome(){
		System.out.println("do some...");
	}
}

运行结果:
在这里插入图片描述

2.1.6 this总结

  1. this 不能出现在static的方法中,可以出现在实例方法中,代表当前对象。
  2. 多数情况下this是可以省略不写的,但是在区分局部变量和实例变量的时候不能省略。
  3. 在实例方法中可以直接访问当前对象实例变量以及实例方法,在static方法中无法直接访问实例变量和实例方法。

2.1.7 this在构造方法中使用

this的另外一种用法,在构造方法第一行(只能出现在第一行,这是规定,记住就行),通过当前构造方法调用本类当中其它的构造方法,其目的是为了代码复用。 调用时的语法格式是:this(实际参数列表),请看以下代码:

class Date {
	private int year;
	private int month;
	private int day;
	//业务要求,默认创建的日期为 1970 年 1 月 1 日
	public Date(){
		this.year = 1970;
		this.month = 1;
		this.day = 1;
	}
	public Date(int year,int month,int day){
		this.year = year;
		this.month = month;
		this.day = day;
	}
	public int getYear() {
		return year;
	}
	public void setYear(int year) {
		this.year = year;
	}
	public int getMonth() {
		return month;
	}
	public void setMonth(int month) {
		this.month = month;
	}
	public int getDay() {
		return day;
	}
	public void setDay(int day) {
		this.day = day;
	}
}
public class DateTest {
	public static void main(String[] args) {
		Date d1 = new Date();
		System.out.println(d1.getYear() + "年 " + d1.getMonth() + "月 " + 
		d1.getDay() + "日");
		Date d2 = new Date(2008 , 8, 8);
		System.out.println(d2.getYear() + "年 " + d2.getMonth() + "月 " + 
		d2.getDay() + "日");
	}
}

运行结果:
在这里插入图片描述
对于无参数构造方法和有参数构造方法,观察发现:
在这里插入图片描述
可以在无参数构造方法中使用this(实际参数列表);来调用有参数的构造方法,这样就可以让代码得到复用了,请看:
在这里插入图片描述
最终运行结果同上。

在 this()上一行尝试添加代码,请看代码截图以及编译结果:
在这里插入图片描述
通过以上测试得出:this()语法只能出现在构造方法第一行

2.2 static(掌握)

2.2.1 static概述

static 是 java 语言中的关键字,表示“静态的”,它可以用来修饰变量、方法、代码块等,修饰的变量叫做静态变量,修饰的方法叫做静态方法,修饰的代码块叫做静态代码块。在 java语言中凡是用 static 修饰的都是类相关的,不需要创建对象,直接通过“类名”即可访问即使使用“引用”去访问,在运行的时候也和堆内存当中的对象无关。

2.2.2 静态变量

java 中的变量包括:局部变量和成员变量。

  • 在方法体中声明的变量为局部变量,有效范围很小,只能在方法体中访问,方法结束之后局部变量内存就释放了,在内存方面局部变量存储在栈当中。
  • 在类体中定义的变量为成员变量,成员变量又包括实例变量和静态变量。
    • 没有使用 static 关键字声明的变量称为实例变量,实例变量是对象级别的,每个对象的实例变量值可能不同,所以实例变量必须先创建对象,通过“引用”去访问。
    • 使用static 关键字声明的成员变量称为静态变量,静态变量访问时不需要创建对象,直接通过“类名”访问。
    • 实例变量存储在堆内存当中,静态变量存储在方法区当中。
    • 实例变量在构造方法执行过程中初始化,静态变量在类加载时初始化。

对于何时要将成员变量声明为静态变量,请看以下代码:

class Man {
		//身份证号
		int idCard;
		//性别(所有男人的性别都是“男”)
		//true 表示男,false 表示女
		boolean sex = true;
		public Man(int idCard){
		this.idCard = idCard;
	}
}
public class ManTest {
	public static void main(String[] args) {
		Man jack = new Man(100);
		System.out.println(jack.idCard + "," + (jack.sex ? "男" : "女"));
		Man sun = new Man(101);
		System.out.println(sun.idCard + "," + (sun.sex ? "男" : "女"));
		Man cok = new Man(102);
		System.out.println(cok.idCard + "," + (cok.sex ? "男" : "女"));
	}
}

运行结果:
在这里插入图片描述
以上程序的内存结构图:
在这里插入图片描述
“男人类”创建的所有“男人对象”,每一个“男人对象”的身份证号都不一样,该属性应该每个对象持有一份,所以应该定义为实例变量,而每一个“男人对象”的性别都是“男”,不会随着对象的改变而变化,性别值永远都是“男”,这种情况下,就没有必要让每一个“男人对象”持有一份,否则会浪费大量的堆内存空间,所以这个时候建议将“性别=男”属性定义为类级别的属性,声明为静态变量,上升为“整个族”的数据,这样的变量不需要创建对象直接使用“类名”即可访问。

修改如下:

class Man {
		//身份证号
		int idCard;
		//性别(所有男人的性别都是“男”)
		//true 表示男,false 表示女
		static boolean sex = true; //声明为静态变量
		public Man(int idCard){
		this.idCard = idCard;
	}
}
public class ManTest {
	public static void main(String[] args) {
		Man jack = new Man(100);
		System.out.println(jack.idCard + "," + (Man.sex ? "男" : "女"));
		Man sun = new Man(101);
		System.out.println(sun.idCard + "," + (Man.sex ? "男" : "女"));
		Man cok = new Man(102);
		System.out.println(cok.idCard + "," + (Man.sex ? "男" : "女"));
	}
}

运行结果同上,再看看内存结构图:
在这里插入图片描述

小结: 当一个类的所有对象的某个“属性值”不会随着对象的改变而变化的时候,建议将该属性定义为静态属性(或者说把这个变量定义为静态变量),静态变量在类加载的时候初始化,存储在方法区当中,不需要创建对象,直接通过“类名”来访问。

2.2.2.1 静态变量使用“引用”访问?

如果静态变量使用“引用”来访问,可以吗,如果可以的话,这个访问和具体的对象有关系吗?来看以下代码:

class Man {
		//身份证号
		int idCard;
		//性别(所有男人的性别都是“男”)
		//true 表示男,false 表示女
		static boolean sex = true; //声明为静态变量
		public Man(int idCard){
		this.idCard = idCard;
	}
}
public class ManTest {
	public static void main(String[] args) {
		//静态变量比较正式的访问方式
		System.out.println("性别 = " + Man.sex);
		//创建对象
		Man jack = new Man(100);
		//使用“引用”来访问静态变量可以吗?
		System.out.println("性别 = " + jack.sex);
		//对象被垃圾回收器回收了
		jack = null;
		//使用“引用”还可以访问吗?
		System.out.println("性别 = " + jack.sex);
	}
}

运行结果:
在这里插入图片描述
通过以上代码以及运行结果可以看出,静态变量也可以使用“引用”去访问,但实际上在执行过程中,“引用”所指向的对象并没有参与,如果是空引用访问实例变量,程序一定会发生空指针异常,但是以上的程序编译通过了,并且运行的时候也没有出现任何异常,这说明虽然表面看起来是采用“引用”去访问,但实际上在运行的时候还是直接通过“类”去访问的

2.2.2.2 静态方法使用“引用”访问?

静态方法也是如此,请看如下代码:

class Man {
	//身份证号
	int idCard;
	//性别(所有男人的性别都是“男”)
	//true 表示男,false 表示女
	static boolean sex = true;
	public Man(int idCard){
		this.idCard = idCard;
	}
	//静态方法
	public static void printInfo(){
		System.out.println("-----" + (Man.sex ? "男" : "女") + "------");
	}
}
public class ManTest {
	public static void main(String[] args) {
		//静态变量比较正式的访问方式
		System.out.println("性别 = " + Man.sex);
		//创建对象
		Man jack = new Man(100);
		//使用“引用”来访问静态变量可以吗?
		System.out.println("性别 = " + jack.sex);
		//对象被垃圾回收器回收了
		jack = null;
		//使用“引用”还可以访问吗?
		System.out.println("性别 = " + jack.sex);
		//静态方法比较正式的访问方式
		Man.printInfo();
		//访问静态方法可以使用引用吗?并且空的引用可以吗?
		jack.printInfo();
	}
}

运行结果:
在这里插入图片描述
通过以上代码测试得知,静态变量和静态方法比较正式的方式是直接采用“类名”访问,但实际上使用“引用”也可以访问,并且空引用访问静态变量和静态方法并不会出现空指针异常。实际上,在开发中并不建议使用“引用”去访问静态相关的成员,因为这样会让程序员困惑,因为采用“引用”方式访问的时候,程序员会认为你访问的是实例相关的成员。

小结:

  1. 所有实例相关的,包括实例变量和实例方法,必须先创建对象,然后通过“引用”的方式去访问,如果空引用访问实例相关的成员,必然会出现空指针异常。
  2. 所有静态相关的,包括静态变量和静态方法,直接使用“类名”去访问。虽然静态相关的成员也能使用“引用”去访问,但这种方式并不被主张。

2.2.3 静态代码块

2.2.3.1 语法格式与介绍
{
	//静态代码块
	static{
		java 语句;
	}
}

静态代码块在类加载时执行,并且只执行一次。其实际上是 java 语言为程序员准备的一个特殊的时刻,这个时刻就是类加载时刻,如果你想在类加载的时候执行一段代码,那么这段代码就有的放矢了。例如我们要在类加载的时候解析某个文件,并且要求该文件只解析一次,那么此时就可以把解析该文件的代码写到静态代码块当中了。我们来测试一下静态代码块:

public class StaticTest {
    //静态代码块
    static{
        System.out.println(2);
    }
    //静态代码块
    static{
        System.out.println(1);
    }
    //main 方法
    public static void main(String[] args) {
        System.out.println("main execute!");
    }
    //静态代码块
    static{
        System.out.println(0);
    }
}

运行结果:
在这里插入图片描述

通过以上的测试可以得知:

  • 一个类当中可以编写多个静态代码块(尽管大部分情况下只编写一个),并且静态代码块遵循自上而下的顺序依次执行,所以有的时候放在类体当中的代码是有执行顺序的(大部分情况下类体当中的代码没有顺序要求,方法体当中的代码是有顺序要求的,方法体当中的代码必须遵守自上而下的顺序依次逐行执行);
  • 静态代码块当中的代码在 main 方法执行之前执行,这是因为静态代码块在类加载时执行,并且只执行一次。
2.2.3.2 静态代码块中访问静态变量

再看一下以下代码:

public class StaticTest02 {
    int i = 100;
    public static void main(String[] args) {
        System.out.println("main execute!");
    }
    static{
        System.out.println(i);
    }
}

编译报错:
在这里插入图片描述
原因分析:
因为 i 变量是实例变量,实例变量必须先创建对象才能访问,静态代码块在类加载时执行,这个时候对象还没有创建呢,所以 i 变量在这里是不能这样访问的。可以考虑在 i 变量前添加 static,这样 i 变量就变成静态变量了,静态变量访问时不需要创建对象,直接通过“类”即可访问,如下:

public class StaticTest02 {
    static int i = 100;
    public static void main(String[] args) {
        System.out.println("main execute!");
    }
    static{
        System.out.println("静态变量:i="+i);
    }
}

运行结果:
在这里插入图片描述
若代码修改为如下:

public class StaticTest02 {
	static{
		System.out.println("静态变量 i = " + i);
	}
	static int i = 100;
 	public static void main(String[] args) {
        System.out.println("main execute!");
    }
}

编译报错:
在这里插入图片描述
通过测试,可以看到有的时候类体当中的代码也是有顺序要求的(类体当中定义两个独立的方法,这两个方法是没有先后顺序要求的),静态代码块在类加载时执行,静态变量在类加载时初始化,它们在同一时间发生,所以必然会有顺序要求,如果在静态代码块中要访问 i 变量,那么 i 变量必须放到静态代码块之前。

2.2.4 静态方法

方法在什么情况下会声明为静态的呢?方法实际上描述的是行为动作,当某个动作在触发的时候需要对象的参与,这个方法应该定义为实例方法,例如:

  1. 每个玩篮球的人都会打篮球,但是你打篮球和科比打篮球最终的效果是不一样的,显然打篮球这个动作存在对象差异化,该方法应该定义为实例方法。
  2. 每个高中生都有考试的行为,但是你考试和学霸考试最终的结果是不一样的,一个上了“家里蹲大学”,一个上了“清华大学”,显然这个动作也是需要对象参与才能完成的,所以考试这个方法应该定义为实例方法。

以上描述是从设计思想角度出发来进行选择,其实也可以从代码的角度来进行判断,当方法体中需要直接访问当前对象的实例变量或者实例方法的时候,该方法必须定义为实例方法,因为只有实例方法中才有 this,静态方法中不存在 this。 请看代码:

class Customer {
	String name;
	public Customer(String name){
		this.name = name;
	}
	public void shopping(){
		//直接访问当前对象的 name
		System.out.println(name + "正在选购商品!");
		//继续让当前对象去支付
		pay();
	}
	public void pay(){
		System.out.println(name + "正在支付!");
	}
}
public class CustomerTest {
	public static void main(String[] args) {
		Customer jack = new Customer("jack");
		jack.shopping();
		Customer rose = new Customer("rose");
		rose.shopping();
	}
}

运行结果:
在这里插入图片描述
在以上的代码中,不同的客户购物,最终的效果都不同,另外在 shopping()方法中访问了当前对象的实例变量 name,以及调用了实例方法 pay(),所以 shopping()方法不能定义为静态方法,必须声明为实例方法。

在实际的开发中,“工具类”当中的方法一般定义为静态方法,因为工具类就是为了方便大家的使用,将方法定义为静态方法,比较方便调用,不需要创建对象,直接使用类名就可以访问。请看以下工具类,为了简化System.out.println();代码而编写的工具类:

class U {
	public static void p(int data){
		System.out.println(data);
	}
	public static void p(long data){
		System.out.println(data);
	}
	public static void p(float data){
		System.out.println(data);
	}
	public static void p(double data){
		System.out.println(data);
	}
	public static void p(boolean data){
		System.out.println(data);
	}
	public static void p(char data){
		System.out.println(data);
	}
	public static void p(String data){
		System.out.println(data);
	}

}
public class HelloWorld {
	public static void main(String[] args) {
		U.p("Hello World!");
		U.p(true);
		U.p(3.14);
		U.p('A');
	}
}

运行结果:
在这里插入图片描述

2.3 难点解惑

对于 static 来说,代码的执行顺序是一个需要大家注意的地方,一般来说方法体当中的代码是有执行顺序要求的,之前所接触的程序中,类体当中的程序没有顺序要求,但自从认识了 static 之后,我们发现类体当中的代码也有执行顺序的要求了,尤其是 static 修饰的静态变量,以及static修饰的静态代码块他们是有先后顺序的,这里需要记住的是:static修饰的静态变量以及静态代码块都是在类加载时执行,并且遵循自上而下的顺序依次逐行执行

2.4 练习

2.4.1 选择题

public class Test {
	static int value = 9;
	public static void main(String[] args) throws Exception{
		new Test().printValue();
	}
	public void printValue(){
		int value = 69;
		System.out.println(this.value);
	}
}

A. 编译错误
B. 打印 9
C. 打印 69
D. 运行时抛出异常

运行结果:
在这里插入图片描述
理解: 方法中的int value = 69;是局部变量,而 static int value = 9;是实例变量,方法中的System.out.println(this.value);调用的是实例变量!

2.4.2 判断下面代码的输出结果,并说明原因

class User {
	private String name;
	public User(){
	}
	public void setName(String name){
		name = name;
	}
	public String getName(){
		return name;
	}
}
public class UserTest {
	public static void main(String[] args) {
		User user = new User();
		user.setName("zhangsan");
		System.out.println(user.getName());
	}
}

最终的输出结果是:null。
原因:setName 方法体当中的 name = name 是把局部变量 name 赋值给局部变量 name,和实例变量 name 无关,所以 getName()方法获取的实例变量值是 null。
在这里插入图片描述

2.4.3 找出下面代码的错误,并说明为什么错了

public class Test {
	int i;
	static int j;
	public void m1(){
		System.out.println(i);
		System.out.println(j);
		m2();
		m3();
	}
	public void m2(){
	}
	public static void m3(){
		System.out.println(i); //报错,因为静态方法中无法直接访问实例变量 i 
		System.out.println(j);
		m2(); //报错,因为静态方法中无法直接访问实例方法 m2()
		m4();
	}
	public static void m4(){
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/390667.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

林浩然与杨凌芸的Java时光魔法:格式化历险记

林浩然与杨凌芸的Java时光魔法&#xff1a;格式化历险记 The Java Time Odyssey of Lin Haoran and Yang Lingyun: A Formatting Adventure 在编程世界的一隅&#xff0c;有一个名叫林浩然的程序员。他是个Java大侠&#xff0c;对代码世界的法则了如指掌&#xff0c;尤其擅长驾…

Vue2学习第三天

Vue2 学习第三天 1. 计算属性 computed 计算属性实现 定义&#xff1a;要用的属性不存在&#xff0c;要通过已有属性计算得来。 原理&#xff1a;底层借助了Objcet.defineproperty方法提供的getter和setter。 get函数什么时候执行&#xff1f; 初次读取时会执行一次。当依赖…

中科院一区论文复现,改进蜣螂算法,Fuch映射+反向学习+自适应步长+随机差分变异,MATLAB代码...

本期文章复现一篇发表于2024年来自中科院一区TOP顶刊《Energy》的改进蜣螂算法。 论文引用如下&#xff1a; Li Y, Sun K, Yao Q, et al. A dual-optimization wind speed forecasting model based on deep learning and improved dung beetle optimization algorithm[J]. Ener…

人工智能学习与实训笔记(十四):Langchain Agent

0、概要 Agent是干什么的&#xff1f; Agent的核心思想是使用语言模型&#xff08;LLM&#xff09;作为推理的大脑&#xff0c;以制定解决问题的计划、借助工具实施动作。在agents中几个关键组件如下&#xff1a; Agent&#xff1a;制定计划和思考下一步需要采取的行动。Tools…

H12-821_44

44.如图所示的网络,R1设备配置路由渗透,那么R1设备把Level-2的LSP发送给R3,使R3可以获知全网路由。 A.正确 B.错误 答案&#xff1a;B 注释&#xff1a; 感觉题目描述有两个问题&#xff1a; 1. R3是Level-1-2路由器&#xff0c;本来就可以学习到Level-2的路由。题目中的R3应该…

会声会影支持哪些视频格式 会声会影2024软件下载 会声会影序列号在哪里购买

时常有朋友遇到这样的烦恼&#xff0c;就是从网络上下载下载来的视频&#xff0c;不能够导入到会声会影里面。这到底是怎么回事&#xff0c;到底支持哪些视频格式呢&#xff1f;下面小编将来给大家介绍一下。 还没有下载会声会影2024的朋友可以通过这个链接下载&#xff1a;ht…

mysql 执行update操作 记录未修改

问题 mysql 执行update操作 记录未修改 详细问题 笔者进行SpringBootMybatis项目开发&#xff0c;确认执行update操作 控制台内容如下 Creating a new SqlSession SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession3cbe9459] was not registered for sync…

【c++】list详细讲解

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;熟悉list库 > 毒鸡汤&#xff1a;你的脸上云淡…

OpenAI Sora是世界模型?

初见Sora&#xff0c;我被OpenAI的野心震撼了。 他们不仅想教会AI理解视频&#xff0c;还要让它模拟整个物理世界&#xff01;这简直是通用人工智能的一大飞跃。 但当我深入了解后&#xff0c;我发现Sora比我想象的更复杂、更强大。 Sora不是简单的创意工具&#xff0c;而是…

【BBuf的CUDA笔记】十四,OpenAI Triton入门笔记二

0x0. 前言 接着【BBuf的CUDA笔记】十三&#xff0c;OpenAI Triton 入门笔记一 继续探索和学习OpenAI Triton。这篇文章来探索使用Triton写LayerNorm/RMSNorm kernel的细节。 之前在 【BBuf的CUDA笔记】十二&#xff0c;LayerNorm/RMSNorm的重计算实现 这篇文章我啃过Apex的La…

Caffeine本地缓存

Caffeine本地缓存 Caffine简介 简单说&#xff0c;Caffine 是一款高性能的本地缓存组件&#xff0c;由下面三幅图可见&#xff1a;不管在并发读、并发写还是并发读写的场景下&#xff0c;Caffeine的性能都大幅领先于其他本地开源缓存组件 常见的缓存淘汰算法 FIFO 它…

电源管理芯片是指在电子设备系统中,负责对电能的变换、分配、检测等进行管理的芯片

萨科微半导体宋仕强介绍说&#xff0c;电源管理芯片是指在电子设备系统中&#xff0c;负责对电能的变换、分配、检测等进行管理的芯片&#xff0c;其性能和可靠性直接影响电子设备的工作效率和使用寿命&#xff0c;是电子设备中的关键器件。萨科微slkor&#xff08;www.slkormi…

wps使用方法(包括:插入倒三角符号,字母上面加横线,将word中的所有英文设置为time new roman)

倒三角符号 字母上面加横线 将word中的所有英文设置为time new roman ctrla选中全文

人工智能学习与实训笔记(四):神经网络之自然语言处理

目录 六、自然语言处理 6.1 词向量 (Word Embedding) 6.1.1 词向量的生成过程 6.1.2 word2vec介绍 6.1.3 word2vec&#xff1a;skip-gram算法的实现 6.2 句向量 - 情感分析 6.2.1 LSTM (Long Short-Term Memory)介绍 6.2.2 基于飞桨实现的情感分析模型 6.3 BERT 六、自…

使用Taro开发鸿蒙原生应用——快速上手,鸿蒙应用开发指南

导读 本指南为开发者提供了使用 Taro 框架开发鸿蒙原生应用的快速入门方法。Taro&#xff0c;作为一个多端统一开发框架&#xff0c;让开发者能够使用一套代码同时适配多个平台&#xff0c;包括鸿蒙系统。文章将详细介绍如何配置开发环境&#xff0c;以及如何利用 Taro 的特性…

MATLAB知识点:datasample函数(★★☆☆☆)随机抽样的函数,能对矩阵数据进行随机抽样

讲解视频&#xff1a;可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇&#xff08;数学建模清风主讲&#xff0c;适合零基础同学观看&#xff09;_哔哩哔哩_bilibili 节选自第3章&#xff1a;课后习题讲解中拓展的函数 在讲解第三…

.NET开源的一个小而快并且功能强大的 Windows 动态桌面软件 - DreamScene2

前言 很多同学都不愿给电脑设动态壁纸&#xff0c;其中有个重要原因就是嫌它占资源过多。今天大姚分享一个.NET开源、免费&#xff08;MIT license&#xff09;的一个小而快并且功能强大的 Windows 动态桌面软件&#xff0c;支持视频和网页动画播放&#xff1a;DreamScene2。 …

Android 车载应用开发之SystemUI 详解

一、SystemUI SystemUI全称System User Interface,直译过来就是系统级用户交互界面,在 Android 系统中由SystemUI负责统一管理整个系统层的 UI,它是一个系统级应用程序(APK),源码在/frameworks/base/packages/目录下,而不是在/packages/目录下,这也说明了SystemUI这个…

【C语言】模拟实现库函数qsort

qsort的头文件是stdlib.h 他的四个参数分别是要进行排序的数组base的首地址&#xff0c;base数组的元素个数&#xff0c;每个元素的大小&#xff0c;以及一个函数指针&#xff0c;这个函数指针指向了一个函数&#xff0c;这个函数的参数是两个void*类型的指针&#xff0c;返回类…

读十堂极简人工智能课笔记04_计算机视觉

1. 仙女蜂 1.1. Megaphragma mymaripenne 1.2. 一种微小的蜂类 1.3. 人类已知第三小的昆虫 1.4. 大脑仅由7400个神经元组成&#xff0c;比大型昆虫的大脑小了好几个数量级 1.5. 微小的身体里没有空间容纳这些神经元&#xff0c;所以在生长的最后阶段&#xff0c;它把每个神…