PL/SQL中的对象类型是一种自定义的复合类型,与包的定义很相似,由两部分组成:
- 对象类型规范:是对象与应用的接口,用于定义对象的公用属性和方法。
- 对象类型体:用于实现对象类型规范所定义的公用方法。
在对象类型规范中定义对象属性时,必须要提供属性名和数据类型,对象类型属性可以使用多数Oracle数据类型,但是不能使用以下类型:
- LONG和LONG RAW
- ROWID和UROWID
- PL/SQL特定的类型,如BINARY_INTEGER、BOOLEAN、%TYPE、%ROWTYPE、REF CURSUR、RECORD等。
在定义对象类型的属性时,不能指定对象属性的默认值,也不能指定NOT NULL选项。
对象类型的方法有以下几种:
- 构造方法:该方法类似于Java语言中的构造函数,用来初始化一个对象类型并返回对象的实例。
- MEMBER方法:该方法允许对象的实例进行调用,在MENBER方法中可以访问对象实例的数据,通常称为实例方法或成员方法。
- STATIC方法:该方法可以直接在对象类型上进行调用,它用于在对象类型上执行全局操作,通常称为静态方法。
- MAP方法:用于在多个对象间排序的映射方法。
- ORDER方法:用于在两个对象实例间排序的排序方法。
定义对象类型的语法比较复杂,选项比较多,所以我们直接来看代码来了解怎么定义一个对象。
首先来定义一个对象类型规范:
--定义对象类型规范employee_obj
CREATE OR REPLACE TYPE employee_obj AS OBJECT (
--定义对象类型属性
empno NUMBER(4),
ename VARCHAR2(20),
job VARCHAR2(20),
sal NUMBER(10,2),
comm NUMBER(10,2),
deptno NUMBER(4),
--定义对象类型方法
MEMBER PROCEDURE Change_sal(p_empno NUMBER,p_sal NUMBER),
MEMBER PROCEDURE Change_comm(p_empno NUMBER,p_comm NUMBER),
MEMBER PROCEDURE Change_deptno(p_empno NUMBER,p_deptno NUMBER),
MEMBER FUNCTION get_sal(p_empno NUMBER) RETURN NUMBER,
MEMBER FUNCTION get_comm(p_empno NUMBER) RETURN NUMBER,
MEMBER FUNCTION get_deptno(p_empno NUMBER) RETURN INTEGER
) NOT FINAL; --指定该类可以被继承,如果指定FINAL,表示该类无法被继承
然后来定义对象体:
--定义对象类型体
CREATE OR REPLACE TYPE BODY employee_obj
AS
MEMBER PROCEDURE change_sal (p_empno NUMBER, p_sal NUMBER)
IS --定义对象成员方法,更改员工薪资
BEGIN
UPDATE emp
SET sal = p_sal
WHERE empno = p_empno;
END;
MEMBER PROCEDURE change_comm (p_empno NUMBER, p_comm NUMBER)
IS --定义对象成员方法,更改员工提成
BEGIN
UPDATE emp
SET comm = p_comm
WHERE empno = p_empno;
END;
MEMBER PROCEDURE change_deptno (p_empno NUMBER, p_deptno NUMBER)
IS --定义对象成员方法,更改员工部门
BEGIN
UPDATE emp
SET deptno = p_deptno
WHERE empno = p_empno;
END;
MEMBER FUNCTION get_sal (p_empno NUMBER)
RETURN NUMBER
IS --定义对象成员方法,获取员工薪资
v_sal NUMBER (10, 2);
BEGIN
SELECT sal
INTO v_sal
FROM emp
WHERE empno = p_empno;
RETURN v_sal;
END;
MEMBER FUNCTION get_comm (p_empno NUMBER)
RETURN NUMBER
IS --定义对象成员方法,获取员工提成
v_comm NUMBER (10, 2);
BEGIN
SELECT comm
INTO v_comm
FROM emp
WHERE empno = p_empno;
RETURN v_comm;
END;
MEMBER FUNCTION get_deptno (p_empno NUMBER)
RETURN INTEGER
IS --定义对象成员方法,获取员工部门
v_deptno INT;
BEGIN
SELECT deptno
INTO v_deptno
FROM emp
WHERE empno = p_empno;
RETURN v_deptno;
END;
END;
每一个MEMBER类型方法都隐式地声明了一个内联参数SELF,它代表了对象类型的一个实例,总是被传递给成员方法的第一个参数。实际上在方法体内,也可以不用SELF。以下两行代码效果是一样的:
MEMBER FUNCTION get_sal RETURN NUMBER IS
BEGIN
RETURN SELF.sal;
--RETURN sal;
END;
当没有显式使用SELF关键字时,实际上也是隐式地使用了这个关键字。
当定义了 一个对象类型后,系统会提供一个接收与每个属性相对应的参数的构造函数。因此大多数情况下不需要自己再编写构造函数。不过我们也可以处于如下的目的来自定义构造函数:
- 为对象提供初始化功能,以避免许多具有特别用途的过程只初始化对象的不同部分,可以通过构造函数进行统一初始化。
- 可以在构造函数中为某些属性提供默认值。
- 出于维护性的考虑,在新的属性添加到对象中时,避免要更改调用构造函数的应用程序中的代码,这样可以使已经存在的构造函数调用继续工作。
自定义构造函数使用CONSTRUCTOR关键字进行声明。
--定义对象类型规范
CREATE OR REPLACE TYPE salary_obj AS OBJECT (
percent NUMBER(10,4), --定义对象属性
sal NUMBER(10,2),
--自定义构造函数
CONSTRUCTOR FUNCTION salary_obj(p_sal NUMBER) RETURN SELF AS RESULT)
INSTANTIABLE --可实例化对象
FINAL; --不可以继承
/
--定义对象类型体
CREATE OR REPLACE TYPE BODY salary_obj
AS
--实现重载的构造函数
CONSTRUCTOR FUNCTION salary_obj (p_sal NUMBER)
RETURN SELF AS RESULT
AS
BEGIN
SELF.sal := p_sal; --设置属性值
SELF.PERCENT := 1.12; --为属性指定初值
RETURN;
END;
END;
/
这两个方法是互斥的,也就是说一次只能在一个对象类型中定义其中一个方法。
- MAP方法:该函数会将对象实例根据一定的调用规则返回DATE、NUMBER、VARCHAR2类型的标量类型,在映射对象类型为标量类型后,就可以通过对标量函数的比较来得到结果了。
- ORDER方法:ORDER方法只能对两个对象进行比较,必须是返回数值型结果的函数,根据结果返回正数、负数或0。该方法只有两个参数,SELF和另一个要比较的对象类型,如果传递该参数为NULL,则返回NULL。
看代码:
--定义一个对象规范,该规范中包含MAP方法
CREATE OR REPLACE TYPE employee_map AS OBJECT (
--定义对象类型属性
empno NUMBER (4),
sal NUMBER (10, 2),
comm NUMBER (10, 2),
deptno NUMBER (4),
MAP MEMBER FUNCTION convert RETURN REAL --定义一个MAP方法
)
NOT FINAL;
--定义一个对象类型体,实现MAP函数
CREATE OR REPLACE TYPE BODY employee_map AS
MAP MEMBER FUNCTION convert RETURN REAL IS --定义一个MAP方法
BEGIN
RETURN sal+comm; --返回标量类型的值
END;
END;
在定义了MAP函数后,PL/SQL会隐式地通过调用MAP函数在多个对象间进行排序或比较。例如下面创建了一个emp_map_tab
的对象表,向这个对象表插入多个对象,然后就可以对这个对象表进行对象的排序:
--创建employee_map类型的对象表
CREATE TABLE emp_map_tab OF employee_map;
--向对象表中插入员工薪资信息对象。
INSERT INTO emp_map_tab VALUES(7123,3000,200,20);
INSERT INTO emp_map_tab VALUES(7124,2000,800,20);
INSERT INTO emp_map_tab VALUES(7125,5000,800,20);
INSERT INTO emp_map_tab VALUES(7129,3000,400,20);
SELECT VALUE(r) val,r.sal+r.comm FROM emp_map_tab r ORDER BY 1;
再来看ORDER方法:
--定义一个对象规范,该规范中包含ORDER方法
CREATE OR REPLACE TYPE employee_order AS OBJECT (
--定义对象类型属性
empno NUMBER (4),
sal NUMBER (10, 2),
comm NUMBER (10, 2),
deptno NUMBER (4),
ORDER MEMBER FUNCTION match(r employee_order) RETURN INTEGER --定义一个ORDER方法
)
NOT FINAL;
--定义一个对象类型体,实现ORDER函数
CREATE OR REPLACE TYPE BODY employee_order AS
ORDER MEMBER FUNCTION match(r employee_order) RETURN INTEGER IS
BEGIN
IF ((SELF.sal+SELF.comm)<(r.sal+r.comm)) THEN
RETURN -1; --可为任何负数
ELSIF ((SELF.sal+SELF.comm)>(r.sal+r.comm)) THEN
RETURN 1; --可为任何正数
ELSE
RETURN 0; --如果相等则为0
END IF;
END match;
END;
定义了ORDER函数后,就可以对两个对象进行比较:
DECLARE
emp1 employee_order:=employee_order(7112,3000,200,20); --定义员工1
emp2 employee_order:=employee_order(7113,3800,100,20); --定义员工2
BEGIN
--对员工1和2进行比较,获取返回结果
IF emp1>emp2 THEN
DBMS_OUTPUT.put_line('员工1的薪资加提成比员工2大!');
ELSIF emp1THEN
DBMS_OUTPUT.put_line('员工1的薪资加提成比员工2小!');
ELSE
DBMS_OUTPUT.put_line('员工1的薪资加提成与员工2相等!');
END IF;
END;
同样,使用ORDER成员方法的对象也可以插入到对象表中,使用对象表的排序功能利用ORDER成员方法进行排序:
--创建employee_order类型的对象表
CREATE TABLE emp_order_tab OF employee_order;
--向对象表中插入员工薪资信息对象。
INSERT INTO emp_order_tab VALUES(7123,3000,200,20);
INSERT INTO emp_order_tab VALUES(7124,2000,800,20);
INSERT INTO emp_order_tab VALUES(7125,5000,800,20);
INSERT INTO emp_order_tab VALUES(7129,3000,400,20);
SELECT VALUE(r) val,r.sal+r.comm FROM emp_order_tab r ORDER BY 1;
如果声明了一个对象类型的变量而没有进行实例化,那么对象变量的值为NULL,如果尝试读取或设置一个未初始化的对象属性会引发预定义异常ACCESS_INTO_NULL
。一个好的编程习惯是在定义对象类型的变量时,就使用构造函数进行初始化,即便不设置值,也可以将构造函数的属性显初始化为NULL,此时对象实例就已经存在,可以使用。代码如下:
DECLARE
o_emp employee_order := employee_order (NULL, NULL, NULL, NULL); --初始化对象类型
BEGIN
o_emp.empno := 7301; --为对象类型赋值
o_emp.sal := 5000;
o_emp.comm := 300;
o_emp.deptno := 20;
END;
对象方法的调用如下:
--employee_method对象类型的实例方法与静态方法列表
CREATE OR REPLACE TYPE employee_method AS OBJECT (
empno NUMBER (4),
sal NUMBER (10, 2),
comm NUMBER (10, 2),
deptno NUMBER (4),
--定义对象类型方法
MEMBER PROCEDURE change_sal, --实例方法,可以访问对象本身的属性
MEMBER FUNCTION get_sal RETURN NUMBER,
--静态方法,不能访问对象本身的属性,只能访问静态数据
STATIC PROCEDURE change_deptno (empno NUMBER, deptno NUMBER),
STATIC FUNCTION get_sal (empno NUMBER) RETURN NUMBER
)
NOT FINAL;
--演示调用employee_method的实例方法与静态方法
DECLARE
o_emp employee_method:=employee_method(7369,5000,800,20);
v_sal NUMBER(10,2);
BEGIN
v_sal:=o_emp.get_sal; --调用对象实例方法
DBMS_OUTPUT.put_line('对象实例级别的工资为:'||v_sal);
v_sal:=employee_method.get_sal(o_emp.empno); --调用静态方法
DBMS_OUTPUT.put_line('对象类型级别的工资为:'||v_sal);
END;
嵌套对象类型是指在一个对象中嵌入另一个对象类型。来看代码,先定义地址对象类型:
--定义地址对象类型规范
CREATE OR REPLACE TYPE address_type
AS OBJECT
(street_addr1 VARCHAR2(25), --街道地址1
street_addr2 VARCHAR2(25), --街道地址2
city VARCHAR2(30), --城市
state VARCHAR2(20), --省份
zip_code NUMBER, --邮政编码
--成员方法,返回地址字符串
MEMBER FUNCTION toString RETURN VARCHAR2,
--MAP方法提供地址比较函数
MAP MEMBER FUNCTION mapping_function RETURN VARCHAR2
)
--定义地址对象类型体,实现成员方法与MAP函数
CREATE OR REPLACE TYPE BODY address_type
AS
MEMBER FUNCTION tostring
RETURN VARCHAR2 --将地址属性转换为字符形式显示
IS
BEGIN
IF (street_addr2 IS NOT NULL)
THEN
RETURN street_addr1
|| CHR (10)
|| street_addr2
|| CHR (10)
|| city
|| ','
|| state
|| ' '
|| zip_code;
ELSE
RETURN street_addr1 || CHR (10) || city || ',' || state || ' '
|| zip_code;
END IF;
END;
MAP MEMBER FUNCTION mapping_function --定义地址对象MAP函数的实现,返回VARCHAR2类型
RETURN VARCHAR2
IS
BEGIN
RETURN TO_CHAR (NVL (zip_code, 0), 'fm00000')
|| LPAD (NVL (city, ''), 30)
|| LPAD (NVL (street_addr1, ''), 25)
|| LPAD (NVL (street_addr2, ''), 25);
END;
END;
/
再来定义包含地址对象类型的对象类型:
--定义一个对象规范,该规范中包含地址对象类型的属性
CREATE OR REPLACE TYPE employee_addr AS OBJECT (
empno NUMBER (4),
sal NUMBER (10, 2),
comm NUMBER (10, 2),
deptno NUMBER (4),
addr address_type,
MEMBER FUNCTION get_emp_info RETURN VARCHAR2
)
NOT FINAL;
--定义对象类型体,实现get_emp_info方法
CREATE OR REPLACE TYPE BODY employee_addr
AS
MEMBER FUNCTION get_emp_info
RETURN VARCHAR2 --返回员工的详细信息
IS
BEGIN
RETURN '员工'||SELF.empno||'的地址为:'||SELF.addr.toString;
END;
END;
使用如下:
DECLARE
o_address address_type;
o_emp employee_addr;
BEGIN
o_address:=address_type('玉兰一街','二巷','深圳','DG',523343);
o_emp:=employee_addr(7369,5000,800,20,o_address);
DBMS_OUTPUT.put_line('员工信息为'||o_emp.get_emp_info);
END;
为了从一个父对象中继承,在子对象中使用UNDER关键字,指定一个父对象名称,该父对象必须是使用NOT FINAL关键字定义的对象。
先来定义一个父对象:
CREATE OR REPLACE TYPE person_obj AS OBJECT (
person_name VARCHAR (20), --人员姓名
gender VARCHAR2 (10), --人员性别
birthdate DATE, --出生日期
address VARCHAR2 (50), --家庭住址
MEMBER FUNCTION get_info
RETURN VARCHAR2 --返回员工信息
)
NOT FINAL; --人员对象可以被继承
CREATE OR REPLACE TYPE BODY person_obj --对象体
AS
MEMBER FUNCTION get_info
RETURN VARCHAR2
IS
BEGIN
RETURN '姓名:' || person_name || ',家庭住址:' || address;
END;
END;
再来定义子对象:
--对象类型使用UNDER语句从person_obj中继承
CREATE OR REPLACE TYPE employee_personobj UNDER person_obj (
empno NUMBER (6),
sal NUMBER (10, 2),
job VARCHAR2 (10),
MEMBER FUNCTION get_emp_info
RETURN VARCHAR2
);
CREATE OR REPLACE TYPE BODY employee_personobj AS
MEMBER FUNCTION get_emp_info RETURN VARCHAR2 IS
BEGIN
--在对象类型体中可以直接访问在父对象中定义的属性
RETURN '员工编号:'||SELF.empno||' 员工名称:'||SELF.person_name||' 职位:'||SELF.job;
END;
END;
使用如下:
DECLARE
o_emp employee_personobj; --定义员工对象类型的变量
BEGIN
--使用构造函数实例化员工对象
o_emp:=employee_personobj('张小五','F',
TO_DATE('1983-01-01','YYYY-MM-DD'),
'中信',7981,5000,'Programmer');
DBMS_OUTPUT.put_line(o_emp.get_info); --输出父对象的人员信息
DBMS_OUTPUT.put_line(o_emp.get_emp_info); --输出员工对象中的员工信息
END;
可以看到,由于employee_personobj
合并了person_obj
对象,因此在构造函数中初始化对象时,必须先对父对象中的属性进行初始化,然后初始化子对象类型中的属性。
在前面介绍子程序和包时,讨论过重载技术,所为的重载就是定义多个具有同名的函数或过程,但是参数类型或个数不同,由编译器根据调用参数确定执行哪一个子程序。这种重载方式也称为静态多态。在使用对象继承时,也可以使用方法重载。这种重载使用了动态方法调用的能力,也称为动态多态或运行时多态。
对象方法的重载使用OVERRIDING关键字,不是个根据参数的个数或类型来决定调用哪一个方法,而是根据优先级进行调用,也就是总是先调用子类的方法。看代码:
--对象类型使用UNDER语句从person_obj中继承
CREATE OR REPLACE TYPE employee_personobj UNDER person_obj (
empno NUMBER (6),
sal NUMBER (10, 2),
job VARCHAR2 (10),
MEMBER FUNCTION get_emp_info
RETURN VARCHAR2
--定义重载方法
OVERRIDING MEMBER MEMBER FUNCTION get_info RETURN VARCHAR2
);
CREATE OR REPLACE TYPE BODY employee_personobj AS
MEMBER FUNCTION get_emp_info RETURN VARCHAR2 IS
BEGIN
--在对象类型体中可以直接访问在父对象中定义的属性
RETURN '员工编号:'||SELF.empno||' 员工名称:'||SELF.person_name||' 职位:'||SELF.job;
END;
--实现重载方法
OVERRIDING MEMBER MEMBER FUNCTION get_info RETURN VARCHAR2 AS
BEGIN
RETURN '员工编号:'||SELF.empno||' 员工名称:'||SELF.person_name||' 职位:'||SELF.job;
END;
END;