要想对数值型数组进行排序, 可以使用 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(); } } } ?
面向对象程序设计(简称 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 的对象。
继承(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 类库分别包含了两个类: 一个是用来表示时间点的 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 类封装了实例域来维护所设置的日期。
再来看上一节中的 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 就是访问器方法。
在 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
通过查看源代码会发现,这 个类包含一个构造器和 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 的,破坏这个域值的捣乱者有 可能会出没在任何地方。
原文:https://www.cnblogs.com/ma-ice/p/13449550.html