面向对象程序设计概述
面向对象的程序(oop)是由对象组成的, 每个对象包含对用户公开的特定功能部分和隐藏的实现部分。
类——class
是构造对象的模板或蓝图。 我们可以将类想象成制作小甜饼的切割机,将对
象想象为小甜饼。
由类构造 (construct) 对象的过程称为创建类的实例 (instance ) .
java编写的所有代码都位于类内
可以通过扩展一个类来建立另外一个类(Object——超类),在下一章赘述
对象
主要特征
- 行为(behavior)—— 可以对对象实施哪些操作?实施哪些方法
- 状态(state)—— 施加方法会返回什么?
- 标识(identity)——辨别相同行为,但状态不同的对象(每个对象的标识都是不同的)
如何使用对象(自定义类来展示)
构造对象→设定初始状态→应用方法
构造器
构造器(constructor) 构造新实例。 构造器是一种特殊的方法, 用来构造并初始化对象。
new Date();
/*new——初始化(为当前的时间和日期) Date()构造新对象 Data类的构造器名为Date*/
/*可执行的操作:1.传递给另一个方法*/
System.out.println(new Date());
/*2.运用该类的方法(将该方法应用于该对象)*/
String s = new Date().toString(); 构造了字符串变量
/*以上过程构建的对象都只能单次使用,为了能多次使用,要构建 对象变量*/
构造器与其他的方法有一个重要的不同。构造器总是伴随着 new 操作符的执行被调用,
而不能对一个已经存在的对象调用构造器来达到重新设置实例域的目的。例如,
janes.EmployeeCJames Bond”, 250000, 1950, 1, 1) // ERROR将产生编译错误。
• **构造器与类同名
• **每个类可以有一个以上的构造器
• **构造器可以有 0 个、1 个或多个参数
• **构造器没有返回值
• **构造器总是伴随着 new 操作一起调用
对象变量
只是一个对于变量的引用,而并不是实际保存一个变量。
Date birthday = new Date();
deadline = birthday
/*deadline 和 birthday 同时引用 date()这个对象 即对象变量保存的地址,而非值*/
一定要认识到: 一个对象变量并没有实际包含一个对象,而仅仅引用一个对象
自定义Employee类 code:
/**
* This program demonstrates static methods.
* @version 1.02 2008-04-10
* @author Cay Horstmann
*/
public class StaticTest
{
public static void main(String[] args)
{
// fill the staff array with three Employee objects
var staff = new Employee[3];
// 设立对象数组,给staff引用; 假如只需一个object,就直接用Object(arg**)就好
staff[0] = new Employee("Tom", 40000);
staff[1] = new Employee("Dick", 60000);
staff[2] = new Employee("Harry", 65000);
// print out information about all Employee objects
for (Employee e : staff)
{
e.setId();
System.out.println("name=" + e.getName() + ",id=" + e.getId() + ",salary="
+ e.getSalary());
}
int n = Employee.getNextId(); // calls static method
System.out.println("Next available id=" + n);
}
}
class Employee
{
// 关键字 private 确保只有 Employee 类自身的方法能够访问这些实例域 Q:id为什么是静态的 A:A是不可修改的,但salary可以
// 类内变量可以不初始化,因为有默认值
private static int nextId = 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 getId()
{
return id;
}
//以上3个为典型的(域)访问器,只返回实例域值
public void setId()
{
id = nextId; // set id to next available id
nextId++;
}
public static int getNextId()
{
return nextId; // returns static field
}
public static void main(String[] args) // unit test
{
var e = new Employee("Harry", 50000);
System.out.println(e.getName() + " " + e.getSalary());
}
// unit test有什么用?A:给你展示下用法
}
类变量默认值:
变量类型 | 默认值 |
---|---|
int,byte,short,long | 0 |
char | 一个空格 |
float,double | 0.0 |
引用值(比如String) | null |
更改器(mutator)和访问器(accessor)方法
更改/访问 实例里的参数
LocalDate aThousandDaysLater = newYearsEve.plusDays(1000);
//不是修改器,该方法是生成一个+1000年的新变量;
CregorianCalendar someDay = new CregorianCalendar(1999, 11, 31);
someDay.add(Calendar.DAY_0F_M0NTH, 1000);
//CregorianCalendar.add是更改器方法
LocalDate.getYear();
//访问器方法人,只return值。
隐式参数
案例
public void raiseSalary(double byPercent)
{
double raise = salary * byPercent / 100;
salary += raise;
}
//实际调用:将num007的salary上涨百分之5.
num007.raiseSalary(5);
Q:如果不用salary命名方法类变量,还能运行吗?(隐式参数的变量名和类变量名要一样吗?)
A:错误,类内的方法会自动调用同类的实例域,假如隐式参数不是实例域则无法使用。
出现在方法名前的(num007对象)被叫做 隐式(implict)参数(方法调用的目标),括号内的数值为 显式参数
补:实例域,就是只能在类内的方法中用的域,包括implict
另一种隐式参数的表示方法:
public void raiseSalary(double byPercent)
{
double raise = this.salary * byPercent / 100;
this.salary += raise;
}
this关键字表示隐式参数,可以更好的显示隐式显式的关系
基于类的访问权限
对上面隐式参数的补充
方法可以访问所调用对象的私有数据。一个方法可以访问所属类的所有对象的私有数据
class Employee
{
public boolean equals(Employee other)
{
return name.equals(other.name) ;
}
}
典型的调用方式是
if (harry.equals(boss));
equals同时访问harry和boss的私有域,因为 harry和boss都是 Employee的类对象,Employee 类的方法可以访问 Employee 类的任何一个对象的私有域。
静态域和静态方法
静态域
将域定义为 static;
在java中,每一个对象对于类内的 实例域操作都是基于(引用的拷贝),比如:1000个Employee对象有1000个id(实例域) 但是只有一个静态域 nextID,即使没有对象静态域也存在。 静态域属于类,不属于对象 书中 “只有一个”的意思是只有一个,没有拷贝;肯定可以有多个静态的变/常量。
静态常量
eg:Math类中的静态常量
public static final double PI = 3.14……;
往后可以用Math.PI调用
tip:
实例域变量 最好设计为私有,但常量无法改变,设为public反而可以简化之。
静态方法
不能向对象实时操作的方法
eg: Math.pow(x,a); 不使用任何对象,换句话说,没有隐式参数
不能访问实例域,可以访问静态域
工厂方法
不利用构造器来创建对象,生成不同风格的对象。
Q:工厂方法和构造器有什么不同
LocalDate date = LocalDate.now();//获取当前日期,eg: 2019-10-22
Var x = new Date();
main 方法
不对对象进行操作,用于对类的演示
方法参数
按值调用 & 按引用调用
按值:方法接受调用者提供的值
按引用:方法接受的是变量地址
Java总是采用 按值调用,具体来说,方法得到的是所有参数值的拷贝(所以不能修改传入的数值)
Java的方法参数类型
- 基本数据类型(数字,Boolean)
- 对象引用(难点)
in 基本数据类型:
eg:
Public static void tripleVal(double x)//无效
{
x = x*3;
}
无效原因:
方法只是对x的拷贝进行了乘三,方法结束即丢弃;
想要修改参数,需要用到对象引用的参数;
eg:
Public static void tripleVal(Employee x)//有效,x为100的话就翻3倍
{
x.raiseSalary(200);
}
harry = new Employee(...);
tripleVal(harry);
原因:
- x为harry值的拷贝,而x为对象引用,拷贝的其实是harry引用对象的 Employee的实例
- raiseSalary应用于引用,同时对引用的对象操作
- 结束后x不适用,被引用的实例的域改变。
对象引用:我们创建的其实都是那个对象的引用(索引),——在Java里都是通过引用来操纵对象的。
对象引用及其他的拷贝同时引用同一个对象。
StringBuffer s;
s = new StringBuffer("Hello World!");
第一个语句仅为引用(reference)分配了空间,
而第二个语句则通过调用类(StringBuffer)的构造函数StringBuffer(String str)为类生成了一个实例(或称为对象)。
这两个操作被完成后,对象的内容则可通过s(引用)进行访问
总结:
对于第三点的补充说明:
swap(a,b) 交换方法,修改的是两个变量引用的拷贝,而无法修改真正的引用(java按值传递不是按引用传递)
**
对象构造
重载
如果多个方法(比如, StringBuilder 构造器方法)有 相同的名字、不同的参数,便产生了重载。编译器必须挑选出具体执行哪个方法,它通过用 各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。如 果编译器找不到匹配的参数, 就会产生编译时错误,因为根本不存在匹配, 或者没有一个比 其他的更好。(这个过程被称为重载解析(overloading resolution)。
eg:
无参数构造器
对象由无参数构造函数创建时, 其状态会设置为 适当的默认值。
public Employee() //无参数在这里
{
name = "";
salary = 0;
hireDay = LocalDate,now();
}
可以用 e = new Employee();
如果是 Employee(String name, double salary....)就叫有参数构造器
再用e = new Employee();则会报错
调用另一个构造器
this关键字: 第二个作用 调用同一类适合的另一个构造器
有一定的好处,即对于 公共构造器(2参数E构造器)只用调用一次,剩下的用1参数的就好
设计技巧
- 一定要保证关键数据私有
- 一定要对数据初始化 最好不要依赖于系 统的默认值, 而是应该显式地初始化所有的数据
- 不要在类中使用过多的基本类型
- 不是所有的域都需要独立的域访问器和域更改器
- 优先使用不可变的类
- .将职责过多的类进行分解