字符串
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 */