java核心技术第十版 卷1 对象和类

本文详细介绍了面向对象编程的基础概念,包括类、对象、封装、继承和多态。强调了对象的三大特性:行为、状态和标识。讨论了类之间的依赖、聚合和继承关系,并提供了Java中LocalDate类和自定义Employee类的实例。同时,讲解了构造器、静态方法、工厂方法、方法参数传递以及包和类路径的管理。此外,还提到了文档注释和类设计的最佳实践。

面向对象程序设计

简称OOP

类(class)是构造对象的模板。由类构造(construct)对象的过程称为创建类的实例(instance)
封装(encapsulation)从形式上是将数据和行为组装在一个包中, 并对对象使用者隐藏数据的实现方法, 对象中的数据称为实例域(instance field), 操纵数据的过程称为方法(method)
可以通过扩展一个类来建立新的类。
java所有的类都源自于一个叫Object 的超类
通过扩展一个类来建立另外一个类的过程称为继承(inheritance)。

对象

需要清楚对象的三个主要特性

  1. 对象的行为(behavior)
  2. 对象的状态(state)
  3. 对象标识(identity)

类之间的关系

  1. 依赖(“uses-a”)
  2. 聚合 (“has-a”)
  3. 继承(‘is-a’)

依赖(dependence)

即“uses-a”关系, 例如Order类使用Account类, 如果一个类操纵另一个类的对象, 我们就说一个类依赖于另一个类(牛头人狂喜)
应尽量让类之间的耦合度最小

聚合 (aggregation)

即“has-a”关系, 聚合关系意味着类A的对象包含类B的对象

继承 (inheritance)

即“is-a”关系, 例如RushOrder类由Order类继承而来, 在具有特殊性的RushOrder类中包含了一些用于优先处理的特殊方法

使用预定义类

要想使用对象, 必须首先构造对象, 并指定其初始状态, 随后对对象应用方法

java中使用构造器(onstructor)构造新实例

new Date()

也可以将对象传递给一个方法

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

也可以将一个方法应用于刚刚创建的对象

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

若希望对象可以重复使用可以将对象存放在一个变量

Date birthday = new Date();

也可以让一个变量引用已存在的对象

Date deadline;
deadline = birthday;

局部变量不会自动初始化为null, 必须通过调用new或将其设置为null

Date birthday;			//java

等同于

Date *birthday;			//C++

java类库中的LocalDate类

Date类的实例有一个状态, 即特定的时间点
这个点即所谓的**纪元(epoch)**为UTC(Coordinated Universal Time)时间的1970-1-1 00:00:00

标准java类库包含了两个类: 一个是用来表示时间点的Date类, 一个是用来表示日历表示法的LocalData类。

不要使用构造器来构造LocalDate类的对象, 应当使用静态工厂方法(factory method)来代表程序员调用构造器

LocalDate.now()

也可以提供年月日来构造一个特定日期的对象

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

有了LocalDate对象后, 可以用方法getYear, getMonthValue和getDayOfMonth得到年, 月, 日

int year = newYearsEve.getYear();
int month = newYearsEve.getMonthValue();
int day = newYearsEve.getDayOfMonth();

plusDays方法会得到一个新的LocalDate

LocalDate aThousandDaysLater = newYearsEve.plusDays(1000);
years = aThousandDaysLater.getYear();
month = aThousandDaysLater.getMonthValue();
day  =aThousandDaysLater.getDayOfMonth();

更改器方法和访问器方法

调用方法后, 对象的state会改变, 这种方法为更改器方法(mutator method)。
相反, 只访问对象而不修改对象的方法有时称之为访问器方法(accessor method).

打印当前月份的日历信息

import java.time.*;
/**
 * @author Cay Horstmann
 */

 public class CalendarTest
 {
     public static void main(String[] args)
     {
         LocalDate date = LocalDate.now();
         int month = date.getMonthValue();
         int today = date.getDayOfMonth();

         date = date.minusDays(today - 1);//set a start of month
         DayOfWeek weekday = date.getDayOfWeek();
         int value = weekday.getValue();
         System.out.println("Mon Tue Wed Thu Fri Sat Sun");
         for(int i = 1; i < value; i++)
            System.out.print("    ");
        while  (date.getMonthValue() == month)
        {
            System.out.printf("%3d", date.getDayOfMonth());
            if(date.getDayOfMonth() == today)
                System.out.print("*");
            else
            System.out.print(" ");
        date = date.plusDays(1);
        if (date.getDayOfWeek().getValue() == 1) System.out.println();
        }
        if (date.getDayOfWeek().getValue() != 1) System.out.println();
     }
 }

API

java.time.LocalDate
static LocalDate now​(Clock clock)
Obtains the current date from the specified clock.
The clock is optional
static LocalDate of​(int year, Type month, int dayOfMonth)
Obtains an instance of LocalDate from a year, month and day.
The Type could be int or Month.
int getYear()
Gets the year field.
int getMonthValue()
Gets the month-of-year field from 1 to 12.
int getDayOfMonth()
Gets the day-of-month field.
DayOfWeek getDayOfWeek()
Gets the day-of-week field, which is an enum DayOfWeek.
int getDayOfYear()
Gets the day-of-year field.
LocalDate plusDays​(long daysToAdd)
Returns a copy of this LocalDate with the specified number of days added.
LocalDate plusMonths​(long monthsToAdd)
Returns a copy of this LocalDate with the specified number of months added.
LocalDate plusWeeks​(long weeksToAdd)
Returns a copy of this LocalDate with the specified number of weeks added.
LocalDate plusYears​(long yearsToAdd)
Returns a copy of this LocalDate with the specified number of years added.
LocalDate minusDays​(long daysToSubtract)
Returns a copy of this LocalDate with the specified number of days subtracted.
LocalDate minusMonths​(long monthsToSubtract)
Returns a copy of this LocalDate with the specified number of months subtracted.
LocalDate minusWeeks​(long weeksToSubtract)
Returns a copy of this LocalDate with the specified number of weeks subtracted.
LocalDate minusYears​(long yearsToSubtract)
Returns a copy of this LocalDate with the specified number of years subtracted.

用户自定义类

java中最简单的类定义形式为

class ClassName
{
	field1;
	field2;
	...
	constructor1;
	constructor2;
	...
	method1;
	method2;
	...
}
import java.time.*;
/**
 * This program tests the Employee class.
 * @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 unformation 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 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数组, 填入了三个雇员对象

多文件的使用

有两种编译源程序的方法

  1. 使用通配符
javac Employee*.java

所有与通配符匹配的源文件都会被编译
2. 隐式编译

javac EmployeeTest.java

会自动对调用自定义类的文件进行最新化编译

构造器与类同名
每个类有一个及以上的构造器
构造器没有返回值
构造器总是伴随new操作一起使用

静态域及静态方法

静态域

每个类中只能有一个静态域

class Employee
{
	private static int nextId = 1;
	private int id;
	...
}
public void setId()
{
	id = nextId;
	nextId++;
}

静态常量

Math类中定义了该静态常量

public class Math 
{
	...
	public static final PI = 3.14159265358979323846;
	...
}

静态方法

静态方法是一种不能对对象实施操作的方法, Math类的pow方法就是一个静态方法。
可以认为静态函数没有隐式参数this

工厂方法

该种静态方法类似LocalDate和NumberFormat的类使用静态工厂方法(factory method)构造对象, 例如LocalDate.now和LocalDate.of
NumberFormat类使用工厂方法生成不同的格式化对象

NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
NumberFormat percentFormatter = NumberFormat.getPercentInstance();
double x = 0.1;
System.out.println(currencyFormatter.format(x));
System.out.println(percentFormatter.format(x));

main方法

不需要使用对象来调用静态方法。

/** 
 * This program demonstrates static methods.
 * @author Cay Horstmann
 */

 public class StaticTest
 {
     public static void main(String[] args)
     {
        //fill the staff array with three Employee objects
        Employee[] staff = new Employee[3];

        staff[0] = new Employee("Tom", 40000);
        staff[1] = new Employee("Dick", 60000);
        staff[2] = new Employee("Harry", 65000);

        //print out information about all Employee objects
        for (Employee e : staff)
        {
            e.setId();
            System.out.println("name=" + e.getName() + ",id=" + e.getId() + ", salary=" + e.getSalary());
        }
        int n = Employee.getNextId();
        System.out.println("Next available id=" + n);
     }
 }

 class Employee
 {
     private static int nextId = 1;
     private String name;
     private double salary;
     private int id;

     public Employee(String n, double s)
     {
         name = n;
         salary = s;
         id = 0;
     }

     public String getName()
     {
         return name;
     }
     public double getSalary()
     {
         return salary;
     }
     public int getId()
     {
         return id;
     }
     public void setId()
     {
        id = nextId;
        nextId++;
     }
     public static int getNextId()
     {
         return nextId;
     }
     public static void main(String[] args)
     {
         Employee e = new Employee("Harry", 50000);
         System.out.println(e.getName() + " " + e.getSalary());
     }
 }

方法参数

按值调用(call by value)表示方法接受的是调用者提供的值
按引用调用(call by reference)表示方法接收的是调用者提供的变量** 地址**
java程序设计语言总是按值调用
将对象引用作为参数则会修改引用的对象的值, 但仍然是按值传递

/**
* This program demostrates parameter passing in Java.
* @author Cay Horstmann
*/
public class ParamTest
{
   public static void main(String[] args)
   {
       /*
       *Test1 : Methods can not modify numeric parameters
       */
       System.out.println("Testing tripleValue:");
       double percent = 10;
       System.out.println("Before: percent=" + percent);
       tripleValue(percent);
       System.out.println("After: percent="+ percent);

       /*
       *Test2 : Methods can change the state of object parameters
       */
       System.out.println("\nTesting tripleSalary:");
       Employee harry = new Employee("Harry", 50000);
       System.out.println("Before: salary=" + harry.getSalary());
       tripleSalary(harry);
       System.out.println("After: salary=" + harry.getSalary());
       /*
       *Test3: Methods can not attach new objects to object parameters
       */
       System.out.println("\nTesting swap:");
       Employee a =new Employee("Alice", 70000);
       Employee b = new Employee("Bob", 60000);
       System.out.println("Before: a=" + a.getName());
       System.out.println("Before: b=" + b.getName());
       swap(a, b);
       System.out.println("After: a=" + a.getName());
       System.out.println("After: b=" + b.getName());
   }
   public static void tripleValue(double x)
   {
       x = 3 * x;
       System.out.println("End of method: x=" + x);
   }

   public static void tripleSalary(Employee x)
   {
       x.raiseSalary(200);
       System.out.println("End of method: salary=" + x.getSalary());
   }
   public static void swap (Employee x, Employee y)
   {
       Employee temp = x;
       x = y;
       y = temp;
       System.out.println("End of method: x=" + x.getName());
       System.out.println("End of method: y=" + y.getName());
   }
}

class Employee
{
   private String name;
   private double salary;

   public Employee(String n, double s)
   {
       name = n;
       salary = s;
   }
   public String getName()
   {
       return name;
   }
   public double getSalary()
   {
       return salary;
   }
   public void raiseSalary(double byPercent)
   {
       double raise = salary * byPercent / 100;
       salary += raise;
   }
}

对象构造

重载

有些类有多个构造器

StringBuilder message = neww StringBuilder();

// 或
StringBuilder todoList = new StringBuilder("To do:\n");

这种特征称为** 重载(overloading) , 挑选执行哪个方法的过程称为重载解析(overloading resolution)**
完整描述一个方法需要指出方法名和参数类型, 即方法签名(signature)。

默认域初始化

最好明确对域进行初始化

无参数的构造器

很多类包含的无参数构造器会将其状态设置为默认值

public Employee()
{
	name = "";
	salary = 0;
	hireDay = LocalDate.now();
}

显式域初始化

在执行构造器之前, 先执行赋值操作, 当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时, 该方法特别有效。

class Employee
{
	private static int nextId;
	private int id = assignId();
	......
	private static int assignId()
	{
	int r = nextId;
	nextId++;
	return r;
	}
	...
}

参数名

参数的命名有如下技巧:

public Employee (String name, double salary)
{
	this.name = name;
	this.salary = salary;
}

调用另一个构造器

如果构造器的第一个语句形如this(…), 这个构造器将调用同一个类的另一个构造器

public Employee(double s)
{
	// call Employee(String, double)
	this("Employee #" + nextId, s);
}

初始化块 (initialization block)

在一个类的声明中可以包含多个代码块, 只要构造类的对象, 这些块就会被执行

class Employee
{
	private static int nextId;

	private int id;
	private String name;
	private double salary;

	//object initialization block
	{
		id = nextId;
		nextId++;
	} 
	public Employee(String n, double s)
	{
		name = n;
		salary = s;
	}
	public Employee()
	{
		name = "";
		salary = 0;
	}
	....
}
import java.util.*;
/**
 * This program demostrates object construction.
 * @author Cay Horstmann
 */

 public class ConstructorTest
 {
     public static void main(String[] args)
     {
         //fill the staff array with three Employee objects
         Employee[] staff = new Employee[3];

         staff[0] = new Employee("Harry", 40000);
         staff[1] = new Employee(60000);
         staff[2] = new Employee();

         //print out information about all Employee objects
         for (Employee e : staff)
            System.out.println("name=" + e.getName() + " ,id=" + e.getId() + " ,salary=" + e.getSalary());
     }
 }

 class Employee
 {
     private static int nextId;

     private int id;
     private String name = "";  //instance field initialization
     private double salary;

     //static initialization block

     static
     {
         Random generator = new Random();
         //set nextId to a random number between 0 and 9999
         nextId = generator.nextInt(10000);
     }

     //object initialization block
     {
         id = nextId;
         nextId++;
     }
     // three overloaded constructors
     public Employee(String n, double s)
     {
         name = n;
         salary = s;
     }
     public Employee(double s)
     {
         // call the Employee(String, double) constructor
         this("Employee #" + nextId, s);
     }
     // the default constructor
     public Employee()
     {
         //name initialized to "" --see above
         //salary not explicitly set --initialized to 0
         //id initialized in initialization block
     }

     public String getName()
     {
         return name;
     }
     public double getSalary()
     {
         return salary;
     }
     public int getId()
     {
         return id;
     }
 }

API

java.util.Random
Random​(long seed)
Creates a new random number generator using a single long seed.
The seed is optional
int nextInt​(int bound)
Returns a pseudorandom, uniformly distributed int value between 0 (inclusive) and the specified value (exclusive), drawn from this random number generator’s sequence.

对象析构与finalize方法

java有自动的垃圾回收器, 所以java不支持析构器
当然, 可以用finalize手动回收资源

包(package)

java允许使用包将类组织起来
Sun建议将公司的因特网域名以逆序形式作为包名
从编译器角度, 嵌套的包没有任何关系。

类的导入

有两种方式访问另一个包的公有类
第一种:

java.time.LocalDate today = java.time.LocalDate.now();

第二种

import java.util.*;
LacalDate today = LocalDate.now();

还可以导入一个包中的特定类

import java.time.LocalDate;

只能使用星号导入一个包
不能使用

import java.*;
import java *.*;

静态导入

import 还可以导入静态方法和静态域

import static java.lang.System.*;
out.println("Goodbye, World!");
exit(0);

还可以导入特定的方法或域

import static java.lang.System.out;

将类放入包中

必须将包的名字放在源文件的开头
如果没有放置package语句, 这个源文件中的类存放在默认包(default package)中
需要将包中的文件放到与包名匹配的子目录中
PackageTest/PackageTest.java

import com.horstmann.corejava.*;
import static java.lang.System.*;

/**
 * This program demonstrates the use of packages.
 * @author Cay Horstmann
 */


public class PackageTest 
{
    public static void main(String[] args)
    {
        //because of the import statement, we do not have to use
        //com.horstmann.corejava.Employee here
        Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);

        harry.raiseSalary(5);

        //because of the static import statement, we do not have to use System.out here
        out.println("name=" + harry.getName() + ", salary=" + harry.getSalary());
    }
    
}

PackageTest/com/horstmann/corejava/Employee.java

package com.horstmann.corejava;

//the class in this file are part of this package
import java.time.*;

/**
 * @author Cay Horstmann 
 */
public class Employee
{

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

    public Employee(String name, double salary, int year, int month, int day)
    {
        this.name = name;
        this.salary = salary;
        this.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;
    }
}

包作用域

标记为public的部分可以被任意类使用, 标记为private的部分只能被定义他们的类使用, 如果没有指定, 该部分可以被同一个包的所有方法访问。
可以通过包密封(package sealing)机制来解决各种包混杂在一起的问题

类路径

类文件也可存储在JAR(java归档)文件中

  1. 把类放入一个目录中
  2. 将JAR文件放在一个目录中
  3. 设置类路径(class path)Windows环境中:
c:\classdir;.;c:\archive\archive.jar

可以使用通配符进行匹配

c:\classdir;.;c:\archive\*

设置类路径

可以采用 -classpath(或-cp)选项指定类路径:

java -classpath c:classdir;.;c:/archives\archive.jar Myprog

可以将该命令行放在一个shell脚本里
也可以通过设置classpath环境变量完成该操作

set CLASSPATH=c:\classdir;.;c:\archives\archive.jar

文档注释

注释的插入

javadoc实用程序(utility)从下面几个特性抽取信息:

  1. 公有类和接口
  2. 公有的和受保护的构造器和方法
  3. 公有的和受保护的域

应该为以上及部分编写注释, 放置在所描述特性的前面
自由格式文档中可以使用html修饰符, 但不能使用 < hl >… < hr >, 若要键入等宽代码需使用{@code…}, 而不是< code >…< /code >, 如此可不必担心对代码中的<字符转义

类注释

类注释必须放在import语句之后, 类定义之前

/**
* A {@code Card} object represents a playing card, such as "Queen of Hearts". A
* card has a suit (Diamond, Heart, Spade or Club) and a value(1 = Ace, 2...10,
* 11 = Jack, 12 = Queen, 13 = King)
*/
public class Card {
   ......
}

方法注释

每一个方法注释前还可以使用如下标记
@param 变量描述
@return 描述
@throws类描述
/**
 * Raise the salary of an employee.
 * @param byPencent the percentage by which to raise the salary(e.g. 10 meaning 10%)
 * @return the amount of the raise
 */
public class test
{
    private double salary;
    
public double raiseSalary(double byPercent)
{

    double raise = salary * byPercent / 100;
    salary += raise;
    return raise;
}
}

域注释

只需要对公有域(通常指静态常量)建立文档

/**
* The "Hearts" card suit
*/
public static final int HEARTS = 1; 

通用注释

下面的标记可用于类文档的注释中
@author 姓名
@version 版本描述
@since 对引入特性的版本描述
@deprecated 对类, 方法, 变量添加一个不再使用的注释, 文本中给出取代建议
@depercated Use < code > setVisible(true) < /code > instead 通过@see和@link标记, 可以使用超链接, 链接到javadoc文档相关部分或外部文档
@see引用 将在“see also“部分增加一个超级链接, 可以用于类中或方法中

必须用#来分隔类名和方法名, 而不是句号
如果@see后面有个<字符, 就需要指定一个超链接

@see <a href="www.horstmann.com/corejava.html">The Core Java home page</a>
@see "Core Java 3 volumn 2"

包与概述注释

产生包注释有两个选择:

  1. package.html命名的文件, 在标记< body > …< /body >之间的的所有文件都会被抽取出来
  2. 提供一个以package-info.java命名的文件, 包含一个初始的Javadoc注释, 不应该包含更多代码或注释

还可以为所有源文件提供一个概述性注释, 放置在overview.html文件中,

注释的抽取

  1. 切换到包含想要生成文档的源文件目录(嵌套的包的话, 则为其父目录)
  2. 如果是一个包执行命令: javadoc -d docDirectory nameOfPackage
  3. 如果是多个包 javadoc -d docDirectory nameOfPackage1 nameOfPackage2…
  4. 若文件在默认包 javadoc -d docDirectory *.java

额外使用-author 和-version 可以强制显示这两个栏

-link可以给标准类添加超链接

javadoc -link http://doc.oracle.com/javase/16/docs/api *.java

所有的标准类库类都会链接到该网站的文档
使用-linksource则每个源文件被转换为html, 每个类和方法名转变为指向源代码的超链接

类设计技巧

  1. 一定要保证数据私有
  2. 一定要对数据初始化
  3. 不要在类中使用过多的基本类型
  4. 不是所有的域都需要独立的域访问器和域更改器
  5. 将职责过多的类分解
  6. 类名和方法名要体现它们的职责
  7. 优先使用不可变的类, 可以安全的在多个线程共享其对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值