Java入门——定义你自己的类(一)

1.一个Employee class

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 getName()
   {
      return name;
   }
   // more methods
   . . .
}

我们将在以下各节中详细介绍该类的实现。 不过,下面是一个展示Employee class在实际应用的程序,

1import java.time.*; 
2  public class EmployeeTest 
3  {
4     public static void main(String[] args)
5    {
6        // fill the staff array with three Employee objects
7        Employee[] staff = new Employee[3];
8
9      staff[0] = new Employee("Carl Cracker", 75000, 1987, 12, 15);
10       staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
11        staff[2] = new Employee("Tony Tester", 40000, 1990, 3, 15);
12
13      // raise everyone's salary by 5%
14       for (Employee e : staff)
15           e.raiseSalary(5);
16 
17        // print out information about all Employee objects
18        for (Employee e : staff)
19           System.out.println("name=" + e.getName() + ",salary=" + e.getSalary() + ",hireDay=" 
20              + e.getHireDay());
21     }
22  }
23 
24 class Employee
25  {
26     private String name;
27     private double salary;
28  private LocalDate hireDay;
29
30     public Employee(String n, double s, int year, int month, int day)
31     {
32       name = n;
33        salary = s;
34        hireDay = LocalDate.of(year, month, day);
35     }
36
37    public String getName()
38    {
39        return name;
40     }
41
42    public double getSalary()
43     {
44        return salary;
45     }
46 
47     public LocalDate getHireDay()
48     {
49        return hireDay;
50     }
51 
52     public void raiseSalary(double byPercent)
53     {
54        double raise = salary * byPercent / 100;
55        salary += raise;
56    }
57  }

在该程序中,我们构造了一个Employee数组并用三个Employee对象填充它:

Employee[] staff = new Employee[3];
staff[0] = new Employee("Carl Cracker", . . .);
staff[1] = new Employee("Harry Hacker", . . .);
staff[2] = new Employee("Tony Tester", . . .);

接下来,我们使用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类和带有公共访问说明符的EmployeeTest类。

我们刚刚描述的指令的main方法包含在EmployeeTest类中。源文件的名称为EmployeeTest.java,因为文件名必须与公共类的名称匹配。 在源文件中只能有一个公共类,但是可以有任意数量的非公共类。

接下来,当您编译此源代码时,编译器会在目录中创建两个类文件:EmployeeTest.class和Employee.class,然后启动 通过为字节码解释器提供包含程序主要方法的类的名称来创建程序:

java EmployeeTest

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

2.使用多个源文件

以上程序在一个源文件中有两个类。 许多程序员喜欢将每个类放入其自己的源文件中。 例如,您可以将Employee类放入文件Employee.java中,并将EmployeeTest类放入EmployeeTest.java中。

如果您喜欢这种安排,则有两种选择来编译程序。您可以使用通配符来调用Java编译器:

javac Employee * .java

然后,所有与通配符匹配的源文件都将被编译为类文件。
或者,您可以简单地键入

EmployeeTest.java

即使从未显式编译Employee.java文件,第二种选择仍然有效,您可能会感到惊讶。 但是,当Java编译器看到EmployeeTest.java中正在使用Employee类时,它将寻找一个带文件名的Employee.class。 如果找不到该文件,它将自动搜索Employee.java并进行编译。 此外,如果找到的Employee.java版本的时间戳记比现有Employee.class文件的时间戳记新,则Java编译器将自动重新编译该文件。

3.剖析Employee类

在以下各节中,我们将剖析Employee类。 让我们从此类中的方法开始。 通过检查源代码可以看到,该类具有一个构造函数和四个方法:

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类本身的方法。 没有外部方法可以读取或写入这些字段.

注意:您可以在实例字段中使用public关键字,但这不是一个好主意。 具有public关键字的字段将允许程序的任何部分读取和修改实例字段,从而完全破坏封装。 任何类的任何方法都可以修改公共字段,并且根据我们的经验,某些代码在您最不期望的时候会利用该访问权限。 我们强烈建议您将所有实例字段都设为私有。

最后,请注意,两个实例字段本身就是对象:
name和hireDayfields是对String和LocalDate对象的引用。

这很常见:类通常包含类类型的实例字段。

4构造函数的第一步

让我们看一下列在我们的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); // January 1, 195

构造函数和其他方法之间存在重要区别。 只能与new运算符一起调用构造函数。 您无法将构造函数应用于现有对象以重置实例字段。 例如,

james.Employee("James Bond", 250000, 1950, 1, 1) // ERRO

现在,请记住以下几点:
•构造函数与类具有相同的名称。
•一个类可以具有多个构造函数。
•构造函数可以接受零个,一个或多个参数。
•构造函数没有返回值 。
•始终使用new运算符调用构造函数。

C++注释:构造函数在java中和C++一样工作。记住,所有java对象都是在堆上构建的,构造函数必须与新的组合。忘记这一点是C++程序员的常见错误Employee number007(“詹姆斯邦德”,100000,1950,1,1);//C++

注意:注意不要引入与实例字段同名的局部变量。 例如,以下构造函数将不会设置薪水:

public Employee(String n,double s,...)
{
String name = n;  //ERROR
double salary = s;  // ERROR
...
}

构造函数声明局部变量名称和工资。 这些变量只能在构造函数内部访问。 它们使实例字段具有相同的名称。不要使用与实例字段名称相同的变量名称。

5.var关键字

从Java 10开始,您可以使用var关键字声明局部变量,而不用指定其类型,只要可以从初始值推断出它们的类型即可。 例如,声明

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

相当于

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

这样做很好,因为它避免了重复使用类型名称Employee。

从现在开始,在那些右侧类型很明显但不知道Java API的情况下,我们将使用var表示法。 一旦您对Java API有了更丰富的经验,您可能希望更频繁地使用var关键字。

请注意,var关键字只能与方法内部的局部变量一起使用。您必须始终声明参数和字段的类型。

6.null值的应用

如果将null值应用于方法,则会发生NullPointerException.

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

这是一个严重的错误,类似于“索引超出范围”例外。 如果您的程序没有“捕获”异常,它将终止。 通常,程序不会捕获这类异常,而是首先要让程序员不要导致异常。定义类时,最好弄清楚哪些字段可以为null。

在我们的示例中,我们不希望name或hiringDay为null。 (我们不必担心薪金字段。它具有原始类型,并且永远不能为null。)

由于使用新的LocalDate对象初始化了hiredDay字段,因此可以保证其不为null。 但是如果使用n的null参数调用构造函数,则名称将为null。

有两种解决方案。 “宽松”的方法是将null参数转换为适当的non-null值:

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

从Java 9开始,Objects类具有用于此目的的便捷方法:

public Employee(String n, double s, int year, int month, int day)
{ 
  name = Objects.requireNonNullElse(n, "unknown");
   . . .
}

另一种方法是拒绝一个null参数:

public Employee(String n, double s, int year, int month, int day)
{ 
  Objects.requireNonNull(n, "The name cannot be null"); 
  name = n; 
  . . .
}

如果有人用空名称构造Employee对象,则会发生aNullPointerException。 乍一看,这似乎不是一种有用的补救方法,但是有两个优点:

1.异常报告中有问题的说明。
2.异常报告指出了问题的位置。 否则,NullPointerException可能会在其他地方发生,没有简单的方法可以将其追溯到错误的构造函数参数。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,492评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,048评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,927评论 0 358
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,293评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,309评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,024评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,638评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,546评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,073评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,188评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,321评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,998评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,678评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,186评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,303评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,663评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,330评论 2 358

推荐阅读更多精彩内容