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、优先使用不可变类(即没有方法能够修改对象状态,方法返回状态更新的新对象)