技术源 WWW.JOCK168.COM
第七章 封装对象

发布于:2015-03-24 16:06亚博vip手机版网页:

 


    本章详细介绍了对象封装的原因及过程,几种常见的程序代码块及单例模式的编写。

    7.1 为什么要封装

    范例1:EncapsulationDemo01.java

    class Person{
        String name;
        int age;
        void say(){
            System.out.println("我叫"+name+",今年"+age+"岁。");
        }
    }
    public class EncapsulationDemo01{
        public static void main(String[] args){
            Person person = new Person();
            person.name = "张三";
            person.age = 20;
            person.say();
        }
    }

    本例非常简单,程序的输出结果是:我叫张三,今年20岁。
    分析:本例中定义了两个类,一个是Person类,一个是EncapsulationDemo01类,在EncapsulationDemo01类中首先实例化了Person类,然后通过Person类的实例化对象进行赋值操作。看上去貌似没有什么问题,但真是这样么?看下范例2。
    范例2:EncapsulationDemo02.java

    class Person{
        String name;
        int age;
        void say(){
            System.out.println("我叫"+name+",今年"+age+"岁。");
        }
    }
    public class EncapsulationDemo02{
        public static void main(String[] args){
            Person person = new Person();
            person.name = "张三";
            person.age = -20;
            person.say();
        }
    }

    本例代码运行时没有任何问题,但是由于不小心把age的值指定为了-20,导致结果发生了逻辑错误,因为人(Person)的年龄(age)不可能是负数。由此可见,通过Person实例对象来给age属性赋值的方式是不安全的。并且age本身是Person类的属性,外界根本不应该拥有直接操纵该属性的权利。而上面两个范例中Person类中的属性都完全的暴露给了使用它的对象。为了程序的安全性和健壮性,所以需要封装对象。

    7.2 什么是封装

    所谓封装就是把对象的属性私有化,不让外部对象直接访问。在程序开发中,通常使用private关键词来实现该功能。语法格式为:private 类型 属性名(或方法名)。被private声明的属性或方法只能在该类使用,而不能在为的外部使用。封装的目的是为了提高程序的安全性。

    7.3 如何封装

    范例3:EncapsulationDemo03.java

    class Person{
        private String name;
        private int age;
        void say(){
            System.out.println("我叫"+name+",今年"+age+"岁。");
        }
    }
    public class EncapsulationDemo03{
        public static void main(String[] args){
            Person person = new Person();
            person.name = "张三";
            person.age = -20;
            person.say();
        }
    }

    本例中除了使用private关键词来修饰了name和age属性外,程序代码其它部分没有任务改动。编译程序时,出现了如下错误信息:
    E:\java>javac EncapsulationDemo03.java
    EncapsulationDemo03.java:11: 错误: name可以在Person中访问private
            person.name = "张三";
                  ^
    EncapsulationDemo03.java:12: 错误: age可以在Person中访问private
            person.age = -20;
                  ^
    2 个错误

    这个错误信息说明了被private修饰后的属性不能再通过外部类来直接访问了。当外部类中不能访问Person对象中的属性后,程序就没有办法给Person对象赋值了,为了让程序既不能直接访问Person对象的属性,又可以给Person对象赋值,那又如何才能实现呢?为了解决这个问题,一般程序设计时,会提供属性对应的set和get方法。
    范例4:EncapsulationDemo04.java

    class Person{
        private String name;
        private int age;
        public void setName(String name){
            this.name = name;
        }
        public String getName(){
            return name;
        }
        public void setAge(int age){
            this.age = age;
        }
        public int getAge(){
            return age;
        }
        void say(){
            System.out.println("我叫"+name+",今年"+age+"岁。");
        }
    }
    public class EncapsulationDemo04{
        public static void main(String[] args){
            Person person = new Person();
            person.setName("张三");
            person.setAge(20);
            person.say();
        }
    }

    本例能正常编译,且运行结果为:我叫张三,今年20岁。
    分析:首先在Person类中增加了void setName(String name)、String getName()、void setAge(int age)、int getAge()这四个方法。其中setName(String name)方法是给Person类中的name属性赋值,getName()方法是获取name属性的值;setAge(int age)方法是给age属性赋值,getAge()方法是获取age属性的值。然后在EncapsulationDemo04类中实例化Person类后,通过person.setName("张三");和person.setAge(20);两行代码来初始化person对象。最后调用say()方法输出。
    在Person类中加了setName()和setAge()方法后,确实可以给name和age属性赋值了,但是否安全呢?
    范例5:EncapsulationDemo05.java

    class Person{
        private String name;
        private int age;
        public void setName(String name){
            this.name = name;
        }
        public String getName(){
            return name;
        }
        public void setAge(int age){
            this.age = age;
        }
        public int getAge(){
            return age;
        }
        void say(){
            System.out.println("我叫"+name+",今年"+age+"岁。");
        }
    }
    public class EncapsulationDemo05{
        public static void main(String[] args){
            Person person = new Person();
            person.setName("张三");
            person.setAge(-20);
            person.say();
        }
    }

    本例再次不小心把age属性的值设置为了-20,貌似好象转了一圈又回到了范例2的问题上了。那么,既然又回到了以前的问题,之前的操作是否有必要呢?
    肯定是有必要的,在范例2中是不能控制数据设置的,但范例5中确可以。下面就是在setName()或setAge()方法中加入数据校验,以确保数据的有效性。
    范例6:EncapsulationDemo06.java

    class Person{
        private String name;
        private int age;
        public void setName(String name){
            if(name == null || "".equals(name)){
                System.out.println("姓名需要提供");
            this.name = "";
            }else{
                this.name = name;
            }
        }
        public String getName(){
            return name;
        }
        public void setAge(int age){
            if(age <= 0){
                System.out.println("年龄不对");
            this.age = 0;
            }else{
                this.age = age;
            }
        }
        public int getAge(){
            return age;
        }
        void say(){
            if(!"".equals(name) && age > 0)
                System.out.println("我叫"+name+",今年"+age+"岁。");
        }
    }
    public class EncapsulationDemo06{
        public static void main(String[] args){
            Person person = new Person();
            person.setName("张三");
            person.setAge(-20);
            person.say();
        }
    }

    本例输出结果为:年龄不对
    分析:在程序第5~10行加入了对String类型数据的校验。name == null是对空对象的比较,如果name没有指定值,那么name就使用字符串对象的默认值null,比较返回值为true;如果指定了值那么比较后的返回值为false。程序第16~21行加入了对age的校验,当age的值小于或等于0时,给出提示信息,并给age初值为0。程序27行加入了输出校验。只有当name属性不为空字符串并且age属性的值大于0时才输出其信息。

    小结:对象封装共有三个步骤:通过private关键词私有化属性;提供公有方法来访问私有属性;在公有方法中加入校验。

    7.4 初始化的几种方式

    在Java中,可以使用构造方法来初始化对象,也可以使用静态代码块来初始化对象,还可以使用普通代码块来初始化对象。那么它们的之间的执行顺序又是怎样的呢?
    范例7:EncapsulationDemo07.java
 

   class Person{
        //构造方法
        public Person(){
            System.out.println("构造方法代码块被执行...");
        }
        //静态代码块
        static{
            System.out.println("静态代码块被执行...");
        }
        //普通代码块
        {
            System.out.println("普通代码块被执行...");
        }
        }
        public class EncapsulationDemo07{
            public static void main(String[] args){
            Person person = new Person();
        }
    }

    程序输出结果为:
    静态代码块被执行...
    普通代码块被执行...
    构造方法代码块被执行...


    为什么是这样一个执行顺序呢?
    通过上一章的学习,我们知道被static修改的属性或方法是随着类加载而加载,随着类消失而消失的。因此,静态代码块最先被执行。而构造方法是在实例化对象时才会被执行,所以构造方法是最后执行。

    7.5 构造方法私有化

    程序代码中构造方法并不都是public的,有时需求不想让外部直接new 该对象,这时就需要把构造方法私有化。
    范例8:PrivateConstructor.java

    class Calcul{
        private static Calcul instance = new Calcul();
        private Calcul(){
            System.out.println("私有构造方法...");
        }
        public static Calcul getInstance(){
            return instance;
        }
    }
    public class PrivateConstructor{
         public static void main(String[] args){
             Calcul.getInstance();
         }
    }

    本例就使用了构造方法私有化,当构造方法私有化后,在这个对象之外就不能实例化它了,为了能实例化这个对象,必须在这个对象中提供一个公共的方法,让这个公共的方法来调用私有构造方法,从而达到实例化对象的目的。
    本例其实就是单例模式的一种编写方式。单例模式的另一种方式我们放到多线程里再讲解。
    所谓单例模式,它是设计模式中的一种,单例模式的好处就在于无论创建多少个实例对象,内存只会分配一块区域。换句话说,使用单例模式创建的对象都是同一个对象。
    范例9:SingletonDemo.java

    class Singleton{
        private static Singleton instance = new Singleton();
            private Singleton(){
        }
        public static Singleton getInstance(){
            return instance;
        }
    }
    public class SingletonDemo{
         public static void main(String[] args){
             Singleton s1 = Singleton.getInstance();
             Singleton s2 = Singleton.getInstance();
             Singleton s3 = Singleton.getInstance();
             System.out.println(s1 == s2);
             System.out.println(s2 == s3);
         }
    }

    程序输出结果为:
    true
    true


    从结果可以直观的看到,创建的三个Singleton对象都是指向同一个地址,因此是同一个对象。

 
  • 链接

  • 百度
  • 新浪
  •