[关闭]
@lemonguge 2017-09-13T14:51:07.000000Z 字数 9398 阅读 381

Java基础部分(二)

JAVA


控制执行流程

迭代

whilefordo-while用来控制循环。

break、continue和标签

  • break用于强行退出循环,不执行循环中剩余的语句;continue则停止当前的迭代,然后退回循环起始处,开始下一次迭代。
  • java中使用标签的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中breakcontinue

标签是后面跟有冒号的标识符,如lable:

  1. label1:
  2. outer-iterator{
  3. inner-iterator{
  4. //...
  5. break; //中断内部迭代,回到外部迭代,执行statement语句
  6. //...
  7. continue; //使执行点移回到内部迭代的起始处,进行下一次内部迭代
  8. //...
  9. continue label1; //同时中断内部迭代以及外部迭代,不执行statement语句,从外部迭代开始继续迭代过程。
  10. //...
  11. break label1;//中止所有迭代,并回到label1处但不重新进入迭代。
  12. }
  13. // statement...
  14. }

switch

case中的类型(基本数据类型可以经过自动转型后)与switch中的一致。

  1. public class VowelsAndConsonants {
  2. public static void main(String[] args) {
  3. Random rand = new Random(47);
  4. for (int i = 0; i < 11; i++) {
  5. int c = rand.nextInt(26) + 'a';
  6. printnb((char) c + ", " + c + ": ");
  7. switch (c) {
  8. case 'a':
  9. case 'e':
  10. case 'i':
  11. case 'o':
  12. case 'u':
  13. print("vowel");
  14. break;
  15. case 'y':
  16. case 'w':
  17. print("Sometimes a vowel");
  18. break;
  19. default:
  20. print("consonant");
  21. }
  22. }
  23. }
  24. } /* Output:
  25. y, 121: Sometimes a vowel
  26. n, 110: consonant
  27. z, 122: consonant
  28. b, 98: consonant
  29. r, 114: consonant
  30. n, 110: consonant
  31. y, 121: Sometimes a vowel
  32. g, 103: consonant
  33. c, 99: consonant
  34. f, 102: consonant
  35. o, 111: vowel
  36. *///:~
  • switch(selector)中的选择因子可以为:bytecharshortint,在JDK5.0后还支持enumString
  • switch语句的执行规则:将selector选择因子与每一个case相比较,如果发现相符的,就首先执行case对应的语句,如果语句的结尾有break,则会直接跳转至switch主体的末尾,若省略了break,就会继续执行后面的case语句,直到遇到break或者到执行流程的末尾。
  1. int x = 2;
  2. switch (x) {
  3. default:
  4. System.out.println("d");
  5. case 4:
  6. System.out.println("a");
  7. case 1:
  8. System.out.println("b");
  9. break;
  10. case 3:
  11. System.out.println("c");
  12. break;
  13. }
  14. //输出d,a,b
  15. //case中没有与x相同的,所以到default中执行语句,由于没有break继续执行下面的语句。

if与switch

  1. if
    • 对具体的值进行判断。
    • 对区间判断。
    • 对运算结果是boolean类型的表达式进行判断。
  2. switch
    • 对具体的值进行判断。
    • 值的个数通常是固定的。

对于几个固定的值判断,建议使用switch语句,因为switch语句会将具体的答案都加载进内存,效率相对高一点。


枚举

在Java SE5中添加了一个看似很小的特性,即enum关键字。由于枚举类型的实例public static final是常量,因此按照命名惯例它们都用大写字母表示。

  1. enum Spiciness {
  2. // 可以省略";"分号
  3. NOT, MILD, MEDIUM, HOT, FLAMING
  4. }
  5. public class SimpleEnumUse {
  6. public static void main(String[] args) {
  7. Spiciness howHot = Spiciness.MEDIUM;
  8. System.out.println(howHot);
  9. //valueOf方法返回带指定名称的指定枚举类型的枚举常量。
  10. Spiciness val = Enum.valueOf(Spiciness.class, "MEDIUM");
  11. System.out.println(val + ":" + val.ordinal());
  12. System.out.println(Spiciness.valueOf("FLAMING").ordinal());
  13. //values方法返回一个包含全部枚举值的数组。
  14. for (Spiciness s : Spiciness.values())
  15. System.out.println(s + ", ordinal " + s.ordinal());
  16. }
  17. } /* Output:
  18. MEDIUM
  19. MEDIUM:2
  20. 4
  21. NOT, ordinal 0
  22. MILD, ordinal 1
  23. MEDIUM, ordinal 2
  24. HOT, ordinal 3
  25. FLAMING, ordinal 4
  26. *///:~

所有的enum都继承自java.lang.Enum类!所以自定义的enum不能再继承其他类。

我们只能覆盖toString()方法,以下是父类中的部分代码:

  1. private final String name;
  2. private final int ordinal;
  3. protected Enum(String name, int ordinal) {
  4. this.name = name;
  5. this.ordinal = ordinal;
  6. }
  7. public final String name() {
  8. return name;
  9. }
  10. public final int ordinal() {
  11. return ordinal;
  12. }
  13. public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {
  14. T result = enumType.enumConstantDirectory().get(name);
  15. if (result != null)
  16. return result;
  17. if (name == null)
  18. throw new NullPointerException("Name is null");
  19. throw new IllegalArgumentException(
  20. "No enum constant " + enumType.getCanonicalName() + "." + name);
  21. }
  22. public String toString() {
  23. return name;
  24. }
  25. public final boolean equals(Object other) {
  26. return this==other;
  27. }
  28. public final int hashCode() {
  29. return super.hashCode();
  30. }
  31. protected final void finalize() { }
  32. ...

ordinal()方法用来表示某个特定enum常量的声明顺序,其中初始常量序数为零。

由于nameordinal在定义的时候就已经被确定,即被显式初始化了,而final修饰的成员变量一旦被显式初始化便不能被改变!所以我们不能调用父类的构造器super(name, ordinal)再进行初始化,可能大家会想,父类的构造器只有这一种,而我们却不能显式声明调用父类构造器,那还能通过编译吗?请大家放心,底层会帮我们做好这一切的。所以切记不能调用父类的构造器!

而且可以发现Enum类中并没有values()方法,那我们为什么可以使用呢?因为这是编译器为我们创建的enum类添加的static values()方法,而且还额外添加了valueof(String)静态方法,这个方法只需要一个参数。

  1. public enum MyEnum {
  2. MyEnum1("Leo", "M", 1), MyEnum2("Mary", "F", 3);
  3. private String name;
  4. private String sex;
  5. private int order;
  6. // 绝对不允许有public构造器
  7. private MyEnum(String name, String sex, int order) {
  8. this.name = name;
  9. this.sex = sex;
  10. this.order = order;
  11. }
  12. public String getName() {
  13. return name;
  14. }
  15. public String getSex() {
  16. return sex;
  17. }
  18. public int getOrder() {
  19. return order;
  20. }
  21. public static void main(String[] args) {
  22. for (MyEnum s : MyEnum.values()) {
  23. System.out.println(s + ", ordinal " + s.ordinal());
  24. System.out.println("name:" + s.getName()+" sex:" + s.getSex()+" order:" + s.getOrder());
  25. }
  26. // ! MyEnum m = new MyEnum("aa","a",2);//annot instantiate the type MyEnum
  27. }
  28. } /* Output:
  29. MyEnum1, ordinal 0
  30. name:Leo sex:M order:1
  31. MyEnum2, ordinal 1
  32. name:Mary sex:F order:3
  33. *///:~

虽然有意识的将MyEnum的构造器声明为private,但对于它的可访问性而言,其实并没有什么变化,因为我们只能在MyEnum定义的内部使用构造器创建实例,一旦MyEnum的定义结束,编译器就不允许我们再使用其构造器来创建任何实例!

由于switch是要在有限的可能值集合中进行选择,因此它与enum正是绝佳的组合。

  1. public class Burrito {
  2. Spiciness degree;
  3. public Burrito(Spiciness degree) {
  4. this.degree = degree;
  5. }
  6. public void describe() {
  7. System.out.print("This burrito is ");
  8. switch (degree) {
  9. case NOT:
  10. System.out.println("not spicy at all.");
  11. break;
  12. case MILD:
  13. case MEDIUM:
  14. System.out.println("a little hot.");
  15. break;
  16. case HOT:
  17. case FLAMING:
  18. default:
  19. System.out.println("maybe too hot.");
  20. }
  21. }
  22. public static void main(String[] args) {
  23. Burrito plain = new Burrito(Spiciness.NOT),
  24. greenChile = new Burrito(Spiciness.MEDIUM),
  25. jalapeno = new Burrito(Spiciness.HOT);
  26. plain.describe();
  27. greenChile.describe();
  28. jalapeno.describe();
  29. }
  30. } /* Output:
  31. This burrito is not spicy at all.
  32. This burrito is a little hot.
  33. This burrito is maybe too hot.
  34. *///:~

内存的划分

程序运算时,对象是怎么进行放置安排的?特别是内存是怎样分配的呢?对这些方面的了解会对我们有很大的帮助。有五个不同的地方可以存储数据:

  1. 寄存器。寄存器拥有非常高的读写速度,因为它位于与其他存储区不同的地方——处理器内部。我们不能直接控制,也不能在程序中感觉到寄存器存在的迹象。
  2. 本地方法区。通常由JVM调用的,本地方法是一种在Java中调用非Java代码的方式。本地方法目前只支持C和C++,但是它们可以调用其他语言写的代码,所以实际上可以调用任何代码。
  3. 方法区。在JVM中也是一个非常重要的区域,在方法区中,存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。
  4. 堆栈。位于通用RAM(随机访问存储器)中,存储的都是局部变量,而且变量所属的作用域一旦结束,该变量就自动释放。记住Java对象并不存储于其中。
  5. 。一种通过的内存池(也位于RAM区),用于存放所有的Java对象。

通过new关键字创建对象,那么对象先会进行默认初始化,如果一个引用与一个新的对象相关联,引用会持有对象在堆内存中的首地址。基本数据类型由于所占存储空间大小的不变性,所以直接存储值,并置于堆栈中,更加高效。

所以也常说:方法中的引用数据类型参数属于址传递,基本数据类型参数属于值传递。


数组

数组只是相同类型的、用同一个标识符名称封装到一起的一个对象序列或基本类型数据序列。

数组通过方括号下标操作符[]来定义和使用的。

创建数组的时候必须显式或隐式的指明数组的长度。

创建数组有三种方式:

  1. // 显式的指明长度
  2. int[] arr = new int[3];
  1. // 隐式的指明长度且明确元素内容
  2. int[] arr = new int[]{89,34,270,17};
  1. // 隐式的指明长度且明确元素内容,是第二种的简写形式
  2. int[] arr = {89,34,270,17};

第一种和第三种的数组定义形式很常用。

由于数组的长度固定,根据一切都是对象的思想,我们可以猜想到数组的长度只有数组这个对象知道,即所有的数组(无论它们的元素是对象还是基本类型)都有一个固有成员。这个成员就是length

当使用显式指明长度的形式创建数组时,数组元素中的基本数据类型会被自动初始化,引用数据类型会被初始化为null

  1. public static void main(String[] args) {
  2. int[] a;
  3. // 使用47作为种子产生一个随机序列
  4. Random rand = new Random(47);
  5. // 不断的运行可以发现数组的长度为18
  6. a = new int[rand.nextInt(20)];
  7. System.out.println("length of a = " + a.length);
  8. System.out.println(Arrays.toString(a));
  9. String[] strs = new String[5];
  10. System.out.println(Arrays.toString(strs));
  11. // 数组的长度可以为0
  12. int[] arr = new int[0];
  13. System.out.println(arr.length);
  14. } /* Output:
  15. length of a = 18
  16. [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
  17. [null, null, null, null, null]
  18. 0
  19. *///:~

可变参数列表

可变参数列表其实就是数组。

有了可变参数,就再也不用显式地编写数组语法了,当你指定参数时,编译器实际上会为你填充数组。你获取的仍是一个数组。

  1. class A {
  2. @Override
  3. public String toString() {
  4. return "A@" + hashCode();
  5. }
  6. }
  7. public class NewVarArgs {
  8. // 该函数功能并没有访问到对象中的特有数据
  9. static void printArray(Object... args) {
  10. for (Object obj : args)
  11. System.out.print(obj + " ");
  12. System.out.println();
  13. }
  14. public static void main(String[] args) {
  15. // Can take individual elements:
  16. printArray(new Integer(47), new Float(3.14), new Double(11.11));
  17. // autoboxing
  18. printArray(47, 3.14F, 11.11);
  19. printArray("one", "two", "three");
  20. printArray(new A(), new A(), new A());
  21. // Or an array:
  22. printArray((Object[]) new Integer[] { 1, 2, 3, 4 });
  23. // 等价于传入一个长度为0的数组
  24. printArray(); // Empty list is OK
  25. }
  26. } /* Output: (75% match)
  27. 47 3.14 11.11
  28. 47 3.14 11.11
  29. one two three
  30. A@617353119 A@1360372376 A@1667617470
  31. 1 2 3 4
  32. *///:~

该程序的最后一行printArray();表明了将0个参数传递给可变参数列表是可行的,当具有可选的尾随参数时,这一特性就会很有用。

可变参数列表中可以使用任何类型的参数,包括基本数据类型。

  1. public class VarargType {
  2. static void f(Character... args) {
  3. System.out.print(args.getClass());
  4. System.out.println(" length " + args.length);
  5. }
  6. static void g(int... args) {
  7. System.out.print(args.getClass());
  8. System.out.println(" length " + args.length);
  9. }
  10. public static void main(String[] args) {
  11. f('a');
  12. f();
  13. g(1);
  14. g();
  15. System.out.println("int[]: " + new int[0].getClass());
  16. }
  17. } /* Output:
  18. class [Ljava.lang.Character; length 1
  19. class [Ljava.lang.Character; length 0
  20. class [I length 1
  21. class [I length 0
  22. int[]: class [I
  23. *///:~

可变参数列表与自动包装机制可以和谐共存。

  1. public class OverloadingVarargs {
  2. static void f(Character... args) {
  3. System.out.print("first");
  4. for (Character c : args)
  5. System.out.print(" " + c);
  6. System.out.println();
  7. }
  8. static void f(Integer... args) {
  9. System.out.print("second");
  10. for (Integer i : args)
  11. System.out.print(" " + i);
  12. System.out.println();
  13. }
  14. static void f(Long... args) {
  15. System.out.println("third");
  16. }
  17. public static void main(String[] args) {
  18. f('a', 'b', 'c');
  19. f(1);
  20. f(2, 1);
  21. f(0);
  22. f(0L);
  23. // ! f(); // Won't compile -- ambiguous
  24. }
  25. } /* Output:
  26. first a b c
  27. second 1
  28. second 2 1
  29. second 0
  30. third
  31. *///:~

在每一种情况中,编译器都会使用自动包装机制来匹配重载的方法,由于被自动包装所以传入的参数都已经是对象,然后调用最明确匹配的方法。

  1. public class OverloadingVarargs2 {
  2. static void f(float i, Character... args) {
  3. System.out.println("first");
  4. }
  5. static void f(Character... args) {
  6. System.out.print("second");
  7. }
  8. public static void main(String[] args) {
  9. f(1, 'a');
  10. // 第一个a可以被自动包装成Character对象,也可以被向上转型为float类型
  11. // ! f('a', 'b');//第二个方法会出现编译期错误!ambiguous
  12. }
  13. }

上面的这两个重载的方法可以简化为f(float)f(Character),为了解决这个问题,可以让这两个方法都添加一个非可变参数,即将f(Character... args)进行一下修改:

  1. // 可以简化这两个重载的方法,如同`a`到重载的方法f(char)与f(float)。
  2. static void f(char a, Character... args) {
  3. System.out.print("second");
  4. }// f('a', 'b')的情况下,仅匹配char的形参。

建议:我们应该只在重载方法的一个版本上使用可变参数列表,或者压根不使用它。


自动装箱

自动装箱的类:基本数据类型对象包装类。

为了方便操作基本数据类型值,将其封装成了对象。在对象中定义了属性和行为丰富了该数据的操作,用于描述该对象的类就称为基本数据类型对象包装类。

基本数据类型 包装类型
boolean Boolean
char Character
byte Byte
short Short
int Integer
long Long
float Float
double Double
  1. public class WrapperDemo {
  2. /**
  3. * @param args
  4. */
  5. public static void main(String[] args) {
  6. Integer i = 4; // i = new Integer(4);自动装箱 简化书写。
  7. i = i + 6; // i = new Integer(i.intValue() + 6); //i.intValue() 自动拆箱
  8. Integer a = new Integer(128);
  9. Integer b = new Integer(128);
  10. System.out.println(a==b);
  11. System.out.println(a.equals(b));
  12. // jdk1.5以后,自动装箱,如果装箱的是一个字节[一个字节8位最多127]
  13. // 那么该数据会被共享不会重新开辟空间。
  14. Integer x = 129;
  15. Integer y = 129;
  16. System.out.println(x==y);
  17. System.out.println(x.equals(y));
  18. Integer m = 127;
  19. Integer n = 127;
  20. System.out.println(m==n);
  21. }
  22. } /* Output:
  23. false
  24. true
  25. false
  26. true
  27. true
  28. *///:~
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注