理解封装
封装,顾名思义,就是将对象或者类的实现细节,成员变量给隐藏起来,不允许外界直接访问,而类只提供set和get这两个方法,相当于是一个接口,外界只能通过这两个接口来进行对象的数据访问和改动。当然,通过这种接口来访问,外界就只能获取值,而不知道具体的实现细节,也不能直接改动。而通过set来修改时,也是在限定范围内进行修改。这显然就实现了良好的封装。
访问控制符
Java通过四种访问控制符来实现封装:private, default, protected, public。
- private: 只能在当前类的内部进行访问。
- default: 当没有访问控制符的时候,就是default。是包访问权限的,也就是说,一个包的内部,都可以进行访问。
- protected: 这个修饰符表示,既可以被包中的其他类访问,也可以被不同包中的子类访问。
- public: 公共访问权限,可以被所有类访问。
外部类只能采用public和默认修饰符进行修饰,因为这时没有所在类的内部,所在类的子类这两个范围了。
实际上一个类就是一个小模块,模块设计追求高内聚,低耦合。就是实现细节不让外部直接干预,仅通过少量的方法让外界访问。
包机制
包实际上就是将功能相关的类放到一起,用于解决类的命名冲突、类文件管理等问题。包中类的完整类名应该是包名加上类名。如果希望把一个类放在指定的包结构下,应该在Java源程序的第一个非注释行放置如下格式的代码:
package 包名;
package ObjectOriented.kxm;
/*
类包含构造器,成员变量,方法。
成员变量表示一种状态数据,方法表示类或者类实例的行为特征或者功能实现。
构造器是类创造对象的根本途径,如果程序员没有编写构造器,那么系统会为类提供一个默认构造器
类中各成员之间的定义顺序没有任何影响,各成员之间可以相互调用。
*/
public class Person {
/*
对于类名的命名规则,每个单词首字母大写,中间不能包含其他分隔符
*/
private String name; // 使用private进行修饰,只有Person类才可以操作和访问
private int age;
public void say(String content)
{
System.out.println(content);
}
public void setName(String name){
if(name.length() > 6 || name.length() < 2){
System.out.println("您设置的人名不符合要求!");
return ;
}
else{
this.name = name;
}
}
public String getName(){
return this.name;
}
public void setAge(int age){
if(age<0 || age > 100){
System.out.println("您输入的年龄不符合要求!");
return;
}
else{
this.age = age;
}
}
public int getAge(){
return this.age;
}
}
使用命令行编译带有package语句的Java文件:
使用-encoding参数的目的是,cmd默认是gbk编码,而Java文件是utf-8,因此在这里需要指定一下编码格式,不然会保存。-d是指定类文件存储地址。表示存储在当前目录下。
现在看目录结构:
可以看到,多了ObjectOriented/kxm这个目录结构,下面有一个class文件,证明该类属于ObjectOriented.kxm这个包。
如果不指定-d这个选项,那么java就不会默认生成相应的目录结构,而是在当前目录下生成一个class文件。这时如果去执行java文件,那么会提示无法加载主类。
当生成了package指定的目录结构时,这时运行就没有问题:但需要注意的是,需要在-d指定的目录下运行,这时才能找的到指定类,否则是无法找到指定类的。
虽然在报错,但这只是我在Java文件内没有定义main方法的原因。
Java虚拟机在装载类时,会依次搜索classpath环境变量所指定的目录(默认是当前目录),如果Java文件指定了报名,那么会按包的层次目录去找对应的类。而现在流行的版本,默认classpath是当前目录,即编译Java源文件的目录,因此一般在运行的时候,直接到当前目录去执行运行命令,并且有package语句的类应该带上相应的包,否则会提示无法加载类。
其实这是Java执行的一些原理,在使用idea等集成环境时则不需要考虑这些问题。
包机制需要两个方面的保证:1. 源文件中指定package语句。 2.class文件必须放到对应的路径下。
构造器
构造器最大的用处就是用于创建对象,创建对象的时候会对实例变量进行一个默认的初始化,那么可以利用构造器为这些实例变量进行一个显示的初始化。如果没有指定构造器,那么Java会提供一个默认的构造器,只不过这个构造器为空,不进行任何操作。
package ObjectOriented;
public class ConstructorTest {
private String name;
private int count;
// 定义构造器,显示初始化实例变量
public ConstructorTest(String name, int count){
this.name = name;
this.count = count;
}
public String getName(){
return this.name;
}
public int getCount(){
return this.count;
}
public static void main(String[] args) {
ConstructorTest tc = new ConstructorTest("xxx", 7777);
System.out.println(tc.getCount());
System.out.println(tc.getName());
}
}
对象的创建并不是由构造器完全负责,当调用构造器的时候,系统会先在堆内存中为对象分配内存,并且进行了默认初始化,也就是说,构造器没有执行之前,对象已经被创建了,只不过还不能被外部访问,因为堆中的需要用引用来访问,但其可以被构造器内的this引用,因此实际上构造器的作用主要是返回对象的引用,顺带可以进行实例变量的显示初始化。这也是一定需要构造器的原因
构造器重载
一般的方法重载就不多说了,主要展示当一个构造器要复用上一个构造器代码时怎么做,通过this关键字来实现另一个构造器代码的复用,尽量使用这种方式去实现代码的复用。
package ObjectOriented;
public class Apple {
private String name;
private String color;
private double weight;
public Apple(){}
public Apple(String name, String color){ // 简单的构造器重载
this.name = name;
this.color = color;
}
public Apple(String name, String color, double weight){
this(name,color); // 调用该类另一个两个参数的构造器
this.weight = weight;
}
public static void main(String[] args) {
Apple a = new Apple("ss","dd", 54);
System.out.println(a.color);
System.out.println(a.name);
System.out.println(a.weight);
}
}