字符串

1.不可变String

String类中每一个看起来会修改String值的方法,实际上都是创建了一个全新的String对象,已包含修改后的字符串内容。而最初的String对象则丝毫未动。

String s="abc";      "abc"   在字符串池里创建"abc"对象,把“abc”对象的引用赋给s;

s="edf";                     "edf"   并不是直接更改“abc”对象,而是到字符串池里去找“edf”对象,如果没有就创建一个“edf”对象,然后把该对象的引用赋给s,此过程“abc”对象没有发生改变;
String s2=“abc”;       并不是重新创建一个“abc”对象,而是到字符串池里去找“abc”对象,把它的引用赋给s2;                                                        

String s3=s2;        直接把对象引用赋给s3

字符串对象一经创建,永远不会发生改变。

import static net.mindview.util.Print.*;

public class Immutable {
  public static String upcase(String s) {//只有方法运行时,局部引用s才会存在。一旦方法运行结束,s就消失了
    return s.toUpperCase();//返回值是一个新的对象的引用
  }
  public static void main(String[] args) {
    String q = "howdy";
    print(q); // howdy
    String qq = upcase(q);//实际传递的是引用的一个拷贝,其实,每当把STring对象作为方法的参数时,都会复制一份引用,而该引用所指的对象其实一直待在单一的物理位置上,从未动过
    print(qq); // HOWDY
    print(q); // howdy,没有变
  }
}

 String类型对象不可变的原因

  1、java.lang.String类型在实现时,其内部成员变量全部使用final来修饰,保证成员变量的引用值只能通过构造函数来修改;

  2、java.lang.String类型在实现时,在外部可能修改其内部存储值的函数实现中,返回时一律构造新的String对象或者新的byte数组或者char数组;

仅凭第1点还不能保证其不可变特性:假如通过String类型的toCharArray方法可以直接访问String类型内部定义的char数组,那么即便String类型内部的char数组使用了final来修饰,也仅仅保证这个成员变量的引用不可变,而无法保证引用指向的内存区域不可变。

第2点保证了外部不可能修改java.lang.String类型对象的内部属性,从而保证String对象是不可变的。

2.重载“+”与StringBuilder

操作符“+”可以用来连接String

public class Concatenation {
  public static void main(String[] args) {
    String mango = "mango";
    String s = "abc" + mango + "def" + 47;
    System.out.println(s);
  }
}

如果想看看以上代码到底是如何工作的,可以用JDK自带的工具javap来反编译以上代码,命令如下:
javap -c Concatenation 这里-c标志表示将生成JVM字节码
以下就是部分字节码:

编译器创建了一个StringBuilder对象,用以构造最终的String,并未每个字符串调用一次StringBuilder的append()方法,最后调用toString()生成结果

 

让我们更深入地看编译器能为我们优化到什么程度

public class WhitherStringBuilder {
  public String implicit(String[] fields) {
    String result = "";
    for(int i = 0; i < fields.length; i++)
      result += fields[i];
    return result;
  }
  public String explicit(String[] fields) {
    StringBuilder result = new StringBuilder();
    for(int i = 0; i < fields.length; i++)
      result.append(fields[i]);
    return result.toString();
  }
}

运行javap -p WhitherStringBuilder,可以看到两个方法对应的(简化过的)字节码
首先是implicit方法

 

第8行:对堆栈中的操作数进行“大于或等于的整数比较运算” ;循环结束时跳到38行
注:StringBuilder是在循环之内构造的,这意味着每经过循环一次,就要创建一个新的StringBuilder对象

explicit()方法对应的字节码

可以看到,不仅循环部分代码更简单、更简单,而且它只生成了一个StringBuilder对象。显示地创建StringBuilder还可以预先为其指定大小。如果你已经知道最终的字符串大概有多长,那预先指定StringBuilder的大小可以避免多次重新分配缓冲。

import java.util.*;

public class UsingStringBuilder {
  public static Random rand = new Random(47);
  public String toString() {
    StringBuilder result = new StringBuilder("[");
    for(int i = 0; i < 25; i++) {
      result.append(rand.nextInt(100));
      result.append(", ");
    }
    result.delete(result.length()-2, result.length());//删除最后的逗号和空格
    result.append("]");
    return result.toString();
  }
  public static void main(String[] args) {
    UsingStringBuilder usb = new UsingStringBuilder();
    System.out.println(usb);
  }
} /* Output:
[58, 55, 93, 61, 61, 29, 68, 0, 22, 7, 88, 28, 51, 89, 9, 78, 98, 61, 20, 58, 16, 40, 11, 22, 4]
*///:~

 

3.无意识的递归

java中的每个类从根本上都是继承自Object,标准容器类自然也不例外。因此容器类都有toString()方法,并且覆写了该方法,使得它生成的String结果能够表达容器自身,已及容器所包含的对象。例如ArrayList.toString(),它会遍历ArrayList中包含的所有对象,调用每个元素上的toString()方法对应的字节码

如果你希望toString()方法打印对象的内存地址,也许你会考虑使用this关键字:

public class InfiniteRecursion {
  public String toString() {
    return " InfiniteRecursion address: " + this + "\n";//这里不该使用this,而是应该调用super.toString()
  }
  public static void main(String[] args) {
    List<InfiniteRecursion> v =
      new ArrayList<InfiniteRecursion>();
    for(int i = 0; i < 10; i++)
      v.add(new InfiniteRecursion());
    System.out.println(v);
  }
}

当你打印该对象时,会得到一长串的异常,原因是当如下代码" InfiniteRecursion address: " + this 运行时发生了自动类型转换,把InfiniteRecursion类型转换为String类型。因为编译器看到一个String对象后面跟着一个“+”,而后面跟着的不是String,就把this转换成一个String。转换方法就是调用this上的toString()方法,于是就发生了递归。
如果你真的想要打印出对象的内存地址,应该调用Object.toString()方法,所以这里不该使用this,而是应该调用super.toString()方法。

 

4.格式化输出

1)System.out.format()

可用于PrintStream和PrintWriter对象 

format()与c语言中的printf()类似 

public class SimpleFormat {
  public static void main(String[] args) {
    int x = 5;
    double y = 5.332542;
    // The old way:
    System.out.println("Row 1: [" + x + " " + y + "]");//Row 1: [5 5.332542]
    // The new way:
    System.out.format("Row 1: [%d %f]\n", x, y);//Row 1: [5 5.332542]
    // or
    System.out.printf("Row 1: [%d %f]\n", x, y);//Row 1: [5 5.332542]
  }
} 

2.Formatter类

在java中,所有新的格式化功能都有java.util.Formatter类处理。当你创建一个Formatter对象的时候,需要向其构造器传递一些信息,告诉它最终的结果将向哪里输出

import java.io.*;
import java.util.*;

public class Turtle {
  private String name;
  private Formatter f;
  public Turtle(String name, Formatter f) {
    this.name = name;
    this.f = f;
  }
  public void move(int x, int y) {
    f.format("%s The Turtle is at (%d,%d)\n", name, x, y);
  }
  public static void main(String[] args) {
    PrintStream outAlias = System.out;//输出到控制台
    Turtle tommy = new Turtle("Tommy",new Formatter(System.out));
    Turtle terry = new Turtle("Terry",new Formatter(outAlias));
    tommy.move(0,0);//Tommy The Turtle is at (0,0)
    terry.move(4,8);//Terry The Turtle is at (4,8)
  }
} 

 3.格式化说明符

width:控制一个域的最小尺寸

默认右对齐,有“-”表示左对齐
将precision应用于String时,表示打印String时输出字符的最大数量。而将precision应用于浮点数时,表示小数部分要显示出来的位数(默认是6为小数)

public class Receipt {
  private double total = 0;
  private Formatter f = new Formatter(System.out);
  public void printTitle() {
    f.format("%-15s %5s %10s\n", "Item", "Qty", "Price");
    f.format("%-15s %5s %10s\n", "----", "---", "-----");
  }
  public void print(String name, int qty, double price) {
    f.format("%-15.15s %5d %10.2f\n", name, qty, price);
    total += price;
  }
  public void printTotal() {
    f.format("%-15s %5s %10.2f\n", "Tax", "", total*0.06);
    f.format("%-15s %5s %10s\n", "", "", "-----");
    f.format("%-15s %5s %10.2f\n", "Total", "",total * 1.06);
  }
  public static void main(String[] args) {
    Receipt receipt = new Receipt();
    receipt.printTitle();
    receipt.print("Jack's Magic Beans", 4, 4.25);
    receipt.print("Princess Peas", 3, 5.1);
    receipt.print("Three Bears Porridge", 1, 14.29);
    receipt.printTotal();
  }
} /* Output:
Item              Qty      Price
----              ---      -----
Jack's Magic Be     4       4.25
Princess Peas       3       5.10
Three Bears Por     1      14.29
Tax                         1.42
                           -----
Total                      25.06
*///:~

4.Formatter转换

import java.math.*;
import java.util.*;

public class Conversion {
  public static void main(String[] args) {
    Formatter f = new Formatter(System.out);

    char u = 'a';
    System.out.println("u = 'a'");//u = 'a'
    f.format("s: %s\n", u);//s: a
    // f.format("d: %d\n", u);
    f.format("c: %c\n", u);//c: a
    f.format("b: %b\n", u);//b: true
    // f.format("f: %f\n", u);
    // f.format("e: %e\n", u);
    // f.format("x: %x\n", u);
    f.format("h: %h\n", u);//h: 61
    
    int v = 121;
    System.out.println("v = 121");//v = 121
    f.format("d: %d\n", v);//d: 121
    f.format("c: %c\n", v);//c: y
    f.format("b: %b\n", v);//b: true
    f.format("s: %s\n", v);//s: 121
    // f.format("f: %f\n", v);
    // f.format("e: %e\n", v);
    f.format("x: %x\n", v);//x: 79
    f.format("h: %h\n", v);//h: 79

    BigInteger w = new BigInteger("50000000000000");
    System.out.println("w = new BigInteger(\"50000000000000\")");//w = new BigInteger("50000000000000")
    f.format("d: %d\n", w);//d: 50000000000000
    // f.format("c: %c\n", w);
    f.format("b: %b\n", w);//b: true
    f.format("s: %s\n", w);//s: 50000000000000
    // f.format("f: %f\n", w);
    // f.format("e: %e\n", w);
    f.format("x: %x\n", w);//x: 2d79883d2000
    f.format("h: %h\n", w);//h: 8842a1a7

    double x = 179.543;
    System.out.println("x = 179.543");//x = 179.543
    // f.format("d: %d\n", x);
    // f.format("c: %c\n", x);
    f.format("b: %b\n", x);//b: true
    f.format("s: %s\n", x);//s: 179.543
    f.format("f: %f\n", x);//f: 179.543000
    f.format("e: %e\n", x);//e: 1.795430e+02
    // f.format("x: %x\n", x);
    f.format("h: %h\n", x);//h: 1ef462c

    Conversion y = new Conversion();
    System.out.println("y = new Conversion()");//y = new Conversion()
    // f.format("d: %d\n", y);
    // f.format("c: %c\n", y);
    f.format("b: %b\n", y);//b: true
    f.format("s: %s\n", y);//s: Conversion@9cab16
    // f.format("f: %f\n", y);
    // f.format("e: %e\n", y);
    // f.format("x: %x\n", y);
    f.format("h: %h\n", y);//h: 9cab16

    boolean z = false;
    System.out.println("z = false");//z = false
    // f.format("d: %d\n", z);
    // f.format("c: %c\n", z);
    f.format("b: %b\n", z);//b: false
    f.format("s: %s\n", z);//s: false
    // f.format("f: %f\n", z);
    // f.format("e: %e\n", z);
    // f.format("x: %x\n", z);
    f.format("h: %h\n", z);//h: 4d5
  }
}

5.String.format()

String.format()是一个static方法,它接受与Formatter.format()方法一样的参数,但返回一个String对象。当你只需使用format()方法一次的时候,String.format()用起来很方便。例如:

public class DatabaseException extends Exception {
  public DatabaseException(int transactionID, int queryID, String message) {
    super(String.format("(t%d, q%d) %s", transactionID,queryID, message));
  }
  public static void main(String[] args) {
    try {
      throw new DatabaseException(3, 7, "Write failed");
    } catch(Exception e) {
      System.out.println(e);
    }
  }
} /* Output:
DatabaseException: (t3, q7) Write failed
*/

 

posted @ 2018-05-11 11:15  邓不利多  阅读(130)  评论(0编辑  收藏  举报