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 引用