Thinking in Java读书笔记(三)

第十一章 持有对象

  1. Java容器类库
    Java容器类库的用途是“保存对象”,这里划分两个概念:
    (1)Collection
      一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而set不能有重复的元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
    (2)Map
      一组成对的“键值对”对象,允许你使用键来查找值。
    在这里插入图片描述
    注:淡绿色表示接口,红色表示常用类。

  2. 泛型和类型安全容器
    使用泛型可以在编译器防止将错误类型的对象放置到容器中。
    使用方法是在容器后用尖括号括起类型参数(可以有多个),它指定了这个容器实例可以保存的类型,如ArrayList<Type>。并且这个类型可以向上转型。

  3. Collection添加一组元素:
    (1) Arrays.asList():接受一个数组或是一个用逗号分隔的元素列表(使用可变参数),并将其转换为一个List对象。
    (2) Collections.addAll():接受一个Collection对象,以及一个数组或是一个用逗号分割的列表,将元素添加到Collection中。
    (3) Collection.addAll():将一个Collection对象全部添加进去。只能接受Collection对象作为参数,不如前两种使用可变参数列表的灵活。

    import java.util.*;
    
    public class AddingGroups {
    	public static void main(String[] args) {
    		Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));
    		Integer[] moreInts = {6, 7, 8, 9, 10};
    		collection.addAll(Arrays.asList(moreInts));
    		//Run significantly faster, but you can't construct a Collection this way:
    		Collections.addAll(collection, 11, 12, 13, 14, 15);
    		Collections.addAll(collection, moreInts);
    		//Produces a list "backed by" an array:
    		List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);
    		list.set(1, 99); //OK -- modify an element
    		//list.add(21); //Running error because the underlying array cannot be resized.
    	}
    }
    

    Collection构造器可以接受另一个Collection,用它来将自身初始化,因此你可以使用Arrays.List()来为这个构造器产生输入。但是,Collection.addAll()方法运行起来要快得多,而且构建一个不包含元素的Collection,然后调用Collections.addAll()这种方式很方便,所以这才是首选办法。

  4. 容器的打印
    数组需要借助Arrays.toString()来打印表示,但打印容器无需任何帮助:

    import java.util.*;
    
    public class PrintingContainers {
    	static Collection fill(Collection<String> collection) {
    		collection.add("rat");
    		collection.add("cat");
    		collection.add("dog");
    		collection.add("dog");
    		return collection;
    	}
    	static Map fill(Map<String,String> map) {
    		map.put("rat", "Fuzzy");
    		map.put("cat", "Rags");
    		map.put("dog", "Bosco");
    		map.put("dog", "Spot");
    		return map;
    	}
    	public static void main(String[] args) {
    		System.out.println(fill(new ArrayList<String>()));
    		System.out.println(fill(new LinkedList<String>()));
    		System.out.println(fill(new HashSet<String>()));
    		System.out.println(fill(new TreeSet<String>()));
    		System.out.println(fill(new LinkedHashSet<String>()));
    		System.out.println(fill(new HashMap<String,String>()));
    		System.out.println(fill(new TreeMap<String,String>()));
    		System.out.println(fill(new LinkedHashMap<String,String>()));
    	}
    }
    /*Output:
    [rat, cat, dog, dog]
    [rat, cat, dog, dog]
    [rat, cat, dog]
    [cat, dog, rat]
    [rat, cat, dog]
    {rat=Fuzzy, cat=Rags, dog=Spot}
    {cat=Rags, dog=Spot, rat=Fuzzy}
    {rat=Fuzzy, cat=Rags, dog=Spot}
    */
    

    这里可以看出Collection和Map的区别在于每个“槽”保存的元素个数。Collection每个槽只能保存一个元素。而Map在每个槽内保存了两个对象,即键和与之相关联的值。Collection打印出来的内容用方括号括住,每个元素由逗号分隔。Map则用大括号括住,键与值由等号联系(键在等号左边,值在右边)。
    另外,该例子可以看出一些容器的特性:

    类型特性
    ArrayList, LinkedList插入顺序和输出顺序一致,可以重复
    HashSet, TreeSet插入顺序和输出顺序不一致,不重复
    LinkedHashSet插入顺序和输出顺序一致,不重复
    HashMap, TreeMap, LinkedHashMap键值对存储,键不重复
  5. List
    List承诺可以将元素维护在特定的序列中。List接口在Collection的基础上加入了大量的方法,使得可以在List中间可以插入和移除元素。有两种类型的List:
    (1) 基本的ArrayList,它长于随机访问元素,但是在List的中间插入和移除元素时比较慢。
    (2) LinkedList,它通过代价较低的在List中间进行得插入和删除操作,提供了优化的顺序访问。LinkedList在随机访问方面相对比较慢,但是它的特性集较ArrayList更大。
    与数组不同,List允许在它被创建之后添加元素、移除元素、或者自我调整尺寸,即一个可修改的序列。

    方法功能
    contains(Object obj)确定某个对象是否在列表中
    remove(int index) , remove(Object obj)移除一个对象(删除没有的对象返回false)
    indexOf(Object obj)返回该对象在List中所处位置的索引编号(找不到就返回-1)
    retainAll(Collection c)返回交集
    removeAll(Collection c)从列表中移除指定collection中包含的其所有元素
    set(int index, E element)设置下标为index的元素为element
    subList(int from_index, int to_index)截取下标从from_index到to_index的列表
    addALL(int index, Collection c)在下标index处插入指定collection
    toArray()转变为具有合适尺寸的数组
    add(int index, E element)在下标index处添加element
    get(int index)获取下标为index的值
  6. 迭代器
    迭代器是一个对象,它的工作是遍历并选择对象中的对象。
    Java的Iterator只能单向移动,这个Iterator只能用来:
    (1) 使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列的第一个元素。
    (2) 使用next()获得序列中的下一个元素。
    (3) 使用hasNext()检查序列中是否还有元素。
    (4) 使用remove()将迭代器新近返回的元素删除。
    迭代器统一了对容器的访问方式。

    ListIterator
    更加强大的Iterator子类型,只能用于各种List类的访问。ListIterator可以双向移动。
    (1) 使用方法listIterator()要求容器返回一个指向List开始处的ListIterator。使用方法listIterator(n)要求容器返回一个一开始指向List索引为n的元素处的ListIterator。
    (2) 使用next()获得序列中的下一个元素。使用previous()获得序列中的上一个元素。
    (3) 使用hasNext()检查序列中是否还有元素。使用hasPrevious()检查序列是否有上一个元素。
    (4) 使用remove()将迭代器新近返回的元素删除。
    (5) 使用set()修改迭代器新近返回的元素。

  7. LinkedList
    LinkedList也实现List接口,插入和删除操作比较高效,但在随机访问操作方面逊色一筹。LinkedList还添加了可以使其使用栈、队列或双端队列的语法。

    方法功能
    getFirst(), element()返回列表的第一个元素,而并不移除它,如果List为空,抛出NoSuchElementException
    peek()功能同上,列表为空时返回null
    removeFirst(), remove()移除并返回列表的第一个元素,列表为空时返回NoSuchElementException
    poll()功能同上,列表为空时返回null
    add(), addFirst(), addLast()将某个元素插入到列表的尾端
    removeLast()移除并返回列表的最后一个元素
  8. Set
    Set不保存重复的元素。查找是Set中最重要的操作,其中HashSet对快速查找进行了优化。最常见操作之一就是使用contains()测试Set的归属性。

  9. Map
    将对象映射到其他对象。与数组和其他Collection一样,Map很容易可以扩展到多维,而我们只需要将其值设置为Map(这些Map的值可以是其他容器,甚至是其他Map),例如:Map<Person, List< Pet >>。

  10. Queue
    队列是一个典型的先进先出(FIFO)容器。 队列常被当做一种可靠的将对象从程序的某各区域传输到另一个区域的途径。LinkedList提供了方法以支持队列的行为,并实现了Queue接口。可以将LinkedList向上转型为Queue以使用Queue的方法。offer()方法是与Queue相关的方法之一,它在允许的情况下,将一个元素插入到队尾,或者返回false。

    PriorityQueue
    优先级队列声明下一个弹出元素时最重要的元素(具有最高的优先级)。

  11. Foreach与迭代器
    foreach利用Itretrable接口在序列中移动,该接口包含一个能够产生Iterator的iterator()方法。所以创建任何实现Iterable的类都可以将它作用于foreach语句:

    import java.util.*;
    
    public class IterableClass implements Iterable<String> {
    
    	protected String[] words = ("And that is how " + "we know the Earth to be banana-shaped.").split(" ");
    
    	public Iterator<String> iterator() {
    		return new Iterator<String>() {
    			private int index = 0;
    			public boolean hasNext() { return index < words.length; }
    			public String next() { return words[ index++]; }
    			public void remove() { throw new UnsupportedOperationException(); }
    		};
    	}
    	
    	public static void main(String[] args) {
    		for(String s : new IterableClass())
    			System.out.print(s + " ");
    	}
    }
    /*Output:
    And that is how we know the Earth to be banana-shaped. 
    */
    

    在Java SE5中,大量的类都是Iterable类型,主要包括所有的Collection类(但是不包括各种Map)。

  12. Summary
    (1) 数组将数字与对象联系起来。 它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本类型的数据。但是,数组一旦生成,其容量就不能改变。
    (2) Collection保存单一的元素,Map保存相关联的键值对。 它们可以自动调整大小。
    (3) List和数组一样建立数字索引与对象的关联,因此,数组和List都是排好序的容器。List能自动扩容。
    (4) 如果要进行大量随机访问就用ArrayList,如果要经常从表中间插入或删除元素,则应该使用LinkedList。
    (5) 各种Queue以及栈的行为由LinkedList提供支持。
    (6) Map是一种将对象(而非数字)与对象相关联的设计。 HashMap设计用来快速访问,而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedHashMap保持元素插入的顺序,但是也通过散列提供了快速访问能力。
    (7) Set不接收重复元素。 HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态。LinkedHashSet以插入顺序保存元素。
    (8) 新程序中不应使用过时的Vector、Hashtable和Stack。

第十二章 通过异常处理错误

  1. 基本异常
    异常情形:exceptional condition,指阻止当前方法或作用域继续执行的问题。
    异常情形和普通问题的区别就是普通问题在当前环境下能够获得足够的信息去处理错误,而异常情形在当前环境下无法获得必要的信息来解决问题,所以不能继续下去。所能做的只是从当前环境跳出,并把问题交给上一级环境,这就是抛出异常时发生的事情。

    抛出异常后:
    (1) 首先,使用new在堆上创建异常对象。
    (2) 当前的执行路径(它不能继续下去了)被终止,且从当前环境中弹出对异常对象的引用。
    (3) 同时,异常处理机制接管程序,并开始寻找一个恰当的地方来继续执行程序。

    以上的恰当的地方指的就是异常处理程序,它的任务是将程序从错误状态中恢复,以使程序要么换一种方式运行,要么继续运行下去。

  2. 异常参数
    与java中的其他对象一样,我们用new在堆上创建异常对象,这伴随着存储空间的分配和构造器的调用。所有标准异常类都有两个构造器:一个是默认构造;另一个是接收字符串作为参数,以便能把相关信息放入异常对象的构造器:

    	throw new NullPointerException("t = null")
    

    可以抛出任意类型的Throwable对象,它是异常类型的根类。

  3. 捕获异常
    监控区域(guarded region):一段可能产生异常的代码,并且后面跟着处理这些异常的代码。
    try块:

    try {
    	// guarded region
    }
    
  4. 异常处理程序
    处理抛出异常的地点称为“异常处理程序”,针对每个捕获的异常都应该有相应的处理程序,异常处理程序紧跟try块之后,以关键字catch表示:

    try {
    	// guarded region
    } catch(Type1 id1) {
    	// Handle exceptions of Type1
    } catch(Type2 id2) {
    	// Handle exceptions of Type2
    } catch(Type3 id3) {
    	// Handle exceptions of Type3
    } 
    ...
    
  5. 创建自定义异常
    自定义异常的方式通常是继承java已有的异常类,只有默认构造器的简单自定义异常,编译器将自动调用基类的默认构造器:

    class SimpleException extends Exception {
    }
    
    public class InheritingException {
        public void f() throws SimpleException{
            System.out.println("Throw SimpleException from f()");
            throw new SimpleException();
        }
    
        public static void main(String[] args) {
            InheritingException sed = new InheritingException();
            try {
                sed.f();
            } catch (SimpleException e) {
                System.out.println("Caught it!");
            }
        }
    }
    /*Output:
    Throw SimpleException from f()
    Caught it!
    */
    

    接受字符串参数构造器的自定义异常:

    class MyException extends Exception {
        public MyException() {
        }
    
        public MyException(String message) {
            super(message);
        }
    }
    
    public class FullConstructors {
        public static void f() throws MyException {
            System.out.println("Throwing MyException from f()");
            throw new MyException();
        }
    
        public static void g() throws MyException {
            System.out.println("Throwing MyException from g()");
            throw new MyException("Originated in g()");
        }
    
        public static void main(String[] args) {
            try {
                f();
            } catch (MyException e) {
                e.printStackTrace(System.out);
            }
            try {
                g();
            } catch (MyException e) {
                e.printStackTrace(System.out);
            }
        }
    }
    /*Output:
    Throwing MyException from f()
    com.MyException
    	at com.FullConstructors.f(FullConstructors.java:15)
    	at com.FullConstructors.main(FullConstructors.java:25)
    Throwing MyException from g()
    com.MyException: Originated in g()
    	at com.FullConstructors.g(FullConstructors.java:20)
    	at com.FullConstructors.main(FullConstructors.java:30)
    */
    

    在异常处理程序中,调用了Throwable类声明的printStackTrace()方法。上例中错误信息发送到了System.out中,如果选用默认的e.printStackTrace()方法,结果将送到标准错误流System.err,因为System.out可能会被重定向。

  6. 异常与记录日志
    java.util.logging工具将输出记录到日志中。不作详述。

  7. 异常说明
    异常说明使用了throws关键字,后面接一个所有潜在异常类型的列表:

    void f() throws Exp1, Exp2, Exp3... { ... }
    
  8. 捕获所有异常
    通过捕获异常类型的基类Exception来实现:

    catch(Exception e) { ... }
    
  9. Java标准异常
    Throwable表示任何可以作为异常被抛出的类,有两种类型:
    (1) Error:表示编译时和系统错误,一般无需关心。
    (2) Exception:可被抛出的基本类型,在Java类库、用户方法以及运行时故障中都可能抛出Exception型异常。

  10. finally
    无论异常是否被抛出,finally子句都会被执行。语法:

    try {
    	// guarded region
    } catch(Type1 id1) {
    	// Handle exceptions of Type1
    } catch(Type2 id2) {
    	// Handle exceptions of Type2
    } catch(Type3 id3) {
    	// Handle exceptions of Type3
    } finally {
    	// Activities that happen every time
    }
    

    使用情况:当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally子句。
    由于finally子句总会被执行,所以在一个方法中可以从多个点返回,并且可以保证重要的清理工作被执行:

    public class MultipleReturns {
         public static void f(int i) {
           System.out.println("Initialization that requires cleanup");
           try {
           	System.out.println("Point 1");
           	if(i == 1) return;
           	System.out.println("Point 2");
           	if(i == 2) return;
           	System.out.println("Point 3");
           	if(i == 3) return;
           	System.out.println("End");
           	return;
           } finally {
           	System.out.println("Performing cleanup");
           }
         }
         public static void main(String[] args) {
           for(int i = 1; i <= 4; i++)
             f(i);
         }
    }
    /*Output:
    Initialization that requires cleanup
    Point 1
    Performing cleanup
    Initialization that requires cleanup
    Point 1
    Point 2
    Performing cleanup
    Initialization that requires cleanup
    Point 1
    Point 2
    Point 3
    Performing cleanup
    Initialization that requires cleanup
    Point 1
    Point 2
    Point 3
    End
    Performing cleanup
    */
    
  11. 异常使用指南
    应该在下列情况下使用指南:
    (1) 在恰当的级别处理问题。(在知道该如何处理的情况下才捕获异常)
    (2) 解决问题并且重新调用产生异常的方法。
    (3) 进行少许修补,然后绕过异常发生的地方继续执行。
    (4) 用别的数据进行计算,以代替方法虞姬会返回的值。
    (5) 把当前运行环境下能做的事情尽量做完,然后把相同的异常重抛到更高层。
    (6) 把当前运行环境下能做的事情尽量做完,然后把不同的异常抛到更高层。
    (7) 终止程序。
    (8) 进行简化。(如果你的异常模式使问题变得太复杂,那用起来会非常痛苦也很烦人。)
    (9) 让类库和程序更安全。

第十三章 字符串

  1. 不可变String
    String对象是不可变的,任何String类看起来会修改String值的方法实际上都是创建一个新的String对象。

  2. 重载“+”与StringBuilder
    在Java中,String可以通过重载的“+”和“+=”进行拼接,这是Java中唯一被重载过的两个操作符,而Java不允许程序员重载任何操作符。
    StringBuilder:Java SE5引入的比String更高效的字符串操作类,提供了insert(),replace(),substring(),reverse(),append(),toString(),delete()等方法。

  3. String上的操作

  4. 格式化输出
    System.out.format() 方法: format方法可以用于 PrintStream’ 和 PrintWriter 对象,其中也包括System.out对象。format()和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 + "]");		
    		// The new way:		
    		System.out.format("Row 1: [%d %f]\n", x, y);
    		// or		
    		System.out.printf("Row 1: [%d %f]\n", x, y);
    	}
    }  
    /*Output:
    Row 1: [5 5.332542]
    Row 1: [5 5.332542]
    Row 1: [5 5.332542]
    */
    

    java.util.Formatter 类:
    (1) 格式化功能都由 java.util.Formatter 类处理。
    (2) Formatter 是一个翻译器: 将格式化字符串与数据翻译成期望的结果。
    (3) Formatter 构造器需要传入目的地输出流参数。 最常用的目的地是: PrintStream、OutputStream 和 File。例子:

    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;
    		
    		// new Formatter(dest), 设置输出目的地
    		Turtle tommy = new Turtle("Tommy", new Formatter(System.out));
    		Turtle terry = new Turtle("Terry", new Formatter(outAlias));
    		
    		tommy.move(0, 0);
    		terry.move(4, 8);
    		tommy.move(3, 4);
    		terry.move(2, 5);
    		tommy.move(3, 3);
    		terry.move(3, 3);
    	}
    }  
    /*Output:
    Tommy The Turtle is at (0,0)
    Terry The Turtle is at (4,8)
    Tommy The Turtle is at (3,4)
    Terry The Turtle is at (2,5)
    Tommy The Turtle is at (3,3)
    Terry The Turtle is at (3,3)
    */
    

    格式化说明符:
    %[argument_index$][flags][width][.precision]conversion

    参数功能
    %表示需要进行格式化
    [argument_index$](可选)一个十进制整数,用于表明参数在参数列表中的位置。第一个参数由 "1$ " 引用,第二个参数由 "2$ " 引用,依此类推
    [flag](可选)默认右对齐,“-”表示左对齐
    [width](可选)宽度
    [.precision](可选)如果是字符串,表示打印输出的最大位数;如果作用于浮点数,则表示打印输出的小数点后的位数
    conversion(必需)s:字符串; d:数字; f:浮点数(和c语言有些相似,详见下表)

    类型转换字符

    参数类型
    d整数型(十进制)e浮点数(科学计数)
    cUnicode字符x整数(十六进制)
    bBoolean值h散列码(十六进制)
    sString%字符%
    f浮点数(十进制)

    注意:b转换对于各种类型都是合法的。对于boolean基本类型或Boolean对象,其转换结果是对应的true或false。但对于其他类型的参数,只要该参数不为null,那转换的结果就永远都是true,即使是数字0。

  5. 正则表达式
    在其他语言中,\\ 表示:我想要在正则表达式中插入一个普通的(字面上的)反斜杠,请不要给它任何特殊的意义。
    在 Java 中,\\ 表示:我要插入一个正则表达式的反斜线,所以其后的字符具有特殊的意义。
    所以,在其他的语言中(如Perl),一个反斜杠 \ 就足以具有转义的作用,而在 Java 中正则表达式中则需要有两个反斜杠才能被解析为其他语言中的转义作用。也可以简单的理解在 Java 的正则表达式中,两个 \\ 代表其他语言中的一个 \,这也就是为什么表示一位数字的正则表达式是 \\d,而表示一个普通的反斜杠是 \\\\
    一言难尽,请移步Java 正则表达式 | 菜鸟教程

  6. split()
    split()方法将输入字符串断开成字符串对象数组,断开边界由下列正则表达式确定:

    String[] split(CharSequence input)
    String[] split(CharSequence input, int limit) //limit可限定通过分割符分割后的字符串数量
    

    这是一个快速方便的方法,可以按照通用边界断开输入文本:

    import java.util.Arrays;
    import java.util.regex.Pattern;
    
    public class SplitDemo {
    
    	public static void main(String[] args) {
    		String input = "This!!unusual use!!of exclamation!!points";
    		System.out.println(Arrays.toString(Pattern.compile("!!").split(input)));
    		//Only do the first three:
    		System.out.println(Arrays.toString(Pattern.compile("!!").split(input, 3)));
    	}
    }
    /*Output:
    [This, unusual use, of exclamation, points]
    [This, unusual use, of exclamation!!points]
    */
    

    第二种形式的split()可以限制将输入分割成字符串的数量。

  7. Scanner类
    Java SE5新增了Scanner类,我们可以通过 Scanner 类来获取用户的输入。
    Scanner类的构造器可以接受任何类型的输入对象,包括File对象、InputStream、String或者Readable对象。
    Readable是Java SE5中新加入的一个接口,表示“具有read()方法的某种东西”。

第十四章 类型信息

  1. RTTI(Run-Time Type Identification)
    运行时类型识别技术。运行时类型信息使得你可以在程序运行时发现和使用类型信息。
    三种形式:
    (1) 传统的类型转换,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException异常。
    (2) 代表对象的类型的Class对象,通过查询Class对象可以获取运行时所需的信息。
    (3) 关键字instanceof,其返回一个布尔值,告诉我们对象是不是某个特定类型的实例。

  2. Class对象
    Class对象包含了与类有关的信息,用来创建类的所有的“常规”对象。Java使用Class对象来在执行其RTTI。
    当生成类的对象时,JVM将使用叫类加载器的子系统。类加载器首先检查这个类的Class对象是否已经加载,如果尚未加载,默认的类加载器就会根据类名查找.class文件。在这个类字节码被加载时,他们会接受验证,以确保其没有被破坏,并且不包含不良Java代码(这就是Java中用于安全防范目的的措施之一)。一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。
    forname()是取得Class对象的引用的一种方法,它是用一个包含目标类的文本名(注意区分大小写)的String作输入参数,返回的是一个Class对象的引用。用这个方法主要是为了:如果这个类没有被加载就加载它。如果Class.forname()找不到你要加载的类,它会抛出异常ClassNotFoundException。

  3. 类字面常量
    Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量。类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。另外,对于基本数据类型的包装器类,还有一个标准字段TYPE。TYPE字段是一个引用。指向对应的基本数据类型的Class对象,如下所示:
    在这里插入图片描述
    当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象,初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非final静态域(注意final静态域不会触发初始化操作)进行首次引用时才执行:。而使用Class.forName时会自动的初始化。

    为了使用类而做的准备工作实际包含三个步骤:
    (1) 加载:由类加载器执行。查找字节码,并从这些字节码中创建一个Class对象
    (2) 链接:验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。
    (3) 初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。

  4. Class<?>
    在Java SE5中,Class<?>优于平凡的Class,即使两个作用等价。好处在于你可以选择非具体的版本。

  5. instanceof与Class的等价性
    在查询类型信息时,以instanceof的形式(即以instanceof的形式或isInstance()的形式,他们产生同样的效果)与直接比较Class对象有一个很重要的差别。

    class Base {}
    class Derived extends Base {}	
    
    public class FamilyVsExactType {
      static void test(Object x) {
        System.out.println("Testing x of type " + x.getClass());
        System.out.println("x instanceof Base " + (x instanceof Base));
        System.out.println("x instanceof Derived "+ (x instanceof Derived));
        System.out.println("Base.isInstance(x) "+ Base.class.isInstance(x));
        System.out.println("Derived.isInstance(x) " +
          Derived.class.isInstance(x));
        System.out.println("x.getClass() == Base.class " +
          (x.getClass() == Base.class));
        System.out.println("x.getClass() == Derived.class " +
          (x.getClass() == Derived.class));
        System.out.println("x.getClass().equals(Base.class)) "+
          (x.getClass().equals(Base.class)));
        System.out.println("x.getClass().equals(Derived.class)) " +
          (x.getClass().equals(Derived.class)));
      }
      public static void main(String[] args) {
        test(new Base());
        test(new Derived());
      }	
    } /* Output:
    Testing x of type class typeinfo.Base
    x instanceof Base true
    x instanceof Derived false
    Base.isInstance(x) true
    Derived.isInstance(x) false
    x.getClass() == Base.class true
    x.getClass() == Derived.class false
    x.getClass().equals(Base.class)) true
    x.getClass().equals(Derived.class)) false
    Testing x of type class typeinfo.Derived
    x instanceof Base true
    x instanceof Derived true
    Base.isInstance(x) true
    Derived.isInstance(x) true
    x.getClass() == Base.class false
    x.getClass() == Derived.class true
    x.getClass().equals(Base.class)) false
    x.getClass().equals(Derived.class)) true
    */
    

    instanceof和isInstanceof:指的是“你是这个类吗?你是这个类的派生类吗?”
    ==和equals():比较实际的Class对象,没有考虑继承。

  6. 反射
    当我们通过反射与一个未知类型的对象打交道时,JVM只是简单的检查这个对象,看看它属于那个特定的类(就像RTTI做的那样)。在用它做其它事情之前必须要先加载那个类的Class对象。因此,那个类的.class文件对于JVM必须是可以获得的:要么在本地机器上,要么可以通过网络获得。所以RTTI与反射之间的真正区别只在于,对RTTI来说,编译器在编译期间就打开和检查.class文件,而对于反射机制来说,.class文件在编译期间是不可获取的,只能在运行的时候打开和检查.class文件。
    Class类和java.lang.reflect.*类库一起对反射的概念进行了支持,该类库包含了Field(对应类的变量),Mechod(对应类的方法)以及Constructor(对应类的构造器)类。这些类型的对象是由JVM在运行时创建的,用以表示未知类中对应的成员。

第十五章 泛型

  1. 泛型
    泛型实现了参数化类型的概念,使代码可以应用于多种类型。泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编辑器来保证类型的正确性。Java泛型核心概念:告诉编译器想使用什么类型,然后编译器帮你处理一切细节。
    简单的泛型:

    class Automobile{}
    
    public class Holder3<T> {
      private T a;
      public Holder3(T a) { this.a = a; }
      public void set(T a) { this.a = a; }
      public T get() { return a; }
      public static void main(String[] args) {
        Holder3<Automobile> h3 =
          new Holder3<Automobile>(new Automobile());
        Automobile a = h3.get(); // No cast needed
        // h3.set("Not an Automobile"); // Error
        // h3.set(1); // Error
      }
    }
    

    除普通类之外,泛型也可以应用于接口、内部类、匿名内部类。
    Java泛型是使用擦除来实现的,这意味着当你使用泛型时,任何具体的类型信息都被擦除,你唯一知道的就是你在使用一个对象。在泛型代码内部,无法获得任何有关泛型参数类型的信息。
    任何基本类型都不能作为类型参数。

  2. 元组
    元组:将一组对象直接打包存储于其中的一个单一对象。这个容器对象允许读取其中元素,但不允许向其中存放新的对象。元组可以具有任意长度,同时元组中的对象可以是任意不同的类型。

  3. 泛型方法
    是否拥有泛型方法,与其所在的类是否是泛型没有关系。要定义泛型方法,只需将泛型参数列表置于返回值之前:

    public class GenericMethods {
      public <T> void f(T x) {
        System.out.println(x.getClass().getName());
      }
      public static void main(String[] args) {
        GenericMethods gm = new GenericMethods();
        gm.f("");
        gm.f(1);
        gm.f(1.0);
        gm.f(1.0F);
        gm.f('c');
        gm.f(gm);
      }
    } /* Output:
    java.lang.String
    java.lang.Integer
    java.lang.Double
    java.lang.Float
    java.lang.Character
    GenericMethods
    */
    

    可变参数与泛型方法共存:

    import java.util.*;
    public class GenericVarargs {
      public static <T> List<T> makeList(T... args) {
        List<T> result = new ArrayList<T>();
        for(T item : args)
          result.add(item);
        return result;
      }
      
      public static void main(String[] args) {
        List<String> ls = makeList("A");
        System.out.println(ls);
        ls = makeList("A", "B", "C");
        System.out.println(ls);
        ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
        System.out.println(ls);
      }
    } /* Output:
    [A]
    [A, B, C]
    [, A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
    */
    
  4. 类型通配符
    (1) 类型通配符一般是使用?代替具体的类型参数。例如 List<?> 在逻辑上是List,List 等所有List<具体类型实参>的父类。
    (2) <? extends T>表示该通配符所代表的类型是T类型的子类。
    (3) <? super T>表示该通配符所代表的类型是T类型的父类。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值