对象与类
类
类是构造对象的模板或蓝图,由类构造对象的过程称为创建类的实例;封装是与对象有关:封装不过是将数据和行为组合在一个包中,并对对象的使用者隐藏了数据的实现方式,对象中的数据称为实例域;操纵数据的过程称为方法,每个特定的实例都有一组特定的实例值,这些值的集合就是这个对象的当前状态;
对象
OOP三个主要特性:
- 对象的行为–可以对对象施加那些操作,或可以对对象施加那些方法?
- 对象的状态–当施加那些方法,对象如何响应?
- 对象的标识–如何辨别具有相同行为与状态的不同对象?
识别类
首先从设计类开始,如何再往每个类中添加方法;
类之间的关系
常见的关系:
- 依赖
- 聚合
- 继承
使用预定义对象
对象与对象变量
要想使用对象,首先构造对象,并指定其初始状态,如何对对象应用方法;使用构造器构造新实列,构造器是一种特殊的方法,用来构造并初始化对象;
//构造器的名字应该与类名相同,因此Data类的构造器名为Data,要想构造一个Data对象,需要在构造器前面加上new操作符
new Data();
system.out.println(new Data());
String s=new Data().toString();
Data Birthday=new Data();
Data dealine=new Data(); //new data()构造了一个Data类型对象,并且它的值是对新创建对象的引用,引用存储在dealine中
//理解其拷贝
c++ Data birthday=Data* birthday;
//前面是java 后面是cpp 两者语法的差异;
java类库中的GregorianCalender类
标准类库java包含了两个类:一个是表示时间点的Data类,另一个是表示日历表示法GregorianCalender,扩展了Calender类;
更改方法与访问器方法
通过构造器方法中的get与set就可以改变,通常的习惯是在访问器方法名前面加上前缀get,在更改器方法前面加上前缀set
用户自定义类
Employee类
定义一个类的简单格式
class ClassName
{
field1
field2
...
construtor1
construtor2
...
method1
method2
...
}
我们先创建一个employee的类看下具体的实现方式
import java.util.GregorianCalendar;
import java.util.*;
public class Employee {
private String name;
private double salary;
private Date hireDay;
public Employee(String n, double s, int year, int month, int day) {
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public Date getHireDay() {
return hireDay;
}
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
public static void main(String[] args) {
Employee[] staff = new Employee[3];
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", 4000, 1990, 3, 15);
for (Employee e : staff) {
e.raiseSalary(5);
}
for (Employee e : staff) {
System.out.println("name = " + e.getName() + "salary =" + e.getSalary() + ",hirDay" + e.getHireDay());
}
}
}
多个源文件使用
一个源文件包含两个类,将Employee类放在Employee.java中,将Employee_test类放在EmployeeTest.java中;
刨析Employee类
public Employee(String n,double s,int year,int month,int day)
public String getName()
public double getSalary()
public Date getHireDay()
这些方法都被定义成public,关键字public意味着任何类的任何方法都可以调用这些方法,
从构造器开始
public Employee(String n, double s, int year, int month, int day) {
name = n;
salary = s;
GregorianCalendar calendar = new GregorianCalendar(year, month - 1, day);
hireDay = calendar.getTime();
}
这个就是构造器,构造器与其他方法有个重要的不同,构造器总是伴随着new操作符的执行被调用,而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的;
现在构造器需要的是
- 构造器与类同名
- 每个类可以有一个以上的构造器
- 构造器可以有0个,1个或多个参数
- 构造器没有返回值
- 构造器总是伴随着new操作符一起使用(c++不需要,写java时一定要注意new)
隐式参数与显式参数
public void raiseSalary(double byPercent) {
double raise = salary * byPercent / 100;
salary += raise;
}
在这个raiseSalary方法有两个参数,第一个参数称为隐式参数,是出现在方法名前的Employee类对象,第二个参数位于方法名后面括号中的数值,这是一个显式参数,由此可以知道显式参数是明显列在方法声明中的,隐式参数没有出现在方法声明中的;
在每一个方法中,关键字this表示隐式参数
public void raiseSalary(double byPercent) {
double raise = this.salary * byPercent / 100;
this.salary += raise;
}
封装的优点
public String getName() {
return name;
}
public double getSalary() {
return salary;
}
public Date getHireDay() {
return hireDay;
}
这些是典型的访问器方法,它们只返回实例域值,又称为域访问器。
这个关键是Name是一个只读域,一旦在构造器设置完成,就没有办法对它进行修改,这样确保name域不会受到外界的破坏
而salary不是只读域,它可以通过raiseSalary方法修改;
基于类的访问权限
方法可以访问所调用对象的私有数据,一个方法可以访问所属类的所有对象的私有数据;
public boolean equals(Employee other)
{
return name.equals(other.name);
}
// 调用方式:
if(harry.equals(boss))
{
}
这个方法访问hurry的私有域,同时它还访问了boss的私有域,这个方法是合法的,原因是boss它本就是Employee类对象,而Employee类的方法可以访问Employee类的如何一个对象的私有域;
私有域
在实现一个类时,由于共有数据非常危险,应该将所有的数据域设置为私有域,这个时候需要关键字private的;在java中把public的地方改为private就可以
final实例域
可以将实例域定义为final,构建对象时必须初始化这样的域,确保在每一个构造器执行后,这个域的值都被设置,而在后面的操作中,不能对他进行修改;
class Employee{
private final string name;
}
final修饰符一般用在基本类型域或不可变类的域中的每个方法都不会改变其对象;这种类就是不可变的类;
静态域与静态方法
静态域
将域定义为static,每个类中只要一个这样的域,而每个对象对于所有的实例域都有一份自己的拷贝;
class Employee
{
private static int nexld=1;
private int id;
}
//也就是说,每个雇员对象都有一个自己的id域,这个类的所有实例将共享一个nextld域;
//如果有1000个Employee类的对象,则有1000个实例域id,但是只有个静态域nextld
//即使没有一个雇员对象,静态域nextld也存在,它属于类,而不属于任何独立对象
public void setld()
{
id=nexld;
nextld++;
}
静态常量
静态变量使用的较少,但是静态常量使用的比较多;
public static final double PI=3.1415;
这个是一个MATH类的一个静态常量,可以用MATH.PI的形式来获得这个常量;
静态方法
静态方法是一种不能向对象实施操作的方法,例如,MATH类中的pow就是一个静态方法,表达式
Math.pow(x,a)
//计算幂,不使用任何Math的对象,也可以理解为没有隐式参数
可以认为静态方法是没有this参数的方法,因为静态方法不能操作对象,所有不能在静态方法中访问实列域,但是静态方法可以访问自身类中的静态域;比如上述写的nextld静态域;
在两种情况下使用静态方法:
- 一个方法不需要访问对象状态,其所需参数都是通过显式参数提供(例如:Math.pow)
- 一个方法只需要访问类的静态域(例如:Employee.getNextld)
工厂方法
在以往,我们会通过构造器和new来调用一个对象;但是工厂方法不需要
Fragment fragment = MyFragment.newIntance();
// or
Calendar calendar = Calendar.getInstance();
// or
Integer number = Integer.valueOf("3");
不过在实际的开发中,我们经常还会见到另外一种获取类实例的方法:
- 静态工厂方法与构造器不同的第一优势在于,它们有名字
- 第二个优势,不用每次被调用时都创建新对象
- 第三个优势,可以返回原返回类型的子类
- 第四个优势,在创建带泛型的实例时,能使代码变得简洁
main方法
不需要使用对象调用静态方法,main方法也是一个静态方法;
public static void main(STring[] args)
//main方法不对任何对象进行操作,事实上,在启动程序后还没有任何一个对象,静态的main方法将执行并创建程序所需要的对象
public class StaticTest {
public static void main(String[] args) {
Employee[] staff = new Employee[3];
staff[0] = new Employee("Tom", 40000);
staff[1] = new Employee("Dick", 60000);
staff[2] = new Employee("Harry", 650000);
for (Employee e : staff) {
e.setld();
System.out.println("name=" + e.getName() + "id=" + e.getld() + "salary=" + e.getSalary());
}
int n = Employee.getNextld();
System.out.println("Next available id =" + n);
}
}
class Employee
{
private static int nextld=1;
private String name;
private double salary;
private int id;
public Employee(String n,double s)
{
name=n;
salary=s;
id=0;
}
public String getName()
{
return name;
}
public double getSalary() {
return salary;
}
public int getld()
{
return id;
}
public void setld()
{
id=nextld;
nextld++;
}
public static int getNextld()
{
return nextld;
}
}
方法参数
有关将参数传递给方法的两种方式:
- 按值调用(call by value)表示方法接受的是调用者提供的值
- 按引用调用(call by reference)表示方法接收的是调用者提供的变量地址;
java程序总是采用按值调用,方法得到的是所有参数值的有关拷贝,特别是,方法不能修改传递给它的任何参数变量的内容;
public static void swap(Employee x,Employee y)
{
Employee temp=x;
x=y;
y=temp;
}
Employee a=new Employee("Alice",20);
Employee b=new Employee("Bob",30);
swap(a,b);
这个调用是无效的,方法并没有改变存储在变量a和b的对象引用,swap方法参数的x和y被初始化为两个对象的靠,这个方法交换的是两个拷贝,根据上面的程序,得出java程序设计语言对对象采用的不是引用调用,实际上,对象引用进行的是值传递;
java程序语言设计中方法参数的使用情况:
- 一个方法不能修改一个基本数据类型的参数;
- 一个方法可以改变一个对象参数的状态;
- 一个方法不能让对象参数引用一个新的对象;
对象构造
重载
有的类有多个构造器,这种特征叫做重载(overloading),如果多个方法有相同的名字,不同的参数,便产生了重载,编译器必须挑选出具体执行那个方法,它通过用各个方法给出的参数类型与特定的方法来挑选相应的方法;如果编译器找不到适合的方法,这个时候就会出现编译时错误,被称为重载解析(overloading resolution)
index(int);
index(int,int);
index(double,int);
默认域初始化
如果在构造器中没有显示的给域赋予初值,那么就会自动的赋为默认值,数值为0,布尔值为false,对象引用为NULL;
无参数的构造器
很多类都包含一个无参数的构造函数,对象由无参数构造函数创建时,其状态会设置为适当的默认值,
public Employee() //这个是Employee类的无参数构造函数
{
name="";
salary=0;
hireDay=new Data;
}
如果在编写一个类时没有编写构造器,那么系统会自动提供给一个无参数构造器,这个构造器将所有的实例域设置为默认值;
如果类中提供至少有一个构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法;
显式域初始化
由于类的构造器可以重载,可以采用多种形式设置类的实例域的初始化状态,确保不管怎么调用构造器,每个实域都有一个有意义的初值;
class Employee
{
private String name=" ";
}
参数名
方便可读性,可以增加this
public Employee(String name,double salary)
{
this.name=name;
this.salary=salary;
}
调用另一个构造器
关键字this还有另外一个用法,可以调用另一个构造器使用
public Employee(double s)
{
this("Employee #"+nextIdms);
nextId++;
}
这个方法就是在一个构造器里面调用另外一个构造器;