- 面向对象程序设计(object-oriented programming,OOP)
- 类(class,构造对象的模块或蓝图)
- 由类构造(construct)对象的过程称为创建类的实例(instance)
- 封装(encapsulation,有时称为数据隐藏)
- 从形式上看,封装就是将数据和行为组合在一个包里,并对对象的使用者隐藏具体实现方式。
- 对象中的数据称为实例字段(instance field),操作数据的过程称为方法(method)
- 在Java中,所有的类都源自 Object 类,所有其他类都扩展自这个 Object类
- 继承(inheritance,继承后的新类具有被继承类的全部属性和方法)
- 只需要在新类中提供适用与这个新类的新方法和数据字段就可以了
- 对象
- 对象的行为——用可调用的方法来定义的
- 对象的状态——每个对象都保存着描述当前状况的信息,其状态的改变必须通过调用方法实现
- 对象的标识——每个对象都有一个唯一的标识(identity,或称身份)
- 类之间的关系
- 依赖(dependence),一个类的方法使用或操纵另一个类的对象,尽可能减少这种类之间的耦合
- 聚合(aggregation),类A的对象包含类B的对象
- 继承(inheritance),如果类A扩展类B,类A不但包含从类B继承的方法,还会有一些额外的功能
- 类(class,构造对象的模块或蓝图)
- 使用预定义类
- 不是所有类都具有面向对象的典型特征。例如,Math类。可以直接使用Math类的方法,如 Math.random
- Math类只封装了功能,不需要隐藏数据。因此不必考虑创建对象和初始化它们的实例字段。
- 对象与对象变量
- 构造器(constructor,或称构造函数)构造新实例。构造器的名字与类名相同,并且需要在构造器前面加上new操作符构造一个对象。
-
new Date(); //该表达式构造了一个新对象,这个对象被初始化为当前的日期和时间
String s=new Date().toString(); //Date类有一个toStrng方法,这个方法将返回日期的字符串描述 -
上面两个例子中,构造的对象仅使用了一次。要想构造的对象可以多次使用,需要将对象存放在一个变量中
-
Date birthday=new Date(); //对象变量 birthday,它引用了新构造的对象
-
对象变量并没有实际包含一个对象,它只是引用一个对象。Java中,任何对象变量都是对存储在另外一个地方的某个对象的引用。
-
Java类库中的 LocalDate类
-
LocalDate类,用大家熟悉的日历表示法表示日期
-
不要使用构造器来构造 LocalDate类的对象。应当使用静态工厂方法(factory method),它会代表调用构造器
-
LocalDate.now(); //会构造一个新对象,表示构造这个对象时的日期
LocalDate.of(1999.12.31); //可以提供年、月和日来构造对于的一个特定日期的对象
LocalDate newYearsEve=LocalDate.of(1999,12,31); //将构造的对象保存在一个对象变量中
-
-
plusDays方法会得到一个新的 LocalDate,这个新日期对象则是距应用这个方法的对象指定天数的一个新日期
-
LocalDate aThousandDaysLater=newYearEve.plusDays(1000); //plusDays方法会生成一个新的 LocalDate对象,然后把这个新对象
赋给aThousandDaysLater变量。原来的对象不做任何改动 int year=aThousandDaysLater.getYear(); //方法 getYear得到年。结果,2002 int month=aThousandDaysLater.getMonthValue(); //方法 getMonthValue得到月。结果,09 int day=aThousandDaysLater.getDayOfMonth(); //方法 getDayOfMonth得到日。结果,26
-
-
Java较早版本增加有另一个处理日历的类,GregorianCalendar
-
GregorianCalendar someDay=new GregorianCalendar(1999, 11, 31); //与 LocalDate.plusDays方法不同,GregorianCalendar.add方法
someDay.add(Calendar.DAY_OF_MONTH, 1000); 是一个更改器方法。调用这个方法后,someDay对象的状态会改变
int year=someDay.get(Calendar.YEAR); //2002
int month=someDay.get(Calendar.MONTH)+1; //09
int day=someDay.get(Calendar.DAY_OF_MONTH); //26
-
-
-
用户自定义类
-
带有 public访问修饰符的EmployeeTest类源文件中包含了一个Employee非公共类
-
在一个源文件中,只能有一个公共类,但可以有任意数目的非公共类
-
Employee[] staff=new Employee[3]; //构造一个 Employee数组,并填入3个Employee对象 staff[0]=new Employee("Carl Cracker",75000,1987,12,15); staff[1]=new Employee("Harry Hacker",50000,1989,10,1); staff[2]=new Employee("Tony Tester",40000,1990,3,15);
for(Employee e:staff) //使用Employee类的raiseSalary方法将每个员工的薪水提高 5%
e.raiseSalary(5);for(Employee e:staff) //调用 getName方法、getSalary方法和getHireDay方法打印各个员工的信息
System.out.println("name="+e.getName()+",salary="+e.getSalary()+",hireDay="+e.getHireDay());
-
-
剖析 Employee类
-
public Employee(String n,double s,int year,int month,int day) //这个类的所有方法都被标记为 public ,关键字public意味着任何类 public String getName() 的任何方法都可以调用这些方法 public double getSalary() public LocalDate getHireDay() public void raiseSalary(double byPercent)
-
在 Employee类中有3个实例字段用来存放将要操作的数据
-
private String name; //关键字 private确保只有 Employee类自身的方法能够访问这些实例字段,而其他类的方法 private double salary; 不能够读写这些字段 private LocalDate hirDay;
-
有两个实例字段本身就是对象:name字段是 String类对象,hireDay字段是LocalDate类对象。(类包含的实例字段通常属于某个类类型)
-
-
从构造器开始
-
Employee类的构造器
-
public Employee(String n,double s,int year,int month,int day) { //构造器与类同名。在构造 Employee对象时,构造器会允许, name=n; 从而将实例字段初始化为所希望的初始状态 salary=s; hirDay=LocalDate.of(year,month,day); }
-
例如,当使用下面这条代码创建 Employee类的实例时:
-
new Employee("James Bond",100000,1950,1,1); //构造器与其他方法有一个重要的不同。构造器总是结合new操作符来调用
-
将会把字段设置为:
-
name="James Bond"; salary=100000; hireDay=LocalDate.of(1950,1,1);
-
有关构造器总结
-
构造器与类同名
-
每个类可以有一个以上的构造器
-
构造器可以有0个、1个或多个参数
-
构造器没有返回值
-
构造器总是伴随着new操作符一起调用
-
-
-
用 var 声明局部变量
-
在 Java10中,如果可以从变量的初始值推导出它们的类型,就可以用 var关键字声明局部变量,而无需指定类型。
-
var harry=new Employee("Harry Hacker", 50000, 1989, 10, 1); //替代 Employee harry=new Employee("Harry Hacker", ...)
-
注意:不会对数值类型使用 var,如 int、long或 double,使你不用当心 0、0L和 0.0之间的区别。
-
var关键字只能用于方法中的局部变量,参数和字段的类型必须声明。
-
-
-
使用 null 引用
-
一个对象变量包含一个对象的引用,或者包含一个特殊值null,后者表示没有引用任何对象。要小心使用 null值
-
LocalDate birthday=null; //如果对 null值应用一个方法,会产生一个 NullPointerException异常 String s=birthday.toString(); //NullPointerException
-
我们不希望 name或 hireDay字段为 null(不用担心 salary字段,这个字段是基本类型,所以不可能是 null)
-
-
隐式参数与显示参数
-
方法用于操作对象以及存取它们的实例字段。例如,以下方法
-
public void raiseSalary(double byPercent) { double raise=salary*byPercent/100; salary+=raise; }
-
将调用这个方法的对象的salary实例字段设置为一个新值。考虑下面这个调用
-
number007.reiseSalary(5);
-
它的结果是将 number007.salary字段的值增加5%。具体地说,这个调用将执行下列指令:
-
double raise=number007.salary * 5 / 100; number007.salary+=raise;
-
raiseSalary方法有两个参数。第一个参数称为隐式参数,是出现在方法名前的Employee类型的对象(number007)。第二个参数是位于方法名后 面括号中的数值,这是一个显式参数
-
在每一个方法中,关键字 this指示隐式参数。如下改写 raiseSalary方法:
-
public void raiseSalary(double byPercent) { double raise=this.salary*byPercent/100; this.salary+=raise; }
-
-
封装的优点
-
public String getName() { //这些都是典型的访问器方法。 return name; } 由于它们只返回实例字段值,因此又称为字段访问器 public double getSalary() { return salary; //这样,name就是一个只读字段。一旦在构造器中设置,就没有任何办法可以对它进行修改 } 这样我们可以确保name字段不会受到外界的破坏 public LocalDate getHireDay() { return hirDay; }
-
想要获得或设置实例字段的值。需要下面三项内容
-
一个私有的数据字段 (例如,private String name)
-
一个公共的字段访问器方法 (例如,getName方法)
-
一个公共的字段更改器方法 (例如,setName方法)
-
-
-
私有方法
-
在 Java中,要实现私有方法,只需将关键字 public 改为 private 即可。
-
如果一个方法是公共的,就不能简单地将其删除,因为有可能会有其他代码依赖这个方法。
-
-
final 实例字段
-
这样的字段必z须在构造对象时初始化。(也就是说,确保每一个构造器执行后,该字段值已经设置,并且以后不会再修改这个字段)
-
class Employee{ //在对象构造后,这个值不会再改变,即没有 setName方法 private final String name; //final 修饰符对于类型为基本类型或者不可变类的字段尤其有用 } String 类就是不可变的。(类中所有方法都不会改变其对象)
-
-
- 静态字段与静态方法
- 静态字段
- 给 Employee类添加一个实例字段 id 和一个静态字段 nextId:
-
class Employee{ private int id; //每一个 Employee对象都有一个自己的 id字段,但这个类的所有实例将共享一个 nextId字段 private static int nextId=1; //即使没有 Employee对象,静态字段 nextId也存在,它属于类,而不属于任何单个的对象 }
-
静态常量
-
静态变量使用得比较少,但静态常量却很常用。例如,在 Math类中定义一个静态常量:
-
public class Math{ //在这个程序中,可以用 Math.PI来访问这个常量 public static final doule PI=3.1415926535; //如果省略了关键字 static,PI就变成了Math类的一个实例字段。也就需要通过 } Math类的一个对象来访问 PI,并且每个Math对象都有它自己的一个 PI副本
-
-
静态方法
-
静态方法是不在对象上执行的方法。例如,Math类的 pow方法就是一个静态方法。表达式 Math.pow(x,a) 幂运算
-
Employee类的静态方法不能访问 id实例字段,因为它不能在对象上执行操作,只可以访问静态字段
-
public static int getNextId() { //可以提供类名来调用这个方法:int n=Employee.getNextId(); return nextId; }
-
在下面两种情况下可以使用静态方法:
-
方法不需要访问对象状态。(对象状态的改变必须通过调用方法实现)
-
方法只需要访问类的静态字段(例如,Employee.getNextId)
-
-
-
工厂方法
-
静态方法的另外一种常见的用途。类似 LocalDate和 NumberFormat的类使用静态工厂方法来构造对象
-
NumberFormat currencyFormatter=NumberFormat.getCurrencyInstance(); //NumberFormat类如下生成不同风格的格式化对象 NumberFormat percentFormatter=NumberFormat.getPercentInstance(); //NumberFormat类不利用构造器完成这些操作的原因: double x=0.1; 1.无法命名构造器。构造器名字必须与类名相同。这里希望有两个不同的名字 System.out.println(currencyFormatter.format(x)); 2.使用构造器时,无法改变构造对象的类型。而工厂方法实际上将返回 System.out.println(percentFormatter.format(x)); DecimalFormat类的对象
-
-
main方法
-
main也是一个静态方法。不对任何对象进行操作。在启动程序时还没有任何对象。静态的main方法将执行并构造程序所需要的对象
-
- 静态字段
-
方法参数
-
按值调用表示方法接收的是调用者提供的值。按引用调用表示方法接收的是调用者提供的变量地址。
-
方法可以修改按引用传递的变量的值,而不能修改按值传递的变量的值。
-
-
Java程序设计语言总是按值调用。方法不能修改传递给它的任何参数变量的内容。例如,下面的调用:
-
double percent=10; harry.raiseSalary(percent); //该方法调用后,percent的值还是10
-
一个方法不可能修改基本数据类型的参数,而对象引用作为参数就不同了。例如,下面:
-
public static void tripleSalary(Employee x){ //将一个员工的工资增至三倍 x.raiseSalary(200); //1.x初始化为harry值的一个副本,这里就是一个对象引用 }
当调用
harry=new Employee(...); //2.raiseSalary方法应用于这个对象引用。x和harry同时引用的那个Employee对象的工资提高了200%
tripleSalary(harry); //3.方法结束后,参数变量x不再使用。对象变量harry继续引用那个工资增至3倍的员工对象 -
总结:在Java中对方法参数能做什么和不能做什么
-
方法不能修改基本数据类型的参数(即数值型或布尔型)
-
方法可以改变对象参数的状态。(对象状态的改变必须通过调用方法实现)
-
-