• 关于

    static关键字的引入

    的搜索结果

回答

PHP 5.3 之后,实现了延迟绑定。在之前的版本中,如果运行下面的代码:<?php abstract class DomainObject { public static function create() { return new self(); } } class User extends DomainObject { } class Document extends DomainObject { } $foo = Document::create();那么 $foo 得到的是一个 Domain 对象而不是 Document 的对象,这是由于之前的 PHP 版本中存在的一个问题,就是父类无法获得子类的状态,导致在继承时 self 这个值指向的是父类而非子类。如果要实现正常的功能的话,必须在子类中重写方法。后来在 PHP 5.3 中,引入了 static 来实现延迟绑定,只需要用 static 来替换 self 关键字即可。
落地花开啦 2019-12-02 02:50:32 0 浏览量 回答数 0

回答

一、Lambda表达式 Lambda表达式可以说是Java 8最大的卖点,她将函数式编程引入了Java。Lambda允许把函数作为一个方法的参数,或者把代码看成数据。 一个Lambda表达式可以由用逗号分隔的参数列表、–>符号与函数体三部分表示。例如: Arrays.asList( "p", "k", "u","f", "o", "r","k").forEach( e -> System.out.println( e ) ); 1 Arrays.asList( "p", "k", "u","f", "o", "r","k").forEach( e -> System.out.println( e ) ); 为了使现有函数更好的支持Lambda表达式,Java 8引入了函数式接口的概念。函数式接口就是只有一个方法的普通接口。java.lang.Runnable与java.util.concurrent.Callable是函数式接口最典型的例子。为此,Java 8增加了一种特殊的注解@FunctionalInterface: 1 @FunctionalInterface2 public interface Functional {3 void method();4 }二、接口的默认方法与静态方法 我们可以在接口中定义默认方法,使用default关键字,并提供默认的实现。所有实现这个接口的类都会接受默认方法的实现,除非子类提供的自己的实现。例如: 1 public interface DefaultFunctionInterface {2 default String defaultFunction() {3 return "default function";4 }5 }我们还可以在接口中定义静态方法,使用static关键字,也可以提供实现。例如: 1 public interface StaticFunctionInterface {2 static String staticFunction() {3 return "static function";4 }5 }接口的默认方法和静态方法的引入,其实可以认为引入了C++中抽象类的理念,以后我们再也不用在每个实现类中都写重复的代码了。 三、方法引用 通常与Lambda表达式联合使用,可以直接引用已有Java类或对象的方法。一般有四种不同的方法引用: 构造器引用。语法是Class::new,或者更一般的Class< T >::new,要求构造器方法是没有参数; 静态方法引用。语法是Class::static_method,要求接受一个Class类型的参数; 特定类的任意对象方法引用。它的语法是Class::method。要求方法是没有参数的; 特定对象的方法引用,它的语法是instance::method。要求方法接受一个参数,与3不同的地方在于,3是在列表元素上分别调用方法,而4是在某个对象上调用方法,将列表元素作为参数传入; 四、重复注解 在Java 5中使用注解有一个限制,即相同的注解在同一位置只能声明一次。Java 8引入重复注解,这样相同的注解在同一地方也可以声明多次。重复注解机制本身需要用@Repeatable注解。Java 8在编译器层做了优化,相同注解会以集合的方式保存,因此底层的原理并没有变化。 五、扩展注解的支持 Java 8扩展了注解的上下文,几乎可以为任何东西添加注解,包括局部变量、泛型类、父类与接口的实现,连方法的异常也能添加注解。 六、Optional Java 8引入Optional类来防止空指针异常,Optional类最先是由Google的Guava项目引入的。Optional类实际上是个容器:它可以保存类型T的值,或者保存null。使用Optional类我们就不用显式进行空指针检查了。 七、Stream Stream API是把真正的函数式编程风格引入到Java中。其实简单来说可以把Stream理解为MapReduce,当然Google的MapReduce的灵感也是来自函数式编程。她其实是一连串支持连续、并行聚集操作的元素。从语法上看,也很像linux的管道、或者链式编程,代码写起来简洁明了,非常酷帅! 八、Date/Time API (JSR 310) Java 8新的Date-Time API (JSR 310)受Joda-Time的影响,提供了新的java.time包,可以用来替代 java.util.Date和java.util.Calendar。一般会用到Clock、LocaleDate、LocalTime、LocaleDateTime、ZonedDateTime、Duration这些类,对于时间日期的改进还是非常不错的。 九、JavaScript引擎Nashorn Nashorn允许在JVM上开发运行JavaScript应用,允许Java与JavaScript相互调用。 十、Base64 在Java 8中,Base64编码成为了Java类库的标准。Base64类同时还提供了对URL、MIME友好的编码器与解码器。 除了这十大新特性之外,还有另外的一些新特性: 更好的类型推测机制:Java 8在类型推测方面有了很大的提高,这就使代码更整洁,不需要太多的强制类型转换了。 编译器优化:Java 8将方法的参数名加入了字节码中,这样在运行时通过反射就能获取到参数名,只需要在编译时使用-parameters参数。 并行(parallel)数组:支持对数组进行并行处理,主要是parallelSort()方法,它可以在多核机器上极大提高数组排序的速度。 并发(Concurrency):在新增Stream机制与Lambda的基础之上,加入了一些新方法来支持聚集操作。 Nashorn引擎jjs:基于Nashorn引擎的命令行工具。它接受一些JavaScript源代码为参数,并且执行这些源代码。 类依赖分析器jdeps:可以显示Java类的包级别或类级别的依赖。 JVM的PermGen空间被移除:取代它的是Metaspace(JEP 122)。
auto_answer 2019-12-02 01:55:13 0 浏览量 回答数 0

回答

在java8之前,接口中的所有方法都是abstract public的,java8引入了default关键字用来定义接口中的普通的有方法体的方法。所有实现了这个接口的类的实例都能直接执行这个普通方法,类似于继承。 // A simple program to Test Interface default // methods in java interface TestInterface { // abstract method public void square(int a); // default method default void show() { System.out.println("Default Method Executed"); } } class TestClass implements TestInterface { // implementation of square abstract method public void square(int a) { System.out.println(a*a); } public static void main(String args[]) { TestClass d = new TestClass(); d.square(4); // default method executed d.show(); } }
景凌凯 2020-04-12 22:59:41 0 浏览量 回答数 0

回答

首先,一些标准语: 6.7.5.3函数声明符(包括原型) ... 7参数声明为“ 类型数组”应调整为“ 类型的限定指针 ”,其中类型限定符(如果有)是指定的在[和]类型的数组派生。如果关键字static也出现在[和]的数组类型推导中,则对于函数的每次调用,相应实际参数的值应提供对数组第一个元素的访问,该元素的大小至少与size指定的数量相同表达。 简而言之,任何声明为T a[]或的函数参数T a[N]都将被视为声明T *a。 那么,为什么将数组参数当作声明为指针一样对待呢?原因如下: 6.3.2.1左值,数组和函数指示符 ... 3除非它是运算sizeof符或一元运算&符的操作数,或者是用于初始化数组的字符串文字,否则其类型为“ array type ' '转换为类型为“ pointer to type ” 的表达式,该表达式指向数组对象的初始元素,而不是左值。如果数组对象具有寄存器存储类,则该行为是不确定的。 给出以下代码: int main(void) { int arr[10]; foo(arr); ... } 在对的调用中foo,数组表达式arr不是sizeof或的操作数&,因此根据6.2.3.1/3 ,其类型从“的10个元素的数组int” 隐式转换为“指向的指针int”。这样,foo将接收一个指针值,而不是一个数组值。 由于6.7.5.3/7,你可以写foo为 void foo(int a[]) // or int a[10] { ... } 但它会被解释为 void foo(int *a) { ... } 因此,两种形式是相同的。 6.7.5.3/7中的最后一句话是C99引入的,基本上意味着如果您有一个参数声明,例如 void foo(int a[static 10]) { ... } 对应的实际参数a必须是至少包含 10个元素的数组。
保持可爱mmm 2020-02-08 10:55:28 0 浏览量 回答数 0

回答

字面值 Python在2008年引入二进制字面值。现在C++14也有了。【更新:Thiago Macieira在评论中指出,GCC实际上早在2007年就已经支持了。】`1 static const int primes = 0b10100000100010100010100010101100;`Python早在1998年引入了 原始字符串字面值。在硬编码正则表达式或Windows路径时很方便。 C++11也添加了同样的特性,只是语法上略有不同:`1 const char* path = R"(c:thisstringhasbackslashes)";`基于范围的For循环(Range-Based For Loops)在Python中,for循环总是迭代遍历一个Python对象: 1for x in myList: 2 print(x) 与此同时,在近30年里。C++仅支持C风格for循环。最后,在C++11中, 基于范围的for循环被添加进去。C++ 1for (int x : myList) 2 std::cout << x; 与Python迭代协议不同,你可以迭代一个 std::vector 或任何实现了begin和end成员函数的类。有了基于范围的for循环后,我经常发现自己希望C++能内建像Python的xrange函数一样的函数。自动化Python一直以来都是一个动态类型语言。你不需要声明变量类型,因为类型是对象本身的属性。`1x = "Hello world!"print(x)`从另一方面来说,C++不是动态类型语言。是静态类型。不过在C++11中将 auto 关键字 改作他用以用于类型推导,你能够写 看起来很像动态类型的代码:C++ 1 auto x = "Hello world!"; 2 std::cout << x; 当你调用重载几个类型的函数时,比如 std::ostream::operator<< 或者一个模板函数,C++更像一个动态类型语言。C++14进一步充实以支持auto关键字,为auto添加了 返回值支持和lambda函数 参数支持。元组 Python从一开始就很好的定义了元组类型。当你需要把几个值整合在一起的时候,元组就非常适合,这样就再不需要命名类来实现同样的功能了。 triple = (5, 6, 7) print(triple[0]) C++在C++11标准库中添加了对元组的支持。C++11的建议书 甚至还提到了这么做是受Python启发的:C++ auto triple = std::make_tuple(5, 6, 7); std::cout << std::get<0>(triple); Pyton允许你把一个元组解析为多个独立的变量:`x, y, z = triple`在C++里,你可以使用std::tie实现同样的功能:C++`std::tie(x, y, z) = triple;`统一的初始化 在Python里,列表是内置类型。因此,你可以只使用一个表达式来创建Python列表: myList = [6, 3, 7, 8] myList.append(5); C++的向量(std::vector)与Python的列表最为相似。如今,C++11里新增的 统一的初始化可以让我们只使用一个表达式来创建向量和列表了:C++ auto myList = std::vector<int>{ 6, 3, 7, 8 }; myList.push_back(5); 在Python里,你还可以只使用一个表达式来创建一个 字典: myDict = {5: "foo", 6: "bar"} print(myDict[5]) 与此类似,统一的初始化也适用于有序映射(std::map)和无序映射(unordered_map):C++ auto myDict = std::unordered_map<int, const char*>{ { 5, "foo" }, { 6, "bar" } }; std::cout << myDict[5]; Lambda表达式 Python从1994年开始支持lambda函数。`1 myList.sort(key = lambda x: abs(x))`Lambda表达式是在C++11中被添加进去。`1std::sort(myList.begin(), myList.end(), [](int x, int y){ return std::abs(x) < std::abs(y); });`2001年,Python添加了 静态嵌套作用域,可以让lambda函数抓取定义在封闭函数内的变量。 1def adder(amount): return lambda x: x + amount 2print(adder(5)(5)) 同样,C++ lambda表达式支持一堆灵活的 抓取规则,可以让你做相似的事情: auto adder(int amount) { return [=](int x){ return x + amount; }; } std::cout << adder(5)(5); 标准算法Python内建 filter 函数可以让你有选择的从一个列表中拷贝项(虽然列表解析是首先):`1result = filter(lambda x: x >= 0, myList)`C++11中 引入了 std::copy_if ,让你可以使用一个类似的、相当功能的类型: auto result = std::vector<int>{}; std::copy_if(myList.begin(), myList.end(), std::back_inserter(result), [](int x){ return x >= 0; }); 其他的C++ 算法模仿了Python的内建函数包括 transform、 any_of、 all_of, min 以及 max。即将到来的 范围提案有潜力进一步简化这些表达式。 参数打包Python 从 1988 年就开始支持任意长度的参数列表. 你可以定义一个函数接受任意数量的实参,Python 会将他们放到一个元组(tuple)中, 你还可以将这个元组重新展开为一个实参列表,并把他们传递进另一个函数: def foo(*args): return tuple(*args) ... triple = foo(5, 6, 7) C++11 引入了对 参数包(parameter packs) 的支持. 它类似于 Python 的任意长度参数列表,而不同于 C 风格的可变参数列表, 这个参数包有自己的标识符来表示整个实参序列。关键区别在于:在 C++ 中,这个参数包不能在运行时做为一个单独的对象来操作。你只能通过模板元编程技术在编译时来操纵他们。 template <typename... T> auto foo(T&&... args) { return std::make_tuple(args...); } ...auto triple = foo(5, 6, 7); 并非所有的 C++ 11 和 14 中的特性都借鉴于 Python。只是其中很大一部分特性看似如此。 Python 被认为是一种对使用者亲近友好的编程语言。随着时间的推移以及这些特性逐渐被其他语言借鉴,它其中一些特质也逐渐暗淡下来。
a123456678 2019-12-02 01:56:27 0 浏览量 回答数 0

回答

排查过程 debug 日志,发现本地代码和服务器代码不一致debug 启动了demo代码,发现slf4j启动时会绑定 log的时效,如果发现多个会打日志发现启动日志中,打印错误,绑定了多个日志 SLF4J: Class path contains multiple SLF4J bindings. SLF4J: Found binding in [jar:file:/WEB-INF/lib/logback-classic-1.2.3.9-struct.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:r.war/WEB-INF/lib/slf4j-log4j12-1.7.5.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: Found binding in [jar:file:twar/WEB-INF/lib/slf4j-nop-1.7.0.jar!/org/slf4j/impl/StaticLoggerBinder.class] SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation. SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder] 询问后得知,目前应用是用log4j 配置的,那么读取logback就是有问题的找到 logback-classic-1.2.3.9-struct.jar!/org/slf4j/impl/StaticLoggerBinder.class 的所有引入应用,发现forest 引进了forest exclude这个依赖 能力沉淀 遇到问题应该先谷歌的 不打印日志通常两个原因 slf4j 找错了实现:可以通过日志查看“binding”关键字解决log4j 或 logback 找错了配置文件:可以通过日志查看”加载的日志文件“解决 log4j 使用 Logger logger = Logger.getLogger();logger.info("xxx"); log4j 流程 LogManager 的static 代码块加载 log4j.xml 配置LogManager 根据入参构造一个Logger对象 Logger 对象遍历其中的appender进行打印 slf4j 使用 Logger logger = LoggerFactory.getLogger();logger.info("xxx"); slf4j 原理 StaticLoggerBinder.getSingleton().getLoggerFactory() 获取 LoggerFactory LoggerFactory 由 slf4j-log4j 这个依赖提供 接下来的流程和 log4j 一样
君浩 2020-03-31 00:19:26 0 浏览量 回答数 0

回答

实例说明   用冒泡排序方法对数组进行排序。 实例解析   交换排序的基本思想是两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录为止。   应用交换排序基本思想的主要排序方法有冒泡排序和快速排序。 冒泡排序   将被排序的记录数组 R[1..n] 垂直排列,每个记录 R[i] 看做是重量为 R[i].key 的气泡。根据轻气泡不能在重气泡之下的原则,从下往上扫描数组 R 。凡扫描到违反本原则的轻气泡,就使其向上“漂浮”。如此反复进行,直到最后任何两个气泡都是轻者在上,重者在下为止。   (1) 初始, R[1..n] 为无序区。   (2) 第一趟扫描,从无序区底部向上依次比较相邻的两个气泡的重量,若发现轻者在下、重者在上,则交换二者的位置。即依次比较 (R[n],R[n-1]) 、 (R[n-1],R[n-2]) 、 … 、 (R[2],R[1]); 对于每对气泡 (R[j+1],R[j]), 若 R[j+1].key<R[j].key, 则交换 R[j+1] 和 R[j] 的内容。   第一趟扫描完毕时,“最轻”的气泡就飘浮到该区间的顶部,即关键字最小的记录被放在最高位置 R[1] 上。   (3) 第二趟扫描,扫描 R[2..n]。扫描完毕时,“次轻”的气泡飘浮到 R[2] 的位置上 …… 最后,经过 n-1 趟扫描可得到有序区 R[1..n]。   注意:第 i 趟扫描时, R[1..i-1] 和 R[i..n] 分别为当前的有序区和无序区。扫描仍是从无序区底部向上直至该区顶部。扫描完毕时,该区中最轻气泡漂浮到顶部位置 R[i] 上,结果是 R[1..i] 变为新的有序区。 冒泡排序算法   因为每一趟排序都使有序区增加了一个气泡,在经过 n-1 趟排序之后,有序区中就有 n-1 个气泡,而无序区中气泡的重量总是大于等于有序区中气泡的重量,所以整个冒泡排序过程至多需要进行 n-1 趟排序。   若在某一趟排序中未发现气泡位置的交换,则说明待排序的无序区中所有气泡均满足轻者在上,重者在下的原则,因此,冒泡排序过程可在此趟排序后终止。为此,在下面给出的算法中,引入一个布尔量 exchange, 在每趟排序开始前,先将其置为 FALSE 。若排序过程中发生了交换,则将其置为 TRUE 。各趟排序结束时检查 exchange, 若未曾发生过交换则终止算法,不再进行下趟排序。  具体算法如下: void BubbleSort(SeqList R){   //R(1..n) 是待排序的文件,采用自下向上扫描,对 R 做冒泡排序   int i,j;   Boolean exchange; // 交换标志   for(i=1;i<n;i++){ // 最多做 n-1 趟排序     exchange=FALSE; // 本趟排序开始前,交换标志应为假     for(j=n-1;j>=i;j--) // 对当前无序区 R[i..n] 自下向上扫描       if(R[j+1].key<R[j].key){ // 交换记录         R[0]=R[j+1]; //R[0] 不是哨兵,仅做暂存单元         R[j+1]=R[j];         R[j]=R[0];         exchange=TRUE; // 发生了交换,故将交换标志置为真       }     if(!exchange) // 本趟排序未发生交换,提前终止算法       return;   } //endfor( 外循环 ) }//BubbleSort  public class BubbleSort {   public static void main(String[] args) { // TODO Auto-generated method stub     List<Integer> lstInteger = new ArrayList<Integer>();         lstInteger.add(1);         lstInteger.add(1);         lstInteger.add(3);         lstInteger.add(2);         lstInteger.add(1);         for(int i = 0; i<lstInteger.size(); i++){             System.out.println(lstInteger.get(i));         }         System.out.println("排序之后-----------------");         lstInteger = sortList(lstInteger);         for(int i = 0; i<lstInteger.size(); i++){             System.out.println(lstInteger.get(i));         }   }       public static List<Integer> sortList(List<Integer> lstInteger){         int i,j,m;         boolean blChange;         int n = lstInteger.size();           for(i=0;i<n;i++){             blChange = false;             for(j = n-1; j>i ; j-- ){                 if(lstInteger.get(j)<lstInteger.get(j-1)){                     m = lstInteger.get(j-1);                     lstInteger.set(j-1, lstInteger.get(j));                     lstInteger.set(j, m);                     blChange = true;                 }             }             if(!blChange){                 return lstInteger;             }         }         return lstInteger;     } } 归纳注释    算法的最好时间复杂度: 若文件的初始状态是正序的, 一趟扫描即可完成排序。所需的关键字比较次数 C 和记录移动次数 M 均达到最小值,即 C(min)=n-1, M(min)= 0 。冒泡排序最好的时间复杂度为 O(n)。    算法的最坏时间复杂度: 若初始文件是反序的,需要进行 n-1 趟排序。每趟排序要进行 n-1 次关键字的比较 (1<=i<=n-1), 且每次比较都必须移动记录 3 次。在这种情况下,比较和移动次数均达到最大值,即 C(max)=n(n-1)/2=O(n ^2 ),M(max)=3n(n-1)/2=O(n ^2 )。冒泡排序的最坏时间复杂度为 O(n^2 )。    算法的平均时间复杂度为 O(n^2 )。虽然冒泡排序不一定要进行 n-1 趟,但由于它的记录移动次数较多,故平均时间性能比直接插入排序要差得多。    算法稳定性:冒泡排序是就地排序,且它是稳定的。    算法改进:上述的冒泡排序还可做如下的改进,① 记住最后一次交换发生位置 lastExchange 的冒泡排序( 该位置之前的相邻记录均已有序 )。下一趟排序开始时,R[1..lastExchange-1] 是有序区, R[lastExchange..n] 是无序区。这样,一趟排序可能使当前有序区扩充多个记录,从而减少排序的趟数。② 改变扫描方向的冒泡排序。冒泡排序具有不对称性。能一趟扫描完成排序的情况,只有最轻的气泡位于 R[n] 的位置,其余的气泡均已排好序,那么也只需一趟扫描就可以完成排序。如对初始关键字序列 12、18、42、44、45、67、94、10 就仅需一趟扫描。需要 n-1 趟扫描完成排序情况,当只有最重的气泡位于 R[1] 的位置,其余的气泡均已排好序时,则仍需做 n-1 趟扫描才能完成排序。比如对初始关键字序列:94、10、12、18、42、44、45、67 就需 7 趟扫描。造成不对称性的原因是每趟扫描仅能使最重气泡“下沉”一个位置,因此使位于顶端的最重气泡下沉到底部时,需做 n-1 趟扫描。在排序过程中交替改变扫描方向,可改进不对称性
寒凝雪 2019-12-02 01:18:46 0 浏览量 回答数 0

回答

毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。在本文中我们将学习这些新特性,并用实际的例子说明在什么场景下适合使用。 这个教程包含Java开发者经常面对的几类问题: 语言 编译器 库 工具 运行时(JVM) 2. Java语言的新特性 Java 8是Java的一个重大版本,有人认为,虽然这些新特性领Java开发人员十分期待,但同时也需要花不少精力去学习。在这一小节中,我们将介绍Java 8的大部分新特性。 2.1 Lambda表达式和函数式接口 Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。 Lambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如: Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) ); 在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如: Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) ); 如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如: Arrays.asList( "a", "b", "d" ).forEach( e -> { System.out.print( e ); System.out.print( e ); } ); Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同: String separator = ","; Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.print( e + separator ) ); 和 final String separator = ","; Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.print( e + separator ) ); Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同: Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) ); 和 Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> { int result = e1.compareTo( e2 ); return result; } ); Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义: @FunctionalInterface public interface Functional { void method(); } 不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。 @FunctionalInterface public interface FunctionalDefaultMethods { void method(); default void defaultMethod() { } } Lambda表达式作为Java 8的最大卖点,它有潜力吸引更多的开发者加入到JVM平台,并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节,可以参考官方文档。 2.2 接口的默认方法和静态方法 Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点类似traits,不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。 默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下: private interface Defaulable { // Interfaces now allow default methods, the implementer may or // may not implement (override) them. default String notRequired() { return "Default implementation"; } } private static class DefaultableImpl implements Defaulable { } private static class OverridableImpl implements Defaulable { @Override public String notRequired() { return "Overridden implementation"; } } Defaulable接口使用关键字default定义了一个默认方法notRequired()。DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。 Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下: private interface DefaulableFactory { // Interfaces now allow static methods static Defaulable create( Supplier< Defaulable > supplier ) { return supplier.get(); } } 下面的代码片段整合了默认方法和静态方法的使用场景: public static void main( String[] args ) { Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new ); System.out.println( defaulable.notRequired() ); defaulable = DefaulableFactory.create( OverridableImpl::new ); System.out.println( defaulable.notRequired() ); } 这段代码的输出结果如下: Default implementation Overridden implementation 由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()、parallelStream()、forEach()和removeIf()等等。 尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档。 2.3 方法引用 方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。 西门的例子中,Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用。 public static class Car { public static Car create( final Supplier< Car > supplier ) { return supplier.get(); } public static void collide( final Car car ) { System.out.println( "Collided " + car.toString() ); } public void follow( final Car another ) { System.out.println( "Following the " + another.toString() ); } public void repair() { System.out.println( "Repaired " + this.toString() ); } } 第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class ::new。注意:这个构造器没有参数。 final Car car = Car.create( Car::new ); final List< Car > cars = Arrays.asList( car ); 第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。 cars.forEach( Car::collide ); 第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参: cars.forEach( Car::repair ); 第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数: final Car police = Car.create( Car::new ); cars.forEach( police::follow ); 运行上述例子,可以在控制台看到如下输出(Car实例可能不同): Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d 如果想了解和学习更详细的内容,可以参考官方文档 2.4 重复注解 自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。 在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明: package com.javacodegeeks.java8.repeatable.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; public class RepeatingAnnotations { @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) public @interface Filters { Filter[] value(); } @Target( ElementType.TYPE ) @Retention( RetentionPolicy.RUNTIME ) @Repeatable( Filters.class ) public @interface Filter { String value(); }; @Filter( "filter1" ) @Filter( "filter2" ) public interface Filterable { } public static void main(String[] args) { for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) { System.out.println( filter.value() ); } } } 正如我们所见,这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节。这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。 另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示: filter1 filter2 如果你希望了解更多内容,可以参考官方文档。 2.5 更好的类型推断 Java 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。例子代码如下: package com.javacodegeeks.java8.type.inference; public class Value< T > { public static< T > T defaultValue() { return null; } public T getOrDefault( T value, T defaultValue ) { return ( value != null ) ? value : defaultValue; } } 下列代码是Value 类型的应用: package com.javacodegeeks.java8.type.inference; public class TypeInference { public static void main(String[] args) { final Value< String > value = new Value<>(); value.getOrDefault( "22", Value.defaultValue() ); } } 参数Value.defaultValue()的类型由编译器推导得出,不需要显式指明。在Java 7中这段代码会有编译错误,除非使用Value. defaultValue()。 2.6 拓宽注解的应用场景 Java 8拓宽了注解的应用场景。现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。下面是一些例子: package com.javacodegeeks.java8.annotations; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.Collection; public class Annotations { @Retention( RetentionPolicy.RUNTIME ) @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } ) public @interface NonEmpty { } public static class Holder< @NonEmpty T > extends @NonEmpty Object { public void method() throws @NonEmpty Exception { } } @SuppressWarnings( "unused" ) public static void main(String[] args) { final Holder< String > holder = new @NonEmpty Holder< String >(); @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>(); } } ElementType.TYPE_USER和ElementType.TYPE_PARAMETER是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。 Java编译器的新特性 3.1 参数名称 为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer liberary。Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及-parameters参数)提供支持。 package com.javacodegeeks.java8.parameter.names; import java.lang.reflect.Method; import java.lang.reflect.Parameter; public class ParameterNames { public static void main(String[] args) throws Exception { Method method = ParameterNames.class.getMethod( "main", String[].class ); for( final Parameter parameter: method.getParameters() ) { System.out.println( "Parameter: " + parameter.getName() ); } } } 在Java 8中这个特性是默认关闭的,因此如果不带-parameters参数编译上述代码并运行,则会输出如下结果: Parameter: arg0 如果带-parameters参数,则会输出如下结果(正确的结果): Parameter: args 如果你使用Maven进行项目管理,则可以在maven-compiler-plugin编译器的配置项中配置-parameters参数: org.apache.maven.plugins maven-compiler-plugin 3.1 -parameters 1.8 1.8 4. Java官方库的新特性 Java 8增加了很多新的工具类(date/time类),并扩展了现存的工具类,以支持现代的并发编程、函数式编程等。 4.1 Optional Java应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。 Optional仅仅是一个容易:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查,可以参考Java 8官方文档了解更多细节。 接下来看一点使用Optional的例子:可能为空的值或者某个类型的值: Optional< String > fullName = Optional.ofNullable( null ); System.out.println( "Full Name is set? " + fullName.isPresent() ); System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) ); 如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。 上述代码的输出结果如下: Full Name is set? false Full Name: [none] Hey Stranger! 再看下另一个简单的例子: Optional< String > firstName = Optional.of( "Tom" ); System.out.println( "First Name is set? " + firstName.isPresent() ); System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) ); System.out.println(); 这个例子的输出是: First Name is set? true First Name: Tom Hey Tom! 如果想了解更多的细节,请参考官方文档。 4.2 Streams 新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。 Steam API极大得简化了集合操作(后面我们会看到不止是集合),首先看下这个叫Task的类: public class Streams { private enum Status { OPEN, CLOSED }; private static final class Task { private final Status status; private final Integer points; Task( final Status status, final Integer points ) { this.status = status; this.points = points; } public Integer getPoints() { return points; } public Status getStatus() { return status; } @Override public String toString() { return String.format( "[%s, %d]", status, points ); } } } Task类有一个分数(或伪复杂度)的概念,另外还有两种状态:OPEN或者CLOSED。现在假设有一个task集合: final Collection< Task > tasks = Arrays.asList( new Task( Status.OPEN, 5 ), new Task( Status.OPEN, 13 ), new Task( Status.CLOSED, 8 ) ); 首先看一个问题:在这个task集合中一共有多少个OPEN状态的点?在Java 8之前,要解决这个问题,则需要使用foreach循环遍历task集合;但是在Java 8中可以利用steams解决:包括一系列元素的列表,并且支持顺序和并行处理。 // Calculate total points of all active tasks using sum() final long totalPointsOfOpenTasks = tasks .stream() .filter( task -> task.getStatus() == Status.OPEN ) .mapToInt( Task::getPoints ) .sum(); System.out.println( "Total points: " + totalPointsOfOpenTasks ); 运行这个方法的控制台输出是: Total points: 18 这里有很多知识点值得说。首先,tasks集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有CLOSED的task;第三,mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。 在学习下一个例子之前,还需要记住一些steams(点此更多细节)的知识点。Steam之上的操作可分为中间操作和晚期操作。 中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。 晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。 steam的另一个价值是创造性地支持并行处理(parallel processing)。对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和: // Calculate total points of all tasks final double totalPoints = tasks .stream() .parallel() .map( task -> task.getPoints() ) // or map( Task::getPoints ) .reduce( 0, Integer::sum ); System.out.println( "Total points (all tasks): " + totalPoints ); 这里我们使用parallel方法并行处理所有的task,并使用reduce方法计算最终的结果。控制台输出如下: Total points(all tasks): 26.0 对于一个集合,经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务,代码如下: // Group tasks by their status final Map< Status, List< Task > > map = tasks .stream() .collect( Collectors.groupingBy( Task::getStatus ) ); System.out.println( map ); 控制台的输出如下: {CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]} 最后一个关于tasks集合的例子问题是:如何计算集合中每个任务的点数在集合中所占的比重,具体处理的代码如下: // Calculate the weight of each tasks (as percent of total points) final Collection< String > result = tasks .stream() // Stream< String > .mapToInt( Task::getPoints ) // IntStream .asLongStream() // LongStream .mapToDouble( points -> points / totalPoints ) // DoubleStream .boxed() // Stream< Double > .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream .mapToObj( percentage -> percentage + "%" ) // Stream< String> .collect( Collectors.toList() ); // List< String > System.out.println( result ); 控制台输出结果如下: [19%, 50%, 30%] 最后,正如之前所说,Steam API不仅可以作用于Java集合,传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理,这里有一个小例子: final Path path = new File( filename ).toPath(); try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) { lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println ); } Stream的方法onClose 返回一个等价的有额外句柄的Stream,当Stream的close()方法被调用的时候这个句柄会被执行。Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用,是Java 8对软件开发的现代范式的响应。 4.3 Date/Time API(JSR 310) Java 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。 因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。 我们接下来看看java.time包中的关键类和各自的使用例子。首先,Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()和TimeZone.getDefault()。 // Get the system clock as UTC offset final Clock clock = Clock.systemUTC(); System.out.println( clock.instant() ); System.out.println( clock.millis() ); 这个例子的输出结果是: 2014-04-12T15:19:29.282Z 1397315969360 第二,关注下LocalDate和LocalTime类。LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。 // Get the local date and local time final LocalDate date = LocalDate.now(); final LocalDate dateFromClock = LocalDate.now( clock ); System.out.println( date ); System.out.println( dateFromClock ); // Get the local date and local time final LocalTime time = LocalTime.now(); final LocalTime timeFromClock = LocalTime.now( clock ); System.out.println( time ); System.out.println( timeFromClock ); 上述例子的输出结果如下: 2014-04-12 2014-04-12 11:25:54.568 15:25:54.568 LocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息。这里有一些关于LocalDate和LocalTime的例子: // Get the local date/time final LocalDateTime datetime = LocalDateTime.now(); final LocalDateTime datetimeFromClock = LocalDateTime.now( clock ); System.out.println( datetime ); System.out.println( datetimeFromClock ); 上述这个例子的输出结果如下: 2014-04-12T11:37:52.309 2014-04-12T15:37:52.309 如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子: // Get the zoned date/time final ZonedDateTime zonedDatetime = ZonedDateTime.now(); final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock ); final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) ); System.out.println( zonedDatetime ); System.out.println( zonedDatetimeFromClock ); System.out.println( zonedDatetimeFromZone ); 这个例子的输出结果是: 2014-04-12T11:47:01.017-04:00[America/New_York] 2014-04-12T15:47:01.017Z 2014-04-12T08:47:01.017-07:00[America/Los_Angeles] 最后看下Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同,例子代码如下: // Get duration between two dates final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 ); final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 ); final Duration duration = Duration.between( from, to ); System.out.println( "Duration in days: " + duration.toDays() ); System.out.println( "Duration in hours: " + duration.toHours() ); 这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下: Duration in days: 365 Duration in hours: 8783 对于Java 8的新日期时间的总体印象还是比较积极的,一部分是因为Joda-Time的积极影响,另一部分是因为官方终于听取了开发人员的需求。如果希望了解更多细节,可以参考官方文档。 4.4 Nashorn JavaScript引擎 Java 8提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本,这类Script引擎遵循相同的规则,允许Java和JavaScript交互使用,例子代码如下: ScriptEngineManager manager = new ScriptEngineManager(); ScriptEngine engine = manager.getEngineByName( "JavaScript" ); System.out.println( engine.getClass().getName() ); System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) ); 这个代码的输出结果如下: jdk.nashorn.api.scripting.NashornScriptEngine Result: 2 4.5 Base64 对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下: package com.javacodegeeks.java8.base64; import java.nio.charset.StandardCharsets; import java.util.Base64; public class Base64s { public static void main(String[] args) { final String text = "Base64 finally in Java 8!"; final String encoded = Base64 .getEncoder() .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) ); System.out.println( encoded ); final String decoded = new String( Base64.getDecoder().decode( encoded ), StandardCharsets.UTF_8 ); System.out.println( decoded ); } } 这个例子的输出结果如下: QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ== Base64 finally in Java 8! 新的Base64API也支持URL和MINE的编码解码。 (Base64.getUrlEncoder() / Base64.getUrlDecoder(), Base64.getMimeEncoder() / Base64.getMimeDecoder())。 4.6 并行数组 Java8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法: package com.javacodegeeks.java8.parallel.arrays; import java.util.Arrays; import java.util.concurrent.ThreadLocalRandom; public class ParallelArrays { public static void main( String[] args ) { long[] arrayOfLong = new long [ 20000 ]; Arrays.parallelSetAll( arrayOfLong, index -> ThreadLocalRandom.current().nextInt( 1000000 ) ); Arrays.stream( arrayOfLong ).limit( 10 ).forEach( i -> System.out.print( i + " " ) ); System.out.println(); Arrays.parallelSort( arrayOfLong ); Arrays.stream( arrayOfLong ).limit( 10 ).forEach( i -> System.out.print( i + " " ) ); System.out.println(); } } 上述这些代码使用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是: Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 Sorted: 39 220 263 268 325 607 655 678 723 793 4.7 并发性 基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作(更多内容可以参考我们的并发编程课程)。 Java 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)。 在java.util.concurrent.atomic包中也新增了不少工具类,列举如下: DoubleAccumulator DoubleAdder LongAccumulator LongAdder 5. 新的Java工具 Java 8提供了一些新的命令行工具,这部分会讲解一些对开发者最有用的工具。 5.1 Nashorn引擎:jjs jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。例如,我们写一个func.js文件,内容如下: function f() { return 1; }; print( f() + 1 ); 可以在命令行中执行这个命令:jjs func.js,控制台输出结果是: 2 如果需要了解细节,可以参考官方文档。 5.2 类依赖分析器:jdeps jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。 我们可以利用jedps分析下Spring Framework库,为了让结果少一点,仅仅分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar。 jdeps org.springframework.core-3.0.5.RELEASE.jar 这个命令会输出很多结果,我们仅看下其中的一部分:依赖关系按照包分组,如果在classpath上找不到依赖,则显示"not found". org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar) -> java.io -> java.lang -> java.lang.annotation -> java.lang.ref -> java.lang.reflect -> java.util -> java.util.concurrent -> org.apache.commons.logging not found -> org.springframework.asm not found -> org.springframework.asm.commons not found org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar) -> java.lang -> java.lang.annotation -> java.lang.reflect -> java.util 更多的细节可以参考官方文档。 JVM的新特性 使用Metaspace(JEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。 结论 通过为开发者提供很多能够提高生产力的特性,Java 8使得Java平台前进了一大步。现在还不太适合将Java 8应用在生产系统中,但是在之后的几个月中Java 8的应用率一定会逐步提高(PS:原文时间是2014年5月9日,现在在很多公司Java 8已经成为主流,我司由于体量太大,现在也在一点点上Java 8,虽然慢但是好歹在升级了)。作为开发者,现在应该学习一些Java 8的知识,为升级做好准备。 关于Spring:对于企业级开发,我们也应该关注Spring社区对Java 8的支持,可以参考这篇文章——Spring 4支持的Java 8新特性一览 参考资料 What’s New in JDK 8 The Java Tutorials WildFly 8, JDK 8, NetBeans 8, Java EE Java 8 Tutorial JDK 8 Command-line Static Dependency Checker The Illuminating Javadoc of JDK The Dark Side of Java 8 Installing Java™ 8 Support in Eclipse Kepler SR2 Java 8 Oracle Nashorn. A Next-Generation JavaScript Engine for the JVM 举报
游客2q7uranxketok 2021-02-08 10:54:06 0 浏览量 回答数 0

问题

Java 8 Lambda限制:报错

Java 8 Lambda限制:闭包 Java 8 Lambda表达式从哪里来? Java 8 Lambda限制 JavaScript及其功能 Java创建闭包? 副作用和JavaScript 副作...
kun坤 2020-06-08 11:12:26 4 浏览量 回答数 1

回答

、No route info of this topic 无法找到路由信息,其完整的错误堆栈信息如下: 在这里插入图片描述而且很多读者朋友会说Broker端开启了自动创建主题也会出现上述问题。 RocketMQ的路由寻找流程如下图所示: 在这里插入图片描述上面的核心关键点如下: 如果Broker开启了自动创建Topic,在启动的时候会默认创建主题:TBW102,并会随着Broker发送到Nameserver的心跳包汇报给Nameserver,继而从Nameserver查询路由信息时能返回路由信息。 消息发送者在消息发送时首先会查本地缓存,如果本地缓存中存在,直接返回路由信息。 如果缓存不存在,则向Nameserver查询路由信息,如果Nameserver存在该路由信息,就直接返回。 如果Nameserver不存在该topic的路由信息,如果没有开启自动创建主题,则抛出 No route info of this topic。 如果开启了自动创建主题,则使用默认主题向Nameserver查询路由信息,并使用默认Topic的路由信息为自己的路由信息,将不会抛出 No route info of this topic。 通常情况下 No route info of this topic 这个错误一般是在刚搭建RocketMQ,刚入门 RocketMQ遇到的比较多,通常的排查思路如下: 可以通过rocketmq-console查询路由信息是否存在,或使用如下命令查询路由信息: cd ${ROCKETMQ_HOME}/bin sh ./mqadmin topicRoute -n 127.0.0.1:9876 -t dw_test_0003 1 2 其输出结果如下所示: 在这里插入图片描述 如果通过命令无法查询到路由信息,则查看Broker是否开启了自动创建topic,参数为:autoCreateTopicEnable,该参数默认为true。但在生产环境不建议开启。 如果开启了自动创建路由信息,但还是抛出这个错误,这个时候请检查客户端(Producer)连接的Nameserver地址是否与Broker中配置的nameserver地址是否一致。 经过上面的步骤,基本就能解决该错误。 2、消息发送超时 消息发送超时,通常客户端的日志如下: 在这里插入图片描述 客户端报消息发送超时,通常第一怀疑的对象是RocketMQ服务器,是不是Broker性能出现了抖动,无法抗住当前的量。 那我们如何来排查RocketMQ当前是否有性能瓶颈呢? 首先我们执行如下命令查看RocketMQ 消息写入的耗时分布情况: cd /${USER.HOME}/logs/rocketmqlogs/ grep -n 'PAGECACHERT' store.log | more 1 2 输出结果如下所示: 在这里插入图片描述 RocketMQ会每一分钟打印前一分钟内消息发送的耗时情况分布,我们从这里就能窥探RocketMQ消息写入是否存在明细的性能瓶颈,其区间如下: [<=0ms] 小于0ms,即微妙级别的。 [0~10ms] 小于10ms的个数。 [10~50ms] 大于10ms小 于50ms的个数 其他区间显示,绝大多数会落在微妙级别完成,按照笔者的经验如果100-200ms及以上的区间超过20个后,说明Broker确实存在一定的瓶颈,如果只是少数几个,说明这个是内存或pagecache的抖动,问题不大。 通常情况下超时通常与Broker端的处理能力关系不大,还有另外一个佐证,在RocketMQ broker中还存在快速失败机制,即当Broker收到客户端的请求后会将消息先放入队列,然后顺序执行,如果一条消息队列中等待超过200ms就会启动快速失败,向客户端返回[TIMEOUT_CLEAN_QUEUE]broker busy,这个在本文的第3部分会详细介绍。 在RocketMQ客户端遇到网络超时,通常可以考虑一些应用本身的垃圾回收,是否由于GC的停顿时间导致的消息发送超时,这个我在测试环境进行压力测试时遇到过,但生产环境暂时没有遇到过,大家稍微留意一下。 在RocketMQ中通常遇到网络超时,通常与网络的抖动有关系,但由于我对网络不是特别擅长,故暂时无法找到直接证据,但能找到一些间接证据,例如在一个应用中同时连接了kafka、RocketMQ集群,发现在出现超时的同一时间发现连接到RocketMQ集群内所有Broker,连接到kafka集群都出现了超时。 但出现网络超时,我们总得解决,那有什么解决方案吗? 我们对消息中间件的最低期望就是高并发低延迟,从上面的消息发送耗时分布情况也可以看出RocketMQ确实符合我们的期望,绝大部分请求都是在微妙级别内,故我给出的方案时,减少消息发送的超时时间,增加重试次数,并增加快速失败的最大等待时长。具体措施如下: 增加Broker端快速失败的时长,建议为1000,在broker的配置文件中增加如下配置: maxWaitTimeMillsInQueue=1000 1 主要原因是在当前的RocketMQ版本中,快速失败导致的错误为SYSTEM_BUSY,并不会触发重试,适当增大该值,尽可能避免触发该机制,详情可以参考本文第3部分内容,会重点介绍system_busy、broker_busy。 如果RocketMQ的客户端版本为4.3.0以下版本(不含4.3.0) 将超时时间设置消息发送的超时时间为500ms,并将重试次数设置为6次(这个可以适当进行调整,尽量大于3),其背后的哲学是尽快超时,并进行重试,因为发现局域网内的网络抖动是瞬时的,下次重试的是就能恢复,并且RocketMQ有故障规避机制,重试的时候会尽量选择不同的Broker,相关的代码如下: DefaultMQProducer producer = new DefaultMQProducer("dw_test_producer_group"); producer.setNamesrvAddr("127.0.0.1:9876"); producer.setRetryTimesWhenSendFailed(5);// 同步发送模式:重试次数 producer.setRetryTimesWhenSendAsyncFailed(5);// 异步发送模式:重试次数 producer.start(); producer.send(msg,500);//消息发送超时时间 1 2 3 4 5 6 如果RocketMQ的客户端版本为4.3.0及以上版本 如果客户端版本为4.3.0及其以上版本,由于其设置的消息发送超时时间为所有重试的总的超时时间,故不能直接通过设置RocketMQ的发送API的超时时间,而是需要对其API进行包装,重试需要在外层收到进行,例如示例代码如下: public static SendResult send(DefaultMQProducer producer, Message msg, int retryCount) { Throwable e = null; for(int i =0; i < retryCount; i ++ ) { try { return producer.send(msg,500); //设置超时时间,为500ms,内部有重试机制 } catch (Throwable e2) { e = e2; } } throw new RuntimeException("消息发送异常",e); } 1 2 3 4 5 6 7 8 9 10 11 12 3、System busy、Broker busy 在使用RocketMQ中,如果RocketMQ集群达到1W/tps的压力负载水平,System busy、Broker busy就会是大家经常会遇到的问题。例如如下图所示的异常栈。 在这里插入图片描述纵观RocketMQ与system busy、broker busy相关的错误关键字,总共包含如下5个: [REJECTREQUEST]system busy too many requests and system thread pool busy [PC_SYNCHRONIZED]broker busy [PCBUSY_CLEAN_QUEUE]broker busy [TIMEOUT_CLEAN_QUEUE]broker busy 3.1 原理分析 我们先用一张图来阐述一下在消息发送的全生命周期中分别在什么时候会抛出上述错误。 在这里插入图片描述 根据上述5类错误日志,其触发的原有可以归纳为如下3种。 pagecache压力较大 其中如下三类错误属于此种情况 [REJECTREQUEST]system busy [PC_SYNCHRONIZED]broker busy [PCBUSY_CLEAN_QUEUE]broker busy 判断pagecache是否忙的依据就是在写入消息时,在向内存追加消息时加锁的时间,默认的判断标准是加锁时间超过1s,就认为是pagecache压力大,向客户端抛出相关的错误日志。 发送线程池挤压的拒绝策略 在RocketMQ中处理消息发送的是一个只有一个线程的线程池,内部会维护一个有界队列,默认长度为1W,如果当前队列中挤压的数量超过1w,执行线程池的拒绝策略,从而抛出[too many requests and system thread pool busy]错误。 Broker端快速失败 默认情况下Broker端开启了快速失败机制,就是在Broker端还未发生pagecache繁忙(加锁超过1s)的情况,但存在一些请求在消息发送队列中等待200ms的情况,RocketMQ会不再继续排队,直接向客户端返回system busy,但由于rocketmq客户端目前对该错误没有进行重试处理,所以在解决这类问题的时候需要额外处理。 3.2 PageCache繁忙解决方案 一旦消息服务器出现大量pagecache繁忙(在向内存追加数据加锁超过1s)的情况,这个是比较严重的问题,需要人为进行干预解决,解决的问题思路如下: transientStorePoolEnable 开启transientStorePoolEnable机制,即在broker中配置文件中增加如下配置: transientStorePoolEnable=true 1 transientStorePoolEnable的原理如下图所示: 在这里插入图片描述 引入transientStorePoolEnable能缓解pagecache的压力背后关键如下: 消息先写入到堆外内存中,该内存由于启用了内存锁定机制,故消息的写入是接近直接操作内存,性能能得到保证。 消息进入到堆外内存后,后台会启动一个线程,一批一批将消息提交到pagecache,即写消息时对pagecache的写操作由单条写入变成了批量写入,降低了对pagecache的压力。 引入transientStorePoolEnable会增加数据丢失的可能性,如果Broker JVM进程异常退出,提交到PageCache中的消息是不会丢失的,但存在堆外内存(DirectByteBuffer)中但还未提交到PageCache中的这部分消息,将会丢失。但通常情况下,RocketMQ进程退出的可能性不大,通常情况下,如果启用了transientStorePoolEnable,消息发送端需要有重新推送机制(补偿思想)。 扩容 如果在开启了transientStorePoolEnable后,还会出现pagecache级别的繁忙,那需要集群进行扩容,或者对集群中的topic进行拆分,即将一部分topic迁移到其他集群中,降低集群的负载。 温馨提示:在RocketMQ出现pagecache繁忙造成的broker busy,RocketMQ Client会有重试机制。 3.3 TIMEOUT_CLEAN_QUEUE 解决方案 由于如果出现TIMEOUT_CLEAN_QUEUE的错误,客户端暂时不会对其进行重试,故现阶段的建议是适当增加快速失败的判断标准,即在broker的配置文件中增加如下配置: #该值默认为200,表示200ms waitTimeMillsInSendQueue=1000
游客2q7uranxketok 2021-02-25 12:14:47 0 浏览量 回答数 0

问题

使用 ASM 实现 Java 语言的“多重继承”:报错

简介: 尽管大部分情况下我们并不需要多重继承,但总有一些情况下我们不得不做出多重继承的考虑。但又由于 Java 语言本身不支持多重继承,这就会带来问题。本文试图使用 ASM 来解决这一问题。 ...
kun坤 2020-06-06 15:29:41 0 浏览量 回答数 1

问题

【Java学习全家桶】1460道Java热门问题,阿里百位技术专家答疑解惑

阿里极客公益活动: 或许你挑灯夜战只为一道难题 或许你百思不解只求一个答案 或许你绞尽脑汁只因一种未知 那么他们来了,阿里系技术专家来云栖问答为你解答技术难题了 他们用户自己手中的技术来帮助用户成长 本次活动特邀百位阿里技术专家对Java常...
管理贝贝 2019-12-01 20:07:15 27612 浏览量 回答数 19

问题

10个迷惑新手的Cocoa,Objective-c开发难点和问题? 400 报错

10个迷惑新手的Cocoa,Objective-c开发难点和问题? 400 报错 首先请谅解我可能使用很多英文,毕竟英文资料将来会是你的主要资料来源。 在你继续深入学习之前,请停下脚步弄清这些问题...
爱吃鱼的程序员 2020-05-31 00:44:29 0 浏览量 回答数 1

云产品推荐

上海奇点人才服务相关的云产品 小程序定制 上海微企信息技术相关的云产品 国内短信套餐包 ECS云服务器安全配置相关的云产品 开发者问答 阿里云建站 自然场景识别相关的云产品 万网 小程序开发制作 视频内容分析 视频集锦 代理记账服务 阿里云AIoT