The Java™ Tutorials——(0)Learning the Java Language

文章目录

0. IDEA环境

0.1 查看源码快捷键

Alt + 7 查看类中有那些方法
Ctrl+F12 搜索当前类的方法或者字段
Ctrl+Shift+Alt+N 根据类型查找类

1. 语言基础

1.0 Getting Start

1.1 基本数据类型

8种基本数据类型:

boolean:数据类型用来表示1-bit的信息,但是它的大小并没有给出明确的定义(but its “size” isn’t something that’s precisely defined)

byte:8-bit有符号,【-128,127】 【-27~ 27,用来在大容量array来节省空间。

short:16-bit有符号。【-32,768,32,767】 【-216~216-1】

char: 16-bit Unicode(UTF-16) character。【’\u0000’’\uffff’】或者【065,535

int:32-bit有符号。【-231,231 -1】 。Java8及之后,能够使用Integer 类来表示32位无符号的整数【0232-1】。一些静态方法,例如compareUnsigned(int x, int y), divideUnsigned(int dividend, int divisor)支持int的无符号算数操作。

Integer integer = Integer.parseUnsignedInt("4294967295"/*2^32-1*/);
out.println(integer);//-1

long:64-bit有符号。【-263263-1】。与int同理,它的包装类 Long也提供了相关无符号算数操作【0264-1】。

float:single-precision 32-bit IEEE 754 floating point。不应该用作精确度较高的领域,如货币,此时应该用 java.math.BigDecimal

out.println(""+(3*0.1));//0.30000000000000004
if(3*0.1 == 0.3) out.println("xxx");//false and will not print anything

double:double-precision 64-bit IEEE 754 floating point。double将作为小数的默认值。如下面代码:

float f =  2.0; // 报错Incompatible type. Required: float. Found:double.
float f2 = 2.0f;// 正确
float f3 = 2.0d;// 报错Incompatible type. Required: float. Found:double.

double d = 2;//这三行代码全正确
double d2 = 2.0f;
double d3 = 2.0d;

1.2 默认值

当变量作为类的字段而非局部变量时,它是有默认值的。(局部变量不赋初值编译时可能会报错)

int i;
//i = 0; 
println(""+i);//如果上一行去掉编译时会报错

类的字段的默认值:

Data TypeDefault Value (for fields)
byte0
short0
int0
long0L
float0.0f
double0.0d
char‘\u0000’
String (or any object)null
booleanfalse

1.3 整数字面量(Integer Literals)

long -> 2l or 1L
float -> 2.0F or 2.0f // 如果不加f/F的话,默认为double
double -> 2.0D or 2.0d // 小数赋值的时候默认为double
E | e -> double d = 1.234e2; //123.4,e代表10的幂
26(十进制数)0x1a(零x | 十六进制)
032(零 | 八进制数)
0b11010(零b | 二进制)
long creditCardNumber = 1234_5678_9012_3456L// 使用下划线分隔整数

1.4 Array

byte[] anArrayOfBytes = /*new byte[]*/{1, 2, 3};
float anArrayOfFloats[] = /*new float[]*/{ 1, 2, 3 };
anArrayOfBytes.length; // 数组的长度
public static void arraycopy(Object src, int srcPos,//System 对数组拷贝操作支持的native方法 
                             Object dest, int destPos, int length)

java.utils.Arrays 工具类操作数组

// Arrays.copyOfRange(...)方法:
// 拷贝区间[from, to),to无上限,to>=from即可 
static char[] copyOfRange(char[] original, int from, int to){
    int newLength = to - from;
    if (newLength < 0) throw new IllegalArgumentException(from + " > " + to);
    char[] copy = new char[newLength];
    System.arraycopy(original, from, copy, 0,
                     Math.min(original.length - from, newLength));
    return copy;
}
// Arrays.binarySearch(...)
// 注意public, private,在使用二分查找之前要确保Array有序
public static int binarySearch(int[] a, int key) {
	return binarySearch0(a, 0, a.length, key);
}
public static int binarySearch(int[] a, int fromIndex, int toIndex, int key) {
    rangeCheck(a.length, fromIndex, toIndex);
    return binarySearch0(a, fromIndex, toIndex, key);
}
private static int binarySearch0(int[] a, int fromIndex, int toIndex, int key) {
	int low = fromIndex;
    int high = toIndex - 1;
    while (low <= high) {
        int mid = (low + high) >>> 1;
        int midVal = a[mid];
        if (midVal < key) low = mid + 1;
        else if (midVal > key) high = mid - 1;
        else return mid; // key found
    }
    return -(low + 1);  // key not found.
}
//Arrays.equals(...)
public static boolean equals(int[] a, int[] a2) {
    if (a==a2) return true;//首先判引用
    if (a==null || a2==null) return false;//判空null
    int length = a.length;
    if (a2.length != length) return false;//判长度
    for (int i=0; i<length; i++) 
        if (a[i] != a2[i]) return false;//判相等
    return true;
}
//Arrays.fill(...) 给数组特定的区间填充val
public static void fill(int[] a, int val) {
    for (int i = 0, len = a.length; i < len; i++) a[i] = val;
}
public static void fill(int[] a, int fromIndex, int toIndex, int val) {
    rangeCheck(a.length, fromIndex, toIndex);
    for (int i = fromIndex; i < toIndex; i++) a[i] = val;
}
//Arrays.sort(...) 普通串行排序
//Arrays.parallelSort(...) Java8,发挥多处理器优势,并行排序
public static void sort(int[] a) {
    DualPivotQuicksort.sort(a, 0, a.length - 1, null, 0, 0);//双轴排序
    //Dual:双;Pivot:枢纽
}
package java.util;

/**
 * This class implements the Dual-Pivot Quicksort algorithm by
 * Vladimir Yaroslavskiy, Jon Bentley, and Josh Bloch. The algorithm
 * offers O(n log(n)) performance on many data sets that cause other
 * quicksorts to degrade to quadratic performance, and is typically
 * faster than traditional (one-pivot) Quicksort implementations.
 *
 * All exposed methods are package-private, designed to be invoked
 * from public methods (in class Arrays) after performing any
 * necessary array bounds checks and expanding parameters into the
 * required forms.
 *
 * @author Vladimir Yaroslavskiy
 * @author Jon Bentley
 * @author Josh Bloch
 *
 * @version 2011.02.11 m765.827.12i:5\7pm
 * @since 1.7
 */
final class DualPivotQuicksort {

    /**
     * Prevents instantiation.
     */
    private DualPivotQuicksort() {}
	
    /*
	...
	*/
    
    /**
     * Sorts the specified range of the array using the given
     * workspace array slice if possible for merging
     *
     * @param a the array to be sorted
     * @param left the index of the first element, inclusive, to be sorted
     * @param right the index of the last element, inclusive, to be sorted
     * @param work a workspace array (slice)
     * @param workBase origin of usable space in work array
     * @param workLen usable size of work array
     */
    static void sort(int[] a, int left, int right,
                     int[] work, int workBase, int workLen) {
        // Use Quicksort on small arrays
        if (right - left < QUICKSORT_THRESHOLD) {
            sort(a, left, right, true);
            return;
        }

        /*
         * Index run[i] is the start of i-th run
         * (ascending or descending sequence).
         */
        int[] run = new int[MAX_RUN_COUNT + 1];
        int count = 0; run[0] = left;

        // Check if the array is nearly sorted
        for (int k = left; k < right; run[count] = k) {
            if (a[k] < a[k + 1]) { // ascending
                while (++k <= right && a[k - 1] <= a[k]);
            } else if (a[k] > a[k + 1]) { // descending
                while (++k <= right && a[k - 1] >= a[k]);
                for (int lo = run[count] - 1, hi = k; ++lo < --hi; ) {
                    int t = a[lo]; a[lo] = a[hi]; a[hi] = t;
                }
            } else { // equal
                for (int m = MAX_RUN_LENGTH; ++k <= right && a[k - 1] == a[k]; ) {
                    if (--m == 0) {
                        sort(a, left, right, true);
                        return;
                    }
                }
            }

            /*
             * The array is not highly structured,
             * use Quicksort instead of merge sort.
             */
            if (++count == MAX_RUN_COUNT) {
                sort(a, left, right, true);
                return;
            }
        }

        // Check special cases
        // Implementation note: variable "right" is increased by 1.
        if (run[count] == right++) { // The last run contains one element
            run[++count] = right;
        } else if (count == 1) { // The array is already sorted
            return;
        }

        // Determine alternation base for merge
        byte odd = 0;
        for (int n = 1; (n <<= 1) < count; odd ^= 1);

        // Use or create temporary array b for merging
        int[] b;                 // temp array; alternates with a
        int ao, bo;              // array offsets from 'left'
        int blen = right - left; // space needed for b
        if (work == null || workLen < blen || workBase + blen > work.length) {
            work = new int[blen];
            workBase = 0;
        }
        if (odd == 0) {
            System.arraycopy(a, left, work, workBase, blen);
            b = a;
            bo = 0;
            a = work;
            ao = workBase - left;
        } else {
            b = work;
            ao = 0;
            bo = workBase - left;
        }

        // Merging
        for (int last; count > 1; count = last) {
            for (int k = (last = 0) + 2; k <= count; k += 2) {
                int hi = run[k], mi = run[k - 1];
                for (int i = run[k - 2], p = i, q = mi; i < hi; ++i) {
                    if (q >= hi || p < mi && a[p + ao] <= a[q + ao]) {
                        b[i + bo] = a[p++ + ao];
                    } else {
                        b[i + bo] = a[q++ + ao];
                    }
                }
                run[++last] = hi;
            }
            if ((count & 1) != 0) {
                for (int i = right, lo = run[count - 1]; --i >= lo;
                    b[i + bo] = a[i + ao]
                );
                run[++last] = right;
            }
            int[] t = a; a = b; b = t;
            int o = ao; ao = bo; bo = o;
        }
    }

    /**
     * Sorts the specified range of the array by Dual-Pivot Quicksort.
     *
     * @param a the array to be sorted
     * @param left the index of the first element, inclusive, to be sorted
     * @param right the index of the last element, inclusive, to be sorted
     * @param leftmost indicates if this part is the leftmost in the range
     */
    private static void sort(int[] a, int left, int right, boolean leftmost) {
        int length = right - left + 1;

        // Use insertion sort on tiny arrays
        if (length < INSERTION_SORT_THRESHOLD) {
            if (leftmost) {
                /*
                 * Traditional (without sentinel) insertion sort,
                 * optimized for server VM, is used in case of
                 * the leftmost part.
                 */
                for (int i = left, j = i; i < right; j = ++i) {
                    int ai = a[i + 1];
                    while (ai < a[j]) {
                        a[j + 1] = a[j];
                        if (j-- == left) {
                            break;
                        }
                    }
                    a[j + 1] = ai;
                }
            } else {
                /*
                 * Skip the longest ascending sequence.
                 */
                do {
                    if (left >= right) {
                        return;
                    }
                } while (a[++left] >= a[left - 1]);

                /*
                 * Every element from adjoining part plays the role
                 * of sentinel, therefore this allows us to avoid the
                 * left range check on each iteration. Moreover, we use
                 * the more optimized algorithm, so called pair insertion
                 * sort, which is faster (in the context of Quicksort)
                 * than traditional implementation of insertion sort.
                 */
                for (int k = left; ++left <= right; k = ++left) {
                    int a1 = a[k], a2 = a[left];

                    if (a1 < a2) {
                        a2 = a1; a1 = a[left];
                    }
                    while (a1 < a[--k]) {
                        a[k + 2] = a[k];
                    }
                    a[++k + 1] = a1;

                    while (a2 < a[--k]) {
                        a[k + 1] = a[k];
                    }
                    a[k + 1] = a2;
                }
                int last = a[right];

                while (last < a[--right]) {
                    a[right + 1] = a[right];
                }
                a[right + 1] = last;
            }
            return;
        }

        // Inexpensive approximation of length / 7
        int seventh = (length >> 3) + (length >> 6) + 1;

        /*
         * Sort five evenly spaced elements around (and including) the
         * center element in the range. These elements will be used for
         * pivot selection as described below. The choice for spacing
         * these elements was empirically determined to work well on
         * a wide variety of inputs.
         */
        int e3 = (left + right) >>> 1; // The midpoint
        int e2 = e3 - seventh;
        int e1 = e2 - seventh;
        int e4 = e3 + seventh;
        int e5 = e4 + seventh;

        // Sort these elements using insertion sort
        if (a[e2] < a[e1]) { int t = a[e2]; a[e2] = a[e1]; a[e1] = t; }

        if (a[e3] < a[e2]) { int t = a[e3]; a[e3] = a[e2]; a[e2] = t;
            if (t < a[e1]) { a[e2] = a[e1]; a[e1] = t; }
        }
        if (a[e4] < a[e3]) { int t = a[e4]; a[e4] = a[e3]; a[e3] = t;
            if (t < a[e2]) { a[e3] = a[e2]; a[e2] = t;
                if (t < a[e1]) { a[e2] = a[e1]; a[e1] = t; }
            }
        }
        if (a[e5] < a[e4]) { int t = a[e5]; a[e5] = a[e4]; a[e4] = t;
            if (t < a[e3]) { a[e4] = a[e3]; a[e3] = t;
                if (t < a[e2]) { a[e3] = a[e2]; a[e2] = t;
                    if (t < a[e1]) { a[e2] = a[e1]; a[e1] = t; }
                }
            }
        }

        // Pointers
        int less  = left;  // The index of the first element of center part
        int great = right; // The index before the first element of right part

        if (a[e1] != a[e2] && a[e2] != a[e3] && a[e3] != a[e4] && a[e4] != a[e5]) {
            /*
             * Use the second and fourth of the five sorted elements as pivots.
             * These values are inexpensive approximations of the first and
             * second terciles of the array. Note that pivot1 <= pivot2.
             */
            int pivot1 = a[e2];
            int pivot2 = a[e4];

            /*
             * The first and the last elements to be sorted are moved to the
             * locations formerly occupied by the pivots. When partitioning
             * is complete, the pivots are swapped back into their final
             * positions, and excluded from subsequent sorting.
             */
            a[e2] = a[left];
            a[e4] = a[right];

            /*
             * Skip elements, which are less or greater than pivot values.
             */
            while (a[++less] < pivot1);
            while (a[--great] > pivot2);

            /*
             * Partitioning:
             *
             *   left part           center part                   right part
             * +--------------------------------------------------------------+
             * |  < pivot1  |  pivot1 <= && <= pivot2  |    ?    |  > pivot2  |
             * +--------------------------------------------------------------+
             *               ^                          ^       ^
             *               |                          |       |
             *              less                        k     great
             *
             * Invariants:
             *
             *              all in (left, less)   < pivot1
             *    pivot1 <= all in [less, k)     <= pivot2
             *              all in (great, right) > pivot2
             *
             * Pointer k is the first index of ?-part.
             */
            outer:
            for (int k = less - 1; ++k <= great; ) {
                int ak = a[k];
                if (ak < pivot1) { // Move a[k] to left part
                    a[k] = a[less];
                    /*
                     * Here and below we use "a[i] = b; i++;" instead
                     * of "a[i++] = b;" due to performance issue.
                     */
                    a[less] = ak;
                    ++less;
                } else if (ak > pivot2) { // Move a[k] to right part
                    while (a[great] > pivot2) {
                        if (great-- == k) {
                            break outer;
                        }
                    }
                    if (a[great] < pivot1) { // a[great] <= pivot2
                        a[k] = a[less];
                        a[less] = a[great];
                        ++less;
                    } else { // pivot1 <= a[great] <= pivot2
                        a[k] = a[great];
                    }
                    /*
                     * Here and below we use "a[i] = b; i--;" instead
                     * of "a[i--] = b;" due to performance issue.
                     */
                    a[great] = ak;
                    --great;
                }
            }

            // Swap pivots into their final positions
            a[left]  = a[less  - 1]; a[less  - 1] = pivot1;
            a[right] = a[great + 1]; a[great + 1] = pivot2;

            // Sort left and right parts recursively, excluding known pivots
            sort(a, left, less - 2, leftmost);
            sort(a, great + 2, right, false);

            /*
             * If center part is too large (comprises > 4/7 of the array),
             * swap internal pivot values to ends.
             */
            if (less < e1 && e5 < great) {
                /*
                 * Skip elements, which are equal to pivot values.
                 */
                while (a[less] == pivot1) {
                    ++less;
                }

                while (a[great] == pivot2) {
                    --great;
                }

                /*
                 * Partitioning:
                 *
                 *   left part         center part                  right part
                 * +----------------------------------------------------------+
                 * | == pivot1 |  pivot1 < && < pivot2  |    ?    | == pivot2 |
                 * +----------------------------------------------------------+
                 *              ^                        ^       ^
                 *              |                        |       |
                 *             less                      k     great
                 *
                 * Invariants:
                 *
                 *              all in (*,  less) == pivot1
                 *     pivot1 < all in [less,  k)  < pivot2
                 *              all in (great, *) == pivot2
                 *
                 * Pointer k is the first index of ?-part.
                 */
                outer:
                for (int k = less - 1; ++k <= great; ) {
                    int ak = a[k];
                    if (ak == pivot1) { // Move a[k] to left part
                        a[k] = a[less];
                        a[less] = ak;
                        ++less;
                    } else if (ak == pivot2) { // Move a[k] to right part
                        while (a[great] == pivot2) {
                            if (great-- == k) {
                                break outer;
                            }
                        }
                        if (a[great] == pivot1) { // a[great] < pivot2
                            a[k] = a[less];
                            /*
                             * Even though a[great] equals to pivot1, the
                             * assignment a[less] = pivot1 may be incorrect,
                             * if a[great] and pivot1 are floating-point zeros
                             * of different signs. Therefore in float and
                             * double sorting methods we have to use more
                             * accurate assignment a[less] = a[great].
                             */
                            a[less] = pivot1;
                            ++less;
                        } else { // pivot1 < a[great] < pivot2
                            a[k] = a[great];
                        }
                        a[great] = ak;
                        --great;
                    }
                }
            }

            // Sort center part recursively
            sort(a, less, great, false);

        } else { // Partitioning with one pivot
            /*
             * Use the third of the five sorted elements as pivot.
             * This value is inexpensive approximation of the median.
             */
            int pivot = a[e3];

            /*
             * Partitioning degenerates to the traditional 3-way
             * (or "Dutch National Flag") schema:
             *
             *   left part    center part              right part
             * +-------------------------------------------------+
             * |  < pivot  |   == pivot   |     ?    |  > pivot  |
             * +-------------------------------------------------+
             *              ^              ^        ^
             *              |              |        |
             *             less            k      great
             *
             * Invariants:
             *
             *   all in (left, less)   < pivot
             *   all in [less, k)     == pivot
             *   all in (great, right) > pivot
             *
             * Pointer k is the first index of ?-part.
             */
            for (int k = less; k <= great; ++k) {
                if (a[k] == pivot) {
                    continue;
                }
                int ak = a[k];
                if (ak < pivot) { // Move a[k] to left part
                    a[k] = a[less];
                    a[less] = ak;
                    ++less;
                } else { // a[k] > pivot - Move a[k] to right part
                    while (a[great] > pivot) {
                        --great;
                    }
                    if (a[great] < pivot) { // a[great] <= pivot
                        a[k] = a[less];
                        a[less] = a[great];
                        ++less;
                    } else { // a[great] == pivot
                        /*
                         * Even though a[great] equals to pivot, the
                         * assignment a[k] = pivot may be incorrect,
                         * if a[great] and pivot are floating-point
                         * zeros of different signs. Therefore in float
                         * and double sorting methods we have to use
                         * more accurate assignment a[k] = a[great].
                         */
                        a[k] = pivot;
                    }
                    a[great] = ak;
                    --great;
                }
            }

            /*
             * Sort left and right parts recursively.
             * All elements from center part are equal
             * and, therefore, already sorted.
             */
            sort(a, left, less - 1, leftmost);
            sort(a, great + 1, right, false);
        }
    }
    /*
............
    */
}

1.5 操作符

Operators(操作符)Precedence(优先级)
postfixexpr++ expr--
unary++expr --expr +expr -expr ~ !
multiplicative* / %
additive+ -
shift<< >> >>>
relational< > <= >= instanceof
equality== !=
bitwise AND&
bitwise exclusive OR^
bitwise inclusive OR|
logical AND&&
logical OR||
ternary? :
assignment= += -= *= /= %= &= ^= |= <<= >>= >>>=

注意点:

+/-前缀运算符表示正负

>>> 无符号移位,0补最高位

instanceof测试对象是否是一个类/子类/实现特定接口的类的实例,对null进行运算永远返回false

null instanceof someClass; // false

1.6 表达式(Expressions)

表达式:变量,操作符,方法调用,表达式都会计算出单个值。

1.7 语句(Statements)

一个语句组成一个完整的执行单元。

语句分三类:表达式语句(expression statements),声明语句(declaration statements),控制流语句( control flow statements

  • 表达式语句:通过在下列表达式后加能够组成一个语句。

    • 赋值表达式
    • ++ or –
    • 方法调用
    • 对象创建表达式
  • 声明语句

    • double aValue = 8933.234;
      
  • 控制流语句

1.8 代码块(Blocks)

0个或者多个阔在大括号{}里面的语句(Statements),

1.9 控制流语句(Control Flow Statements

  • 决策语句(decision-making):if-then, if-then-else, switch

    • switch支持的数据类型:
      • 四种基本数据类型:byteshort,char,int,
      • 包装类型:Byte, ShortCharacter, `Integer
      • String 类型,相当于调用String的equals方法,所以switch(s){//}中的s不能为null
      • enum 枚举类型
  • 循环语句(looping ):for, while, do-while

    //for能直接遍历Array,和Collections
    int[] numbers = {1,2,3,4,5,6,7,8,9,10};
    for (int item : numbers) System.out.println("Count is: " + item);
    
  • 分支语句(branching):break, continue, return

2. 类与对象

2.1 类

2.1.1类的三种常见组成部分

Fields,Constructor,Methods;字段,构造器,方法。

// 类的声明(Declaring):
class MyClass {
    // field, constructor, and 
    // method declarations
}
class MyClass extends MySuperClass implements YourInterface, YourAnotherInterface {
    // field, constructor, and
    // method declarations
}
2.1.2类中的三种变量要清楚

fields, local variables, or parameters字段,局部变量,函数传参

2.1.3成员函数六个组成部分
  • Modifiers 访问修饰符public等
  • The return type 返回值
  • The method name 方法名
  • The parameter list in parenthesis 参数列表
  • An exception list 异常列表
  • The method body, enclosed between braces 函数体
// 函数的签名(method signature):
// - method's name 方法名
// - parameter types 参数类型
2.1.4函数的重载(OverLoading)

重载:相同函数名,不同参数列表(相同函数名,不同函数签名)

例子:

public class DataArtist {
    /*...*/
    public void draw(String s) {/*...*/}
    public void draw(int i) {/*...*/}
    public void draw(double f) {/*...*/}
    public void draw(int i, double f) {/*...*/}
}

怎么重载?当函数名相同时,可以改变函数传参的数据类型参数个数

建议:保守使用重载,因为它可能会使代码可读性下降。

2.1.5构造器

当我们编写的类没有任何构造函数的类时,编译器会自动给我们添加一个无参的构造函数。默认的构造函数将会调用父类(如果没有显式的父类,那么父类就是Object,Object有无参构造器)的无参构造函数。在这种情况下,当父类没有无参构造器时,编译器将会出错。

Constructor

// 1. 当子类没有构造器,而父类有的时候,调用父类的
public static void main(String[] args) {
        Child c = new Child();// 输出Super Constructor
        c.println("Child");//输出Child
}

static class Child extends Super {
    void println(String str){ System.out.println(str); }
}

static class Super{
    Super(){ System.out.println("Super Constructor"); }
}
// 2. 当Child没有任何构造器的时候,默认调用Object的无参构造器
public static void main(String[] args) {
    Child c = new Child();// 没任何输出,成功创建对象
    c.println("Child");//Child
}

static class Child extends Super {
    void println(String str){ System.out.println(str); }
}

static class Super{
}
// 3. 当子类没有无参构造器,父类没有/有无参构造器,都会出错。
public static void main(String[] args) {
    Child c = new Child();//报错
}

static class Child extends Super {
    Child(String str){ println(str); }
    void println(String str){ System.out.println(str); }
}

static class Super{
}

总结:

  • Child类没有任何构造器,new Child()总会成功。
    • 父类有无参构造器,则调用父类的无参构造器
    • 父类无无参构造器,则调用Object类的无参构造器
  • Child有构造器,但是只有有参构造器。new Child()总会失败报错。
2.1.6任意数量的参数(varargs)
//典型的一个应用:
public PrintStream printf(String format, Object... args)

// 数据类型...名称 (三个点)
// 在函数体中访问的时候就像访问 Point[] points 一样。
// points.length访问参数的数量(长度), points[0]第一个参数
void print(Point...points){/**/}

public static void main(String[] args) {
    Point points[] = { new Point(1, 2), new Point(2, 3) };
    Point.print(points[0], points[1]);
    // [1, 2]
    // [2, 3]
}

static class Point{
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
    static void print(Point...points){
        for(int i = 0 ; i < points.length ; i++){
            System.out.printf("[%d, %d]\n", points[i].x, points[i].y);
        }
    };
}
2.1.7函数传引用类型的参数
// main函数中
// 整个程序运行后输出:
// Code 1. [3, 4]
// Code 2. [0, 0]
// Code 3. [3, 4]
// 拷贝,引用的拷贝。
public static void main(String[] args) {
    Point point = new Point(1, 2);
    movePoint(point, 3, 4);
    System.out.printf("Code 3. [%d, %d]\n", point.x, point.y);
}
static void movePoint(Point point, int x, int y) {
    // code to move origin of circle to x+deltaX, y+deltaY
    point.setX(x);
    point.setY(y);
    System.out.printf("Code 1. [%d, %d]\n", point.x, point.y);
    // code to assign a new reference to circle
    point = new Point(0, 0);
    System.out.printf("Code 2. [%d, %d]\n", point.x, point.y);
}
static class Point{
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
    /*...*/
    public void setX(int x) { this.x = x; }
    public void setY(int y) { this.y = y; }
}
/*
注意引用的意义
obj1
    ↘→
       内存地址
    ↗→
obj2

obj2 = null时,只是说obj2不指向了此引用地址。
此时obj1仍然有引用。
*/
public static void main(String[] args) {
        String[] students = new String[10];
        String studentName = "Peter Smith";
        students[0] = studentName;
        studentName = null;
        //此时students[0]的值是?
        System.out.println(students[0]);//Peter Smith

        Object obj1 = new Object();
        Object obj2 = new Object();
        println("obj1: " + obj1.hashCode() + ", obj2: " + obj2.hashCode());
        obj1 = obj2;
        println("obj1: " + obj1.hashCode() + ", obj2: " + obj2.hashCode());
        obj2 = null;
        println("obj1: " + obj1.hashCode() + ", obj2: " + obj2);
    }
/*而一般的对象与String对象又有一些不一样。*/
public class Ref {
    int value = 0;

    Ref(int value){ this.value = value; }

    public static void main(String[] args) {
        Ref rf1 = new Ref(1);
        Ref rf2 = new Ref(2);
        println(""+rf1.value);//1
        rf1 = rf2;
        println(""+rf1.value);//2
        rf2.value = 3;
        println(""+rf1.value);//3
    }
    public static void print(String s){  System.out.print(s); }
    public static void println(String...s){ print( (s.length == 0?"":s[0]) + "\n");  }
}

2.2 对象

Class提供Objects的BluePrint。使得用户能够从Class创建Object。

类是对象的模板,对象从模板上创建。

Point point = new Point(23, 94);
//声明(Declaration):声明一个变量,配有特定 对象类型 的 变量名
//实例化(Instantiation):关键字new,用来创建对象
//初始化(Initialization):关键字new后接一个构造函数调用,它用来初始化一个新的对象

//new操作符返回,一个由new操作后,得到的特定对象的引用。

记住,调用一个对象的方法就相当于给那个对象发送消息。

2.2.1 垃圾收集器简介(The Garbage Collector

一些面向对象的语言要求程序员实时跟踪他们程序中创建的对象,并且当它们不再被使用的时候显示地销毁它们。显示地管理内存很可能会产生错误。Java平台允许你在系统允许地情况下尽可能地创建多的对象,并且你并不需要顾虑销毁它们的事情。Java运行环境(JRE,Java runtime environment)会在它不再被使用的时候释放它们所占有的资源。这个过程就叫做垃圾收集(garbage collection.

下面我们都检测GC。当我们谈论GC选中某个对象了,那么我们就认为此对象会被GC回收。

当一个对象不再被引用的时候,他就被GC选中了。一个变量一般在离开其作用域的时候就会不再引用此对象。或者,我们能够显示地去除一个变量地引用,只要将此变量赋值为null即可。记住!程序可能对同一个对象,会有多个引用。GC选中此对象地条件是:所有引用此对象的变量都必须释放其对此对象的引用。

JRE的GC会周期性地自动去完成垃圾收集工作:当时机/时间到达时,GC会释放那些不再被引用对象所占有的内存。

2.3 有关类的更多信息

2.3.1 方法的返回值

这里讨论方法返回值类型为:Class ,Interface

covariant return type(协变返回值类型):返回值类型允许在和子类相同的方向上变化。

也能够返回一个接口,不过返回的这个对象必须要实现此接口。

2.3.2 this关键字

在实例方法,或者构造器中,this是当前对象的引用(当前对象:方法/构造器正被调用的那个对象)

  • 使用this来访问当前的成员变量/成员函数
  • 使用this来调用当前的构造函数this(...)this构造函数只能够出现在构造函数的第一条语句!!!
2.3.3 访问修饰符

类的声明:只能够是public或者没有修饰符,默认package-private

ModifierClass(当前类)Package(同一个包下)Subclass(子类)World
publicYYYY
protectedYYYN
no modifierYYNN
privateYNNN

no modifier也叫package-private。只能够当前类/当前包能够访问。

protected区别?protected范围为当前类/当前包/子类

访问等级选择的小建议。(Tips on Choosing an Access Level)

  • 1. 如果其它程序员使用你的类,你要确保它们不会滥用你的类。访问等级就能起到这个作用
  • **2.使用private ,除非你有好的原因不使用private **
  • 3. 避免public字段,除了常量。

Are private methods really safe? - Stack Overflow

2.3.4 类成员(Class Variables,Class Methods)

类变量,也叫静态成员变量,所有此类的实例都共有同一分此变量。static fields or class variables

任何此类的实例对象都能够操作类变量的值,另外,值得注意的是,我们不用创建类的实例,也可以操作类变量。

/*输出结果为:
Static Class 4, s1 Obj: 4, s2 Obj: 4, s3 Obj: 4
Static Class 1, s1 Obj: 1, s2 Obj: 1, s3 Obj: 1
Static Class 2, s1 Obj: 2, s2 Obj: 2, s3 Obj: 2
Static Class 3, s1 Obj: 3, s2 Obj: 3, s3 Obj: 3
Static Class 5, s1 Obj: 5, s2 Obj: 5, s3 Obj: 5
*/
public class Static {
    static int value = 0;
    
    static void println(Static s1, Static s2, Static s3){
        System.out.println("Static Class " + Static.value +
                            ", s1 Obj: " + s1.value +
                            ", s2 Obj: " + s2.value +
                            ", s3 Obj: " + s3.value);
    }
    
    public static void main(String[] args){
        Static s1 = new Static();
        Static s2 = new Static();
        Static s3 = null;
        Static.value = 4; println(s1, s2, s3);
        s1.value = 1;  println(s1, s2, s3);
        s2.value = 2;  println(s1, s2, s3);
        s3.value = 3;  println(s1, s2, s3);//注意:Static s3 = null;
        ((Static)null).value = 5; println(s1, s2, s3);//即使是null也能访问静态成员
    }
}

类方法

//推荐用法
ClassName.methodName(args)
//不推荐
instanceName.methodName(args)

注意事项!!

  • static函数(类函数)常用来访问静态成员字段
  • 实例方法能够访问:实例变量/实例函数;类变量/类方法
  • 类方法能够访问:类变量 / 类方法。
  • 类方法不能够直接访问:实例变量 / 实例方法。
  • 类方法中不能使用this关键字,因为没有给this来引用的实例
2.3.5 常量(Constants)

static + final 修饰,我们通常称为常数。 习惯上,大写+下划线_来定义常数变量名。

final变量不能改变,不然出现的错误为编译时错误(compile-time error)

compile-time constant: 基本数据类型/String类型的常量,它的值在编译的时候已知,那么编译器会将代码中的所有的此常量名替换成它的值。

2.3.6 初始化(Initializing Fields,Static Initialization Blocks,Initializing Instance Members)

变量初始化;静态代码块;

/*
注意:
除了在一行代码上初始化值为一个字面量,
还可以调用函数来初始化值。

输出如下。
显然先执行{静态的初始化},再执行{实例初始化},
而这两类中的语句,都是按照语句的先后顺序执行
staticInitFunc
Static Block...
instanceInitFunc
Instance Block...
Init Constructor
Instance Value: 0
Static Value: -2
*/
public class Init {

    private static int cur = 0;

    private int i = 0;

    private static int j = 1;

    private int instanceValueByFunc = instanceInitFunc();

    private static int staticValueByFunc = staticInitFunc();

    static {
        println("Static Block...");
        staticValueByFunc = -1;
    }

    {
        println("Instance Block...");
        instanceValueByFunc = 0;
        staticValueByFunc = -2;
    }

    private /*static*/ int instanceInitFunc(){// 初始化实例变量对 实例函数/静态函数 没要求
        println("instanceInitFunc");
        int sum = 0;
        for(int i = 1 ; i <= 10 ; i++ ) sum += i;
        return sum;
    }

    private static int staticInitFunc(){ // 初始化静态变量需要用静态函数
        println("staticInitFunc");
        int sum = 0;
        for(int i = 1 ; i <= 10 ; i++ ) sum += i;
        return sum;
    }

    Init(){
        println("Init Constructor");
    }

    public int getInstanceValue(){  return this.instanceValueByFunc; }

    public static int getStaticValue(){ return staticValueByFunc; }

    public static void println(String str){ System.out.println(str); }

    public static void main(String[] args) {
        Init init = new Init();
        System.out.println("Instance Value: " + init.getInstanceValue() + "\n" +
                           "Static Value: " + Init.getStaticValue() );
    }
}

2.4 嵌套类(Nested Classes)

Nested Classes分为静态的和非静态的。静态的我们称之为静态内嵌类(static nested classes,非静态的我们称之为内部类 inner classes

  • 内部类 有权 访问外部类的成员,即使它们是 private
  • 静态内部类,无权 访问外部类的成员。

我们知道外部类只能有两种访问修饰符(public,默认)。而内部类可以有四种(public,private,protected,默认)

2.4.1 为啥使用嵌套类
  • 组合那些只被用在一个地方的类
  • 加强封装
  • 代码更加可读/可维护
2.4.2 静态嵌套类(Static Nested Classes)

和静态类函数一样,静态内嵌类并不能直接访问外部类的实例变量/实例方法:它只能通过对象的引用来访问。

OuterClass.StaticNestedClass nestedObject =
     new OuterClass.StaticNestedClass();
2.4.3 内部类(Inner Classes)

内部类的实例化方法与静态嵌套类的实例化代码不一样。

内部类只能够在已经实例化的外部类的基础上new;内部类与实例化相关,所以内部类中不能定义任何静态成员。

//outerObject已经实例化了
OuterClass.InnerClass innerObject = outerObject.new InnerClass();

//下面给出一个例子,输出如下所示
//----------------StaticInnerClass----------------
//Local value: 14
//This.value: 0
//----------------InnerClass----------------
//Local value: 28
//This.value: 1
//OuterClass.this.value: -1
class OuterClass{

    int value = -1;

    public static class StaticInnerClass{//Static Nested Class
        int value = 0;
        StaticInnerClass(int value){  this.value = value; }

        void test(int value) {
            System.out.println("----------------StaticInnerClass----------------");
            System.out.println("Local value: " + value);
            System.out.println("This.value: " + this.value);
            //System.out.println("OuterClass.this.value: " + OuterClass.this.value);//这句出错
        }
    }

    public class InnerClass{//Inner Class
        int value = 1;
        InnerClass(int value){ this.value = value; }
        void test(int value){
            System.out.println("----------------InnerClass----------------");
            System.out.println("Local value: " + value);
            System.out.println("This.value: " + this.value);
            System.out.println("OuterClass.this.value: " + OuterClass.this.value);
        }
    }
}

public class StaticAndInner {
    public static void main(String[] args) {
        //可以直接单独地对内嵌静态类实例化
        OuterClass.StaticInnerClass cls1 = new OuterClass.StaticInnerClass(0);
        OuterClass outerClass = new OuterClass();
        OuterClass.InnerClass cls2 = outerClass.new InnerClass(1);//需先实例化outerClass

        cls1.test(14);
        System.out.println("\n");
        cls2.test(28);
    }
}

**能够使用内部类(Inner Classes)来实现Helper Classes。**处理UI事件,很有必要熟悉Inner Classes

public class DataStructure {
    
    // Create an array
    private final static int SIZE = 15;
    private int[] arrayOfInts = new int[SIZE];
    
    public DataStructure() {
        // fill the array with ascending integer values
        for (int i = 0; i < SIZE; i++) {
            arrayOfInts[i] = i;
        }
    }
    
    public void printEven() {
        
        // Print out values of even indices of the array
        DataStructureIterator iterator = this.new EvenIterator();
        while (iterator.hasNext()) {
            System.out.print(iterator.next() + " ");
        }
        System.out.println();
    }
    
    interface DataStructureIterator extends java.util.Iterator<Integer> { } //继承接口

    // Inner class implements the DataStructureIterator interface,
    // which extends the Iterator<Integer> interface
    
    private class EvenIterator implements DataStructureIterator {//这里是private
        
        // Start stepping through the array from the beginning
        private int nextIndex = 0;
        
        public boolean hasNext() {
            
            // Check if the current element is the last in the array
            return (nextIndex <= SIZE - 1);
        }        
        
        public Integer next() {
            
            // Record a value of an even index of the array
            Integer retValue = Integer.valueOf(arrayOfInts[nextIndex]);//这里能直接引用arrayOfInts
            
            // Get the next even element
            nextIndex += 2;
            return retValue;
        }
    }
    
    public static void main(String s[]) {
        // Fill the array with integer values and print out only
        // values of even indices
        DataStructure ds = new DataStructure();
        ds.printEven();
    }
}

下面介绍两种特别地内部类:局部内部类类匿名内部类Local Classes,anonymous classes

2.4.4 局部类(Local Classes)
  • 声明一个局部内部类

能够被声明在任何一个块(block)里,例如方法体,for循环,if语句块。

这里我们拿方法体中地内部类讲。

public class LocalClassExample {
  
    static String regularExpression = "[^0-9]";
  
    public static void validatePhoneNumber(
        String phoneNumber1, String phoneNumber2) {
      
        final int numberLength = 10;
        
        // Valid in JDK 8 and later:
       
        // int numberLength = 10;
       
        class PhoneNumber {
            
            String formattedPhoneNumber = null;

            PhoneNumber(String phoneNumber){
                // numberLength = 7;
                String currentNumber = phoneNumber.replaceAll(
                  regularExpression, "");
                if (currentNumber.length() == numberLength)
                    formattedPhoneNumber = currentNumber;
                else
                    formattedPhoneNumber = null;
            }

            public String getNumber() {
                return formattedPhoneNumber;
            }
            
            // Valid in JDK 8 and later:

//            public void printOriginalNumbers() {
//                System.out.println("Original numbers are " + phoneNumber1 +
//                    " and " + phoneNumber2);
//            }
        }

        PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
        PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);
        
        // Valid in JDK 8 and later:

//        myNumber1.printOriginalNumbers();

        if (myNumber1.getNumber() == null) 
            System.out.println("First number is invalid");
        else
            System.out.println("First number is " + myNumber1.getNumber());
        if (myNumber2.getNumber() == null)
            System.out.println("Second number is invalid");
        else
            System.out.println("Second number is " + myNumber2.getNumber());

    }

    public static void main(String... args) {
        validatePhoneNumber("123-456-7890", "456-7890");
    }
}
  • Local classes能够访问局部变量外套块的参数,这些被访问的变量/参数叫做**“captured variable.”**

条件是这些变量/参数必须是***final 或者 effectively final**

  • 与Inner classes差不多,Local Classes也与具体的实例相关,所以并不能声明Static成员。

  • 不能在一个块中声明一个接口,因为接口本身是static的(inherently static)。

    public void greetInEnglish() {
        interface HelloThere {//接口是static的,不允许
            public void greet();
        }
        class EnglishHelloThere implements HelloThere {
            public void greet() {
                System.out.println("Hello " + name);
            }
        }
        HelloThere myGreeting = new EnglishHelloThere();
        myGreeting.greet();
    }
    
  • 局部内部类不能有静态声明,当然,能够有常量(基本数据类型/String类型的常量)

    public void sayGoodbyeInEnglish() {
        class EnglishGoodbye {
            static int x = 0; // 这两行都出错,不能有静态声明
            public static void sayGoodbye() {//Inner classes cannot have static declarations
                System.out.println("Bye bye");
            }
        }
        EnglishGoodbye.sayGoodbye();
    }
    // 下面是正确的声明
    public void sayGoodbyeInEnglish() {
        class EnglishGoodbye {
            static final int x = 0;//常量,允许
            static final String s = "Bye bye";//常量,允许
            public void sayGoodbye() {//非静态,允许
                System.out.println("Bye bye");
            }
        }
        EnglishGoodbye goodbye = new EnglishGoodbye();
        goodbye.sayGoodbye();//Bye bye
        System.out.println(goodbye.x + ", " + goodbye.s);//0, Bye bye
    }
    
2.4.5 匿名类(Anonymous Classes)
  • 使代码更简介:在同一时间声明,实例化一个类的对象。
  • 除了没有类名外,访问/声明性质与Local Classes类似。
  • 如果你只需要使用一次local classes,那么可以选择使用匿名内部类
  • 匿名类是一个表达式(expression)
public class HelloWorldAnonymousClasses {
  
    interface HelloWorld {
        public void greet();
        public void greetSomeone(String someone);
    }
  
    public void sayHello() {

        class EnglishGreeting implements HelloWorld {//这里是一个Local Class
            String name = "world";
            public void greet() {
                greetSomeone("world");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hello " + name);
            }
        }
        
        HelloWorld englishGreeting = new EnglishGreeting();
        
        //Anonymous Class,因为是new一个Interface,所以必须实现其方法。
        HelloWorld chineseGreeting = new HelloWorld() {
            @Override
            public void greet() {
                //...
            }

            @Override
            public void greetSomeone(String someone) {
                //...
            }
        };
        
        HelloWorld frenchGreeting = new HelloWorld() {//Anonymous Class
            String name = "tout le monde";
            public void greet() {
                greetSomeone("tout le monde");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Salut " + name);
            }
        };
        
        HelloWorld spanishGreeting = new HelloWorld() {//Anonymous Class
            String name = "mundo";
            public void greet() {
                greetSomeone("mundo");
            }
            public void greetSomeone(String someone) {
                name = someone;
                System.out.println("Hola, " + name);
            }
        };
        englishGreeting.greet();
        frenchGreeting.greetSomeone("Fred");
        spanishGreeting.greet();
    }

    public static void main(String... args) {
        HelloWorldAnonymousClasses myApp =
            new HelloWorldAnonymousClasses();
        myApp.sayHello();
    }            
}
2.4.6 Lambda表达式(Lambda Expressions)

这个内容非常重要,并且这篇官方文档写得非常精彩:Lambda Expressions

注意点:

  • Local Class —> Anonymous Class ----> Lambda Expressions
  • 集合的流操作
  • 访问局部变量和外部作用域
  • 方法的引用
  • 两个接口Function<T, R>的R apply(T t)和Consumer的void accept(T t)
//Function<T, R> 接收T对象,返回R对象


//Consumer<T> 接收T对象,不返回值
/*  一个简单的例子  */
public class Calculator {
    interface IntegerMath {
        int operation(int a, int b);   
    }

    public int operateBinary(int a, int b, IntegerMath op) {
        return op.operation(a, b);
    }

    public static void main(String... args) {
        Calculator myApp = new Calculator();
        IntegerMath addition = (a, b) -> a + b;
        IntegerMath subtraction = (a, b) -> a - b;
        System.out.println("40 + 2 = " +
            myApp.operateBinary(40, 2, addition));
        System.out.println("20 - 10 = " +
            myApp.operateBinary(20, 10, subtraction));    
    }
}
import java.util.function.Consumer;

public class LambdaScopeTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {

            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // x = 99;
            
            Consumer<Integer> myConsumer = (y) -> { // 这里(y),不能(x)。
                System.out.println("x = " + x); // Statement A
                System.out.println("y = " + y);
                System.out.println("this.x = " + this.x);
                System.out.println("LambdaScopeTest.this.x = " +
                    LambdaScopeTest.this.x);
            };
            myConsumer.accept(x);
        }
    }

    public static void main(String... args) {
        LambdaScopeTest st = new LambdaScopeTest();
        LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}
2.4.7 各种嵌套类应该在什么时候使用
  • Local Class:创建该类的多个实例,访问它的构造器,或者引入一个新的/有类名的类。(有类名是方便多次访问对象的成员)。
  • Anonymout Class:如果你需要声明字段或者额外的方法。
  • Lambda Expression:
    • 封装单个行为单元,并且你不像将它传递给其它的代码;
    • 只使用简单的函数接口实例;我们不需要构造器/类名/字段/额外的方法
  • Nested Class:
    • 要求与Local Class相似,但是你想要使这个类更加得广泛地被使用,并且你不需要访问局部变量和方法参数(local variables or method parameters)
    • 如果你需要访问外层类的non-public字段和方法,使用Inner Class;否则使用Static Nested Class。

2.5 枚举类型(Enum Type)

枚举类型是一种特殊的数据类型,适用于变量是一些预定义的常量集合。变量必须是预定义的常量集合中的一个。

Java中的枚举类型相比其它语言的枚举类型,强大得多。

我们前面讲了,习惯上,我们将常量名定义成大写字母。

Java中通过enum关键字来定义枚举类型。如:

// 例子1,怎么声明一个枚举数据类型
public enum Day {
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY 
}

// 例子2,测试枚举类型。
public class EnumTest {//calss 

    public enum Day {
        SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY 
	}
    
    Day day;
    
    public EnumTest(Day day) {
        this.day = day;
    }
    
    public void tellItLikeItIs() {
        switch (day) {
            case MONDAY:
                System.out.println("Mondays are bad.");
                break;
                    
            case FRIDAY:
                System.out.println("Fridays are better.");
                break;
                         
            case SATURDAY: case SUNDAY:
                System.out.println("Weekends are best.");
                break;
                        
            default:
                System.out.println("Midweek days are so-so.");
                break;
        }
    }
    
    public static void main(String[] args) {
        EnumTest firstDay = new EnumTest(Day.MONDAY);
        firstDay.tellItLikeItIs();
        EnumTest thirdDay = new EnumTest(Day.WEDNESDAY);
        thirdDay.tellItLikeItIs();
        EnumTest fifthDay = new EnumTest(Day.FRIDAY);
        fifthDay.tellItLikeItIs();
        EnumTest sixthDay = new EnumTest(Day.SATURDAY);
        sixthDay.tellItLikeItIs();
        EnumTest seventhDay = new EnumTest(Day.SUNDAY);
        seventhDay.tellItLikeItIs();
    }
}

// 例子3,枚举类,含有其它字段/方法。
// 1. 枚举类型的构造器必须是:package-private / private access
// 2. 我们不能自己去调用构造器,它会自动创建被定义在类最开始的常量。
// 3. 枚举类中的常量不能够有访问修饰符
// 4. 枚举类"隐式"继承自Enum类,由于单继承,所以它不能再继承任何类
// 5. 隐式有一个values()方法,返回类型为枚举类型的数组(如Planet[]),能够使用for来遍历全部的常量
public enum Planet {//枚举类
    MERCURY (3.303e+23, 2.4397e6), //不允许有访问修饰符
    VENUS   (4.869e+24, 6.0518e6),
    EARTH   (5.976e+24, 6.37814e6),
    MARS    (6.421e+23, 3.3972e6),
    JUPITER (1.9e+27,   7.1492e7),
    SATURN  (5.688e+26, 6.0268e7),
    URANUS  (8.686e+25, 2.5559e7),
    NEPTUNE (1.024e+26, 2.4746e7);

    private final double mass;   // in kilograms
    private final double radius; // in meters
    Planet(double mass, double radius) {
        this.mass = mass;
        this.radius = radius;
    }
    private double mass() { return mass; }
    private double radius() { return radius; }

    // universal gravitational constant  (m3 kg-1 s-2)
    public static final double G = 6.67300E-11;

    double surfaceGravity() {
        return G * mass / (radius * radius);
    }
    double surfaceWeight(double otherMass) {
        return otherMass * surfaceGravity();
    }
    public static void main(String[] args) {
        if (args.length != 1) {
            System.err.println("Usage: java Planet <earth_weight>");
            System.exit(-1);
        }
        double earthWeight = Double.parseDouble(args[0]);
        double mass = earthWeight/EARTH.surfaceGravity();
        for (Planet p : Planet.values())
           System.out.printf("Your weight on %s is %f%n",
                             p, p.surfaceWeight(mass));
    }
}

3. 注释(Annotations)

3.1 注释基础

注释不会对它们所标记的程序代码的操作有直接的影响。主要用处:

  • 提供给编译器信息。提供编译器检测错误,抑制警告的信息。
  • 编译/部署时的参考信息。软件工具能够根据注释来生成代码,xml文件等。
  • 运行时的参考信息。运行时检查。

Annotations是的Interface一种形式。

package basisLang.objclasses.annotations;
import java.lang.annotation.Documented;

@Documented  // @Documented:保证javac生成的文档可以看到@ClassPreamble(...)注释信息
@interface ClassPreamble{
    String author();
    String date();
    int currentVersion() default 1; //default的非必填,不填则取默认值
    String lastModified() default "N/A";
    String lastModifiedBy() default "N/A";
    String[] reviewers();
}

//----------------------------------------------------------------------------------//
package basisLang.objclasses.annotations;

@ClassPreamble(
        author = "Liture",
        date = "3/17/2018",
//      currentVersion = 1,       // default 的字段非必填
//      lastModified = "4/12/2004",
//      lastModifiedBy = "Liture",
        reviewers = {"Alice", "Bill", "Cindy"}
)
public class AnnotationsTest {
    // My code goes here.
}

3.2 Java SE API 预定义的注释

一个例子:

// @deprecated是给javadoc解析的
// @Deprecated是给javac编译器解析的

/**
 * @deprecated
 * explanation of why it was deprecated
 */
@Deprecated
static void deprecatedMethod() { }
// 重写父类的方法,不要求一定要使用@Override
// 但是如果不能够正确得重写父类得方法,编译器会报错
// 所以@Override是给编译器来检查信息的

@Override 
int overriddenMethod() { }

另外一个例子:

//------------------------------------------------------------//
public @interface Schedules {
    Schedule[] value();//数组
}

//------------------------------------------------------------//
@Repeatable(Schedules.class)
public @interface Schedule {
  String dayOfMonth() default "first";
  String dayOfWeek() default "Mon";
  int hour() default 12;

4.接口与继承(Interfaces and Inheritance)

4.1接口(Interface)

  • 接口,引用类型;能够只包含常数方法签名(抽象方法)默认方法静态方法嵌套类型

  • 拥有方法体的方法只能是默认方法静态方法

  • 接口不能被实例化,只能被Class来implements,或者Interface来extends。

  • 接口能够被作为工业标准API{ an industry standard Application Programming Interface (API) },认真看这段文字,很典型地描述了接口的作用。

    /*An example would be a package of digital image processing methods that are sold to companies making end-user graphics programs. The image processing company writes its classes to implement an interface, which it makes public to its customers. The graphics company then invokes the image processing methods using the signatures and return types defined in the interface. While the image processing company's API is made public (to its customers), its implementation of the API is kept as a closely guarded secret—in fact, it may revise the implementation at a later date as long as it continues to implement the original interface that its customers have relied on.
    接口是个标准,具体实现不会公开。外界通过接口来调用就行。*/
    
4.1.1定义接口

一个Interface能够extends多个接口,然而一个Class只能extends一个Class。

**所有的抽象/默认/静态方法,都隐式地是public修饰,**所以我们可以省略public,不能使用protected/private

所有常数都默认是public static final修饰,同样可以省略这些关键字。

interface Interface1{
    int E1 = 1;
}
interface Interface2{
    int E2 = 2;
}
public interface InterfaceNewer extends Interface1, Interface2 {
    int E3 = 3;
    void doSomething (int i, double x);
    int doSomethingElse(String s);
}
4.1.2实现接口

给个例子

// Relatable.java
public interface Relatable {
    // this (object calling isLargerThan)
    // and other must be instances of
    // the same class returns 1, 0, -1
    // if this is greater than,
    // equal to, or less than other
    int isLargerThan(Relatable other);//后面的实现上,我们定义:返回-2,代表other非同一个类
}
// RectanglePlus
public class RectanglePlus implements Relatable {
	
    /**
    ...一些代码省略
    */
    
    // a method for computing
    // the area of the rectangle
    public int getArea() {
        return width * height;
    }

    /**
     * 比较两个RectanglePlus的面积大小
     * @param other RectanglePlus
     * @return 1, 0, -1, -2 for greater than, equal to, or less than other, not a instance of RectanglePlus
     */
    @Override
    public int isLargerThan(Relatable other) {
        if(other instanceof RectanglePlus){//instance操作符在这里起作用了
            int thisArea = this.getArea();
            int otherArea = ((RectanglePlus) other).getArea();
            if(thisArea > otherArea)
                return 1;
            else if(thisArea == otherArea)
                return 0;
            else
                return -1;
        }
        return -2; // not a instance of class RectanglePlus
    }
}

// 测试类的主方法
public static void main(String[] args) {
    RectanglePlus rp1 = new RectanglePlus(new Point(-1, -1), 1, 1);
    RectanglePlus rp2 = new RectanglePlus(new Point(-1, -1), 1, 0);

    // 1. 符合,直接传相同的类RectanglePlus的实例
    System.out.println( rp1.isLargerThan(rp2) );

    // 2. 传一个实现类,但并非RectanglePlus实例
    System.out.println( rp1.isLargerThan( rp1.new Rectangle() ) );
    
    // 3. 同2,不过是通过lambda表达式的方式来传实例
    Relatable relatable = (other)->0;
    System.out.println( rp1.isLargerThan( relatable )); // lambda,直接传一个匿名内部类进去
    //or rp1.isLargerThan( (other)->0 );
}
// 测试类中的一个Relatable实现类
class Rectangle implements Relatable{

    @Override
    public int isLargerThan(Relatable other) {
        return 0;
    }
}
4.1.3使用接口作为数据类型
  • 定义一个接口,就相当于定义了一种新的引用数据类型;
  • 如果你定义了一个接口引用类型的变量,任何赋给此变量的对象,必须是一个对这个接口的一个具体实现类的实例。
  • 当类实现Relatable接口时,它既是{它们自己类/它们的父类}的数据类型,又是Relatable接口引用数据类型,实现具体的接口给了类"多继承"的特性。即:它们能够继承父类,接口的行为特性。
// 虽然obj是Relatable数据类型,但是它确是Relatable具体的实现类。
// 我们不关心obj1,obj2的继承关系是怎样;
// 我们关心它们是Relatable的实现类即可。
public Object findLargest(Object object1, Object object2) {//假定传入的obj1/obj2都是Relatable的实现类
   Relatable obj1 = (Relatable)object1;
   Relatable obj2 = (Relatable)object2;
   if ((obj1).isLargerThan(obj2) > 0)
      return object1;
   else 
      return object2;
}
4.1.4接口的演变

现在考虑一个场景,假设我们定义了一个接口,DoIt。

public interface DoIt {
   void doSomething(int i, double x);
   int doSomethingElse(String s);
}

现在,接口已经开始被许多其它的程序代码开始引用了;

过了一段时间,我们想要在此接口上添加一个新的方法,此时,接口逻辑上就应该演变成如下所示:

public interface DoIt {
   void doSomething(int i, double x);
   int doSomethingElse(String s);
   boolean didItWork(int i, double x, String s);//add a new method to the interface
}

但是,如此一改,实现此接口的所有类都需要进行改动——override此方法。

如果在真实项目中实现此接口的类非常多,那么将导致整个项目的代码的可维护性极差。

现在我们考虑如何对接口进行演变?这里提供两种方法:

  • 定义新接口extends旧接口。新接口能够重写旧接口的,下面给出extends能/不能做的操作
    • 继承父接口的默认方法,静态方法。
    • 重新声明默认方法,使其成为抽象方法。
    • 重写默认方法。
  • 使用static定义接口静态方法,那么程序员可认为这个方法为一个工具方法,不是必要的/核心的方法(utility methods, not as essential, core methods
  • 使用default定义接口默认方法
// Method 1. 
public interface DoItPlus extends DoIt {
   boolean didItWork(int i, double x, String s);
   //注意,这里能够重写旧的接口方法。
}
// Method 2 or 3.
public interface DoIt {
   void doSomething(int i, double x);
   int doSomethingElse(String s);
    
   default boolean didItWorkMethod2(int i, double x, String s) {
       // Method body 
   }
    
   static boolean didItWorkMethod3(int i, double x, String s) {
       // Method body
   }
}

4.2 继承(Inheritance)

除了Object没有父类外,每个类都有且只有一个直接父类(单继承);一个类没有显示地声明父类,那么它的父类就是Object。

子类会继承父类的 所有 成员(字段,方法,内嵌类),注意:构造器并不是成员,所以构造器不会被继承,但是父类的构造器能够在子类中调用。

子类会继承父类所有 public and protected 成员,不管子类所在的包在哪。如果子类所在的包与父类同包,则可继承父类的 package-private 成员。

  • 继承的字段能够直接使用,就像子类的自己定义的字段一样。
  • 声明和父类一样的字段, 隐藏(hiding) 父类的字段(不推荐
  • 定义一个实例方法重写(overriding )与父类有相同签名的方法。
  • 定义一个静态方法隐藏(hiding) 与父类有相同签名的方法。
  • 写子类构造器,通过super关键字调用父类的构造器。
  • 父类的private成员
    • 子类并不继承父类的private成员,但是继承的public/protected成员能够访问这些private成员。
    • 因为内嵌类能够访问外部类的private成员,所以子类继承的内嵌类能够直接访问父类的private成员。
4.2.1重写与隐藏方法(Overriding and Hiding Methods)

重写方法:子类定义的实例方法与父类实例方法有相同方法签名相同返回值类型 / 返回值的子类型

隐藏方法:子类定义的静态方法父类的静态方法有相同的方法签名。

这张表非常重要Superclass Instance MethodSuperclass Static Method
Subclass Instance MethodOverridesGenerates a compile-time error
Subclass Static MethodGenerates a compile-time errorHides

重写,隐藏方法的区别:

  • 重写的实例方法,被调用时,调用的是子类的那个。
  • 隐藏的静态方法,被调用时,调用的哪个,取决于从哪里调用的——父类还是子类。

当父类的方法与接口默认方法有相同的方法签名,起冲突时。Java 编译器会遵循下列规则来继承:

  • 1. 父类与接口冲突:实例方法优于接口默认方法
// 继承的父类与接口的默认方法冲突:实例方法 > 默认方法
public class Horse {
    public String identifyMyself() {
        return "I am a horse.";
    }
}
public interface Flyer {
    default public String identifyMyself() {
        return "I am able to fly.";
    }
}
public interface Mythical {
    default public String identifyMyself() {
        return "I am a mythical creature.";
    }
}
public class Pegasus extends Horse implements Flyer, Mythical {
    public static void main(String... args) {
        Pegasus myApp = new Pegasus();
        System.out.println(myApp.identifyMyself());//I am a horse.
    }
}
  • **2. ** 接口与接口冲突,且这些接口有共同的祖先:重写的接口方法优先。
// 接口与接口冲突,且这些接口有共同的祖先,重写的 > 非重写
public interface Animal {
    default public String identifyMyself() {
        return "I am an animal.";
    }
}
public interface EggLayer extends Animal { // 重写了
    default public String identifyMyself() {
        return "I am able to lay eggs.";
    }
}

public interface FireBreather extends Animal { } // 没有重写

public class Dragon implements EggLayer, FireBreather {
    public static void main (String... args) {
        Dragon myApp = new Dragon();
        System.out.println(myApp.identifyMyself());// I am able to lay eggs.
    }
}
  • 3. 如果两个或者多个独立定义的默认方法冲突,或者默认方法与抽象方法冲突,那么Java 编译器会报错;我们必须显示地进行重写才行。
public interface OperateCar {
    // ...
    default public int startEngine(EncryptedKey key) {
        // Implementation
    }
}
public interface FlyCar {
    // ...
    default public int startEngine(EncryptedKey key) {
        // Implementation
    }
}
public class FlyingCar implements OperateCar, FlyCar {
    // ...
    public int startEngine(EncryptedKey key) {//两个独立地接口方法有冲突,必须重写startEngine
        FlyCar.super.startEngine(key);
        OperateCar.super.startEngine(key);
    }
}
  • 4. 实例方法与抽象接口方法冲突:继承的实例方法相当于重写了抽象接口方法。
public interface Mammal {
    String identifyMyself();//抽象方法
}
public class Horse {
    public String identifyMyself() {//实例方法
        return "I am a horse.";
    }
}
public class Mustang extends Horse implements Mammal {
    public static void main(String... args) {
        Mustang myApp = new Mustang();
        System.out.println(myApp.identifyMyself());//I am a horse
    }
}
  • **5. ** 接口里的静态方法,永远不会被实现类继承过来。
interface InterfaceStatic {//接口
    static void printStatic(){//接口的静态方法
        System.out.println("Print Static...");
    }
}

class ExtendsClass {//实例类
    static void printExtends(){//一般类的静态方法
        System.out.println("Print Extends...");
    }
}

public class Interface extends ExtendsClass implements InterfaceStatic{
    public static void main(String[] args) {
        Interface i = new Interface();
        i.printExtends();//父类的静态方法能够被继承

        Interface i1 = new Interface();
        i1.printStatic(); // error 出错;接口的静态方法不能被继承
    }
}
4.2.2隐藏字段(Hiding Filed)

子类的字段名字与父类的一样,即使它们的数据类型不一样,子类仍会隐藏父类的相关字段。要想在子类中访问到父类的相关字段,需要使用下面我们将介绍的super关键字。

4.2.2Super关键字
  • 访问父类的成员。super.XXmethod(param list)super.XXFiled
  • 构造器。super(); or super(parameter list);
    • 如果一个构造器不显示地调用父类的构造器,那么Java编译器会自动调用父类的无参构造器。Object类有无参构造器
    • 注意单词:constructor chaining,构造器会有调用链,一直调用到最底层的Object类的构造函数。
class Parent{
    Parent(){
        System.out.println("Calling Parent's Constructor...");
    }
}

public class Super extends Parent {

    int value;

    Super(){
        System.out.println("Calling Super's Constructor0...");
        value = 0;
    }

    Super(int value){
        System.out.println("Calling Super's Constructor1...");
        this.value = value;
    }

    public static void main(String[] args) {
        Super sp1 = new Super();
        Super sp2 = new Super(-1);
        /* 输出:
            Calling Parent's Constructor...
            Calling Super's Constructor0...
            Calling Parent's Constructor...
            Calling Super's Constructor1...
	    */
    }
}
4.2.3Object作为父类(Object as a Superclass)
protected native Object clone() throws CloneNotSupportedException;//创建并返回此类的拷贝

/*    Called by the garbage collector on an object when garbage
      collection determines that there are no more references to the object*/

public final native Class<?> getClass();//返回对象运行时(runtime)的类
public native int hashCode();//返回对象的hashcode值,是一个整数值哦。
public boolean equals(Object obj) {//判断另一个类是否“等于”此类
    return (this == obj);//注意,这里时直接判断 “引用是否相等”
}

public String toString() {//返回类的字符串表示。
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
protected void finalize() throws Throwable { }//当GC发现此对象上没有任何引用时,GC会调用对象的此方法。

// 线程同步相关,后续再谈,这里不做解释
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait() throws InterruptedException {
    wait(0);
}
public final void wait(long timeout, int nanos) throws InterruptedException {
    if (timeout < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException(
            "nanosecond timeout value out of range");
    }

    if (nanos > 0) {
        timeout++;
    }

    wait(timeout);
}
4.2.3.1clone()方法
class InnerClass{
    String s = "xxx";
    InnerClass(String s){
        this.s = s;
    }
}


// 	Exception in thread "main" java.lang.CloneNotSupportedException: 
// 	basisLang.objclasses.inheritance.Objects
//		at java.lang.Object.clone(Native Method)
//		at basisLang.objclasses.inheritance.Objects.main(Objects.java:38)
// 必须要实现Cloneable接口,否则调用clone方法的时候会报如上所示的错误
// Cloneable接口没有任何方法,是一个标识接口,供jvm来参考的
public class Objects implements Cloneable {

    int value;

    InnerClass innerClass;

    Objects(){
        value = 0;
        innerClass = new InnerClass("0000");
    }

    Objects(int value){
        this.value = value;
        innerClass = new InnerClass(String.valueOf(value));
    }

	@Override
    public String toString(){
        return ":{ " + value + ", " + innerClass.s + " }";
    }

    public static void printObjs(Objects obj, String prefix){
        System.out.println(prefix + obj);
    }

    public static void main(String[] args) throws CloneNotSupportedException {

        Objects objs1 = new Objects();
        Objects objs2 = (Objects) objs1.clone();

        printObjs(objs1, "obj1"); printObjs(objs2, "obj2");

        objs1.innerClass.s = "objs1";

        printObjs(objs1, "obj1"); printObjs(objs2, "obj2");
        
        /* 整个程序输出如下所示,这说明jvm底层默认提供的是浅拷贝(通过简单的赋值语句=拷贝)。
            obj1:{ 0, 0000 }
            obj2:{ 0, 0000 }
            obj1:{ 0, objs1 }
            obj2:{ 0, objs1 } */
    }
}

// 下面对上面程序进行改进:
// 在Objects中重写clone方法,实现深拷贝
@Override
public Objects clone(){
    Objects clonedObjects = new Objects(value);
    clonedObjects.innerClass = new InnerClass(innerClass.s);
    return clonedObjects;
}
  • 重写equals(),必须重写hashcode()
4.2.3.2getClass()方法
class ParentClass{

}

interface Interfaces{

}

public class GetClass extends Parent implements Interfaces {

    public static void println(String str){  System.out.println(str); }
    public static void println(){ println("\n");}

    public static void main(String[] args) {

        GetClass getClass = new GetClass();

        Class cls = getClass.getClass();
        println("Simple Name: " + cls.getSimpleName());// Simple Name: GetClass
        println("Interface? " + cls.isInstance(getClass) );// Interface? true
        println("Annotation? " + cls.isAnnotation() );// Annotation? false

        println();

        Class superClass = cls.getSuperclass();
        println("Simple Name: " + superClass.getSimpleName());// Simple Name: Parent
        println("Interface? " + superClass.isInstance(getClass) );// Interface? true
        println("Annotation? " + superClass.isAnnotation() );// Annotation? false

        Class[] interfaces = cls.getInterfaces();
    }
}
4.2.3.3hashCode方法

hashCode是对象的16进制内存地址。

通过定义,如果两个对象相等,那么它们的hashCode必定相等。如果Override equals方法,那就会改变两个对象相等的方式,此时Object的hashCode的实现不再合理。

因此,如果重写equals()方法,你必须重写hashCode()方法。

4.2.3.4toString方法

你应该在你的类中,总是考虑重写toString()方法。

toString()方法对debug很有用。

4.2.4 final类/方法(Final Classes and Methods)

类/方法的声明中带有final关键字的,表示,此类/方法不能够被继承。

  • 构造器中调用的方法应该声明为final。因为如果构造器调用一个non-final方法,而子类重写了此方法,可能会导致不良后果。
  • 一个类声明为final****。那么它不能够被继承**。这对创建 **不可变类( immutable class)**很有用,例如String类就是final类型的类。
4.2.5 抽象方法与抽象类(Abstract Methods and Classes)
4.2.5.1基本概念
  • 当一个类被声明为abstract时,我们称之为抽象类(abstract class)。
  • 抽象类可以不定义抽象方法。
  • 抽象类不能够被实例化,能够被继承。
  • 当抽象类被继承时,子类必须要实现抽象类中的所有抽象方法。如果有没有被实现的方法,那么子类必须声明为抽象类(abstract class)
  • 抽象方法不能声明为private
  • 接口中的抽象方法隐含地是abstract,所以接口中的方法可以省略abstract关键字。

抽象类/方法声明实例

//抽象方法
abstract void moveTo(double deltaX, double deltaY);

//抽象类实例
public abstract class GraphicObject {//抽象类,1. 可能会包含抽象方法 
   // declare fields			             2. 不能够被实例化
   // declare nonabstract methods
   abstract void draw();
}
4.2.5.2抽象类与接口的比较(Abstract Classes Compared to Interfaces)
  • 都不能够实例化。
  • 抽象类能够声明字段,它们是non-static,non-final,并且定义public/protected/private的具体方法(concrete methods)。
  • 接口声明的字段,它们自动是public static final修饰的;所有的方法都是public的。
  • 抽象类只能继承一个类,但是接口可以继承任意数量的父接口。

使用 abstract classes的情况:

  • 你想要在多个相关类之间共享代码。
  • 你期望你的子类很多通用的方法,字段,或者要求访问修饰符不仅仅是public的。
  • 你想要声明non-static , non-final字段,能让你定义一些方法来修改类的状态。

使用interfaces的情况:

  • 你期待互不相关联的类实现你的接口;例如 ComparableCloneable 这两个接口被大量的互不不相关联的类实现。
  • 你想要指定特定数据类型的行为,但是你并不关心谁实现了它的行为。
  • 你想要充分利用接口的多继承特性。

在JDK中的一个抽象类的例子就是 AbstractMap,这是Collections Framework的一部分。它的子类享有许多AbstractMap定义的方法(如get, put, isEmpty, containsKey, and containsValue)。(AbstractMap的子类有HashMap, TreeMap, and ConcurrentHashMap

JDK中实现了若干个接口的类,像 HashMap,它实现了Serializable, Cloneable, and Map<K, V>三个接口。通过它实现的三个接口名,我们就知道HashMap 能够被clone,是serializable可序列化的,并且 有Map的功能(另外,Map通过许多默认方法加强了接口的功用)。

HashMap是继承了AbstractMap抽象类,并且实现了三个接口。

5.Number 与 String

5.1Number抽象类

public abstract class Number 
	extends Object 
	implements Serializable                           /*↑↓←→
	

  [------高精度计算-----]  [--------多线程-----------]
  BigInteger BigDecimal  AtomicInteger  AtomicLong
 	 ↓			↓	  ↓       ↓                ↓
     →----------→----→↓←----←------------------←
					 Number
					   ↑
	 →---------→------→↑←----←-----------------←
	 ↑     ↑     ↑     ↑         ↑         ↑ 
	Byte Short Integer Long    Float     Double
	[-----------------包装类--------------------]

*/    

为啥使用Number对象,而不是基本数据类型?

  • 函数的参数期待的数据类型为引用数据类型。(常用作集合的操作
  • 为了使用这些类定义的常数。例如:MIN_VALUEMAX_VALUE,提供了数据类型的属性——下界,上界。
  • 为了使用Number类的方法来:
    • 与其它基本数据类型互相转换
    • 与String互相转换
    • 在不同数制之间转换(decimal, octal, hexadecimal, binary)

*5.2格式化数字输出Formatting Numeric Print Output

java.io.PrintStream 类;输出格式化java.util.Formatter类。

System.out是PrintStream类。两个常用的方法

//format 与 printf 一样
PrintStream	printf(String format, Object... args)
PrintStream	printf(Locale l, String format, Object... args)

5.3Math类提供的高级算数功能

Beyond Basic Arithmetic提供了许多的API指导。

这里简单的介绍一些注意点,技巧点。

// 静态导入。
// 当我们的程序中有大量的数学函数调用的时候,可以很方便的直接使用相关数学函数
import static java.lang.Math.*;
// 如
cos(angle);
double e = E;
double pi = PI;
// abs:绝对值
// ceil, floor 向下/上取整
// rint 最接近,相同接近则返回偶数
// round 四舍五入
// 三角函数
// 指数对数

随机数:

//Tips: Math.random()产生单个随机数推荐使用
//      java.util.Random 产生一连串的随机数,推荐使用

Math.random() //伪随机数:[ 0.0, 1.0 )
//产生[l, r]区间的数可以通过下面方式:
int num = (int)(random()*(r-l+1)) + l;

// java.util.Random 用来创建一连串的随机数

测试题:

boolean b = Integer.valueOf(1).equals(Long.valueOf(1));
// b为啥值?
// 答案:false。

Integer valueOf(int i);//Integer.valueOf(1)返回Integer引用类型
Long valueOf(long l);//Long.valueOf(1)返回Long引用类型

public boolean equals(Object obj) {//Integer的equals方法
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

5.4Character 类

Character:基本数据类型char的包装类。它是不可变类(immutable class)。

characters类的在线文档说明

// 包裹的基本数据类型声明
private final char value;

// 判断大小写
// 判断数字/字母/空白
// 转化成大/写 或者 字符串

5.5String类

5.5.1基本概念
public final class String
    extends Object
    implements Serializable, Comparable<String>, CharSequence

//String实现了CharSequence接口

一旦代码中遇到string字面量(string literal),编译器就会创建一个String对象。

String类是不可变的(immutable)一旦其创建,就不可以改变。String类有许多的方法,似乎看起来能够改变String,但事实上,这些方法真正做的事情是——创建并返回一个包含操作结果的新的String对象。

//将string的[srcBegin, srcEnd) 拷贝到dst的[dstBegin, dst.length())
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)
public String concat(String str) // 连接,使用+号更加方便
public static String format(String format,//使用格式化来返回一个String
                            Object... args)
5.5.2Number←→String转换(Converting Between Numbers and Strings)
  • String→Number
// Number子类的valueOf(String)方法会返回一个包装类。
// static Double valueOf(str);
// double parseDouble(str);

public static void exercise3(String... args) {
    double ret = 0;
    for(String str : args){
        ret += Double.valueOf(str);//返回包装类,然后自动拆箱进行运算。
        // ret += Double.parseDouble(str);//或者直接返回double类型。
    }
    System.out.printf("%.2f", ret);//108.75
}
public static void main(String[] args) {//java com.xx.Adder 1 1e2 3.0 4.754
    if(args.length < 1) {
        System.out.println("Please enter more than 1 args !!");
        System.exit(1);
    }
    exercise3(args);
}
  • Number→String
String str1 = String.valueOf( i /*基本数据类型*/ );
String str2 = Integer.toString();//包装类型直接toString()返回串
5.5.3String操作字符
5.5.3.1获取字符串,子串
char charAt(int index);
String substring(int beginIndex/*, int endIndex*/);// [beginIndex, endIndex)
5.5.3.2其它操作
String[] split(String regex/*, int limit*/);//limit:maximum size of the returned array
CharSequence subSequence(int beginIndex, int endIndex);//[beginIndex, endIndex)
String trim();// " xx " -> "xx"
String toLowerCase(); String toUpperCase(); //大小写
5.5.3.3搜索字符串,字串
int indexOf(int ch/*, int fromIndex*/);
int lastIndexOf(int ch/*, int fromIndex*/);
int indexOf(String str/*, int fromIndex*/);
int lastIndexOf(String str/*, int fromIndex*/);
boolean contains(CharSequence s);//是否含有特定字符串
public interface CharSequence {
    int length();
    char charAt(int index);
    CharSequence subSequence(int start, int end);
    public String toString();
    public default IntStream chars() {
        class CharIterator implements PrimitiveIterator.OfInt {
            int cur = 0;

            public boolean hasNext() {
                return cur < length();
            }

            public int nextInt() {
                if (hasNext()) {
                    return charAt(cur++);
                } else {
                    throw new NoSuchElementException();
                }
            }

            @Override
            public void forEachRemaining(IntConsumer block) {
                for (; cur < length(); cur++) {
                    block.accept(charAt(cur));
                }
            }
        }

        return StreamSupport.intStream(() ->
                Spliterators.spliterator(
                        new CharIterator(),
                        length(),
                        Spliterator.ORDERED),
                Spliterator.SUBSIZED | Spliterator.SIZED | Spliterator.ORDERED,
                false);
    }
    public default IntStream codePoints() {
        class CodePointIterator implements PrimitiveIterator.OfInt {
            int cur = 0;

            @Override
            public void forEachRemaining(IntConsumer block) {
                final int length = length();
                int i = cur;
                try {
                    while (i < length) {
                        char c1 = charAt(i++);
                        if (!Character.isHighSurrogate(c1) || i >= length) {
                            block.accept(c1);
                        } else {
                            char c2 = charAt(i);
                            if (Character.isLowSurrogate(c2)) {
                                i++;
                                block.accept(Character.toCodePoint(c1, c2));
                            } else {
                                block.accept(c1);
                            }
                        }
                    }
                } finally {
                    cur = i;
                }
            }

            public boolean hasNext() {
                return cur < length();
            }

            public int nextInt() {
                final int length = length();

                if (cur >= length) {
                    throw new NoSuchElementException();
                }
                char c1 = charAt(cur++);
                if (Character.isHighSurrogate(c1) && cur < length) {
                    char c2 = charAt(cur);
                    if (Character.isLowSurrogate(c2)) {
                        cur++;
                        return Character.toCodePoint(c1, c2);
                    }
                }
                return c1;
            }
        }

        return StreamSupport.intStream(() ->
                Spliterators.spliteratorUnknownSize(
                        new CodePointIterator(),
                        Spliterator.ORDERED),
                Spliterator.ORDERED,
                false);
    }
}
5.5.3.4替换字符串,字串
String replace(char oldChar, char newChar);
String replace(CharSequence target, CharSequence replacement);
String replaceAll(String regex, String replacement);
String replaceFirst(String regex, String replacement)
5.5.3.5比较串/部分串
boolean endsWith(String suffix);
boolean startsWith(String prefix/*, int offset*/);
int compareTo/*IgnoreCase*/(String str);//>0:grater than; ==0:equals; <0:less than 
boolean equals(Object anObject);//true: 相同字符序列
boolean equalsIgnoreCase(String anotherString)
boolean regionMatches(int toffset, String other, int ooffset, int len);
boolean regionMatches(/*boolean ignoreCase, */int toffset, String other, int ooffset, int len);
boolean matches(String regex)

5.6StringBuilder类

public final class StringBuilder
    extends AbstractStringBuilder
    implements java.io.Serializable, CharSequence{
	/*这里省略了具体的代码*/    
}
    
abstract class AbstractStringBuilder implements Appendable, CharSequence {
    //这里省略了很多代码
}

/*
这里有意思的就是它的每个方法的返回值都是它本身。
这为Builder设计模式提供了接口支持。
*/
public interface Appendable {
	Appendable append(CharSequence csq) throws IOException;
    Appendable append(CharSequence csq, int start, int end) throws IOException;
    Appendable append(char c) throws IOException;
}

StringBuilder 类与String类很相似,但是Stringbuilder是可修改的。

内部实现上,StringBuilder有点像可变长字符串数组

当有大量的的字符连接操作的时候,可以考虑使用StringBuilder。

// 1. 构造函数
StringBuilder();//默认容量为16
StringBuilder(CharSequence cs);//cs + 16个空元素
StringBuilder(int initCapacity);
StringBuilder(String s)//s + 16个空元素

// 构造函数实践例子:
public static void main(String[] args) {//static PrintStream o = System.out;
    StringBuilder sb = new StringBuilder("1234");
    
    //length: 4, capacity: 20
    o.println("length: " + sb.length() +  ", capacity: " + sb.capacity());
    
    sb.append("123456");
    //length: 10, capacity: 20
    o.println("length: " + sb.length() +  ", capacity: " + sb.capacity());
    
    sb.append("01234567891");
    //length: 21, capacity: 42
    o.println("length: " + sb.length() +  ", capacity: " + sb.capacity());
}

// 2. 长度,容量相关方法
void setLength(int newLength)//设置长度
void ensureCapacity(int minCapacity)//容量至少要等于给定的最小值
    

// 3. StringBuilder操作
StringBuilder append(/*8种基本数据类型,除了short,byte*/)
StringBuilder append(char[] str/*, int offset, int len*/);
StringBuilder append(Object obj)
StringBuilder append(String s)
    
StringBuilder delete(int start, int end)
StringBuilder deleteCharAt(int index)

StringBuilder insert(int offset/*, 6中基本数据类型,除了short, byte*/);
StringBuilder insert(int offset, char[] str);
StringBuilder insert(int offset, Object obj);
StringBuilder insert(int offset, String s);
StringBuilder insert(int index, char[] str, int offset, int len);

StringBuilder replace(int start, int end, String s);//[start, end)
void setCharAt(int index, char c);
    
StringBuilder reverse();

String toString();

我们能够对StringBuilder使用任意的String方法:

//StringBuilder sb ...
sb = new StringBuilder( sb.toString().stringXXMethod(/*..*/) );
//StringXXMethod(/**/) 表示String的一些任意方法

5.7StringBuffer类

除了StringBuffer通过让它的方法同步变得线程安全之外,StringBuffer实际上和StringBuilder一样。

也就是说StringBuffer是线程安全的StringBuilder是非线程安全的

更多关于StringBuffer类,我们将在并发章节详解。

5.8自动装箱与自动拆箱(Autoboxing and Unboxing)

Primitive typeWrapper class
booleanBoolean
byteByte
charCharacter
floatFloat
intInteger
longLong
shortShort
doubleDouble
  • Autoboxing:基本数据类型 -------> 包装类
    • 基本数据类型被传递给一个方法参数,此参数期待一个相应的包装类型。
    • 赋值给一个相应的包装类型变量。
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2)
    li.add(i);

// 在运行时,编译器会将上面的代码转换成如下的代码
List<Integer> li = new ArrayList<>();
for (int i = 1; i < 50; i += 2)
    li.add(Integer.valueOf(i));//static Integer valueOf(int i);
  • unboxing:包装类 -------> 基本数据类型
    • 包装类被传递给一个方法参数,此方法参数期待一个相应的基本数据类型。
    • 赋值给一个相应的基本数据类型变量。
public static int sumEven(List<Integer> li) {
    int sum = 0;
    for (Integer i: li)
        if (i % 2 == 0)
            sum += i;
        return sum;
}

// 在运行时,编译器会将上面的代码转换成如下的代码
public static int sumEven(List<Integer> li) {
    int sum = 0;
    for (Integer i : li)
        if (i.intValue() % 2 == 0)
            sum += i.intValue();
        return sum;
}

6.泛型(Generics)

6.1为啥使用泛型?(Why Use Generics?)

  • **1.**编译时更加强大的类型检查
  • **2.**无需强制类型转换
// 对比下面两端代码
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);

List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);   // no cast
  • **3.**让程序员实现通用算法(generic algorithms)

6.2泛型(Generic Types)

泛型(Generic Types)是数据类型参数化通用类接口

泛型类的定义:

class name<T1, T2, ..., Tn> { /* ... */ }
// Ti -> 类型变量 / 类型参数 可以为任意的非基本数据类型(non-primitive type)

// 类型参数的命名惯例
// E - Element (used extensively by the Java Collections Framework)
// K - Key
// N - Number
// T - Type
// V - Value
// S,U,V etc. - 2nd, 3rd, 4th types

类设计的演变:

public class Box {
    private Object object;

    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}
//             ↓   ↓   ↓   ↓   ↓
public class Box<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

6.3原类型(Raw Types)

// 这是Box<T>参数化类型
Box<Integer> intBox = new Box<>();
// 这是Box<T>的原类型
Box rawBox = new Box();

// 我们说Box是泛型Box<T>的原类型
6.3.1 Unchecked Warning Message
Box<String> box = new Box();
System.out.println(box);
/*
javac .\basisLang\objclasses\generics\Box.java

Note: .\basisLang\objclasses\generics\Box.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

*/


Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK

Unchecked这种情况多发生在遗留代码与泛型代码的混合。

unchecked意味着编译器没有足够的类型信息,来执行类型检查,确保类型安全。

-Xlint:-unchecked

@SuppressWarnings("unchecked")

6.4泛型方法(Generic Methods)

泛型方法是那些引入它们自己的类型参数的方法。泛型方法的类型参数的作用域只在此方法。

适用于静态,非静态,泛型类的构造器。

举个例子

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

public class Pair<K, V> {

    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}

// 怎么使用此泛型方法?
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);

//也能够省略方法的类型参数,这种特性叫做"type inference"
boolean same1 = Util.compare(p1, p2);

6.5有界的类型参数(Bounded Type Parameters)

限制参数化类型的类型传参。

6.5.1上界(upper bound)

上界的关键字为extends,这里的extends的意思是,继承或者实现{ either “extends” (as in classes) or “implements” (as in interfaces) }

// 从下面的输出,我们应该能够明白参数化类型的意思了。
// 类型作为传参,它的参数名为参数类型。
public <U extends Number> void inspect(U u){    
	System.out.println("T: " + t.getClass().getSimpleName());//T: Integer
    System.out.println("U: " + u.getClass().getSimpleName());//U: Integer
}
Box<Integer> integerBox = new Box<Integer>();
integerBox.setBox(new Integer(10));
integerBox.inspect(1);
//integerBox.inspect("str"); // 会报错

当然了,除了方法能够指定有界类型参数,类的声明也能够如此:

// <T extends Integer> 确保n肯定是一个整数
public class NaturalNumber<T extends Integer> {

    private T n;

    public NaturalNumber(T n)  { this.n = n; }

    public boolean isEven() {
        return n.intValue() % 2 == 0;
    }
    // ...
}
6.5.2多边界(Multiple Bounds)
<T extends B1 & B2 & B3>

T类型一定是B1,B2,B3的子类(要么实现它,要么继承它)

类必须放最前面,也就是说,B1如果是类,B2,B3是接口,那么B1必须放最前面。

class A { /* ... */ }
interface B { /* ... */ }
interface cC { /* ... */ }

//正确
class D <T extends A & B & C> { /* ... */ }

//错误,会产生编译错误
class D <T extends B & A & C> { /* ... */ }  // compile-time error
6.5.3泛型函数中的边界类型参数(Generic Methods and Bounded Type Parameters)

给个例子:

// 这里的代码,不能够进行编译,会出现编译错误。
// 原因是 > 操作符只能运用在 8种基本数据类型中(除了boolean类型)
public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // compiler error
            ++count;
    return count;
}

// 为了解决此问题,我们可以把类型参数设置一个上界,使其能够进行比较
public static <T extends Comparator<T>> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e.compareTo(elem) > 0)  // right
            ++count;
    return count;
}

// 使用Comparator接口
public interface Comparable<T> {
    public int compareTo(T o);
}

6.6泛型,继承,子类型(Generics, Inheritance, and Subtypes)

考虑下面的代码:

public void boxTest(Box<Number> n) { /* ... */ }

// 它允许我们这样调用吗?
// Box<Integer> integerBox ...
// Box<Double> doubleBox ...
boxTest(integerBox); // 不能,错误的。
boxTest(doubleBox);  // 不能,错误的。
  • MyClass<A>MyClass<B>没有关系,它们的共同父类是Object
  • 它们的关系,我们只能通过extends,implements关键字来判断。

Collections 类举个例子

                                                                             /*
                Collection<String>
                      ↑
                  List<String>
                 	  ↑
                ArrayList<String>


    只要我们不改变类型参数(这里是String),
    那么它们之间的关系总是如上图所示
    
*/

// 现在我们定义一个新的接口
interface PayloadList<E,P> extends List<E> {
  void setPayload(int index, P val);
  //...
}

//那么下面的三种泛型类都是List<String>的子类
PayloadList<String,String>
PayloadList<String,Integer>
PayloadList<String,Exception>

																			/*
                             Collections<String>
                                      ↑
                                List<String>
                                     ↑
→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→→↗ ↖←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←←
↑                                    ↑                                       ↑ 
PayloadList<String,String>  PayloadList<String,Integer>  PayloadList<String,Exception>
 
 
 */

6.7泛型中的类型推断(Type Inference)

先看一段代码

static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());

详细讲解点击链接Type Inference

6.8?通配符(Wildcards)

通配符能够用在很多场景参数类型字段局部变量返回值类型

从不用作泛型方法调用泛型类的实例创建/类型传参父类型

6.8.1上界通配符(Upper Bounded Wildcards)
// 匹配Number及其子类	
List<? extends Number>
    
// 匹配Foo及其子类
public static void process(List<? extends Foo> list) {
    for (Foo elem : list) {
        // ...
    }
}

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

// 演示泛型
List<Integer> li = Arrays.asList(1, 2, 3); // Integer类型
System.out.println("sum = " + sumOfList(li));

List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);// Double类型
System.out.println("sum = " + sumOfList(ld));
6.8.2无界通配符(Unbounded Wildcards)
List<?> // 表示不明类型集合

两种场景无界通配符是很有用的

  • 如果你写的方法能够被Object类提供的功能来实现。 例如打印,Object提供了toString()方法。
  • 当你的泛型类中的函数并不依赖于类型参数。 例如List.sizeList.clear。实际上,Class<?>被经常使用,就是因为Class的大多数方法并不依赖于类型T。

考虑下面的代码

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

printList的设计初衷,就是打印任意类型列表,很可惜,它并不能实现此功能,它只能打印Object列表。原因我们在之前的章节讲了,函数的参数列表为List<Object>,我们只能传此类型的参数,或者它的子类。而我们知道List<Object>List<Integer>或者List<String>之间并没有任何类继承或者接口实现关系

为了实现我们的目标,我们需要将代码修改成如下的方式。

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

// 现在我们能够打印任意类型的列表了。
List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);//打印List<Integer>类型
printList(ls);//打印List<String>类型

Arrays.asList(…) 方法会被我们经常使用

对于任意类型A,List<A>都是List<?>的子类。

6.8.2.1List<Object>与List<?>的区别
  • 我们能够插入任意Object子类,也就是任意引用类型到List<Object>
// 输出:1,1,1,1.0,
List<Object> list = new ArrayList<>();
list.add(1);
list.add(String.valueOf(1));
list.add("1");
list.add(1.0);
for(Object o : list) System.out.print(o+",");
  • 我们只能插入null到List<?>
List<?> list = new ArrayList<>();
list.add(null); // right
list.add(1); // error
list.add("1"); // error
6.8.3下界通配符(Lower Bounded Wildcards)
<? super A> // 类A或者A的父类

// restricts the unknown type to be a specific type
// or a super type of that type

注意,我们不能同时指定上界,下界。只能指定其中一个。

6.8.4通配符与子类型(Wildcards and Subtyping)
//根据我们所学,很容易写出下面代码
class A { /* ... */ }
class B extends A { /* ... */ }

// 我们都知道下面两行代码是正确的
B b = new B();
A a = b;

// 但是下面的代码中有错误
List<B> lb = new ArrayList<>();
List<A> la = lb;   // compile-time error

现在我们解释为啥上面的代码会出错。

																   /*
            List<?>
               ↑
      →→→→→→→↗ ↖←←←←←←←←←
      ↑                   ↑ 
   List<Number>      List<Integer>  
   
List<Number> 与 List<Integer>实际上没有任何继承关系
唯一的关系就是它们有共同的父类List<?>
*/



// 实际上,我们可以得到下面的子类关系

List类继承关系

怎么理解?我们可以把上下界理解成父类子类的关系图

往上,是父类,最上是Object。

往下,是子类。

// 这里的区间大小,指的是,大的区间包含小的区间
// 类型引用规则如下,
// {区间更小的} is a {区间更大的}
// 区间更大的 = 区间更小的;   // 因为存在is a关系,所以此赋值语句成立。

// 上界为Number,  [Number, Integer, +++ ) 
<? extends Number> //Number <= ... <= ...

// 下界为Number,  [Object, Number] , Integer]
<? super Number>  //Object <= ... <= Number

*6.8.5通配符捕获与Helper方法(Wildcard Capture and Helper Methods)

Wildcard Capture and Helper Methods

Why use a wild card capture helper method?

6.9 通配符使用指南(Guidelines for Wildcard Use)

上界通配符,下界通配符分别使用的场景

在分析使用场景前,我们先介绍一个"输入输出模型(in and out model)"

例如copy(src, dest)就是以src作为输入,dest作为输出的一个输入输出模型。

下面给出通配符使用指南:

  • 输入变量使用上界通配符来定义;extends关键字
  • 输出变量使用下界通配符来定义:super关键字
  • 输入变量通过使用Object类中的方法就能够访问(完成功能)时,使用无界通配符
  • 当输入变量与输出变量为同一个时,不使用通配符

上面的指南不适用于方法返回值,应该避免方法返回值使用通配符

class NaturalNumber {
    private int i;
    public NaturalNumber(int i) { this.i = i; }
    @Override
    public String toString(){ return String.valueOf(i); }
    // ...
}

class EvenNumber extends NaturalNumber {
    public EvenNumber(int i) { super(i); }
    // ...
}

List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35));  // compile-time error

// List<EvenNumber> 是 List<? extends NaturalNumber>的子类,所以能赋值
// 但是我们不能添加自然数到ln中。

我们只能对ln作如下操作,为啥只能作这些操作?可以查看6.10.1.类型消除部分。

  • add null
  • 调用clear
  • 获得iterator并且remove
  • 捕获通配符(引用赋值),并且写从此列表中读的元素。(capture the wildcard and write elements that you’ve read from the list)

如下所示:

/* 整个程序输出如下所示;
我们证明了上面的操作观点;
同时不要忘记,对象的引用,使两者同时变化。

1,2,4,5,6,7,8,9,10,null,
1,2,4,5,6,7,8,9,10,null,
1,2,4,5,6,7,8,9,10,
1,2,4,5,6,7,8,9,10,
ln is Cleared up ...
le is Cleared up ...

                                                            */
public class NumberTest {

    static <T> void println(T t){ print(t + "\n"); }
    static <T> void print(T t){ System.out.print(t); }
    static void println(){print("\n");}

    public static void main(String[] args) {
        List<EvenNumber> le = new ArrayList<>();

        for(int i = 1 ; i <= 10 ; i++) le.add(new EvenNumber(i));

        List<? extends NaturalNumber> ln = le;

        // ln.add(new EvenNumber(11));   // error
        // ln.add(new NaturalNumber(12)); // error
        ln.add(null);
        ln.remove(2);
        for(int i = 0 ; i < ln.size() ; i++) print(ln.get(i) + ","); println();
        for(int i = 0 ; i < le.size() ; i++) print(le.get(i) + ","); println();

        // remove null
        ln.remove(ln.size()-1);
        for(int i = 0 ; i < ln.size() ; i++) print(ln.get(i) + ","); println();
        for(int i = 0 ; i < le.size() ; i++) print(le.get(i) + ","); println();

        ln.clear();//清除
        if(ln.size() == 0) print("ln is Cleared up ..."); println();
        if(le.size() == 0) print("le is Cleared up ..."); println();
    }
}

6.10类型消除(Type Erasure)

泛型在编译时提供更严的类型检查,为了实现泛型,Java编译器应用类型检查来做一些事情:

  • 边界类型或者Object类(如果类型参数无界)来替换泛型的类型参数。所以,生成的字节码只包含一般的类,接口,方法;所以泛型导致造成运行时间上的额外开销。
  • 必要时插入强制类型转换,以保持类型安全
  • 生成桥接方法以在继承的泛型类型中保持多态性
6.10.1泛型类的类型消除(Erasure of Generic Types)
  • 1. 看一个无界泛型的类型消除
// 以一个单链表举例,我们写的代码如下所示
public class Node<T> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

// 由于T类型是无界的(unbounded),{Node<T>, Node}, {T, Object} 
// Java Compiler编译器会将它转化成如下所示的代码
public class Node {

    private Object data;
    private Node next;

    public Node(Object data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Object getData() { return data; }
    // ...
}

  • 2. 看一个有界泛型的类型消除
// T是有界的,以Comparable<T>上界
public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

// 编译器会将其转换为如下所示的代码,{Node*, Node}, {T, Comparable}, 
public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}


更多于类型消除相关文章 ——

1. 【Java】白话编译器对List和List类型实例的add()和get()操作限制

2. Java中泛型 类型擦除

6.10.2泛型方法的类型消除(Erasure of Generic Methods)

与泛型类的类型消除一样。下面直接给出代码:

// Counts the number of occurrences of elem in anArray.
public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

// 因为T是无界的(unbounded),可以转换成如下所示代码
public static int count(Object[] anArray, Object elem) {
    int cnt = 0;
    for (Object e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

再给一个例子:

class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }

public static <T extends Shape> void draw(T shape) { /* ... */ }

// Java编译器会将上面的draw函数转换成如下所示的函数 
public static void draw(Shape shape) { /* ... */ }

6.10.3桥接方法(Effects of Type Erasure and Bridge Methods)

在我们没有介绍桥接方法之前,考虑下面的代码:

class Node<T> {

    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {//因为消除,方法被继承:void setData(Object data)
        System.out.println("Node.setData");
        this.data = data;
    }
}

class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }
	
    public void setData(Integer data) {//方法签名不一样,不叫作重写,叫重载。
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

public class ErasureTest {

    public static void main(String[] args) {
        MyNode mn = new MyNode(5);
        Node n = mn;            // A raw type - compiler throws an unchecked warning
        n.setData("Hello");
        Integer x = mn.data;    // Causes a ClassCastException to be thrown.
    }
}

// 在不考虑桥接方法之前,上面的代码可以这样理解:
Object data = new Integer(5); //ok,第1行代码
data = "Hello"; //ok,第2,3行代码
Integer x = data; //报错,不能强制转换,ClassCastException to be thrown.

MyNode mn = new MyNode(5);
Node n = (MyNode)mn;         // A raw type - compiler throws an unchecked warning
n.setData("Hello");
Integer x = (String)mn.data; // Causes a ClassCastException to be thrown.

但是,实际上,真正在运行到第3行n.setData("Hello");就会报ClassCastException错误了。

原因就在于桥接方法的生成。实际上,编译后MyNode的代码如下所示:

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
    
    // Bridge method generated by the compiler
    public void setData(Object data) {// n.setData("Hello");会直接调用此方法,直接报错。
        setData((Integer) data);
    }
}

6.11泛型的限制

  • 1.不能用基本数据类型来实例化泛型类型。也就是说泛型参数列表不能够传基本数据类型
Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error
Pair<Integer, Character> p = new Pair<>(8, 'a');// right

  • 2.不能创建类型参数的实例
public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

// 通过下面的方式来创建
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}
List<String> ls = new ArrayList<>();
append(ls, String.class);

  • 3.不能声明 数据类型在泛型参数列表中静态字段
public class MobileDevice<T> {
    private static T os; // 报错
    // ...
}
// 如果允许的话,下面代码就会有歧义,冲突。
MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();

  • 4.参数化类型的数据不能够使用强制类型转换或者instanceof操作符
//Java编译器的泛型消除机制,在运行时,我们不会知道泛型到底是哪个类。
public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
}

public static void rtti(List<?> list) {
    if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
        // ...
    }
}

  • 5.不能创建参数化数据类型的数组
List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

  • 6.不能创建捕获抛出参数化类型的对象
// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ }    // compile-time error

// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ } // compile-time error

public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // compile-time error
        // ...
    }
}

// 但是,throws 语句 允许
class Parser<T extends Exception> {
    public void parse(File file) throws T {     // OK
        // ...
    }
}
  • 7.不能重载经过类型擦除后形参转化为相同原始类型的方法
public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

6.12Questions and Exercises

Questions and Exercises: Generics

Check your answers

7.包(Packages)

7.1创建包

为啥组织包结构?

  • 更容易找到,使用;
  • 避免命名冲突;命名空间管理
  • 访问控制;访问保护

确定包名,包语句出现在代码文件的第一条语句;

如果放多个类在同一个源文件里面,只能有一个类能声明为public,且此public声明类的类名必须要跟文件名相同;其它非public的类,只能默认不加访问修饰符,它们是包私有的(package private

// APackage.java 文件
class A{}
public class APackage {}

// BPackage.java 文件,与APackage同包
public class BPackage {
    public static void main(String[] args) {
        A a = new A(); // Ok
    }
}

// CPackage.java 文件,与APackage不同包
// 会产生错误
public class CPackage {
    public static void main(String[] args) {
        A  a = new A(); // error
    }
}

如果你没有声明包语句,那么你的类最终是一个未命名的包,我们极不推荐不声明包,除非你是初学者,或者仅仅想写一个小的或者临时的应用程序。

7.2 命名包

Naming a Package

7.3管理源码/类文件

CLASSPATH

文件管理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值