-
2020-08-17 21:40:06
朋友介绍对象加微信后,对方不搭理你的真实原因都在这里面。
现在生活中很多人因为单身而通过相亲来解决个人问题,但是朋友介绍对象加微信后,却不见对方搭理自己,对比感到很是困惑。有朋友和我聊过,说:”相亲对象加了我的微信后,从不主动联系我,甚至我去找她聊天,她也是不冷不热的回一两句,之后就慢慢地不理我了,我也不知道怎么处理这种情况……”
其实,加了相亲对象微信后就没有下文的情况是常有的,在这里,我们为大家分析了经朋友介绍对象后不回微信的三种可能性。
朋友介绍对象加微信后第一种情况:对象并非自己情愿相亲,加微信也是被迫所为。
平时很多人对相亲都不是自己的意愿,觉得自己年龄还没到非要相亲不可的时候,或者目前还没有谈恋爱的打算,想要享受一个人的自由生活。但是由于父母的安排,不断向自己介绍对象,以多交朋友为由,要自己加对方微信,后来,耐不住父母的念叨而加了相亲对象的微信。
但事实上,加微信本就是被逼行为,所以有了微信以后也不可能去找对方聊天,一般采取爱答不理的态度,久而久之,双方也就不再联系了。
朋友介绍对象加微信后第二种情况:不喜欢以相亲的形式来找对象,所以不搭理对方。
虽说现如今社会上的单身男女众多,有些甚至年龄不小了还是孤身一人,找对象这种想法当然也是有的,但是她们不喜欢以相亲的形式来结束单身生活,甚至是排斥这种方式。
在她们看来,通过相亲认识的对象,都是到了年龄而不得不来解决个人问题的人,这样即使相互看对了眼,之后的相处也缺钱激情。觉得这种被安排的相遇的不对的。
有这种想法的她们,理所当然的对相亲对象不上心,甚至敷衍。从不会主动去找对方聊天,更不会对对方有好感,所以当相亲对象来找自己聊天时,也只是三言两语就打发了,只有做到基本的尊重,面子上过的去就可以了,那么识趣的自然也就不会再来找她了。
朋友介绍对象加微信后第三种情况:通过了解后觉得不适合自己,所以就不理你了。
有些人在找对象时,也会认真的对待,会去了解相亲对象的信息,也想真正的找个人相处。她们不排斥相亲,会去加对方的微信,但是,当通过了解发现自己并不喜欢对方,或者觉得两人不合适,认为再接触下去也是浪费时间,就果断放弃,以后就不想再来往了。
通过相亲来找对象,本来就是看缘分的事情,双方加了微信之后,无论对方对你是热情还是冷淡,都应该保持一颗随遇而安的心。倘若相亲对象对你爱答不理,发消息我不回,她可能是觉得你们不适合,没有再继续发展下去的可能,所以就冷淡处理了。
另一半是会陪我们走过平生的人,我们应该保持真心,相信总会一个人在等着你的!
更多相关内容 -
别再问我 new 字符串创建了几个对象了!我来证明给你看!
2021-01-20 02:05:20有人说创建了 1 个对象,也有人说创建了 2 个对象,还有人说可能创建了 1 个或 2 个对象,但谁都没有拿出干掉对方的证据,这就让我们这帮吃瓜群众们陷入了两难之中,不知道到底该信谁得。 但是今天就斗胆和大家聊聊... -
JavaScript入门教程(8) Location地址对象
2021-01-19 18:19:38先前写了一片用[removed].href实现刷新另个框架页面 ,特此我看了一下locaiton的详细用法,对此有点改进,具体如下: 注意:属于不同协议或不同主机的两个地址之间不能互相引用对方的 location 对象,这是出于安全性... -
(软件工程复习核心重点)第八章面向对象方法学-第三节:面向对象建模之对象模型
2022-01-19 20:31:09文章目录一:概念(1)定义(2)工具二:类图的基本符号(1)定义类A:表示B:命名规则(2)定义属性(3)定义服务三:表示关系的符号(1)关联A:定义B:...对象模型表示静态的、结构化的系统的数据性质。它是对模拟客文章目录
一:概念
(1)定义
对象模型表示静态的、结构化的系统的数据性质。它是对模拟客观世界实体的对象以及对象彼此间的关系的映射,描述了系统的静态结构。对象模型为建立动态模型和功能模型,提供了实质性的框架。
(2)工具
使用UML(统一建模语言)提供的类图来建立对象模型。在UML中,类的实际含义是一个类及属于该类的对象
具体来说,UML提供了以下13种图
- 用例图:从用户角度描述系统功能。
- 类图:描述系统中类的静态结构。
- 对象图:系统中的多个对象在某一时刻的状态。
- 状态图:是描述状态到状态控制流,常用于动态特性建模
- 活动图:描述了业务实现用例的工作流程
- 顺序图:对象之间的动态合作关系,强调对象发送消息的顺序,同时显示对象之间的交互
- 协作图:描述对象之间的协助关系
- 构件图:一种特殊的UML图来描述系统的静态实现视图
- 部署图:定义系统中软硬件的物理体系结构
- 包图:对构成系统的模型元素进行分组整理的图
- 组合结构图:表示类或者构建内部结构的图
- 交互概览图:用活动图来表示多个交互之间的控制关系的图
二:类图的基本符号
(1)定义类
A:表示
UML中类的图形符号为长方形,用两条横线把长方形分上、中、下3个区域,3个区域分别放类的名字、属性和服务
B:命名规则
类名应该是富于描述的、简洁的而且无二义性的
- 使用标准术语,不要随意创造名字
- 使用具有确切含义的名词,不要使用空洞或含义模糊的词作名字
- 必要时可用名词短语作名字,有时也可以加入形容词
(2)定义属性
具体格式为
- 可见性:有公有的(+)、私有的(-)和保护的(#)
- 类型名:表示该属性的数据类型
- 赋值:在创建类的实例时应给其他属性赋值,如果给某个属性定义了初值,则该初值可作为创建实例时这个属性的默认值
- 性质串:明确地列出该属性所有可能取值,用逗号隔开
(3)定义服务
具体格式为
- 可见性:有公有的(+)、私有的(-)和保护的(#)
- 参数表:用逗号隔开不同参数,每个参数语法为 “
参数名:类型名=默认值
”
三:表示关系的符号
类与类之间通常具有以下四种关系
(1)关联
A:定义
关联表示两个类的对象之间存在某种语义上的联系
B:关联的角色
在任何关联中都会涉及参与此关联的对象所扮演的角色,在某些情况下显式标明角色名有助于别人理解类
- 如果没有显式标出角色名,则意味着用类名作为角色名
C:普通关联
①:定义
普通关联是最常见的关联关系,只要在类与类之间存在连接关系就可以用普通关联表示
②:表示
- 第一,普通关联的图示符号是连接两个类之间的直线,如下图
- 第二,关联是双向的,可为关联起一个名字。在名字前面(或后面)加一个表示关联方向的黑三角
- 第三,在表示关联的直线两端可以写上重数,它表示该类有多少个对象与对方的一个对象连接。未明确标出关联的重数,则默认重数是1
D:限定关联
①:定义
限定关联通常用在一对多或多对多的关联关系中,可以把模型中的重数从一对多变成一对一, 或从多对多简化成多对一
②:表示
在类图中把限定词放在关联关系末端的一个小方框内。
- 利用限定词“文件名”表示了目录与文件之间的关系,利用限定词把一对多关系简化成了一对一关系
③:意义
限定提高了语义精确性,增强了查询能力
E:关联类
①:定义
为了说明关联的性质,可能需要一些附加信息。关联类可以用来记录相关信息
②:表示
关联类通过一条虚线与关联连接
- 关联中的每个连接与关联类的一个对象相联系
(2)聚集(它是关联的特例)
聚集(聚合)是关联的特例。表示类与类之间的关系是整体与部分的关系。在陈述需求时使用的**“包含”、“组成”、“分为…部分”**等字句,往往意味着存在聚集关系。除了一般聚集之外,还有两种特殊的聚集关系,分别是共享聚集和组合聚集
A:共享聚集
如果在聚集关系中处于部分方的对象可同时参与多个处于整体方对象的构成,则该聚集称为共享聚集
- 一般聚集和共享聚集的图示符号,都是在表示关联关系的直线末端紧挨着整体类的地方画一个空心菱形
B:组合聚集
如果部分类完全隶属于整体类,部分与整体共存,整体不存在了部分也会随之消失, 则该聚集称为组合聚集(组成)
- 组成关系用实心菱形示例
(3)泛化(本质就是继承)
UML中的泛化关系就是继承关系,它是通用元素和具体元素之间的一种分类关系。具体元素完全拥有通用元素的信息,并且还可以附加一些其他信息。在UML中,用一端为空心三角形的连线表示泛化关系,三角形的顶角紧挨着通用元素
A:普通泛化
①:抽象类
没有具体对象的类称为抽象类。抽象类通常都有抽象操作,来指定该类的所有子类应具有哪些行为
- 表示抽象类是在类名下方附加一个标记值{abstract},表示抽象操作是在操作标记后面跟随一个性质串{abstract}
②:具体类
具体类有自己的对象,并且该类的操作都有具体的实现方法
B:受限泛化
①:定义
可以给泛化关系附加约束条件,以进一步说明该泛化关系的使用方法或扩充方法,这样的泛化关系称为受限泛化
②:约束
预定义的约束有4种(都是语义约束)
-
多重:一个子类可以同时多次继承同一个上层基类
-
不相交:一个子类不能多次继承同一个基类。-般的继承都是不相交继承
-
完全:父类的所有子类都已在类图中穷举出来了
-
不完全:父类的子类并没有都穷举出来,随着对问题理解的深入,可不断补充和维护。是默认的继承关系
(4)依赖和细化
A:依赖关系
依赖关系描述两个模型元素之间的语义连接关系:其中一个模型元素是独立的,另一个模型元素不是独立的,它依赖于独立的模型元素,如果独立的模型元素改变了,将影响依赖于它的模型元素
- 在UML类图中用带箭头的虚线连接有依赖关系的两个类,箭头指向独立的类。在虚线上可以带一个版类标签,具体说明依赖的种类
B:细化关系
对同一个事物在不同抽象层次上描述时,这些描述之间具有细化关系
- 细化的图示符号为由元素B指向元素A的一端为空心三角形的虚线
-
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);的执行过程?
- 因为new用到了Person.class,所以会先找到Person.class文件并加载到内存中
- 执行该类中的static代码块,如果有的话,给Person.class类进行初始化
- 在堆内存中开辟空间,分配内存地址
- 在堆内存中建立对象的特有属性,并进行默认初始化(对象为null,整数为0)
- 对属性进行显示初始化(类中声明成员变量初始化值)
- 对对象进行构造代码块初始化
- 对对象进行对应的构造函数初始化
- 将内存地址值赋给栈内存中的p引用
- 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多个实例)
想要保证对象唯一:
- 为了避免其他程序过多建立该类对象,先要控制禁止其他程序建立该类对象
- 为了让其他程序可以访问到该类对象,只好在本类中,自定义一个对象
- 为了方便其他程序对自定义对象的访问,可以对外提供一些访问方式
代码实现:
- 将构造函数私有化(以控制其他程序建立该类对象)
- 在类中创建一个本类对象
- 提供一个方法可以获取到该对象(方便其他程序对自定义对象的访问)
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修饰的方法,不可以被复写。
- 被final修饰的变量是一个常量,只能赋值一次,既可以修饰成员变量,也可以修饰局部变量。如下图:
只要在内存中,x和y的值都是固定的,不可以更改。
当在描述事物时,一些数据的出现值是固定的,那么这时为了增强阅读性,都给这些值起个名字,方便阅读,而这个值不需要改变,所以加上final修饰。作为常量:常量的书写规范所有的字母都大写,如果有多个单词组成,单词间通过下划线连接。
2.内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量。
8.7抽象类
如上图,发现两个方法都一致,所以需要抽取相同功能。但发现虽然两者都在学习,但是学习内容却不一样。解决:可以进行向上抽取,只抽取功能定义,不抽取功能主体(即抽象类)。对于这种方法,必须使用abstract进行修饰。抽象方法必须定义在抽象类中。如下图:
抽象:即看不懂的事物。
抽象类的特点:
- 抽象方法一定在抽象类中
- 抽象方法和抽象类都必须被abstract关键字修饰
- 抽象类不可以用new创建对象,因为调用抽象方法没意义(里面的抽象方法连方法体都没有)
- 抽象类中的抽象方法要想被使用,必须由子类复写其所有的抽象方法后,建立子类对象调用,如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。
抽象类和一般类没有太大的不同。只是要注意该如何描述事物就如何描述事物,只不过该事物中出现了一些看不懂的东西。这些不确定的部分也是该事物的功能,需要明确出来,但是无法定义主体。而通过抽象方法来表示。
- 抽象类比一般类多了抽象方法。就是在类中可以定义抽象方法,也可以不定义抽象方法。
- 抽象类不可以实例化。
- 抽象类也可以不定义抽象方法,这样只是为了不让该类创建对象。
抽象类练习:
假如我们在开发一个系统时需要对员工进行建模,员工包含3个属性:姓名、工号、工资。经理也是员工,除了含有员工的属性外,另外还有一个奖金属性,请使用继承的思想设计出员工类和经理类。要求类中提供必要的方法进行属性访问。
分析:
员工类:name、id、pay
经理类:继承了员工并有自己特有的奖金属性bonus
会有一个抽象员工类,员工属于这一类,经理也属于这一类
8.8模板方法设计模式:
需求:获取一段程序运行的时间
原理:获取程序开始和结束的时间并相减即可。获取本机系统时间:System.currentTimeMillis()获取当前时间的毫秒值,返回当前时间减去1970年1月1日0点0分0秒之间的时间差。
问题:想获取另一段代码的运行时间。解决:直接子类复写掉getTime方法
但是发现代码重复性很高。
解决:可以将需要运行代码提取出来形成另一个方法,子类复写时,只需要复写提取出来的代码即可。如下图:
但是却发现提取出来的需要运行的代码是不确定的,解决:只要将方法写成抽象方法并由子类继承后进行具体实现即可,且getTime方法不可复写所以使用final进行修饰。如下图:
当代码完成优化后,就可以解决这类问题,这种方式叫做模板方法设计模式。
什么是模板方法?
在定义功能时,功能的一部分是确定的,但是一部分是不确定的,而确定的部分在使用不确定的部分时就将不确定的部分暴露出去,由该类的子类去完成。这样可以提高扩展性和复用性。注意:暴露出去的方法不一定抽象,有时会有默认的实现方式。
8.9接口
接口:初期理解,可以认为是一个特殊的抽象类,当抽象类中的方法都是抽象的,那么该类可以通过接口的形式表示。
接口定义时,格式特点:
- 接口中常见定义:常量,抽象方法
- 接口中的成员都有固定修饰符(常量:public static final;方法:public abstract)
总结:接口中的成员(包括成员变量和方法)都是public的
如下,即使少写了成员上的,interface也会进行默认的补全。
但是,一般建议写全:
接口也是不可能创建对象的,因为接口中的方法都是抽象的,而抽象方法需要被子类实现。即子类对接口中的抽象方法全都覆盖后,子类才可以实例化,若子类只是实现了父类,却没有覆盖父类的方法,那么子类仍是一个抽象类。如下图:
如上图中,实现类Test实现了Inter接口后,覆盖了父类接口中的show()方法。因此Test类才可以被实例化。
接口可以被类多实现,也是对多继承不支持的转换形式,java不支持多继承,但是支持多实现。如下图:
java不支持多继承是因为父类当中的方法有重复,多继承会导致子类调用时出现冲突,为什么接口不会出现这种问题?
因为多实现接口中,接口没有方法主体,实现类在实现时,可以重写方法体,即使出现接口中出现多个相同方法,实现类中一个方法就可以全部重写多个接口中的那些相同方法。如下图所示:
注意:多个接口的相同方法的返回值必须一致。
类和接口相互之间的关系:
- 类与类之间:继承
- 类与接口之间:实现
- 接口与接口之间关系:继承关系
接口与接口之间可以多继承(因为接口之间没有方法体,方法之间不会冲突)。所以java中也存在多继承,只是多继承只存在在接口与接口之间,而不存在与类与类或者类与接口之间。
接口的特点:
- 接口是对外暴露的规则
- 接口是程序的功能扩展
- 接口可以用来多实现(降低了耦合性)
- 类与接口之间是实现关系,而且类可以继承一个类的同时实现多个接口
- 接口与接口之间可以有继承关系
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多态的扩展示例
需求:对数据库的操作
数据是:用户信息
- 连接数据库 JDBC Hibernate
- 操作数据库 CRUD
- 关闭数据库连接
问题:某天发现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(),如下)。
方法中的局部变量,内部类在局部,那么内部类就能访问局部变量?
修改后:
有上例可知,
- 内部类定义在局部时,不能被成员修饰符修饰;
- 可以直接访问外部类中的成员,因为还持有外部类中的引用,但是不可以访问它所在的局部变量,只能访问被final修饰的局部变量
如上图,方法中的参数也必须是final。
如何只有一个对象实现多次调用method(),如下图:
调用method()时a=7进栈,执行完后,会释放掉,再调用method()a =8,所以不会冲突。
匿名内部类:
- 匿名内部类其实就是内部类的简写格式
- 定义匿名内部类的前提:内部类必须继承一个类,或者实现接口
将下列例子简化成匿名内部类:
简化后:匿名了,没有Inner类了,怎么new对象?如下:
可以使用 new AbsDemo(){
void show();
}
AbsDemo本身是抽象类,不能new,但是可以像下面这样写,在复写掉里面的方法。红框的内容是一个对象,是AbsDemo的子类对象(只有子类才可以复写父类的抽象方法)。
匿名内部类调用本匿名内部类中的方法:
- 总结:匿名内部类的格式,new 父类或者接口(构造函数,可以往其传参){定义子类的内容}
- 其实匿名内部类就是一个匿名子类对象,可以理解为带内容的对象
匿名内部类中也可以有子类特有的方法,不一定是覆盖父类的方法,但是,不可以同时调用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的区别:
- throws使用在函数上,throw使用在函数内;
- 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。
所以自定义异常示例可改为:
输出结果:
对于异常分两种:
- 编译时被检测的异常(javac编译时,发现方法中抛出了非RuntimeException及其子类,而方法上没有标识throws,就会认为有安全隐患。此异常时可处理的,要标识出去,让调用者进行对应的处理;如果函数上标识了throws,函数的调用者也必须进行处理,try或抛);
- 编译时不被检测的异常(运行时异常: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{
}
注意:
- finally中定义的通常是关闭资源代码,因为资源必须要释放。
- finally有一种情况读不到:即系统退出(System.exit(0)虚拟机结束时,finally不会再执行)
自定义异常:
定义类继承Exception或者RuntimeException
- 为了让该自定义类具备可抛性
- 让该类具备操作异常的共性方法
当要定义自定义异常的信息时,可以使用父类已经定义好的功能。异常信息传递给父类的构造函数
class MyException extends Exception{
MyException(String message){
super(message);
}
}
自定义异常:
按照java的面向对象思想,将程序中出现的特有问题进行封装
自定义异常好处:
- 将问题进行封装
- 将正常流程代码和问题处理代码相分离,方便阅读
异常的处理原则:
- 处理方式有两种:try 或者throws
- 调用到抛出异常的功能时,抛出几个,就处理几个(不多抛不多处理)。一个try对应多个catch的情况。
- 多个catch,父类的catch放到最下面
- catch内需要定义针对性的处理方式,不要简单的定义printStackTrace输出语句,也不要不写。当捕获到的异常,本功能处理不了时,可以继续在catch中抛出即:
try{
throw new AException();
}catch(AException e){
throw e;
}
如果该异常处理不了,但并不属于该功能出现的异常,可以将异常妆花后,再抛出和该功能相关的异常。
或者异常可以处理,当需要将异常产生的和本功能相关的问题提供出去,让调用者知道,并处理。也可以将捕获异常处理后,转换为新的异常
try{
throw new AException();
}catch(AException e){
//捕获到AException但是AException处理不了,可以转换为新的异常处理类
throw new BException();
}
比如,汇款的例子
异常的注意事项:
在子父类覆盖时:
- 子类抛出的异常必须是父类的异常的子类或者子集;
- 如果父类或者接口没有异常抛出时,子类覆盖出现异常,只能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:
- 修饰类,变量(成员变量、静态变量、局部变量),函数
- 修饰的类不可以被继承
- 修饰的方法不可以被覆盖
- 修饰的变量是一个常量,只能赋值一次
- 内部类只能访问局部的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
- 对类文件进行分类管理
- 给类提供多层命名空间
- 写在程序文件的第一行
- 类名的全称是 包名.类名
- 包也是一种封装形式
命令中加参数用于创建包的目录
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的压缩包
- 方便项目的携带
- 方便与使用,只要在classpath设置jar路径即可
- 数据库驱动,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文件中
-
『Java面经』Java中对象何时需要回收?常见的 GC 回收算法有哪些?
2021-10-15 19:21:03文章目录1、什么是垃圾回收2、如何...垃圾回收(Garbage Collection),释放垃圾占用的空间,防止内存泄露,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。 2、如何定义垃圾 既然要做垃圾回收,那么文章目录
今天我们就从头到尾完整地聊一聊 Java 的垃圾回收。1、什么是垃圾回收
垃圾回收(Garbage Collection),释放垃圾占用的空间,防止内存泄露,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。
2、如何定义垃圾
既然要做垃圾回收,那么就得知道垃圾的定义是什么,哪些内存需要回收。
2.1 引用计数算法
通过在对象头部分配一个空间,来保存该对象被引用的次数。对象被其他对象引用,则它的计数加1,删除该对象的引用,计数减1,当该对象计数为0时,会被回收。
String m = new String("方糖算法");
创建一个字符串m,这时候
"方糖算法"
字符串被m引用了,"方糖算法"
字符串计数加1。
此时将m设置为null
,则"方糖算法"
的引用次数就变为0,意味着要被回收了。m = null;
引用计数算法将垃圾回收分摊到整个程序运行中
,而不是在垃圾收集时,不属于严格意义上的"Stop-The-World"的垃圾收集机制。JVM放弃了引用计数算法,这是为什么?我们看下面的例子。
public class ReferenceCountingGC { public Object instance; public ReferenceCountingGC(String name){} } public static void testGC(){ ReferenceCountingGC a = new ReferenceCountingGC("objA"); ReferenceCountingGC b = new ReferenceCountingGC("objB"); a.instance = b; b.instance = a; a = null; b = null; }
- 定义2个对象a,b
- 相互引用
- 声明引用置空
从图中,ab置空后,这两个对象已经不能被访问了,但是他们相互引用对方,导致他们两个的计数永远不为0,永远不会被回收。
2.2 可达性分析算法
通过
引用链(GC Root)
作为起点,向下搜索,搜索过的路径被称为(Reference Chain)。当一个对象不能被引用链搜索到,说明该对象不可用,被回收。
通过可达性算法,可以解决引用计数算法无法解决的循环依赖
问题,只要不能被GC Root搜索到,就会被回收。那么哪些属于GC Root?往下看鸭!
2.3 Java内存区域
在Java中,GC Root 对象包括四种:
- 虚拟机栈中的引用对象
- 方法区静态属性引用的对象
- 方法区常量引用的对象
- 本地方法栈JNI引用的对象
虚拟机栈中的引用对象
此时的 s,即为GC Root。 当s置空时,localParameter
对象也断掉与GC Root 的引用链,将被回收。public class StackLocalParameter { public StackLocalParameter(String name){} } public static void testGC(){ StackLocalParameter s = new StackLocalParameter("localParameter"); s = null; }
方法区静态属性引用的对象
s 为 GC Root,s 置空后,s 指向的properties
对象被回收。
m 为类静态属性,也属于GC Root,parameter
对象依旧与 GC Root 连接着,所以不会被回收。public class MethodAreaStaicProperties { public static MethodAreaStaicProperties m; public MethodAreaStaicProperties(String name){} } public static void testGC(){ MethodAreaStaicProperties s = new MethodAreaStaicProperties("properties"); s.m = new MethodAreaStaicProperties("parameter"); s = null; }
方法区常量引用的对象
m 为常量引用,是GC Root,s 置空后,final
对象也会被回收。public class MethodAreaStaicProperties { public static final MethodAreaStaicProperties m = MethodAreaStaicProperties("final"); public MethodAreaStaicProperties(String name){} } public static void testGC(){ MethodAreaStaicProperties s = new MethodAreaStaicProperties("staticProperties"); s = null; }
本地方法栈中引用的对象
任何 Native 接口都会使用某种本地方法栈,实现的本地方法是使用 C 连接模型的话,那么它的本地方法栈就是 C 栈。
当线程调用 Java 方法时,虚拟机会创建一个新的栈帧并压入 Java 栈,然而当它调用的是本地方法时,虚拟机会保持 Java 栈不变,虚拟机只是简单地动态连接并直接调用指定的本地方法。
3、怎么回收垃圾
在确定哪些垃圾可以回收后,我们来讨论一下如何高效的回收垃圾呢?
Java虚拟机没有规定实现垃圾收集器,所以各个厂商的虚拟机可以采用不同的方法实现垃圾收集器。
3.1 标记清除算法
标记清除算法(Mark - Sweep),分为 2 部分,①先把内存中可回收的标记出来
②再把这些清理掉
,清理完的区域变成未使用,等待下次使用。但是存在一个很大的问题,那就是
内存碎片
。假设图中 中等方块是 2M,小的是 1M,大的是 4M。等回收完,内存就会被切成很多段。而开辟内存需要的是连续区域,需要一个 2M 的内存,用 2个 1M 是没法用的。这样就导致,其实内存还挺多,但是分散了无法使用。
3.2 复制算法
复制算法(Copying),是在MS算法上演变而来,解决了内存碎片问题
。它将内存按容量平分成两块,每次使用其中的一块。当一块用完了,将其存活的对象复制到另一块上,再把这一块内存清理掉。保证了内存连续可用,不会产生内存碎片,逻辑清晰,运行高效。但是明显暴露了一个问题,合着我 300 平的别墅,只能当 150 平的小三房来使?代价太高了
3.3 标记整理算法
标记整理算法(Mark - Compact)标记过程与MS算法一样,但后续不是直接回收对象,而是让存活的对象向一端移动,再清理端边界以外的内存
。不仅解决了
内存碎片
,也规避了只能使用一半的内存
。但是问题又来了,它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比MS算法差很多。3.4 分代收集算法
分代收集算法(Generational Collection),融合上面 3 种思想。 根据对象存活周期的不同划分为几块,一般分为
新生代
和老年代
,根据年代的特点采用适当的收集算法。新生代:每次回收发现有大量对象死去,少量存活,则使用复制算法,付出少量存活对象复制的成本完成回收。
老年代:存活率高,没有额外空间分配,则使用MS,MC算法来回收。问题又来了,内存区域被分为哪几块,每一块又用什么算法合适?
4、内存模型与回收策略
Java 堆(Heap)是 JVM 管理的内存最大的一块,堆又是垃圾收集器管理的主要区域,我们来分析一下堆的结构。堆主要分为 2 个区域,年轻代和老年代。年轻代分为
Eden
和Survivor
,其中 Survivor 又分为From
和To
,老年代分为Old
。Eden
研究表名,98%对象是朝生夕死,所以大部分情况,对象会在新生代的 Eden 区分配,当 Eden 内存不足时,虚拟机发起一次Minor GC
, Minor GC 比 Major GC 更频繁,回收更快。通过 Minor GC 后,Eden会被清空,绝大部分对象被回收,而那些
存活对象会进入 Survivor 的 From 区
(From 区不够,则进入 Old 区)。Survivor
Survivor 相当于 Eden 区和 Old 区的一个缓冲区
。通过 Minor GC 后,会将Eden 区和 From 区的存活对象放到 To 区
(To 区不够,则进入 Old 区)。为啥需要缓冲区?
不就是新生代到老年代吗,直接 Eden 到 Old 不就好了?非要这么复杂。
如果没有 Survivor 区,Eden 区每进行一次 Minor GC,存活的对象就会被送到 Old 区,老年代很快被填满。而且很多对象虽然一次 Minor GC 后没有死,可能一会后就死了,直接把它送入老年代,明显不合适。总结:Survivor 存在的意义就是
减少被送到老年代的对象
,减少 Major GC 的发送。Survivor 的预筛保证,只有经历16次
Minor GC 还能在新生代中存活的对象,才会被送到老年代。为啥需要两个缓冲区?
两个 Survivor 最大的好处是解决内存碎片化
。如果 Survivor 有1个区域,Minor GC 执行后,Eden 区被清除,存活对象放入 Survivor 区,而之前 Survivor 区的对象可能也有一部分要清除。此时只能使用
MS算法
,那就会产生内存碎片,尤其是在新生代这种经常死亡的区域,产生严重碎片化。如果 Survivor 有2个区域,每次 Minor GC,会将之前 Eden 区和 From 区中的存活对象复制到 To 区域。第二次 Minor GC 时,
From 与 To 职责切换
,这时候会将 Eden 区和 To 区中的存活对象再复制到 From 区域,以此反复。(有点复制算法的感觉)这种机制最大的好处就是,永远有一个 Survivor 区是空的,另一个 区是无碎片的。那为啥不分更多的 Survivor 区呢?分的越多,每个区就越小,两块是经过权衡的最佳方案。
Old
老年代占据着 2/3 的堆内存,只有 Major GC 才会清理,每次 GC 都会触发 “Stop-The-World”。内存越大 STW时间越长,所以内存不是越大越好。老年代对象存活时间较长,采用MC算法
。除了上面说的,
无法安置的对象会直接送入老年代
,以下情况也可以。- 大对象
大对象是需要大量连续空间
的对象,不管是不是"朝生夕死",都会直接进入老年代。避免在 Eden 和 Survivor 中来回复制。 - 长期存活对象
虚拟机给每个对象定义了对象年龄计数器
。对象在 Survivor 区每经历一次 Minor GC ,年龄加1,当年龄为15岁,直接进入老年代。这里的15可以设置。 - 动态对象年龄
虚拟机不关注年龄必须到15岁才可以进入老年代,如果Survivor 区相同年龄的所有对象大小超过 Survivor 空间一半
,则年龄大于相同年龄
的对象就进入老年代,无需成年。
5、微信关注『方糖算法』
各类面试资料、内推资源,关注微信公众号获取哦。
-
js 对象操作问题:把一个对象A赋值给另一个对象B (1)对象B 修改 不会影响 A对象(2)对象B修改影响 A对象
2020-05-29 10:12:21js 对象操作 对象原型操作 把一个对象A赋值给另一个对象B 并且对象B 修改 不会影响 A对象 我最近在做一个vue + element-UI + vue-resource + vuex项目的时候,遇到了一个对象的问题。 当我们在项目需要 复制一个对象... -
win10网络共享找不到对方电脑怎么办
2021-06-22 21:46:50我们为了提高办公效率,有时候需要将本机电脑文件共享,不过有的朋友遇到了win10网络共享找不到对方电脑的情况,那么win10网络共享找不到对方电脑怎么办呢?很多朋友不了解情况,所以下面给大家介绍win10网络共享找... -
C++中的private:类型相同的两个对象,是否可以访问对方的private成员?
2017-03-28 17:41:14下面的代码,为什么可以编译通过?class Point{ public: Point(Point & p); private: int x; } Point::Point(Point & p){ ...Point::Point(Point & p)函数体中的语句“x ...一、先看来自ISO/IEC 14882(C++ 98年标准),中 -
记一次"未将对象引用设置到对象的实例"问题的排查过程
2021-01-19 01:00:54这种情况有可能是你要调试的项目没有载入断点符号,载入符号的具体步骤如下: 1.打开调试里的模块窗口。 2.找到项目的dll,右键点击加载符号,就能命中断点了。 通过调试发布以后的项目,发现有一处参数传值不正确... -
同时介绍两个对象怎么办_相亲女从不主动联系 相亲对象不主动联系怎么办
2020-11-14 09:29:11随着大龄单身青年的人数越来越多,很多适龄青年被逼或自主走上相亲的...了解相亲对象的工作相亲的人有很多的共同点,喜欢拼事业,年龄大,社交圈又不广泛,但是事业往往很成功,特别是作为男士,所以在工作的时候,... -
什么是面向对象,如何用面向对象的思想写代码
2022-05-05 20:32:26面向对象到底是啥 -
同一个类不同对象,可以互相访问对方的私有成员而不通过get()或set()
2016-03-07 20:15:05今天看C++的书看到有一个类,类中定义了静态私有的本类对象,然后默认的构造函数是用此静态对象直接用 . 运算符访问自身成员来初始化的。成员的访问控制符是针对类而言的,而不是对象的,下面用C++验证 -
跟相亲对象微信聊什么?聊天绝学四步走一试便知有没有
2020-08-14 17:38:00聊天绝学四步走一试便知有没有。 很多男生在微信上和妹子聊天时,经常聊着聊着就冷场了,搞得彼此很尴尬,特别是对方是你相亲对象的时候,更是巨尬无比。那么从微信上该怎么和女生聊天,才能寻得女生的芳心,让妹子... -
面试官:垃圾回收中GC Root对象有哪几种?
2019-06-18 17:37:45介绍 写过C++程序的小伙伴都...引用计数法(Reference Counting):给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1,任何时刻计数器为0的对象就是不可能被再使用的. -
JVM如何判断对象能否被回收
2019-12-16 16:11:57•对象的创建 •引用计数算法 •可达性分析算法 •引用 •不可达必须“死”? •最后 •写在前面 说起Java和C++,很容易想到让人疯狂的指针,Java使用了内存动态分配和垃圾回收技术,让我们从C++的各种指针... -
Java虚拟机:如何判定哪些对象可回收?
2021-02-28 08:39:21在堆内存中存放着Java程序中几乎所有的对象实例,堆内存的容量是有限的,Java虚拟机会对堆内存进行管理,回收已经“死去”的对象(即不可能再被任何途径使用的对象),释放内存。垃圾收集器在对堆内存进行回收前,首先... -
面向对象程序设计
2020-12-09 15:37:28之前复习面向对象的时候整理的,丢出来一起分享一下。因为复习得很赶,只是大致的整理,且大部分图片来自老师的ppt,可能不是很准确。如果要详细了解其中的某个知识点请另外搜索。 但是老师不讲武德啊,明明提纲给了... -
Java程序设计(面向对象)- 基本概念
2019-05-25 20:10:06Java 是面向对象的编程语言,对象...对象有以下特点: 对象具有属性和行为。 对象具有变化的状态。 对象具有唯一性。 对象都是某个类别的实例。 一切皆为对象,真实世界中的所有事物都可以视为对象。 类 ... -
python使用any判断一个对象是否为空的方法
2021-02-03 17:02:53这两者是有区别的,下面举例具体说明: reaCode = Noneif ( reaCode == None ): print "is null"if ( reaCode is None ): print "is 你忍了好久不联系对方,然后发现人家一点都没在忍。 python 判断文件是否为空if ... -
设计模式-可复用面向对象软件的基础
2019-06-22 11:12:12一般而言,一个模式有四个基本要素: 模式名称 — 一个助记名,它用一两次来描述模式的问题、解决方案和效果。 问题 — 描述了应该在何时使用模式。解释了设计问题和问题存在的前因后果。 解决方案 — 描述了设计的... -
JVM笔记 垃圾对象判断
2020-07-18 20:55:13在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的. 引用计数算法(Reference Counting)虽然占用了一些额外的... -
Redis对象系统——五种对象、底层实现和常用命令
2018-04-09 15:16:39第八章 对象前面介绍的都是基本数据结构,Redis并没有直接使用这些基本数据结构,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型。... -
python:对象复制
2018-12-22 00:19:541、Python中一切皆对象 ⑴在Python中,无论是定义的数值、字符串、列表、元组、集合、字典还是函数、类等都看作是一种对象 ⑵列表、元组、集合、字典属于复合对象,它们可以包含数值、字符串,也可以互相嵌套 ⑶... -
C++类和对象数组
2018-05-14 23:47:15//有10个元素的学生类对象数组 2:对象数组的访问形式:数组名[下标].成员名; Eg: asa[j].print(); 3:对象数组的初始化:对象数组的初始化通常也是由构造函数来完成的。 Eg: #include”student.h” int main ... -
DDD领域驱动设计实战(四)-值对象
2020-10-03 03:43:58DDD中描述领域的特定方面,并且是一个没有标识符的对象。 也就说,值对象描述了领域中的一件东西,这个东西是不可变的,它将不同的相关属性组合成了一个概念整体。当度量和描述改变时,可以用另外一个值对象予以替换... -
java对象相等问题
2019-07-31 19:17:15也就是说Integer(100)这样的对象已经有了一个了,自动装箱之前就不去new了,直接使用缓存里的,所以是同一个对象,指向的是同一个引用地址。 以上代码等同于: Integer b = Integer.valueOf(100); Integer c =... -
【JVM原理】如何判定对象可以回收
2018-07-07 18:21:48GC即垃圾收集机制是指JVM用于释放那些不再使用的对象所占用的内存。Java语言并不要求JVM有GC, 也没有规定GC如何工作。不过常用的jvm都有GC,而且大多数GC都使用类似的算法管理内存和执行收集操作。 在充分理解了... -
Java对象的深克隆与浅克隆(对象复制)
2020-03-11 16:07:35假如说你想复制一个简单变量。很简单: int apples = 5; int pears = apples;...但是如果你复制的是一个对象,情况就有些复杂了。 假设说我是一个beginner,我会这样写: class Student { private ...