商量String

写那篇文章的缘起是前日同事改了二个常量类中的提示,揭橥到测量检验际遇后尚未卓有效能,正赏心悦目《Java 解惑(谜题 93: 类的战火)》
提到了那个标题,所以写篇文章记录一下。

Java Interface 是常量存放的特级地方呢?(转帖学习,非原创)

  由于java interface中宣示的字段在编写翻译时会自动抬高static
final的修饰符,即宣称为常量。因此interface平常是存放在常量的极品地方。可是在java的骨子里运用时却会产生局地难点。

  难点的缘起有三个,第一,是大家所使用的常量并非雷打不动的,而是相对于变量不能够赋值改造。举个例子大家在三个工程前期定义常量∏=3.14,而出于总计精度的滋长我们兴许会再次定义∏=3.14159,此时整整项目对此常量的援用都应充当出改动。第二,java是动态语言。与c++之类的静态语言差别,java对有的字段的援用能够在运维期动态进展,这种灵活性是java那样的动态语言的一大优势。也就使得我们在java工程中不常部分内容的改变实际不是再行编写翻译整个项目,而只需编写翻译改造的一对重新发表就可以转移一切应用。

  讲了那样多,你还不晓得自身要说如何吧?好,大家来看三个轻巧的例子:

  有叁个interface A,四个class B,代码如下:

//file A.java
public interface A{
    String name = "bright";
}
//file B.java
public class B{
    public static void main(String[] args){
        System.out.println("Class A's name = " + A.name);
    }
}

  够轻巧吗,好,编写翻译A.java和B.java。

  运营,输入java B,明显结果如下:

Class A's name = bright

  大家今日涂改A.java如下:

//file A.java
public interface A{
    String name = "bright sea";
}

  编写翻译A.java后再行运转B class,输入java B,注意:结果如下

Class A's name = bright

  为何不是”Class A’s name = bright
sea”?让我们运用jdk提供的反编写翻译工具javap反编写翻译B.class看个毕竟,输入:javap
-c B ,结果如下:

Compiled from B.java
public class B extends java.lang.Object {
    public B();
    public static void main(java.lang.String[]);
}
Method B()
   0 aload_0
   1 invokespecial #1 <Method java.lang.Object()>
   4 return
Method void main(java.lang.String[])
   0 getstatic #2 <Field java.io.PrintStream out>
   3 ldc #3 <String "Class A's name = bright">
   5 invokevirtual #4 <Method void println(java.lang.String)>
   8 return

  注意到注解3的代码了啊?由于引用了七个static final
的字段,编写翻译器已经将interface A中name的内容编写翻译进了class
B中,实际不是对interface A中的name的援用。由此唯有大家重新编写翻译class
B,interface A中name发生的变迁不能在class
B中反映。假若那样去做那么java的动态优势就声销迹灭殆尽。

  技术方案,有三种缓和办法。

  第一种办法是不再选取常量,将所需字段放入class中声称,并去掉final修饰符。但这种办法存在一定的危机,由于不再是常量着由此在系统运作时有希望被其它类修改其值而发生错误,也就违背了大家设置它为常量的初志,由此不引入应用。

  第二种方法,将常量放入class中宣称,使用class方法来获取此常量的值。为了保全对此常量引用的轻便性,大家得以采纳一个静态方法。大家将A.java和B.java修改如下:

//file A.java
public class A{
    private static final String name = "bright";
    public static String getName(){
        return name;
    }
}
//file B.java
public class B{
    public static void main(String[] args){
        System.out.println("Class A's name = " + A.getName());
    }
}

  同样大家编写翻译A.java和B.java。运营class B,输入java B,显然结果如下:

Class A’s name = bright

  未来大家修改A.java如下:

//file A.java
public class A{
    private static final String name = "bright";
    public static String getName(){
        return name;
    }
}

  大家再一次编写翻译A.java后再行运维B class,输入java B:结果如下

Class A's name = bright sea

  终于得到了小编们想要的结果,大家能够重复反编写翻译B.class看看class
B的改变,输入:javap -c B,结果如下:

Compiled from B.java
public class B extends java.lang.Object {
    public B();
    public static void main(java.lang.String[]);
}
Method B()
   0 aload_0
   1 invokespecial #1 <Method java.lang.Object()>
   4 return
Method void main(java.lang.String[])
   0 getstatic #2 <Field java.io.PrintStream out>
   3 new #3 <Class java.lang.StringBuffer>
   6 dup
   7 invokespecial #4 <Method java.lang.StringBuffer()>
  10 ldc #5 <String "Class A's name = ">
  12 invokevirtual #6 <Method java.lang.StringBuffer append(java.lang.String)>
  15 invokestatic #7 <Method java.lang.String getName()>
  18 invokevirtual #6 <Method java.lang.StringBuffer append(java.lang.String)>
  21 invokevirtual #8 <Method java.lang.String toString()>
  24 invokevirtual #9 <Method void println(java.lang.String)>
  27 return

  注意标号10至15行的代码,class B中已经形成对A
class的getName()方法的引用,当常量name的值退换时大家只需对class
A中的常量做修改并再度编写翻译,没有供给编写翻译整个项目工程大家就能够退换总体应用对此常量的援用,即维持了java动态优势又保持了我们应用常量的初衷,因此方法二是一个顶级实施方案。

 

注:本文是转帖学习笔记,非原创,不喜勿喷。原来的小说链接:

提出20: 不要只替换一个类

至于常量接口(类)大家来看二个事例,首先定义贰个常量类:

public class Constant {
    //定义人类寿命极限
    public final static int MAX_AGE = 150;
}

这是贰个非常轻松的常量类,定义了人类的最新禧纪,我们援用那些常量,代码如下:

public class Client {
    public static void main(String[] args) {
            System.out.println("人类寿命极限是:" + Constant.MAX_AGE);   }
}

运维的结果特别简单(结果粗略)。前段时间的代码编写都是在“智能型”IDE工具中做到的,上边大家临时回溯到原始时期,约等于回归到用记事本编写代码的年份,然后看看会怎么。
修改常量Constant类,人类的寿命扩张了,最大能活到177虚岁,代码如下:

public class Constant {
    //定义人类寿命极限
    public final static int MAX_AGE = 180;
}

然后重新编写翻译:javac Constant,编写翻译实现后实行:java Client.

输出的结果是:“人类寿命极限是:150”,竟然从未改变为180,那是为何?

由来是:对于final修饰的为主类型和String类型,编写翻译器会感觉它是安静态(Immutable
Status),所以在编写翻译时就径直把值编写翻译到字节码中了,防止了在运营期引用(Run-time
Reference),以进步代码的进行效用。针对大家的事例来讲,Client类在编写翻译时,字节码中就写上了“150”这几个常量,而不是三个地点援用,因而不论你承接怎么修改常量类,只要不重复编写翻译Client类,输出依旧照样。

而对于final修饰的类(即非基本项目),编写翻译器感觉它是不平稳态(Mutable
Status),在编写翻译时创立的则是引用关系(该类型也称之为Soft
Final),如果Client类引进的常量是二个类或实例,即便不重复编写翻译也会输出最新值。
纯属不能忽视了那一点知识,细坑也能绊倒大象,比方在三个Web项目中,开辟职员修改多少个final类型的值(基本项目),思念到再一次发布风险一点都不小,恐怕是时刻较长,大概是审查批准流程过于繁琐,反就是为着偷懒,于是直接动用替换class类文件的法子发布。替换落成后应用服务器自动重启,然后轻易测验一下(举例本类援用final类型的常量),一切OK。可运维几天后意识工作数据对不上,有的类(引用关系的类)使用了旧值,有的类(承继关系的类)使用的是新值,而且并非头绪,令人焦头烂额,其实难题的来源于就在于此。

还应该有个小难题从未证实,大家的例证为啥不在IDE工具(比如Eclipse)中运维吧?那是因为在IDE中无法重现该难点,若修改了Constant类,IDE工具会活动编写翻译全数的引用类,“智能”化屏蔽了该难点,但神秘的风险实在依然存在。

**
注意 公布应用系统时幸免选用类公事替换格局,全体WAPRADO包发表才是万全之计**

String它是贰个援引数据类型,不是二个基础数据类型。
先探究三个主题素材:String为啥是不可改造的。
查阅String类的具名如下:

以下均采取命令行实行自己要作为轨范遵守规则,至于缘何没有使用 IDE 后边会波及。

public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence {}  

先看贰个差不离的 Constants 类:

然后再看看String到底是怎么存储字符串的:

/** * Created by Poison on 15/05/2017. */public class Constants { public static final String a = "before fixing";}
/** The value is used for character storage. */  
    private final char value[];  

再看下 Solution 类:

String类的签定,和积存String的char数组都被final修饰,它们确认保障了String对象是世代不会被涂改的。

/** * Created by Poison on 15/05/2017. */public class Solution { public static void main(String[] args) { System.out.println(Constants.a); }}

一、内部存款和储蓄器存储

下边大家后续String之旅吧,先贴一段学习代码。

public class LearnString {
    public static void main(String args[]) {
        String a = "abc";
        String b = "abc";
        String c = new String("abc");
        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(a.equals(c));
    }
}

运行结果也如大家所想:

true
false
true

咱们先看看String的内部存款和储蓄器布满情形呢

public class LearnString {
     String b = "abc";
    public static void main(String args[]) {
        String a = "abc";
    }
}

编写翻译后,我们经过javap -v
LearnString来查看内部存款和储蓄器的分布境况,能够看看在字节码的Constant
pool区有如下一行:

Constant pool:
   #1 = Methodref          #6.#23         // java/lang/Object."<init>":()V
   #2 = String             #24            // ab
   #3 = Fieldref           #5.#25         // com/example/learn/LearnString.d:Ljava/lang/String;
   #4 = String             #26            // abc

可知不管是成员变量照旧有的变量,只要String一伊始就被赋值了,那么它的值就能被保存在字节码的常量池中。其实你会发现只要把在此之前的:

String d = "ab" + "d";

再一次编写翻译后再javap后如下:

Constant pool:
   #1 = Methodref          #6.#23         // java/lang/Object."<init>":()V
   #2 = String             #24            // ab
   #3 = Fieldref           #5.#25         // com/example/learn/LearnString.d:Ljava/lang/String;
   #4 = String             #26            // abc

澳门新萄京,那会儿我们得以窥见结果是同等的,相当于说compiler开掘这几个”+”操作完全能够在编写翻译阶段优化掉,compiler就能够进行自然的优化操作。
接下去大家得以谈谈开篇的那三个例子了。“abc”字符串依照上面所说,在编译的时候就被保留在字节码的常量池中了,所以a
和 b都以拿的常量池中的“abc”值(指向了同八个堆地址),故a==b为true;c =
new String(“abc”)
,它实际创制了八个对象,多个new出来的另三个即是常量“abc”,c的援用指向了new出来的指标,故a!=c。

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

那是String重写Object的equals方法,因而能够窥见equals方法是比较的是三个String对象里头的字符数组(char[]),比较的是堆中值而非堆地址,故a.equals(c)是相等。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图