4.1.1类

类是构造对象的模板,由类构造对象的过程称为创建类的实例

封装即将数据和行为组合在一个包里,并对数据使用者隐藏具体实现方法,对象中的数据称为实例字段,操作数据的过程称为方法。

实现封装的关键在于,绝对不能让类中的方法直接访问其他类的实例字段,程序只能通过对象的方法与对象数据进行交互

 

4.1.2对象

三特性:对象的行为、状态、标识

对象状态的改变必须通过调用方法来实现,,对象的状态影响他的行为(一个空订单不应该送货)

 

4.1.4类之间的关系

常见关系有:依赖(uses-a)、聚合(has-a)、继承(is-a)

如果一个类的方法使用或操纵另一个类的对象,即一个类依赖另一个类。应该尽可能减少相互以来的类,即减少类之间的耦合

聚合关系意味着,类A的对象包含类B的对象,例如一个订单对象包含一些商品对象

继承关系意味着,类A不但包含从类B继承的方法,还有一些额外的功能。例如一个加急订单包含较普通订单更优先处理方法,也有与普通订单不同的费用计算方法

 ————————————————————————————————————————————————————————————————————————————

 4.2.1对象与对象变量

Date birthday=new Date();
Date deadline=birthday;
现在这两个变量都引用同一个对象
 
4.2.2Java中的LocalDate类
 
LocalDate.now();//构造这个对象时的日期
LocalDate Eve=LocalDate.of(1999,12,31);//构造对象保存在一个变量中
int year=Eve.getYear();
int month=Eve.getMonth();
int day=Eve.getDay();
LocalDate dayLater=Eve.plusDays(1000);//eve之后的第1000天
 
调用plusDays方法没有改变dayLater的值,该方法生成了一个新的LocalDate对象
而如果某个方法的调用会改变调用对象的值则是更改器方法 ,否则是访问器方法
程序示例:CalendarTest.java
import java.time.*;
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);//当前日期-(当前日期-1)即设置为本月1号
        DayOfWeek weekday=date.getDayOfWeek();
        int value=weekday.getValue();//1=Monday,2=Tuesday...7=Sunday

        System.out.println("Mon Tue Wed Thu Fri Sat Sun");
        for(int i=1;i<value;i++)
        {
            System.out.println("        ");
        }
        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();
    }
}
 ————————————————————————————————————————————————————————————————————
4.3用户自定义类
4.3.1Employee类
程序示例:EmployeeTest.java
import java.time.*;
public class EmployeeTest {
    public static void main(String[] args)
    {
        Employee[] staff = new Employee[3];
        staff[0] = new Employee("s1",30000,1988,8,8);
        staff[1] = new Employee("s2",20000,1993,3,5);
        staff[2] = new Employee("x1",15000,2001,1,16);
        for(Employee e:staff)
            e.raiseSalary(5);
        for(Employee e:staff)
            System.out.println("name="+e.getName()+",salary="+e.getSalary()+",hireDay="+e.getHireDay());
    }
}
class Employee{
    private String name;//private确保只有Employee类自身的方法能够访问这些实例字段
    private double salary;
    private LocalDate hireDay;
    public Employee(String n,double s,int year,int month,int day){
        this.name=n;
        this.salary=s;
        this.hireDay=LocalDate.of(year,month,day);

    }
    public String getName()
    {
        return this.name;
    }
    public double getSalary()
    {
        return this.salary;
    }
    public LocalDate getHireDay()
    {
        return (Date) this.hireDay.clone();//如果需要返回一个可变对象的引用,首先应该对他进行克隆
    }
    public void raiseSalary(double precent)
    {
        double raise=this.salary*precent/100;
        this.salary+=raise;
    }
}
此示例程序包含两个类Employ类和带有public修饰符的EmployeeTest类,其中包含了main方法
源文件名是EmployeeTest.java 文件名必须与public类的名字匹配
在一个文件中只能有一个公共类,可以有任意数目的非公共类
 
当编译这段源代码的时候,编译器将在目录中创建两个类文件EmployTest.class和Employ.class
将程序中包含main方法的类名提供给字节码解释器,以此启动这个程序
java EmployTest
 
4.3.4构造器
构造器与类同名
每个类可以有一个以上的构造器
构造器可以有0个、1个或多个参数
构造器没有返回值
构造器伴随new操作符一起被调用
 
4.3.5用var声明局部变量 java10
Employee s1=new Employee(......);
替换为
var s1=new Employee(.......);
var关键字只能用于方法中的局部变量
 
4.3.6使用null
对null值引用方法 将会产生异常
java9中提供捕获null的方法
name = Obejects.requireNonNullElse(n,"unknown")//如果给name赋值null 则替换为unknown
 
Objects.requireNonNull(n,"The name cannot be null");//给name赋值时拒绝null
name=n;
 
4.3.7隐式参数和显示参数
raiseSalary方法有两个参数,第一个为隐式参数,即方法名前的Employee对象,第二个参数为显式参数,即括号中的数值
关键字this指示隐式参数
 
4.3.9基于类的访问权限
方法可以访问调用这个方法的对象的私有数据,一个方法还可以访问所属类的所有对象私有数据
例子:
class Employee
{
  public boolean equal(Employee other)
  {
    return name.equals(other.name);
  }
}
典型调用是 if(s1.equal(boss))
这个语句访问了s1的私有字段 并不奇怪,但他同时也访问了boss的私有字段,原因在于boss也是Employee类型对象
 
4.3.11 final关键字
带有final关键字的字段 必须在构造对象时初始化,并且以后不能修改该字段
——————————————————————————————————————————————————
4.4静态字段与静态方法
4.4.1静态字段
如果将一个字段定义为static,每个类只有一个这样的字段。而对于非静态的实例字段,每个对象都有一个自己的副本
例子:
class Employee
{
  private static int nextID=1;
  private int ID;
}
现在每一个Employee对象都有一个自己的ID字段,但这个类的所有实例都将共享一个nextID字段
静态字段属于类,不属于任何单个的对象 ,即 Employee.nextID
 
4.4.2静态常量
例子:
public class Math
{
  public static final double PI=3.141592653589
}
在程序中使用Math.PI来访问这个常量,如果省略掉static关键字,PI就需要通过Math的对象来访问PI,并且每一个Math对象都有它自己的一个PI副本
 
4.4.3静态方法
静态方法是不在对象上执行的方法,例如Math类的pow方法
静态方法没有隐式参数,可以访问静态字段,
例子:
public static int getNextId()
{
  return nextId;
}
 
4.4.5main方法
每一个类都可以有一个·main方法,这常用于对类进行单元测试
要想单独测试某个类 只需输入java 类名
当执行主类时,部件类的main不会被执行
例子:
public class StaticTest {
    public static void main(String[] args)
    {
        var staff=new Employee[3];
        staff[0] = new Employee("s1",40000);
        staff[1] = new Employee("s2",60000);
        staff[2] = new Employee("s3",65000);
        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 avaliable id="+n);
    }
}
class Employee
{
    private static int nextId=1;
    private String name;
    private double salary;
    private int id;
    public Employee(String n,double s)
    {
        this.name=n;
        this.salary=s;
        this.id=0;
    }
    public String getName()
    {
        return this.name;
    }
    public double getSalary()
    {
        return this.salary;
    }
    public int getId()
    {
        return id;
    }
    public void setId()
    {
        id=nextId;
        nextId++;
    }
    public static int getNextId()
    {
        return nextId;
    }
    public static void main(String[] args)
    {
        var e=new Employee("s4",12345);
        System.out.println(e.getName()+" "+e.getSalary());
    }
}
__________________________________________________________________________________
4.5方法参数
按值调用:方法接收的是调用者提供的值
按引用调用:方法接收的是调用者提供的是变量地址
java总是采用按值调用,即方法不能修改传递给他的任何参数变量的内容
public static void tripleValue(double x)
{
        x=3*x;
}
double precent=10;
tripleValue(precent);
调用这个方法之后 precent的值还是10
执行过程如下:
1、x初始化为precent值的一个副本
2、x乘以3后等于30,但是precent仍然是10
3、这个方法结束后,参数变量x不再使用
 
当使用对象引用作为参数时
public static void tripleSalary(Employee x)
{
        x.raiseSalary(200);
}
harry=new Employee{......};
tripleSalary(harry);
具体执行过程如下:
1、x初始化为harry值的一个副本,这里就是一个对象引用
2、raiseSalary方法应用于这个对象引用。x和harry同时引用的那个Employee对象的工资提高了200%
3、方法结束后,参数变量x不再使用。对象变量harry继续引用那个工资增至3倍的员工对象
 
可以看到,实现一个改变对象参数状态的方法是完全可以的。方法得到的是对象引用的副本,原来的对象引用和这个副本都引用同一个对象
总结来说
方法不能修改基本数据类型的参数(即数值型和布尔型)
方法可以改变对象参数的状态
方法不能让一个对象参数引用一个新的对象
程序示例:
 
public class ParamTest {
    public static void main(String[] args)
    {
        /*
        *Test1:方法不能修改数值类型的参数值
        */
        System.out.println("Test value:");
        double percent=10;
        System.out.println("Before:"+percent);
        tripleValue(percent);
        System.out.println("After:"+percent);
        /*
        *Test2:方法可以修改参数状态
        */
        System.out.println("\nTesting tripleSalary:");
        Employee harry = new Employee("Harry",5000);
        System.out.println("Before:"+harry.getSalary());
        tripleSalary(harry);
        System.out.println("After:"+harry.getSalary());
        /*
        *Test3:方法不能给参数链接新对象
        */
        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);
    }
    public static void tripleSalary(Employee x)
    {
        x.raiseSalary(200);
        System.out.println("End of method:"+x.getSalary());
    }
    public static void swap(Employee a,Employee b)
    {
        Employee temp=a;
        a=b;
        b=temp;
        System.out.println("End of method:"+a.getName());
        System.out.println("End of method:"+b.getName());
    }
}
class Employee{
    private String name;
    private double salary;
    public Employee(String n,double s){
        name=n;
        salary=s;
    }
    public String getName()
    {
        return this.name;
    }
    public double getSalary()
    {
        return this.salary;
    }
    public void raiseSalary(double percent)
    {
        double raise=this.salary*percent/100;
        this.salary+=raise;
    }
}
————————————————————————————————————————————————————
4.6对象构造
4.6.1重载
有些类有多个构造器 例如
var message=new StringBuilder();
var message2=new StringBuilder("To do:\n");
方法之间有相同的名字和不同的参数(返回类型不能作为区分的特征)
 
4.6.2默认字段初始化
方法中的局部变量必须明确初始化。但在类中,如果没有初始化类中字段,将会自动初始化为默认值(0,false,null)
 
4.6.3无参数构造器
如果写一个类时,没有编写构造器,就会为你提供一个无参数构造器,这个构造器将所有实例字段设置为默认值。
但如果类中提供了构造器且需要参数,那么调用无参数构造器来创造对象就是非法的
 
4.6.5参数名
参数变量会遮蔽同名的实例字段,示例如下
public Employee(String name,double salary)
{
  this.name=name;
  this.salary=salary;
}
4.6.6调用另一个构造器
public Employee(double s)
{
  this("Employee #"+nextId,s);//calls Employee(String,double)
  nextId++;
}
 
调用构造器的具体处理步骤
1、如果构造器第一行调用另一个构造器,则基于所提供的参数执行第二个构造器
2、所有数据字段初始化为其默认值 按照在类中声明的顺序,执行所有字段初始化方法和初始化块
3、执行构造器主体代码
程序示例
import java.util.*;
public class ConstructorTest {
    public static void main(String[] args)
    {
        var staff=new Employee[3];
        staff[0] = new Employee("harry",8000);
        staff[1] = new Employee(1000);
        staff[2] = new Employee();
        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="";
    private double salary;
    static
    {
        var generator=new Random();
        nextId=generator.nextInt(10000);
    }
    {
        Id=nextId;//object initialization block
        nextId++;
    }

    public Employee(String n,double s){
        this.name=n;
        this.salary=s;
    }
    public Employee(double s)
    {
        this("Employee #"+nextId,s);
    }
    public Employee()
    {
        //name=""
        //salary=0
    }
    public String getName()
    {
        return this.name;
    }
    public double getSalary()
    {
        return this.salary;
    }
    public int getId()
    {
        return this.Id;
    }
    public void raiseSalary(double precent)
    {
        double raise=this.salary*precent/100;
        this.salary+=raise;
    }
}
——————————————————————————————————
4.7包
4.7.1包名
  com.baidu.www
  com.horstmann.corejava
4.7.2类的导入
  一个类可以使用所属包中所有类,以及其他包中的公共类
  两种方式访问另一个包中的公共类,第一种方法就是使用完全限定名,就是包名后面跟着类名
  另一种方法是使用import
  import java.time.*或具体类import java.time.LocalDate
4.7.5包访问
  标记为public的部分可以由任何类使用,标记为private的部分只能有由定义他们的类使用
  若未标记,则可以被同一个包中的所有方法访问
4.7.6类路径
  类的路径需要与包名匹配、
  java -classpath 路径1:路径2:路径3
4.8JAR
  将应用程序打包为压缩文件格式
4.9文档注释
  类注释,方法注释,字段注释,通用注释,包注释
4.10类设计技巧
  1、保证数据私有,这可能需要编写配套的访问器和更改器
  2、要对数据进行初始化
  3、不要再类中使用过多基本数据(可单独制成类)
  4、并非所有字段需要配套访问器与更改器
  5、分解有过多职责的类
  6、类名和方法名要能够体现职责
  7、优先使用不可变类(即没有方法能够修改对象状态,方法返回状态更新的新对象)