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. 优先使用不可变的类, 可以安全的在多个线程共享其对象
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值