Java入门(四)JAVA SE 02

📢:哈喽~☀️欢迎加入程序🐒大家庭,快点开始✒️自己的黑客帝国吧 ~🌊🌊

内容简述: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的子接口,

  • 规定了双端队列的相关方法。

  • 双端队列的特点是可以分别从队列首尾做进出队操作。

  • 双端队列提供了更有方向性的进出队方法。

wps5.jpg

队列提供了引用队首元素方法peek

  • 它与poll的区别,在于获取后该元素依然在队列中。

  • 在双端队列中还支持peekFirst,peekLast

Stack 栈:

  • 栈用来存一组元素,但是存取元素必须遵循先进后出
  • FILO——First In Last Out

wps2.jpg

用途:用来进行操作的后退/撤销。

在这里插入图片描述

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解释器生成错误。

    实体引用字符说明
    &lt;<小于
    &gt;>大于
    &amp;&与字符(和字符)
    &apos;单引号
    &quot;"双引号

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表示秒,区分大小写

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不愿放下技术的小赵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值