精华内容
下载资源
问答
  • 实验四 数据库模式对象管理

    千次阅读 2016-12-31 20:26:55
    一、实验目的 1) 掌握表、索引、索引化表的概念及管理 2) 掌握分区的概念及分区管理 ...表空间、用户、角色、目录、概要文件及上下文问等数据库对象属于某个模式吗? 3) 如何进行模式的选择与切换

    一、实验目的

    1)  掌握表、索引、索引化表的概念及管理

    2)  掌握分区的概念及分区管理

    3)  掌握外部表的概念及其管理

    4)  了解簇、视图、序列、同义词、数据库链接等的管理

    二、预习内容

    1)  什么是模式,模式有什么特点?

    2)  Oracle数据库中模式对象有哪些?表空间、用户、角色、目录、概要文件及上下文问等数据库对象属于某个模式吗?

    3)  如何进行模式的选择与切换?

    4)  说明索引的作用,以及Oracle数据库中索引的类型。

    三、实验环境

    32位Windows XP/WindowsServer2000/Windows Server2003 +Oracle10g环境

    四、实验内容

    1)  按照下列表结构利用SQL语句创建class、student两个表。

    class表

    列名

    数据类型

    约束

    备注

    CNO

    NUMBER(2)

    主键

    班号

    CNAME

    VARCHAR2(20)

     

    班名

    NUM

    NUMBER(3)

     

    人数

    student表

    列名

    数据类型

    约束

    备注

    SNO

    NUMBER(4)

    主键

    学号

    SNAME

    VARCHAR2(10)

    唯一

    姓名

    SAGE

    NUMBER

     

    年龄

    SEX

    CHAR(2)

     

    性别

    CNO

    NUMBER(2)

     

    班级号

    create table class( CNO number(2) primary key, CNAME varchar2(20), NUM number(3) ) 

    create table student( SNO number(4) primary key, SNAME varchar2(10) unique, SAGE number, SEX char(2), CNO number(2) ) 

    2)  为student表添加一个外键约束,其CNO列参照class表的CNO列。

    alter table student add constraint fk_cno foreign key (cno) references class(cno) deferrable;

    3)  为student表的SAGE列添加一个检查约束,保证该列取值在0~100之间。

    alter table student add constraint ck_sage check (sage>0 and sage<=100);

    4)  为student表的SEX列添加一个检查约束,保证该列取值为“M”或“F”,且默认值为“M”。

    alter table student add constraint ck_stu check(sex='M' or sex='F') modify sex default 'M'; 

    5)  在class表的CNAME列上创建一个唯一性索引。

    create unique index ind_cname on class(cname);

    6)  利用子查询分别创建一个事务级的临时表和会话级的临时表,其结构与student表的结构相同。

    Create global temporary table stu_trans oncommit delete rows as select * from student;

    Create global temporary table stu_sess oncommit preserve rows as select * from student;

    7)  创建一个索引化表,其表结构与student表相同。

    Create table index_student(sno primary key,sname , sage, sex, cno)organization index as select * from student;

    8)  创建一个student_range表(列、类型与student表的列、类型相同),按学生年龄分为3个区,低于20岁的学生信息放入part1区,存储在EXAMPLE表空间中;20-30岁的学生信息放在part2区,存放在ORCLTBS1表空间中;其他数据放在part3区,存放在ORCLTBS2表空间中。

    create table student_range(sno number(4)primary key,sname varchar2(10),sage number,

    sex char(2),cno number(2)) partition byrange(sage)(partition part1 values less than(20) tablespace example,partitionpart2 values less than(30) tablespace orcltbs1,partition part3 values lessthan(maxvalue) tablespace orcltbs2)

    9)  创建一个student_list表(列、类型与student表的列、类型相同),按学生性别分为两个区。

    create table student_list(sno number(4) primary key,snamevarchar2(10),sage number,sex char(2),cno number(2))partition bylist(sex)(partition man values('M') tablespace orcltbs1,partition womanvalues('F') tablespace orcltbs2)

     

    10)    创建一个起始值为10000的序列,步长为2,最大值为100000,不可循环。

    Create sequence sequ1 increment by 2 start with 10000 maxvalue100000 nocycle;

    11)    为scott模式下的emp表创建一个公共同义词,名称为employee。

    createpublic synonym emp_info for scott.emp;

    12)    创建一个视图,包含员工号、员工名和该员工领导的员工号、员工名。

    Create view employee_v(empno,ename,mgr_no,mgr_name) 

    As Select e1.empno,e1.ename,e1.mgr,e2.ename  

    from scott.emp e1 join scott.emp e2 on e1.mgr=e2.empno;

    五、实验步骤

    写出每一条实验内容对应的SQL语句。

    六、思考题

    1)  思考数据库中使用索引的优点和缺点,索引是如何工作的

    优点:

    第一,通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 
    第二,可以大大加快数据的检索速度,这也是创建索引的最主要的原因。 
    第三,可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 
    第四,在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。 
    第五,通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

    缺点:

    第一,  创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 

    第二,索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。 
    第三,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。

    工作原理

    SQL 当一个新表被创建之时,系统将在磁盘中分配一段以8K为单位的连续空间,当字段的值从内存写入磁盘时,就在这一既定空间随机保存,当一个8K用完的时候, SQLS指针会自动分配一个8K的空间。这里,每个8K空间被称为一个数据页(Page),又名页面或数据页面,并分配从0-7的页号,每个文件的第0页记录引导信息,叫文件头(File header);每8个数据页(64K)的组合形成扩展区(Extent),称为扩展。全部数据页的组合形成堆(Heap)。 SQLS 规定行不能跨越数据页,所以,每行记录的最大数据量只能为8K。这就是charvarchar这两种字符串类型容量要限制在8K以内的原因,存储超过 8K的数据应使用text类型,实际上,text类型的字段值不能直接录入和保存,它只是存储一个指针,指向由若干8K的文本数据页所组成的扩展区,真正的数据正是放在这些数据页中。

    2)  Oracle数据库中对表和索引进行分区管理有何优点?

    对巨型表进行分区后即可以对整个表进行操作也可以针对特定的分区进行操作从而简化了对表的管理和维护。

    七、实验总结

    通过本次实验我掌握表、索引、索引化表的概念及管理以及分区的概念及分区管理。应加强练习,才能在实验的时候游刃有余。

    展开全文
  • Sql小白入门(三)管理数据库对象

    千次阅读 2017-01-10 22:18:46
     数据库对象是数据库里定义的、用于存储或引用数据的对象,比如表、视图、促,序列、索引和异名。本章的内容以表为主,因为它是关系型数据库里最主要、最简单的数据存储形式。 2、什么是规则  规则是与数据库

        前面两篇文章都是概念性的,全部文字描述!看起来比较费劲!有的读者在看文章时是不是会睡着呢!偷笑今天我们就正式进入到数据库和表的实战中!开始下文吧!

    一、概念。

    1、什么是数据库对象

        数据库对象是数据库里定义的、用于存储或引用数据的对象,比如表、视图、促,序列、索引和异名。本章的内容以表为主,因为它是关系型数据库里最主要、最简单的数据存储形式。

    2、什么是规则

       规则是与数据库某个用户名相关联的数据库对象结婚。相应的用户名被称为规则所有人,或是关联对象组的所有人。数据库里可以有一个或多个规则。用户至于同名规则相关联,通常情况下反之亦然。一般来说,当用户创建一个对象时,就是在自己的规划里创建了它,除非明确指定在另一个规划里创建它。因此,根据在数据库里的权限,用户可以创建、操作和删除对象。规划可以只包含一个表,也可以包含无数个对象,其上限由具体的Sql实现决定。

    假设我们从管理员获得了一个数据用户名和密码,用户名是USER1,我们登录到数据库并创建一个名为EMPLOYEE_TBL的表,这时对于数据库来说,表的实际名称是USER1.EMPLOYEE_TBL,这个表的规划名是USER1,也就是这个表的所有者。这样,我们就为这个规划创建了第一个表。

    当我们访问自己所拥有的表时(在级的规划里),不必引用规划名称。举例来说,使用下面两种方式都可以引用刚才创建的表:

    MPLOYEE_TBL

    USER1.MPLOYEE_TBL

    我们当然喜欢使用第一种方法,因为它简单,需要敲击键盘的次数比较少。如果其他用户要访问这个表,就必须知道规划名称,如下所示;

    USER1.MPLOYEE_TBL

    3.表,数据的主要存储方式

    表是关系型数据库里最主要的数据存储对象,其最简单形式是由行和列组成,分别都包含着数据。表在数据库占据实际的物理空间,可以是永久的或是临时的。

    (1).列

    字段在关系型数据库也被称为列,它是表的组成部分,被设置为特定的数据类型。数据类型觉得了什么样的数据尅保存在相应的列中,从而确保了数据的完整性。

    每个数据库表都至少要包含一列、列元素在表里用于保存特定类型的数据,比如人名或地址。举例来说,姓名就可以作为顾客表里一个有效的列。

    一般来说,列的名称应该是连续的字符串,其长度在不同的Sql实现中都是由明确的规定。我们一般使用下划线作为分隔符,比如表示顾客姓名的列可以命名为CUSTOMER_NAME更好一些。这样做可以提高数据库对象的可读性。读者也可以使用其他命名规则,例如驼峰匹配,以满足特定的需求。对于一个数据库开发团队来说,明确一个命名规则,并在开发过程中严格遵守这一规则,是非常重要的。

    列中最常见的数据类型是字符串。这一数据可以保存为大写或小写字符,应该根据数据的使用方式具体选择。在大多数情况下,出于简化和一致的目的,数据是以大写存储的。如果数据库里存储的数据具有不同的大小写,我们可以根据需要利用函数把数据转化为大写或小写。

    列也可以指定为NULl或NOT NULL,当设置为NOT NULL时,表示其中必须包含数据;这是为NULL是,就表示不包含数据。NULL不是空白,而是类似一个空的字符串,在数据库中占据了一个特殊的位置。因此,如果某一个位置缺少数据,就尅使用NULL。

    (2).行

    行是数据库表里的一条记录。举例来说,顾客表里的一行数据可能包含顾客的标识号码、姓名、地址、电话号码、传真号码等。行由字段组成,表最少可以包含一行数据,也可以包含数以百万计的记录。

    二、表。

    1.CREATE TABLE命令。

    Sql里的CREATE TABLE语句用于创建表。虽然创建表的实际操作十分简单,但在执行CREATE TABLE命令之前,应该花更多的时间和精力来设计表的结构,这样可以节省反复修改表结构而浪费的时间。

    在创建表时,需要考虑以下一些基本问题。

    (1).表里会包含什么类型的数据;

    (2).表的名称是什么;

    (3).哪个(或哪些)列组成主键;

    (4).列(字段)的名称是什么;

    (5).每一列的数据类型是什么;

    (6).每一列的长度是多少;

    (7).表里的哪些列可以是NULL;

    在考虑了这些问题之后,实际的CREATE TABLE命令就很简单了!

    创建表的基本语法如下所示:

    CREATE TABLE table_name(

    field1 data_type [ not null],

    field2 data_type [ not null],

    field3 data_type [ not null],

    );

    2.命名规范

    在为对象选择名称时,特别是表和列的名称,应该让名称反应出所保存的数据。比如说,保存雇员信息的表可以命名为EMPLOYEE_TBL。列的名称也是如此,比如说,保存雇员电话号码的列,显然命名为PHONE_NUMBER是比较合适的。

    3.ALTER TABLE命令

    在表被创建之后,我们可以使用ALTER TABLE命令对其进行修改。可以调节列、删除列、修改列定义、添加和去除约束,在某些实现中可以修改表的值。ALTER TABLE命令的

    (1).修改表的元素。

    列的属性是其所包含数据的规则和行为。利用ALTER TABLE命令修改列的属性,在此“属性”的含义是:

    列的数据类型,列的长度、有效位或标度,列值能否为空。

    下面的范例是使用ALTER TABLE命令修改表EMPLOYEE_TBL的EMP_ID列,

    ALTER TABLE EMPLOYEE_TBL MODIFY EMP_ID VARCHAR(10)

    这一列定义的数据类型没有变,但是长度从9变为10。

    (2).添加列。

    如果表已经包含数据,这是添加的列就不能定义为NOT NULL,这是一条基本规则。NOT NULL意味着这一列在每条记录里都是必须包含数据。所以,在添加一条定义为NOT NULL的列时,如果现有的记录没有包含新的列所需的数据,我们就会陷入到自相矛盾的境地。

    因此,强行向表添加一列的方法如下:

    添加一列,把它定义为NULL(这一行不一定要包含数据),

    给这个新列在每条记录里都插入数据,

    把列的定义修改为NOT  NULL.

    (3).添加自动增加的列。

    有时我们需要一列的数据能够自动增加,从而让每一行都具有不同的序号。在很多情况下都需要这样做,比如数据中如果没有适合充当主键的值,或是我们想利用序列号对数据进行排序。创建自动增加的列是相当简单的。MySql提供了SERIAL方法为表生成真正的唯一值,如下所示:

    CREATE TABLE TEST_INCREMENT(

     ID SERIAL,

    TEST_NAME VARCHAR(20)

    );

    PS:列的默认属性是NULL,所以在CREATE TABLE语句里不必明确设置。但NOT NULL必须明确指定。

    (4).修改列

    在修改现有表里的列时,需要考虑很多因素。下面是修改列的一些通用规则:

    列的长度可以增加到特定数据类型所允许的最大长度;

    如果想缩短某列的长度,则长度要求这一列在表里所有数据的长度都小于或等于新长度;

    数值数据的位数可以增加;

    如果要缩短数值数据的位数,则必须要求这一列在表里所有数值的位数小于或等于新指定的位数;

    数值里的小数位数可以增加或减少;

    列的数据类型一般是可以改变的。于其它表的列,或是被其它表的列所引用,在撤销这一列时就可能发生问题。

    有些实现会限制用户使用ALTER TABLE的某些选项。举例来说,可能不允许从表里撤销列。为了绕过这种限制,我们可以撤销整个表,然后重建新的表。如果某一列是依赖于其它表的列,或是被其它表的列所引用,在撤销这一列时就可能发生问题。

    4.删除表

    删除表示一种相当简单的操作。如果使用了RESTRICT选项,并且表被视图或约束所引用,DROP语句就会返回一个错误。当使用了CASCADE选项时,删除操作会成功执行,而且全部引用视图和约束都被删除。删除表的语句如下所示:

    drop table  table_name

    PS:删除表的操作务必指向准确,在删除表时,在提交命令之前要确保指定了表的规划名或所有者,否则可能误删除其它的表。如果使用多用户账户,在删除表之前一定要确定使用了适当的用户名链接数据库。

    三、完整性约束。

    完整性约束用于确定关系型数据库里数据的准确性和一致性。在关系型数据库里,数据完整性是通过引用完整性的概念实现的,而在引用完整性里包含了很多类型。

    1.主键约束

    主键是表里一个或多个用于实现记录唯一性的字段。虽然主键通常是由一个字段构成的,但也可以由多个字段组成。举例来数,雇员的社会保险号码或雇员被分配的标识号码都可以再雇员表里作为主键。主键的作用在于表里每条记录都具有唯一值。由于在雇员表里一般不会出现多条记录表示一个雇员的情况,所以雇员的标识号码可以作为主键。主键是在创建表时指定的。

    2.唯一性约束

    唯一性约束要求表里某个字符的值在每条记录里都是唯一的,这一点与主键类似。即使我们对一个字段设置了主键约束,也可以对另一个字段设置唯一性约束,尽管它不会被当做主键使用。

    3.外键约束

    外键是子表里的一个字段,引用父表里的主键。外键约束是确保表与表之间引用完整性的主要机制。一个被定义为外键的字段用于引用另一个表里的主键。

    4. NOT NULL 约束

    NOT NULL是一个可以用于字段的约束,它不允许字段包含NULL值;话句话时候,定义NOT NULL的字段在每条记录里都必须有值。在没有指定NOT NULL时,字段默认为NULL,也就是可以是NULL值。

    5.检查约束

    检查约束用于检查输入到特定字段的数据的有效性,可以提供后端的数据库编辑,虽然编辑通常是在前端程序里完成的。一般情况下,编辑功能限制了能够输入字段或对象的值,无论这个功能是在数据库还是在前端程序里实现的。检查约束为数据提供了另一层保护。

    7.去除约束

    利用ALTER TABLE命令的DROP CONSTRAINT选项可以去除已经定义的约束。

    PS: 有效实现允许终止约束,这样我们可以选择暂时中止它,而不是从数据库里去除它,稍后还尅再启动它。

    四、实战操作

        本系列博文中使用的数据库是MySql,有关MySql的下载和安装就不多说了,直接百度就知道了!并且在实际开发中,还使用了数据库可视化工具,例如SQLyog,使用这个工具可以快速开发、管理MySql数据库。在MySql命令行中输入的命令,目前只在MySql中验证有效,其他数据库没有验证过!有关SQLyog有问题,找度娘!偷笑

    安装完MySql后,打开SQLyog,新建一个连接,输入密码,如果显示如下图所示,就表示成功了,


    1.创建数据库。

    创建数据库有两种方式。

    (1).在mySQL命令提示符下,输入如下所示命令:
    MySQL>create datebase dbname;

    其中dbname是数据库名称。一定要记得命令最后还有“;”!

    (2).在SQLyog中,在左侧列表的空白处单击鼠标右键,弹出的菜单中,选择“创建数据库”,接着会弹出如下图的对话框,填写数据库名的选项框,我们这里填写“studensManager”,其中两个选择项选择默认值即可,接着单击“创建”按钮,“studensManager”数据库就创建成功了,在左边的数据库列表里会出现“studensManager”数据库。


    创建数据库成功后,接着就可以创建表了!

    PS:

    (1).在MySql命令行中,如果我们想查看目前都有哪些数据库?该使用什么命令呢?

    show databases; 一定要记得命令最后还有“;”!可以试试不输入“;”,是什么结果!

    便会显示目前MySql中有哪些数据库。例如我本机上显示如下图所示,


    (2).如何选择进入哪个数据库呢?

    命令: use  数据库名;

    例如,我本机显示如下,

    (3).那么如何查看当前使用的数据库
    命令:mysql> select database();

    例如,我本机显示如下,

    (4).查看数据库的表信息
    命令:mysql> show tables;


    2.创建表。

    还记得创建表的命令吗?CREATE TABLE TABLE_NAME(...);

    (1).使用命令创建。
    CREATE TABLE studens(sno INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,sname VARCHAR(20),INT(3) age,sex CHAR(2),birth DATE);

    (2).在SQLyog中,左侧的导航中选择所要操作的数据库,单击鼠标右键,从弹出的快捷菜单中选择创建表命令,创建新表。


    创建表成功后,就会在Tables目录下看到表students,


    3.修改表。

    如果我们要对已经创建成功的表的列进行修改,该如何操作呢?还记得前面说的吗?

    ().使用Alter命令!

    (2).在SQLyog中,选中要修改的表,选择更改表选项,就会显示类似一个创建表的窗口,接着在这个窗口中就可以修改表了!添加或者删除某一列,


    修改完后,点击Alter按钮!


    再次刷新表,就可以看到表多了一列!修改成功!

    五、小结。

    1.创建数据库命令。

    CREATE DATABASE database_name
    现在我们希望创建一个名为 "mserver_db" 的数据库。
    我们使用下面的 CREATE DATABASE 语句:
    CREATE DATABASE mserver_db

    2.创建表命令。

    CREATE TABLE 表名称
    (
    列名称1 数据类型,
    列名称2 数据类型,
    列名称3 数据类型,
    ....
    )

    例如创建一个Persons表,

    CREATE TABLE Persons
    (
    Id_P int,
    LastName varchar(255),
    FirstName varchar(255),
    Address varchar(255),
    City varchar(255)
    )
    3.修改列。

    如需在表中添加列,请使用下列语法:
    ALTER TABLE table_name
    ADD column_name datatype
    要删除表中的列,请使用下列语法:
    ALTER TABLE table_name
    DROP COLUMN column_name
    注释:某些数据库系统不允许这种在数据库表中删除列的方式 (DROP COLUMN column_name)。
    要改变表中列的数据类型,请使用下列语法:
    ALTER TABLE table_name
    ALTER COLUMN column_name datatype

    今天就到这里!下篇文章,我们继续表的操作!详情请看,Sql小白入门(四)CRUD
















    展开全文
  • java面向对象

    万次阅读 多人点赞 2018-08-21 16:51:59
    包括面向对象概念、类与对象的关系、封装、构造函数、this关键字、static关键字、单例设计模式、继承、多态、内部类、异常、包等java基础知识。 1、面向对象 面向对象是相对面向过程而言 面向对象和面向过程都是...

    本文内容是根据毕晓东老师的视频教程总结而得。包括面向对象概念、类与对象的关系、封装、构造函数、this关键字、static关键字、单例设计模式、继承、多态、内部类、异常、包等java基础知识。

    1、面向对象

    • 面向对象是相对面向过程而言
    • 面向对象和面向过程都是一种思想
    • 面向过程强调的是功能、行为
    • 面向对象:将功能封装进对象,强调具备了功能的对象
    • 面向对象是基于面向过程的

    面向过程例子:

    把大象放进冰箱里分为以下步骤:把冰箱门打开;把大象放进去;关上冰箱门(强调过程和过程中所涉及的行为(强调行为、动作、过程))。

    用面向对象思想考虑:无论是打开冰箱,放进大象,关闭冰箱,所有操作都是操作冰箱这个对象,所以只需要将所有功能都定义在冰箱这个对象上,冰箱上就有打开、存储、关闭得所有功能 。      

    由上可知,面向对象是一种思想,能让复杂问题简单化,程序员不需要了解具体的实现过程,只需要指挥对象去实现功能。例,面试官面试面试者就是面向对象的体现,面试官需要找具有编程功能的对象(面试者),而我就是一个具有编程功能的对象,面试完后,让面试者去编程,面试者就去实现编程功能。

    面向过程和面向对象图示:

    2、类与对象的关系

    面向对象三大基本特征:封装、继承、多态。而面向对象的过程就是找对象、建立对象、使用对象、维护对象的关系的过程。

    2.1类和对象的关系:

    类:是对现实生活中事物的描述。

    对象:就是这类事物,实实在在存在的个体。

    如现实生活中的对象:张三、李四。想要描述对象张三和李四,就需要提取对象中的共性内容。即对具体对象的共性的抽取。在描述时,这些对象的共性有:姓名、性别、年龄、学习java功能。而每个学员又有自己独有的姓名、性别、年龄、学习方式。

    在java中,描述是用类的方式实现,而类是通过new操作符所产生的实体来实现,而这个实体在堆内存中再映射到java中去。简单的说,描述就是class定义的类,具体对象就是对应java在堆内存中用new建立的实体

    描述事物其实就是在描述事物的属性和行为(方法),属性对应的是类中的变量,行为对应的是类中的函数(方法)。其实定义类,就是在描述事物,就是在定义属性和行为,属性和行为共同成为类中的成员(成员变量和成员方法)。

    示例:描述汽车。

    package com.vnb.javabase;
    public class Car {
       //描述颜色
       String color="red";
       //描述轮胎数
       int num = 4;
       //运行行为
       void run(){
          System.out.println("color:"+color+";  轮胎数:"+num);
       }
    }
    
    class CarDemo{
       public static void main(String[] args) {
          //生产汽车。在java中通过new操作符来完成,其实就是在堆内存中产生一个实体
          //car引用型变量(句柄),car就是一个类类型变量。类类型变量指向对象(该类产生的实体)
          //堆内存中有默认初始化值(color=null);而"red"称为显示初始化值;
          Car car = new Car();
    
          //需求:将已有车的颜色改为蓝色,指挥该对象做使用。在java中指挥方式是,对象.对象成员
          car.color="blue";
    
          //让车行驶起来
          car.run();
       }
    }

    生产汽车类在内存中的图示及解析:

    如上图,首先在栈内存中有一个Car c;然后在堆内存中new Car(),默认初始化color为null,num为0;再将color设置显示初始化值为”red”,将num设置显示初始化值为4;将new Car()产生的地址值0x0099赋给栈内存,再由栈指向堆中的0x0099的地址。

    生产两辆车(建立多个对象)在内存中的图示及解析:

    如上图所示,建立多个对象的过程:首先在栈中初始化c,通过new Car()方式在堆中建立Car对象,初始化color为null,num为0,再将color赋值为”red”,将num赋值为4。将new Car()产生的地址值0x0099赋给栈内存,再由栈指向堆中的0x0099的地址。

    在栈中初始化C1,通过new Car()方式在堆中建立Car对象,初始化color为null,num为0,再将color赋值为”red”,将num赋值为4。将new Car()产生的地址值0x0045赋给栈内存,再由栈指向堆中的0x0045的地址。

    此处产生的是两个对象对应的两个地址值。

    多个引用指向同一个对象的图示及解析:

    如上图所示,首先在栈中初始化c,通过new Car()方式在堆中建立Car对象,初始化color为null,num为0,再将color设置显示初始化值为”red”,将num设置显示初始化值为4。将new Car()产生的地址值0x0078赋给c的栈内存,再由栈指向堆中的0x0078的地址。再将堆中num的值改为5(因为成员变量在堆内存中);

    建立第二个引用时,首先在栈中初始化c1,然后将c的地址值0x0078赋给c1,并且c1指向堆中的0x0078所引用的对象,再将堆中的对象的color改为green。由此,无论在c还是c1引用上修改值后,指向的都是堆内存中的对象地址值0x0078。所以打印出的值为:5,green。

    2.2成员变量和局部变量的区别:

    作用范围:成员变量作用于整个类中。局部变量作用于方法中或者语句中。

    在内存中的位置:成员变量在堆内存中,因为对象的存在,才在内存中存在;局部变量存在栈内存中。

    2.3匿名对象:

    匿名对象是对象的简化形式。有两种使用情况:当对对象方法仅进行一次调用时;匿名对象可以作为实际参数进行传递

    例如:

    Car c = new Car();

    c.num = 5;

    可简化为:new Car().num = 5;

                      new Car().color = “red”;

                      new Car().run();

    匿名对象和非匿名对象在内存中存储方式区别:

    如上图,匿名对象和非匿名对象区别(注:匿名对象在栈中无数据)。使用匿名对象时,当new Car().num = 5;执行完后创建的对象就成为垃圾;new Car().color = “red”执行完后,此对象也会成为垃圾,因此匿名对象调用属性是没有意义的。但是,new Car().run()有意义。

    综上所述,匿名对象调用属性无意义,调用方法有意义

    匿名对象的两种使用方式:

    • 匿名对象使用方式一:当对对象的方法只调用一次时,可以用匿名对象来完成,这样比较简化;但是,如果对一个对象进行多个成员调用,必须给这个对象起名字。例如:

    Car c =  new Car();

    c.run();

    c.num=4;

    new Car().run();

    • 匿名对象使用方式二:可以将匿名对象作为实际参数进行传递。例如:

    需求:汽车修配厂,对汽车进行改装,将来的车都改成黑色且只有三个轮胎。

    main(){

     Car c = new Car();

     show(c);

    }

    public static void show(Car c){

    c.num = 3;

    c.color = “black”;

    c.run();

    }

    上例的内存图示及解析:

    执行main方法后,首先会再栈内存中有一个Car c的引用,然后会在堆内存中new一个Car对象,生成对应的地址值0x0034;当执行show()方法时,会在show方法中再建立一个引用c(注意连个引用不同),再将main方法中c的地址值通过匿名类参数形式传递给show()方法,因此两个引用指向的是同一个地址0x0034。匿名对象传入show()方法时,当show方法执行完后,栈中show()指向的内存空间将会成为垃圾,其对应指向的对象地址和对象也成为垃圾。

    所以就有了使用java程序写缓存机制:用java定义空间存储缓存型的数据,需要的时候就用,不需要的时候就不用。此时需保证其的生命周期,不指定则虚拟机会直接回收,而虚拟机回收垃圾是不定时的,且有些对象不会回收。所以写缓存的时候,在释放对象空间时,必须考虑对象的强引用、弱引用、软引用、虚引用。

    3、封装

    封装:是指隐藏对象的属性和实现细节,仅对外提供公共访问方式。(在使用对象时,没有必要知道对象内容是如何完成对应功能的,我们只需要指挥对象去执行即可)。

    封装的好处:将变化隔离(内部功能细节变化不影响使用);便于使用(不用了解内部的具体实现);提高重用性;提高安全性(只对外暴露一些简单的内容供使用)。

    封装的原则:将不需要对外提供的内容都隐藏起来;把属性都隐藏起来,只提供公共方法对其访问。

    函数本身就是java代码中的最简单的封装体。也是封装体,类中方法不一定全部暴露出去,可通过权限修饰符进行隐藏。也是封装体,如框架,拿来即用。

    函数封装体:

    封装具体示例:

    如上图,当p.age=-20时,就会出现安全隐患,人的年龄不可能为负数,所以可以使用修饰符进行控制权限。使用private int age;私有化后,类以外即使建立了对象也不能再直接访问该属性。但是属性或方法一旦私有了,就需要对外提供访问方式(即提供getter和setter方法等操作)。因此需要给这个人的年龄提供对外访问方式。

    之所以对外提供访问方式,是因为可以在这种访问方式中加入逻辑判断等语句,对访问的数据进行操作。提高代码的健壮性。如下例所示:

     在设置人的年龄时,set方法中需要满足条件,才可继续执行,否则提示非法。

    该示例在内存中表示,如下图:

    执行main()方法后,在栈内存中产生了一个p的引用,然后在堆内存中建立Person对象,并分配内存空间0x0012,并将这个地址值赋给p引用,然后由p再指向Person对象中的这个地址0x0012。new Person对象,执行到private int age时,在堆内存(成员变量在堆内存中分配空间)中产生了一个age,并有了默认初始化值0。当p引用设置年龄为40时(p.setAge(40)),会通过setAge()方法中的this.age = a(this即代表p引用)将堆中的age值改为40。当执行p.speak()时,拿到成员变量age的值40并输出(成员变量age由局部(setAge())接收进来并改变其值后,其他方法(speak())也可以用(拿到的还是setAge()执行后的40))。

    私有仅仅是封装的一种表现形式,只要权限在别人访问不到的范围都是封装。

     

    4、构造函数

    构造函数特点:

    • 函数名与类名相同
    • 不用定义返回值类型
    • 不可以写return语句

    作用:给对象进行初始化。

    构造函数示例:

    构造函数对象一建立就会调用与之对应的构造函数,可用于给对象进行初始化。当一个类中没有定义构造函数时,系统会默认给该类加入一个空参数的构造函数,当自己定义了构造函数后,默认的空构造函数就不存在了。

    注意:默认构造函数的特点;多个构造函数是以重载的形式存在的

    多个构造函数以重载形式存在的示例:

    构造代码块:

    在构造代码块中,对象一建立就立即运行,而且优先于构造函数执行。构造代码块中定义的是不同对象具有共性的初始化内容

    构造代码块和构造函数的区别:构造代码块是给所有对象进行统一初始化;而构造函数是给对应的对象初始化。

    5、this关键字

    5.1this关键字的基本阐述

    如上图,发现Person(String a)构造函数中,a变量是无意义的,不能见名知意。因此将a改成name表示姓名,即局部变量和成员变量名称相同。但如下图,发现执行结果name并没有赋值成功:

     

    因此如何区分局部和成员变量?即使用this关键字。

    如上图,this关键字看上去,是用于区分局部变量和成员变量同名情况。其实,this代表本类的对象。

    那么this到底代表哪一个对象?如下图:

    this代表它所在函数所属对象的引用。简单的说,哪个对象在调用this所在的函数,this就代表哪个对象。例如,当执行到Person p = new Person("lisi");时,建立Person对象,执行到Person对象的构造函数Person(String name)后,此时的this代表的是p引用。而当代码执行到Person p1 = new Person("zhangsan");时,执行到Person对象的构造函数Person(String name)后,this代表的是p1引用。

    5.2this关键字的应用:

    需求:给人定义一个判断是否是同龄人的功能。

    如上图,this和p1的地址值是指向同一个对象。当执行到boolean b = p1.compare(p2);时,会调用到compare(Person p)方法,此时,p2会以参数形式传给compare()方法,而由于是由p1调用的compare()方法,所以p1引用即当前对象this引用。即his和p1的地址值是指向同一个对象。

    this的应用:当定义类中方法时,该方法内部要用到调用该方法的对象时,这时用this表示这个对象。但凡本类功能内部使用到了本类对象,都用this表示。

    5.3this关键字在构造函数中的应用:构造函数间调用,只能使用this进行互相调用,this函数不能用在一般函数间

    this(name);对Person对象进行姓名初始化,this代表本对象。this.name = name是将值传递过去。

    this语句(不是this关键字)只能定义在构造函数的第一行。如下图:

    初始化动作要先执行,如先执行this(name)必须在this.name=name前才行。

    6、static关键字

    6.1概述

    static关键字:用于修饰成员(成员变量和成员函数)

    被修饰后的成员具备以下特点:

    • 随着类的加载而加载(类一加载到内存中时,静态static就已经加载到内存空间(方法区)中,反之随着类的消失而消失,说明它的生命周期最长)
    • 优先于对象存在(静态是先存在的,对象是后存在的)
    • 被所有对象所共享
    • 可以直接被类名调用

    使用注意:

    • 静态方法只能访问静态成员
    • 静态方法中不可以写this,super关键字
    • 主函数是静态的

    6.2为什么要使用静态static关键字?

    如以上两图所示,创建了两个对象引用P和P1,如果只在中国范围内,而不使用静态关键字static,则需要在堆内存中开辟两个country=”cn”空间(多个空间存在共同数据),那么对象创建得越多占用的内存就越多,如下图,将country=”cn”定义成静态的,则只需要在方法区中定义一次即可(static String country = “CN”),如下图:

    方法区(共享区、数据区):存放共享数据,方法及方法体等内容。

    当成员(包括成员变量和方法)被静态修饰后,就多了一个调用方式,除了可以被对象调用外,还可以直接被类名调用。格式:类名.成员。

    静态static优先于对象的存在,即静态会随着类的加载而加载,消失而消失(说明生命周期最长)。静态的成员变量也叫类变量,非静态的成员变量也叫实例变量。

    6.3为什么不把所有的成员都定义为静态static?

    要区分什么数据是对象特有的,什么数据是多个对象共享的,这样才符合生活中的描述;对象在被用完后会被回收,但是静态static的数据生命周期特别长,用完后还会一直存在,因此会存在垃圾

    6.4实例变量和类变量的区别

    • 存放位置:类变量(静态成员变量)随着类的加载而存在于方法区中;实例变量随着对象的建立而存在于堆内存中
    • 生命周期:类变量生命周期最长,随着类的消失而消失;实例变量生命周期随着对象的消失而消失。

    6.5静态的使用注意事项

    • 静态方法只能访问静态成员(成员变量和方法),非静态方法既可以访问静态成员也可以访问非静态成员

    • 静态方法中不可以定义this,super关键字。因为静态优先于对象存在

    如下图,this.name不可用,this代表对象,而静态方法优先于对象创建,在执行静态方法show()时,对象Person还未创建,所以此时this 还未初始化过,所以不可用。

    6.6静态有利有弊

    利:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象中存储一份;可以直接被类名调用。

    弊:生命周期过长(可能会有垃圾不能被回收);访问出现局限性(静态虽好,但只能访问静态)。

    6.7主函数中的静态

    主函数是静态的:主函数是一个特殊的函数,作为程序的入口,可以直接被JVM调用。

    主函数的定义:

    • public:代表着该函数访问权限是最大的
    • static:代表主函数随着类的加载就已经存在了
    • void:代表主函数没有具体的返回值返给虚拟机JVM
    • main:不是关键字,但是是一个特殊的单词,可以被JVM识别,且是主函数特有的。
    • String[] args: 函数的参数,参数类型是一个数组,该数组中的元素是字符串,字符串类型的数组。

    主函数是固定格式的:JVM识别

    public static void main(int x){}也可以写(主函数被重载),但是JVM会优先从public static void main(String[] args){}开始执行。JVM在调用主函数时,传入的是 new String[0],作用:可以往参数里传入数据,在主函数里就可以拿到参数。

    传递参数给主函数并由主函数获取:

    • 使用命令传: java 类名 参数

    • 通过另一个类的主函数传值,然后由测试类主函数进行获取:

    6.8什么时候使用静态?

    要从两个方面下手:因为静态修饰的内容包括成员变量和成员函数。

    什么时候定义静态变量(类变量)?

    当对象中出现共享数据时,该数据需要被静态所修饰;而对象中的特有数据要定义成非静态存在堆内存中。

    6.9什么时候定义静态函数?

    当功能内部没有访问到非静态数据(对象的特有数据(非静态成员变量))时,该功能可以定义成静态的。

    例:对象是为了封装数据的,但是下例中对象里的特有数据name并没有使用到,所以show()方法可以使用静态。

    class Person{
    String name;
    public static void show(){
      System.out.println(“haha”);
    
    }
    
    public static void main(String[] args){
        Person.show();
       }
    }

    但是下例中有使用到对象中的非静态成员name,所以show()方法绝对不能使用静态:

    class Person{
    String name;
    public static void show(){
      System.out.println(name+“haha”);//因为静态先于对象存在,在创建静态方法show时,对象Person还不存在,所以name也不存在
    }
    
    public static void main(String[] args){
     Person p = new Person();
     p.show();
    }
    }

    6.10静态的应用—工具类

    如上图,一个获取数组中最大值的类。可以发现,如果有很多类,都需要使用到获取数组最大值这个方法时,每个类都需要写一遍这些代码,因此可以将获取数组最大值的代码统一封装到一个静态工具类中,需要使用时调用即可。如下图所示:

    因此,静态工具类即将每一个应用程序中都有共性的功能,进行抽取,独立封装,以便复用。

    原始的ArrayTool.java类:

    package com.vnb.javabase;
    /**
     * 
     * Description:数组工具类
     * @author li.mf
     * @date 2018年8月15日
     *
     */
    public class ArrayTool {
    
    	/**
    	 * 获取数组最大值
    	 * @param arr
    	 * @return
    	 */
    	public int getMax(int[] arr){
    	
    		int max = 0;
    		for (int i = 1; i < arr.length; i++) {
    			if(arr[max]<arr[i]){
    				max = i;
    			}
    		}
    		return arr[max];
    	}
    	
    	/**
    	 * 获取数组最小值
    	 * @param arr
    	 * @return
    	 */
    	public int getMin(int[] arr){
    		
    		int min = 0;
    		for (int i = 1; i < arr.length; i++) {
    			if(arr[min]>arr[i]){
    				min = i;
    			}
    		}
    		return arr[min];
    	}
    	/**
    	 * 选择排序
    	 * @param arr
    	 */
    	public void selectSort(int[] arr){
    		for (int i = 0; i < arr.length-1; i++) {
    			for (int j = i+1; j < arr.length; j++) {
    				if(arr[i]>arr[j]){
    					swap(arr,i,j);
    				}
    			}
    		}
    		
    	}
    	/**
    	 * 冒泡排序
    	 * @param arr
    	 */
    	public void bubbleSort(int[] arr){
    		for (int i = 0; i < arr.length; i++) {
    			for (int j = 0; j < arr.length-i-1; j++) {
    				if(arr[j]>arr[j+1]){
    					swap(arr,j,j+1);
    				}
    			}
    		}
    		
    	}
    	
    	public void swap(int[] arr,int a,int b){
    		int temp = arr[a];
    		arr[a] = arr[b];
    		arr[b] = temp;
    	}
    	
    	public void printArray(int[] arr){
    		System.out.print("{");
    		for (int i = 0; i < arr.length; i++) {
    			if(i!=arr.length-1){
    				System.out.print(arr[i]+",");
    			}else{
    				System.out.print(arr[i]+"}");
    			}
    		}
    	}
    }
    

    数组工具类的测试类ArrayToolTest.java:

    package com.vnb.javabase;
    
    public class ArrayToolTest {
    
    	public static void main(String[] args) {
    
    		int[] arr = {1,2,6,8,3};
    		
    		ArrayTool arrayTool = new ArrayTool();
    		int max = arrayTool.getMax(arr);
    		System.out.println("最大值为:"+max);
    		
    		int min = arrayTool.getMin(arr);
    		System.out.println("最小值为:"+min);
    		
    		arrayTool.printArray(arr);
    		arrayTool.selectSort(arr);
    		arrayTool.printArray(arr);
    	}
    }
    

    由例可见,ArrayTool类都没有用到ArrayTool中特有的数据(非静态的成员变量),都是由用户传参的arr进去,所以可以写成static。虽然可以通过建立ArrayTool的对象使用这些工具方法,对数组进行操作。但是,对象是用于封装数据的,而ArrayTool对象并未封装特有数据;操作数组的每一个方法都没有用到ArrayTool对象中的特有数据。所以,这时就可以考虑,让程序更严谨而不需要对象。可以将ArrayTool中的方法都定义成static的,直接通过类名调用即可。

    ArrayTool.java中所有方法写成静态,以便调用:

    package com.vnb.javabase;
    /**
     * 
     * Description:数组工具类
     * @author li.mf
     * @date 2018年8月15日
     *
     */
    public class ArrayTool {
    
    	/**
    	 * 获取数组最大值
    	 * @param arr
    	 * @return
    	 */
    	public static int getMax(int[] arr){
    	
    		int max = 0;
    		for (int i = 1; i < arr.length; i++) {
    			if(arr[max]<arr[i]){
    				max = i;
    			}
    		}
    		return arr[max];
    	}
    	
    	/**
    	 * 获取数组最小值
    	 * @param arr
    	 * @return
    	 */
    	public static int getMin(int[] arr){
    		
    		int min = 0;
    		for (int i = 1; i < arr.length; i++) {
    			if(arr[min]>arr[i]){
    				min = i;
    			}
    		}
    		return arr[min];
    	}
    	/**
    	 * 选择排序
    	 * @param arr
    	 */
    	public static void selectSort(int[] arr){
    		for (int i = 0; i < arr.length-1; i++) {
    			for (int j = i+1; j < arr.length; j++) {
    				if(arr[i]>arr[j]){
    					swap(arr,i,j);
    				}
    			}
    		}
    		
    	}
    	/**
    	 * 冒泡排序
    	 * @param arr
    	 */
    	public static void bubbleSort(int[] arr){
    		for (int i = 0; i < arr.length; i++) {
    			for (int j = 0; j < arr.length-i-1; j++) {
    				if(arr[j]>arr[j+1]){
    					swap(arr,j,j+1);
    				}
    			}
    		}
    		
    	}
    	
    	public static void swap(int[] arr,int a,int b){
    		int temp = arr[a];
    		arr[a] = arr[b];
    		arr[b] = temp;
    	}
    	
    	public static void printArray(int[] arr){
    		System.out.print("{");
    		for (int i = 0; i < arr.length; i++) {
    			if(i!=arr.length-1){
    				System.out.print(arr[i]+",");
    			}else{
    				System.out.print(arr[i]+"}");
    			}
    		}
    	}
    }
    

    ArrayToolTest.java

    package com.vnb.javabase;
    
    public class ArrayToolTest {
    
    	public static void main(String[] args) {
    
    		int[] arr = {1,2,6,8,3};
    		
    		int max = ArrayTool.getMax(arr);
    		System.out.println("最大值为:"+max);
    		
    		int min = ArrayTool.getMin(arr);
    		System.out.println("最小值为:"+min);
    		
    		ArrayTool.printArray(arr);
    		ArrayTool.selectSort(arr);
    		ArrayTool.printArray(arr);
    	}
    }
    

    将方法都写成静态后,可以方便与使用,但是仍然发现问题,即该类还是可以被其他程序建立对象的。所以为了更为严谨,强制该类不可以实例化以建立对象,所以可以考虑通过将构造函数私有化完成(私有构造)。

    package com.vnb.javabase;
    /**
     * 
     * Description:数组工具类
     * @author li.mf
     * @date 2018年8月15日
     *
     */
    public class ArrayTool {
    
    	private ArrayTool(){
    		
    	}
    	/**
    	 * 获取数组最大值
    	 * @param arr
    	 * @return
    	 */
    	public static int getMax(int[] arr){
    	
    		int max = 0;
    		for (int i = 1; i < arr.length; i++) {
    			if(arr[max]<arr[i]){
    				max = i;
    			}
    		}
    		return arr[max];
    	}
    	
    	/**
    	 * 获取数组最小值
    	 * @param arr
    	 * @return
    	 */
    	public static int getMin(int[] arr){
    		
    		int min = 0;
    		for (int i = 1; i < arr.length; i++) {
    			if(arr[min]>arr[i]){
    				min = i;
    			}
    		}
    		return arr[min];
    	}
    	/**
    	 * 选择排序
    	 * @param arr
    	 */
    	public static void selectSort(int[] arr){
    		for (int i = 0; i < arr.length-1; i++) {
    			for (int j = i+1; j < arr.length; j++) {
    				if(arr[i]>arr[j]){
    					swap(arr,i,j);
    				}
    			}
    		}
    		
    	}
    	/**
    	 * 冒泡排序
    	 * @param arr
    	 */
    	public static void bubbleSort(int[] arr){
    		for (int i = 0; i < arr.length; i++) {
    			for (int j = 0; j < arr.length-i-1; j++) {
    				if(arr[j]>arr[j+1]){
    					swap(arr,j,j+1);
    				}
    			}
    		}
    		
    	}
    	
    	private static void swap(int[] arr,int a,int b){
    		int temp = arr[a];
    		arr[a] = arr[b];
    		arr[b] = temp;
    	}
    	
    	public static void printArray(int[] arr){
    		System.out.print("{");
    		for (int i = 0; i < arr.length; i++) {
    			if(i!=arr.length-1){
    				System.out.print(arr[i]+",");
    			}else{
    				System.out.print(arr[i]+"}");
    			}
    		}
    	}
    }
    

    ArrayToolTest.java

    package com.vnb.javabase;
    
    public class ArrayToolTest {
    
    	public static void main(String[] args) {
    
    		int[] arr = {1,2,6,8,3};
    		
    		int max = ArrayTool.getMax(arr);
    		System.out.println("最大值为:"+max);
    		
    		int min = ArrayTool.getMin(arr);
    		System.out.println("最小值为:"+min);
    		
    		ArrayTool.printArray(arr);
    		ArrayTool.selectSort(arr);
    		ArrayTool.printArray(arr);
    	}
    }
    

     

    6.11帮助文档的制作(javadoc)

    如果从别的地方拿来的java文件且不在同一目录下,则执行时会报错:找不到文件。如下图,

    所以可以在dos命令行中设置classpath:set classpath-.i;c:\myclass 。-.i;先在当前目录下找,再C盘指定目录myclass下去找。

    /** */  里面写类的描述信息、开发人、开发时间、版本号等。java通过javadoc.exe程序来进行帮助文档的制作。通过命令可以将帮助文档存放到指定位置:javadoc –d myhelp –author –version ArrayTool.java。(如果无此文件夹,会自动创建)。

    要将一个文件写出帮助文档,需要将类修饰符写为public或者protected 。以供外部读取。

    ArrayTool.java帮助文档的查看:

    从索引页面开始看即可(index.html)。

    帮助文档示例:

    一个类中默认会有一个空参数的构造函数,这个默认的构造函数的权限和所属类一致,如果类被public修饰,那么默认的构造函数也带public修饰,如果没有,默认的空构造也没有。注意:默认的构造函数不是自己写的空构造函数。

    6.12静态代码块

    格式:

    static{
         静态代码块的执行语句
    }

    示例:

    static{
         System.out.println(“asdf”);
    }

    静态代码块特点:静态代码块随着类的加载而执行,且执行一次(类加载完后,执行完了,就已经在内存中了),用于给类进行初始化,且优先于主函数执行。

    静态代码块和主函数执行,示例:

    打印顺序:b c a。

    如上图,想让show方法执行,必须先加载StaticCodeDemo类。

    如上图,该类类变量没有任何实体指向,所以StaticCode类不会进行加载,所以没有打印结果。

    如上图,执行顺序:a c d ; 注:如果想要执行特定的构造函数需要在调用的时候进行指定,如上例new StaticCode(4);指定调用的是StaticCode(int x){}构造方法,而不是空参数的构造方法。

    • a表示静态代码块给初始化;
    • b不会执行,因为还未创建过与之对应的对象(已经指定调用其他的构造方法);
    • c表示构造代码块给对象初始化
    • d表示构造函数给对应对象初始化

    {}表示构造代码块给对象初始化的,所以可以用this

    6.13对象初始化的过程

    对象建立过程:

    当类的主函数入口main()方法执行到Person p = new Person(“zhangsan”,20);时,会有一个p引用加载到栈中:

    1)new Person()时会将Person.class文件从硬盘中通过java的虚拟机JVM加载进内存;

    2)执行静态代码块

    3)并开辟了堆内存空间。(非静态成员变量,如属性:name、age)

    4)初始化动作,构造代码块

    执行结果null,0。

    所以会先有默认初始化null和0,再有显示初始化(成员属性的初始化),如下图:

    再进行构造代码块初始化(此代码中无),再构造函数初始化(赋值zhangsan,20)。

    总结:Person p = new Person(“zhangsan”,20);的执行过程?

    1. 因为new用到了Person.class,所以会先找到Person.class文件并加载到内存中
    2. 执行该类中的static代码块,如果有的话,给Person.class类进行初始化
    3. 在堆内存中开辟空间,分配内存地址
    4. 在堆内存中建立对象的特有属性,并进行默认初始化(对象为null,整数为0)
    5. 对属性进行显示初始化(类中声明成员变量初始化值)
    6. 对对象进行构造代码块初始化
    7. 对对象进行对应的构造函数初始化
    8. 将内存地址值赋给栈内存中的p引用
    9. p引用再指向堆内存中的地址值

    6.14对象调用成员过程

    Person p = new Person(“zhangsan”,20);

    p.setName(“lisi”);

    1)在栈中有main主函数的p引用生成

    2)然后在方法区中初始化Person类的方法(showCountry()、setName()、speak())、方法体、静态变量

    3)在堆中开辟空间

    4)在默认初始化(对象为null,整数为0)

    5)再到堆中new Person(“Zhangsan”,20)

    6)再将地址值0x0023赋给栈,再从栈中指向堆中的地址

    7)setName()在栈中开辟空间,非静态的只要一调用this就会有值(指向),this的引用用于给对象赋值(哪个对象调用就代表哪个对象,这里this代表p),即this为0x0023,即this指向堆中的0x0023,因此 setName中this.name = name一执行时,局部变量name的lisi赋值给了堆内存中的this.name。

    调用过程的内存图示:

    当再创建一个对象p1时,在内存中的调用过程,如下:

    当再创建一个对象p1后,会重新在栈内存中初始化p1引用,然后在堆内存中重新new 一个Person对象,设置默认初始化值,然后设置显示初始化值name为ahah,age为90,并开辟空间设置地址值,并将地址值赋给p1,当setName(“qq”)时,会在栈中开辟一块空间,通过this.name = name将堆内存中的name值设置为qq。

    如上图,静态方法showCountry()方法一被调用,在开辟完空间后,没有找到this关键字,所以showCountry()方法就会所属于Person类,不会再进堆内存。调用showCountry()方法时也会用“类名.showCountry()”执行里面的静态方法。

    成员只有被调用才能用,静态的本类省略了类名.method。非静态对象省略this.方法; 静态省略类名.静态方法。

    7、单例设计模式

    7.1概念

    设计模式: 解决某一类问题最行之有效的方法。java中共有23种设计模式。

    单例模式:解决一个类在内存中只存在一个对象(不能new多个实例)

    想要保证对象唯一:

    1. 为了避免其他程序过多建立该类对象,先要控制禁止其他程序建立该类对象
    2. 为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象
    3. 为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式

    代码实现:

    1. 将构造函数私有化(以控制其他程序建立该类对象)
    2. 在类中创建一个本类对象
    3. 提供一个方法可以获取到该对象(方便其他程序对自定义对象的访问)

    7.2饿汉式单例模式

    package com.vnb.javabase;
    /**
     * 
     * Description:单例模式
     * @author li.mf
     * @date 2018年8月16日
     *
     */
    public class Singleton {
    	
    	private static Singleton instance = new Singleton();
    	
    	private Singleton(){
    		
    	}
    	
    	public static Singleton getInstance(){
    		
    		return instance;
    	}
    }
    
    class SingleDemo{
    	
    	public static void main(String[] args) {
    		Singleton ss = Singleton.getInstance();
    	}
    }
    

    内存中实现过程:

    再获取一次单例,发现获取的实例时一致的。如下图:

    测试是否获取的是同一个实例:

    class SingleDemo{
       public static void main(String[] args) {
          Singleton s1 = Singleton.getInstance();
          Singleton s2 = Singleton.getInstance();
          s1.setNum(20);
          System.out.println("num值为"+s2.getNum());
       }
    }

    结果发现,s2获取的值是s1 set进的值。

    单例模式,对于事物该怎么描述,还怎么描述,当需要将该事物的对象保证在内存中唯一时,就将以上者三步加上即可。

    7.3懒汉式单例模式

    package com.vnb.javabase;
    public class Single {
    	private static Single instance = null;
    	private Single(){	
    	}
    	public static Single getInstance(){
    		if(instance == null){
    			instance = new Single();
    		}
    		return instance;
    	}
    }
    

    对象在被调用时,才进行初始化,就叫做对象的延时加载,也称为懒汉式。

    如下图,Single类进内存时,对象还没有存在,而是只有调用了getInstance方法时,才建立对象。

    懒汉式单例模式在内存中表示:

    开发一般使用饿汉式,因为其简单且安全,且懒汉式当有多个人来调用这个方法时会出现问题。当A执行到if(s==null) 后挂掉了,突然去执行其他程序了,此时其他程序B来判断后new了一个对象,之后A又继续执行了,但是A并不知道B已经创建了对象,所以B又new了一个对象,这样就new了多个对象不唯一了。

    为解决上述问题:可以在方法上加同步锁synchronized

    package com.vnb.javabase;
    public class Single {
    	private static Single instance = null;
    	private Single(){	
    	}
    	public static synchronized Single getInstance(){
    		if(instance == null){
    			instance = new Single();
    		}
    		return instance;
    	}
    }
    

    但是加上同步锁后,每次进getInstance()方法后都要执行同步锁,程序的效率会变低。

    为解决低效率问题:可以使用双重判断形式(多线程时进行讲解),使用同步代码块。如下图。但是此方法代码会较多。所以定义单例时建立使用饿汉式。

    package com.vnb.javabase;
    public class Single {
    	private static Single instance = null;
    	private Single(){	
    	}
    	public static Single getInstance(){
    		if(instance == null){
    			synchronized (Single.class) {
    				if(instance == null){
    					instance = new Single();
    				}
    			}
    		}
    		return instance;
    	}
    }
    

    8、继承

    本节主要内容:继承的概述;继承的特点;super关键字;函数覆盖;子类的实例化过程;final关键字。

    8.1继承的概述

    有以下两个类:

    将学生和工人两个类的共性描述提取出来,单独进行描述,只要让学生和工人与单独描述的这个类有关系,就可以了。

     

    继承的好处:提高了代码的复用性;继承让类与类之间产生了关系,有了这个关系,才有了多态的特性。

    注意:千万不能为了获取其他类的功能,简化代码而继承。必须是类与类之间有所属关系才可以继承。所属关系即 is a。java语言中,只支持单继承,不支持多继承,因为多继承容易带来安全隐患(接口与接口之间可以多继承(因为接口之间没有方法体,方法之间不会冲突))。

    如上图,继承多个类,当多个父类中定义了相同功能,当功能内容不同时,子类对象不知道运行哪个功能,就会出现问题。但是java保留另一种体现形式来完成这种表示,叫接口多实现。

    java支持多层继承。也就是一个继承体系。如下图所示:

    C类也可以使用到A类中的功能。

    如何使用一个继承体系中的功能呢?

    想要使用体系,先查阅体系中父类的描述,因为父类中定义的是该体系中的共性功能,通过了解共性功能,就可以知道该体系的基本功能。也可以基本使用了。在具体调用时,为什么要创建最子类的对象?一是因为有可能父类不能创建对象;二是创建子类对象可以使用更多的功能,包括父类的也包括特有的。简单的一句话:查阅父类功能,创建子类对象使用功能。

    8.2类与类之间关系—聚集关系(组合关系)

    事物之间不一定是继承关系,也有聚集关系 has a,简单的说就是谁里面有谁。

    聚合:球员和球队(球队中有球员)

    组合:事物之间的联系比聚合关系更高,比如,心脏和人;手和人,心脏和手必不可少。

    8.3子父类中变量的特点

    以上当父类子类成员变量相同时,打印的是4,因为子类中的num 即为this.num。要打印父类的变量,使用super.num即可,打印子类的变量直接使用num即可。

    new 子类class文件时,会先加载父类的class文件

    变量:如果子类中出现非私有的同名变量时,子类要访问本类中的变量,用this;子类要访问父类中的同名变量,用super。super的使用和this的使用几乎一致,this代表本类对象的引用;super代表父类对象的引用。

    如上图,num前省略super和this打印结果都是4,原因:子类继承父类,子类也能拿到父类的num 4(相当于子类里有自己的num),所以用子类自己的this能拿到num=4;super是指父类引用,this是指本类引用,由于现在没有父类对象只有子类对象(Zi z = new Zi()),所以现在super和this引用指向的是同一个对象(Zi子类对象)即new Zi(),所以只有一个num=4,即同一个子类对象的num(这就是面向对象的多态即父类引用指向子类对象List list = new ArrayList())。

    8.4子父类中函数的特点—覆盖(重写)

    如上图,若子父类中的方法一致,子类运行就会覆盖了父类中的方法。子类出现和父类一模一样的函数时,当子类对象调用该函数,会运行子类函数的内容,如同父类的函数被覆盖一样,这种情况是函数的另一个特性,重写(覆盖)。

    当子类继承了父类,沿袭了父类的功能到子类中,但是子类虽具备该功能,但是功能的内容却和父类却不一致,这时,就没有必要定义新功能,而是使用覆盖特性,保留父类功能定义,并重新父类内容。这就叫重写,也叫做覆盖。如下图:

    用重写特性提高程序扩展性:

    new的时候只要new NewTel即可,而不需要修改Tel类的任何内容,但是重写时会有代码重复以实现父类原有的功能,所以只要使用super调用原有内容即可。如下图:

    覆盖:子类覆盖父类,必须保证子类权限要大于或等于父类权限,才可以覆盖,且父类不能为private修饰(如果是private修饰,子类都不知道父类有此方法存在),否则编译失败;静态只能覆盖静态(虽然此做法无意义);

    重载和重写的区别:

    • 重载只看同名函数的参数列表;
    • 重写是子父类方法要一模一样包括返回值类型。

    如上图,是不允许存在的。因此重写要求子父类中的方法名和返回值类型必须相同。

    8.5子父类中构造函数的特点—子类实例化过程

    子父类构造函数不能覆盖,因为覆盖必须函数名和返回值等都一模一样,但是构造函数的名字必须和类名一致;子类的构造函数有一句隐示的语句super();如下图:

    在对子类对象进行初始化时,父类构造函数也会运行,是因为子类的构造函数默认第一行有一条隐式的语句super(),super()会访问父类中空参数的构造函数,而且子类中所有的构造函数默认第一行都是super()。如下图所示:

    如下图,当父类中没有空参数的构造时,系统会报错,所以必须手动指定子类引用父类的哪个非空的构造函数。

    为什么子类一定要访问父类中的构造函数?见下图:

    总结:(如上图)因为父类中的数据子类可以直接获取,所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的,所以子类在对象初始化时,要先访问一下父类中的构造函数,如果要访问父类中指定的构造函数,可以通过手动定义super语句的方式进行指定。如上图,在建立子类对象后后去查看父类有没有改变num的值,这里父类已经将num值设置为60,所以子类的num也对应改了,最后打印的z.num即父类中设置的60。

    应用:

    父类已经给name赋过值了,子类继承父类后,直接调用(super(name))父类的方法即可获取到父类中设置的值。

    总结:父类已经定义过的方法属性,子类想要使用,直接通过super即可拿到

    如下图,super()语句必须写在子类构造函数的第一行,因为需要先把父类的数据初始化完成后,再初始化自己的。

    当子类构造函数中写了this()方法后,就不能再用super()了,this和super不能同时存在。此时zi(int x)中的this()会访问本类中的zi()空构造,而zi()构造函数中默认有super()就能调用到父类中的属性和方法。

    结论(子类的实例化过程):子类中所有的构造函数,默认都会访问父类中的空参数的构造函数,因为子类每一个构造函数内的第一行都有一句隐式的super(),当父类中没有空参数的构造函数时,子类必须手动通过super语句或者this语句形式来指定要访问的父类中的构造函数。子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数,子类中至少要有一个构造函数会访问父类中的构造函数(没写也会有默认的)。

    this和super为什么不能同时存在?因为this和super都必须存在第一行。

    为什么this和super都必须存在第一行?因为初始化动作必须先做。

    8.6 final关键字

    final关键字:

    • 作为一个修饰符,可以修饰类、函数、变量。
    • 被final修饰的类不可以被继承(继承的弊端,打破了封装性,可以复写掉父类的东西,很多基类就会很不安全,可能会被修改),为了避免被继承,被子类复写功能,在类上写final即可(叫做最终类,最终类是不可以被继承的)。
    • 被final修饰的方法,不可以被复写。

    1. 被final修饰的变量是一个常量,只能赋值一次,既可以修饰成员变量,也可以修饰局部变量。如下图:

    只要在内存中,x和y的值都是固定的,不可以更改。

    当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字,方便阅读,而这个值不需要改变,所以加上final修饰。作为常量:常量的书写规范所有的字母都大写,如果有多个单词组成,单词间通过下划线连接。

    2.内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。

    8.7抽象类

    如上图,发现两个方法都一致,所以需要抽取相同功能。但发现虽然两者都在学习,但是学习内容却不一样。解决:可以进行向上抽取,只抽取功能定义,不抽取功能主体(即抽象类)。对于这种方法,必须使用abstract进行修饰。抽象方法必须定义在抽象类中。如下图:

    抽象:即看不懂的事物。

    抽象类的特点:

    1. 抽象方法一定在抽象类中
    2. 抽象方法和抽象类都必须被abstract关键字修饰
    3. 抽象类不可以用new创建对象,因为调用抽象方法没意义(里面的抽象方法连方法体都没有)
    4. 抽象类中的抽象方法要想被使用,必须由子类复写其所有的抽象方法后,建立子类对象调用,如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。

    抽象类和一般类没有太大的不同。只是要注意该如何描述事物就如何描述事物,只不过该事物中出现了一些看不懂的东西。这些不确定的部分也是该事物的功能,需要明确出来,但是无法定义主体。而通过抽象方法来表示。

    • 抽象类比一般类多了抽象方法。就是在类中可以定义抽象方法,也可以不定义抽象方法。
    • 抽象类不可以实例化。
    • 抽象类也可以不定义抽象方法,这样只是为了不让该类创建对象。

    抽象类练习:

    假如我们在开发一个系统时需要对员工进行建模,员工包含3个属性:姓名、工号、工资。经理也是员工,除了含有员工的属性外,另外还有一个奖金属性,请使用继承的思想设计出员工类和经理类。要求类中提供必要的方法进行属性访问。

    分析:

    员工类:name、id、pay

    经理类:继承了员工并有自己特有的奖金属性bonus

    会有一个抽象员工类,员工属于这一类,经理也属于这一类

    8.8模板方法设计模式:

    需求:获取一段程序运行的时间

    原理:获取程序开始和结束的时间并相减即可。获取本机系统时间:System.currentTimeMillis()获取当前时间的毫秒值,返回当前时间减去1970年1月1日0点0分0秒之间的时间差。

    问题:想获取另一段代码的运行时间。解决:直接子类复写掉getTime方法

    但是发现代码重复性很高。

    解决:可以将需要运行代码提取出来形成另一个方法,子类复写时,只需要复写提取出来的代码即可。如下图:

    但是却发现提取出来的需要运行的代码是不确定的,解决:只要将方法写成抽象方法并由子类继承后进行具体实现即可,且getTime方法不可复写所以使用final进行修饰。如下图:

    当代码完成优化后,就可以解决这类问题,这种方式叫做模板方法设计模式。

    什么是模板方法?

    在定义功能时,功能的一部分是确定的,但是一部分是不确定的,而确定的部分在使用不确定的部分时就将不确定的部分暴露出去,由该类的子类去完成。这样可以提高扩展性和复用性。注意:暴露出去的方法不一定抽象,有时会有默认的实现方式。

    8.9接口

    接口:初期理解,可以认为是一个特殊的抽象类,当抽象类中的方法都是抽象的,那么该类可以通过接口的形式表示。

    接口定义时,格式特点:

    1. 接口中常见定义:常量,抽象方法
    2. 接口中的成员都有固定修饰符(常量:public static final;方法:public abstract)

    总结:接口中的成员(包括成员变量和方法)都是public的

    如下,即使少写了成员上的,interface也会进行默认的补全。

    但是,一般建议写全:

    接口也是不可能创建对象的,因为接口中的方法都是抽象的,而抽象方法需要被子类实现。即子类对接口中的抽象方法全都覆盖后,子类才可以实例化,若子类只是实现了父类,却没有覆盖父类的方法,那么子类仍是一个抽象类。如下图:

    如上图中,实现类Test实现了Inter接口后,覆盖了父类接口中的show()方法。因此Test类才可以被实例化。

    接口可以被类多实现,也是对多继承不支持的转换形式,java不支持多继承,但是支持多实现。如下图:

    java不支持多继承是因为父类当中的方法有重复,多继承会导致子类调用时出现冲突,为什么接口不会出现这种问题?

    因为多实现接口中,接口没有方法主体,实现类在实现时,可以重写方法体,即使出现接口中出现多个相同方法,实现类中一个方法就可以全部重写多个接口中的那些相同方法。如下图所示:

    注意:多个接口的相同方法的返回值必须一致。

    类和接口相互之间的关系:

    • 类与类之间:继承
    • 类与接口之间:实现
    • 接口与接口之间关系:继承关系

    接口与接口之间可以多继承(因为接口之间没有方法体,方法之间不会冲突)。所以java中也存在多继承,只是多继承只存在在接口与接口之间,而不存在与类与类或者类与接口之间。

    接口的特点:

    1. 接口是对外暴露的规则
    2. 接口是程序的功能扩展
    3. 接口可以用来多实现(降低了耦合性)
    4. 类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口
    5. 接口与接口之间可以有继承关系

    9、多态

    9.1概念

    多态:可以理解为事物存在的多种体现形态

    人:男人、女人

    动物:猫、狗

    猫 x = new 猫();

    动物 x = new 猫();

    函数也具有多态性:重载和覆盖

    本节主要内容:多态的体现;多态的前提;多态的好处;多态的应用等。

    9.2多态—扩展性

    需求:动物(猫、狗)

    package com.vnb.javabase;
    
    abstract class Animal{
    	
    	abstract void eat();
    }
    
    class Cat extends Animal{
    
    	@Override
    	void eat() {
          System.out.println("吃鱼");		
    	}
    	
    	public void catchMouse(){
    		
    		System.out.println("抓老鼠");
    	}
    }
    
    class Dog extends Animal{
    
    	@Override
    	void eat() {
          System.out.println("啃骨头");		
    	}
    	
    	public void kanJia(){
    		
    		System.out.println("看家");
    	}
    }
    
    public class DuotaiDemo {
    	
    	public static void main(String[] args) {
    		Cat c = new Cat();
    		function(c);
    		function(new Dog());
    	}
    	
    	public static void function(Cat c){
    		c.eat();
    	}
    	public static void function(Dog d){
    		d.eat();
    	}
    }
    

    由例可发现,例中只有继承,如果再要加其他动物,还得写其他动物的类并继承动物类,且还要重写动物的吃方法。

    分析发现,所有动物都具有吃的行为,因为他们都是动物。如下图:

    package com.vnb.javabase;
    
    abstract class Animal{
    	
    	abstract void eat();
    }
    
    class Cat extends Animal{
    
    	@Override
    	void eat() {
          System.out.println("吃鱼");		
    	}
    	
    	public void catchMouse(){
    		
    		System.out.println("抓老鼠");
    	}
    }
    
    class Dog extends Animal{
    
    	@Override
    	void eat() {
          System.out.println("啃骨头");		
    	}
    	
    	public void kanJia(){
    		
    		System.out.println("看家");
    	}
    }
    
    public class DuotaiDemo {
    	
    	public static void main(String[] args) {
    		/*Cat c = new Cat();
    		function(c);
    		function(new Dog());*/
    		Animal a = new Cat();
    		a.eat();
    	}
    	
    	public static void function(Cat c){
    		c.eat();
    	}
    	public static void function(Dog d){
    		d.eat();
    	}
    }
    

    总结:多态的体现:父类的引用指向了自己的子类对象(Animal a = new Cat())。

    父类的引用也可以接收自己的子类对象(提高代码的扩展性:以后也其他的猪、老虎类,只要有这个类继承动物类即可)。所以可以通过动物类,去new其子类(即多态),传入什么动物,便由其自己去调用自己的吃方法。如下图:

    package com.vnb.javabase;
    
    abstract class Animal{
    	
    	abstract void eat();
    }
    
    class Cat extends Animal{
    
    	@Override
    	void eat() {
          System.out.println("吃鱼");		
    	}
    	
    	public void catchMouse(){
    		
    		System.out.println("抓老鼠");
    	}
    }
    
    class Dog extends Animal{
    
    	@Override
    	void eat() {
          System.out.println("啃骨头");		
    	}
    	
    	public void kanJia(){
    		
    		System.out.println("看家");
    	}
    }
    
    public class DuotaiDemo {
    	
    	public static void main(String[] args) {
    		function(new Cat());
    		function(new Dog());
    	}
    	public static void function(Animal a){
    		a.eat();
    	}
    }
    

    多态的好处:大大提高了程序的扩展性

    多态的前提:必须是类与类之间有关系(继承或者实现);通常还有一个前提,存在覆盖。

    多态的弊端:提高了扩展性,但是只能使用父类的引用访问父类的成员(如Animal的eat()方法)。

    9.3多态—转型

    package com.vnb.javabase;
    
    abstract class Animal{
    	
    	abstract void eat();
    }
    
    class Cat extends Animal{
    
    	@Override
    	void eat() {
          System.out.println("吃鱼");		
    	}
    	
    	public void catchMouse(){
    		
    		System.out.println("抓老鼠");
    	}
    }
    
    class Dog extends Animal{
    
    	@Override
    	void eat() {
          System.out.println("啃骨头");		
    	}
    	
    	public void kanJia(){
    		
    		System.out.println("看家");
    	}
    }
    
    public class DuotaiDemo {
    	
    	public static void main(String[] args) {
    		
    		Animal a = new Cat();//引用数据类型的类型提升,也称为向上转型
    	}
    	public static void function(Animal a){
    		a.eat();
    	}
    }
    

    多态:Animal a = new Cat();存在引用数据类型的类型提升,即向上转型。

    如果想要调用猫的特有方法时,要如何操作?

    解决:可以向上转型,也可以向下转型,强制将父类的引用,转成子类类型。如下例:

    Animal a = new Cat();

    a.eat();

    Cat c = (Cat)a;

    a.catchMouse();//调用子类特有方法

    注意:Animal a = new Animal();

    Cat c = (Cat)a;//这里是不能强转的,这里的动物类是不明确到底是哪个动物类的

    千万不要出现以下操作:即将父类对象转成子类类型。能转换的是父类引用指向了自己的子类对象时,该引用可以被提升(运行结果是子类结果),也可以被强转转换(向下转型)

    规律:多态自始至终都是子类对象在变化。

    package com.vnb.javabase;
    
    abstract class Animal{
    	
    	abstract void eat();
    }
    
    class Cat extends Animal{
    
    	@Override
    	void eat() {
          System.out.println("吃鱼");		
    	}
    	
    	public void catchMouse(){
    		
    		System.out.println("抓老鼠");
    	}
    }
    
    class Dog extends Animal{
    
    	@Override
    	void eat() {
          System.out.println("啃骨头");		
    	}
    	
    	public void kanJia(){
    		
    		System.out.println("看家");
    	}
    }
    
    public class DuotaiDemo {
    	
    	public static void main(String[] args) {
    		
    		Animal a = new Cat();//引用数据类型的类型提升,也称为向上转型
    	}
    	public static void function(Animal a){
    		a.eat();
    		if(a instanceof Cat){
    			Cat c = (Cat)a;
    			c.catchMouse();
    		}else if(a instanceof Dog){
    			Dog d = (Dog)a;
    			d.kanJia();
    		}
    	}
    }
    

    a instanceof Cat 判断a是否属于Cat这个引用类型。

    一般只用于传的类型是有限的;或传的类型的该类的所属类型,并对此类型有所使用。

    9.4 多态—示例(应用)

    需求:基础班的学生:学习、睡觉等

    高级班的学生:学习、睡觉等

    可以将这两类事物进行抽取。

    9.5多态中成员的特点

    运行结果:

    在多态中(父类有指向子类对象时)成员函数的特点:

    • 在编译时期,参阅引用型变量(Fu类)所属的类中是否有调用的方法,如果有编译通过,如果没有编译失败;
    • 在运行时期,参阅对象(Zi类)所属的类中是否有调用的方法(静态方法除外)。

    对于Fu f = new Zi();后调用子父类中的方法说明:子父类中都有此方法,使用子类中的方法(子类覆盖了父类方法);子类中没有此方法,调用父类的此方法(子继承父类);Fu类中没有此方法,编译失败。如下图(method3会编译失败):

     多态中,成员变量的特点,如下图:

    输出结果:

    在多态中,成员变量的特点:无论编译还是运行,都参考左边(引用型变量所属的类)(此情况一般只会面试出现)。因为有父类是,会先加载父类去拿num。

    打印结果:(开发一般不会出现这种情况)

    所以静态时不会出现多态中成员函数的特点。

    总结:在多态中,静态成员函数的特点:无论是编译时期还是运行时期,都是参考左边。

    因为静态方法存在方法区中,不需要对象,只需要Fu.method4()即可,看的是引用型对象加载的内容(Fu),而不是对象(Zi),只要父类型引用还在,用的还是父类引用,而不是对象。如下图:

    当调用f.method1()时,f确实执行的是对象,method1()被运行的时候是被对象运行,对象在调用非静态方法时,访问的是对象中的数据;但是静态方法method4()本身不访问对象特有的数据,用类名调用的话,它只看所属引用型(Fu)变量中的数据类型,即静态区中的方法,它不参考右边非静态区的方法。如下图:

    动态静态绑定:

    当method4()方法一进内存,因为是静态方法,就已经被绑定在方法所属的类的引用上(Fu),即method4属于Fu。而静态区上,this是动态的,this指向哪个对象就代表哪个对象。而现在写的是Fu f = new Zi(),f.method1(), new的还是Zi,所以运行的还是子类的方法(动态绑定);当Fu f = new Fu()此时就找的是Fu类上的方法。

    多态总结:

    9.6多态的电脑的主板示例

    需求:电脑运行示例,电脑运行是基于主板

    上图中,主板就可以跑起来了

    但是发现问题:运行一段时间后,想上网,想听音乐,主板没有此功能,因此就需要给主板提供扩展性的功能(原来功能毫无扩展性)。如下图:

    该类在内存中表示:

    为降低网卡声卡等和主板的耦合性,会在主板上留一个槽,并约定这些槽的规则,此后,只要符合这个规则,就可以在主板上运行。而这些插槽即接口的编写。如上图的PCI即设定的某些规则。代码如下图所示:

    PCI p = new NetCard();//接口型引用指向自己的子类对象。

    如上图,网卡声卡等都需要符合规则才能用,所以都需要实现PCI接口。

    9.7多态的扩展示例

    需求:对数据库的操作

    数据是:用户信息

    1. 连接数据库 JDBC Hibernate
    2. 操作数据库 CRUD
    3. 关闭数据库连接

    问题:某天发现JDBC太麻烦,需要使用某种框架进行连接数据库,按照以上方法,就需要修改所有方法。如下图:

    内存中:

    但是发现,主程序也需要改,这样代码耦合性太高,所以需要重新设计代码。

    分析:无论如何连接数据库,最后要的只是CRUD,无论采用哪种方式连接,内部的实现方式仍然相同,所以可以专门定义出数据库连接的规则。如下图,interface接口,事先定义好CRUD的接口。之后再需要使用其它数据库连接方式时,也只需要实现该接口即可。

    由此发现,连接数据库的程序和主程序不再有强的耦合关系。

    9.8 Object类 —equals()

    Object:是所有对象的直接或者间接父类,该类中定义的肯定是所有对象都具备的功能。

    java指定所有对象都具备比较性,都能比较两个对象是否相同。此方法定义在Object中,即equals()方法。

    例如:

    equals()方法比较的是对象的地址值。equals()里的参数不确定,所以使用Object,即可多态实现比较。

    自定义比较方法:

    使用“==”对象不同,数据相同即为真,如下图:

    问题:Demo中已经有了比较的方法equals()(超类Object),就不需要重新比较了(功能一致,可以沿袭父类,写自己特有的功能(复写(覆盖)equals()))。如下图:

    Object中没有定义过num,如果不向下转型,是不能进行编译的。

    问题,当传入的对象不同时,运行时出现不可转换错误。解析:一般是自己的对象和自己比较,不是则直接返回false。如下图:

    输出false。

    9.9Object类—toString()

    java 任意对象都可以通过toString()方法返回字符串,所以类.toString()就可以拿到对象的hash值。而hashCode方法也可以返回对象的hash值。

    图中Demo如何得到?

    一个对象的建立要根据类文件进行建立。一个类文件中包含名称、很多构造函数、一般方法等很多东西,如何获取到某个构造函数,只有这个对象最明确,所以可以通过Class来描述这些class文件,通过  类.getClass() 进行获得。

    类中有Class.getClass()方法,可以通过类.getMethods()获取到类文件中所有的方法。

    10、内部类

    10.1内部类概念

    将一个类定义在另一个类的里面,而里面那个类就称为内部类(内置类、嵌套类)。

    内部类访问特点:内部类可以直接访问外部类中的成员,包括私有成员;而外部类要访问内部类中的成员必须要建立内部类的对象。

    好处:可以直接访问外部类中的成员(包括私有成员),而不用创建对象。

    外部能不能直接访问内部类中的方法(function)?

    分析:inner不是独立存在,而是在Outer中,所以使用inner所在类.inner即可(很少用,因为内部类很可能会被private修饰(当内部类是外部类的成员时))。如下图:

    为什么内部类能直接访问外部类中的内容?

    输出结果为6。

    当内部外部存在变量相同时,优先获取内部变量;

    想拿到4(内部类的变量),使用this.x;想要拿到外部类的x=3,可用Outer.this.x。如下图:

    之所以可以直接访问外部类中的成员是因为内部类中持有了一个外部类的引用,该引用写法是”外部类名.this”。

    10.2静态内部类

    访问格式:

    • 当内部类定义在外部类的成员位置上,而且非私有,可以在外部其他类中,直接建立内部对象。格式:外部类名.内部类名  变量名 = 外部类对象.内部类对象(Outer.Inner in = new Outer().new Inner())
    • 当内部类在成员位置上,就可以被成员修饰符所修饰。比如,private:将内部类在外部类中进行封装,Static:内部类就具备了静态的特性。当内部类被静态修饰后,只能直接访问外部类中的静态成员了(外部成员也要用static修饰才行)。修饰出现限制了。如下图:

    修改后:在外部其他类中,如何直接访问static内部类的非静态呢?new Outer.Inner().function();如下图:

    在外部其他类中,如何直接访问static内部类的静态呢?

    new Outer.Inner.function();但是这种方式使用得很少。

    注意当内部类中定义了静态成员,该内部类必须是静态的(static)。如下例:

    即当function()方法被static修饰时,Inner也必须是静态修饰

    当外部类中的静态方法访问内部类时,静态类也必须是静态的。如下图:

    改为以下即可:

    10.3内部类定义原则

    类是用来描述现实中事物的,当描述事物时,事物内部还有事物,该事物就用内部类来描述,因为内部事物在使用外部事物中的内容(成员和函数等)。

    示例:描述人体,在描述心脏时,认为心脏也是一个功能,但是心脏描述比较复杂,有跳动、血液等等众多属性,对于这些属性就得用类进行描述,因为,心脏可以访问人体中其他部分,所以,将心脏定义成内部类最合适(内部类:类中包含另一个类),而且心脏不能直接被外部访问,但是提供外部访问的功能。

    10.4匿名内部类

    只有定义在成员位置上,才能被私有或静态所修饰。内部类可以写在类的任意位置上(成员、局部成员(变量或方法)等)

    如下例,访问规则没变:

    上例已经不能被静态私有修饰,Inner在局部方法里,在这里也不能使用static。局部内部类不能用static修饰,非静态没对象不会运行(必须有调用的地方new Inner().function(),如下)。

    方法中的局部变量,内部类在局部,那么内部类就能访问局部变量?

    修改后:

    有上例可知,

    1. 内部类定义在局部时,不能被成员修饰符修饰;
    2. 可以直接访问外部类中的成员,因为还持有外部类中的引用,但是不可以访问它所在的局部变量,只能访问被final修饰的局部变量

    如上图,方法中的参数也必须是final。

    如何只有一个对象实现多次调用method(),如下图:

    调用method()时a=7进栈,执行完后,会释放掉,再调用method()a =8,所以不会冲突。

    匿名内部类:

    1. 匿名内部类其实就是内部类的简写格式
    2. 定义匿名内部类的前提:内部类必须继承一个类,或者实现接口

    将下列例子简化成匿名内部类:

    简化后:匿名了,没有Inner类了,怎么new对象?如下:

    可以使用 new AbsDemo(){

                      void show();

                    }

    AbsDemo本身是抽象类,不能new,但是可以像下面这样写,在复写掉里面的方法。红框的内容是一个对象,是AbsDemo的子类对象(只有子类才可以复写父类的抽象方法)。

    匿名内部类调用本匿名内部类中的方法:

    1. 总结:匿名内部类的格式,new 父类或者接口(构造函数,可以往其传参){定义子类的内容}
    2. 其实匿名内部类就是一个匿名子类对象,可以理解为带内容的对象

    匿名内部类中也可以有子类特有的方法,不一定是覆盖父类的方法,但是,不可以同时调用show和abc方法(匿名内部类只能调用一次)。如下图:

    但是可以,建立多次匿名对象:

    简化,可以将多个匿名内部类取名(利用多态),如下图:

    d.show()可以是父类的方法,d.abc()不能调用,多态中的父类没有abc()方法。

    注:匿名内部类中定义的方法最好不要超过3个。可读性差。

    匿名内部类练习:

    如上图,Test.function:Test类中有一个静态方法function,.method():function这个方法运算后的结果是一个对象。而且是一个Inter类型的对象。因为是Inter类型的对象,才可以调用method()方法。

    Test.function().method();即等同于:

    Inter in = Test.function();

    in.method();

    什么时候用匿名内部类?

    当使用的方法参数类型是接口时,可以再调用方法时传一个匿名内部类(如AWT匿名监听器等)。如下图:

    若没有父类也没有抽象类或接口,想写个匿名内部类,怎么写?

    使用所有类的父类(Object对象),如下图:

    11、异常

    11.1异常概述

    异常:程序运行时出现不正常情况。

    异常由来:问题也是现实生活中的一个具体事物,也可以通过java的类的形式来进行描述,并封装成对象。异常其实就是java对不正常情况进行描述后的对象体现。

    对于问题的划分:一种是严重的问题;一种是非严重的问题。

    对于严重的,java通过Error类进行描述。对于Error类问题一般不编写针对性的代码进行处理;对于非严重的问题,java通过Exception类进行描述,对于Exception类的问题可以使用针对性的处理方式进行处理。

    无论Error或者是Exception都具有一些共性内容:不正常情况的信息、引发原因等,会向上抽取,抽取出来的即为:

    Throwable

         |--Error

         |--Exception

    API解释:

     

    Error异常:

    Exception异常:

    11.2异常try catch

    java 有内置的异常处理方式,只要有某些异常就会进行处理。java提供了特有的语句处理方式,即

    try{

      需要检测的代码

    }catch(异常类 变量){

      处理异常的代码(处理方式)

    } finally{

      一定会执行的语句

    }

    例如:

    在方法div()中,捕获到异常后封装成new AritchmeticException()后,会抛给main中的有try进行异常捕获newAritchmeticException()异常,捕获到后不会继续往下执行,而是有catch到(Exception e = new ArithmeticException();),再执行catch中的语句。

    对捕获到的异常对象进行常见方法操作:

    • String getMessage()  打印异常信息
    • String toString()  打印异常名及信息
    • void printStackTrace();  异常信息、类名及异常出现位置

    其实JVM默认的异常处理机制,就是在调用printStackTrace方法,打印异常的堆栈的跟踪信息。

    11.3异常声明throws

    编写功能时,不清楚是否调用者会传入正确的参数,所以,可以在功能上通过throws的关键字声明该功能有可能会出现问题。例:

    编译时会出现以下错误:

    但是如果不抛出,编译时不会出现问题,而运行会报错:

    如果一直抛出,直至抛给JVM,则会使用JVM默认的异常处理机制:

    抛异常和捕捉异常是有使用场景的。

    11.4多异常处理

    对多异常的处理:

    • 声明异常时,建议声明更为具体的异常,这样处理得更具体

    • 原则:对方声明几个异常就有几个catch块,不要定义多余的catch块,如果多个catch块中的异常出现继承关系,则父类异常catch块放在最下面。建议在进行catch处理时,catch一定要定义具体的处理方式,不要简单定义依据e.printStackTrace(),也不要简单的书写一条输出语句。做法:用硬盘文件记录异常日志文件。

    也可以抛出父类异常Exception:

    若出现了,意料之外的异常,应该是停止执行,让我们发现问题,进行针对性处理,而不是通过Exception进行。如下图:

    11.5自定义异常

    因为项目中会出现特有的问题,而这些问题并未被java所描述并封装对象,所以对这些特有的问题,可以按照java的对问题封装的思想,对本项目中的特有问题进行自定义异常封装。

    需求:在本程序中,对于除数是-1,也视为是错误的无法进行运算。那么就需要对这个问题进行自定义的描述。

    当函数内部出现了throw抛出异常对象,那么就必须要给对应的处理动作,要么在内部try catch;要么在函数上声明让调用者处理。一般情况在函数内出现异常,函数上需要声明。

    打印结果:

    发现打印结果中只有异常名称却没有异常信息,因为自定义异常并未定义所属信息,那么如何定义异常信息?

    打印结果:

    问题:发现父类构造函数中有个带message的方法

    父类中已经定义了构造函数接收异常信息,所以就会有变量接收这个信息,子类在继承这个类时,只要new Exception()就能获取到这些信息。类似下例:

    所以,异常类如下:

    总结:因为父类中已经把异常信息的操作都完成了,所以子类只要在构造时将异常信息通过super语句传递给父类,就可以直接通过getMessage()获取异常信息。

    自定义类,根据特有数据定义异常,如异常类名、异常的值等,如下图:

    输出结果:

    自定义异常:必须是自定义类继承Exception类。

    为什么继承Exception:

    异常体系有一个特点,因为异常类和异常对象都需要被抛出,他们都具备可抛性,这个可抛性是Throwable这个体系中的独有特点,只有这个体系中的类和对象才可以被throws和throw操作。只有继承Throwable、Exception、Error,只有在这个体系中才能使用throws和throw进行抛出异常。

    11.5异常—throws和throw的区别

    throws和throw的区别:

    1. throws使用在函数上,throw使用在函数内;
    2. throws后面跟的异常类,可以跟多个,用逗号隔开。throw后面跟的是异常对象(throw new 异常类)

    11.6RuntimeException

    ArithmeticException类构造函数有定义message的方法,可以直接使用这个构造方法打印错误信息:

    打印结果:发现函数内抛了,但是函数上并没有声明过,但是编译通过了。

    但是,当抛Exception时发现有安全隐患,编译时直接报错。

    原因:

    由API可发现ArithmeticException异常类的父类是RuntimeException异常,即运行时异常。该异常很特殊。RuntimeException或者RuntimeException的子类如果在函数内抛出了该异常,函数上可以不用声明异常,编译一样通过。如果在函数上声明了该异常,调用者可以不用进行处理(catch或throws),编译一样通过(如下例)。

    原因:之所以不用再函数上声明是因为不需要让调用者处理,当该异常发生,希望程序直接停止。因为在运行时出现了无法继续运算的情况,希望停止程序后由程序员对代码进行修正。如int x = 3/0,一旦3/0被允许执行了,那么x将无法得到正确的值,因此必须对3/0进行正确处理才行

    示例:

    如上例,发现当name传null时,很容易出现空指针异常,我们再出现name传null时,必须要程序员进行修改才行。因此要让其抛异常才行。而且不能在方法上使用throws,让其抛出去。如下图:

    总结:自定义异常时,如果该异常的发生,无法继续进行运算,就让自定义异常继承RuntimeExcetion。

    所以自定义异常示例可改为:

    输出结果:

    对于异常分两种:

    1. 编译时被检测的异常(javac编译时,发现方法中抛出了非RuntimeException及其子类,而方法上没有标识throws,就会认为有安全隐患。此异常时可处理的,要标识出去,让调用者进行对应的处理;如果函数上标识了throws,函数的调用者也必须进行处理,try或抛);
    2. 编译时不被检测的异常(运行时异常:RuntimeException及其子类)(方法内抛throw,但是方法上不标识throws,它会拿着异常对象去判断是否是运行时异常(e instanceof RuntimeException),如果是,无论标识与否都不管)

    11.7 异常练习(应用)

    需求:毕老师用电脑上课

     

    异常:电脑宕机、电脑蓝屏

    要对问题进行描述,封装成对象

    根据电脑操作状态的不同,导致不同问题的产生。

     

    电脑冒烟了,不能抛出去,因为抛出去了其他老师也处理不了,但是当冒烟出现问题后,讲课进度无法继续,出现了讲课进度无法完成的问题,所以虽然捕捉到的是MaoYanExcetion,但是要把它封装成我的问题再跑出去(讲课无法完成问题)再抛出去

    注意:throw后面不会继续执行,throw是函数结束的标志。

    11.7异常—finally

    finally块的代码无论如何都会执行,通常用于释放资源

    示例:数据库连接(数据库连接有限,一旦占用不释放,其他人就会一直连不上)

    连接数据库;数据库操作(throw new SQLException());关闭数据库(无论操作是否成功一定要关闭资源)

    数据没有存储成功也是异常,SQLException我们处理不了,但是数据没有存储成功是可以处理的(分层思想,模块式开发):

    11.8异常—处理语句其他格式

    以下,因为throw new Exception()语句,已经被catch捕捉处理掉,所以语句正确

    catch是处理异常,如果没有catch就代表异常没有被处理,如果该异常时检测时异常,那么必须声明。

    11.9异常—覆盖时异常的特点

    异常在子父类覆盖中的体现:

    • 子类在覆盖父类时,如果父类的方法抛出异常,那么子类的覆盖方法,只能抛出父类的异常或者该异常的子类。

    父类已经有问题了,子类要继承父类的话不能比分类还有问题,只能跑父类或者父类的子类的异常

    有一个子类覆盖父类,继承CException,且Test中传入子类对象:

    这样程序会挂,编译就是失败。

    • 如果父类方法抛出多个异常,那么子类在覆盖该方法时,只能抛出父类异常的子集
    • 如果父类或者接口的方法中没有异常抛出,那么子类在覆盖方法时,也不可以抛出异常;如果子类发生了异常,就必须要进行try处理,绝对不能抛

     

    10.10异常—练习

    有一个圆形和长方形,都可以获取面积,对于面积如果出现非法的数值,视为获取面积出现问题,问题通过异常来表示。

    基本设计:

     

    当传入负数时,面积为负数,无意义

    之前处理方式:

    会发现正常流程代码和问题处理代码结合比较紧密,阅读性差。异常的产生可以让流程处理代码和问题处理代码分离。

     

    输出:

    如果真的出现问题,一旦为负数,就没意义,不会继续执行,所以直接使用RuntimeException即可,也不用进行标识。主函数中也就不需要catch了

    求圆的面积:写RuntimeException也可以,但是,此名称跟程序意义无关,不能见名知意

    所以还是写自定义异常比较直观:

    11.11异常—总结

    异常:是对问题的描述,将问题进行对象的封装。

    异常体系:

    Throwable

    |--Error

    |--Exception

       |--RuntimeException

    异常体系的特点:异常体系中的所有类以及建立的对象都具备可抛性,也就是说可以被throw和throws关键字所操作。只有异常体系具备这个特点。

    throw和throws的用法:

    throw定义在函数内,用于抛出异常对象,throws定义在函数上,用于抛出异常类,可以抛出多个用逗号隔开。

    当函数内容有throw抛出异常对象,并未进行try处理。必须在函数上声明,否则编译失败。注意RuntimeException除外。也就是说,如果函数内抛出的是RuntimeException异常,函数上可以不用声明。

    如果函数声明了异常,调用者需要进行处理,处理方式可throws可以try。

    异常有两种:一种叫做编译时被检测异常(该异常在编译时,如果没有处理(没有抛也没有try),那么编译失败,该异常被标识,代表着可以被处理);一种叫做运行时异常(编译时不检测)(在编译时,不需要处理,编译器不检查,该异常的发生,建议不处理,让程序停止,需要对代码进行修正)

    异常处理语句:

    try{

      需要被检测的代码

    }catch(){

      处理异常的代码

    }finally{

      一定会处理的代码

    }

    有三种结合格式:

    try{

    }catch(){

    }

    try{

    }finally{

    }

    try{

    }catch(){

    }finally{

    }

    注意:

    1. finally中定义的通常是关闭资源代码,因为资源必须要释放。
    2. finally有一种情况读不到:即系统退出(System.exit(0)虚拟机结束时,finally不会再执行)

    自定义异常:

    定义类继承Exception或者RuntimeException

    1. 为了让该自定义类具备可抛性
    2. 让该类具备操作异常的共性方法

    当要定义自定义异常的信息时,可以使用父类已经定义好的功能。异常信息传递给父类的构造函数

    class MyException extends Exception{

    MyException(String message){

      super(message);

    }

    }

    自定义异常:

    按照java的面向对象思想,将程序中出现的特有问题进行封装

    自定义异常好处:

    1. 将问题进行封装
    2. 将正常流程代码和问题处理代码相分离,方便阅读

    异常的处理原则:

    1. 处理方式有两种:try 或者throws
    2. 调用到抛出异常的功能时,抛出几个,就处理几个(不多抛不多处理)。一个try对应多个catch的情况。
    3. 多个catch,父类的catch放到最下面
    4. catch内需要定义针对性的处理方式,不要简单的定义printStackTrace输出语句,也不要不写。当捕获到的异常,本功能处理不了时,可以继续在catch中抛出即:

    try{

      throw new AException();

    }catch(AException e){

      throw e;

    }

    如果该异常处理不了,但并不属于该功能出现的异常,可以将异常妆花后,再抛出和该功能相关的异常。

    或者异常可以处理,当需要将异常产生的和本功能相关的问题提供出去,让调用者知道,并处理。也可以将捕获异常处理后,转换为新的异常

    try{

      throw new AException();

    }catch(AException e){

      //捕获到AException但是AException处理不了,可以转换为新的异常处理类

     throw new BException();

    }

    比如,汇款的例子

    异常的注意事项:

    在子父类覆盖时:

    1. 子类抛出的异常必须是父类的异常的子类或者子集;
    2. 如果父类或者接口没有异常抛出时,子类覆盖出现异常,只能try不能抛

    参阅:ExceptionTest.java 老师用电脑上课

          ExceptionTest1.java图形的面积

    11.12异常—练习四

    如上图,首先执行main中的func()方法,调用到func(),执行try抛异常,执行finally中B,再由main()中捕捉到异常执行main中catch得到C,A在func()有异常后不再执行,再执行D,所以,输出为B  C  D。

    如上图,main方法中首先执行new Demo后,执行到默认的super()方法(默认有,没有写而已),从而执行到其父类Test类,输出Test,然后输出Demo,在执行main中new Test(),输出Test。

    在多态中(父类有指向子类对象时)成员函数的特点:

    在编译时期,参阅引用型变量(Fu类)所属的类中是否有调用的方法,如果有编译通过,如果没有编译失败;//此处A接口中没有定义func()方法,所以编译失败

    在运行时期,参阅对象(Zi类)所属的类中是否有调用的方法。

    如上图,首次执行到for循环时,f.show(‘A’)执行Demo类中Demo()方法,打印A输出false,而f.show(‘B’)也执行Demo类中的Demo()方法打印B输出false满足条件,(i<2)不再执行,因此循环直接结束。(在运行时期,参阅对象(Zi类)所属的类中是否有调用的方法。)。

    如上图,A接口中没有定义Test方法,编译失败。

    如上图,执行main中new Demo(“A”)后,执行到Demo类中构造方法,而其中会有个默认的super()方法,从而执行到父类Super中的狗仔函数Super(),从而打印B,执行i+=2,然后继续执行Demo类中Demo()构造函数中代码,打印C,将i赋值为5,然后继续执行mian(),从而打印d.i  为5

    如上图,定义匿名内部类的前提:内部类必须继承一个类,或者实现接口。

    interface Inter{

    void show(int a,int b);

    void func();

    }

    class Demo {

    public static void main(String[] args){

      Inter in = new Inter(){

        public void show(int a,int b){}

        public void func(){}

    }

    in.show();

    in.func();

    }

    }

    匿名内部类一定要在方法上加上public标识共有;

    要调用匿名内部类的多个方法,使用Inter in = new Inter(){}格式后,用实例名in调用即可

    如上图,编译失败,非静态内部类,不可以定义静态成员;

    内部类中如果定义了静态成员,该内部类必须被静态修饰

    如上图,读清楚题目,是存在于Demo的子类中,不是再同一个函数的重载

    A 可以,覆盖

    B 不可以 权限不够

    C 可以  和父类不是一个函数。没有覆盖相当于重载

    D 不可以,因为该函数不可以和给定函数出现在同一类中,或者子父类中

    E 不可以,静态只能覆盖静态

    this:代表本类对象,哪个对象调用this所在函数,this就代表哪个对象

    final:

    1. 修饰类,变量(成员变量、静态变量、局部变量),函数
    2. 修饰的类不可以被继承
    3. 修饰的方法不可以被覆盖
    4. 修饰的变量是一个常量,只能赋值一次
    5. 内部类只能访问局部的final形式变量

    11.

    如上图,输出结果:4  5  showZi  showZi

    成员变量看左边,变量不存在覆盖;

    方法看右边

     

    如上图,定义成员变量,然后计算和

    局部接收进来的,值改变后,其他方法也可以用

    如上图,输出BCD

    如上图,编译失败,应该父类中缺少空参数的构造函数;或者子类应该通过super语句指定要调用的父类中的构造函数

    如上图,编译失败:因为子父类中的get方法没有覆盖,子类调用的时候不能明确返回值是什么类型,所以这样的函数不能存在在子父类中。

    如上图,编译失败:因为打印字符串“A”的输出语句执行不到。

    如上图,16题和13题区:把抛出异常封装在方法中,代表有可能有问题,有可能没有问题,所以13题中的A是有可能执行到的,但是16题中A绝对不可能执行到。

    如上图,A ok 

    B 不可以,因为主函数是静态的,如果要访问inner需要被static修饰

    C 错误,格式错误

    D 错误 因为inner不是静态的

    编译失败,多个catch时,父类的catch要放在最下面。

     

     

    如上图,方法时静态的,所以调用多次后,前面一次方法不会销毁,且output也是共享的不会销毁,foo(0)执行后,在执行foo(1)时继续累加

    return语句后,finally中的语句继续执行,但是finally外的不会继续执行

    所以结果是 13423         

     

    已经做过

     

     

    输出4

     

    已经做过

    如上图,数组查找,数组可能无序,所以不要折半

     

    如上图,用到本类对象this

    前面的cir用this表示,cir2传入

    12包

    12.1包package

    1. 对类文件进行分类管理
    2. 给类提供多层命名空间
    3. 写在程序文件的第一行
    4. 类名的全称是 包名.类名
    5. 包也是一种封装形式

    命令中加参数用于创建包的目录

    javac –d . PackageDemo.java

    -d 指定包所存放的位置

    . 表示当前目录

    PackageDemo.java 类名

    执行时:

    java pack.PackageDemo

    不存在当前目录(而存放到指定目录):

    javac –d c:\ PackageDemo.java

    set classpath–c:\  指向包(pack)的父目录即可

    java pack.PackageDemo

    包的出现可以让java的类文件和class文件相分离,当别人要执行时,可以只给class运行文件,而不用给源文件

    12.2包与包之间的访问

     

    先编译DemoA,再编译PackageDemo

    编译时将编译文件存在存一个路径

    发现报错。

    错误原因:类名写错

    因为类名的全名是:包名.类名

    改正后:

    发现又报错:

    错误原因:软件包不存在

    packa包不再当前目录下,需要设置classpath,告诉JVM去哪里找指定的packa包

    包找到了,但是又发现其他错误

    有了包,范围变大,一个保重的类要被访问,必须要有足够大的权限,所以要被访问的类需要被public修饰

    因为DemoA类的修饰符没有权限

    类的修饰符有两个:protected、public

    改完后发现其他错误:

    错误原因:类共有后,被访问的成员也要共有才可以被访问

    改正后,运行正确

    总结:

    • 包与包之间进行访问,被访问的包中的类以及类中的成员,需要public修饰
    • 不同包中的子类还可以直接访问父类中被protected权限修饰的成员(java给包与包中的方法提供权限protected权限(默认),使其不继承也可以拿到某类中的方法)

    包与包之间使用的权限只有两种:public和protected(只能给子类用)

     

    public

    protected

    default

    private

    同一类中

    Ok

    Ok

    Ok

    Ok

    同一包中

    Ok

    Ok

    Ok

     

    子类

    Ok

    Ok

     

     

    不同保重

    Ok

     

     

     

    一个java文件里不能出现两个公有类或接口:(以下代码错误)

    可以将两个文件放在同一个包下

    12.3导入import

    为了简化类名的书写,使用一个关键字:import

    同一目录下有多个类时,使用*,导入到文件

    import导入的是包中的类,如果有包是不能导入的

    建议不要写通配符*,需要用到包中的那个类就导入哪个类,否则占用内存太多

    导入不同包的同名的包时,必须写清楚类的全名(包名+类名)

    如上例,packa和backb包中存在同名的DemoC类时,使用时,必须写DemoCracy的全名packa.DemoC c = new packa.DemoC();

    建议定义包名时不要重复,可以使用URL来完成定义,URL是唯一的

    12.4jar包

    java的压缩包

    1. 方便项目的携带
    2. 方便与使用,只要在classpath设置jar路径即可
    3. 数据库驱动,SSH框架等都是以jar包体现的

    java打jar包需要借助java JDK工具java.exe

    jar命令的用法:

    如:

    -c 创建新的归档文件

    -f 创建的归档文件名

    haha.jar jar包名字

    packa pack 需要打成包的文件夹(注:命令必须在当前文件夹下执行,如例两个文件夹在myclass文件下)

    jar包和打成其他包的区别:

    jar –tf haha.jar  查看归档目录

    存放了java特有的配置文件,且

    目录下存放的文件都删除了,双击jar包时仍然可以执行的。如下,

    使用jar –cvf a.jar packa pack  可以显示打印时的详细信息

    jar –tvf a.jar packa pack 可以显示时间等详细信息

    数据重定向

    将打包时的数据定向到1.txt文件夹内:

    将jar包中所有的类重定向放到rt.txt文件中

    展开全文
  • R语言面向对象指南

    千次阅读 2015-09-21 21:53:00
    面向对象指南:这一章主要介绍怎样识别和使用 R 语言的面向对象系统(以下简称 OO)。R 语言主要有三种 OO 系统(加上基本类型)。本指南的目的不是让你精通 R 语言的 OO,而是让你熟悉各种系统,并且能够准确地区分...

    原文链接:OO field guide


    面向对象指南:

    这一章主要介绍怎样识别和使用 R 语言的面向对象系统(以下简称 OO)。R 语言主要有三种 OO 系统(加上基本类型)。本指南的目的不是让你精通 R 语言的 OO,而是让你熟悉各种系统,并且能够准确地区分和使用它们。
    OO 最核心的就是类和方法的思想,类在定义对象的行为时主要是通过对象的属性以及它和其它类之间的关系。根据类的输入不同,类对方法、函数的选择也会不同。类的建造是有层次结构的:如果一个方法在子类中不存在,则使用父类中的方法;如果存在则继承父类中方法。

    三种 OO 系统在定义类和方法的时候有以下不同:

    • S3 实现的是泛型函数式 OO ,这与大部分的编程语言不同,像 Java、C++ 和 C# 它们实现的是消息传递式的 OO 。如果是消息传递,消息(方法)是传给一个对象,再由对象去决定调用哪个方法的。通常调用方法的形式是“对象名.方法名”,例如:canvas.drawRect(“blue”) 。而 S3 不同,S3 调用哪个方法是由泛型函数决定的,例如:drawRect(canvas, “blue”)。S3 是一种非正式的 OO 模式,它甚至都没有正式定义类这个概念。
    • S4 与 S3 很相似,但是比 S3 正规。S4 与 S3 的不同主要有两点:S4 对类有更加正式的定义(描述了每个类的表现形式和继承情况,并且对泛型和方法的定义添加了特殊的辅助函数);S4 支持多调度(这意味着泛型函数在调用方法的时候可以选择多个参数)。
    • Reference classes (引用类),简称 RC ,和 S3、S4有很大区别。RC 实现的是消息传递式 OO ,所以方法是属于类的,而不是函数。对象和方法之间用”$”隔开,所以调用方法的形式如:canvas$drawRect(“blue”) 。RC 对象也总是可变的,它用的不是 R 平常的 copy-on-modify 语义,而是做了部分修改。从而可以解决 S3、S4 难以解决的问题。

    还有另外一种系统,虽然不是完全的面向对象,但还是有必要提一下:

    • base types(基本类型),主要使用C语言代码来操作。它之所以重要是因为它能为其它 OO 系统提供构建块。

    以下内容从基本类型开始,逐个介绍每种 OO 系统。你将学习到怎样识别一个对象是属于哪种 OO 系统、方法的调用和使用,以及在该 OO 系统下如何创建新的对象、类、泛型和方法。本章节的结尾也有讲述哪种情况应该使用哪种系统。

    前提:

    你首先需要安装 pryr 包来获取某些函数:install.packages(“pryr”) 。

    问题:

    你是否已经了解本文要讲述的内容?如果你能准确地回答出以下问题,则可以跳过本章节了。答案请见本文末尾的问题答案

    1. 你怎样区分一个对象属于哪种 OO 系统?
    2. 如何确定基本类型(如整型或者列表)的对象?
    3. 什么是类的函数?
    4. S3 和 S4 之间的主要差异是什么? S4 和 RC 之间最主要的差异又是什么?

    文章梗概:


    基本类型:

    基本上每个 R 对象都类似于描述内存存储的 C 语言结构体,这个结构体包含了对象的所有内容(包括内存管理需要的信息,还有对象的基本类型)。基本类型并不是真的对象系统,因为只有 R 语言的核心团队才能创建新的类型。但结果新的基本类型竟然也很少见地被添加了:最近是在2011年,添加了两个你从来没在 R 里面见过的奇异类型(NEWSXP 和 FREESXP),它们能够有效地诊断出内存上的问题。在此之前,2005年为 S4 对象添加了一个特殊的基本类型(S4SXP)。

    Data structures 章节讲述了大部分普通的基本类型(原子向量和列表),但基本类型还包括 functions、environments,以及其它更加奇异的对象,如 names、calls、promises,之后你将会在本书中学到。你可以使用 typeof() 来了解对象的基本类型。但基本类型的名字在 R 中并不总是有效的,并且类型和 “is” 函数可能会使用不同的名字:

    # The type of a function is "closure"
    f <- function() {}
    typeof(f)
    #> [1] "closure"
    is.function(f)
    #> [1] TRUE
    
    # The type of a primitive function is "builtin"
    typeof(sum)
    #> [1] "builtin"
    is.primitive(sum)
    #> [1] TRUE

    你可能听过 mode() 和 storage.mode(),我建议不要使用这两个函数,因为它们只是 typeof() 返回值的别名,而且只使用与 S 语言。如果你想了解它们具体如何实现,可以去看一下它们的源代码。

    不同基本类型的函数一般都是用 C 语言编写的,在调度时使用switch语句(例如:switch(TYPEOF(x)))。尽管你可能没有写过 C 语言,但理解基本类型仍然很有必要,因为其他系统都是在此基础上的:S3 对象可以建立在所有基本类型上,S4 使用一个特殊的基本类型,而 RC 对象是 S4 和 environments(一个特殊的基本类型)的结合体。查看对象是否是一个单纯基本类型(即它不同时含 S3、S4、RC 的行为),使用 is.object(x) ,返回TRUE/FALSSE。


    S3:

    S3 是 R 语言的第一种也是最简单的一种 OO 系统。它还是唯一一种在基础包和统计包使用的 OO 系统,CRAN包中最平常使用的 OO 系统。

    识别对象、泛型函数、方法:

    你遇到的大部分对象都是 S3 对象。但不幸的是在 R 中并没有可以简单检测一个方法是否是 S3 的方法。最接近的方法就是 is.object(x) & !isS4(x),即它是一个对象,但不是 S4 对象。一个更简单的方法就是使用 pryr::otype() :

    library(pryr)
    
    df <- data.frame(x = 1:10, y = letters[1:10])
    otype(df)    # A data frame is an S3 class
    #> [1] "S3"
    otype(df$x)  # A numeric vector isn't
    #> [1] "base"
    otype(df$y)  # A factor is
    #> [1] "S3"

    在 S3,方法是属于函数的,这些函数叫做泛型函数,或简称泛型。S3 的方法不属于对象或者类。这和大部分的编程语言都不同,但它确实是一种合法的 OO 方式。

    你可以调用 UseMethod() 方法来查看某个函数的源代码,从而确定它是否是 S3 泛型。和 otype() 类似,prpy 也提供了 ftype() 来联系着一个函数(如果有的话)描述对象系统。

    mean
    #> function (x, ...) 
    #> UseMethod("mean")
    #> <bytecode: 0x24bfa50>
    #> <environment: namespace:base>
    ftype(mean)
    #> [1] "s3"      "generic"

    有些 S3 泛型,例如 [ 、sum()、cbind(),不能调用 UseMethod(),因为它们是用 C 语言来执行的。不过它们可以调用 C 语言的函数 DispatchGroup() 和 DispatchOrEval()。利用 C 代码进行方法调用的函数叫作内部泛型。可以使用 ?”internal generic” 查看。

    给定一个类,S3 泛型的工作是调用正确的 S3 方法。你可以通过 S3 方法的名字来识别(形如 generic.class())。例如,泛型 mean() 的 Date 方法为 mean.Date(),泛型print() 的向量方法为 print.factor() 。
    这也就是为什么现代风格不鼓励在函数名字里使用 “.” 的原因了。类的名字也不使用 “.” 。pryr::ftype() 可以发现这些异常,所以你可以用它来识别一个函数是 S3 方法还是泛型:

    ftype(t.data.frame) # data frame method for t()
    #> [1] "s3"     "method"
    ftype(t.test)       # generic function for t tests
    #> [1] "s3"      "generic"

    你可以调用 methods() 来查看属于某个泛型的所有方法:

    methods("mean")
    #> [1] mean.Date     mean.default  mean.difftime mean.POSIXct  mean.POSIXlt 
    #> see '?methods' for accessing help and source code
    methods("t.test")
    #> [1] t.test.default* t.test.formula*
    #> see '?methods' for accessing help and source code

    (除了在基础包里面定义的一些方法,大多数 S3 的方法都是不可见的使用 getS3method() 来阅读它们的源码。)

    你也可以列出一个给出类中包含某个方法的所有泛型:

    methods(class = "ts")
    #>  [1] aggregate     as.data.frame cbind         coerce        cycle        
    #>  [6] diffinv       diff          initialize    kernapply     lines        
    #> [11] Math2         Math          monthplot     na.omit       Ops          
    #> [16] plot          print         show          slotsFromS3   time         
    #> [21] [<-           [             t             window<-      window       
    #> see '?methods' for accessing help and source code

    你也可以从接下来的部分知道,要列出所有的 S3 类是不可能的。

    定义类和创建对象:

    S3 是一个简单而特殊的系统,它对类没有正式的定义。要实例化一个类,你只能拿一个已有的基础对象,再设置类的属性。你可以在创建类的时候使用 structure(),或者事后用 class<-():

    # Create and assign class in one step
    foo <- structure(list(), class = "foo")
    
    # Create, then set class
    foo <- list()
    class(foo) <- "foo"

    S3 对象的属性通常建立在列表或者原子向量之上(你可以用这个属性去刷新你的内存属性),你也能把函数转成 S3 对象,其他基本类型要么在 R 中很少见,要么就是该语义不能很好地在属性下运行。
    你可以通过 class() 把类看作任意的对象,也可以通过 inherits(x, “classname”) 来查看某个对象是否继承自某个具体的类。

    class(foo)
    #> [1] "foo"
    inherits(foo, "foo")
    #> [1] TRUE

    S3 对象所属于的类可以被看成是一个向量,一个通过最重要的特性来描述对象行为的向量。例如对象 glm() 的类是 c(“glm”, “lm”),它表明着广义线性模型的行为继承自线性模型。类名通常是小写的,并且应该避免使用 “.” 。否则该类名将会混淆为下划线形式的 my_class,或者 CamelCase 写法的 MyClass。

    大多数的 S3 类都提供了构造函数:

    foo <- function(x) {
      if (!is.numeric(x)) stop("X must be numeric")
      structure(list(x), class = "foo")
    }

    如果它是可用的,则你应该使用它(例如 factor() 和 data.frame())。这能确保你在创造类的时候使用正确的组件。构造函数的名字一般是和类名是相同的。

    开发者提供了构造函数之后,S3 并没有对它的正确性做检查。这意味着你可以改变现有对象所属于的类:

    # Create a linear model
    mod <- lm(log(mpg) ~ log(disp), data = mtcars)
    class(mod)
    #> [1] "lm"
    print(mod)
    #> 
    #> Call:
    #> lm(formula = log(mpg) ~ log(disp), data = mtcars)
    #> 
    #> Coefficients:
    #> (Intercept)    log(disp)  
    #>      5.3810      -0.4586
    
    # Turn it into a data frame (?!)
    class(mod) <- "data.frame"
    # But unsurprisingly this doesn't work very well
    print(mod)
    #>  [1] coefficients  residuals     effects       rank          fitted.values
    #>  [6] assign        qr            df.residual   xlevels       call         
    #> [11] terms         model        
    #> <0 rows> (or 0-length row.names)
    # However, the data is still there
    mod$coefficients
    #> (Intercept)   log(disp) 
    #>   5.3809725  -0.4585683

    如果你在之前使用过其他的 OO 语言,S3 可能会让你觉得很恶心。但令人惊讶的是,这种灵活性带来的问题很少:虽然你能改变对象的类型,但你并不会这么做。R 并不用提防自己:你可以很容易射自己的脚,只要你不把抢瞄在你的脚上并扣动扳机,你就不会有问题。

    创建新的方法和泛型:

    如果要添加一个新的泛型,你只要创建一个叫做 UseMethod() 的函数。UseMethod() 有两个参数:泛型函数的名字和用来调度方法的参数。如果第二个参数省略了,则根据第一个参数来调度方法。但是没有必要去省略 UseMethod() 的参数,你也不应该这么做。

    f <- function(x) UseMethod("f")

    没有方法的泛型是没有用的。如果要添加方法,你只需要用 generic.class 创建一个合法的函数:

    f.a <- function(x) "Class a"
    
    a <- structure(list(), class = "a")
    class(a)
    #> [1] "a"
    f(a)
    #> [1] "Class a"

    用同样的方法可以对已有的泛型添加方法:

    mean.a <- function(x) "a"
    mean(a)
    #> [1] "a"

    如你所看到的,它并没有确保类和泛型兼容的检查机制,它主要是靠编程者自己来确定自己的方法不会违反现有代码的期望。

    方法调度:

    S3 的方法调度比较简单。UseMethod() 创建一个向量或者一个函数名字(例如:paste0(“generic”, “.”, c(class(x), “default”))),并逐个查找。default 类作为回落的方法,以防其他未知类的情况。

    f <- function(x) UseMethod("f")
    f.a <- function(x) "Class a"
    f.default <- function(x) "Unknown class"
    
    f(structure(list(), class = "a"))
    #> [1] "Class a"
    # No method for b class, so uses method for a class
    f(structure(list(), class = c("b", "a")))
    #> [1] "Class a"
    # No method for c class, so falls back to default
    f(structure(list(), class = "c"))
    #> [1] "Unknown class"

    组泛型方法增加了一些复杂性,组泛型为一个函数实现复合泛型的多个方法提供了可能性。它们包含的四组泛型和函数如下:

    • Math: abs, sign, sqrt, floor, cos, sin, log, exp, …
    • Ops: +, -, *, /, ^, %%, %/%, &, |, !, ==, !=, <, <=, >=, >
    • Summary: all, any, sum, prod, min, max, range
    • Complex: Arg, Conj, Im, Mod, Re

    组泛型是相对比较先进的技术,超出了本章的范围。但是你可以通过 ?groupGeneric 查看更多相关信息。区分组泛型最关键的是要意识到 Math、Ops、Summary 和 Complex 并不是真正的函数,而是代表着函数。注意在组泛型中有特殊的变量 .Generic 提供实际的泛型函数调用。

    如果你有复数类模板的层次结构,那么调用“父”方法是有用的。要准确定义它的意义的话有点难度,但如果当前方法不存在的话它基本上都会被调用。同样的,你可以使用 ?NextMethod 查看相关信息。

    因为方法是正规的 R 函数,所以你可以直接调用它:

    c <- structure(list(), class = "c")
    # Call the correct method:
    f.default(c)
    #> [1] "Unknown class"
    # Force R to call the wrong method:
    f.a(c)
    #> [1] "Class a"

    不过这种调用的方法和改变对象的类属性一样危险,所以一般都不这样做。不要把上膛了的枪瞄在自己的脚上。使用上述方法的唯一原因是它可以通过跳过方法调用达到很大的性能改进,你可以查看性能章节查看详情。

    非 S3 对象也可以调用 S3 泛型,非内部的泛型会调用基本类型的隐式类。(因为性能上的原因,内部的泛型并不会这样做。)确定基本类型的隐式类有点难,如下面的函数所示:

    iclass <- function(x) {
      if (is.object(x)) {
        stop("x is not a primitive type", call. = FALSE)
      }
    
      c(
        if (is.matrix(x)) "matrix",
        if (is.array(x) && !is.matrix(x)) "array",
        if (is.double(x)) "double",
        if (is.integer(x)) "integer",
        mode(x)
      )
    }
    iclass(matrix(1:5))
    #> [1] "matrix"  "integer" "numeric"
    iclass(array(1.5))
    #> [1] "array"   "double"  "numeric"

    练习:

    1. 查阅 t() 和 t.test() 的源代码,并证明 t.test() 是一个 S3 泛型而不是 S3 方法。如果你用 test 类创建一个对象并用它调用 t() 会发生什么?
    2. 在 R 语言的基本类型中什么类有 Math 组泛型?查阅源代码,该方法是如何工作的?
    3. R 语言在日期时间上有两种类,POSIXct 和 POSIXlt(两者都继承自 POSIXt)。哪些泛型对于这两个类是有不同行为的?哪个泛型共享相同的行为?
    4. 哪个基本泛型定义的方法最多?
    5. UseMethod() 通过特殊的方式调用方法。请预测下列代码将会返回什么,然后运行一下,并且查看 UseMethod() 的帮助文档,推测一下发生了什么。用最简单的方式记下这些规则。
    y <-1
    g <-function(x) {
      y <-2UseMethod("g")
    }
    g.numeric <-function(x) y
    g(10)
    
    h <-function(x) {
      x <-10UseMethod("h")
    }
    h.character <-function(x) paste("char", x)
    h.numeric <-function(x) paste("num", x)
    
    h("a")
    1. 内部泛型不分配在基类类型的隐式类。仔细查阅 ?”internal generic”,为什么下面例子中的 f 和 g 的长度不一样?哪个函数可以区分 f 和 g 的行为?
    f <- function() 1
    g <- function() 2
    class(g) <- "function"
    
    class(f)
    class(g)
    
    length.function <- function(x) "function"
    length(f)
    length(g)


    S4:

    S4 工作的方式和 S3 比较相似,但它更加正式和严谨。方法还是属于函数,而不是类。但是:

    • 类在描述字段和继承结构(父类)上有更加正式的定义。
    • 方法调用可以传递多个参数,而不仅仅是一个。
    • 出现了一个特殊的运算符——@,从 S4 对象中提取 slots(又名字段)。

    所以 S4 的相关代码都存储在 methods 包里面。当你交互运行 R 程序的时候这个包都是可用的,但在批处理的模式下则可能不可用。所以,我们在使用 S4 的时候一般直接使用 library(methods) 。
    S4 是一种丰富、复杂的系统,并不是一两页纸能解释完的。所以在此我把重点放在 S4 背后的面向对象思想,这样大家就可以比较好地使用 S4 对象了。如果想要了解更多,可以参考以下文献:

    • S4 系统在 Bioconductor 中的发展历程
    • John Chambers 写的《Software for Data Analysis》
    • Martin Morgan 在 stackoverflow 上关于 S4 问题的回答

    识别对象、泛型函数和方法:

    要识别 S4 对象 、泛型、方法还是很简单的。对于 S4 对象:str() 将它描述成一个正式的类,isS4() 会返回 TRUE,prpy::otype() 会返回 “S4” 。对于 S4 泛型函数:它们是带有很好类定义的 S4 对象。
    常用的基础包里面是没有 S4 对象的(stats, graphics, utils, datasets, 和 base),所以我们要从内建的 stats4 包新建一个 S4 对象开始,它提供了一些 S4 类和方法与最大似然估计:

    library(stats4)
    
    # From example(mle)
    y <- c(26, 17, 13, 12, 20, 5, 9, 8, 5, 4, 8)
    nLL <- function(lambda) - sum(dpois(y, lambda, log = TRUE))
    fit <- mle(nLL, start = list(lambda = 5), nobs = length(y))
    
    # An S4 object
    isS4(fit)
    #> [1] TRUE
    otype(fit)
    #> [1] "S4"
    
    # An S4 generic
    isS4(nobs)
    #> [1] TRUE
    ftype(nobs)
    #> [1] "s4"      "generic"
    
    # Retrieve an S4 method, described later
    mle_nobs <- method_from_call(nobs(fit))
    isS4(mle_nobs)
    #> [1] TRUE
    ftype(mle_nobs)
    #> [1] "s4"     "method"

    用带有一个参数的 is() 来列出对象继承的所有父类。用带有两个参数的 is() 来验证一个对象是否继承自该类:

    is(fit)
    #> [1] "mle"
    is(fit, "mle")
    #> [1] TRUE

    你可以使用 getGenerics() 来获取 S4 的所有泛型函数,或者使用 getClasses() 来获取 S4 的所有类。这些类包括 S3 对 shim classes 和基本类型。另外你可以使用 showMethods() 来获取 S4 的所有方法。

    定义类和新建对象

    在 S3,你可以通过更改类的属性就可以改变任意一个对象,但是在 S4 要求比较严格:你必须使用 setClass() 定义类的声明,并且用 new() 新建一个对象。你可以用特殊的语法 class?className(例如:class?mle)找到该类的相关文档。
    S4 类有三个主要的特性:

    • 名字:一个字母-数字的类标识符。按照惯例,S4 类名称使用 UpperCamelCase 。
    • 已命名的 slots(字段),它用来定义字段名称和允许类。例如,一个 person 类可能由字符型的名称和数字型的年龄所表征:list(name = "character", age = "numeric")
    • 父类。你可以给出多重继承的多个类,但这项先进的技能增加了它的复杂性。

    slotscontains,你可以使用setOldClass()来注册新的 S3 或 S4 类,或者基本类型的隐式类。在slots,你可以使用特殊的ANY类,它不限制输入。
    S4 类有像 validity 方法的可选属性,validity 方法可以检验一个对象是否是有效的,是否是定义了默认字段值的 prototype 对象。使用?setClass查看更多细节。
    下面的例子新建了一个具有 name 字段和 age 字段的 Person 类,还有继承自 Person 类的 Employee 类。Employee 类从 Person 类继承字段和方法,并且增加了字段 boss 。我们调用 new() 方法和类的名字,还有name-values这样成对的参数值来新建一个对象。

    setClass("Person",
      slots = list(name = "character", age = "numeric"))
    setClass("Employee",
      slots = list(boss = "Person"),
      contains = "Person")
    
    alice <- new("Person", name = "Alice", age = 40)
    john <- new("Employee", name = "John", age = 20, boss = alice)

    大部分 S4 类都有一个和类名相同名字的构造函数:如果有,可以直接用它来取代 new()
    要访问 S4 对象的字段,可以用 @ 或者 slot()

    alice@age
    #> [1] 40
    slot(john, "boss")
    #> An object of class "Person"
    #> Slot "name":
    #> [1] "Alice"
    #> 
    #> Slot "age":
    #> [1] 40

    @$ 等价,slot()[] 等价)
    如果一个 S4 对象继承自 S3 类或者基本类型,它会有特殊的属性 .Data

    setClass("RangedNumeric",
      contains = "numeric",
      slots = list(min = "numeric", max = "numeric"))
    rn <- new("RangedNumeric", 1:10, min = 1, max = 10)
    rn@min
    #> [1] 1
    rn@.Data
    #>  [1]  1  2  3  4  5  6  7  8  9 10

    因为 R 是响应式编程的语言,所以它可以随时创建新的类或者重新定义现有类。这将会造成一个问题:当你在响应式地调试 S4 的时候,如果你更改了一个类,你要知道你已经把该类的所有对象都更改了。

    新建方法和泛型函数

    S4 提供了特殊的函数来新建方法和泛型。setGeneric() 将产生一个新的泛型,或者把已有函数转成泛型。

    setGeneric("union")
    #> [1] "union"
    setMethod("union",
      c(x = "data.frame", y = "data.frame"),
      function(x, y) {
        unique(rbind(x, y))
      }
    )
    #> [1] "union"

    如果你要重新创建了一个泛型,你需要调用 standardGeneric() :

    setGeneric("myGeneric", function(x) {
      standardGeneric("myGeneric")
    })
    #> [1] "myGeneric"

    S4 中的 standardGeneric() 相当于 UseMethod()


    测试的答案

    1. 要确定一个对象属于哪种面向对象系统,你可以用排除法,如果 !is.object(x) 返回 TRUE,那么它是一个基本对象。如果 !isS4(x) 返回 TRUE,那么它是一个 S3 。如果 !is(x, "refClass") 返回 TRUE, 那么它是一个 S4 ,否则它是 RC 。
    2. typeof() 来确定基本类型的对象。
    3. 泛型函数调用特殊方法的时候主要是通过它的参数输入来确定的,在 S3 和 S4 系统,方法属于泛型函数,不像其他编程语言那样属于类。
    4. S4 比 S3 更加正式,并且支持多重继承和多重调度,RC 对象的语义和方法是属于类的,而不属于函数。
    展开全文
  • 2 JVM中对象的生命周期  在JVM运行空间中,对象的整个生命周期大致可以分为7个阶段:创建阶段(Creation)、应用阶段(Using)、不可视阶段 (Invisible)、不可到达阶段(Unreachable)、可收集阶段(Collected...
  • 在OC这种面向对象语言里,内存管理是个重要概念。要像用一门语言写出内存使用效率较高且又没有bug的代码,就得掌握内存管理模型的种种细节。一旦理解这些规则,你就会发现,其实OC的内存管理没有那么复杂,再进入ARC...
  • 在python中,变量不直接存储值,而是存储值的引用,也就是值在内存中的地址。(T) 35.在random模块的函数randint(1,100)获取随机数时,有可能会得到100。(T) 36.在python中可以使用for作为变量名。(F) 37.Python...
  • 项目管理学习总结

    万次阅读 2016-06-24 09:45:22
    5、面向对象设计(编码过程) 6、开发管理 7、成品交付风险评估软件项目风险是指在整个项目周期中所涉及的成本预算、开发进度、技术难度、经济可行性、安全管理等各方面的问题,以及由这些问题而对项目所产生的...
  • 面向对象编程思想

    万次阅读 2014-06-24 21:18:46
    它是从现实世界中客观存在的事物(即对象)出发来构造软件系统,并在系统构造中尽可能运用人类的自然思维方式,强调直接以问题域(现实世界)中的事物为中心来思考问题,认识问题,并根据这些事物的本质特点,把它们...
  • 类和面向对象习题

    万次阅读 2017-04-13 21:49:31
    3.4 精选习题 ...1.下列属于面向对象编程的特性的是( )。 A.封装 B.继承 C.抽象 D.多态 2.(*)下述概念中不属于面向对象的是( )。 A.对象、消息 B.继承、多态 C.类、封装 
  • Java实现面向对象编程

    万次阅读 2018-07-17 16:18:06
    1.1用面向对象设计电子宠物系统... 14 1.1.1为什么使用面向对象... 14 1.1.2使用面向对象进行设计... 15 1.2通过创建对象实现领养宠物功能... 17 1.2.1创建类的对象... 17 1.2.2构造方法及其重载... 23 1.2.3...
  • 进程、线程及内核对象

    千次阅读 2014-08-07 15:02:47
    进程、线程及内核对象 ...由于内核对象的数据结构只能被内核访问,so应用程序是无法在内存中找到这些数据结构的并直接改变其内容的。Windows提出这个限制为了确保内核对象结构保持状态的一致,也是为了保证M
  • 集群的控制节点,负责整个集群的管理和控制,kubernetes的所有的命令基本都是发给Master,由它来负责具体的执行过程。 1.1. Master的组件 kube-apiserver:资源增删改查的入口kube-controller-manager:资源对象...
  • 面向功能的程序设计方法、结构化程序设计方法、面向数据流的处理方式与结构化分析(Structrued Analysis,即SA)、结构化编程... 面向对象程序分析(OOA)、面向对象程序设计(OOD)、面向对象程序开发(OOP)
  • 俗称二八定律,又名80/20定律、帕累托法则(定律)也叫巴莱特定律、最省力的法则、不平衡原则等,被广泛应用于社会学及企业管理学等。 是19世纪末20世纪初意大利经济学家巴莱多发现的。他认为,在任何一组东西中,...
  • 管理

    万次阅读 2015-06-14 06:43:43
    管理思想的发展科学管理理论泰罗提出的管理制度:1. 对工人提出科学的操作方法,以便合理利用工时,提高效率。2. 在工资制度上实行差别计件制3. 对工人进行科学的选择、培训和提高4指定科学的工艺规程,并用文件形式...
  • Wscript对象详解

    千次阅读 2016-03-23 02:47:49
    15.6 Windows脚本宿主的内建对象 每个编程环境都提供了自己的对象模型,开发人员 可以使用这些对象模型实现各种解决方案,WSH也不例外。WSH包含了一组核心对象,分别包含了属性和方法,可以用于访问网络中的其他...
  • 面向对象软件工程知识点

    千次阅读 2016-07-11 11:46:28
    面向对象软件工程知识点  1.封装是指把对象的(A)结合在一起,组成一个独立的对象。 A.属性和操作 B.信息流 C.消息和事件 D.数据的集合 2.状态图和活动图建立了UML面向对象开发过程中的对象动态(B)...
  • 不论是在技术层面还是在产品层面,大数据平台环境下的权限管理工作都是一个让人伤脑筋的烫手山芋,它不仅仅是一个技术问题,还是一个业务问题,甚至还可能是一个人际沟通和权衡利益得失的哲学问题。。。所以,以下...
  • weblogic管理4——命令行管理工具WLST

    千次阅读 2014-06-24 22:31:42
    WebLogic 脚本工具 (WLST) 是一个命令行脚本界面,系统管理员和操作员可以使用它来监视和管理 WebLogic Server 实例和域。WLST 脚本环境基于 Java 脚本解释器 Jython。除 WebLogic 脚本功能外,还可以使用解释语言...
  • C和C++安全编码笔记:动态内存管理

    千次阅读 多人点赞 2020-05-04 18:23:21
    4.1 C内存管理: C标准内存管理函数: ...(2).aligned_alloc(size_t alignment, size_t size):为一个对象分配size个字节的空间,此对象的对齐方式是alignment指定的。alignment的值必须是实现支持...
  • 四、数据结构:对象和数组 原文:Data Structures: Objects and Arrays 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 部分参考了《JavaScript 编程精解(第 2 版)》 On two occasions I...
  • 面向对象分析与设计(第3版)

    千次阅读 2012-08-22 15:59:34
    面向对象分析与设计(第3版)权威精选植根于开发实践的最佳读物 (美) 布奇(Booch,G.) 等著 王海鹏,潘加宇译 ISBN978-7-121-17389-9 2012年7月出版 定价:99.00元 16开 608页 宣传语:一项技术产品只有在获得...
  • 面向对象复习总结

    千次阅读 2016-12-07 21:11:27
    强调直接以问题域(现实世界)中的事物为中心来思考问题、认识问题,并根据这些事物的本质特征,把它们抽象地表示为系统中的对象,作为系统的基本构成单位。这可以使系统直接映射问题域,保持问题域中事物及其相互...
  • oVirt管理手册(一) 概述

    千次阅读 2016-06-12 15:56:44
    概述oVirt架构一个oVirt环境由下列部分组成: 基于KVM(the Kernel-based Virtual Machine)的虚拟主机(Virtual machine hosts); 用于运行虚拟主机的代理和工具(Agents and tools),包括:VDSM, QEMU, 和 libvirt。...
  • 1、JScript的BugIE的ECMAScript实现JScript严重混淆了命名函数表达式,搞得现很多人都出来反对命名函数表达式,而且即便是现在还一直在用的一版(IE8中使用的5.8版)仍然存在下列问题。下面我们就来看看IE在实现中...
  • Recordset记录集对象的属性

    千次阅读 2012-01-28 00:38:54
    因为删除、更新、添加操作不需要返回记录集,因此可以直接使用连接对象或是命令对象的Exexut方法,但是利用记录集对象有时会更简单,此外,通过记录集对象能够实现比较复杂的数据库管理任务,比如要采用分页显示记录...
  • Windows核心编程:内核对象

    千次阅读 2009-09-19 20:36:00
    作者:shenzi链接:http://hi.csdn.net/shenziWindows核心编程:内核对象 1.对象和句柄 对象是静态定义的对象类型的单个运行时实例。... 对象为完成下列四种重要的操作系统任务提供了方便的方法:为系统资源

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 89,912
精华内容 35,964
关键字:

下列属于管理直接对象的是