Java 对象与类
面向对象
面向对象(Object-oriented programming OOP ),所有程序由对象组成。
类(Class)
类是构造对象的模板,对象是类的实例。
对象(Object)
对象包含三个主要特性
行为(behavior) 对象可以执行的操作
状态(staate) 当执行方法是,对象如何响应
标识(identity) 辨别对象
创建过程
面向对象编程从设计类开始,再向类中添加方法。
类对应这名词,动词为类的方法。
例如订单系统中由名词
- 商品(Item)
- 订单(Order)
- 配送地址(Address)
这些名词会成为类Item,Order等
下一步是查找动词,例如商品加入到订单,订单的确认与取消。
例如商品加入到订单,加入是一个动词,订单对象是要加入的目标,所以add将会是Oder的一个方法,Item将会是add方法的参数
public class Order{
public void add(Item){
}
}
类之间的关系
常用关系
依赖
uses-a聚合
has-a继承
is-a
依赖关系,例如订单类依赖Account类,因为订单对象需要访问Account查看信用状态。Item不依赖Account,因为Item与Account无关。
应该尽可能减少依赖关系,如果A不知道B,B改变就不会影响到A。(低耦合)。
聚合关系,例如一个订单包含多个Item。聚合关系就是A对象包含B对象。
继承,就是面向对象的继承关系,B扩展与A,可以说B继承于A。

预定义类
Java类库预先定义好的类。
对象与变量
例如创建一个Date对象,
Date date = new Date();
执行完这条语句,变量date引用内存中新创建的Date对象。

如果另一个变量也引用新创建Date对象,两个变量会引用同一份对象。
Date date = new Date();
Date date2 = date;

可以显式的将对象变量设置为null表示此变量目前没有引用任何对象。
运行下面的语句会抛出一个java.lang.NullPointerException异常。
date = null;
System.out.println(date.toString());
LocalDate类
Date类实例有一个状态,即特定时间点。内部是距离一个固定时间点的毫秒数,这个点就是所谓的纪元(epoch),即UTC(1970年1月1日00:00:00)
Date的构造函数
public Date() {
this(System.currentTimeMillis());
}
Java中Date用来表示时间点,LocalDate用来表示日历。
将时间与日历分开是一种很好的面向对象设计。
LocalDate对象没有public构造方法,所以要使用静态工厂来创建LocalDate对象。
// 当前日期
LocalDate.now()
// 指定年月日
LocalDate localDate1 = LocalDate.of(2020, 5, 18)
创建完LocalDate对象后,可使用相关方法获取年月日
public static void main(String[] args) {
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2020, 5, 18);
int year = localDate.getYear();//年
int month = localDate.getMonthValue();//月
int day = localDate.getDayOfMonth();//日
System.out.printf("%04d-%02d-%02d", year, month, day);
System.out.println();
// 同等实现
System.out.printf("%1$tY-%<tm-%<td",new Date());
}
更改与访问方法
例如,LocalDate对象加上1000天
LocalDate localDate = LocalDate.now();
LocalDate thousandDaysLater = localDate.plusDays(1000);
// 2023-02-12
System.out.println(thousandDaysLater);
// 2020-05-18
System.out.println(localDate);
调用plusDays方法后,原来的localDate不会有任何方法,此方法返回一个新的LocalDate对象,我们用thousandDaysLater来引用了这个新的对象。
Java较早版本使用GregorianCalendar来处理日期,以下方法来为该类表示的日期增加1000天
public class GregorianCalendarTest {
public static void main(String[] args) {
GregorianCalendar gregorianCalendar = new GregorianCalendar();
System.out.println(gregorianCalendar);
gregorianCalendar.add(Calendar.DATE,1000);
System.out.println(gregorianCalendar);
}
}
输出
java.util.GregorianCalendar[time=1589816171797,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2020,MONTH=4,WEEK_OF_YEAR=21,WEEK_OF_MONTH=4,DAY_OF_MONTH=18,DAY_OF_YEAR=139,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=3,AM_PM=1,HOUR=11,HOUR_OF_DAY=23,MINUTE=36,SECOND=11,MILLISECOND=797,ZONE_OFFSET=28800000,DST_OFFSET=0]
java.util.GregorianCalendar[time=1676216171797,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=29,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2023,MONTH=1,WEEK_OF_YEAR=7,WEEK_OF_MONTH=3,DAY_OF_MONTH=12,DAY_OF_YEAR=43,DAY_OF_WEEK=1,DAY_OF_WEEK_IN_MONTH=2,AM_PM=1,HOUR=11,HOUR_OF_DAY=23,MINUTE=36,SECOND=11,MILLISECOND=797,ZONE_OFFSET=28800000,DST_OFFSET=0]
GregorianCalendar的add方法是一个更改器方法,调用后会修改原来的对象的状态。
GregorianCalendar的get方法访问器方法,用来获取状态值。
int year = gregorianCalendar.get(Calendar.YEAR);
int month = gregorianCalendar.get(Calendar.MONTH);
int day = gregorianCalendar.get(Calendar.DAY_OF_MONTH);
// 2023-01-12
System.out.printf("%04d-%02d-%02d",year,month,day);
LocalDate API
java.time.LocalDate 8 • static LocalTime now() constructs an object that represents the current date. • static LocalTime of(int year, int month, int day) constructs an object that represents the given date. • int getYear() • int getMonthValue() • int getDayOfMonth() get the year, month, and day of this date. • DayOfWeek getDayOfWeek Gets the weekday of this date as an instance of the DayOfWeek class. Call getValue to get a weekday between 1 (Monday) and 7 (Sunday). • LocalDate plusDays(int n) • LocalDate minusDays(int n) Yields the date that is n days after or before this date.
自定义类
Java中最小化定义一个类
class ClassName
{
field1
field2
. . .
constructor1
constructor2
. . .
method1
method2
. . .
}
一个类由字段,构造方法,普通方法组成。
定义Employee类
例如定义一个Employee类
package top.himcs.basic.clazz;
import java.time.LocalDate;
/**
* @author mcs
*/
public class Employee {
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String name, double salary, int year, int month, int dayOfMonth) {
this.name = name;
this.salary = salary;
this.hireDay = LocalDate.of(year, month, dayOfMonth);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public LocalDate getHireDay() {
return hireDay;
}
public void setHireDay(LocalDate hireDay) {
this.hireDay = hireDay;
}
public void raiseSalary(double byPercent) {
double raise = this.salary * byPercent / 100;
this.salary += raise;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", salary=" + salary +
", hireDay=" + hireDay +
'}';
}
public static void main(String[] args) {
Employee[] staff = new Employee[3];
staff[0] = new Employee("Carl Cracker", 75900, 1987, 12, 15);
staff[1] = new Employee("Harry Hacker", 5000, 1989, 12, 15);
staff[2] = new Employee("Tony Tester", 4000, 1990, 12, 15);
for (Employee e : staff) {
e.raiseSalary(5);
}
for (Employee e : staff) {
System.out.println(e);
}
}
}
分析Employee类
方法
这个类包括一个构造器方法和一些get普通方法,使用public关键字标记为任何类的任何方法都可调用这些方法。
public Employee(String name, double salary, int year, int month, int dayOfMonth)
public getXXX()
实例字段
Employee定义了三个private字段,即只有Employee自身的方法才可以访问这些实例域。
private String name;
private double salary;
private LocalDate hireDay;
构造器
构造器是与类名同名的方法,没有返回值.
public Employee()即为构造方法
public class Employee {
public Employee()
{
}
}
如果不设置构造方法,Java会默认一个空的无参构造方法。
构造方法可以设置为private,此时要设置静态构造方法来获取类示例。
构造方法可以重载,即可以有多个构造方法。
隐式参数与显示参数
Java使用this关键字来显示说明使用此类的某个字段或方法.
如果不使用this关键字,并且没有同名的局部变量,此时也会获取类的字段,此时为隐式参数。
为什么要使用get set 方法
通过方法调用而不是将实例域设置为public可以对存取过程进行控制,否则字段的修改将无迹可寻。
不要引用对象
类返回一个引用对象时,如果不使用clone方法,我们对返回对象的修改就是修改类的引用对象,因为类字段和返回值引用了同一个对象。

可以使用clone方法,返回一个对象的副本,此时修改是独立的。
私有方法
使用关键字private即可,私有方法和字段只有类本身的方法才可以调用。
final实例域
将实例域定义为final,构造对象时必须初始化这类域。并且对象构造之后,final域的值不可修改。
private final String name
final大多用于基本类型,将其修饰对象引用类型时的含义是此变量不能再引用其他的对象。
private final StringBuilder evaluations = new StringBuilder();
evaluations不能引用其他的对象,StringBuilder本身还是可修改自身的属性的.
public void giveGoldStarO
{
evaluations.append(LocalDate.now() + ": Gold star!\n");
}
静态域与静态方法
静态域
如果使用static修饰一个字段,这个字段将是基于类的,即每一个对象实例都访问的同一个静态域。
public class Employee {
private static int nextID = 1;
private int id;
public Employee() {
this.id = Employee.nextID++;
);
}
}
静态常量
static+final修饰的变量为静态常量,例如Math中定义的一个静态常量
public final class Math {
public static final double PI = 3.14159265358979323846;
}
静态常量通过类名访问,并且不可修改。
又例如System.out也是一个静态常量
public final class System {
public final static PrintStream out = null;
}
静态常量可以设置成public可以直接通过类名访问。
如果查看一下System 类, 就会发现有一个setOut 方法, 它可以将System.out 设 置为不同的流。读者可能会感到奇怪, 为什么这个方法可以修改final 变量的值。原因在 于, setOut 方法是一个本地方法, 而不是用Java 语言实现的。本地方法可以绕过Java 语 言的存取控制机制。这是一种特殊的方法, 在自己编写程序时, 不应该这样处理。
静态方法
通过类名直接访问的方法。例如Math的pow方法就是一个静态方法
Math.pow(x,a)
调用pow方法不需要实例化Math对象。
静态方法不能访问实例作用域,只能访问自身的静态域
public class Employee{
public static int nextld = 1;
public static int getNextldO
{
return nextld; // returns static field
}
}
可以通过类名直接调用此方法
int n = Employee.getNextld();
也可以通过类实例调用类的静态方法,不过不推荐这么做。
工厂方法
通过工厂方法类构造对象,即类的静态方法用来返回一个类的实例
例如通过LocalDate的now静态方法获取一个LocalDate实例:
LocalDate today = LocalDate.now();
main 方法
public class App{
public static void main(String[] args) {
}
}
当使用java命令行来执行类时,会寻找这个类的static void main(String[] args) 方法并且执行。
java App
方法参数
参数分为两种,
- 值
- 引用
按值调用表示接受的时调用者提供的值,Java中为8种基本类型,
方法会创建一个值的本地副本,即在方法种修改参数值,不会影响调用者提供的值

按引用调用表示接受的时调用者提供的变量地址,函数用变量来接受这个变量地址,表示为引用这个对象,和调用者提供的变量时同一个对象,所以在行书中修改引用类型会影响外部对象的是兼职。

总结:
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
对象构造
重载 Overloading
如果多个方法由相同的名字,不同的参数,便产生了重载。 Java运行重载方法,包括构造器方法。 Java中用参数类型区分方法。
默认域
若没有在构造器中显式的给域服赋予初始值,呢么就会自动给给域赋予默认值:数值为0,布尔值为fasle,对象引用为null。
函数中局部变量必须明确的初始化,类中会默认初始化,但不要这么做,会导致代码可读性低。
无参构造器
如果一个没有编写构造器,系统会提供一个无参构造器,这给构造器将所有实例域设置为默认值。
如果类中至少提供了一个构造器,则系统不会提供默认无参构造器。
显示域初始化
- 在构造器中赋值
- 声明中赋值
通过重载类的构造器,可以为类的实例域设置状态。
也可以在定义域时赋予初始值。
class Employee{
private String name = "";
...
}
初始值不一定是常量,也可调用方法赋值。
class Employee
{
private static int nextld;
private int id = assignldO;
private static int assignldO
{
int r = nextld;
nextld++;
}
}
参数名
参数名可以与域字段同名,但引用域字段要用this
public Employee(String naie, double salary)
{
this.name = name;
this.salary = salary;
}
调用另一个构造器
关键字this代表引用当前对象。还可以使用this(...)的方式调一个构造器。
class Employee{
public Employee(double s)
{
// calls Employee(St ring, double)
this("Employee #" + nextld, s);
nextld++;
}
public Employee(String s, double s)
{
...
}
}
初始化块
Java中无论使用哪个构造器,初始化块都会被执行,
初始化块是类中直接用{}包裹的块。
语法如下
class Employee{
{
System.out.println("HELLO");
}
}
Java中调用构造器的具体步骤:
- 所有数据与初始化为默认值
- 按早类声明中出现的词语,依次执行所有域初始化块语句和初始化块。
- 如果构造器第一行调用了另一个构造器,则执行另一个构造器
- 执行构造器
如果类静态域初始化代码比较复杂,可以使用静态初始化块,即
static{
...
}
类第一次加载时,将会进行静态域的初始化,所有的静态初始化语句及静态初始化块将按定义的顺序依次执行。
类静态语句只会执行一次,且一定在类在实例化之前执行。
Random API
- Random() 构造一个新的随机数生成器
- int nextInt(int n) 返回一个0~n-1之间的随机数
对象析构域finalize方法
Java有自动垃圾回收器,所以Java不支持析构器,可以为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象之前调用,实际中不要依赖finalize回收短缺资源,因为调用时间不可知。
关闭资源可以显式的调用close()方法完成清理操作。
包
包导入
可以使用完整的包+类名使用一个类
java.time.LocalDate localDate = java.time.LocalDate.now();
这样做很麻烦,仅在类名冲突时这样做。
常用的方式时使用import语句,IDEA按Alt+Enter自动导入
import java.time.LocalDate;
导入完成后可以直接使用
LocalDate localDate = LocalDate.now();
可以使用*导入一个包下所有的类,只能是最后一级使用*
import java.time.*
静态导入
import static java.lang.System.*;
导入之后可以直接使用out输出了
out.println("hello");
使用包名
类的第一行使用package ...;语句,这个类就属于这个包了,如果没有使用package这个类就会放在默认包中。
包名要与子目录完全匹配 top.himcs.basic.clazz;在子目录top\himcs\basic\clazz\中
package top.himcs.basic.clazz;
import java.time.LocalDate;
import static java.lang.System.*;
/**
* @author mcs
*/
public class PackageTest {
public static void main(String[] args) {
LocalDate localDate = LocalDate.now();
out.println("hello");
}
}
包作用域
public 所有都可访问
private 只有类本身可访问
默认 只有同包可访问
protected 只有同包及子类可访问
类路径
类可以存储在文件系统子目录中,类路径必须与包名匹配。
类也可以存储在JAR包中。
使用JAR包过程
- 创建
archive.jar - 将jar放到目录中如
/home/usr/jars/ - 将jar包包含到
PATH中/home/usr/jars/archive.jar
虚拟机寻找类过程,首先会查找/jre/lib和/jre/lib/ext下的jar系统类文件,找不到会从类路径开始查找。
设置类路径
通过使用-classpath或-cp指定类路径
也可以通过设置CLASSPATH来完成这个操作
文档注释
使用
/**
*/
定界符的注释可以被javadoc工具生成文档
使用
/**
<em></em>
...HTML标签
<code></code>
{@code 转义<}
*/
类注释
放在类名上方
/**
* @author mcs
*/
public class Card{
}
方法注释
- @param 变量描述
- @return 描述
- ©throws 类描述
通用注释
@author 作者
@version 版本
@since 起始版本
@deprecate 标记过时
@see 引用
