深入分析类与对象

成员属性封装

在类之中的组成就是属性与方法,一般而言方法都是对外提供服务的,所以是不会进行封装处理的,而对于属性由于其需要较高的安全性,所以往往需要对其进行保护,这个时候就需要采用封装性对属性进行保护。

在默认情况下,对于类中的属性是可以通过其它类利用对象进行调用。

范例:属性不封装情况下的问题

class Person {
    String name ;
    int age ;
    public void tell() {
        System.out.println("姓名:" + name + "、年龄:" + age) ;
    }
}

public class JavaDemo {  // 主类
    public static void main(String args[] ) {
        Person per = new Person() ;  // 声明并实例化对象
        per.name = "张三" ;  // 在类外部修改属性
        per.age = -18 ;     // 在类外部修改属性
        per.tell() ;  // 进行方法调用
    }
}

D:\fgqjava>javac JavaDemo.java

D:\fgqjava>java JavaDemo
姓名:张三、年龄:-18

此时在person类中提供的name与age两个属性并没有进行封装处理,这样外部就可以直接进行调用了,但是有可能所设置的数据是错误的数据。如果要想解决这样的问题就可以利用private关键字对属性进行封装处理。

只是在classs中加了private:
class Person {
    private String name ;
    private int age ;
    public void tell() {
        System.out.println("姓名:" + name + "、年龄:" + age) ;
    }
}

public class JavaDemo {  // 主类
    public static void main(String args[] ) {
        Person per = new Person() ;  // 声明并实例化对象
        per.name = "张三" ;  // 在类外部修改属性
        per.age = -18 ;     // 在类外部修改属性
        per.tell() ;  // 进行方法调用
    }
}

D:\fgqjava>javac JavaDemo.java
JavaDemo.java:12: 错误: name 在 Person 中是 private 访问控制
                per.name = "张三" ;  // 在类外部修改属性
                   ^
JavaDemo.java:13: 错误: age 在 Person 中是 private 访问控制
                per.age = -18 ;         // 在类外部修改属性
                   ^
2 个错误

而属性一旦封装之后外部将不能够直接访问,即:对外部不可见。但是对类的内部是可见的,那么如果要想让外部的程序可以访问封装的属性,则在Java开发标准中提供有如下要求:

【setter、getter】设置或取得属性可以使用setXxx()、getXxx()方法,
以:private String name为例;
    设置属性方法:public void setName(String n);
    获取属性方法:public String getName()。
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 ;
    }
}

public class JavaDemo {  // 主类
    public static void main(String args[] ) {
        Person per = new Person() ;  // 声明并实例化对象
        //per.name = "张三" ;  // 属性被封装之后就看不见了,所以就不用属性了
        //per.age = -18 ;
        per.setName("张三") ;
        per.setAge(-18) ;
        per.tell() ;  // 进行方法调用
    }
}

D:\fgqjava>javac JavaDemo.java

D:\fgqjava>java JavaDemo
姓名:张三、年龄:-18


属性被封装之后就看不见了,所以就不用属性了,即:注释掉或者去掉per.name 和 per.age,
增加两项:per.setName("张三") ; 和 per.setAge(-18) ;

增加一个age数值检测

范例:实现封装

只是在" public void setAge(int a) " 中加了一个条件
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) {
            age = a ;
        }
    }
    public String getName() {
        return name ;
    }
    public int getAge() {
        return age ;
    }
}

public class JavaDemo {  // 主类
    public static void main(String args[] ) {
        Person per = new Person() ;  // 声明并实例化对象
        //per.name = "张三" ;  // 属性被封装之后就看不见了,所以就不用属性了
        //per.age = -18 ;
        per.setName("张三") ;
        per.setAge(-18) ;
        per.tell() ;  // 进行方法调用
    }
}

D:\fgqjava>javac JavaDemo.java

D:\fgqjava>java JavaDemo
姓名:张三、年龄:0

如果不满足 age>=0 的条件,那么age的默认值就是int的默认值:0

在以后进行任何类定义的时候一定要记住,类中的所有属性都必须使用private封装(98%),并且属性如果要进行访问必须要提供有setter、getter方法。

封装性是Java面向对象的第一大主要特征,但是封装性不仅仅是一个属性封装这么简单,
它跟访问权限有关,关于访问权限后续说明。

以后开发中属性都加上private,这是标准做法:
private String name;
private int age;
构造方法与匿名对象

现在的程序在使用类的时候一般都按照了如下的步骤进行:

1.声明并实例化对象,这个时候实例化对象中的属性并没有任何的数据存在,都是其对应数据类型的默认值;
2.需要通过一系列的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) {
        age = a ;
    }
    public String getName() {
        return name ;
    }
    public int getAge() {
        return age ;
    }
}
public class JavaDemo {  // 主类
    public static void main(String args[]) {
        // 1、对象初始化准备
        Person per = new Person() ;  // 声明并实例化对象
        per.setName("fgq") ;
        per.setAge(-18) ;       // 在类外部修改属性
        // 2、对象的使用
        per.tell() ;        // 进行方法的调用
    }
}

但是如果按照这样的方式来进行思考的话就会发现一个问题:假设说现在类中的属性很多个(8个),那么这样一来按照之前的做法,此时就需要调用8次的setter方法进行内容设置,这样的调用实在是太啰嗦了,所以在Java里面为了考虑到对象初始化的问题,专门提供有构造方法,即:可以通过构造方法实现实例化对象中的属性初始化处理。只有在关键字new的时候使用构造方法,在Java程序里面构造方法的定义要求如下:
构造方法名称必须与类名称保持一致;
构造方法不允许设置任何的返回值类型,即:没有返回值定义;
构造方法是在使用关键字new实例化对象的时候自动调用的。

范例:定义构造方法

class Person {
    private String name ;
    private int age ;
    // 方法名称与类名称相同,并且无返回值定义
    public Person (String n,int a) {    //  定义有参构造
        name = n ;  // 为类中的属性赋值(初始化)
        age = a ;   // 为类中的属性赋值(初始化)
    }
    public void tell() {
        System.out.println("姓名:" + name + "、年龄:" +age) ;
    }
}
public class JavaDemo {  // 主类
    public static void main(String args[]) {
        // 1、对象初始化准备
        Person per = new Person("张三",18) ;  // 声明并实例化对象
        // 2、对象的使用
        per.tell() ;        // 进行方法的调用
    }
}

D:\fgqjava>javac JavaDemo.java

D:\fgqjava>java JavaDemo
姓名:张三、年龄:18

下面针对于当前的对象实例化格式与之前的对象实例化格式做一个比较:

之前的对象实例化格式:
①Person ②per = ③new ④Person() ;
当前的对象实例化格式:
①Person ②per = ③new ④Person("张三",18) ;

“①Person”:主要是定义对象的所属类型,类型决定了你可以调用的方法;
“②per”:实例化对象的名称,所有的操作通过对象来进行访问;
“③new”:开辟一块新的堆内存空间;
“④Person("张三",18)”:调用有参构造、
“④Person()”:调用无参构造、

在Java程序里面考虑到程序结构的完整性,所以所有的类都会提供有构造方法,也就是说如果现在你的类中没有定义任何的构造方法,那么一定会默认提供有一个无参的,什么都不做的构造方法,这个构造方法是在程序编译的时候自动创建的。如果你现在已经在类中明确的定义有一个构造方法的时候,那么这个默认的构造方法将不会被自动创建。

结论:一个类至少存在有一个构造方法,永恒存在。

把"Person per = new Person("张三",18) ;"中的参数去掉
同时加上 setter 和 getter 方法

代码如下:执行后报错,目的是验证上面的话
class Person {
    private String name ;
    private int age ;
    // 方法名称与类名称相同,并且无返回值定义
    public Person (String n,int a) {    //  定义有参构造
        name = n ;  // 为类中的属性赋值(初始化)
        age = a ;   // 为类中的属性赋值(初始化)
    }
    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 ;
    }
}
public class JavaDemo {  // 主类
    public static void main(String args[]) {
        // 1、对象初始化准备
        Person per = new Person() ;  // 声明并实例化对象
        // 2、对象的使用
        per.tell() ;        // 进行方法的调用
    }
}

D:\fgqjava>java JavaDemo
姓名:张三、年龄:18

D:\fgqjava>javac JavaDemo.java
JavaDemo.java:28: 错误: 无法将类 Person中的构造器 Person应用到给定类型;
                Person per = new Person() ;  // 声明并实例化对象
                             ^
  需要: String,int
  找到: 没有参数
  原因: 实际参数列表和形式参数列表长度不同
1 个错误

疑问:为什么构造方法上不允许设置返回值类型?
既然构造方法是一个方法,那么为什么不让它定义返回值类型呢?
既然构造方法不会返回数据,为什么不使用void定义呢?
正确:public Person (String n,int a) {}
为什么不使用:public void Person (String n,int a) {}

分析:
程序编译器是根据代码结构来进行编译处理的,执行的时候也是根据代码结构来处理的。
构造方法:public Person (String n,int a) {}  
普通方法:public void tell() {}

如果在构造方法上使用了void,那么此结构就与普通方法的结构完全相同了,
这样编译器会认为此方法是一个普通方法,普通方法与构造方法最大的区别:
    构造方法是在类对象实例化的时候调用的,
    而普通方法是在类对象实例化产生之后调用的。

既然构造方法本身是一个方法,那么方法就具有重载特点,而构造方法重载的时候只需要考虑参数的类型及个数即可。

范例:构造方法重载

class Person {
    private String name ;
    private int age ;
    public Person() {
        name = "无名氏" ;
        age = -1 ;
    } 
    public Person(String n) {
        name = n ;
    }
    public Person (String n,int a) {    //  定义有参构造
        name = n ;  // 为类中的属性赋值(初始化)
        age = a ;   // 为类中的属性赋值(初始化)
    }
    public void tell() {
        System.out.println("姓名:" + name + "、年龄:" +age) ;
    }
}
public class JavaDemo {  // 主类
    public static void main(String args[]) {
        // 1、对象初始化准备
        Person per = new Person() ;  // 声明并实例化对象
        // 2、对象的使用
        per.tell() ;        // 进行方法的调用
    }
}

D:\fgqjava>javac JavaDemo.java

D:\fgqjava>java JavaDemo
姓名:无名氏、

在进行多个构造方法定义的时候强烈建议大家有一些定义的顺序,例如:可以按照参数的个数降序或升序排列。此处是升序排列
无参 public Person() {}
一参 public Person(String n) {}
二参 public void Person (String n,int a) {}

经过分析可以发现,构造方法的确是可以进行数据的设置,而对于setter也可以进行数据的设置,这个时候一定要清楚,构造方法是在对象实例化的时候为属性设置初始化内容,而setter除了拥有设置数据的功能之外,还具有修改数据的功能。

范例:使用setter修改数据

class Person {
    private String name ;
    private int age ;
    public Person (String n,int a) {    //  定义有参构造
        name = n ;  // 为类中的属性赋值(初始化)
        age = a ;   // 为类中的属性赋值(初始化)
    }
    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 ;
    }
}
public class JavaDemo {  // 主类
    public static void main(String args[]) {
        // 1、对象初始化准备
        Person per = new Person("张三",10) ;  // 声明并实例化对象
        per.setAge(18) ;    // 修改属性内容
        // 2、对象的使用
        per.tell() ;        // 进行方法的调用
    }
}

D:\fgqjava>javac JavaDemo.java

D:\fgqjava>java JavaDemo
姓名:张三、年龄:18

经过了分析之后可以发现,利用构造方法可以传递属性数据,于是现在进一步分析对象的产生格式:
定义对象的名称:类名称 对象名称 = null ;
实例化对象:对象名称 = new 类名称()。
如果这个时候只是通过实例化对象来进行类的操作也是可以的,而这种形式的对象由于没有名字就称为匿名对象。

范例:观察匿名对象

class Person {
    private String name ;
    private int age ;
    public Person (String n,int a) {    //  定义有参构造
        name = n ;  // 为类中的属性赋值(初始化)
        age = a ;   // 为类中的属性赋值(初始化)
    }
    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 ;
    }
}
public class JavaDemo {  // 主类
    public static void main(String args[]) {
        new Person("张三",10).tell() ;        // 进行方法的调用
    }
}

D:\fgqjava>javac JavaDemo.java

D:\fgqjava>java JavaDemo
姓名:张三、年龄:10

这个程序纯粹娱乐,玩玩就可以了,不要上心

此时依然通过了对象进行了类中tell()方法的调用,但是由于此对象没有任何的引用名称,所以该对象使用一次之后就将成为垃圾,而所有的垃圾将被GC进行回收与释放。

什么时候用有名对象,什么时候用匿名对象呢?看你的需求

现在发现此时的程序里面已经存在有构造方法了,那么下面通过一个程序来利用构造方法进行一次内存分析。

范例:编写一个分析程序

无聊的测试而已

class Message {
    private String title ;
    public Message(String t) {
        title = t ;
    }
    public void setTitle(String t) {    // 具有修改功能
        title = t ;
    }
    public String getTitle() {
        return title ;
    }
}
class Person {
    private String name ;
    private int age ;
    public Person (Message msg,int a) { //  定义有参构造
        name = msg.getTitle() ; // 为类中的属性赋值(初始化)
        age = a ;   // 为类中的属性赋值(初始化)
    }
    public Message getInfo() {
        return new Message(name + " : " + age) ;
    }
    public void tell() {
        System.out.println("姓名:" + name + "、年龄:" +age) ;
    }
}
public class JavaDemo {  // 主类
    public static void main(String args[]) {
        Message msg = new Message("fgqcom") ;
        Person per = new Person(msg,20) ;
        msg = per.getInfo() ;
        System.out.println(msg.getTitle()) ;
    }
}

D:\fgqjava>javac JavaDemo.java

D:\fgqjava>java JavaDemo
fgqcom : 20

下面通过此程序进行一个简短的内存分析。

内存分析


1
public Message getInfo() {
    return new Message(name + " : " + age) ;
}
public Message(String t) {定义了title内容}
new 开辟一块新的堆内存空间;匿名对象-->title = "fgqcom : 20",如上图所示
匿名对象最终结果会给 "msg"
但是msg本身有引用:title = "fgqcom" ;
所以msg要抛弃原本的引用,从而指向新的引用:title = "fgqcom : 20"
msg = per.getInfo() ; 此处msg的引用指向新的引用:匿名空间:title = "fgqcom : 20",原来的空间变为垃圾空间。
当我们再次输出的时候,就是 title = "fgqcom : 20"。

这个程序没有实际意义,分析它,也是告诉大家:只要是方法都可以传递任意的数据类型(基本数据类型、引用数据类型)。String字符串本身也是一个引用,只不过特殊点而已。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 81,714评论 1 180
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 28,670评论 1 144
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 33,484评论 0 105
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 18,252评论 0 90
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 23,553评论 0 148
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 19,431评论 1 88
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 12,133评论 2 165
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 11,521评论 0 80
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 10,051评论 5 114
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 13,291评论 0 130
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 12,027评论 1 128
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 12,880评论 0 133
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 7,665评论 0 18
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 10,467评论 2 119
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 13,669评论 3 129
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 9,252评论 0 3
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 9,542评论 0 80
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 14,226评论 2 138
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 14,699评论 2 134

推荐阅读更多精彩内容