添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
冲动的弓箭  ·  解决报错 ...·  1 年前    · 
闷骚的灌汤包  ·  curl转python工具 ...·  2 年前    · 
健身的冲锋衣  ·  Feign ErrorDecoder : ...·  2 年前    · 
//----------代 码 片 段---------- float a = 1.23456789123456789f ; float b = 2.23456789123456789f ; float c = 3.23456789123456789f ; float d = 4.23456789123456789f ; float e = 5.23456789123456789f ; float f = 6.23456789123456789f ; float g = 7.23456789123456789f ; float h = 8.23456789123456789f ; float i = 9.23456789123456789f ; System . out . println ( a ); System . out . println ( b ); System . out . println ( c ); System . out . println ( d ); System . out . println ( e ); System . out . println ( f ); System . out . println ( g ); System . out . println ( h ); System . out . println ( i ); //----------代 码 片 段---------- //----------打 印 结 果---------- 1.2345679 2.2345679 3.2345679 4.234568 5.234568 6.234568 7.234568 8.234568 9.234568

要回答此问题,得了解如下的背景知识:

任意两个实数之间是有无穷多个实数,故有实数能把数轴填满一说
而浮点数(双精度类似)则不同,两浮点数之间只包含有限个浮点数
换句话说,某个浮点数是存在确定的前序和后序的浮点数的

相邻浮点数之间存在距离,被称为最小精度单位(Unit of Least Precision)
或最后位置单位(Unit in the Last Place),简称ULP

Java对于任意浮点字面量,最终都舍入到所能表示的最靠近的浮点值
遇到该值离左右两个能表示的浮点值距离相等时,采取 IEEE_754 舍入原则

在左右距离相同时采用偶数优先(Ties To Even,默认)即取其中的偶数(在二进制中以0结尾的)

Java获取浮点数的前序或后序浮点数、ULP的方法为Math类的静态方法:

 public static float nextDown(float f) // 前序,即比所给值小
 public static float nextUp(float f)   // 后序,即比所给值大
 public static float ulp(float f)      // 获取ULP
        
  float e = 1.1110999f;		
  System.out.println("1.1110999f 打印真实为:" + e);
  System.out.println("1.1110999f 的前序:" + Math.nextDown(e));
  System.out.println("1.1110999f 的后序:" + Math.nextUp(e));
  System.out.println("1.1110999f 的最小精度单位:" + Math.ulp(1.1110999f));
  // e会等于d吗?
  float d = 1.1111f;
  System.out.println(e == d);
  // 打印结果:
  1.1110999f 打印真实为1.1111           //  ①
  1.1110999f 的前序1.1110998           //  ②
  1.1110999f 的后序1.1111001           //  ③
  1.1110999f 的最小精度单位1.1920929E-7 //  ④
  true                                 //  ⑤
        

①⑤可以验证背景知识第3条,字面量的确舍入到最近所能表示的浮点数‘1.1111f’ 至于它为何只是诡异的保留5位(还压根没达到7位或8位)看问题总结

①②③可以得知在[1.1110998, 1.1111001]范围内其实只存在三个浮点数,即: { 1.1110998、 1.1111、 1.1111001 }

④可以得知,‘1.1110999f’这个区段的ULP为:0.00000011920929 将上面三个数字任意相邻两个数字相减,确实也能印证,只是精度稍差点

再回到问题,为啥输出有时保留7位或8位,甚至是像上面例子中的5位‘1.1111’
其实就是根据字面量(本例为:1.1110999f),选取两个可表示的浮点值A和B
使得A<字面量<B(本例为:A =1.1110998, B=1.1111)然后根据浮点数舍入规则
选取其中之一(本例选取B,B二进制尾数为0)至于结果到底保留几位
由最终被选择的可表示的浮点数决定,本例由于选择1.1111,故有效位就是5位

参考资料:

  • IEEE floating point
  • Unit in the last place
  • 字符型 char

    占用一个字节,java使用UTF-16字符集,故需两个char
    码点:一个编码表中某个字符对应的代码值

    布尔型 boolean
    true 真值
    false 假值
    注意:整型与布尔型不可转换

  • String str = “Str” + “ing”;
    这种方式编译器自动优化为“String”并纳入常量池
  • 字符串引用之间 “+” 操作会生产新对象,而非纳入常量池(生成StringBuilder对象)
  • String.intern() 方法会判断常量池,没有就讲当前字符串加入常量池
    推荐不错的一篇关于Java内存的模型及字符串分配的博文:
    Java内存模型及字符串的内存操作
  • 嵌套的两个语句块中不能有同名变量
  • 语句块(复合语句)可以在原本只能放一条语句的地方放多条语句
  • 加上 -Xlint:fallthrough 编译器参数,可检验switch语句漏加break语句
  • Java7及之后case标签开始支持字符串字面量
  • 返回一个数组长度为0的数组 与 返回null 不同
  • 第四章 对象与类

    隐式参数与显式参数

    对象的方法第一个参数称为隐式参数,该参数即当前对象this
    第二个参数开始称为显式参数,即方法名后面括号中的变量

    编译器监视那些简洁、经常被调用、没有重载、可优化的方法,
    将其包含到调用者内部的行为,称作方法内联

    对象成员变量的封装
    需要返回可变对象的引用时,应该对它进行克隆,否则破坏封装

    方法可以调用对象的所有私有特性隐式参数

    final 实例域
    被 final 修饰的实例域,自动放弃编译器的默认初始化,必须确保每个
    构造方法都要手动初始化该域的值,且一旦初始化不可更改

    为什么设计成成员变量声明的同时可以进行赋值(显式初始化),这样有啥好处?

    方便多个构造方法初始化时的代码共享,不需要每个构造方法都初始化一次。当然只针对以下几种情况的成员: final修饰的成员不想被赋予编译器默认值的成员不能确保无论最初调用的构造是哪个但总能被初始化的成员

    protected 修饰的域或方法,同包的类中,可以用定义的类的引用直接访问
    效果同default,但不同包的子类中,不能通过定义类的引用直接访问
    只能通过 子类引用super关键字 进行特殊调用

    protected、private 虽然不能修饰外部类,但却可以修饰内部类
    作用域与修饰域和方法类似,无显式构造方法时,默认构造方法修饰符同类修饰符一致

    invokespecial obj.protect.SuperClass() invokevirtual obj.protect.SuperClass.getObj()

    ③字节码指令为

          aload_0 [this]
          invokespecial obj.protect.SuperClass.getObj()  
        

    ④字节码指令为

          new obj.protect.InSubClass
          invokespecial obj.protect.InSubClass()
          astore_1 [one]
          aload_1 [one]
          invokevirtual obj.protect.SuperClass.getObj()  
        

    ①、②、④ 都使用 invokevirtual 调用getObj()方法,即动态绑定调用。
    根据所传对象实际的类来调用方法,如果子类重写了父类方法,
    调用的将是子类的方法,反之调用父类的。
    像④的最后一行指令,虽然引用是父类但实际的类确实子类,
    但由于子类没有重写, 故还是调用父类的。

    ③ 使用 invokespecial 调用getObj()方法,即静态调用
    根据所传对象的引用类型来调用方法,引用是父类就调用父类的方法

    谈到这里有必要理清容易忽视的细节:

    子类能重写的,永远是子类能够访问到的父类的方法

  • 父类的private方法,任何子类都不能重写
  • 父类的default方法,同包下子类能重写,不同包子类不能
  • ④的调用明明可以优化为:

      invokespecial obj.protect.SuperClass.getObj()  
        

    为何却采用invokevirtual调用?

    虽然子类没有重写父类方法,但还是在运行动态绑定的情况下 检测出由于子类没复写而调用父类的这一过程 如果子类一旦复写父类方法,那么编译期间就已经确定是调用 子类的方法,调用字节码即会被优化为:

      invokespecial obj.protect.InSubClass.getObj()  
        

    说到这,Java的几种方法调用的字节码指令用法,请参考如下资料:

    JVM方法调用的那些事
    invokevirtual,invokespecial,invokestatic,invokein

    一个源文件只能包含一个公有类,且文件名必须与公有类相同
    这样规定方便编译器查找包外引用的类,因为仅能引用其它包的公有类,
    并且公有类刚好与文件名相同,当然包内引用在其它类能定义的类还是需要 搜索当前包的所有源文件

    运行时库会自动加入类搜索路径中
    %JAVA_HOME%/jre/lib
    %JAVA_HOME%/jre/lib/ext

    类的设计技巧

  • 保证数据私有
  • 保证数据初始化
  • 不要过多使用基本类型
  • 不是所有域都需要访问器和修改器
  • 将职责过多的类进行分解
  • 类名方法名要体现它的职责
  • 优先使用不可变类
  • 第五章 继承

    类、超类和子类

    子类通过super关键字调用父类方法
    super 与 this 不同,它不是对象引用,只是告知编译器调用父类方法的一个标示

    子类构造器没显示调用父类构造器时,默认调用父类的无参构造器
    父类没有无参构造器则编译报告异常

    一个对象变量只是多种实际类型的现象叫做多态
    运行时自动选择调用哪个方法的现象称为动态绑定
    从某个类到其祖先的路径被称为该类的继承链

    final关键字修饰类或方法会阻止其继承
    早期使用 final ,主动让方法不能被重写好让编译器对它进行优化内联
    防止 CPU 使用分支转移影响指令预取策略
    现在更为高级的虚拟机采用即时编译器( JIT )处理,它能准确的知道
    类之间的继承关系,并知道方法是否真正地存在覆盖的方法,必要时会
    进行内联处理,将来发生变化方法被重写了,还能取消内联

    所有类的超类 Object

  • equals方法最佳实践
  • 显式命名参数为otherObject,稍后将它转换成other变量
  • 检测this与otherObject是否是同一引用
  • 检测otherObject是否为null,是直接返回
  • 如果相等概念由具体子类决定(子类间非跨代比较,保证对称性)
    使用下面语句:
     getClass() != otherObject.getClass()  
          
  • 如果相等概念由父类决定(子类间、跨代都有相同语义)
    使用下面语句:
     otherObject instanceof ClassName
          
  • 将otherObject转换为相应类的类型变量
     ClassName object = (ClassName) otherObject  
          
  • 现在开始对需要的域进行比较
  • 如果子类重新定义了equals,就要包含super.equals(other)
  • hashCode方法

    如果重新定义equals方法,就必须重新定义hashCode方法

    关于hashCode值的算法,参考《Effective Java》第二版第9条

    toString方法

    默认格式 对象所属类目@散列码,例如:java.io.PrintStream@2f6684
    数组格式 [X@散列码 X为JLS规定的类型字母,例如:[I@1a46e30

    数组默认toString不直观,可以改用静态方法:

    	Arrays.toString();
        

    多维数组可以采用:

    	Arrays.deepToString();
        

    其实作者所述再装箱为 Double **想表达的意思是拆箱后提升为 double 值,
    与原 double 计算后的结果
    可能**会再次封装为 Double (注意这里是说可能)

    拿书中的例子来说:

      Integer n = 1;
      Double x = 2.0;
      System.out.println(true ? n : x);	// Prints 1.0
        

    打印的是 1.0 没错,但这是调用 println 的基本类型 double 的重载方法,
    故而没有再封装为 Double 的操作,字节码可以看出这点:

      invokevirtual #23   // Method java/io/PrintStream.println:(D)V  
        

    相反,如果是执行如下语句:

      // 受后面x.toString的对象调用影响  
      System.out.println(true ? n+x : x.toString())
        

    打印的是 3.0 ,而且是调用 println 的 Object 类型的重载方法,
    toString 方法影响,结果 double 被再封装为 Double 对象

    invokestatic #21 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double; invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V

    另外,不光是 Double 类, Integer 混合 Long 、Float 类只要参与比它精度高的计算过程,
    都会有拆箱晋升的过程,但是如果没有参与比它精度高的计算过程,就不会拆箱晋升
    例如下面语句,就没有拆箱晋升的过程:

      System.out.println(true ? n.toString() : x);
        

    除非拥有访问权限,否则Java安全机制只允许反射查看对象有哪些域,
    而不允许读它们的值,但可用 Field 、Method 、Constructor 的 setAccessible(true) 方法
    解除此限制

    Tips:

    实现方法的回调有几种办法:
    1. 接口方式(注册监听器)
    2. 反射机制,使用Method的invoke
    3. lambda表达式
    

    已检查异常:竭尽全力仍不能完全避免的异常,此时就要提供异常处理器
    未检查异常:精心控制可以完全避免的异常,例如:空指针、除0操作

    第六章 接口、lambda表达式与内部类

    接口方法为public、域为public static final,JLS推荐无须显式标记

    Java SE 8中,接口支持静态方法,之前的做法是将静态方法放在伴随类中。
    例如: Collection/Collections Path/Paths

    Java SE 8 中,接口方法可以提供一个默认实现,用 default 修饰符

  • 可以消除伴随类(当然 API 中肯定无法移除)
  • 利于接口演进,就接口添加新默认方法可保证源代码、二进制兼容性

    源代码兼容:改变类后,依赖该类的其他类能重新编译
    二进制兼容:改变类后,依赖该类的其他类使用旧的编译文件还能执行

  • 解决方法冲突规则 某类继承超类实现某接口,超类及接口默认含有相同方法名及参数的方法
    且不管接口是否包含默认方法,此时子类一定使用是超类的方法
  • 冲突特指类不实现接口时,使用接口默认方法是否存不知道该调用哪个方法(二义性)
    某类实现了两个接口,且两接口含有方法名参数一致的方法
  • 当这两个方法是抽象时,该类不会提示冲突,选择其中之一实现即可
  • 当某个接口含默认方法,该类必须实现方法解决冲突(虽然只有一个默认方法)
  • 当两接口都含有默认方法,该类同样必须实现方法解决冲突
  • Object类包含一个 protected 的 clone 方法
  • 某类只能在内部调用 Object 的默认 clone 方法克隆它自己
  • 某类包含其他类,要克隆其他类时,不能调用其他类默认继承 Object 的 clone 方法
  • 只能让其他类实现 Cloneable 标记接口,并覆盖 Object 的 clone 方法,将修饰符扩大为public才行
  • 所有数组类型都有一个 public 的 clone 方法,包含所有数组元素的副本(引用也是副本)
  • 只有一个抽象方法的接口称函数式接口,需要这种接口对象时可以提供一个 λ 表达式
    其实,接口中是可以抽象声明 Object 类的 public 方法,
    但实际上每个接口的实例都有这些被声明为抽象的方法的Object类的默认实现

    最好把λ表达式看做函数而非对象,λ表达式可以传递到函数式接口中
    执行λ表达式体与传统接口的实体对象的调用相比,可能更加高效,
    相比 invokeinterface , λ 采用 invokedynamic 调用引导方法( Bootstrape Method )

    反编译后λ表达式的调用被编译成为一个invokedynamic的字节码操作
    关于此字节码的介绍请查看: Invokedynamic :Java 的秘密武器

    有时候现成方法可以完成想要传递到其他代码的某个动作就可以使用方法引用

    方法引用有3种情况:

  • object::instanceMethod 例:System.out::println
  • Class::staticMethod 例:Math::pow
  • Class::instanceMethod 例:String::compareToIgnoreCase
  • 第一种,等同于 x -> Sytem.out.println(x)
    第二种,等同于 (x,y) -> Math.pow(x,y)
    第三种,等同于 (x,y) -> x.compareToIgnoreCase(y)

    this::instanceMethodsuper::instanceMethod 也是合法的
    分别调用当前类的方法或当前类的父类的方法

    构造器的引用为:Object::new
    Java无法构造泛型类型的T的数组,利用构造器引用有时能够保证类型不被丢失
    例如:new T[n] 编译错误,所有之前返回不确定类型的方法声明只能为Object[]
    新方法使用构造方法引用来解决此问题,例如 Stream 的方法:

      <A> A[] toArray(IntFunction<A[]>) 
        

    lambda变量作用域

    lambda表达式有3个部分:代码块、参数、自由变量的值(不在代码块定义的变量)
    这些自由变量被lambda表达式引用,被称为捕获
    被捕获的变量即使外部方法调用栈执行完后撤栈后仍然能被lambda表达式使用
    被捕获的变量被要求在lambda代码块中及外部代码中不能改变(effectively final)

    闭包:能够维持外部自由变量的代码块,即使外部自由变量已经不存在

    lambda表达式中声明的变量名不能与局部变量同名
    lambda表达式中的this关键字是指创建这个表达式的方法的this

    处理lambda表达式

    使用lambda表达式的重点是延迟执行(deferred execution)
    想延迟的原因:

  • 在单独线程中运行代码
  • 多次运行代码
  • 在算法适当的位置运行代码
  • 发生某种情况时执行代码
  • 只在必要时才运行代码
  • Java API 中默认提供了函数式接口,详见链接
    大多数函数式接口都提供了非抽象方法来生产或合并函数
    Predicate 的 and 、 or 等方法
    使用 @FunctionalInterface 标注函数式接口是个好习惯

    许多接口包含很多方便的静态方法来创建接口实例

    Comparator 的静态 cpmparing 方法有一个键提取器函数,
    接受方法引用或lambda表达式:

      comparing(Function<? super T,? extends U> keyExtractor)  
        

    内部类访问外部类的私有域是通过在外部类加入特殊的静态的访问方法,类似:access$0
    局部内部类不能有public、static或private修饰符,它们的作用域被限定在局部代码块中

    内部类也可访问外部类的局部变量,只不过局部变量必须为final、或事实上的final
    Java 7及之前:局部变量必须由final修饰
    Java 8及之后:局部变量不须强制加final,但在外部类或内部类中都不能更改其值

    匿名内部类可被用于进行双括号初始化,由于是匿名子类,故equals的getClass会不等

      Arrays.toString(new ArrayList<String>() { {
          add("One"; 
          add("Two");
      } } );
        

    如果要对匿名子类调用 equals 方法,由于 getClass 获取到的是匿名子类的,
    故并不相等,要在内部类获取外部类的类型信息可以使用如下技巧:

      inner.getClass().getEnclosingClass();
        

    出于隐藏一个类,且该类不需引用外部类对象,可以声明为静态内部类
    声明在接口内的内部类,自动为static和public的类
    声明在静态方法里的内部类,虽无法被static修饰但其也不包含外部类对象

    外部类为为何不能被static修饰?

    链接给的回答中,necromancer的回答及Lumi的补答蛮贴切:

    Top level classes are static by default. Inner classes are non-static by default You can change the default for inner classes by explicitly marking them static Top level classes, by virtue of being top-level, cannot have non-static semantics because there can be no parent class to refer to. Therefore, there is no way to change the default for top-level classes.
    Top Level classes are static because you don’t need any instance of anything to refer to them. They can be referred to from anywhere. They’re in static storage (as in C storage classes). The language designers could have allowed the static keyword on a top-level class to denote and enforce that it may only contain static members, and not be instantiated; that could, however, have confused people as static has a different meaning for nested classes.

    大概译文:

    顶层类(外部类)默认即为静态的类 ,内部类默认为非静态的。可以通过static关键字修饰内部类,从而更改为非默认的静态内部类。顶层类不能有非静态语义其优点在于可以不需要父类就能引文它,因此不能通过任何方式更改顶层类的默认语义。
    顶层类它们之所以是静态的是因为它们不需要任何具体的实例去引用它们,它们可以在任何地方被引用。它们存储在静态区,Java语言的设计者们原本大可允许使用static关键字修饰顶层类,强制该类只能包含静态的成员及方法而不能被实例化,但是为何不最终不允许static修饰呢?是因为如果允许static修饰外部类会使人们对staic修饰内部类的含义产生误导,静态的内部类是可以同时包含静态的、非静态的成员及方法的。

    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { // 自定义操作 // do something... return m.invoke(target, args);

    所有代理类都覆盖了 Object 类的 toString 、equals 、hashCode 方法
    Sun 虚拟机中的 Proxy 类将生成以 $Proxy 开头的类名
    使用同一个加载器、接口数组调用两次 newProxyInstance 得到同一个类两个对象,

    Class proxyClass = Proxy.getProxyClass(loader, interfaces);
    

    代理类一定是 publicfinal 的,其所实现的所有接口为 public 时,代理类不属于某个
    特定包,否则所有 非 public 的接口必须属于同包,并且代理类也属于该包

    第七章 异常、断言与日志

    异常都派生于Throwable类,下层分解为:ErrorException

    Error:Java运行时系统的内部错误和资源耗尽错误,应用程序不抛此类型

  • Exception:下层分为RuntimeException、其他异常(例如:IOException)
  • RuntimeException:程序错误导致的异常,例如:数组越界、null指针
  • 其他异常:程序本身没问题,由于像I/O错误问题导致的属其他异常
  • 非受查异常与受查异常
  • 非受查异常包括:派生于Error、RuntimeExceptioin的错误或异常
  • 受查异常:除非受查异常外的其他异常 (此类异常需要进行异常处理)
  • 应该抛异常情况:
  • 调用一个抛出受检查异常的方法
  • 程序运行过程中发现错误,并利用throw语句抛出一个受查异常
  • 程序出现错误,如数组越界
  • Java虚拟机和运行时库出现的内部错误
  • 1、2两条是需要使用throws声明显示抛出的,或者捕获异常
    3、4两条为非检查异常,要么不可控、要么应该避免,故不强制一定抛出

  • 子类覆盖超类方法,抛出异常不能比超类的异常更通用,只能更特定或不抛出
  • 没有throws说明符的方法不能抛出任何受查异常
  • Java SE 7 及之后,单个 catch 支持捕获多个异常类型,但不存在子类关系,语法:

    catch (Exception | Exception e) { ... }
    

    此时,e 变量为隐含的 final 的变量只能被赋值为其中之一,这种语法更简洁更高效

  • 再次抛出异常与异常链
  • catch子句可以抛出异常,可以起到如下作用
  • 出于异常识别分类,改变异常类型
  • 受查异常转运行异常(用于不能抛出受查异常的情形)
  • 记录异常日志
  • catch子句抛出新异常时,可以调用 newE.initCause( oldE ) 使得原始异常不丢失
  • Java SE 7之后,抛出比声明的异常更广的类型时,只要try块仅有声明的异常类型,
    且在catch块没被改变,声明的异常是合法的

    如果final块也可能有异常时,最佳实践代码如下:

      try {
          try {
              // might throw exceptions
          } finally {
              // might throw exceptions
      } catch(Exception e) {
          // show error message
        

    这样可以解耦 try/catch 和 try/finally ,使职责清晰明了
    try/finally 只负责确保异常后仍可执行 finally 的语句
    try/catch 只负责记录出现的错误

    嵌套try时,很可能会出现外层异常覆盖了里层异常,导致原始异常丢失,
    在外层增加个里层异常是否为空的判断,如果不为空抛出里层异常否则抛出外层异常很有用,

      InputStream in = ...;
      Exception ex = null;
      try {
          try {
              // 可能出现异常代码
          } catch(Exception e) {
              ex = e;
              throw e;
      } finally {
          try {
              in.close();
          } catch(Exception e) {
              if (ex == null) throw e; // 如果里层没有异常,才抛外层异常
              throw ex; // 否则抛出里层异常
        

    当然,Java SE 7有自动关闭资源的特性,会比这里的处理更为简洁

      try (InputStream in = ...) {	// 这里的in资源会自动关闭
          // 可能出现异常代码
      } catch(Exception e) {
          // 处理代码可能出现的异常
      } finally {
        

    当in关闭出现异常时,会被自动捕获并被抑制
    异常将由e.addSuppressed方法追加到原代码异常的后面,
    当然带资源的try/catch会在资源关闭之后执行

  • 使用异常机制技巧 (早抛出,晚捕获)
  • 异常不能代替简单的测试(不能将异常用于逻辑判断,性能太差)
  • 不要过分细化异常(不要把每条可能出现异常的代码装在独立的try块里)
  • 利用异常层次结构(寻找合适子类或自创的异常,逻辑错误不抛受查异常)
  • 不要压制异常(吃掉异常不处理)重要的异常一定要处理
  • 检测到错误及时处理,不要放过 早抛出
  • 不要羞于传递异常(读一个文件时,出现错误传递异常好过捕获)晚捕获
  • 断言只用于测试阶段确定程序内部的错误位置,它不改变程序的逻辑运行
    不要将断言参与业务逻辑处理,实际上也参与不了,断言是致命的不可恢复的错误
    处理系统错误的三种机制:抛异常、使用断言及记录日志

    日志可以很容易的定制记录级别、或禁止,也可使用不同处理器进行不同格式的输出,
    并且记录器、处理器都可过滤记录、格式化日志,可配置性很强

  • 简单使用可以仅使用一个全局日志记录器
    Logger.getGlobal().info("Need to be log...")
  • 取消所有日志
    Logger.getGlobal().setLevel(Level.OFF)
  • 企业级的日志中,应该自定义日志记录器而不要全部交给全局日志记录器
      private static final Logger myLog = Logger.getLogger("org.package");
          
  • 之所以使用static修饰,是为了防止垃圾回收
  • 传入的类似包名字符串能很好的使得子包默认继承父包的设置
    例如:org包设置日志级别,org.package默认与org设置一致
  • 日志有7个记录器级别:
    SEVEREWARNINGINFOCONFIGFINEFINERFINEST

    默认值记录前三个级别的日志,调用myLog.setLevel(Level.FINE)可修改默认值
    Level.ALLLevel.OFF为开启、关闭所有日志的特殊常量

    记录日志有下面几种方法:

  • myLog.info(message) 方法名指定日志级别
  • myLog.log(Level.INFO, message) 参数指定日志级别
  • 默认将显示日志调用的类名方法名,虚拟机优化执行时得不到确切调用信息,采用:

      void logp(Level l, String className, String method, String msg);  
        

    来获取类和方法的具体位置,另外下面这些方法可用来跟踪方法执行流向:

  • void entering(String cName, String mName)
  • void entering(String cName, String mName, Object p)
  • void entering(String cName, String mName, Object[] ps)
  • void exiting(String cName, String mName)
  • void exiting(String cName, String mName, Object r)
  • cName 类名,mName 方法名,p 单参数,ps 参数数组,r 返回值
    这些调用产生FINER级别、字符串为ENTRY、RETURN开始的日志记录

    记录异常可使用

  • void throwing(String cName, String mName, Throwable t)
  • void log(Level l, String message, Throwable t)
  • 日志管理器配置
    系统默认配置文件存在于jre/lib/logging.properties文件中

  • 想要另外指定配置文件,有以下几种设置方式:
  • 虚拟机参数式:-Djava.util.logging.config.file = configFile
  • 代码调用式: System.setProperty("java.util.logging.config.file", file)
  • 修改默认日志级别,可以在配置文件中加入
    .level=INFO
    或者指定自己的记录器的记录级别
    org.package.level=FINE

    程序运行时,可通过jconsole改变日志的记录级别
    具体查看 Using JConsole to Monitor Applications

    日志处理器
    默认日志记录器发送到ConsoleHandler,由它输出到System.err流
    并还将记录发送到父处理器,最终处理器为ConsoleHandler(名称为空,即“”)

    通过myLog.addHandler(yourHandler)可以安装自己的处理器
    通过myLog.setUseParentHandlers(false)可以阻止处理器向父处理器传递

    常用的处理器:

  • SocketHandler 将记录发送到特定的主机和端口
  • FileHandler 将记录收集到文件当中,默认写到用户主目录 javan.log
    其中 n 为唯一编号,例如:java001.logjava002.log
    文件处理器的参数及用法,参考API文档
  • 日志记录的加工处理 通过实现Filter接口,实现boolean isLoggable(LogRecord record)
  • 扩展Formatter类,覆写String format(LogRecord record)
    如果记录有头及尾部时,覆写String getHead(Handler h)
    String getTail(Handler h)
  •   Thread.setDefaultUncaughtExceptionHandler(
          new Thread.UncaughtExceptionHandler(){
              public void uncaughtException(Thread t, Throwable e){
                  // 将非捕获异常信息额外处理的代码
            

    虚拟机参数
    -verbose 能提供虚拟机启动相关信息,排除类加载及路径信息
    -Xlint 告知编译器对代码进行检查,例如:-Xlint:fallthrough(case穿透)
    -Xprof 能剖析代码调用,将分析结果发送到标准输出,获取哪些方法由JIT编译

    注:-X选项非正式支持,可运行java -X打印非标准参数列表

    jconsole图形工具对应用进行监控和管理,例如:内存、线程使用、类加载等情况

    jmap工具获得堆的快照,jhat加载该快照浏览localhost:70000查看堆内容

  •