Java float, double, decimal

‘java float,double,decimal’ decimal:数字型,128bit,不存在精度损失,常用于银行帐目计算。 (28个有效位) float f = 345.98756f;//结果显示为345.9876,只显示7个有效位,对最后一位数四舍五入。 double d=345.975423578631442d;//结果显示为345.975423578631,只显示15个有效位,对最后一位四舍五入。 注: float和double的相乘操作,数字溢出不会报错,会有精度的损失。 decimal dd=345.545454879…..//可以支持28位,对最后一位四舍五入。 注: 当对decimal类型进行操作时,数值会因溢出而报错。 由于对float或double 的使用不当,可能会出现精度丢失的问题。问题大概情况可以通过如下代码理解: public class FloatDoubleTest { public static void main(String[] args) { float f = 20014999; double d = f; double d2 = 20014999; System.out.println("f=" + f); System.out.println("d=" + d); System.out.println("d2=" + d2); } } 得到的结果如下: f=2.0015E7 d=2.0015E7 d2=2.0014999E7 从输出结果可以看出double 可以正确的表示20014999 ,而float 没有办法表示20014999 ,得到的只是一个近似值。这样的结果很让人讶异。20014999 这么小的数字在float下没办法表示。于是带着这个问 题,做了一次关于float和double学习,做个简单分享,希望有助于大家对java 浮 点数的理解。 关于 java 的 float 和 double ...

Created 3036-07-03 · Updated 9096-10-09 · 2 min · 295 words · -

Java GC 监控, jstat

java gc监控, jstat jstat -<option> [-t] [-h] <vmid> [<interval> [<count>]] jstat -gc -h3 <jvm PID> 3000 -1 # -gc 显示gc的信息,查看gc的次数,及时间。 # -h: 即-h跟数字,代表隔几行显示标题 # interval: 毫秒,代表监控间隔时间段,默认毫秒做单位 # count: 代表取数次数 Jstat在分析java的内存GC时的应用 jstat可以查看堆内各个部分的使用量,以及加载类的数量。使用时,需加上查看进程的进程id,和所选参数。 参数: jstat -class pid: Statistics on the behavior of the class loader. 显示加载class的数量,及所占空间等信息; jstat -compiler pid: Statistics of the behavior of the HotSpot Just-in-Time compiler.显示VM实时编译的数量等信息; jstat -gc pid: Statistics of the behavior of the garbage collected heap.可以显示gc的信息,查看gc的次数,及时间。其中最后五项,分别是young gc的次数,young gc的时间,full gc的次数,full gc的时间,gc的总时间。 jstat -gccapacity:可以显示,VM内存中三代 (young,old,perm) 对象的使用和占用大小,如: PGCMN显示的是最小perm的内存使用量,PGCMX显示的是perm的内存最大使用量,PGC是当前新生成的perm内存占用量,PC是但前perm内存占用量。其他的可以根据这个类推, OC是old内纯的占用量。 ...

Created 9096-08-09 · Updated 9096-10-09 · 2 min · 353 words · -

javadoc

javadoc eclipse 在项目列表中按右键,选择Export (导出) ,然后在Export(导出)对话框中选择java下的javadoc. Java8下 忽略Javadoc编译错误 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-javadoc-plugin</artifactId> <version>2.10.3</version> <executions> <execution> <id>attach-javadocs</id> <goals> <goal>jar</goal> </goals> <configuration> <additionalparam>-Xdoclint:none</additionalparam> </configuration> </execution> </executions> </plugin> http://www.javajia.com/JAVAbiancheng/7713.html 维护记录 时间 修改内容 原因 2026-10-09 修复本地 Hugo 构建的 Raw HTML 警告:代码/配置放入代码块,正文中的尖括号占位符改为行内代码 正文中的 HTML/XML 片段被当作原始 HTML 丢弃;文件名/URL/标题按规范调整

Created 28286-08-28 · Updated 9096-10-09 · 1 min · 40 words · -

Java 数组, List, Set, 数组转 Set

java 数组 list set, 数组转set http://jerval.iteye.com/blog/1001643 //数组->Set String[] strs = {"AA","BB"}; Set<String> set2 = new HashSet<String>(Arrays.asList(strs)); System.out.println(set2); //Set->数组 Set<String> set3 = new HashSet<String>(Arrays.asList("PP","OO")); String[] strSet = new String[set3.size()]; set3.toArray(strSet); System.out.println(Arrays.toString(strSet)); List(interface): 次序是List最重要的特点;它确保维护元素特定的顺序。List为Collection添加了许多方法,使得能够向List中间插入与移除元素(只推荐LinkedList使用)。一个List可以生成ListIterator,使用它可以从两个方向遍历List,也可以从List中间插入和删除元素。 ArrayList: 由数组实现的List。它允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。ListIterator只应该用来由后向前遍历ArrayList,而不是用来插入和删除元素,因为这比LinkedList开销要大很多。 LinkedList: 对顺序访问进行了优化,向List中间插入与删除得开销不大,随机访问则相对较慢(可用ArrayList代替)。它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast(),这些方法(没有在任何接口或基类中定义过)使得LinkedList可以当作堆栈、队列和双向队列使用。 #################################### Set(interface): 存入Set的每个元素必须是唯一的,因为Set不保存重复元素。加入Set的Object必须定义equals()方法以确保对象的唯一性。Set与Collection有完全一样的接口。Set接口不保证维护元素的次序。 HashSet: 为快速查找而设计的Set。存入HashSet的对象必须定义hashCode()。采用散列函数对元素进行排序,这是专门为快速查询而设计的; TreeSet: 保持次序的Set,底层为树结构。使用它可以从Set中提取有序的序列。TreeSet采用红黑树的数据结构进行排序元素; LinkedHashSet: 具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。LinkedHashSet内部使用散列以加快查询速度,同时使用链表维护元素的次序,使得看起来元素是以插入的顺序保存的。 需要注意的是,生成自己的类时,Set需要维护元素的存储顺序,因此要实现Comparable接口并定义compareTo()方法。 维护记录 时间 修改内容 原因 2026-10-09 修复本地 Hugo 构建的 Raw HTML 警告:代码/配置放入代码块,正文中的尖括号占位符改为行内代码;title 改为「Java 数组, List, Set, 数组转 Set」 正文中的 HTML/XML 片段被当作原始 HTML 丢弃;文件名/URL/标题按规范调整

Created 8086-09-08 · Updated 9096-10-09 · 1 min · 68 words · -

Servlet

Servlet servlet的由来 自从计算机软件开发进入网络时代,就开始涉及到通讯问题。在客户/服务器 (C/S) 时期,每个软件都有自己的客户端和服务器端软件。并且客户端和服务器端之间的通讯协议差别也很大。后来随着互联网的发展,基于浏览器/服务器(B/S)的应用逐渐成为主流,通讯协议也统一到HTTP协议。但是,在HTTP协议之上,如何处理来自客户端的请求信息,以及如何对请求进行回应,则经历了很长时间也没有统一下来。目前,对于这个问题的解决方案主要有两种,一个是CGI,另一个是Servlet。 servlet是在服务器上运行的小程序。这个词是在Java applet的环境中创造的,Java applet是一种当作单独文件跟网页一起发送的小程序,它通常用于在客户端运行,结果得到为用户进行运算或者根据用户互作用定位图形等服务。服务器上需要一些程序,常常是根据用户输入访问数据库的程序。这些通常是使用公共网关接口 (CGI(Common Gateway Interface)) 应用程序完成的。然而,在服务器上运行Java,这种程序可使用Java编程语言实现。在通信量大的服务器上,servlet的优点在于它们的执行速度更快于CGI程序。各个用户请求被激活成单个程序中的一个线程,而无需创建单独的进程,这意味着服务器端处理请求的系统开销将明显降低。最早支持Servlet技术的是JavaSoft的Java Web Server。此后,一些其它的基于Java的WebServer开始支持标准的ServletAPI。 Servlet最初是在1995年由James Gosling 提出的,因为使用该技术需要复杂的Web服务器支持,所以当时并没有得到重视,也就放弃了。后来随着Web应用复杂度的提升,并要求提供更高的并发处理能力,Servlet被重新捡起,并在Java平台上得到实现,现在提起Servlet,指的都是Java Servlet。Java Servlet要求必须运行在Web服务器当中,与Web服务器之间属于分工和互补关系。确切的说,在实际运行的时候Java Servlet与Web服务器会融为一体,如同一个程序一样运行在同一个Java虚拟机 (JVM) 当中。与CGI不同的是,Servlet对每个请求都是单独启动一个线程,而不是进程。这种处理方式大幅度地降低了系统里的进程数量,提高了系统的并发处理能力。另外因为Java Servlet是运行在虚拟机之上的,也就解决了跨平台问题。 在Servlet出现之后,随着使用范围的扩大,人们发现了它的一个很大的一个弊端。那就是为了能够输出HTML格式内容,需要编写大量重复代码,造成不必要的重复劳动。为了解决这个问题,基于Servlet技术产生了Java Server Pages (JSP) 。 Servlet和JSP两者分工协作,Servlet侧重于解决运算和业务逻辑问题,JSP则侧重于解决展示问题。Servlet与JSP一起为Web应用开发带来了巨大的贡献,后来出现的众多Java Web应用开发框架都是基于这两种技术的,更确切的说,都是基于Servlet技术的。 Servlet与Web容器之间的关系 Java是一种动态加载和运行的语言。也就是说当应用程序持有一个类的地址 (CLASSPATH) 和名称 (包名和类名) 的情况下,可以在程序运行期间任何时候加载这个类,并创建和使用该类的对象。Servlet就是基于这个机制与Web容器融合在一起的。目前已知的所有支持Java Servlet的Web容器都是采用Java开发的。当Web容器接收到来自客户端的请求信息之后,会根据URL中的Web元件地址信息到Servlet队列中查找对应的Servlet对象,如果找到则直接使用,如果没有找到则加载对应的类,并创建对象。也就是说,Servlet对象是在第一次被使用的时候才创建的,并且一旦创建就会被反复使用,不再创建新的对象。所有创建出的Servlet对象会在Web服务器停止运行的时候统一进行垃圾回收。 为了解决客户端请求地址与 Servlet之间对应关系问题,Web容器需要一个用来描述这种对应关系的文件,一般是web.xml文件。如果一个Web应用程序中存在很多个Servlet,那么web.xml会变得非常庞大。在Servlet 3.0规范推出之后,允许在Servlet代码中使用声明式语法(annotation)来代替web.xml中的描述信息,这才让web.xml瘦身下来. Servlet是一种服务器端的Java应用程序,具有独立于平台和协议的特性,可以生成动态的Web页面。 它担当客户请求 (Web浏览器或其他HTTP客户程序) 与服务器响应 (HTTP服务器上的数据库或应用程序) 的中间层。 Servlet是位于Web 服务器内部的服务器端的Java应用程序,与传统的从命令行启动的Java应用程序不同,Servlet由Web服务器进行加载,该Web服务器必须包含支持Servlet的Java虚拟机。 Servlet的主要功能在于交互式地浏览和修改数据,生成动态Web内容。这个过程为: 客户端发送请求至服务器端; 服务器将请求信息发送至Servlet ; Servlet生成响应内容并将其传给Server。响应内容动态生成,通常取决于客户端的请求; 服务器将响应返回给客户端; Servlet看起来像是通常的Java程序。Servlet导入特定的属于Java ServletAPI的包。因为是对象字节码,可动态地从网络加载,可以说Servlet对Server就如同Applet对Client一样,但是,由于Servlet运行于Server中,它们并不需要一个图形用户界面。从这个角度讲,Servlet也被称为FacelessObject。 一个servlet就是Java编程语言中的一个类,它被用来扩展服务器的性能,服务器上驻留着可以通过"请求-响应"编程模型来访问的应用程序。虽然servlet可以对任何类型的请求产生响应,但通常只用来扩展Web服务器的应用程序。 目前最新版本为3.0草案命名: Server + Applet =Servlet 意为服务器端的小程序 Servlet生命周期 装载Servlet。这项操作一般是动态执行的。然而,Server通常会提供一个管理的选项,用于在Server启动时强制装载和初始化特定的Servlet。 Server创建一个Servlet的实例 Server调用Servlet的init()方法 一个客户端的请求到达Server Server创建一个请求对象 Server创建一个响应对象 Server激活Servlet的service()方法,传递请求和响应对象作为参数 service()方法获得关于请求对象的信息,处理请求,访问其他资源,获得需要的信息 service()方法使用响应对象的方法,将响应传回Server,最终到达客户端。service()方法可能激活其它方法以处理请求,如doGet()或doPost()或程序员自己开发的新的方法。 对于更多的客户端请求,Server创建新的请求和响应对象,仍然激活此Servlet的service()方法,将这两个对象作为参数传递给它。如此重复以上的循环,但无需再次调用init()方法。一般Servlet只初始化一次(只有一个对象),当Server不再需要Servlet时(一般当Server关闭时),Server调用Servlet的Destroy()方法。 ...

Created 16166-10-16 · Updated 9096-10-09 · 3 min · 519 words · -

Collection, Array, Vector, ArrayList, List, LinkedList

Collection, Array, Vector, ArrayList, List, LinkedList array(数组)和Vector是十分相似的Java构件 (constructs) ,两者全然不同,在选择使用时应根据各自的功能来确定。 数组 Array Java arrays 的元素个数不能下标越界,从很大程度上保证了Java程序的安全性,而其他一些语言出现这一问题时常导致灾难性的后果。 Array 可以存放Object和基本数据类型,但创建时必须指定数组的大小,并不能再改变。值得注意的是: 当Array中的某一元素存放的是Objrct reference 时,Java不会调用默认的构造函数,而是将其初值设为null,当然这跟Java对各类型数据赋默认值的规则是一样的,对基本数据类型同样适用。 线性表,链表,哈希表是常用的数据结构,在进行Java开发时,JDK已经为我们提供了一系列相应的类来实现基本的数据结构。这些类均在java.util包中。本文试图通过简单的描述,向读者阐述各个类的作用以及如何正确使用这些类。 Collection接口 Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素 (Elements) 。一些 Collection允许相同的元素而另一些不行。一些能排序而另一些不行。Java SDK不提供直接继承自Collection的类, Java SDK提供的类都是继承自Collection的"子接口"如List和Set。 所有实现Collection接口的类都必须提供两个标准的构造函数: 无参数的构造函数用于创建一个空的Collection,有一个Collection参数的构造函数用于创建一个新的 Collection,这个新的Collection与传入的Collection有相同的元素。后一个构造函数允许用户复制一个Collection。 如何遍历Collection中的每一个元素?不论Collection的实际类型如何,它都支持一个iterator()的方法,该方法返回一个迭代子,使用该迭代子即可逐一访问Collection中每一个元素。典型的用法如下: Iterator it = collection.iterator(); // 获得一个迭代子 while(it.hasNext()) { Object obj = it.next(); // 得到下一个元素 } 由Collection接口派生的两个接口是List和Set。 List接口 List是有序的Collection,使用此接口能够精确的控制每个元素插入的位置。用户能够使用索引 (元素在List中的位置,类似于数组下标) 来访问List中的元素,这类似于Java的数组。 和下面要提到的Set不同,List允许有相同的元素。 除了具有Collection接口必备的iterator()方法外,List还提供一个listIterator()方法,返回一个 ListIterator接口,和标准的Iterator接口相比,ListIterator多了一些add()之类的方法,允许添加,删除,设定元素,还能向前或向后遍历。 实现List接口的常用类有LinkedList,ArrayList,Vector和Stack。 LinkedList类 LinkedList实现了List接口,允许null元素。此外LinkedList提供额外的get,remove,insert方法在 LinkedList的首部或尾部。这些操作使LinkedList可被用作堆栈 (stack) ,队列 (queue) 或双向队列 (deque) 。 注意LinkedList没有同步方法。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List: ...

Created 9096-11-09 · Updated 9096-10-09 · 3 min · 486 words · -

jinfo

jinfo jinfo可以输出并修改运行时的java 进程的opts。用处比较简单,用于输出JAVA系统参数及命令行参数。用法是jinfo -opt pid 如: 查看2788的MaxPerm大小可以用 jinfo -flag MaxPermSize 2788 jinfo -flag MaxHeapSize 13112 <no option> 打印命令行标识参数和系统属性键值对。 -flag name 打印指定的命令行标识参数的名称和值。 -flag [+|-]name 启用或禁用指定的boolean类型的命令行标识参数。 -flag name=value 为给定的命令行标识参数设置指定的值。 -flags 成对打印传递给JVM的命令行标识参数。 -sysprops 以键值对形式打印Java系统属性。 -h 打印帮助信息。 -help 打印帮助信息。 http://www.softown.cn/post/182.html 维护记录 时间 修改内容 原因 2026-10-09 修复本地 Hugo 构建的 Raw HTML 警告:代码/配置放入代码块,正文中的尖括号占位符改为行内代码;文件重命名为 jinfo.md;url 改为 jinfo;旧 url 加入 aliases;categories 改为 Java 正文中的 HTML/XML 片段被当作原始 HTML 丢弃;文件名/URL/标题按规范调整

Created 11116-11-11 · Updated 9096-10-09 · 1 min · 60 words · -

Java Comparable, Comparator

Comparable Comparable 是排序接口。 若一个类实现了Comparable接口,就意味着“该类支持排序”。 即然实现Comparable接口的类支持排序,假设现在存在“实现Comparable接口的类的对象的List列表(或数组)”,则该List列表(或数组)可以通过 Collections.sort (或 Arrays.sort)进行排序。 此外,“实现Comparable接口的类的对象”可以用作“有序映射(如TreeMap)”中的键或“有序集合(TreeSet)”中的元素,而不需要指定比较器。 Comparable 定义 Comparable 接口仅仅只包括一个函数,它的定义如下: package java.lang; import java.util.*; public interface Comparable<T> { public int compareTo(T o); } 说明: 假设我们通过 x.compareTo(y) 来“比较x和y的大小”。若返回“负数”,意味着“x比y小”;返回“零”,意味着“x等于y”;返回“正数”,意味着“x大于y”。 Comparator 简介 Comparator 是比较器接口。 我们若需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口);那么,我们可以建立一个“该类的比较器”来进行排序。这个“比较器”只需要实现Comparator接口即可。 也就是说,我们可以通过“实现Comparator类来新建一个比较器”,然后通过该比较器对类进行排序。 Comparator 定义 Comparator 接口仅仅只包括两个个函数,它的定义如下: package java.util; public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); } 说明: (01) 若一个类要实现Comparator接口:它一定要实现compareTo(T o1, T o2) 函数,但可以不实现 equals(Object obj) 函数。 ...

Created 1016-12-01 · Updated 9096-10-09 · 1 min · 114 words · -

Spring 的静态工厂方法

Spring的静态工厂方法 http://blog.csdn.net/chensugang/article/details/3357593 上一次写了一篇关于DI的三种方式,其中里面介绍了构造方法的方式,今天学习了一种替代构造器的方法,这就是静态工厂方法来返回对象,下面来看一下静态工厂方法的实现。 静态工厂方法: LoginAction类: package com.spring.test.di; public class LoginAction { private Logic logic; private LoginAction(Logic logic){ this.logic = logic; } public static LoginAction createInstance(Logic logic){ LoginAction loginAction = new LoginAction (logic); return loginAction; } public void execute() { String name = logic.getName(); System.out.print("My Name Is " + name); } } 注意: 这里使用的构造方法是一个私有方法,createInstance这个就是我们的静态工厂方法 Logic接口: package com.spring.test.di; public interface Logic { public String getName(); } 这里只是做为一个简单的例子,所以我们在该接口中只有一个方法,并且在该接口的实现方法也超级简单 ...

Created 26266-03-26 · Updated 9096-10-09 · 1 min · 170 words · -

MANIFEST.MF

字段速查 MANIFEST.MF 是 jar 的清单文件,位于 META-INF/ 下,声明 jar 的入口、classpath 等元数据。常用字段: 字段 说明 Manifest-Version manifest 格式版本,通常 1.0 Main-Class java -jar 时 JVM 执行的入口类 Class-Path 运行时需要的外部 jar 路径 Start-Class Spring Boot 专有:真正的应用主类(此时 Main-Class 指向 loader 的 JarLauncher) 格式规则(容易踩坑): 每行 Key: Value,冒号后一个空格 每行最长 72 字节,超长的值需换行并以一个前导空格续行 文件必须以空行结尾,否则最后一个属性会被静默忽略 Spring Boot 可执行 JAR 中 Main-Class / Start-Class 的分工,以及 JVM 如何读取 manifest 启动应用,见 Spring Boot Executable JAR。 历史 规范:Java JAR File Specification,属 Java SE 标准,由 Oracle(原 Sun)维护 jar 格式基于 ZIP;META-INF/MANIFEST.MF 在最初的 jar 规范里就被强制要求 年份 事件 1996 JDK 1.0,尚无 jar 格式 1997 JDK 1.1 引入 jar 与 MANIFEST.MF 1999 JDK 1.2 扩展签名、sealed package 等 至今 核心结构未变;Spring Boot 等框架在其上追加自定义属性 完整字段说明(历史整理) 打开Java的JAR文件我们经常可以看到文件中包含着一个META-INF目录,这个目录下会有一些文件,其中必有一个MANIFEST.MF,这个文件描述了该Jar文件的很多信息,下面将详细介绍MANIFEST.MF文件的内容,先来看struts.jar中包含的MANIFEST.MF文件内容: ...

Created 8086-04-08 · Updated 9096-10-09 · 2 min · 273 words · -

Class 文件内容, 方法区, 常量池

Class 文件内容, 方法区, 常量池 当JVM运行Java程序的时候,它会加载对应的 class文件,并提取class文件中的信息存放在JVM开辟出来的方法区内存中。那么这个 class 文件里面到底有些什么内容呢? 一、class文件内容概述 class文件是由8bits的字节流组成,全部字节构成了15个有意义的项目。这些项目之间没有任何无意义的字节,因此class文件非常紧凑。占据多字节空间的项目按照高位在前的顺序存放。下面我们详细讨论这些项目: magic (魔数) 每个class文件的前4个字节称为魔数,值为 0xCAFEBABE。作用在于轻松的辨别class文件与非class文件。 minor_version、major_version(次、主版本号) 各占2个字节。随着Java技术的发展,class文件的格式会发生变化。版本号的作用在于使得虚拟机能够认识当前加载class的文件格式。从而准确的提取class文件信息。 constant_pool_count 、constance_pool (常量池) 从这里开始的字节组成了常量池 。 存储了诸如符号常量、final常量值、基本数据类型的字面值等内容。JVM会将每一个常量构成一个常量表,每个常量表都有自己的入口地址。而实际上在JVM会将这些常量表存储在方法区中一块连续的内存空间中,因此class文件会根据常量表在常量池中的位置对其进行索引。比如常量池中的第一个常量表的索引值就是1,第二个就是2。有的时候常量表A需要常量表B的内容,则在常量表A中会存储常量表B的索引值x。而 constant_pool_count 就记录了有多少个常量表,或者所有多少个索引值。实际上,常量池中没有索引值为0的常量表,但这缺失的索引值也被记录在constant_pool_count中,因此 constant_pool_count等于常量表的数量加1。关于常量池的具体内容,我们会在下面详细讲述,并用一个例子来显示整个class文件的内容。 access_flags(访问标志) 占用2个字节。用来表明该class文件中定义的是类还是接口,访问修饰符是public还是缺省。类或接口是否是抽象的。类是否是final的。 this_class 占用2个字节。 它是一个对常量池的索引。指向的是常量池中存储类名符号引用的CONSTANT_Class_info常量表(见下面常量池具体结构)。比如this_class=0x0001。则表示指向常量池中的第一个常量表。通常这个表是指向当前class文件所定义的类名。 super_class 占用2个字节 与this_class类似,指向存放当前class文件所定义类的超类名字的索引的CONSTANT_Class_info常量表。 inteface_count、interfaces interface_count 是class文件所定义的类直接实现的接口或父类实现的接口的数量。占2个字节。intefaces包含了对每个接口的 CONSTANT_Class_info常量表的索引。 fields_count、fields fields_count 表明了类中字段的数量 。fields是不同长度的field_info表的序列。这些field_info表中并不包含超类或父接口继承而来的字段。field_info表展示了一个字段的信息,包括字段的名字,描述符和修饰符。如果该字段是final的,那么还会展示其常量值。注意,这些信息有些存放在field_info里面,有些则存放在field_info所指向的常量池中。下面我们阐述一下这个field_info表的格式: access_flags(2byte 访问修饰符) name_index(2byte 存储字段名的常量表在常量池中的索引) description_index(2byte 存储字段的所属类型的常量表在常量池中的索引) attribute_count(2byte 属性表的数量) attribute (属性) 其中attribute是由多个attribute_info组成。而JVM规范定义了字段的三种属性: ConstanceValue、Deprecated和Synthetic。 method_count、methods 与字段类似,method_count表明类中方法的数量和每个方法的常量表的索引。methods表明了不同长度的method_info表的序列。该表格式如下: access_flags(2byte 访问修饰符) name_index(2byte 存储方法名的常量表在常量池中的索引) description_index(2byte 存储方法的返回类型和参数类型的常量表在常量池中的索引) attribute_count(2byte 属性表的数量) attribute (属性) 其中方法的属性JVM规定了四种: Code,Deprecated,Exceptions,Synthetic。 常量池的具体结构 在Java程序中,有很多的东西是永恒的,不会在运行过程中变化。比如一个类的名字,一个类字段的名字/所属类型,一个类方法的名字/返回类型/参数名与所属类型,一个常量,还有在程序中出现的大量的字面值。比如下面小段源码红色显示的东西。 public class ClassTest { ...

Created 15156-04-15 · Updated 9096-10-09 · 7 min · 1388 words · -

Java 各种数据类型转换

java 各种数据类型转换 String > Double String str="122.202"; double dnum = Double.parseDouble(str); hex > int // 默认hex 大端字节序 int decimal = Integer.parseInt(hexNumber, 16); list > array>set Set<T> mySet = new HashSet<>(Arrays.asList(someArray)); array > set, java 9+ Set<T> mySet = Set.of(someArray); array > set, java 10+ var mySet = Set.of(someArray); int > double Double d = new Double(i) double > int int i = d.intValue(); byte to binary string byte b1 = (byte) 129; String s1 = String.format("%8s", Integer.toBinaryString(b1 & 0xFF)).replace(' ', '0'); System.out.println(s1); // 10000001 date localdatetime Instant instant = date.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); instant.atZone(zoneId).toLocalDateTime(); int > bytes byte[] bytes = ByteBuffer.allocate(4).putInt(i).array(); public static byte[] int2Bytes(int value) { byte[] out = new byte[4]; for (int i = 0; i < 4; i++) { out[i] = (byte) (value >> (8 * (4 - i - 1))); } return out; } public static int bytes2Int(byte[] value) { int out = 0; if (value != null && value.length == 4) { for (int i = 0; i < 4; i++) { out |= (value[i] & 0xFF) << (8 * (4 - i - 1)); } } return out; } value & 0xFF It sets result to the (unsigned) value resulting from putting the 8 bits of value in the lowest 8 bits of result. ...

Created 29296-05-29 · Updated 9096-10-09 · 4 min · 686 words · -

Java web 乱码

Java web 乱码 http://www.jb51.net/web/12714.html (一) get提交 1.首先说下客户端 (浏览器) 的form表单用get方法是如何将数据编码后提交给服务器端的吧。 对于get方法来说,都是把数据串联在请求的url后面作为参数,如: http://localhost:8080/servlet?msg=abc (很常见的一个乱码问题就要出现了,如果url中出现中文或其它特殊字符的话,如: http://localhost:8080 /servlet?msg=杭州,服务器端容易得到乱码) ,url拼接完成后,浏览器会对url进行URL encode,然后发送给服务器,URL encode的过程就是把部分url做为字符,按照某种编码方式 (如: utf-8,gbk等) 编码成二进制的字节码,然后每个字节用一个包含3个字符的字符串 “%xy” 表示,其中xy为该字节的两位十六进制表示形式。我这里说的可能不清楚,具体介绍可以看下java.net.URLEncoder。了解了 URL encode的过程,我们能看到2个很重要的问题,第一: 需要URL encode的字符一般都是非ASCII的字符 (笼统的讲) ,再通俗的讲就是除了英文字母以外的文字 (如: 中文,日文等) 都要进行URL encode,所以对于我们来说,都是英文字母的url不会出现服务器得到乱码问题,出现乱码都是url里面带了中文或特殊字符造成的;第二: URL encode到底按照那种编码方式对字符编码?这里就是浏览器的事情了,而且不同的浏览器有不同的做法,中文版的浏览器一般会默认的使用GBK,通过设置浏览器也可以使用UTF-8,可能不同的用户就有不同的浏览器设置,也就造成不同的编码方式,所以很多网站的做法都是先把url里面的中文或特殊字符用 javascript做URL encode,然后再拼接url提交数据,也就是替浏览器做了URL encode,好处就是网站可以统一get方法提交数据的编码方式。 完成了URL encode,那么现在的url就成了ASCII范围内的字符了,然后以iso-8859-1的编码方式转换成二进制随着请求头一起发送出去。这里想多说几句的是,对于get方法来说,没有请求实体,含有数据的url都在请求头里面,之所以用URL encode,我个人觉的原因是: 对于请求头来说最终都是要用iso-8859-1编码方式编码成二进制的101010…..的纯数据在互联网上传送,如果直接将含有中文等特殊字符做iso-8859-1编码会丢失信息,所以先做URL encode是有必要的。 2。服务器端 (tomcat) 是如何将数据获取到进行解码的。 第一步是先把数据用iso-8859-1进行解码,对于get方法来说,tomcat获取数据的是ASCII范围内的请求头字符,其中的请求url里面带有参数数据,如果参数中有中文等特殊字符,那么目前还是URL encode后的%XY状态,先停下,我们先说下开发人员一般获取数据的过程。通常大家都是request.getParameter(“name”)获取参数数据,我们在request对象获得的数据都是经过解码过的,而解码过程中程序里是无法指定,这里要说下,有很多新手说用 request.setCharacterEncoding(“字符集”)可以指定解码方式,其实是不可以的,看servlet的官方API说明有对此方法的解释: Overrides the name of the character encoding used in the body of this request. This method must be called prior to reading request parameters or reading input using getReader().可以看出对于get方法他是无能为力的。那么到底用什么编码方式解码数据的呢,这是tomcat的事情了,默认缺省用的是 iso-8859-1,这样我们就能找到为什么get请求带中文参数为什么在服务器端得到乱码了,原因是在客户端一般都是用UTF-8或GBK对数据 URL encode,这里用iso-8859-1方式URL decoder显然不行,在程序里我们可以直接 ...

Created 3036-06-03 · Updated 9096-10-09 · 1 min · 151 words · -

JAVA 调试, JPDA, Java Debug

JAVA 调试, JPDA, Java Debug JPDA 三层:JDI(调试器 Java API)→ JDWP(线协议)→ JVMTI(JVM native)。JVMTI 专文见 JVMTI。开发期改已加载类(HotSwap)走本体系,与 BTrace attach 观测不是同一路;热替换总览见 开发期热替换。 远程调试 远程调试分为主动连接调试,和被动连接调试。这里以Eclipse为例。 主动连接调试:服务端配置监控端口,本地IDE连接远程监听端口进行调试,一般调试问题用这种方式。 被动连接调试:本地IDE监听某端口,等待远程连接本地端口。一般用于远程服务启动不了,启动时连接到本地调试分析。 主动连接调试 首先需要远程服务配置启动脚本: JAVA_OPTS="$JAVA_OPTS -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005" 如果是启动jar包,指令: java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 -jar test.jar JAVA 自身支持调试功能,并提供了一个简单的调试工具 - JDB, 类似于功能强大的 GDB,JDB 也是一个字符界面的调试环境,并支持设置断点,支持线程线级的调试。 JAVA的调试方法如下: 首先设置JVM,并设置参数,使之工作在 DEBUG 模式下,加入参数: -Xdebug -Xrunjdwp,transport=dt_socket,server=y,address=5432,suspend=n,onthrow=java.io.IOException,launch=/sbin/echo -Xdebug: 通知 JVM 工作在 DEBUG 模式下 -Xrunjdwp 是通知 JVM 使用 (java debug wire protocol) 来运行调试环境。该参数提供了一系列的调试选项: transport 指定调试数据的传送方式,dt_socket 是指用 SOCKET 模式,另有 dt_shmem 指用共享内存方式,其中,dt_shmem 只适用于 Windows 平台。 server 参数是指是否支持在 server 模式的 JVM 中. onthrow 当产生该类型的 Exception 时,JVM 就会中断下来,进行调式。(可选参数) launch 当JVM被中断下来时,执行的可执行程序。(可选参数) suspend: y/n 指明,是否在调试客户端建立起来后,再执行JVM。 onuncaught (=y或n) 指明出现 uncaught exception 后, 是否中断JVM的执行. address 端口 启动调试工具。 最简单的调试工具就是上面提到的JDB,以上述调试用JVM为例,可以用下面的命运行启动JDB: ...

Created 7076-06-07 · Updated 9096-10-09 · 8 min · 1625 words · -

ReentrantLock,互斥锁,重入锁

ReentrantLock,互斥锁,重入锁 在并发编程中,多线程同时并发访问的资源叫做临界资源,当多个线程同时访问对象并要求操作相同资源时,分割了原子操作就有可能出现数据的不一致或数据不完整的情况,为避免这种情况的发生,我们会采取同步机制,以确保在某一时刻,方法内只允许有一个线程。 synchronized 采用 synchronized 修饰符实现的同步机制叫做互斥锁机制,它所获得的锁叫做互斥锁。每个对象都有一个 monitor (锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其创建一个互斥锁,这个锁是为了分配给线程的,防止打断原子操作。每个对象的锁只能分配给一个线程,因此叫做互斥锁。 这里就使用同步机制获取互斥锁的情况,进行几点说明: 如果同一个方法内同时有两个或更多线程,则每个线程有自己的局部变量拷贝。 类的每个实例都有自己的对象级别锁。当一个线程访问实例对象中的 synchronized 同步代码块或同步方法时,该线程便获取了该实例的对象级别锁,其他线程这时如果要访问 synchronized 同步代码块或同步方法,便需要阻塞等待,直到前面的线程从同步代码块或方法中退出,释放掉了该对象级别锁。 访问同一个类的不同实例对象中的同步代码块,不存在阻塞等待获取对象锁的问题,因为它们获取的是各自实例的对象级别锁,相互之间没有影响。 持有一个对象级别锁不会阻止该线程被交换出来,也不会阻塞其他线程访问同一示例对象中的非synchronized代码。当一个线程A持有一个对象级别锁 (即进入了 synchronized 修饰的代码块或方法中) 时,线程也有可能被交换出去,此时线程B有可能获取执行该对象中代码的时间,但它只能执行非同步代码 (没有用synchronized修饰) ,当执行到同步代码时,便会被阻塞,此时可能线程规划器又让A线程运行,A线程继续持有对象级别锁,当A线程退出同步代码时 (即释放了对象级别锁) ,如果B线程此时再运行,便会获得该对象级别锁,从而执行synchronized中的代码。 持有对象级别锁的线程会让其他线程阻塞在所有的 synchronized 代码外。例如,在一个类中有三个synchronized方法 a,b,c,当线程A正在执行一个实例对象M中的方法 a 时,它便获得了该对象级别锁,那么其他的线程在执行同一实例对象 (即对象M) 中的代码时,便会在所有的 synchronized 方法处阻塞,即在方法 a,b,c处都要被阻塞,等线程A释放掉对象级别锁时,其他的线程才可以去执行方法a,b或者c中的代码,从而获得该对象级别锁。 使用 synchronized (obj) 同步语句块,可以获取指定对象上的对象级别锁。obj为对象的引用,如果获取了obj对象上的对象级别锁,在并发访问obj对象时时,便会在其synchronized代码处阻塞等待,直到获取到该obj对象的对象级别锁。当obj为 this 时,便是获取当前对象的对象级别锁。 类级别锁被特定类的所有示例共享,它用于控制对static成员变量以及static方法的并发访问。具体用法与对象级别锁相似。 互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式. synchronized 关键字经过编译后,会在同步块的前后分别形成 monitorenter 和 monitorexit 这两个字节码指令。根据虚拟机规范的要求,在执行 monitorenter 指令时,首先要尝试获取对象的锁,如果获得了锁,把锁的计数器加1,相应地,在执行 monitorexit 指令时会将锁计数器减1,当计数器为0时,锁便被释放了。由于synchronized 同步块对同一个线程是可重入的,因此一个线程可以多次获得同一个对象的互斥锁,同样,要释放相应次数的该互斥锁,才能最终释放掉该锁。 synchronized 是非公平锁 (TODO: 需要测试,没重现成功…) https://blog.csdn.net/FU250/article/details/106640613 ReentrantLock ReentrantLock 是一个可重入的互斥锁,又被称为"独占锁"。ReentrantLock 的实现不仅可以替代隐式的 synchronized 关键字,而且能够提供超过关键字本身的多种功能。 而锁的名字也是说明了这个锁具备了重复进入的可能,也就是说能够让当前线程多次的进行对锁的获取操作,这样的最大次数限制是 Integer.MAX_VALUE,约 21 亿次左右。 ...

Created 8086-07-08 · Updated 9096-10-09 · 5 min · 894 words · -

Java 正则表达式分组与捕获

JAVA 正则表达式 分组与捕获 import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Fenzhu { public static void main(String[] args) { Pattern p = Pattern.compile("(/d{3,5})([a-z]{2})"); String s = "123aa-34345bb-234cc-00"; Matcher m = p.matcher(s); while(m.find()) { System.out.println("m.group():"+m.group()); //打印所有 System.out.println("m.group(1):"+m.group(1)); //打印数字的 System.out.println("m.group(2):"+m.group(2)); //打印字母的 System.out.println(); } System.out.println("捕获个数:groupCount()="+m.groupCount()); } } 1 概述 1.1 什么是捕获组 捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用。当然,这种引用既可以是在正则表达式内部,也可以是在正则表达式外部。 捕获组有两种形式,一种是普通捕获组,另一种是命名捕获组,通常所说的捕获组指的是普通捕获组。语法如下: 普通捕获组: (Expression) 命名捕获组: (?<name>Expression) ...

Created 19196-08-19 · Updated 9096-10-09 · 4 min · 642 words · -

Java 线程池, thread pool, Executors, ThreadPoolExecutor

Java 线程池, thread pool, Executors, ThreadPoolExecutor Executors ThreadPoolExecutor ExecutorService 在 Java 1.5 引入 ExecutorService 之后,基本上已经不建议直接创建 Thread 对象,而是统一使用 ExecutorService。毕竟从接口的易用程度上来说 ExecutorService 就远胜于原始的 Thread,更不用提 java.util.concurrent 提供的数种线程池,Future 类,Lock 类等各种便利工具。 在操作系统中,线程是一个非常重要的资源,频繁创建和销毁线程会降低系统性能。Java 线程池原理类似于数据库连接池,目的就是帮助我们实现线程复用,减少频繁创建和销毁线程。ThreadPoolExecutor Executors Executors 提供了一些创建线程池的工具方法。 ExecutorService es = Executors.newSingleThreadExecutor() 源码实现: new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()) corePoolSize 和maximumPoolSize都为1,也就是创建了一个固定大小是1的线程池,workQueue是 new LinkedBlockingQueue<Runnable>() 也就是队列的大小是Integer.MAX_VALUE,可以认为是队列的大小不限制。 由此可以得出通过该方法创建的线程池,每次只能同时运行一个线程,当有多个任务同时提交时,那也要一个一个排队执行。 Executors.newFixedThreadPool(int nThreads) 源码实现: new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()) 类似Executors.newSingleThreadExecutor()也是创建了一个固定大小的线程池,但是可以指定同时运行的线程数量为nThreads。 Executors.newCachedThreadPool() 源码实现: new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()) corePoolSize为0,maximumPoolSize为Integer.MAX_VALUE可以视为无穷大,workQueue是一个SynchronousQueue。SynchronousQueue可以认为是一个长度限制为0的队列,也就是向这个队列添加任务会永远是已满的状态。 由此可以得出通过该方法创建的线程池并不限制线程数量,每次添加的任务都会直接执行而不会放入workQueue,它的主要提供的功能是线程复用,但不能控制线程数量。 ...

Created 26266-08-26 · Updated 9096-10-09 · 4 min · 721 words · -

ArrayBlockingQueue

ArrayBlockingQueue ArrayBlockingQueue 是一个基于数组的阻塞队列实现,此队列按 FIFO (先进先出) 原则对元素进行排序, 在 ArrayBlockingQueue 内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。 ArrayBlockingQueue 在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue, 按照实现原理来分析 ArrayBlockingQueue 完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea 之所以没这样去做,也许是因为 ArrayBlockingQueue 的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的 Node 对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于 GC 的影响还是存在一定的区别。而在创建 ArrayBlockingQueue 时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。 队列的头部是在队列中存在时间最长的元素。队列的尾部是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列检索操作则是从队列头部开始获得元素。 这是一个典型的 “有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致放入操作受阻塞;试图从空队列中检索元素将导致类似阻塞。 此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness) 设置为 true 而构造的队列允许按照 FIFO 顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了"不平衡性"。 ArrayBlockingQueue 在构造时需要指定容量,并可以选择是否需要公平性,如果公平参数被设置true,等待时间最长的线程会优先得到处理 (其实就是通过将 ReentrantLock 设置为 true 来达到这种公平性的: 即等待时间最长的线程会先操作) 。通常,公平性会使你在性能上付出代价,只有在的确非常需要的时候再使用它。它是基于数组的阻塞循环队列,此队列按 FIFO (先进先出) 原则对元素进行排序。 PriorityBlockingQueue PriorityBlockingQueue 是一个带优先级的队列,而不是先进先出队列。元素按优先级顺序被移除,该队列也没有上限 (看了一下源码,PriorityBlockingQueue 是对 PriorityQueue 的再次包装,是基于堆数据结构的,而 PriorityQueue 是没有容量限制的,与ArrayList一样,所以在优先阻塞队列上put时是不会受阻的。虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError) ,但是如果队列为空,那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,往入该队列中的元素要具有比较能力。 最后,DelayQueue (基于PriorityQueue来实现的) 是一个存放Delayed 元素的无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。如果延迟都还没有期满,则队列没有头部,并且poll将返回null。当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。 下面是延迟接口: ...

Created 26266-08-26 · Updated 9096-10-09 · 2 min · 386 words · -

Java Thread/线程

Java Thread/线程 java的线程是映射到操作系统原生线程之上的 java线程阻塞的代价 java的线程是映射到操作系统原生线程之上的,如果要阻塞或唤醒一个线程就需要操作系统介入,需要在户态与核心态之间切换,这种切换会消耗大量的系统资源,因为用户态与内核态都有各自专用的内存空间,专用的寄存器等,用户态切换至内核态需要传递给许多变量、参数给内核,内核也需要保护好用户态在切换时的一些寄存器值、变量等,以便内核态调用结束后切换回用户态继续工作。 如果线程状态切换是一个高频操作时,这将会消耗很多CPU处理时间; 如果对于那些需要同步的简单的代码块,获取锁挂起操作消耗的时间比用户代码执行的时间还要长,这种同步策略显然非常糟糕的。 线程状态 线程共包括以下5种状态。 新建状态 (New): 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。 就绪状态(Runnable): 也被称为 “可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: 等待阻塞 – 通过调用线程的 wait() 方法,让线程等待某工作的完成。 同步阻塞 – 线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。 其他阻塞 – 通过调用线程的 sleep() 或 join() 或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 死亡状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 这 5 种状态涉及到的内容包括 Object 类, Thread 类, 和 synchronized 关键字。 这些内容我们会在后面的章节中逐个进行学习。 Object 类,定义了 wait(), notify(), notifyAll( ) 等休眠/唤醒函数。 Thread 类,定义了一些列的线程操作函数。例如,sleep() 休眠函数, interrupt() 中断函数, getName() 获取线程名称等。 synchronized,是关键字;它区分为 synchronized 代码块和 synchronized 方法。 synchronized 的作用是让线程获取对象的同步锁。 在后面详细介绍 wait(), notify() 等方法时,我们会分析为什么 “wait(), notify() 等方法要定义在 Object 类,而不是 Thread 类中” ...

Created 22226-09-22 · Updated 9096-10-09 · 5 min · 866 words · -

Lambda 表达式

Lambda 与内部类 JDK 8 之前,要把「一段逻辑」当作参数传给方法,常见写法是匿名内部类——创建一个没有名字的类,并实现目标接口。Lambda 表达式针对其中最常见的一类场景做了简化:目标类型是函数式接口(通过 SAM 判定的接口)时,可以用 (参数) -> 表达式 代替整个匿名类。 同一件事,两种写法 以 Runnable 为例: // Runnable is an interface (not a class); SAM: single abstract method void run() // Left: Runnable r1 — variable type is the interface // Right: new Runnable() { ... } — not "new interface"; defines an anonymous class // that implements Runnable and overrides run(); compiler emits EnclosingClass$1.class Runnable r1 = new Runnable() { @Override public void run() { System.out.println("hello"); } }; // JDK 8 lambda; target type is still Runnable (functional interface) // Semantically same as the anonymous class above; often compiled via invokedynamic Runnable r2 = () -> System.out.println("hello"); 以 Comparator 为例(同样是「接口 + 匿名实现类」): ...

Created 17176-11-17 · Updated 9096-10-09 · 7 min · 1345 words · -