目录
📢:哈喽~☀️欢迎加入程序🐒大家庭,快点开始✒️自己的黑客帝国吧 ~🌊🌊
内容简述:List、队列、栈、多线程并发安全、缓冲队列、HTTP请求、Map映射、Content-Type、Content-Length、XML语法、XML解析、线程池、反射、日期处理。
一、List、队列、栈、Lambda表达式
List提供了可以获取子集的方法:
- List subList(int start,int end)
- 获取当前集合指定范围的子集,含头不含尾。
注意:subList获取的List与原List占有相同的存储空间,对子List的操作会影响原List。
(因为 Java 的变量是地址引用)
所有的集合都可以转换为数组
- Collection定义了集合转换为数组的方法:toArray
数组转换为集合
- Arrays.asList(T… var0)
- 需要注意,该方法仅能将数组转换为List集合;
- 需要注意,该方法转换的List集合为固定长度,不能 add 和 remove,否则会抛出UnsupportedOperationException 异常
当将一个数组转换为集合后,对该集合元素操作
- 就是对原数组对应元素的操作。
集合的排序
- 需要注意,仅能对List集合进行排序。
- 排序可以使用集合的工具类:java.util.Collections
- 其提供了一个静态方法sort可以对List集合进行自然排序(从小到大)
- Collections.sort() 方法在排序List集合时,要求该集合的元素必须要实现:Comparable接口,该接口规定了元素。
当一个类实现了Comparable接口之后,就要求必须
- 重写方法:CompareTo()
- 该方法的作用是用当前对象(this)与参数对象o
- 比较大小。
- 返回值是一个整数,返回值并不关注具体取值,而
- 关注的是取值范围:
- 当返回值>0:表示当前对象大于参数对象(this>o)
- 当返回值<0:表示当前对象小于参数对象(this<o)
- 当返回值=0:两个对象相等
举个栗子:
public int compareTo(Employee o) {
/*
* 按照年龄排序
*/
return o.getAge() - this.age();
}
如果想要自定义排序方式,比如想按照字数排序
- 需要写一个Comparator比较器用来定义如何比较
排序自定义类型元素时,推荐使用下面的
- 重载 sort() 方法。
- 该方法要求传入一个额外的比较器,使用匿名内部类即可。
- 这样一句代码就可以完成排序操作。而无需在类中实现接口并为其提供一个方法。
- 当我们使用某个功能时,该功能要求我们为其修改代码越多,侵入性越强。
- 实际开发中应尽量避免侵入性强的功能。
- java.util.Queue 队列接口,规定了队列相关方法。
Queue 队列
- 队列用于存放一组元素,但是存取元素只能从"两端"进行且遵循“先进先出”原则。
- FIFO——Fist In Fist Out
- java.util.Queue 队列接口,规定了队列相关方法
boolean offer(E e)
- 入队操作,向队列末尾追加给定元素
E poll()
- 出队操作,获取队首元素,获取后该元素即从队列中被移除。
因为队列可以用迭代器,所以遍历可以简化为如下代码:
for(String s:queue) {
System.out.println(s);
}
System.out.println(queue);
如果用队列的迭代器遍历的话,原队列的数据不会改变
java.util.Deque是Queue的子接口,
-
规定了双端队列的相关方法。
-
双端队列的特点是可以分别从队列首尾做进出队操作。
-
双端队列提供了更有方向性的进出队方法。
队列提供了引用队首元素方法peek
-
它与poll的区别,在于获取后该元素依然在队列中。
-
在双端队列中还支持peekFirst,peekLast
Stack 栈:
- 栈用来存一组元素,但是存取元素必须遵循先进后出
- FILO——First In Last Out
用途:用来进行操作的后退/撤销。
lambda表达式:
-
JDK8 推出的新特性,实现函数式编程。
-
lambda常用在快捷方便的创建匿名内部类上。
-
需要注意的是Lambda创建匿名内部类(要求该接口或者类只能有一个方法)
-
Lambda是编译器认可,而非虚拟机,编译器最终会将其改写为内部类形式。
lambda表达式语法
-
形式一:(parameter) -> expression // 只有一句话
-
形式二:(parameters) -> {statements;} // 多句话
例如:
- ()->5 不需要参数,返回值为5
- (x,y)->x-y 需要传入两个参数,返回值为x-y
- (int x,int y)->x-y 需要传入两个int参数,返回值为x-y
- (s)->System.out.println(s) 需要一个参数s,并且输出到控制台
二、多线程并发安全、双缓冲队列、HTTP(请求)、Map(查找表)
多线程并发安全问题
- 当多个线程并发访问同一资源时,由于线程切换时机不确定,
- 导致执行代码顺序的混乱,从而出现执行未按程序设计顺序运行,
- 导致出现各种错误,严重时可能导致系统瘫痪。
当一个方法使用synchronized修饰后
-
该方法成为同步方法,即:多个线程不能同一时间在该方法内执行。
只能一个线程一个线程的执行该方法。
-
在一个方法上使用synchronized,那么同步监视器对象就是该方法所属对象,
即:方法中的“this”.
补充:
- 有效缩小同步范围可以在保证并发安全的前提下提高并发执行效率。
- 同步块同步块可以更精确的控制需要同步的代码片段。
同步块:
-
synchronized(同步监视器对象){
需要同步执行的代码片段
}
-
同步监视器对象可以是java中任何类的实例,
-
只要保证多个线程看到的是“同一个”对象,
-
那么这些线程在执行同步块中的代码时就是同步执行的。
补充:静态方法使用synchronized修饰后,该方法一定具有同步效果
synchronized的互斥性
- synchronized修饰多段代码,并且这些同步块的同步监视器对象是同一个时,
- 那么这些代码间就是互斥的,多个线程不能同时在这些代码中运行。
注意:
1.synchronized关键字是不能继承的,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。
2.在静态方法上加锁,就相当于在类上加锁
注意:多线程操作同一个字符串的时候要用StringBuffer
StringBuilder不是线程安全的
-
当多个线程操作同一个字符串时应当使用StringBuffer,
-
对于集合而言,常用的实现类:ArrayList、LinkedList、HashSet它们都不是线程安全的。
-
Collections可以将现有的集合转换为线程安全的
/*
* 将给定List集合转换为线程安全的List
*/
list = Collections.synchronizedList(list);
注意:通过这种方法获得的集合不和迭代器互斥
HTTP-超文本传输协议(请求)
- 浏览器与服务端建立连接后,会发送过来HTTP请求。
DNS - Domain Name System,域名系统(解析服务器域名)
HTTP请求的格式分为三部分
- 1:请求行
- 2:消息头
- 3:消息正文
回车换行是两个字符:
- CR:回车(\r)
- LF:换行(\n)
1. 请求行
- 请求方式 请求路径 版本号
- method url protocol
- 例如:
- GET /index.html HTTP/1.1CRLF
2:消息头
- 根据请求资源的不同消息头中的内容也不完全一样。
- 消息头中每一个信息都以CRLF结束
- name:valueCRLF
- 消息头中会有若干组信息发送过来,当所有消息头内容发送完毕后,
- 会单独发送一个CRLF表示消息头中所有内容发送完毕。
- 格式例如:
- name1:value1CRLF
- name2:value2CRLF
- …
- CRLF 单独读取到一个CRLF表示消息头全部发送完毕
3:消息正文(暂时略)
try中抛出异常,
- catch中在实际开发中会做一个工作就是记录日志,
- 若该异常不应当在当前方法中被解决,可以继续在catch中将该异常抛出。
使用 BlockingQueue
-
BlockingQueue是双缓冲队列
-
在多线程并发时,若需要使用队列,我们可以使用Queue,但是要解决一个问题就是同步,
但同步操作会降低并发对 Queue操作的效率。
-
BlockingQueue内部使用两条队列,可允许两个线程同时向队列一个做存储,
一个做取出操作,在保证并发安全的同时提高了队列的存取效率。
使用BlockingQueue(续1)
-
第一个元素(头部)
抛异常 返回特定值(true/false) 阻塞 超时 插入 addFirst(o) offerFirst(o) putFirst(o) offerFirst(o,timeout,timeunit) 移除 removeFirst(o) pollFirst(o) takeFirst(o) pollFirst(timeout,timeunti) 获取 getFirst(o) peekFirst(o) 不适用 不适用 -
最后一个元素(尾部)
抛异常 返回特定值(true/false) 阻塞 超时 插入 addLast(o) offerLast(o) putLast(o) offerLast(o,timeout,timeunit) 移除 removeLast(o) pollLast(o) takeLast(o) pollLast(timeout,timeunit) 获取 getLast(o) peekLast(o) 不适用 不适用
注:
- 抛异常:若操作无法执行,则抛出异常.
- 返回特定值:若操作完成,则返回true;否则返回false.
- 阻塞:若无法执行,则阻塞该线程,直到可以执行.
- 超时:若操作无法执行,则阻塞线程.若超过指定时间若无法执行,则返回false.
- 不能插入null,否则抛出NullPointerException.
- 基于队列的数据结构查找元素效率低.
使用 BlockingQueue(续2)
-
Array BlockingQueue:规定大小的 Blocking Deque其构造函数必须带一个int参数来指明其大小其所含的对象是以FFO(先入先出)顺序排序的。
-
LinkedBlockingQueue:大小不定的 Blocking Deque若其构造函数带一个规定大小的参数生成的Blocking Deque有大小限制若不带大小参数所生成的Blocking Deque的大小由 nteger. MAX- VALUE来决定其所含的对象是以FFO(先入先出)顺序排序的。
-
PriorityBlockingQueue:类似于 -LinkedBlock Deque,但其所含对象的排序不是FFO而是依据对象的自然排序顺序或者是构造函数的 Comparato决定的顺序。
-
SynchronousQueue:特殊的 BlockingQueue,对其的操作必须是放和取交替完成的。
双缓冲队列
-
双缓冲队列是一个线程安全的队列,并且由于
-
内部使用两条线程交替完成存取元素工作,实际
-
上解决了并发存取元素时的互斥问题。更高效。
java.util.Map 映射
-
Map在实际开发中是一种非常常用的数据结构。
-
存储元素开起来像是一个“多行两列的表格”。
-
Map中每个元素有两个部分组成,分别为:key,value。
-
俗称:键值对。
-
而从Map中查询数据的原则是根据key查找对应的value,
-
所以Map要求key不允许重复(Map中不允许有key的equals比较为true。)
-
Map是一个接口,规定了Map这种数据结构操作的相关方法。
-
常用实现类:java.util.HashMap(俗称:散列表,哈系表)
V put(K k,V v)
- 将给定的key-value对存入Map中。
- 由于Map中不允许出现重复的key,所以若使用Map中已经存在的key存入一个新的value时,
- 是做替换value操作,那么put方法的返回值就为该key原来对应的value,
- 即:返回被替换的value
- 若指定的key在Map中不存在,则存入后返回值为NULL。
V get(K k)
- 根据给定的key获取对应的value,若给定的key在Map中不存在,则返回值为null
// 返回类型为Integer
Integer num = map.put("语文", 99);
// 如果用int接收会进行自动拆箱操作
int num = map.put("语文",99).intValue();
V remove(K k)
- 将给定的key所对应的键值对从当前Map中删除,返回值为该key所对应的value值
三、遍历Map,HashMap,装载因子及HashMap优化,HTTP协议(响应)
Map的遍历
-
遍历Map有三种方式:
-
1:遍历所有的key
-
2:遍历所有的键值对(Entry)
-
3:遍历所有的value(用的较少)
遍历所有的key
-
Set<K> keySet()
-
将当前Map中所有的key存入一个Set集合后返回。
遍历所有的键值对
-
Map中每一组键值对有内部类Entry的实例表示,
-
Entry有两个常用方法:
-
getKey(),getValue() 这两个方法用于获取当前Entry实例表示的这组键值对中的key与value
-
Set<Entry> entrySet()
-
将当前Map中每组键值对存入一个Set集合后返回
HashMap
-
HashMap内部由数组实现,是当今查询速度最快的数据结构。
-
HashMap根据key元素决定这组键值对应当存放在数组的位置,
-
并且也是根据key元素得到位置,来检索数组中的该组键值对,一次省去了遍历数组的过程。
-
但是使用HashMap时应当尽量避免出现链表的情况。
-
HashMap中链表的产生原因:
-
经过散列计算得到该组键值对应当存放在数组的哪个位置,
-
而根据key元素equeals比较查看当前key是否已经存在于Map中,
-
若存在则直接替换该位置这个key所对应的value,
-
但是若数组该位置已经存在键值对,并且key与当前准备存放的键值对中的key的equals比较不同时,
-
则会在数组当前位置产生链表。这样在将来会根据key检索时会出现遍历链表的情况,
-
这样就会降低HashMap查询性能。
总结:
当key元素hashcode值相同,但是equals比较不同时就会在HashMap中产生链表,影响查询性能。
JDK中提供的类作为Key元素没问题,都应妥善重写完毕。
当我们自己定义的类需要作为key元素时,也要妥善的重写 equals()、hashcode() 两个方法。
在API手册的Object类中对equals,hashcode方法的重写有相关说明。重写准则:
-
1:成对重写,即:当我们需要重写一个类的equeals方法时,就应当连同重写hashcode。
-
2:一致性,即:当两个对象equals比较为true时,hashcode返回的数字应当相等,反之亦然。
因为若两个对象equals比较不同但是hashcode相等时,在HashMap中会产生链表。
-
3:hashcode的稳定性,即:当参与equals比较的属性值没有发生改变的前提下,
多次调用hashcode方法返回的数字应当相等,不应当变化。
**补充:**LinkedHashMap是可以按照put元素时的顺序遍历Map中的所有元素。
装载因子及HashMap优化
-
Capacity:容量,hash表里 bucket(桶)的数量也就是散列数组大小。
-
Initial capacity:初始容量,创建hash表时初始 bucket的数量默认构建容量是16.也可以使用特定容量。
-
Size:大小,当前散列表中存储数据的数量
-
Load factor:加载因子,默认值0.75(就是75%),当向散列表增加数据时如果size/ capacity的值大于Load factor则发生扩容并目重新散列 ( rehash)
-
性能优化:加载因子较小时,散列查找性能会提高,同时也浪费了散列桶空间容量。0.75是性能和空间相对平衡结果。在创建散列表时指定合理容量,减少 rehash提高性能。
HTTP协议(响应)
HTTP协议要求一个响应要求三部分:
- 状态行,响应头,响应正文
状态行
- 状态行格式:
- 协议版本 状态代码 状态描述CRLF
- 例如:
- HTTP/1.1 200 OK
响应头
- 响应头格式:(与请求头中的格式相同)
- name1:value1CRLF
- name2:value2CRLF
- …
- namex:valuexCRLF
- CRLF(单独发送一个CRLF表示响应头发送完毕)
响应正文
- 响应正文为实际数据(字节数据)
四、设置Content-Type和Content-Length返回各种类型的文件
响应头信息值中的Content-Type,该头信息用来告知客户端
我们响应的实际数据表示的是什么类型的
HTTP协议中针对不同的数据类型,有专门的定义,常见的:
Http页面的类型:text/html
jpeg图片的类型:image/jpeg
Content-Length用于描述HTTP消息实体的传输长度the
transfer-length of the message-body。在HTTP协议中,消息
实体长度和消息实体的传输长度是有区别,比如说gzip压缩下,
消息实体长度是压缩前的长度,消息实体的传输长度是gzip压
缩后的长度。
五、XML语法、XML解析
XML处理指令
- XML处理指令,简称PI( processing instruction)处理指令用来指挥解析引擎如何解析XM文档内容。
- <?xml version=“1.0” encoding=“ut8” ?>
- 在XML中,所有的处理指令都以<?开始,?>结束。
- <?后面紧跟的是处理指令的名称。XML处理指令要求指定一个version属性。
- 并允许指定可选的 standalone和 encoding,
- 其中 standalone是指是否允许使用外部声明,可设置为yes或no。
- yes是指定不使用外部声明,no为使用。
- encoding是指作者使用的字符编码格式。有UTF-8,GBK,gb2312等等。
XML用途
-
XML指句扩展标记语言( EXtensible Markup Language),是独立于软件和硬件的信息传输工具。
-
XML应用于web开发的许多方面,常用于简化数据的存储和共享。
-
XML简化数据共享
-
XML简化数据传输
-
XML简化平台的变更
元素和属性:
-
XML文档包含XML元素
-
XML元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。
元素可包含其他元素、文本或者两者的混合物。元素也可以拥有属性。
<datasource id="db_oracle">
<property name="url">
jdbc thin@192.168.0. 26: 152l: tarena
</property>
<property name="dbUser">openlab</property>
<property name="dbPwd">open123</property>
</datasource>
注意:属性只能加在开始标签中,属性值要用引号括起来(单引号、双引号都行,不能穿插使用)
大小写敏感:
-
XML对大小写是敏感的,这一点不像HTML。在XML中,标记< Letter>和标记< letter>是不一样的。
-
因此,起始和结束标记的大小写应该写成相同的:
<letter>…</letter>
<Letter>…</Letter>
元素必须有关闭标签:
-
XML要求每个元素必须由起始标签和关闭标签组成。
-
关闭标签与起始标签的名字相同,写法上多一个"/"
-
< Letter>只有起始标记是不行的。
-
< Letter></ Letter>必须要有关闭标签
必须有根元素:
-
XML要求必须有根元素,所谓根元素就是不被其它元素包围。并且根元素只能有一个。
< datasource id="db_oracle"> 根元素 <property name="ur"> jdbc:thin@192.168.26:1521:tarena </property> <property name="dbUser">openlab</property> <property name="dbPwd">open123</property> </datasource> <!--这里不能再定义与 datasource 平级的元素!-->
必须正确嵌套:
-
XML要求所有元素必须正确的嵌套
<datasource id="db_oracle"> <property name="dbPwd"> open123 </datasource> <!--这里嵌套关系出现了错误!--> </property>
实体引用:(类似于XML文件中的转义字符)
-
实体可以是常用的短语、键盘字符、文件、数据库记录或任何包含数据的项,
在XML中,有时实体内包含一字符,如 & < > " ’ 等,这些均需要对其进行转义,
否则会对XML解释器生成错误。
实体引用 字符 说明 < < 小于 > > 大于 & & 与字符(和字符) ' ’ 单引号 " " 双引号
DOM解析方式:
-
DOM:( Document Object Model,即文档对象模型)是W3C组织推荐的处理XML的一种方式。
-
DOM解析器在解析XML文档时,会把文档中的所有元素按照其出现的层次关系,解析成一个个Node对象(节点)。
-
优点:把xml文件在内存中构造树形结构,可以遍历和修改节点。
-
缺点:如果文件比较大,内存有压力,解析的时间会比较长。
CDATA段:
-
格式:<![CDATA[文本内容]]>
-
特殊标签中的实体引用都被忽略,所有内容被当成一整块文本数据对待。
<?xml version="1.0" encoding="utf-8"?> <root> <![CDATA[ 无论这里写什么,都会被当做一个文本 <hello> <world> ]]> <subRoot> </subRoot> </root>
SAX解析方式:
-
SAX( Simple API for XML)是一种XML解析的替代方法。
- 相比于DOM,SAX是一种速度更快,更有效的方法。
- 它逐行扫描文档,一边扫描边解析。
- 而且相比于DOM,SAX可以在解析文档的任意时刻停止解析。
-
优点:解析可以立即开始,速度快,没有内存压力。
-
缺点:不能对节点做修改。
对比 SAX 和 DOM:
SAX------—读一点解析一点
DOM-------先一次性全部读完
SAXReader读取XML文档
-
使用 SAXReader需要导入 dom4j-full.jart包。
-
dom4j是一个Java的 XML API,类似于jdom,用来读写XML文件的。
- dom4是一个非常优秀的 Java XML API,具有性能优异、功能强大和易用的特点,
- 同时它也是个开放源代码的软件。
-
创建 SAXReade来读取XML文档。
VO value object 值对象
- 所谓值对象,就是当前类的每个实例就是用来保存一组数据使用。
- 例如:表示一个员工的信息
**注意:**值对象类中需要
1:重写无参构造
2:重载有参构造
3:增加get,set方法
4:重写toString方法
原来我们程序使用的数据都是写死在程序里的
- 这样带来的问题是数据发生改变就会修改源代码,这在实际开发中是极其不可取的!
- 所以在实际开发中,我们通常的做法是将程序与程序运行时需要的数据分离开,
- 将数据保存到一个文件中,程序运行时通过读取文件中的数据来运行。
- 这样将来数据有修改只需要在该文件中作出修改即可,程序无需做任何改动。
使用DOM解析XML的大致步骤:
- 1:创建SAXReader
- 2:使用SAXReader读取XML文档并生成Document对象,
- 这一步就是DOM耗时耗资源的地方,
- 因为要将XML文档全部读取完毕并以Document对象存入内存。
- 3:通过Document获取根元素:
- Document提供的方法:Element getRootElement()
- 获取当前XML文档的根元素;
- org.dom4j下的Element的每一个实例用于表示XML文档中的一个元素(一对标签)
- 4:按照XML文档结构从根元素开始逐级获取子元素以达到获取XML文档数据的目的。
Element提供了获取当前元素信息的方法:
-
1:String getName()
获取当前元素的名字。
-
2:String getText()
获取当前元素中间的文本数据(开始标签与结束标签中间的文本内容)。
-
3:List elements()
获取当前元素中的所有元素。
-
4:List elements(String name)
获取当前元素中的所有同名子元素。
-
5:Element element(String name)
获取当前元素中指定名字的子元素,返回第一个匹配到的子元素。
-
6:String elementText(String name)
获取当前元素中指定名字子元素中间的文本。
String str=ele.elementTest(name); // 效果等同于 String str=ele.element(name).getText();
-
7:String attributeValue(String name)
获取当前元素中指定名字的属性的值。
注意:
1.SAX解析因为是逐行逐句扫描解析,比起DOM方式一次性把所有内容存到内存中来说,效率来说应该更高一些。
2.但是SAX这种解析方式,虽然SAX解析不适合对XML文档进行增删改等操作。而DOM解析的增删改操作相比之下就十分方便。
3.DOM解析式一次性装在所有内容到内存中,如果用户只需要其中一部分内容,DOM解析的方式的效率就会降低
六、线程池
使用 ExecutorService 实现线程池
-
Executor Service 是java提供的用于管理线程池的类。
-
线程池有两个主要作用
- 1、控制线程数量;
- 2、重用线程。
-
当一个程序冲中若创建大量线程,并在任务结束后销毁,会给系统带来过度消耗资源,
以及过度切换线程的危险,从而可能导致系统崩溃。为此我们应使用线程池来解决这个问题。
使用 ExecutorService实现线程池(续2)
-
线程池有以下几种实现策略:
-
Executors.newCachedThreadPool()
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
-
Executors. newFixedThreadPool(int nThreads)
创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程。
-
Executors.newScheduledThreadPool(int corePoolsize)
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
-
Executors.newSingleThreadExecutor()
创建一个使用单个 worker线程的 Executor,以无界队列方式来运行该线程。
-
线程池
- 频繁地创建销毁线程,或者创建过多的线程都会给系统带来风险。
- 轻者拖慢系统,出现卡顿现象,严重时可能出现内存溢出系统瘫痪。
- 为此,我们在处理并发量大的业务逻辑时,常常使用线程池来管理和调度线程。
- 线程池主要解决两个问题:
- 1:重用线程(避免频繁创建销毁线程)
- 2:控制线程数量(避免因为大量的线程导致的系统崩溃)
七、反射
反射:
- 反射是 Java 的动态机制,用于在运行期间检查对象的类型,检查对象的类结构(属性,方法等),还可以动态加载类,动态创建对象,动态访问属性和方法等。
**注意:**反射是 Java 的API。
Public void print(Object obj){
//利用反射API,动态检查obj引用的对象类型
Class cls=obj.getClass();
System.out.println(obj);
}
getClass方法在Object类上定义
-
被所有对象继承,全部对象上都有的方法
-
用于返回调用方的类,例如:
Object obj;
Class cls=obj.getClass();
经典面试题:
Eclipse 中快捷菜单采用了什么技术实现的?
答案:反射技术,反射API
反射的功能:
-
1:动态加载类
-
2:动态创建对象
-
3:动态调用属性和方法
静态调用:
-
如下程序编译后就已经确定了执行次序,
-
执行期间按照编译时候确定的执行顺序
-
执行,这个情况称为静态执行。例如:
Foo foo=new Foo();
foo.test();
动态执行:
- Java反射API提供了动态执行机制
动态加载类:
-
在程序运行之前不知道类名,
-
在运行期间动态根据类名加载类到内存(方法区)
-
例如:
Class cls=Class.forName(类名);
动态创建对象:
-
如下方法可以动态创建类的一个实例
Class cls=Class.forName(类名); Object obj = cls.newInstance();
-
使用前提:cls代表的类必须存在无参数构造器!
-
如果没有无参数构造器则抛出异常,
-
反射中可以调用有参数构造器(略)
必须使用反射实现的案例:
- 执行一个类中的全部以test为开头的方法,这些方法都是非静态方法,方法没有返回值,没有参数。
实现过程:
-
1:动态输入一个类名
-
2:动态加载类型
-
3:动态创建对象
-
4:利用反射查找全部方法信息
-
5:找到以test为开头方法
-
6:利用反射动态执行对象的方法
访问不可见方法:
- Java中的访问修饰词用于控制方法和属性的可见范围,称为封装特性。
- 反射API可以打破如上封装,实现访问不可见的方法和属性!
method.setAccessible(true);
动态调用有参数的方法:
//找到方法
cls.getDeclaredMethod(方法名,参数类型1,参数类型2...)
//创建对象
obj=cls.newInstance();
//准备参数
Object method.invoke(obj,参数1,参数2,参数3….)
解耦:
-
解除耦合性:减低/解除 两段代码(两个组建)之间的耦合关系。
public void test(){ Foo foo=new Foo(); foo.test(); }
-
利用反射API,可以实现一个组件与未来的一个组件的松散耦合,
-
甚至可以在不知道类名不知道方法名的情况下实现调用关系。
动态访问对象的属性:
-
Field 代表类中的属性信息
//读取对象obj的指定属性,返回属性的值 Object Field.get(obj) //设置属性的值 void Field.set(obj,value);
案例:反射读取对象的一个属性
Stirng name=in.nextLine();
//动态加载类
Class cls=Class.forName(className);
//动态创建一个对象
Object obj=cls.newInstance();
//获取类的一个属性信息
fld=cls.getDeclaredField(name);
Object val=fld.get(obj);
System.out.println(val);
修改属性:
// 检查属性 age=10
String className = in.nextLine();
String name = in.nextLine();
String value = in.nextLine();
// 动态加载类
Class cls = Class.forName(className);
//动态创建一个对象
Object obj = cls.newInstance();
//获取类的一个属性信息
fld = cls.getDeclaredField(name);
fld.set(obj,value);
System.out.println(obj);
经典案例:
-
需求:检查一个类是否包含service(Request req,Response res)
-
方法,如果包含这个方法,就执行这个方法,不包含就抛出异常。
-
类名,Request 对象,Response对象作为参数输入。
-
设计:
public void execute(String className,Request req, Response res) throw Exception{ //动态加载类 //根据方法签名查找方法“service” Request Response //创建对象,执行方法 }
-
这个设计的好处:解耦
**注意:**反射尽量少用,因为不会有编译错误,容易出错。
很多框架使用反射:
- 框架为了解决对未来未知组件进行管理,所以经常使用反射API
八、日期处理
Hello,大家能看到这里真是棒棒哒~💪💪
这里跟大家说一下,这里的日期处理是针对 JDK8 之前的版本的,JDK8 之后有更方便的日期处理方式啦~
可以去看下我整理的 JDK8 的日期处理文章哦~ 《Java8 新的日期和时间》
(img-ERbHfEh2-1606381325176)(file:///C:\Users\TSINGS~1\AppData\Local\Temp\ksohtml14552\wps33.jpg)]
聊点别的:
时间是什么?没有开始,没有结束的一个过程。
为了解决计时问题,人类为时间人为的设定了原点和刻度! 称为“历法”。
计算机时间:原点(元年)是1970年元旦,刻度是毫秒数。
//获取当前系统时间毫秒数
long now=System.currentTimeMillis();
注意:此时now中获取的是国际标准时间,即:格林威治时间,格林威治时间比中国时间早八个小时(中国在东八区+8,西八区-8)
-
new Date(); 默认构造器封装的就是当前时间。
-
Date的年份设计为2位数年份
-
如:1975年返回整数 75
-
如:1995年返回整数 95
-
如:2017年返回整数 117
注意:Date的日期分量处理API不推荐使用
Date内部封装了一个时间毫秒数
-
(UTC/GMT)用于表示的时间,提供
-
时间分量的计算方法,但是由于历史
-
原因,不建议使用,这些时间分量计算
-
方法建议使用Calender替换。
-
date.getTime() 获取时间毫秒数
-
date.getTime(t) 设置(修改)时间毫秒数
由于Date的API方法有设计缺陷
-
建议使用Calendar替换Date的API方法
-
这些方法的功能包括:
-
1.计算时间分量:根据毫秒数换算时间分量(年月日时分秒)
-
2.设置时间分量:修改年月日时分秒,底层是利用毫秒数换算得到结果。
-
3.增加/减少时间分量:增加/减少指定的毫秒数
// 获得Calender对象:其中包含当前毫秒数
// Calendar的默认实现类:格里高里历
// 就是公元历法
Calendar cal=Calendar.getInstance();
// 获取(计算)日期分量的方法
int sec=cal.get(Calendar.SECOND);// 秒
int min=cal.get(Calendar.MINUTE);// 分
int hour=cal.get(Calendar.HOUR);// 时
// 月(MONTH)中的天数
int day=cal.get(Calendar.DAY_OF_MONTH);// 天
int month=cal.get(Calendar.MONTH)+1;// 月
int year=cal.get(Calendar.YEAR);// 年
System.out.println(year + "-" + month + "-" + day);
System.out.println(hour + ":" + min + ":" + sec);
// Calendar能够设置时间分量,其底层就是
// 利用时间毫秒计算实现的
cal.set(Calendar.YEAR, 2018);
// cal可以转换为Date
Date date=cal.getTime();
System.out.println(date);
// 设置时间的月份为1月(JANUARY)
cal.set(Calendar.MONTH,Calendar.JANUARY);
date=cal.getTime();
System.out.println(date);
// Calendar 可以增加时间分量,也可以减少时间分量
cal.add(Calendar.YEAR, 2);
cal.add(Calendar.MONTH, 1);
date=cal.getTime();
System.out.println(date);
// 检查一个月中最大的天数
int max=cal.getActualMaximum(Calendar.DAY_OF_MONTH);
System.out.println(max);
日期格式化输出
-
SimpleDateFormat 类
-
1.format()方法
提供了日期数据格式化为String方法,用于将日期显示为我们常用的格式;
-
2.parse()方法
提供了将人类习惯的日期字符串,转换为计算机能处理的Date时间也就是毫秒数。
SimpleDateFormat fmt=new SimpleDateFormat();
// 将date的时间毫秒数,转换为日常的"年月日"、"时分"的字符串格式
Date date=new Date();
String str=fmt.format(date);
System.out.println(str);
// 可以利用自定义格式实现日期的格式化
fmt=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
str=fmt.format(date);
System.out.println(str);
注意:“yyyy-MM-dd HH:mm:ss”–一个M不补0,m表示秒,区分大小写