对象与类(上)

  • 面向对象程序设计(object-oriented programming,OOP)
    • 类(class,构造对象的模块或蓝图)
      • 由类构造(construct)对象的过程称为创建类的实例(instance)
      • 封装(encapsulation,有时称为数据隐藏)
        • 从形式上看,封装就是将数据和行为组合在一个包里,并对对象的使用者隐藏具体实现方式。
        • 对象中的数据称为实例字段(instance field),操作数据的过程称为方法(method)
      • 在Java中,所有的类都源自 Object 类,所有其他类都扩展自这个 Object类
      • 继承(inheritance,继承后的新类具有被继承类的全部属性和方法)
        • 只需要在新类中提供适用与这个新类的新方法和数据字段就可以了
    • 对象
      • 对象的行为——用可调用的方法来定义的
      • 对象的状态——每个对象都保存着描述当前状况的信息,其状态的改变必须通过调用方法实现
      • 对象的标识——每个对象都有一个唯一的标识(identity,或称身份)
    • 类之间的关系
      • 依赖(dependence),一个类的方法使用或操纵另一个类的对象,尽可能减少这种类之间的耦合
      • 聚合(aggregation),类A的对象包含类B的对象
      • 继承(inheritance),如果类A扩展类B,类A不但包含从类B继承的方法,还会有一些额外的功能
  • 使用预定义类
    • 不是所有类都具有面向对象的典型特征。例如,Math类。可以直接使用Math类的方法,如 Math.random
    • Math类只封装了功能,不需要隐藏数据。因此不必考虑创建对象和初始化它们的实例字段。
    • 对象与对象变量
      • 构造器(constructor,或称构造函数)构造新实例。构造器的名字与类名相同,并且需要在构造器前面加上new操作符构造一个对象。
      • new Date();   //该表达式构造了一个新对象,这个对象被初始化为当前的日期和时间
        String s=new Date().toString(); //Date类有一个toStrng方法,这个方法将返回日期的字符串描述
      • 上面两个例子中,构造的对象仅使用了一次。要想构造的对象可以多次使用,需要将对象存放在一个变量中

      • Date birthday=new Date();   //对象变量 birthday,它引用了新构造的对象
      • 对象变量并没有实际包含一个对象,它只是引用一个对象。Java中,任何对象变量都是对存储在另外一个地方的某个对象的引用。

    • Java类库中的 LocalDate类

      • LocalDate类,用大家熟悉的日历表示法表示日期

      • 不要使用构造器来构造 LocalDate类的对象。应当使用静态工厂方法(factory method),它会代表调用构造器

        • LocalDate.now();  //会构造一个新对象,表示构造这个对象时的日期
          LocalDate.of(1999.12.31); //可以提供年、月和日来构造对于的一个特定日期的对象
          LocalDate newYearsEve=LocalDate.of(1999,12,31); //将构造的对象保存在一个对象变量中
      • plusDays方法会得到一个新的 LocalDate,这个新日期对象则是距应用这个方法的对象指定天数的一个新日期

        • LocalDate aThousandDaysLater=newYearEve.plusDays(1000); //plusDays方法会生成一个新的 LocalDate对象,然后把这个新对象
          赋给aThousandDaysLater变量。原来的对象不做任何改动
          int year=aThousandDaysLater.getYear(); //方法 getYear得到年。结果,2002 int month=aThousandDaysLater.getMonthValue(); //方法 getMonthValue得到月。结果,09 int day=aThousandDaysLater.getDayOfMonth(); //方法 getDayOfMonth得到日。结果,26
      • Java较早版本增加有另一个处理日历的类,GregorianCalendar

        • GregorianCalendar someDay=new GregorianCalendar(1999, 11, 31);  //与 LocalDate.plusDays方法不同,GregorianCalendar.add方法
          someDay.add(Calendar.DAY_OF_MONTH, 1000); 是一个更改器方法。调用这个方法后,someDay对象的状态会改变
          int year=someDay.get(Calendar.YEAR); //2002
          int month=someDay.get(Calendar.MONTH)+1; //09
          int day=someDay.get(Calendar.DAY_OF_MONTH); //26
  • 用户自定义类

    • 带有 public访问修饰符的EmployeeTest类源文件中包含了一个Employee非公共类

      • 在一个源文件中,只能有一个公共类,但可以有任意数目的非公共类

      • Employee[] staff=new Employee[3];   //构造一个 Employee数组,并填入3个Employee对象
        staff[0]=new Employee("Carl Cracker",75000,1987,12,15);
        staff[1]=new Employee("Harry Hacker",50000,1989,10,1);
        staff[2]=new Employee("Tony Tester",40000,1990,3,15);
        for(Employee e:staff) //使用Employee类的raiseSalary方法将每个员工的薪水提高 5%
           e.raiseSalary(5);

          for(Employee e:staff)   //调用 getName方法、getSalary方法和getHireDay方法打印各个员工的信息       
               System.out.println("name="+e.getName()+",salary="+e.getSalary()+",hireDay="+e.getHireDay());

    • 剖析 Employee类

      • public Employee(String n,double s,int year,int month,int day)  //这个类的所有方法都被标记为 public ,关键字public意味着任何类
        public String getName()                                         的任何方法都可以调用这些方法
        public double getSalary()
        public LocalDate getHireDay()                                 
        public void raiseSalary(double byPercent)
      • 在 Employee类中有3个实例字段用来存放将要操作的数据

      • private String name;          //关键字 private确保只有 Employee类自身的方法能够访问这些实例字段,而其他类的方法
        private double salary;            不能够读写这些字段
        private LocalDate hirDay;
      • 有两个实例字段本身就是对象:name字段是 String类对象,hireDay字段是LocalDate类对象。(类包含的实例字段通常属于某个类类型)

    • 从构造器开始

      • Employee类的构造器

      • public Employee(String n,double s,int year,int month,int day) {    //构造器与类同名。在构造 Employee对象时,构造器会允许,
                name=n;                                                        从而将实例字段初始化为所希望的初始状态
                salary=s;
                hirDay=LocalDate.of(year,month,day);
            }
      • 例如,当使用下面这条代码创建 Employee类的实例时:

      • new Employee("James Bond",100000,1950,1,1);   //构造器与其他方法有一个重要的不同。构造器总是结合new操作符来调用
      • 将会把字段设置为:

      • name="James Bond";
        salary=100000;
        hireDay=LocalDate.of(1950,1,1);
      • 有关构造器总结

        • 构造器与类同名

        • 每个类可以有一个以上的构造器

        • 构造器可以有0个、1个或多个参数

        • 构造器没有返回值

        • 构造器总是伴随着new操作符一起调用  

    • 用 var 声明局部变量

      • 在 Java10中,如果可以从变量的初始值推导出它们的类型,就可以用 var关键字声明局部变量,而无需指定类型。

      • var harry=new Employee("Harry Hacker", 50000, 1989, 10, 1);   //替代 Employee harry=new Employee("Harry Hacker", ...)
      • 注意:不会对数值类型使用 var,如 int、long或 double,使你不用当心 0、0L和 0.0之间的区别。

        • var关键字只能用于方法中的局部变量,参数和字段的类型必须声明。

    • 使用 null 引用

      • 一个对象变量包含一个对象的引用,或者包含一个特殊值null,后者表示没有引用任何对象。要小心使用 null值

      • LocalDate birthday=null//如果对 null值应用一个方法,会产生一个 NullPointerException异常  
        String s=birthday.toString();   //NullPointerException
      • 我们不希望 name或 hireDay字段为 null(不用担心 salary字段,这个字段是基本类型,所以不可能是 null)

    • 隐式参数与显示参数

      • 方法用于操作对象以及存取它们的实例字段。例如,以下方法

      • public void raiseSalary(double byPercent) {
                double raise=salary*byPercent/100;
                salary+=raise;
            }
      • 将调用这个方法的对象的salary实例字段设置为一个新值。考虑下面这个调用

      • number007.reiseSalary(5); 
      • 它的结果是将 number007.salary字段的值增加5%。具体地说,这个调用将执行下列指令:

      • double raise=number007.salary * 5 / 100;
        number007.salary+=raise;
      • raiseSalary方法有两个参数。第一个参数称为隐式参数,是出现在方法名前的Employee类型的对象(number007)。第二个参数是位于方法名后     面括号中的数值,这是一个显式参数

      • 在每一个方法中,关键字 this指示隐式参数。如下改写 raiseSalary方法:

      • public void raiseSalary(double byPercent) {
                double raise=this.salary*byPercent/100;
                this.salary+=raise;
            }
    • 封装的优点

      • public String getName() {           //这些都是典型的访问器方法。
                return name;                     
            }                                       由于它们只返回实例字段值,因此又称为字段访问器
        public double getSalary() {
                return salary;              //这样,name就是一个只读字段。一旦在构造器中设置,就没有任何办法可以对它进行修改       
            }                                     这样我们可以确保name字段不会受到外界的破坏 
        public LocalDate getHireDay() {
                return hirDay;
            }
      • 想要获得或设置实例字段的值。需要下面三项内容

        • 一个私有的数据字段     (例如,private String name)

        • 一个公共的字段访问器方法     (例如,getName方法)

        • 一个公共的字段更改器方法     (例如,setName方法)

    • 私有方法

      • 在 Java中,要实现私有方法,只需将关键字 public 改为 private 即可。

      • 如果一个方法是公共的,就不能简单地将其删除,因为有可能会有其他代码依赖这个方法

    • final 实例字段

      • 这样的字段必z须在构造对象时初始化。(也就是说,确保每一个构造器执行后,该字段值已经设置,并且以后不会再修改这个字段)

      • class Employee{    //在对象构造后,这个值不会再改变,即没有 setName方法                
              private final String name;   //final 修饰符对于类型为基本类型或者不可变类的字段尤其有用
        }                                        String 类就是不可变的。(类中所有方法都不会改变其对象)

 

  • 静态字段与静态方法
    • 静态字段
      • 给 Employee类添加一个实例字段 id 和一个静态字段 nextId:
      • class Employee{
            private int id;    //每一个 Employee对象都有一个自己的 id字段,但这个类的所有实例将共享一个 nextId字段  
            private static int nextId=1;  //即使没有 Employee对象,静态字段 nextId也存在,它属于类,而不属于任何单个的对象
        }
    • 静态常量

      • 静态变量使用得比较少,但静态常量却很常用。例如,在 Math类中定义一个静态常量:

      • public class Math{   //在这个程序中,可以用 Math.PI来访问这个常量
              public static final doule PI=3.1415926535;  //如果省略了关键字 static,PI就变成了Math类的一个实例字段。也就需要通过 
        }                                                        Math类的一个对象来访问 PI,并且每个Math对象都有它自己的一个 PI副本
    • 静态方法

      • 静态方法是不在对象上执行的方法。例如,Math类的 pow方法就是一个静态方法。表达式 Math.pow(x,a) 幂运算 

      • Employee类的静态方法不能访问 id实例字段,因为它不能在对象上执行操作,只可以访问静态字段 

      • public static int getNextId() {   //可以提供类名来调用这个方法:int n=Employee.getNextId();  
                return nextId;                  
            }
      • 在下面两种情况下可以使用静态方法:

        • 方法不需要访问对象状态。(对象状态的改变必须通过调用方法实现)

        • 方法只需要访问类的静态字段(例如,Employee.getNextId)

    • 工厂方法

      • 静态方法的另外一种常见的用途。类似 LocalDate和 NumberFormat的类使用静态工厂方法来构造对象

      • NumberFormat currencyFormatter=NumberFormat.getCurrencyInstance(); //NumberFormat类如下生成不同风格的格式化对象  
        NumberFormat percentFormatter=NumberFormat.getPercentInstance();  //NumberFormat类不利用构造器完成这些操作的原因: 
        double x=0.1;                                       1.无法命名构造器。构造器名字必须与类名相同。这里希望有两个不同的名字
        System.out.println(currencyFormatter.format(x));    2.使用构造器时,无法改变构造对象的类型。而工厂方法实际上将返回 
        System.out.println(percentFormatter.format(x));          DecimalFormat类的对象 
    • main方法

      • main也是一个静态方法。不对任何对象进行操作。在启动程序时还没有任何对象。静态的main方法将执行并构造程序所需要的对象

  • 方法参数

    • 按值调用表示方法接收的是调用者提供的值。按引用调用表示方法接收的是调用者提供的变量地址。

      • 方法可以修改按引用传递的变量的值,而不能修改按值传递的变量的值。

    • Java程序设计语言总是按值调用。方法不能修改传递给它的任何参数变量的内容。例如,下面的调用:

    • double percent=10;
      harry.raiseSalary(percent); //该方法调用后,percent的值还是10  
    • 一个方法不可能修改基本数据类型的参数,而对象引用作为参数就不同了。例如,下面:

    • public static void tripleSalary(Employee x){  //将一个员工的工资增至三倍 
              x.raiseSalary(200);   //1.x初始化为harry值的一个副本,这里就是一个对象引用
      }
      当调用
      harry=new Employee(...); //2.raiseSalary方法应用于这个对象引用。x和harry同时引用的那个Employee对象的工资提高了200%
      tripleSalary(harry); //3.方法结束后,参数变量x不再使用。对象变量harry继续引用那个工资增至3倍的员工对象
    • 总结:在Java中对方法参数能做什么和不能做什么

      • 方法不能修改基本数据类型的参数(即数值型或布尔型)

      • 方法可以改变对象参数的状态。(对象状态的改变必须通过调用方法实现)                    

               

    

      

    

                

             

      

    

    

    

        

    

    

          

 

  

 

posted @ 2020-08-06 19:38  菜鸟CM  阅读(183)  评论(0编辑  收藏  举报