@lemonguge
2017-09-13T14:51:07.000000Z
字数 9398
阅读 381
JAVA
while、for和do-while用来控制循环。
while和do-while唯一区别就是do-while中的语句至少会执行一次,即便表达式第一次就被计算为false。for和while可以互换。如果需要通过变量来对循环进行控制,该变量只作为循环增量存在时,区别就体现出来了,即for循环中该变量随着循环结束而从内存消亡,而while循环中该变量却可以继续存在并使用。while(true){}和for(;;){}。if语句;对一个条件进行多次判断时,可以使用while语句。
break用于强行退出循环,不执行循环中剩余的语句;continue则停止当前的迭代,然后退回循环起始处,开始下一次迭代。- 在
java中使用标签的唯一理由就是因为有循环嵌套存在,而且想从多层嵌套中break或continue。
标签是后面跟有冒号的标识符,如lable:。
label1:outer-iterator{inner-iterator{//...break; //中断内部迭代,回到外部迭代,执行statement语句//...continue; //使执行点移回到内部迭代的起始处,进行下一次内部迭代//...continue label1; //同时中断内部迭代以及外部迭代,不执行statement语句,从外部迭代开始继续迭代过程。//...break label1;//中止所有迭代,并回到label1处但不重新进入迭代。}// statement...}
case中的类型(基本数据类型可以经过自动转型后)与switch中的一致。
public class VowelsAndConsonants {public static void main(String[] args) {Random rand = new Random(47);for (int i = 0; i < 11; i++) {int c = rand.nextInt(26) + 'a';printnb((char) c + ", " + c + ": ");switch (c) {case 'a':case 'e':case 'i':case 'o':case 'u':print("vowel");break;case 'y':case 'w':print("Sometimes a vowel");break;default:print("consonant");}}}} /* Output:y, 121: Sometimes a voweln, 110: consonantz, 122: consonantb, 98: consonantr, 114: consonantn, 110: consonanty, 121: Sometimes a vowelg, 103: consonantc, 99: consonantf, 102: consonanto, 111: vowel*///:~
switch(selector)中的选择因子可以为:byte、char、short、int,在JDK5.0后还支持enum和String。switch语句的执行规则:将selector选择因子与每一个case相比较,如果发现相符的,就首先执行case对应的语句,如果语句的结尾有break,则会直接跳转至switch主体的末尾,若省略了break,就会继续执行后面的case语句,直到遇到break或者到执行流程的末尾。
int x = 2;switch (x) {default:System.out.println("d");case 4:System.out.println("a");case 1:System.out.println("b");break;case 3:System.out.println("c");break;}//输出d,a,b//case中没有与x相同的,所以到default中执行语句,由于没有break继续执行下面的语句。
- if
- 对具体的值进行判断。
- 对区间判断。
- 对运算结果是boolean类型的表达式进行判断。
- switch
- 对具体的值进行判断。
- 值的个数通常是固定的。
对于几个固定的值判断,建议使用switch语句,因为switch语句会将具体的答案都加载进内存,效率相对高一点。
在Java SE5中添加了一个看似很小的特性,即enum关键字。由于枚举类型的实例public static final是常量,因此按照命名惯例它们都用大写字母表示。
enum Spiciness {// 可以省略";"分号NOT, MILD, MEDIUM, HOT, FLAMING}public class SimpleEnumUse {public static void main(String[] args) {Spiciness howHot = Spiciness.MEDIUM;System.out.println(howHot);//valueOf方法返回带指定名称的指定枚举类型的枚举常量。Spiciness val = Enum.valueOf(Spiciness.class, "MEDIUM");System.out.println(val + ":" + val.ordinal());System.out.println(Spiciness.valueOf("FLAMING").ordinal());//values方法返回一个包含全部枚举值的数组。for (Spiciness s : Spiciness.values())System.out.println(s + ", ordinal " + s.ordinal());}} /* Output:MEDIUMMEDIUM:24NOT, ordinal 0MILD, ordinal 1MEDIUM, ordinal 2HOT, ordinal 3FLAMING, ordinal 4*///:~
所有的
enum都继承自java.lang.Enum类!所以自定义的enum不能再继承其他类。
我们只能覆盖toString()方法,以下是父类中的部分代码:
private final String name;private final int ordinal;protected Enum(String name, int ordinal) {this.name = name;this.ordinal = ordinal;}public final String name() {return name;}public final int ordinal() {return ordinal;}public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name) {T result = enumType.enumConstantDirectory().get(name);if (result != null)return result;if (name == null)throw new NullPointerException("Name is null");throw new IllegalArgumentException("No enum constant " + enumType.getCanonicalName() + "." + name);}public String toString() {return name;}public final boolean equals(Object other) {return this==other;}public final int hashCode() {return super.hashCode();}protected final void finalize() { }...
ordinal()方法用来表示某个特定enum常量的声明顺序,其中初始常量序数为零。
由于name和ordinal在定义的时候就已经被确定,即被显式初始化了,而final修饰的成员变量一旦被显式初始化便不能被改变!所以我们不能调用父类的构造器super(name, ordinal)再进行初始化,可能大家会想,父类的构造器只有这一种,而我们却不能显式声明调用父类构造器,那还能通过编译吗?请大家放心,底层会帮我们做好这一切的。所以切记不能调用父类的构造器!
而且可以发现Enum类中并没有values()方法,那我们为什么可以使用呢?因为这是编译器为我们创建的enum类添加的static values()方法,而且还额外添加了valueof(String)静态方法,这个方法只需要一个参数。
public enum MyEnum {MyEnum1("Leo", "M", 1), MyEnum2("Mary", "F", 3);private String name;private String sex;private int order;// 绝对不允许有public构造器private MyEnum(String name, String sex, int order) {this.name = name;this.sex = sex;this.order = order;}public String getName() {return name;}public String getSex() {return sex;}public int getOrder() {return order;}public static void main(String[] args) {for (MyEnum s : MyEnum.values()) {System.out.println(s + ", ordinal " + s.ordinal());System.out.println("name:" + s.getName()+" sex:" + s.getSex()+" order:" + s.getOrder());}// ! MyEnum m = new MyEnum("aa","a",2);//annot instantiate the type MyEnum}} /* Output:MyEnum1, ordinal 0name:Leo sex:M order:1MyEnum2, ordinal 1name:Mary sex:F order:3*///:~
虽然有意识的将MyEnum的构造器声明为private,但对于它的可访问性而言,其实并没有什么变化,因为我们只能在MyEnum定义的内部使用构造器创建实例,一旦MyEnum的定义结束,编译器就不允许我们再使用其构造器来创建任何实例!
由于
switch是要在有限的可能值集合中进行选择,因此它与enum正是绝佳的组合。
public class Burrito {Spiciness degree;public Burrito(Spiciness degree) {this.degree = degree;}public void describe() {System.out.print("This burrito is ");switch (degree) {case NOT:System.out.println("not spicy at all.");break;case MILD:case MEDIUM:System.out.println("a little hot.");break;case HOT:case FLAMING:default:System.out.println("maybe too hot.");}}public static void main(String[] args) {Burrito plain = new Burrito(Spiciness.NOT),greenChile = new Burrito(Spiciness.MEDIUM),jalapeno = new Burrito(Spiciness.HOT);plain.describe();greenChile.describe();jalapeno.describe();}} /* Output:This burrito is not spicy at all.This burrito is a little hot.This burrito is maybe too hot.*///:~
程序运算时,对象是怎么进行放置安排的?特别是内存是怎样分配的呢?对这些方面的了解会对我们有很大的帮助。有五个不同的地方可以存储数据:
通过new关键字创建对象,那么对象先会进行默认初始化,如果一个引用与一个新的对象相关联,引用会持有对象在堆内存中的首地址。基本数据类型由于所占存储空间大小的不变性,所以直接存储值,并置于堆栈中,更加高效。
所以也常说:方法中的引用数据类型参数属于址传递,基本数据类型参数属于值传递。
数组只是相同类型的、用同一个标识符名称封装到一起的一个对象序列或基本类型数据序列。
数组通过方括号下标操作符[]来定义和使用的。
创建数组的时候必须显式或隐式的指明数组的长度。
创建数组有三种方式:
// 显式的指明长度int[] arr = new int[3];
// 隐式的指明长度且明确元素内容int[] arr = new int[]{89,34,270,17};
// 隐式的指明长度且明确元素内容,是第二种的简写形式int[] arr = {89,34,270,17};
第一种和第三种的数组定义形式很常用。
由于数组的长度固定,根据一切都是对象的思想,我们可以猜想到数组的长度只有数组这个对象知道,即所有的数组(无论它们的元素是对象还是基本类型)都有一个固有成员。这个成员就是length。
当使用显式指明长度的形式创建数组时,数组元素中的基本数据类型会被自动初始化,引用数据类型会被初始化为null。
public static void main(String[] args) {int[] a;// 使用47作为种子产生一个随机序列Random rand = new Random(47);// 不断的运行可以发现数组的长度为18a = new int[rand.nextInt(20)];System.out.println("length of a = " + a.length);System.out.println(Arrays.toString(a));String[] strs = new String[5];System.out.println(Arrays.toString(strs));// 数组的长度可以为0int[] arr = new int[0];System.out.println(arr.length);} /* Output:length of a = 18[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0][null, null, null, null, null]0*///:~
可变参数列表其实就是数组。
有了可变参数,就再也不用显式地编写数组语法了,当你指定参数时,编译器实际上会为你填充数组。你获取的仍是一个数组。
class A {@Overridepublic String toString() {return "A@" + hashCode();}}public class NewVarArgs {// 该函数功能并没有访问到对象中的特有数据static void printArray(Object... args) {for (Object obj : args)System.out.print(obj + " ");System.out.println();}public static void main(String[] args) {// Can take individual elements:printArray(new Integer(47), new Float(3.14), new Double(11.11));// autoboxingprintArray(47, 3.14F, 11.11);printArray("one", "two", "three");printArray(new A(), new A(), new A());// Or an array:printArray((Object[]) new Integer[] { 1, 2, 3, 4 });// 等价于传入一个长度为0的数组printArray(); // Empty list is OK}} /* Output: (75% match)47 3.14 11.1147 3.14 11.11one two threeA@617353119 A@1360372376 A@16676174701 2 3 4*///:~
该程序的最后一行printArray();表明了将0个参数传递给可变参数列表是可行的,当具有可选的尾随参数时,这一特性就会很有用。
可变参数列表中可以使用任何类型的参数,包括基本数据类型。
public class VarargType {static void f(Character... args) {System.out.print(args.getClass());System.out.println(" length " + args.length);}static void g(int... args) {System.out.print(args.getClass());System.out.println(" length " + args.length);}public static void main(String[] args) {f('a');f();g(1);g();System.out.println("int[]: " + new int[0].getClass());}} /* Output:class [Ljava.lang.Character; length 1class [Ljava.lang.Character; length 0class [I length 1class [I length 0int[]: class [I*///:~
可变参数列表与自动包装机制可以和谐共存。
public class OverloadingVarargs {static void f(Character... args) {System.out.print("first");for (Character c : args)System.out.print(" " + c);System.out.println();}static void f(Integer... args) {System.out.print("second");for (Integer i : args)System.out.print(" " + i);System.out.println();}static void f(Long... args) {System.out.println("third");}public static void main(String[] args) {f('a', 'b', 'c');f(1);f(2, 1);f(0);f(0L);// ! f(); // Won't compile -- ambiguous}} /* Output:first a b csecond 1second 2 1second 0third*///:~
在每一种情况中,编译器都会使用自动包装机制来匹配重载的方法,由于被自动包装所以传入的参数都已经是对象,然后调用最明确匹配的方法。
public class OverloadingVarargs2 {static void f(float i, Character... args) {System.out.println("first");}static void f(Character... args) {System.out.print("second");}public static void main(String[] args) {f(1, 'a');// 第一个a可以被自动包装成Character对象,也可以被向上转型为float类型// ! f('a', 'b');//第二个方法会出现编译期错误!ambiguous}}
上面的这两个重载的方法可以简化为f(float)与f(Character),为了解决这个问题,可以让这两个方法都添加一个非可变参数,即将f(Character... args)进行一下修改:
// 可以简化这两个重载的方法,如同`a`到重载的方法f(char)与f(float)。static void f(char a, Character... args) {System.out.print("second");}// f('a', 'b')的情况下,仅匹配char的形参。
建议:我们应该只在重载方法的一个版本上使用可变参数列表,或者压根不使用它。
自动装箱的类:基本数据类型对象包装类。
为了方便操作基本数据类型值,将其封装成了对象。在对象中定义了属性和行为丰富了该数据的操作,用于描述该对象的类就称为基本数据类型对象包装类。
| 基本数据类型 | 包装类型 |
|---|---|
| boolean | Boolean |
| char | Character |
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
public class WrapperDemo {/*** @param args*/public static void main(String[] args) {Integer i = 4; // i = new Integer(4);自动装箱 简化书写。i = i + 6; // i = new Integer(i.intValue() + 6); //i.intValue() 自动拆箱Integer a = new Integer(128);Integer b = new Integer(128);System.out.println(a==b);System.out.println(a.equals(b));// jdk1.5以后,自动装箱,如果装箱的是一个字节[一个字节8位最多127]// 那么该数据会被共享不会重新开辟空间。Integer x = 129;Integer y = 129;System.out.println(x==y);System.out.println(x.equals(y));Integer m = 127;Integer n = 127;System.out.println(m==n);}} /* Output:falsetruefalsetruetrue*///:~