目录
一、面向对象程序设计(OOP)
面向对象的程序由对象组成,每个对象包含对用户公开的特定功能部分和隐藏的实现部分
OOP将数据放在第一位,然后考虑操作数据的算法
规模较小的问题分解为过程的开发方式较理想,面向对象更适用于解决规模较大的问题
1.1类
类是构造对象的模板或蓝图
由类构造对象的过程称为创建类的实例
Java编写的所有代码都位于某个类的内部
封装
- 从形式上看,封装是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的使用方式
- 对象的数据称为实例域,操纵数据的过程称为方法
- 实现封装的关键在于绝对不能让类中的方法直接地访问其他类的实例域,程序仅通过对象的方法与对象数据进行交互
- 一个类可以全面地改变存储数据的方式,只要仍使用同样的方法操作数据,其他对象就不会知道或介意所发生的变化
继承
- 通过扩展一个类来建立另外一个类的过程称为继承
- 扩展后的新类具有所扩展类的全部属性和方法
1.2对象
对象的三个主要特性
- 对象的行为——可以对对象施加哪些操作
- 对象的状态——当施加那些方法时,对象如何响应
- 对象标识——如何辨别具有相同行为与状态的不同对象
每个对象都保存着描述当前特征的信息,即对象的状态
对象状态的改变必须通过调用方法实现,否则只能说明封装性遭到了破坏
1.3类之间的关系
类之间最常见的关系有依赖,聚合,继承
1.3.1依赖
"use-a"关系,是一种明显的、最常见的关系
如果一个类的方法操纵另一个类的对象,就说一个类依赖于另一个类
应尽可能地将相互依赖的类减至最少,让类之间的耦合度最小
1.3.2聚合
也称“关联”
"has-a"关系
类A的对象包含类B的对象
1.3.3继承
"is-a"关系,表示特殊与一般的关系
二、类的使用
不是所有的类都具有面向对象的特征
2.1对象与对象变量
想要使用对象,就必须先构造对象,并指定其初始状态,然后对该对象应用方法
构造器是一种特殊的方法,用来构造并初始化对象,且构造器的名字应该与类名相同
如果希望构造的对象可以多次使用,需要将对象存放在一个变量中,但对象与对象变量间存在重要区别,例如
Date deadline; 定义了一个对象变量 deadline,它可以引用 Date类型的对象,但要注意,变量 deadline不是一个变量,不能将任何 Date方法应用于该变量,s = deadline.toString(); 将产生编译错误
必须首先初始化变量 deadline:用新构造的对象初始化该变量 deadline = new Date(); ,或者让该变量引用一个已经存在的对象;此时两个变量引用同一个对象
一个对象变量没有实际包含一个对象,仅仅引用一个对象
(后续配图4-4)
Java中任何对象变量的值都是对存储在另外一个地方的一个对象的引用
new操作符的返回值是一个引用
Date deadline = new Date(); 有两个部分:表达式 new Date()构造了一个 Date类型的对象,且它的值是对新创建对象的引用;这个引用存放在变量 deadline中
可以显式地将对象设置为 null,表明该对象变量目前没有引用任何对象
局部变量不会自动地初始化为 null,必须通过调用 new或将它们设置为 null进行初始化
2.2更改器方法与访问器方法
通过一个类的接口来完成相当复杂的任务,而无须了解实现细节
import java.time.*;
/**
* This program introduces the modifier and accessor methods
* @version 20:03 2019-05-09
* @auther 云烟成雨yycy
*/
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); //把日期设置为本月第一天
DayOfWeek weekday=date.getDayOfWeek();
int value=weekday.getValue(); //得到1~7的数字,分别对应周一~周日
System.out.println("Mon Tue Wed Thu Fri Sat Sun"); //打印表头
for(int i=1; i<value; ++i)
System.out.printf(" "); //若本月1日是周X,则要打印 X-1 个长空格
while(month ==date.getMonthValue()){
System.out.printf("%3d",date.getDayOfMonth());
if(today ==date.getDayOfMonth())
System.out.printf("\b\b *");
else
System.out.printf(" "); //每个数字后都会填充一个字符作为标记,如果是当天用*表示,否则用空格表示
date=date.plusDays(1); //当前日期后延一天
if(1 ==date.getDayOfWeek().getValue())
System.out.println();
}
if(1 !=date.getDayOfWeek().getValue())
System.out.println();
}
}
- static LocalDate now() 构造一个表示当前日期的对象
- LocalDate of(int year, Month month, int dayOfMonth) 构造一个表示给定日期的对象
- int getYear(),int getMonthValue(),int getDayOfMonth() 得到当前日期的年/月/日
- DayOfWeek getDayOfWeek() 得到当前日期是星期几, 返回类型是DayOfWeek。调用getValue来得到1-7之间的一个数字,表示这是星期几,1 表示星期一, 7表示星期日
- LocalDate plusDays(long daysToAdd),LocalDate minusDays(long daysToSubtract) 生成当前日期之前或之后前n天的日期
2.3用户自定义类
想要创建一个完整的程序,应该将若干类组合在一起,只有一个类有 main方法
import java.time.*;
/**
* This program tests the Employee class
* @version 20:38 2019-05-09
* @auther 云烟成雨yycy
*/
public class EmployeeTest{
public static void main(String[] args){
Employee[] staff = new Employee[3];
staff[0] = new Employee("Tom",40000,1987,12,15);
staff[1] = new Employee("Tomas",67000,1989,10,1);
staff[2] = new Employee("Jerry",98000,1990,3,15);
//每人薪水提高5%
for(Empolyee e : staff)
e.raiseSalary(5);
//输出所有人的情况
for(Empolyee e : staff)
System.out.println("name="+ e.getName(+ ",salary="+ e.getSalary()+ ",hireDay="+ e.getHireDay));
}
}
class Employee{
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String n, double s, int year, int month, int day){
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
}
public String getName(){
return name;
}
public double getSalary(){
return salary;
}
public int getHireDay(){
return hireDay;
}
public void raiseSalary(double byPersent){
double raise = salary*byPersent/100;
salary += raise;
}
}
源文件名必须与 public类的名字相匹配;一个源文件中只能有一个公共类,但可以有任意数目的非公共类
2.3.1从构造器开始
观察 Employee类的构造器
public Employee(String n, double s, int year, int month, int day){
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
}
可以看到,构造器与类同名。在构造 Employee类的对象时,构造器会运行以便将实例域初始化为希望状态
例如通过语句 staff[0] = new Employee("Tom",40000,1987,12,15); 创建 Employee类实例时,会把实例域设置成:
name = "Tom";
salary = 40000;
hireDay = LocalDate.of(1987,12,15);
构造器总是伴随着 new操作符的执行被调用。而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的
构造器特点
- 构造器与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有0个、1个或多个参数
- 构造器没有返回值
- 构造器总是伴随着 new操作一起调用
- 不要在构造器中定义与实例域重名的局部变量
2.3.2隐式参数与显式参数
隐式参数是出现在方法名前面的类对象,显式参数是位于方法名后面括号中的数值
每个方法中,关键字 this表示隐式参数
//更改程序中 Employee类中的 raiseSala函数
public void raiseSalary(double byPersent){
double raise = this.salary*byPersent/100;
this.salary += raise;
}
三、静态域与静态方法
3.1静态域
每个对象对所属类的所有实例域有自己的一份拷贝
如果将域定义为 static,类中只有一个这样的域,它的所有对象共享这一个域
例如有如下类
class Employee{
private static int nextId = 1;
private int id;
…
}
如果有1000个 Employee类的对象,则有1000个实例域 id,但只有一个静态域 nextId,即使没有一个雇员对象,静态域 nextId也存在
静态域属于类,不属于任何独立的对象
3.2静态方法
静态方法是一种不能向对象实施操作的方法,换句话说没有隐式参数
静态方法没有 this参数
静态方法可以访问自身类中的静态域
使用静态方法情况
- 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供
- 一个方法只需要访问类的静态域
main方法不对任何对象进行操作。实际上,启动程序时还没有对象,静态的 main方法将执行并创建需要的对象
四、对象构造
4.1重载
定义:多个方法有相同的名字、不同的参数
编译器通过各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来选出相应的方法
要完整地描述一个方法需要指出方法名和参数类型,这叫方法的签名。返回类型不是方法签名的一部分,即不能有两个名字相同、参数类型相同但返回不同类型的方法
4.2初始化
4.2.1默认域初始化
如果构造器中没有显式地给域赋初值(有构造器),就会自动赋默认值:数值为0,布尔值为 false,对象引用为 null
4.2.2无参数的构造器
如果编写函数时没有编写构造器,系统会提供一个无参数构造器,将所有实例域设置为默认值
如果类中提供了至少一个构造器,但是没有提供无参数的构造器,则构造对象时如果没有提供参数就会被视为不合法,为预防此事一般手动编写一个无参数构造器,例如
new Employee(){
//no information
}
4.2.3显式域初始化
当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时,可以在类定义中直接将一个值赋给域
4.2.4调用另一个构造器
如果构造器的第一个语句形如 this(…),这个构造器将调用同一个类的另一个构造器,例如
public Employee(double aSalary){
//calls Employee(String, double)
this("Employee #"+ nextId, aSalary);
}
当调用 new Employee(60000)时,Employee(double)构造器将调用 Employee(String, double)构造器
调用构造器的步骤
- 所有数据域被初始化为默认值
- 按照在类声明中出现的次序,依次执行所有域初始化语句和初始化块
- 如果处理器第一行调用了第二个构造器,则执行第二个构造器主体
- 执行构造器主体
import java.util.*;
/**
* This program demonstrates object construction
* @version 10:22 2019-05-11
* @auther 云烟成雨yycy
*/
public class ConstructorTest{
public static void main(String[] args){
Employee[] staff = new Employee[3];
staff[0] = new Employee("harry", 40000);
staff[0] = new Employee(60000);
staff[0] = new Employee();
//print out information about all Employee objects
for(Employee e : staff)
System.out.println("name="+ e.getName()+ ",id="+ e.getId()+ ",salary="+ e.detSalary());
}
}
class Employee{
private static int nextId;
private int id;
private String name = ""; //intance field initialization
private double salary;
//static initialization block
static{
Random generator = new Random;
//set nextId to a random number between 0 and 9999
nextId = generator.nextInt(10000);
}
//object initialization block
{
id = nextId;
nextId++;
}
//three overloaded constructors
public Employee(String aName, double aSalary){
name = aName;
salary = aSalary;
}
public Employee(double aSalary){
//calls the Employee(String, double) constructor
this("Employee #"+ nextId, aSalary);
}
//the default constructor
public Employee(){
//name initialized to "" --see above
//salary not explicitly set --initialized to 0
//id initialized in initialization block
}
public String getName(){
return name;
}
public double detSalary(){
return salary;
}
public int getId(){
return id;
}
}
五、包
使用包将类组织起来,借助于包可以很方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理
从编译器角度看,嵌套的包之间没有任何关系
5.1类的导入
一个类可以使用所属包中的所有类,以及其他包中的共有类
import语句应该位于源文件的顶部,package语句的后面
可以使用语句 import java.util.*; 导入 java.util包中所有的类。注意,星号 * 只能导入一个包,不能使用 import java.*或 import java.*.*导入以Java为前缀的所有包
5.2将类放入包中
要想将类放入包中,必须将包的名字放在源文件开头,包中定义类的代码之前,例如
package com.horstmann.corejava;
public class Employee{
…
}
编译器对文件(带有文件分隔符和扩展名 .java 的文件)进行操作,Java解释器加载类(带有 . 分隔符)
编译器在编译源文件时不检查目录结构。例如,假定有一个源文件开头有下列语句 package com.mycompany;
即使这个源文件没有在子目录 com/mycompany下也可以进行编译。如果它不依赖于其他的包就不会出现编译错误,但最终的程序无法运行,除非将所有类文件移到正确的位置上
如果包与目录不匹配,虚拟机就找不到类
import com.horstmann.corejava.*;
//which defines the Employee class
import Static java.lang.System.*;
/**
* This program demonstrates the use of packages
* @version 20:02 2019-05-11
* @auther 云烟成雨yycy
*/
public class ConstructorTest{
public static void main(String[] args){
//Because of the import statement, we don't have to use com.horstmann.corejava.Employee here
Employee harry = new Employee("Harry Hacker",50000,1989,10,1);
harry.raiseSalary(5);
//Because of the static import statement, we don't have to use System.out here
out.println("name="+ harry.getname()+ ",salary="+ harry.getSalary());
}
}
package com.horstmann.corejava.*;
//the classes in this file are part of this package
import java.time.*;
//import statements come after the package statement
/**
* @version 20:26 2019-05-11
* @author 云烟成雨yycy
*/
public class Employee{
private String name;
private double salary;
private LocalDate hireDay;
public Employee(String name, double salary, int year, int month, int day){
this.name = name;
this.salary = salary;
hireDay = LocalDate.of(year,month,day);
}
public String getName(){
return name;
}
public double getSalary(){
return salary;
}
public LocalDate getHireDay(){
return hireDay;
}
public void raiseSalary(double byPersent){
double raise = salary*byPersentt/100;
salary += raise;
}
}
by 云烟成雨yycy