刚才介绍了如何定义类,有个概念必须先理清,

定义类并不等于做好了面向对象中封装(Encapsulation)的概念,那么到底什么才有封装的含义?

这就需要我们以对象的角度来思考问题了。

封装对象初始流程

假设要写个可以管理会员卡的应用程序。假设有两个以上的开发者。

我们从了解的知识一步一步前进。

我们首先想到的是什么?

  1. 会员卡都有哪些数据信息。
    • 卡号码
    • 余额
    • 积分(每存100,增加一个积分,只有大于1000的时候才会给积分)

那么,在Java我们需要定义一个类,保存这些信息。

package base.encapsulation.CashCard.v1;

public class CashCard {
	String number;
	int balance;
	int point;
}

现在,我们已经可以创建会员卡了。

比如说我们现在需要创建8张会员卡。

CashCard card1 = new CashCard();
card1.number = "A001";
card1.balance = 500;
card1.point = 0;
CashCard card2 = new CashCard();
card2.number = "A002";
card2.balance = 500;
card2.point = 0;
...............

我们发现创建初始化的动作是重复的,我们可以定义一个构造方法。

package base.encapsulation.CashCard.v2;

public class CashCard {
	String number;
	int balance;
	int point;

	public CashCard(String number, int balance, int point) {
		this.number = number;
		this.balance = balance;
		this.point = point;
	}
}

现在,可以这样创建会员卡了。

CashCard card1 = new CashCard("A001", 500, 0);
CashCard card2 = new CashCard("A002", 500, 0);
....
思考时间
  • 想一下,V1和V2相比,我们添加了什么?

用了Java的构造方法语法,实现对象初始化流程的封装。

  • 我们为什么这么做?

拿到CashCard类的用户,不用重复撰写对象初始化流程,不用知道对象如何初始化,就算我们修改了构造函数的内容,重新编译之后,CashCard类的用户也无须修改代码。

封装对象操作流程

假设现在使用CashCard建立5个对象, 并要再对所有对象进行存钱的动作。

  • 关于存钱动作

金额不能是负的,而金额大于1000的话,就给每充值100给一个积分。

Scanner scanner = new Scanner(System.in);
CashCard card1 = new CashCard("A001", 500, 0);
int money = scanner.nextInt();
 if(money > 0) {
 		 card1.balance += money;
     if(money >= 1000) {
        card1.point += money/100;
     }
 } else {
 		System.out.println("你想干什么?");
 }
CashCard card2 = new CashCard("A002", 500, 0);
money = scanner.nextInt();
 if(money > 0) {
 		 card2.balance += money;
     if(money >= 1000) {
        card2.point += money/100;
     }
 } else {
 		System.out.println("你想干什么?");
 }
...

写完之后,发现又有重复,我们这个存钱的动作每次写的代码都是一样的。

我们想一个,存钱的这个动作应该是会员卡自己处理。

为了避免重复,我们定义一个方法就可以解决。

package base.encapsulation.CashCard.v3;

public class CashCard {
	String number;
	int balance;
	int point;

	public CashCard(String number, int balance, int point) {
		this.number = number;
		this.balance = balance;
		this.point = point;
	}

	void store(int money) {
		if (money > 0) {
			this.balance += money;
			if (money >= 1000) {
				this.point += money / 100;
			}
		} else {
			System.out.println("你想干什么?");
		}
	}
}

有了存钱的这个动作的方法之后,我们的代码就很简洁了。

Scanner scanner = new Scanner(System.in);
CashCard card1 = new CashCard("A001", 500, 0);
card1.store(scanner.nextInt());
CashCard card2 = new CashCard("A002", 500, 0);
card2.store(scanner.nextInt());

最后,对象的动作不只有存钱,还有消费等,这里我们就不展开了。

封装对象内部数据

我们在CashCard类上定义了store(),希望其他的开发同事,都使用store方法,

但是,可能我们又想多了。

有的人可能会直接去修改我们的金额和积分。

CashCard card1 = new CashCard("A001", 500, 0);
card1.balance += scanner.nextInt();
card1.point += 10000;
思考时间
  • 问题出在哪里?

CashCard类中的所以字段在CashCard类以外也可以被回去和修改。

为了保证数据不能再CashCard类以外被修改,我们需要把字段定义为私有的。

在Java中可以使用private关键字来定义。

package base.encapsulation.CashCard.v4;

public class CashCard {
	private String number;
	private int balance;
	private int point;

	public CashCard(String number, int balance, int point) {
		this.number = number;
		this.balance = balance;
		this.point = point;
	}

	void store(int money) {
		if (money > 0) {
			this.balance += money;
			if (money >= 1000) {
				this.point += money / 100;
			}
		} else {
			System.out.println("你想干什么?");
		}
	}

	public String getNumber() {
		return number;
	}

	public int getBalance() {
		return balance;
	}

	public int getPoint() {
		return point;
	}

}

这样使用的CashCard类的时候,就不能直接去更改里面的字段了。

小总结

在这里对封装做个小小总结,封装目的主要就是隐藏对象细节,将对象当作黑箱进行操作。就如前面的例子,用户会调用构造函数,但不知道构造函数的细节,用户会调用方法,但不知道方法的流程,用户也不会知道有哪些私有数据,要操作对象,一律得通过你提供的方法调用。

private也可以用在方法或构造函数声明上,私有方法或构造函数通常是类内部某个共享的演算流程,外界不用知道私有方法的存在。

类的语法细节

public权限修饰

Java中,可以使用访问控制符来保护对类、变量、方法和构造方法的访问。Java 支持 4 种不同的访问权限。

  • default (即缺省,什么也不写): 在同一包内可见,不使用任何修饰符。使用对象:类、接口、变量、方法。
  • private : 在同一类内可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
  • public : 对所有类可见。使用对象:类、接口、变量、方法
  • protected : 对同一包内的类和所有子类可见。使用对象:变量、方法。 注意:不能修饰类(外部类)
关于构造函数

在定义类时,可以使用构造函数定义对象建立的初始流程。构造函数是与类名称同名,无须声明返回类型的方法。

如果我们没有编写构造函数。那么编译器会我们自动生成一个没有参数的构造函数。

如果我们添加了构造函数,那么编译器就不会为我们生成没有参数的构造函数。

public class CashCard {
	String number;
	int balance;
	int point;

	public CashCard(String number, int balance, int point) {
		this.number = number;
		this.balance = balance;
		this.point = point;
	}
}
构造函数与方法重载

视使用情境或条件的不同,创建对象时也许希望有对应的初始流程。可以定义多个构造函数,只要参数类型或个数不同,这称为重载(Overload)构造函数。

比如 Stirng类。

  • String
  • valueOf
使用this

除了被声明为static的地方外,this关键字可以出现在类中任何地方,在对象建立后为“这个对象”的参考名称。

在Java中,this()代表了调用另一个构造函数,至于调用哪个构造函数,则视调用this()时给的自变量类型与个数而定

static类成员

image-20190511001547044

image-20190511001552892

不定长度自变量

System.out.printf()

内部类

可以在类中再定义类,这称为内部类(Inner class)。

传值调用

Java只有传值调用

package cc.openhome;
class Customer {
    String name;
    Customer(String name) {
        this.name = name;
    }
}

public class CallByValue {
    public static void some(Customer c) {
        c.name = "John";
    }
    
    public static void other(Customer c) {
        c = new Customer("Bill");
    }
    
    public static void main(String[] args) {
        Customer c1 = new Customer("Justin");
        some(c1);
        System.out.println(c1.name);
        
        Customer c2 = new Customer("Justin");
        other(c2);
        System.out.println(c2.name);
    }
}