跳至主要內容

Java

LincZero大约 7 分钟

Java

目录

对象和类

简概

OOP简概

  • 面向对象程序设计(简称OOP)
    • 在OOP中,不必关心对象的具体实现,只要能够满足用户的需求即可
  • 传统的结构化程序设计
    • 通过设计一系列的过程(即算法)来求解问题。一旦确定了这些过程,就要开始考虑存储数据的方式。
    • 这就是Pascal语言的设计者Niklaus Wirth将其著作命名为《算法+数据结构=程序》(Algorithms+Data Structures=Programs,Prentice Hall,1975)的原因。
    • 在Wirth命名的书名中,算法是第一位的,数据结构是第二位的,这就明确地表述了程序员的工作方式。
    • 传统的过程化程序设计,必须从顶部的main函数开始编写程序。在面向对象程序设计时没有所谓的“顶部”。对于学习OOP的初学者来说常常会感觉无从下手。答案是:首先从设计类开始,然后再往每个类中添加方法。
  • 选择
    • 对于一些规模较小的问题,将其分解为过程的开发方式比较理想。而面向对象更加适用于解决规模较大的问题。
    • 要想实现一个简单的Web浏览器可能需要大约2000个过程,这些过程可能需要对一组全局数据进行操作。 采用面向对象的设计风格,可能只需要大约100个类,每个类平均包含20个方法 后者更易于程序员掌握,也容易找到bug。假设给定对象的数据出错了,在访问过这个数据项的20个方法中查找错误要比在2000个过程中查找容易得多。

类简概

  • 三个主要特性
    • 对象的行为(behavior)——可以对对象完成哪些操作,或者可以对对象应用哪些方法
    • 对象的状态(state)——当调用那些方法时,对象会如何相应
    • 对象的标识(identity)——如何区分具有相同行为与状态的不同对象
  • 类、构造(construct)、实例(instance)
    • 是构造对象的模板或蓝图。由类构造对象的过程称为创建类的实例
  • 封装(encapsulation,有时称为数据隐藏)
    • 形式上看,封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式
    • 关键在于绝对不能让类中的方法直接地访问其他类的实例域。程序仅通过对象的方法与对象数据进行交互
  • 实例域(instance field)、方法(method)
    • 对象中的数据称为实例域,操纵数据的过程称为方法
  • 状态(state)
    • 对于每个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态
  • 继承(inheritance)
    • 可以通过扩展一个类来建立另外一个新的类。扩展一个已有的类时,这个扩展后的新类具有所扩展的类的全部属性和方法。过程称为继承
  • Object
    • 事实上,在Java中,所有的类都源自于一个“神通广大的超类”,它就是Object。

类之间的关系

  • 依赖(dependence),即“uses-a”关系
    • 应该尽可能地将相互依赖的类减至最少。用软件工程的术语来说,就是让类之间的耦合度最小。
  • 聚合(aggregation),即“has-a”关系
    • 聚合关系意味着类A的对象包含类B的对象
    • 有些方法学家不喜欢聚合这个概念,而更加喜欢使用“关联”这个术语。从建模的角度看,这是可以理解的。但对于程序员来说,“has-a”显得更加形象。
  • 继承(inheritance),即“is-a”关系
    • 一种用于表示特殊与一般关系的
  • UML符号
    • 很多程序员采用UML(Unified Modeling Language,统一建模语言)绘制类图,用来描述类之间的关系。
    • img
      img

类(class)

使用预定义类

并不是所有的类都具有面向对象特征。例如Math类只封装了功能,它不需要也不必隐藏数据。

与C/C++不同:外壳类

  • 用Java编写的所有代码都位于某个类的内部(包括main函数)
  • 而C/C++、Python可以有代码在外面

使用预设类(通过Data类介绍)

定义类

Date birthday = new Date();
String s = new Date().toString();

创建一个新对象

对象不等于对象变量。一个对象变量并没有实际包含一个对象,而仅仅引用一个对象

在对象与对象变量之间存在着一个重要的区别,例如:

Date deadline;			// dones't refer to any object
s = deadline.toString;	// not yet

deadline = new Date();	// 必须首先初始化变量deadline或引用一个已经存在的对象

对象的指针、引用、内存相关

与C/C++不同:指针还是引用?

很多人错误地认为Java对象变量与C++的引用类似。然而,在C++中没有空引用,并且引用不能被赋值。可以将Java的对象变量看作C++的对象指针

Date birthday;	// Java
// 等同于
Date* birthday;	// C++
// 不同于
Date& birthday;	// C++,不可空引用、不可被赋值

与C/C++不同:存储空间

所有的Java对象都存储在堆中。当一个对象包含另一个对象变量时,这个变量依然包含着指向另一个堆对象的指针。

如果使用一个没有初始化的指针,运行系统将会产生一个运行时错误,而不是生成一个随机的结果。同时,不必担心内存管理问题,垃圾收集器将会处理相关的事宜。

自定义类

主力类(workhorse class)

通常,这些类没有main方法,却有自己的实例域和实例方法。要想创建一个完整的程序,应该将若干类组合在一起,其中只有一个类有main方法

定义类

类定义

class ClassName
{
    field1;			// 实例域
    field2;
    constructor1;	// 构造器
    constructor2;
    method1;		// 方法
    method2;
}

例如

class Employee
{
    // instance field
    private String name;
    private double salary;
    private Localdate hireDay;
    
    // constructor
    public Employee(String n, double s, int year, int month, int day)
    {
        name = n;
        salary = s;
        hireDay = LocalDate.of(year, moth, day);
    }
    
    // a method
    public String getName()
    {
        return name;
    }
}

使用

james = new Employee("James Bond", 100000, 1950, 1, 1);

类设计技巧

  • 1.一定要保证数据私有

    • 不要破坏封装性。需要编写一个访问器方法或更改器方法。即使出现bug也易于检测
  • 2.一定要对数据初始化

    • Java不对局部变量进行初始化,但是会对对象的实例域进行初始化。最好不要依赖于系统的默认值
  • 3.不要在类中使用过多的基本类型

    • 例如,用一个称为Address的新的类替换以下的实例域

      private String street;
      private String city;
      private String state;
      private int zip;
      
  • 4.不是所有的域都需要独立的域访问器和域更改器

    • 不想被知道的实例域就不需要域访问器和域更改器
  • 5.将职责过多的类进行分解

    • 设计模式
  • 6.类名和方法名要能够体现它们的职责

  • 7.优先使用不可变的类

    • 例如LocalDate类以及java.time包中的其他类是不可变的——没有方法能修改对象的状态
    • 更改对象的问题在于,如果多个线程试图同时更新一个对象,就会发生并发更改。其结果是不可预料的
    • 当然,并不是所有类都应当是不可变的