面向对象程序设计
简称OOP
类
类(class)是构造对象的模板。由类构造(construct)对象的过程称为创建类的实例(instance)
封装(encapsulation)从形式上是将数据和行为组装在一个包中, 并对对象使用者隐藏数据的实现方法, 对象中的数据称为实例域(instance field), 操纵数据的过程称为方法(method)
可以通过扩展一个类来建立新的类。
java所有的类都源自于一个叫Object 的超类
通过扩展一个类来建立另外一个类的过程称为继承(inheritance)。
对象
需要清楚对象的三个主要特性
- 对象的行为(behavior)
- 对象的状态(state)
- 对象标识(identity)
类之间的关系
- 依赖(“uses-a”)
- 聚合 (“has-a”)
- 继承(‘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数组, 填入了三个雇员对象
多文件的使用
有两种编译源程序的方法
- 使用通配符
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归档)文件中
- 把类放入一个目录中
- 将JAR文件放在一个目录中
- 设置类路径(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)从下面几个特性抽取信息:
- 包
- 公有类和接口
- 公有的和受保护的构造器和方法
- 公有的和受保护的域
应该为以上及部分编写注释, 放置在所描述特性的前面
自由格式文档中可以使用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"
包与概述注释
产生包注释有两个选择:
- package.html命名的文件, 在标记< body > …< /body >之间的的所有文件都会被抽取出来
- 提供一个以package-info.java命名的文件, 包含一个初始的Javadoc注释, 不应该包含更多代码或注释
还可以为所有源文件提供一个概述性注释, 放置在overview.html文件中,
注释的抽取
- 切换到包含想要生成文档的源文件目录(嵌套的包的话, 则为其父目录)
- 如果是一个包执行命令: javadoc -d docDirectory nameOfPackage
- 如果是多个包 javadoc -d docDirectory nameOfPackage1 nameOfPackage2…
- 若文件在默认包 javadoc -d docDirectory *.java
额外使用-author 和-version 可以强制显示这两个栏
-link可以给标准类添加超链接
javadoc -link http://doc.oracle.com/javase/16/docs/api *.java
所有的标准类库类都会链接到该网站的文档
使用-linksource则每个源文件被转换为html, 每个类和方法名转变为指向源代码的超链接
类设计技巧
- 一定要保证数据私有
- 一定要对数据初始化
- 不要在类中使用过多的基本类型
- 不是所有的域都需要独立的域访问器和域更改器
- 将职责过多的类分解
- 类名和方法名要体现它们的职责
- 优先使用不可变的类, 可以安全的在多个线程共享其对象
本文详细介绍了面向对象编程的基础概念,包括类、对象、封装、继承和多态。强调了对象的三大特性:行为、状态和标识。讨论了类之间的依赖、聚合和继承关系,并提供了Java中LocalDate类和自定义Employee类的实例。同时,讲解了构造器、静态方法、工厂方法、方法参数传递以及包和类路径的管理。此外,还提到了文档注释和类设计的最佳实践。
2496

被折叠的 条评论
为什么被折叠?



