封装的基础实现
在Java中所有面型对象的概念都是一类与对象的关系为主。下面通过具体程序研究为什么需要封装性。
范例:观察如下程序代码
class Person{
String name;
int age;
public void tell(){
System.out.println("姓名:"+name+",年龄:"+age);
}
}
public class Hello {//另外一个类
public static void main(String args[]){
Person per = new Person();
per.name="小不点儿";
per.age=10;
per.tell();
}
}
此时代码没有语法错误,但是从现实角度来讲,代码问题很大。这样的错误严格来讲属于业务错误。下面讨论造成此类错误的原因是什么?
最大错误在于当前类中的属性可以直接被类外部的对象所直接调用。所以此时认为这样的操作是不安全的。
那么现在最需要解决的问题是将内部属性保存起来,不让外部直接操作,为此Java中提供private关键字实现封装。
class Person{
private String name;
private int age;
public void tell(){
System.out.println("姓名:"+name+",年龄:"+age);
}
}
加入了private之后,就表示此时name与age两个属性只能被Person类所访问。
范例:错误调用
class Person{
private String name;
private int age;
public void tell(){
System.out.println("姓名:"+name+",年龄:"+age);
}
}
public class Hello {//另外一个类
public static void main(String args[]){
Person per = new Person();
per.name="小不点儿";
per.age=10;
per.tell();
}
}
现在只用了private定义的属性,类的外部不能够直接进行访问,所以安全性是最高的。
那么现在如果需要通过对象操作类中的属性,在Java中就有了一个明确的要求:可以使用setter、getter方法设置或取得封装属性内容,以private String name封装属性为例。
- 设置数据:public void setName(String n);
- 取得数据:public String getName();
以private int age;封装属性为例: - 设置数据:public void setAge(int a);
- 取得数据:public int getAge();
范例:修改程序使得外部可以访问私有属性
class Person{
private String name;
private int age;
public void tell(){
System.out.println("姓名:"+name+",年龄:"+age);
}
public void setName(String n){
name = n;
}
public void setAge(int a){
age=a;
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
}
开发原则:以后只要是类中的属性全部使用private封装,封装后的属性必须严格按照要求编写setter与getter方法。
如果非要进行检测操作,则可以修改setter方法。(只是现在临时说明)。
class Person{
private String name;
private int age;
public void tell(){
System.out.println("姓名:"+name+",年龄:"+age);
}
public void setName(String n){
name = n;
}
public void setAge(int a){
if(a>=0&&a<=250){
age=a;
}
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
}
public class Hello {//另外一个类
public static void main(String args[]){
Person per = new Person();
per.setName("小不点儿");
per.setAge(-10);
per.tell();
}
}
总结
- private声明的属性只能被类的内部所使用;
- private声明的属性必须有对应的setter、getter方法,而且方法名称的命名要严格按照标准编写。
构造方法与匿名对象(非常重要)
- 构造方法的作用以及定义要求;
- 匿名对象的使用。
构造方法
看一个格式:实例化对象 操作
类名称 对象名称 = new 类名称();
每个组成部分的意义:
- “类名称”:用于标记对象的类型,因为对象开辟空间后需要开辟堆内存,堆内存保存属性,而属性在类中定义;
- “对象名称”:如果要想操作类中的属性或者是方法,必须依靠对象的名称完成;
- “new”:开辟新的堆内存空间;
- “类名称()”:只要出现“()”的都表示方法,这个实际上就是构造方法。
- 构造方法的定义要求:
-
- 要求构造方法的名称与类相同,并且没有返回值类型声明。
范例:构造方法基本定义
- 要求构造方法的名称与类相同,并且没有返回值类型声明。
class Student{
public Student(){//方法名称与类名称相同,无返回值声明
System.out.println("*********");
}
}
public class JavaDemo {
public static void main(String args[]){
Student stu=null;//声明对象
stu=new Student();//实例化对象时调用构造方法
}
}
通过以上代码执行可以发现,所有类中的构造方法都在使用new实例化新对象的时候才会被使用的。一使用new就需要构造方法。
但是之前没有定义构造方法
实际上在进行Java代码变异的过程中,系统会自动加一些代码,所以这个时候如果发现如果没有定义构造方法,系统会自动帮助用户提供一个无参的什么都不做的构造方法,类似于如下形式:
public Student(){//方法名称与类名称相同,无返回值声明
}
这样就可以保证每个类都一定至少存在一个构造方法。所有的类都肯定有构造方法,但是至少有一个。
构造方法的作用:构造方法是在使用关键字new实例化对象的时候才会被调用,那么实例化对象的时候实际上也就属于内存空间的开辟,也就属于属性的初始化过程,但是发现,默认情况下属性初始化后的内容都是其对应数据类型的默认值,所以希望可以在对象实例化时传递一些属性的内容,那么就可以利用构造方法完成。
范例:通过构造方法设置属性内容
class Person{
private String name;
private int age;
public Person(String n,int a){//定义有参构造方法
setName(n);
setAge(a);
}
public void tell(){
System.out.println("姓名:"+name+",年龄:"+age);
}
public void setName(String n){
name = n;
}
public void setAge(int a){
if(a>=0&&a<=250){
age=a;
}
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
}
public class Hello {//另外一个类
public static void main(String args[]){
Person per = new Person("小不点儿",-10);
per.tell();
}
}
此时程序在实例化Person类对象的时候会自动将name与age属性传递到对应的属性值中,这样做的好处相当于省略了一系列的setter调用。
疑问?既然构造方法没有返回值,为什么不使用void说明?
构造方法在实例化对象的时候只调用一次,而所有的普通方法可以利用对象调用多次。
如果说构造方法定义成:public void Person(){},这个就属于普通方法了。
构造方法本身就属于方法,既然属于方法,那么方法本身就一定可以重载。而幸运的是,构造方法重载时不需要考虑方法名称,因为必须跟类名称一样,只需要考虑参数的类型或个数即可。
构造方法重载
class Person{
private String name;
private int age;
public Person(){}
public Person(String n){//单参数构造法
setName(n);
}
public Person(String n,int a){//定义有参构造方法
setName(n);
setAge(a);
}
调用单参,非调用参数无值,调用无参,所有参数都无值,使用默认值。
但是在进行构造方法重载时需要注意,按照参数的个数降序或者是升序排列。以上的代码发现重载的构造方法是按照参数个数的升序排列的。
而且需要注意:类中的基本组成:属性、构造方法、普通方法,在编写的时候也要注意顺序,先写属性,再写构造,最后写普通方法。
实际上在对象的构造过程中牵扯到许多的步骤,例如:加载类、为对象开辟空间属性赋值操作,构造方法是在整个构造过程的最后一步,这一步是留给用户处理的。而属性如果在声明时设置了具体内容,那么这个内容一定是在构造完成后才会赋值成功的,在这之前都属于其对应类型的默认值。
class Student{
private String name = "桃子";//设置了属性的默认值
public Student(){
System.out.println(name);
}
}
public class JavaDemo {
public static void main(String args[]){
Student stu=null;//声明对象
stu=new Student();//实例化对象时调用构造方法
}
}
这一点会通过后面的具体实例来说明。
匿名对象
匿名对象指的是没有名字的对象,对象的名字都保存在栈内存之中。
范例:有名对象
Person per = new Person("小不点儿",-10);
per是对象的名字,此对象可以依靠per这个名字一直使用。
在整个对象操作中,真正有用的并不是栈内存,因为栈指向的是堆内存,真正用到的是堆内存,堆内存依靠new来开辟,所以此时的代码可以直接用后半部分“new Person(“小不点儿”,-10);”来调用类中方法。
范例:匿名对象调用
class Person{
private String name;
private int age;
public Person(){}
public Person(String n){//单参数构造法
setName(n);
}
public Person(String n,int a){//定义有参构造方法
setName(n);
setAge(a);
}
public void tell(){
System.out.println("姓名:"+name+",年龄:"+age);
}
public void setName(String n){
name = n;
}
public void setAge(int a){
if(a>=0&&a<=250){
age=a;
}
}
public String getName(){
return name;
}
public int getAge(){
return age;
}
}
public class Hello {//另外一个类
public static void main(String args[]){
new Person("小不点儿",-10).tell();
}
}
但是此时的对象因为没有名字,所以使用一次之后会自动成为垃圾。不用去纠结使用匿名还是有名对象,随着代码的不断深入,就能轻松理解什么时候使用哪种。
总结
- 开辟新的堆内存空间除了使用关键字new之外还要调用构造方法;
- 构造方法定义要求:
- 方法名称与类名称相同,无返回值声明;
- 构造方法允许进行重载,重载构造方法只需要考虑参数类型及个数即可;
- 如果一个类没有声明构造方法,则在编译时会自动的创建一个无参的什么都不做的构造方法,也就是说一个类之中至少会保留一个构造方法;
- 只使用一次的对象可以利用匿名对象完成,匿名对象的本质就是只有堆内存没有栈引用的对象。
综合实战:简单Java类(很重要)
简单Java类的开发原则及具体实现
现在要求定义一个雇员的信息类,在这个类之中包含有雇员编号、姓名、职位、基本工资、佣金等信息。
这样的类的组成结构是非常简单的,但是在开发中,如果想把这个类写好,也是有一定的开发原则的。
简单Java类的开发原则(第一式)
- 类名称必须要有实际的意义,可以明确的描述某一类实体,例如:学生;
- 类中的所有属性必须使用private封装;
- 所有封装的属性必须按照定义要求编写setter、getter方法;
- 类中可以定义若干个构造方法,但是必须要保留有一个明确的无参构造方法定义;
- 类中不允许出现任何的输出操作,所有的输出操作必须返回给调用处输出;
- 【临时】类中应该提供一个可以取得对象完整信息的方法,现在暂时将方法命名为getInfo().
范例:定义雇员类
class Emp{//雇员信息,名称有意义
private int empno;
private String ename;
private String job;
private double sal;
private double comm;
public Emp(){}
public Emp(int eno,String ena,String j,double s,double c){
setEmpno(eno);
setEname(ena);
setJob(j);
setSal(s);
setComm(c);
}
public String getInfo(){
return "雇员编号:"+empno+"\n"+
"雇员姓名:"+ename+"\n"+
"雇员职位:"+job+"\n"+
"基本工资:"+sal+"\n"+
"佣金:"+comm;
}
public void setEmpno(int eno){
empno=eno;
}
public void setEname(String ena){
ename=ena;
}
public void setJob(String j){
job=j;
}
public void setSal(double s){
sal=s;
}
public void setComm(double c){
comm=c;
}
public int getEmpno(){
return empno;
}
public String getEname(){
return ename;
}
public String getJob(){
return job;
}
public double getSal(){
return sal;
}
public double getComm(){
return comm;
}
}
随后可以针对此类操作进行测试,就测试设置属性和取得内容。
范例:测试代码
public class Hello {
public static void main(String args[]){
Emp emp= new Emp(7369,"Smith","Manager",50000.0,40000.0);
System.out.println(emp.getInfo());
}
}
整个代码没有任何的逻辑,但是所有的学习到的面向对象的基础概念此处都有涉及到。
总结
现在如果不考虑很多,任何的实体都是可以进行抽象的,利用这样的概念可以轻松实现抽象操作。