首页 > 编程语言 > 详细

2020.8.6Java 核心技术四章前

时间:2020-08-06 23:40:46      阅读:125      评论:0      收藏:0      [点我收藏+]

数组排序

要想对数值型数组进行排序, 可以使用 Arrays类中的 sort 方法:

int[] a = new int[10000];
...
Arrays.sort(a);

 

不规则数组

到目前为止,读者所看到的数组与其他程序设计语言中提供的数组没有多大区别。但实 际存在着一些细微的差异, 而这正是 Java 的优势所在:Java 实际上没有多维数组,只有一维 数组。多维数组被解释为“ 数组的数组。

技术分享图片
/**
 * This program demonstrates a triangular array.
 * @version 1.20 2004-02-10
 * @author Cay Horstmann
 */
public class LotteryArray
{
   public static void main(String[] args)
   {
      final int NMAX = 10;
?
      // allocate triangular array
      int[][] odds = new int[NMAX + 1][];
      for (int n = 0; n <= NMAX; n++)
         odds[n] = new int[n + 1];
?
      // fill triangular array
      for (int n = 0; n < odds.length; n++)
         for (int k = 0; k < odds[n].length; k++)
         {
            /*
             * compute binomial coefficient n*(n-1)*(n-2)*...*(n-k+1)/(1*2*3*...*k)
             */
            int lotteryOdds = 1;
            for (int i = 1; i <= k; i++)
               lotteryOdds = lotteryOdds * (n - i + 1) / i;
?
            odds[n][k] = lotteryOdds;
         }
?
      // print triangular array
      for (int[] row : odds)
      {
         for (int odd : row)
            System.out.printf("%4d", odd);
         System.out.println();
      }
   }
}
?
View Code

 

[注]  要想创建一个不规则的数组, 首先需要分配一个具有所含行数的数组。

 

第四章 对象与类别

技术分享图片

技术分享图片

 

技术分享图片

 

面向对象程序设计概述

面向对象程序设计(简称 OOP) 是当今主流的程序设计范型, 它已经取代了 20 世纪 70 年代的“ 结构化” 过程化程序设计开发技术。Java 是完全面向对象的, 必须熟悉 OOP 才能 够编写 Java 程序。

传统的结构化程序设计通过设计一系列的过程(即算法)来求解问题。 Pascal 语言的设计者 Niklaus Wirth将其著作命 名为《算法 + 数据结构 = 程序》,在 Wirth命名的书名中, 算法是第一位的,数据结构是第二位的,这就明确地表述了程序员的X作方式。 t先要确定如何操作数据, 然后再决定如何组织数 据, 以便于数据操作。 而 OOP 却调换了这个次序, 将数据放在第 •位,然后再考虑操作数 据的算法。

 

类(class) 是构造对象的模板或蓝图。我们可以将类想象成制作小甜饼的切割机,将对 象想象为小甜饼。由类构造(construct) 对象的过程称为创建类的实例 (instance)。

封装(encapsulation, 有时称为数据隐藏)是与对象有关的一个重要概念。从形式上看, 封装不过是将数据和行为组合在一个包中, 并对对象的使用者隐藏了数据的实现方式。对象 中的数据称为实例域( instance field), 操纵数据的过程称为方法(method )。对于每个特定的 类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态(state)。 无论何时,只要向对象发送一个消息,它的状态就有可能发生改变。

在扩展一个已有的类时, 这个扩展后的新类具有所扩展的类的全部属性和方法。在新类 中,只需提供适用于这个新类的新方法和数据域就可以了。通过扩展一个类来建立另外一个 类的过程称为继承(inheritance)。

对象

要想使用 OOP, —定要清楚对象的三个主要特性:

•对象的行为(behavior)— —可以对对象施加哪些操作,或可以对对象施加哪些方法?

•对象的状态(state)— —当施加那些方法时,对象如何响应?

•对象标识(identity)— —如何辨别具有相同行为与状态的不同对象?

对象的状态可能会 随着时间而发生改变,但这种改变不会是自发的。对象状态的改变必须通过调用方法实现 。

(如果不经过方法调用就可以改变对象状态,只能说明封装性遭到了破坏)

识别类

传统的过程化程序设计, 必须从顶部的 main 函数开始编写程序。在面向对象程序设计 时没有所谓的“ 顶部”。对于学习OOP 的初学者来说常常会感觉无从下手。答案是:首先从 设计类开始,然后再往每个类中添加方法。

识别类的简单规则是在分析问题的过程中寻找名词,而方法对应着动词。

 

• 类:class。

• 对象:Object, instance(实例)。以后我们说某个类的对象,某个类的实例。是一样的意思。 • 对象和类的关系:

• 特殊到一般,具体到抽象。

• 类可以看成一类对象的模板,对象可以看成该类的一个具体实例。

• 类是用于描述同一类形的对象的一个抽象的概念,类中定义了这一类对象所应具有的静态和动 态属性。

• JDK提供了很多类供编程人员使用,编程人员也可定义自己的类。

类之间的关系

在类之间, 最常见的关系有

•依赖( “ uses-a”)

•聚合( “ has-a”)

•继承( “ is-a”)

依赖(dependence), 即“ uses-a” 关系, 是一种最明显的、最常见的关系。

应该尽可能地将相互依赖的类减至最少。如果类 A 不知道 B 的存在, 它就不会关心 B 的任何改变(这意味着 B 的改变不会导致 A 产生任何bug)。用软件工程的术语来说,就是 让类之间的耦合度最小。

聚合(aggregation), 即“ has-a” 关系, 是一种具体且易于理解的关系。

聚合关系意味着类 A 的对象包含类 B 的对象。

[注]  有些方法学家不喜欢聚合这个概念,而更加喜欢使用“ 关联” 这个术语。从建模 的角度看, 这是可以理解的。但对于程序员来说, “ has-a” 显得更加形象。喜欢使用聚 合的另一个理由是关联的标准符号不易区分,请参看表 4-1。

技术分享图片

继承(inheritance), 即“ is-a” 关系, 是一种用于表示特殊与一般关系的。

一般而言, 如果类 A 扩展类 B, 类 A 不但包含从类 B 继承的方法,还会 拥有一些额外的功能。

使用预定义类

在程序中,可以使用 Math 类的方法, 如 Math, random, 并只需要知道方法名和参数(如果有的话),而不必了解它的具体实现过程。这正是 封装的关键所在,当然所有类都是这样。但遗憾的是,Math类只封装了功能,它不需要也不 必隐藏数据。由于没有数据,因此也不必担心生成对象以及初始化实例域。

对象与对象变量

在 Java 程序设计语言中, 使用构造器(constructor) 构造新实例。构造器是一种特殊的方法, 用来构造并初始化对象。

构造器的名字应该与类名相同。因此 Date类的构造器名为 Date。要想构造一个 Date 对 象,需要在构造器前面加上 new 操作符,如下所示:

 new Date() 

这个表达式构造了一个新对象。这个对象被初始化为当前的日期和时间。 如果需要的话,也可以将这个对象传递给一个方法:

System.out.printTn(new Date());

或者, 也可以将一个方法应用于刚刚创建的对象。Date 类中有一个 toString方法。这 个方法将返回日期的字符串描述。下面的语句可以说明如何将 toString方法应用于新构造的 Date 对象上。

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

在这两个例子中, 构造的对象仅使用了一次。通常, 希望构造的对象可以多次使用, 因 此,需要将对象存放在一个变量中:

Date birthday = new Date(); 

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

 Date deadline; // deadline doesn‘t refer to any object 

 

定义了一个对象变量 deadline, 它可以引用 Date 类型的对象。但是,一定要认识到: 变量 deadline 不是一个对象, 实际上也没有引用对象。此时,不能将任何 Date 方法应用于这个变 量上。语句

 s = deadline.toString(); // not yet 将产生编译错误。

必须首先初始化变量 deadline, 这里有两个选择。当然,可以用新构造的对象初始化这 个变量: deadline = new Date(); 也让这个变量引用一个已存在的对象:

deadline = birthday; 

现在,这两个变量引用同一个对象

 

Java 类库中的 LocalDate 类

类库设计者决定将保存时间与给时间点命名分开。所以标准 Java 类库分别包含了两个类: 一个是用来表示时间点的 Date 类;另一个是用来表示大家熟悉的日历表示法的 LocalDate 类。

不要使用构造器来构造 LocalDate类的对象。实际上,应当使用静态工厂方法 (factory method) 代表你调用构造器。下面的表达式

LocalDate.now()

会构造一个新对象,表示构造这个对象时的日期。 可以提供年、 月和日来构造对应一个特定日期的对象:

LocalDate.of(1999, 12, 31)

当然,通常都希望将构造的对象保存在一个对象变量中:

LocalDate newYearsEve = LocalDate.of(1999, 12, 31) ;

一旦有了一个 LocalDate 对象, 可以用方法 getYear、getMonthValue 和 getDayOfMonth 得到年、月和日:

int year = newYearsEve.getYearO; // 1999

int month = newYearsEve.getMonthValueO; // 12

int day = newYearsEve.getDayOfMonth() ; // 31

看起来这似乎没有多大的意义, 因为这正是构造对象时使用的那些值。不过,有时可能 某个日期是计算得到的,你希望调用这些方法来得到更多信息。例如, plusDays 方法会得到 一个新的 LocalDate, 如果把应用这个方法的对象称为当前对象,这个新日期对象则是距当 前对象指定天数的一个新日期:

LocalDate aThousandDaysLater = newYearsEve.piusDays(1000) ;

year = aThousandDaysLater.getYear();// 2002

month = aThousandDaysLater.getMonthValue(); // 09

day = aThousandDaysLater.getDayOfMonth(); // 26

LocalDate 类封装了实例域来维护所设置的日期。

[注]  实际上,Date 类还有 getDay、getMonth 以及 getYear 等方法, 然而并不推荐使用 这些方法。当类库设计者意识到某个方法不应该存在时, 就把它标记为不鼓励使用。

更改器方法与访问器方法

再来看上一节中的 plusDays 方法调用:

 LocalDate aThousandDaysLater = newYearsEve.plusDays(1000) ; 

这个调用之后 newYeareEve 会有什么变化? 它会改为 1000 天之后的日期吗? 事实上,并没 有。plusDays方法会生成一个新的 LocalDate 对象,然后把这个新对象赋给 aThousandDaysLater 变量。原来的对象不做任何改动。 我们说plusDays方法没有更改调用这个方法的对象。

Java 库的一个较早版本曾经有另一个类来处理日历,名为 GregorianCalendar。 可以如下 为这个类表示的一个日期增加 1000 天:

CregorianCalendar someDay = new CregorianCalendar(1999, 11, 31) ; 
?
// Odd feature of that class: month numbers go from 0 to 11 
?
someDay.add(Calendar.DAY_0F_M0NTH, 1000) ; 

 

与 LocalDate.plusDays 方法不同,GregorianCalendar.add 方法是一个更改器方法 ( mutator method ) 调用这个方法后,someDay 对象的状态会改变。可以如下査看新状态:

year = someDay.get(Calendar.YEAR) ; // 2002 
?
month = someDay.get(Calendar.MONTH)+ 1;//09 
?
day = someDay.get(Ca1endar.DAY_0F_M0NTH) ; //26 

 

正是因为这个原因,我们将变量命名为someDay 而不是 newYearsEve 调用这个更改 器方法之后,它不再是新年前夜。 相 反, 只 访 问 对 象 而 不 修 改 对 象 的 方 法 有 时 称 为 访 问 器 方 法 例 如, LocalDate.getYear 和 GregorianCalendar.get 就是访问器方法。

用户自定义类

Employee 类

在 Java 中, 最简单的类定义形式为:

class ClassName { 
?
field1 
?
field2
?
...
?
constructor1
?
 constructor2
?
\...
?
method1
?
method2
?
}

 

下面看一个非常简单的 Employee 类。在编写薪金管理系统时可能会用到。

class Employee { 
?
// instance fields 
?
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, month, day);
?
}
?
// a method
?
public String getNameO {
?
 return name;
?
}
?
// more methods
?
...
?
}

这里将这个类的实现细节分成以下几个部分, 并分別在稍后的几节中给予介绍:

import java.util.*;
?
/**
 * This program tests the Employee class.
 * @version 1.11 2004-02-19
 * @author Cay Horstmann
 */
public class EmployeeTest
{
   public static void main(String[] args)
   {
      // fill the staff array with three Employee objects
      Employee[] staff = new Employee[3];
?
      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);
?
      // raise everyone‘s salary by 5%
      for (Employee e : staff)
         e.raiseSalary(5);
?
      // print out information about all Employee objects
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary() + ",hireDay="
               + e.getHireDay());
   }
}
?
class Employee
{
   private String name;
   private double salary;
   private Date hireDay;
?
   public Employee(String n, double s, int year, int month, int day)
   {
      name = n;
      salary = s;
      GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
      // GregorianCalendar uses 0 for January
      hireDay = calendar.getTime();
   }
?
   public String getName()
   {
      return name;
   }
?
   public double getSalary()
   {
      return salary;
   }
?
   public Date getHireDay()
   {
      return hireDay;
   }
?
   public void raiseSalary(double byPercent)
   {
      double raise = salary * byPercent / 100;
      salary += raise;
   }
}

?

多个源文件的使用

Employee.一个源文件包含了两个类。许多程序员习惯于将每一个类存在一个 单独的源文件中。例如,将 Employee类存放在文件 Employee.java中, 将 EmployeeTest 类存 放在文件 EmployeeTest.java 中。 如果喜欢这样组织文件, 将可以有两种编译源程序的方法。一种是使用通配符调用 Java编译器:

javac Employee*.java 

于是,所有与通配符匹配的源文件都将被编译成类文件。或者键人下列命令:

 javac EmployeeTest.java 

剖析 Employee 类

通过查看源代码会发现,这 个类包含一个构造器和 4 个方法:

public Employee(String n, double s, int year, int month, int day)
?
 public String getName() 
?
public double getSalary()
?
 public LocalDate getHireDay() 
?
public void raiseSalary(double byPercent) 

这个类的所有方法都被标记为 public。关键字 public 意味着任何类的任何方法都可以调用这 些方法。

接下来,需要注意在 Employee 类的实例中有三个实例域用来存放将要操作的数据:

private String name;
?
 private double salary;
?
 private LocalDate hireDay; 

 

关键字 private 确保只有 Employee类自身的方法能够访问这些实例域, 而其他类的方法不能 够读写这些域。

最后, 请注意, 有两个实例域本身就是对象: name 域是 String类对象, hireDay 域是 LocalDate 类对象。这种情形十分常见:类通常包括类型属于某个类类型的实例域。

从构造器开始

下面先看看 Employee 类的构造器:

 public Employee(String n, double s, int year, int month, int day)
   {
      name = n;
      salary = s;
      GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
      // GregorianCalendar uses 0 for January
      hireDay = calendar.getTime();
   }

隐式参数与显式参数

方法用于操作对象以及存取它们的实例域。例如,方法:

public void raiseSalary(double byPercent) { 
?
double raise = salary * byPercent / 100; 
?
salary += raise;
?
 } 

 

将调用这个方法的对象的 salary 实例域设置为新值。看看下面这个调用:

 number007.raiseSalary(5); 

 

它的结果将 number007.salary 域的值增加 5%。具体地说,这个调用将执行下列指令:

double raise = nuaber007.salary * 5 / 100; 
?
nuiber007.salary += raise; 

 

raiseSalary方法有两个参数。 第一个参数称为隐式( implicit) 参数, 是出现在方法名前的 Employee 类对象。第

二个参数位于方法名后面括号中的数值,这是一个显式( explicit) 参 数

在每一个方法中, 关键字 this 表示隐式参数。 如果需要的话,可以用下列方式编写 raiseSalary 方法:

public void raiseSalary(double byPercent) { 
?
double raise = salary * byPercent / 100; 
?
salary += raise;
?
 } 

 

有些程序员更偏爱这样的风格,因为这样可以将实例域与局部变量明显地区分开来。

封装的优点

最后,再仔细地看一下非常简单的 getName方法、getSalary 方法和 getHireDay 方法。

public String getName(){
?
return name;
?
}
?
public double getSalary() {
?
 return salary;
?
 }
public LocalDate getHireDay() {
?
return hireDay; 
?
}

 

这些都是典型的访问器方法。由于它们只返回实例域值, 因此又称为域访问器。

关键在于 name 是一个只读域。一旦在构造器中设置完毕,就没有任何一个办法可以对 它进行修改,这样来确保 name 域不会受到外界的破坏。 虽然 salary 不是只读域,但是它只能用 raiseSalary 方法修改。

特别是一旦这个域值出现 了错误, 只要调试这个方法就可以了。如果 salary 域是 public 的,破坏这个域值的捣乱者有 可能会出没在任何地方。

2020.8.6Java 核心技术四章前

原文:https://www.cnblogs.com/ma-ice/p/13449550.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!