前言

  之前的例子中,我们已经编写了一些简单的类。但是,那些类都只包含一个简单的main方法。现在来学习如何编写复杂应用程序所需要的那种主力类。通常这些类没有main方法,却有自己的实例字段和实例方法。要想构建一个完整的程序,会结合使用多个类,其中只有一个类有main方法。

自定义简单的类

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

class ClassName {
// 字段
field1
field2
...
// 构造方法
constructor1
constructor2
...
// 普通方法
method1
method2
...
}

  接下来将上面的伪代码填充完整

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

  上面就是我们定义的一个普通的类,分为3个部分,变量 + 构造器 + 方法,下面我们编写一个完整的程序,最后输出员工的名字、薪水和出生日期

文件:EmployeeTest/EmployeeTest.java

import java.time.LocalDate;

public class EmployeeTest {
public static void main(String[] args) {
Employee[] staff = new Employee[3]; staff[0] = new Employee("jkc1", 75000, 1987, 12, 15);
staff[1] = new Employee("jkc2", 50000, 1987, 10, 1);
staff[2] = new Employee("jkc3", 40000, 1990, 3, 15); for (Employee e: staff) {
e.raiseSalary(5);
} 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 LocalDate hireDay; public Employee(String n, double s, int year, int month, int day) {
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
} public String getName() {
return name;
} public double getSalary() {
return salary;
} public LocalDate getHireDay() {
return hireDay;
} public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
}

  在这个程序中,我们构造了一个Employee数组,并填入了3个Employee对象:

Employee[] staff = new Employee[3];

staff[0] = new Employee("jkc1", 75000, 1987, 12, 15);
staff[1] = new Employee("jkc2", 50000, 1987, 10, 1);
staff[2] = new Employee("jkc3", 40000, 1990, 3, 15);

  接下里,使用Employee类的raiseSalary方法将每个员工的薪水提高5%:

for (Employee e: staff) {
e.raiseSalary(5);
}

  最后调用getName方法、getSalary方法和getHireDay方法打印各个员工的信息:

for (Employee e: staff) {
System.out.println("name=" + e.getName() + ", salary=" + e.getSalary() + ", hireDay=" + e.getHireDay());
}

  注意,在这个示例程序中包含两个类:Employee类和带有public访问修饰符的EmployeeTest类。EmployeeTest类包含了main方法,其中使用了前面介绍的指令。

  源文件名是EmployeeTest.java,这是因为文件名必须与public类的名字相匹配。在一个源文件中,只能有一个公共类,但可以有任意数目的非公共类。

  接下来,当编译这段源代码的时候,编译器将在目录下创建两个类文件:EmployeeTest.classEmployee.class

  将程序中包含main方法的类名提供给字节码解释器,以启动这个程序:

java EmployeeTest

  字节码解释器开始运行EmployeeTest类的main方法中的代码。在这段代码中,先后构造了3个新的Employee对象,并显示它们的状态。

多个源文件的使用

  上面那个程序包含了两个类。我们通常习惯于将每一个类存放在一个单独的源文件中。例如:将Employee类存放在文件Employee.java中,将EmployeeTest类存放在文件EmployeeTest.java中。

  如果喜欢这样组织文件,可以有两种编译源程序的方法。一种是使用通配符调用Java编译器:

javac Employee*.java

  这样一来,所有与通配符匹配的源文件都将被编译成类文件。或者写以下命令:

javac EmployeeTest.java

  虽然我们第二种方式并没有显示地编译Employee.java,但当Java编译器发现EmployeeTest.java使用了Employee类时,它会查找名为Employee.class的文件。如果没有找到这个文件,就会自动搜索Employee.java,然后对它进行编译。更重要的是:如果Employee.java版本较已有的Employee.class文件版本更新,Java编译器就会自动地重新编译这个文件。

剖析Employee类

  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类的实例中有3个实例字段用来存放将要操作的数据:

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

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

构造器解析

  我们先看看Employee类的构造器:

public Employee(String n, double s, int year, int month, int day) {
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
}

  可以看到,构造器与类同名。在构造Employee类的对象时,构造器会运行,从而将实例字段初始化为所希望的初始状态。

  例如,当使用下面这条代码创建Employee类的实例时:

new Employee("James Bond", 100000, 1950, 1, 1)

  将会把实例字段设置为:

name = "James Bond";
salary = 100000;
hireDay = LocalDate.of(1950, 1, 1);

  构造器与其他方法有一个重要的不同。构造器总是结合new运算符来调用。不能对一个已存在的对象调用构造器来达到重新设置实例字段的目的。例如:

james.Employee("James Bond", 280000, 1950, 1, 1)

将产生编译错误

 

构造器注意点

  • 构造器与类必须同名
  • 每个类可以有一个以上的构造器。
  • 构造器可以有0个、1个或多个参数。
  • 构造器没有返回值。
  • 构造器总是伴随着new操作符一起调用。

用var变量声明局部变量

  在Java10中,如果可以从变量的初始值推导出它们的类型,那么可以用var关键字声明局部变量,而无须指定类型。例如,可以不这样声明:

Employee harry = new Employee("jkc", 50000, 1989, 10, 1);

  只需写以下代码:

var harry = new Employee("jkc", 50000, 1989, 10, 1);

  这一点很好,因为可以避免重复写类型名Employee

使用null引用

  我们之前了解到一个对象变量包含一个对象的引用,或者包含一个特殊值null,后者表示没有引用任何对象。

  听上去这是一种处理特殊情况的便捷机制,如未知的名字或雇用日期。不过使用null值时要非常小心。

  如果对null值应用一个方法,会产生一个NullPointerException异常。

LocalDate birthday = null;
String s = birthday.toString(); // NullPointerExcetion

  这是一个很严重的错误,类似于索引越界异常。如果你的程序没有"捕获"异常,程序就会终止。正常情况下,程序并不捕获这些异常,而是依赖于我们从一开始就不要带来异常。

  定义一个类时,最好清楚地知道哪些字段可能为null。在我们例子中,我们不希望namehireDay字段为null。(不用担心salary字段。这个字段是基本类型,所以不可能是null)。

  hireDay字段肯定是非null的,因为它初始化一个新的LocalDate对象。但是name可能为null,如果调用构造器时为n提供的实参是null,name就会是null.

  对此有两种解决办法。"宽容型"办法是把null参数转换为一个适当的非null值

if (n == null) name = "unknown"; else name = n;

隐式参数与显式参数

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

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

  上面的方式是调用这个方法的对象的sarlary实例字段设置为一个新值。现在我们考虑下面这个调用:

number001.raiseSsalary(5);

  它的结果是将number001.salary字段的值增加5%。具体的说,这个调用将执行下列指定。

double raise = number001.salary * 5 / 100;
number001.salary += raise;

  raiseSalary方法有两个参数。第一个参数称为隐式参数,是出现在方法名前的Employee类型的对象。第二个参数是位于方法名后面括号中的数值,这是一个显式参数。(有人把隐式参数称为方法调用的目标或者接受者)

  可以看到,显式参数显式地列在方法声明中,例如double byPercent。隐式参数没有出现在方法声明中。

在每一个方法中,关键字this指示隐式参数。我们可以改写raiseSalary方法:

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

封装的优点

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

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

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

  如果将namesalaryhireDay字段标记为公共,而不是编写单独的访问器方法,难道不是更容易一些吗?

  不过,name是一个只读字段。一旦在构造器中设置,就没有任何办法可以对它进行修改,这样我们可以确保name字段不受外界的破坏。

  虽然salary不是只读字段,但是它只能用raiseSalary方法修改。特别是一旦这个值出现了错误,只需要调试这个方法就可以了。如果salary字段是公共的,破坏这个字段值的捣乱者有可能会出没在任何地方。

  有些时候,可能想要获得或设置实例字段的值。那么你需要提供下面三项内容:

  • 一个私有的数据字段;
  • 一个公共的字段访问器方法;
  • 一个公共的字段更改器方法。

  这样做比提供一个简单的公共数据字段复杂些,但却有着下列明显的好处:

  首先,可以改变内部实现,而除了该类的方法之外,这不会影响其他代码。例如,如果将存储名字的字段改为:

String firstName;
String lastName;

  那么getName方法可以改为返回

firstName + " " + lastName

  这个改变对于程序的其他部分是完全不可见的。

  当然,为了进行新旧数据表示之间的转换,访问器方法和更改器方法可能需要做许多工作。但是,这将为我们带来第二点好处:更改器方法可以完成错误检查,而只对字段赋值的代码可能没有这个麻烦。例如,setSalary方法可以检查工资是否小于0。

注意:不要编写返回可变对象引用的访问器方法,如果你需要返回一个可变对象的引用,那么应该对它进行克隆。

基于类的访问权限

  从前面已经知道,方法可以访问调用这个方法的对象的私有数据。一个方法可以访问所属类的所有对象的私有数据,这令很多人感到奇怪!例如,下面看一下用来比较两个员工的equals方法。

class Employee{
...
public boolean equals(Employee other) {
return name.euqals(other.name)
}
}

  典型的调用方式是

if (harry.euqals(boss))...

  这个方法访问harry的私有字段,这点并不会让人奇怪,不过, 它还访问了boss的私有字段。这是合法的,其原因是bossEmployee类型的对象,而Employee类的方法可以访问任何Employee类型对象的私有字段。

私有方法

  在实现一个类时,由于公共数据非常危险,所以应该将所有的数据字段都设置为私有的。然而,方法又应该如何设计呢?尽管绝大多数方法都被设计为公共的,但在某些特殊情况下,将方法设计为私有可能很有用。有时,你可能希望将一个计算代码分解成若干个独立的辅助方法,通常,这些辅助方法不应该成为公共接口的一部分,这是由于它们往往与当前实现关系非常紧密,或者需要一个特殊协议或者调用次序。最好将这样的方法设计为私有方法。

 

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

  通常将方法设计为私有,如果你改变了方法的实现方式,将没有义务保证这个方法依然可用。如果数据的表示发生了变化,这个方法可能会变得难以实现,或者不再需要;这并不重要。重点在于,只要方法是私有的,类的设计者就可以确信它不会在别处使用,所以可以将其山区。如果一个方法是公共的,就不能简单地将其删除,因为可能会有其他代码依赖这个方法。

final实例字段

  可以将实例字段定义为final。这样的自动断必须在构造对象时初始化。也就是说,必须确保在每一个构造器执行之后,这个字段的值已经设置,并且以后不能再修改这个字段。例如,可以将Employee类中的name字段声明为final,因此在对象构造之后,这个值不会再改变,即没有setName方法。

class Employee {
private final String name;
}

  final修饰符对于类型为基本类型或者不可变类的字段尤其有用(如果类中的所有方法都不会改变其对象,这样的类就是不可变的类。例如,String类就是不可变的)

  对于可变的类,使用final修饰符可能会造成混乱。例如,考虑以下字段:

private final StringBuilder evaluations;

  它在Employee构造器中初始化为

evaluations = new StringBuilder();

  final关键字只是表示存储在evaluations变量中的对象引用不会再指示另一个不同的StringBuilder对象。不过这个对象可以更改:

public void giveGoldStar() {
evaluations.append(LocalDate.now() + ":Gold star!\n")
}

零基础学Java(11)自定义类的更多相关文章

  1. 零基础学Java第四节(字符串相关类)

    本篇文章是<零基础学Java>专栏的第四篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! String 本文章首发于公众号[编程攻略] 在Java中,我们经 ...

  2. 零基础学Java第三节(基本输入输出)

    本篇文章是<零基础学Java>专栏的第三篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! 本文章首发于公众号[编程攻略] Java程序的命令行参数 我们可以 ...

  3. 零基础学Java第五节(面向对象一)

    本篇文章是<零基础学Java>专栏的第五篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! 本文章首发于公众号[编程攻略] 类与对象 在哲学体系中,可以分为主 ...

  4. 零基础学Java第二节(运算符、输入、选择流程控制)

    本篇文章是<零基础学Java>专栏的第二篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! 第一章 运算符 1.1 算术运算符的概述和用法 运算符 对常量和变 ...

  5. 零基础学Java第一节(语法格式、数据类型)

    本篇文章是<零基础学Java>专栏的第一篇文章,从本篇文章开始,将会连更本专栏,带领大家将Java基础知识彻底学懂,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! ...

  6. 零基础学Java第六节(面向对象二)

    本篇文章是<零基础学Java>专栏的第六篇文章,文章采用通俗易懂的文字.图示及代码实战,从零基础开始带大家走上高薪之路! 本文章首发于公众号[编程攻略] 继承 创建一个Person类 我们 ...

  7. 零基础学Java,PayPal技术专家手把手带你入门

    在最权威的 TIOBE 编程语言排名榜单上,Java 常年稳居第一,可以说是世界上应用最为广泛的一门语言. 同时,在微服务.云计算.大数据.Android App 开发等领域,Java 也是当之无愧的 ...

  8. 零基础学Java(1)初识Java程序

    前言 就国内来说,Java毫无疑问是后端语言中的No.1没有之一,所以今天我们也来0基础学习Java!!! Java的好处(针对测试工程师) 面试加分->涨薪 大多数公司服务端用的都是Java, ...

  9. [零基础学JAVA]Java SE基础部分-01. Java发展及JDK配置

    转自:http://redking.blog.51cto.com/27212/114976 重点要会以下两个方面: 1. 抽象类与接口 2. API==>类集 这是两个最重要部分,这两个部分理解 ...

随机推荐

  1. (数据科学学习手札136)Python中基于joblib实现极简并行计算加速

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 我们在日常使用Python进行各种数据计算 ...

  2. 使用RoslynSyntaxTool工具互相转换C#代码与语法树代码

    ​ 项目地址 MatoApps/RoslynSyntaxTool: 此工具能将C#代码,转换成使用语法工厂构造器(SyntaxFactory)生成等效语法树代码 (github.com) 基础概念 S ...

  3. 521. Longest Uncommon Subsequence I - LeetCode

    Question 521. Longest Uncommon Subsequence I Solution 题目大意:给两个字符串,找出非共同子串的最大长度 思路:字符串相等就返回-1,不等就返回长度 ...

  4. 482. License Key Formatting - LeetCode

    Question 482. License Key Formatting Solution 思路:字符串转化为char数组,从后遍历,如果是大写字母就转化为小写字母,如果是-就忽略,如果遍历了k个字符 ...

  5. 我使用Spring AOP实现了用户操作日志功能

    我使用Spring AOP实现了用户操作日志功能 今天答辩完了,复盘了一下系统,发现还是有一些东西值得拿出来和大家分享一下. 需求分析 系统需要对用户的操作进行记录,方便未来溯源 首先想到的就是在每个 ...

  6. Mac下iTerm2安装rzsz后上传下载失败解决

    背景描述 mac环境,安装了iTerm2,需要使用ssh登陆linux服务器.服务器登陆需要经过以下步骤 输入token 输入登陆选项 输入IP 因此写了expect脚本来完成自动输入 但是在上传下载 ...

  7. BI 如何让SaaS产品具有 “安全感”和“敏锐感”(上)

    SaaS模式一经推出,凭借自身的高性价比.低维护成本,无需软硬件维护.无需运维等明晃晃的优点,得到了爆发式的增长,甚至全面改变了软件的开发模式.各位老总的问候语,不知从什么时候开始,都变成了:&quo ...

  8. 2021.10.19 CSP 模拟赛 总结

    T1 题意: \(n\) 个人摘苹果,跳起高度为 \(a_i\),苹果高度为 \(h_i\),高度小的先摘,摘了就没了 直接排序+双指针,复杂度 \(O(n+m)\) T2 题意:要轰炸一个有向图的所 ...

  9. 【Java面试】介绍下Spring IoC的工作流程

    Hi,我是Mic 一个工作了4年的粉丝,在面试的时候遇到一个这样的问题. "介绍一下Spring IOC的工作流程" 他说回答得不是很好,希望我能帮他梳理一下. 关于这个问题,我们 ...

  10. 我给航母做3D还原:这三处细节,太震撼了…

    前两天,我国第三艘航母正式下水,受到国际舆论高度关注.国产福建舰火出了圈,"航母"从军事专业领域,也火到了普通人的视野中. 图源网络 人们一边感叹我国实力强劲,一边对"航 ...