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,因为ItemAccount无关。

应该尽可能减少依赖关系,如果A不知道B,B改变就不会影响到A。(低耦合)。

聚合关系,例如一个订单包含多个Item。聚合关系就是A对象包含B对象。

继承,就是面向对象的继承关系,B扩展与A,可以说B继承于A。

image-20200518230029036

预定义类

Java类库预先定义好的类。

对象与变量

例如创建一个Date对象,

Date date = new Date();

执行完这条语句,变量date引用内存中新创建的Date对象。

image-20200518230345037

如果另一个变量也引用新创建Date对象,两个变量会引用同一份对象。

        Date date = new Date();
        Date date2 = date;

image-20200518230741095

可以显式的将对象变量设置为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]

GregorianCalendaradd方法是一个更改器方法,调用后会修改原来的对象的状态。

GregorianCalendarget方法访问器方法,用来获取状态值。

        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方法,我们对返回对象的修改就是修改类的引用对象,因为类字段和返回值引用了同一个对象。

image-20200519001401037

可以使用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 语 言的存取控制机制。这是一种特殊的方法, 在自己编写程序时, 不应该这样处理。

静态方法

通过类名直接访问的方法。例如Mathpow方法就是一个静态方法

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();

也可以通过类实例调用类的静态方法,不过不推荐这么做。

工厂方法

通过工厂方法类构造对象,即类的静态方法用来返回一个类的实例

例如通过LocalDatenow静态方法获取一个LocalDate实例:

LocalDate today = LocalDate.now();

main 方法

public class App{
	public static void main(String[] args) {
	}
}

当使用java命令行来执行类时,会寻找这个类的static void main(String[] args) 方法并且执行。

java App

方法参数

参数分为两种,

  1. 引用

按值调用表示接受的时调用者提供的值,Java中为8种基本类型,

方法会创建一个值的本地副本,即在方法种修改参数值,不会影响调用者提供的值

image-20200519004351367

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

image-20200519004445220

总结:

  • 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型)。
  • 一个方法可以改变一个对象参数的状态。
  • 一个方法不能让对象参数引用一个新的对象。

对象构造

重载 Overloading

如果多个方法由相同的名字,不同的参数,便产生了重载。 Java运行重载方法,包括构造器方法。 Java中用参数类型区分方法。

默认域

若没有在构造器中显式的给域服赋予初始值,呢么就会自动给给域赋予默认值:数值为0,布尔值为fasle,对象引用为null。

函数中局部变量必须明确的初始化,类中会默认初始化,但不要这么做,会导致代码可读性低。

无参构造器

如果一个没有编写构造器,系统会提供一个无参构造器,这给构造器将所有实例域设置为默认值。

如果类中至少提供了一个构造器,则系统不会提供默认无参构造器。

显示域初始化

  1. 在构造器中赋值
  2. 声明中赋值

通过重载类的构造器,可以为类的实例域设置状态。

也可以在定义域时赋予初始值。

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中调用构造器的具体步骤:

  1. 所有数据与初始化为默认值
  2. 按早类声明中出现的词语,依次执行所有域初始化块语句和初始化块。
  3. 如果构造器第一行调用了另一个构造器,则执行另一个构造器
  4. 执行构造器

如果类静态域初始化代码比较复杂,可以使用静态初始化块,即

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语句,IDEAAlt+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包过程

  1. 创建archive.jar
  2. 将jar放到目录中如/home/usr/jars/
  3. 将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 引用

Last Updated:
Contributors: himcs