Java基础知识【下】,
Java基础知识【下】,
(最终还是决定重新写一份Java基础相关的内容,原来因为在写这一个章节的时候没有考虑到会坚持往后边写,这次应该是更新该内容。而且很讨厌写基础的东西,内容比较琐碎,而且整理起来总会很多,有可能会打散成两个章节,但是我不保证,有可能一个章节就写完了,所以有时候希望基础的很多内容还是读者自己去看看,我基本保证把基础的内容全部都写出来,见谅。这一个章节写了过后我会把前边那个关于基础类型的章节从目录里面删除掉,以保证教材的完整性和唯一性,防止有人收藏过链接,我会继续保留在BLOG地址上边不删除,所以请读者见谅!初学者可以从本章的第三小节开始看,因为前两个章节内容也是比较概念性的,刚开始可以不去理解。这里直接从Java语法开始,不提及Java的历史以及Java的前序发展,如果有什么笔误,就发我Email:silentbalanceyh@126.com)本章目录 1.概念以及提纲 2.语言基础 3.数据类型[一部分] 4.操作符 5.控制流程 6.关键字清单
5)Java的浮点精度:
[1]精确的浮点运算:
在Java里面,有时候为了保证数值的准确性需要精确的数据,先提供一个例子就可以发现问题了:
package org.susan.java.basic;
public class FloatNumberTester { public static void main(String args[]){ System.out.println(0.05+0.01); System.out.println(1.0 - 0.42); System.out.println(4.015 * 100); System.out.println(123.3 / 100); } } 按照我们的期待,上边应该是什么结果呢,但是看输出我们就会发现问题了: 0.060000000000000005 0.5800000000000001 401.49999999999994 1.2329999999999999 这样的话这个问题就相对严重了,如果我们使用123.3元交易,计算机却因为1.2329999999999999而拒绝了交易,岂不是和实际情况大相径庭 [2]四舍五入: 另外的一个计算问题,就是四舍五入。但是Java的计算本身是不能够支持四舍五入的,比如: package org.susan.java.basic;
public class GetThrowTester { public static void main(String args[]){ System.out.println(4.015 * 100.0); } } 这个输出为: 401.49999999999994 所以就会发现这种情况并不能保证四舍五入,如果要四舍五入,只有一种方法java.text.DecimalFormat: package org.susan.java.basic;
import java.text.DecimalFormat;
public class NumberFormatMain { public static void main(String args[]){ System.out.println(new DecimalFormat("0.00").format(4.025)); System.out.println(new DecimalFormat("0.00").format(4.024)); } } 上边代码输出为: 4.02 4.02 发现问题了么?因为DecimalFormat使用的舍入模式,关于DecimalFormat的舍入模式在格式化一章会讲到 [3]浮点输出: Java浮点类型数值在大于9999999.0就自动转化成为科学计数法,看看下边的例子: package org.susan.java.basic;
public class FloatCounter { public static void main(String args[]){ System.out.println(9969999999.04); System.out.println(199999999.04); System.out.println(1000000011.01); System.out.println(9999999.04); } } 输出结果为: 9.96999999904E9 1.9999999904E8 1.00000001101E9 9999999.04 但是有时候我们不需要科学计数法,而是转换成为字符串,所以这样可能会有点麻烦。 [4]BigInteger类和BigDecimal介绍: BigInteger用法: package org.susan.java.basic;
import java.math.BigInteger;
public class BigIntTester { public static void main(String args[]){ BigInteger leftInteger = new BigInteger("1234567890123400"); BigInteger rightInteger = BigInteger.valueOf(123L); //大数加法 BigInteger resultInteger = leftInteger.add(rightInteger); System.out.println("Add:" + resultInteger); //大数除法 resultInteger = leftInteger.divide(rightInteger); System.out.println("Divide:" + resultInteger); //大数减法 resultInteger = leftInteger.subtract(rightInteger); System.out.println("Substract:" + resultInteger); //大数乘法 resultInteger = leftInteger.multiply(rightInteger); System.out.println("Multiply:" + resultInteger); } } 上边的输出为: Add:1234567890123523 Divide:10037137318076 Substract:1234567890123277 Multiply:151851850485178200 在BigInteger里面有很多运算,这里只是提供了普通的+、-、*、/的运算,可以参考以下BigInteger的方法列表: BigInteger abs():返回BigInteger的绝对值 BigInteger add(BigInteger val):返回为(this + val)的BigInteger,做加法 BigInteger and(BigInteger val):返回为(this & val)的BigInteger,进行位与运算 BigInteger andNot(BigInteger val):返回为(this & ~val)的BigInteger int bitCount():返回此BigInteger的二进制补码表示形式中与符号不同的位的常量 int bitLength():返回此BigInteger的最小的二进制补码表示形式的位数,不包括符号位 BigInteger clearBit(int n):返回其值与清除了指定位的此BigInteger等效的BigInteger int compareTo(BigInteger val):将此BigInteger与指定的BigInteger进行比较 BigInteger divide(BigInteger val):返回值为(this/val)的BigInteger,做除法 BigInteger[] divideAndRemainder(BigInteger val):包含返回(this/val)后跟(this & val)的两个BigInteger的数组 double doubleValue():将此BigInteger转换为double boolean equals(Object s):比较此BigInteger与指定的Object的相等性 BigInteger flipBit(int n):返回其值与对此BigInteger进行指定位翻转过后的值等效的BigInteger float floatValue():将此BigInteger转换为float BigInteger gcd(BigInteger val):返回一个BigInteger,值为abs(this)和abs(val)的最大公约数 int getLowestSetBit():返回此 BigInteger 最右端(最低位)1 比特的索引(即从此字节的右端开始到本字节中最右端 1 比特之间的 0 比特的位数) int hashCode():返回此BigInteger的哈希码 int intValue():将此BigInteger转换为int boolean isProbablePrime(int certaintry):如果此 BigInteger 可能为素数,则返回 true,如果它一定为合数,则返回 false long longValue():将此BigInteger转换为long BigInteger max(BigInteger val):返回this和val的最大值 BigInteger min(BigInteger val):返回this和val的最小值 BigInteger mod(BigInteger val):返回this mod val的结果,取模运算 BigInteger modInverse(BigInteger val):返回其值为 (this-1 mod val) 的 BigInteger BigInteger modPow(BigInteger exponent,BigInteger val):返回其值为 (thisexponent mod val) 的 BigInteger BigInteger multiply(BigInteger val):返回值为(this*val)的BigInteger BigInteger negate():返回值是(-this)的BigInteger BigInteger nextProbablePrime():返回大于此BigInteger的可能为素数的第一个整数 BigInteger not():返回其值为(~this)的BigInteger BigInteger or(BitInteger val):返回值为(this|val)的BigInteger BigInteger pow(int exponent):返回其值为 (thisexponent) 的 BigInteger static BigInteger probablePrime(int bitLength,Random rnd):返回有可能是素数的、具有指定长度的正 BigInteger BigInteger remainder(BigInteger val):返回其值为 (this % val) 的 BigInteger BigInteger setBit(int n):返回其值与设置了指定位的此 BigInteger 等效的 BigInteger BigInteger shiftLeft(int n):返回其值为 (this << n) 的 BigInteger BigInteger shiftRight(int n):返回其值为 (this >> n) 的 BigInteger int signum():返回此 BigInteger 的正负号函数 BigInteger substract(BigInteger val):返回其值为 (this - val) 的 BigInteger BigInteger xor(BigInteger val):返回其值为 (this ^ val) 的 BigInteger BigDecimal用法: BigDecimal是Java提供的一个不变的、任意精度的有符号十进制数对象。它提供了四个构造器,有两个是用BigInteger构造,在这里我们不关心,我们重点看用double和String构造的两个构造器: BigDecimal(double val):把一个double类型十进制数构造为一个BigDecimal对象实例 BigDecimal(String val):把一个以String表示的BigDecimal对象构造为BigDecimal对象实例 用一段代码来说明两个构造函数的区别: package org.susan.java.basic;
import java.math.BigDecimal;
public class BigDecimalMain { public static void main(String args[]){ System.out.println(new BigDecimal(123456789.01).toString()); System.out.println(new BigDecimal("123456789.01").toString()); } } 看了输出结果过后,两种构造函数的区别一目了然: 123456789.01000000536441802978515625 123456789.01 BigDecimal舍入模式介绍: 舍入模式在java.math.RoundingMode里面: RoundingMode.CEILING:向正无限大方向舍入的舍入模式。如果结果为正,则舍入行为类似于 RoundingMode.UP;如果结果为负,则舍入行为类似于 RoundingMode.DOWN。注意,此舍入模式始终不会减少计算值
输入数字 | 使用CEILING舍入模式将数字舍入为一位数 |
5.5 | 6 |
2.5 | 3 |
1.1 | 2 |
1.0 | 1 |
-1.0 | -1 |
-1.1 | -1 |
-1.6 | -1 |
-2.5 | -2 |
-5.5 | -5 |
输入数字 | 使用DOWN舍入模式将数字舍入为一位数 |
5.5 | 5 |
2.5 | 2 |
1.1 | 1 |
-1.0 | -1 |
-1.6 | -1 |
-2.5 | -2 |
-5.5 | -5 |
输入数字 | 使用FLOOR舍入模式将输入数字舍入为一位 |
5.5 | 5 |
2.3 | 2 |
1.6 | 1 |
1.0 | 1 |
-1.1 | -2 |
-2.5 | -3 |
-5.5 | -6 |
输入数字 | 使用HALF_DOWN输入模式舍入为一位 |
5.5 | 5 |
2.5 | 2 |
1.6 | 2 |
1.0 | 1 |
-1.1 | -1 |
-1.6 | -2 |
-2.5 | -2 |
-5.5 | -5 |
输入数字 | 使用HALF_EVEN舍入模式将输入舍为一位 |
5.5 | 6 |
2.5 | 2 |
1.6 | 2 |
1.1 | 1 |
-1.0 | -1 |
-1.6 | -2 |
-2.5 | -2 |
-5.5 | -6 |
输入数字 | 使用HALF_UP舍入模式舍入为一位数 |
5.5 | 6 |
2.5 | 3 |
1.6 | 2 |
1.0 | 1 |
-1.1 | -1 |
-1.6 | -2 |
-2.5 | -3 |
-5.5 | -6 |
输入数字 | 使用UNNECESSARY模式 |
5.5 | 抛出 ArithmeticException |
2.5 | 抛出 ArithmeticException |
1.6 | 抛出 ArithmeticException |
1.0 | 1 |
-1.0 | -1.0 |
-1.1 | 抛出 ArithmeticException |
-1.6 | 抛出 ArithmeticException |
-2.5 | 抛出 ArithmeticException |
-5.5 | 抛出 ArithmeticException |
输入数字 | 使用UP舍入模式将输入数字舍入为一位数 |
5.5 | 6 |
1.6 | 2 |
1.1 | 2 |
1.0 | 1 |
-1.1 | -2 |
-1.6 | -2 |
-2.5 | -3 |
-5.4 | -6 |
import java.math.BigDecimal; import java.text.DecimalFormat; /** *使用舍入模式的格式化操作 **/ public class DoubleFormat { public static void main(String args[]){ DoubleFormat format = new DoubleFormat(); System.out.println(format.doubleOutPut(12.345, 2)); System.out.println(format.roundNumber(12.335, 2)); } public String doubleOutPut(double v,Integer num){ if( v == Double.valueOf(v).intValue()){ return Double.valueOf(v).intValue() + ""; }else{ BigDecimal b = new BigDecimal(Double.toString(v)); return b.setScale(num,BigDecimal.ROUND_HALF_UP).toString(); } } public String roundNumber(double v,int num){ String fmtString = "0000000000000000"; //16bit fmtString = num>0 ? "0." + fmtString.substring(0,num):"0"; DecimalFormat dFormat = new DecimalFormat(fmtString); return dFormat.format(v); } } 这段代码的输出为: 12.35 12.34
ii.数组、复杂类型 1)数组的定义、初始化: 在Java语言里面使用下边的方式定义数组: Type[] arrayone; Type arrayone[]; 这里的Type可以是基本数据类型,也可以是Object类的子类的引用,arrayone在这里为一个Java合法的标识符 ——[$]这里提供一个参考代码进行数组的定义和初始化—— package org.susan.java.basic;
import java.util.Arrays; /** *数组的定义和初始化过程 **/ public class ArraysMain { public static void main(String args[]){ String[] arrays1 = new String[3]; String[] arrays2 = new String[]{"A","B","C"}; String[] arrays3 = {"A","B","C","D"}; System.out.println("Array 1:" + Arrays.asList(arrays1)); System.out.println("Array 2:" + Arrays.asList(arrays2)); System.out.println("Array 3:" + Arrays.asList(arrays3)); } } 先看上边代码段的输出: Array 1:[null, null, null] Array 2:[A, B, C] Array 3:[A, B, C, D] 这里简单总结一下Java里面定义、创建、初始化数组的一些细节问题:
- 定义数组一般用Type[] arrays或者Type arrays[]两种格式,Java里面推荐使用Type[] arrays这种方式定义数组
- 创建数组的时候可以使用三种方式创建,上边的arrays1创建了一个长度为3的字符串数组,但是未被初始化
- 初始化过程有两种方式,上边arrays2和arrays3是直接赋值,这种情况不仅仅创建了数组,而且进行了初始化的操作,还有一种方式像下边这样针对每个元素初始化
import java.util.Arrays;
public class ArrayItemMain { public static void main(String args[]){ String[] arrayStrings = new String[3]; for(int i = 0; i < arrayStrings.length; i++){ arrayStrings[i] = new String("String" + i); } System.out.println(Arrays.asList(arrayStrings)); } } 上边的输出为: [String0, String1, String2] 由此可以知道,在这样的循环里面已经将定义的长度为3的字符串数组的每一项都初始化了,而且值分别为String0,String1,String2,而且从上边可以知道,数组的元素访问使用索引进行访问,格式为: arrays[index],这里需要特殊说明的是索引的范围:0 <= index < arrays.length;关于索引的范围需要特别注意,最小的index是0,最大的index值是length-1 2)多维数组: Java里面不仅仅可以定义一纬数组,还可以定义多维数组,多维数组的定义如下: int[][] arrays1 = new int[3][2]; int[][] arrays2 = new int[3][]; 不合法的定义方式为: int[][] arrays3 = new int[][2]; 【*:这里定义的多维数组为两维数组,不仅仅如此,根据需要可以定义三维数组或者多维数组,其实在Java里面,二维数组就是数组的数组,也就是说在一纬数组的基础之上每一项又保存了一个数组的引用。】 ——[$]提供一个多维数组的例子—— package org.susan.java.basic;
import java.util.Arrays; import java.util.Random;
public class ArrayMultiMain { public static void main(String args[]){ // 二维规则数组 int[][] arrays = new int[3][3]; Random random = new Random(); for( int i = 0; i < arrays.length; i++){ for( int j = 0; j < arrays[i].length; j++){ arrays[i][j] = random.nextInt(40); } } printArray(arrays); System.out.println("------------------------"); // 二维不规则数组 int[][] arrays1 = new int[3][]; for( int i = 0; i < arrays1.length; i++){ int innerLength = random.nextInt(6) + 1; arrays1[i] = new int[innerLength]; for( int j = 0; j < arrays1[i].length; j++){ arrays1[i][j] = random.nextInt(40); } } //System.out.println(Arrays.asList(arrays1)) printArray(arrays1); System.out.println("------------------------"); int[][] arrays2 = {{12,33},{45,44,46,47},{56,12,9}}; printArray(arrays2); } private static void printArray(int [][] arrays){ for(int[] array:arrays){ for(int item:array){ System.out.print("[" + item + "],"); } System.out.println(); } } } 这里运行的输出为: [8],[16],[4], [36],[2],[34], [20],[35],[37], ------------------------ [4], [8],[38],[37],[7], [18],[17],[36], ------------------------ [12],[33], [45],[44],[46],[47], [56],[12],[9], 针对输出内容需要说明的有几点:
- 第一个数组的行列是定死的,但是里面的数据是随机的,所以每次运行可能里面盛放的项不一样
- 第二个数组为不规则的二维数组,行市定死的,但是每行的列数十不定的,每次运行可能都不一样
- 第三个数组在创建和初始化的时候因为是直接赋值,是一个死数组,所以第三那个数组每次输出都一样
特点一、容量扩充性
从内部实现机制来讲ArrayList和Vector都是使用Objec的数组形式来存储的。当你向这两种类型中增加元素的时候,如果元素的数目超出了内部数组目前的长度它们都需要扩展内部数组的长度,Vector缺省情况下自动增长原来一倍的数组长度,ArrayList是原来的50%,所以最后你获得的这个集合所占的空间总是比你实际需要的要大。所以如果你要在集合中保存大量的数据那么使用Vector有一些优势,因为你可以通过设置集合的初始化大小来避免不必要的资源开销。
特点二、同步性
ArrayList,LinkedList是不同步的,而Vestor是的。所以如果要求线程安全的话,可以使用ArrayList或LinkedList,可以节省为同步而耗费开销。但在多线程的情况下,有时候就不得不使用Vector了。当然,也可以通过一些办法包装ArrayList,LinkedList,使他们也达到同步,但效率可能会有所降低。
特点三、数据操作效率
ArrayList和Vector中,从指定的位置(用index)检索一个对象,或在集合的末尾插入、删除一个对象的时间是一样的,可表示为O(1)。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置。为什么会这样呢?以为在进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行(n-i)个对象的位移操作。LinkedList中,在插入、删除集合中任何位置的元素所花费的时间都是一样的—O(1),但它在索引一个元素的时候比较慢,为O(i),其中i是索引的位置。所以,如果只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是对其它指定位置的插入、删除操作,最好选择LinkedList、ArrayList 和Vector是采用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,都允许直接序号索引元素,但是插入数据要设计到数组元素移动等内存操作,所以索引数据快插入数据慢,Vector由于使用了synchronized方法(线程安全)所以性能上比ArrayList要差,LinkedList使用双向链表实现存储,按序号索引数据需要进行向前或向后遍历,但是插入数据时只需要记录本项的前后项即可,所以插入数度较快。
4.操作符
操作 | 优先级 | 结合性 |
后缀运算符 | [] . () 函数调用 | 从左到右 |
单目运算符 | ! ~ ++ -- +(单操作符) -(单操作符) | 从右到左 |
创建 | new | 从左到右 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> >>> | 从左到右 |
关系 | < <= > >= instanceof | 从左到右 |
相等 | == != | 从左到右 |
按位与 | & | 从左到右 |
按位异或 | ^ | 从左到右 |
按位或 | | | 从左到右 |
逻辑与 | && | 从左到右 |
逻辑或 | || | 从左到右 |
条件 | ? : | 从右到左 |
赋值 | = += -= *= /= %= ^= <<= >>= >>>= | 从右到左 |
// Reference类型 System.out.println("--------------------"); StringBuffer bBuffer = new StringBuffer("b"); StringBuffer aBuffer = bBuffer; System.out.println("Before assign aBuffer is " + aBuffer); System.out.println("Before assgin bBuffer is " + bBuffer); aBuffer.append(" assign a"); System.out.println("Before assign aBuffer is " + aBuffer); System.out.println("Before assgin bBuffer is " + bBuffer); } } 上边程序的输出为: -------------------- Before assign aNumber is 1 Before assign bNumber is 1 After assign aNumber is 12 After assign bNumber is 1 -------------------- Before assign aBuffer is b Before assgin bBuffer is b Before assign aBuffer is b assign a Before assgin bBuffer is b assign a 【*:上边的程序输出刚好说明了上边的两点内容,这里有一点需要注意的是,在使用引用赋值的时候,所说的修改引用指代对象的内容的时候,这里不能使用String类型,因为使用了String类型就会出现很特殊的情况,这一点在String的说明里面我会特别讲述,需要重视的一点是:修改对象内容。这里针对StringBuffer而言,append方法就修改了StringBuffer对象的内容,所以这个程序这样说明是没有问题的,这里再次提醒一下:String类型的对象属于不可变对象,在针对不可变对象的时候,修改对象内容的原理和可变对象修改对象内容的原理不一样的,所以这里不能使用String,演示的代码使用的是StringBuffer。】 简单总结:
- leftVarl op= rightVal格式的表达式等同于leftVal = leftVal op rightVal
- 原始类型赋值操作和引用类型的赋值操作有本质的区别
- 可变对象和不可变对象对内容的更改本质也不一样的
double b1 = 4.5f; double a1 = 2; double result1 = b1 % a1; System.out.println(" b % c = " + (b % c)); System.out.println(" b1 % a1 = " + result1); } } 上边的输出为: c + b / a = 5 c * b / a = 6 b % c = 1 b1 % a1 = 0.5 根据上边的运算符,需要说明一点: 在Java运算符里面,不仅仅像C++一样支持整数取模,而且还支持浮点类型的取模,就像上边的最后一个表达式result1 = b1 % a1,就是进行的浮点取模操作;而运算符的优先级,上边代码已经一目了然了这里就不重复了 iii.自动递增、递减运算: Java和C++一样存在前递增和前递减(++A和--A),这种情况先进行运算,再生成值;后递增和后递减(A++和A--),会先生成值,再执行运算;因为++和--操作一样,所以提供一段说明代码: package org.susan.java.basic; /** *自动递增递减的代码示例 **/ public class AutoIncreasement { public static void main(String args[]){ int i = 3; int j = 3; System.out.println("++i:" + (++i)); System.out.println("j++:" + (j++)); System.out.println("i:" + i); System.out.println("j:" + j); System.out.println(i==(i++)); System.out.println(i==(++i));
float floatValue = 0.3F; System.out.println(floatValue++); System.out.println(floatValue); } } 上边代码的输出为: ++i:4 j++:3 i:4 j:4 true false 0.3 1.3 需要说明的是:A++和A = A + 1是等效的,++A的最终执行效果和 A = A + 1也是等效的,二者只有一个最简单的区别,上边代码可以看出来 [1]前递增(递减)和后递增(递减)就是上边表达的那种情况,但是有一点特殊就是前递增(递减)是先改变变量的值,而后递增(递减)是在进行运算过后修改变量的值 [2]而且在Java里面,自动递增递减是支持浮点类型的,最后那一些代码可以说明这一点 注意:可以这样来理解,i,i++,++i代表三个不同的变量,如果i的值是a,那么i++本身的值是在递增之前返回的,i++的判断是和i一样的,而++i本身的值是在递增之后返回的,++i和i的判断却是不一样的,所以这里应该理解的是变量是什么时候进行更改的。主要参考上边的代码: System.out.println(i==(i++)); System.out.println(i==(++i)); 根据这段代码的输出就可以知道(更改变量的时间)在自增自减的运算中的重要性了,提供一个更加迷惑的例子: package org.susan.java.basic; /** *一个比较迷惑的自增自减的例子 **/ public class AutoIncreaseMain { public static void main(String args[]){ int i = 4; int j = 6; int result = i+++ ++j + i+++ ++i + i + ++i; System.out.println("result:" + result); int a = 5; int result1 = a+++a+++a+++a++; System.out.println("result1:" + result1); } } 先看输出: result:38 result1:26 仔细分析一下上边的输出: 在分析过程模拟一个堆栈,把每次的结果都压入堆栈进行运算: 第一个表达式result的结果: [1]最先压入栈的是i++,这个地方i的值为4,那么i++返回值就是4,但是运算过后i的值变成了5,结果为:4 + [2]然后压入栈的是++j,这个地方j的值为6,那么++j返回值就是7,但是运算过后同样使得j变成了7,结果为:4 + 7 + [3]然后压入栈的同样是i++,但是这个时候i的值为5,那么i++也是5,运算过后i的值变成了6,结果为:4 + 7 + 5 + [4]按照这种方式运算下去,最终的结果是:4 + 7 + 5 + 7 + 7 + 8,所以result的值为38 按照同样的方式可以运算result1的结果:result1的最终结果为26;至于这种操作的表达式写法有一定的规则,下边的是合法和不合法的例子: 合法的写法: i+++ ++i; i+++i++; i+ ++i; i+++i; i++ +(++i); 不合法的写法: i+++++i; i++ +++i; 至于其他的写法对与不对可以直接在Eclipse里面进行编译来判断表达式的正确与否 iv.关系运算符: 关系运算符包括<、>、<=、>=、==、!=: 关于关系运算符需要说明的是:
- ==和!=是适合所有的基本类型(Primitives)的,但是其他关系运算符不能比较boolean
- 如果是针对两个引用指向的对象内容进行比较,必须用特殊的操作方法equals()
- ==和!=在比较两个引用的时候,比较的是引用的指向,而不是对象的内容信息
public class CompareMain { public static void main(String args[]){ int i = 4; System.out.println((i==4)); System.out.println((i!=4)); System.out.println((i >= 3)); System.out.println((i < 4)); System.out.println((i > 6)); System.out.println((i <= 4)); } } 上边的输出为: true false true false false true v.逻辑运算符和按位运算符: 逻辑运算符有&&、||、!能够结合表达式生成一个boolean返回值 按位运算符AND(&)、OR(|)、XOR(^)、NOT(~)运算 下边是所有的逻辑关系表: 非关系表:
A | !A |
true | false |
false | true |
A | B | A && B |
false | false | false |
true | false | false |
false | true | false |
true | true | true |
A | B | A || B |
false | false | false |
true | false | true |
false | true | true |
true | true | true |
A | ~A |
1 | 0 |
0 | 1 |
A | B | A & B |
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
A | B | A | B |
1 | 1 | 1 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
A | B | A ^ B |
1 | 1 | 0 |
1 | 0 | 1 |
0 | 1 | 1 |
0 | 0 | 0 |
public class ShortTruFalseTester { public static void main(String args[]){ boolean a = true; boolean b = false; System.out.println(!a); System.out.println(a && b); System.out.println(a || b);
int aNumber = 15; int bNumber = 5; System.out.println(aNumber & bNumber); System.out.println(aNumber | bNumber); System.out.println(aNumber ^ bNumber); System.out.println(~aNumber); } } 输出为: false false true 5 15 10 -16 逻辑运算符这里不做任何解释,但是另外三个计算机底层的位运算符是需要简单解释一下: aNumber=15中,aNumber转换成为二进制位为:1111 bNumber=5中,bNumber转换成为二进制位为:0101 1111&0101的结果每一位进行&运算可以得到1111&0101=0101,运算规则就为每一位1&1=1,1&0和0&1为0,0&0=0,最后结果转换为0101=5 1111|0101的结果是每一位进行|运算1111|0101=1111,运算规则为1|1=1,1|0=1和0|1=1,0|0=0,最后结果为1111=15 1111^0101的结果是每一位进行^运算1111^0101=1010,运算规则为1^1和0^0为0,1^0和0^1为1,最后结果为1010=10 ~aNumber在这个地方相对复杂: 其实在32bit系统中,aNumber的真实数值为: 0000 0000 0000 0000 0000 0000 0000 1111 按位运算结果为: 1111 1111 1111 1111 1111 1111 1111 0000 转化成为数值就为:-16 vi.三元操作符: 三目运算符是一个特殊的运算符,它的语法形式如下: 布尔表达式?表达式1:表达式2 运算过程,如果布尔表达式的值为true就返回表达式1的值,如果为false返回表达式2的值,简单用一个例子说明: package org.susan.java.basic; /** *三元操作符 **/ public class ThirdMain { public static void main(String args[]){ int sum = 90; String resultValue = (sum > 90)?"Bigger than 90":"Smaller than 90"; String resultValue1 = (sum < 100)?"Smaller than 100":"Bigger than 100"; System.out.println(resultValue); System.out.println(resultValue1); } } 输出结果为: Smaller than 90 Smaller than 100 vii.移位运算符: 移位运算符操作的对象是二进制位,可以单独用移位运算符来处理int类型的整数,下边是概念表:
运算符 | 含义 | 例子 |
<< | 左移运算符,将运算符左边的对象向左异动运算符右边指定的位(在低位补0) | x << 3 |
>> | "有符号"右移运算 符,将运算符左边的对象向右移动运算符右边指定的位数。使用符号扩展机制,也就是说,如果值为正,则在高位补0,如果值为负,则在高位补1。 | x >> 3 |
>>> | "无符号"右移运算 符,将运算符左边的对象向右移动运算符右边指定的位数。采用0扩展机制,也就是说,无论值的正负,都在高位补0。 | x >>> 3 |
有符号的整数 | 原码 | 反码 | 补码 |
47 | 00101111 | 00101111 | 00101111(正数补码和原码、反码相同,不能从字面的值进行理解) |
-47 | 10101111 | 11010000 | 11010001(负数补码是在反码上加1) |
-5>>3=-1
1111 1111 1111 1111 1111 1111 1111 1011
1111 1111 1111 1111 1111 1111 1111 1111
其结果与 Math.floor((double)-5/(2*2*2)) 完全相同。
-5<<3=-40
1111 1111 1111 1111 1111 1111 1111 1011
1111 1111 1111 1111 1111 1111 1101 1000
其结果与 -5*2*2*2 完全相同。
5>>3=0
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0000 0000
其结果与 5/(2*2*2) 完全相同。
5<<3=40
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0010 1000
其结果与 5*2*2*2 完全相同。
-5>>>3=536870911
1111 1111 1111 1111 1111 1111 1111 1011
0001 1111 1111 1111 1111 1111 1111 1111
无论正数、负数,它们的右移、左移、无符号右移 32 位都是其本身,比如 -5<<32=-5、-5>>32=-5、-5>>>32=-5。
一个有趣的现象是,把 1 左移 31 位再右移 31 位,其结果为 -1。
0000 0000 0000 0000 0000 0000 0000 0001
1000 0000 0000 0000 0000 0000 0000 0000
1111 1111 1111 1111 1111 1111 1111 1111
小结:到这里基本的Java运算符就已经讲完了,唯一没有讲到的就是instanceof操作符以及关于String的一些操作符,instanceof已经在《类和对象》章节里面讲到了,而String的操作会在String的专程章节里面讲述。
public class CommonFlow { public static void main(String args[]){ System.out.println("Step One"); System.out.println("Step Two"); System.out.println("Step Three"); } } 上边的输出这里就不列出了,它会按照顺序打印Step One——>Step Two——>Step Three 2)选择运行: 选择运行需要使用到Java里面的条件语句,条件语句主要包括: [1]if语句; [2]if...else语句; [3]switch语句: if语句: if语句的语法有两种: if(boolean表达式) 语句1; if(boolean表达式){ 语句1;语句2;} 它所意味着的含义为如果boolean表达式的返回为true就执行语句1或者执行语句1所在的语句块{}内的内容: package org.susan.java.basic;
import java.util.Random;
public class IfSingleMain { public static void main(String args[]){ Random random = new Random(); int result = random.nextInt(3); if( result == 2){ System.out.print("This is if block,"); System.out.println("Test block flow."); } if( result == 1) System.out.println("This is if single flow."); System.out.println("This is Inner or Outer."); //这句的写法是故意的 System.out.println("Main flow."); } } 上边这段代码的输出是不固定的,主要是看result返回的值是多少,它会随机生成三个整数值:1、2、3 result值为2的时候直接是块语句,所以不需要讲解什么,但是需要知道if还支持直接的不带块的语句,所以不论任何值生成,都会有下边这两句输出: This is Inner or Outer. Main flow. 这里提醒读者的只有一个关键点:在使用if语句的时候,如果后边的语句块不带{},那么它能产生的效果只有紧跟着if的后边一句话(单条语句),这种情况下,其他的语句都会视为和if无关的语句 if-else语句 该语句的语法为: if(布尔表达式1) {语句执行块1} else if(布尔表达式2) {语句执行块2} else {语句执行块3} 针对该语句,简单修改一下上边的程序: package org.susan.java.basic;
import java.util.Random;
public class IfSingleMain { public static void main(String args[]){ Random random = new Random(); int result = random.nextInt(3); if( result == 2){ System.out.print("This is if block,"); System.out.println("Test block flow."); }else if( result == 1){ System.out.println("This is if single flow."); System.out.println("This is Inner or Outer."); }else { System.out.print("This is other flow."); System.out.println("This is single else flow."); } System.out.println("Main flow."); } } 这是一个简单的if-else语句,这里只用了else的块语句,没有使用else的单条语句,如果使用else的单条语句,和if是一样的规则: 如果result随机生成的值是0【*:这个地方result只可能有三个值就是0,1,2】,就会得到下边的结果: This is other flow.This is single else flow. Main flow. 如果去掉最后一个else后边的{}后会有什么效果呢,去掉了最后一个括号过后,下边的语句不论result为任何值的时候都会输出: This is single else flow. Main flow. 原因和if语句一样,如果紧随其后的不是{}的语句块,只能影响一行完整的语句,也就是当去掉上边代码最后一个else后边的{}过后,else只能影响语句: System.out.print("This is other flow."); switch语句: 该语句的语法为: switch(输入因子){ case 匹配因子1:{执行语句块1;}break; caes 匹配因子2:{执行语句块2;}break; default:{执行语句块3;}break; } 当编程过程需要多个分支语句的时候,就需要使用到switch语句,也就是进行多项选择的语句,这里同样提供一个概念说明例子: package org.susan.java.basic;
import java.util.Random;
public class SwitchTester { public static void main(String args[]){ Random random = new Random(); int result = random.nextInt(4); switch (result) { case 1: System.out.println("Result is 1"); break; case 2: System.out.println("Result is 2"); case 3: System.out.println("Result is 3"); break; default: System.out.println("Result is 4"); break; } } } 当result的值为各种值的时候输出为: Result is 1【*:result的值为1的输出】 Result is 2 Result is 3【*:result的值为2的输出】 Result is 3【*:result的值为3的输出】 Result is 4【*:result的值为0的输出】 针对switch语句有几点需要说明:
- switch后边的括号里面的输入因子有类型限制的,只能为:
[1]int类型的变量
[2]short类型的变量
[3]char类型的变量
[4]byte类型的变量
[5]enum的枚举类型的变量【JDK1.5过后支持】
其他的变量都是不能传入switch的输入因子的 - 关于case语句的一点点限制:
[1]case语句后边必须是一个整型的常量或者常量表达式
[2]如果使用常量表达式,表达式中的每个变量,必须是final的
[3]case后边不能是非final的变量,比如使用一个case val这种情况将会直接通不过编译,因为val不是final类型的变量,只是一个普通变量
[4]case后边的常量和常量表达式不能使用相同的值 - 关于语句的执行顺序:
当接收到合法的输入因子过后,JVM会去寻找和系统输入因子匹配的case语句,如果没有任何一个case语句匹配的话就直接执行default语句;当匹配到case语句过后,会执行该case到紧接着代码里面写的该case的下一个case之间的语句块,一旦执行完该语句块过后,如果没有遇到return或者break就继续执行下一个case,执行的流程和该case的执行流程是一样的,知道碰到最终的case为止。所以就可以理解为什么上边的代码当result的值为2的时候会输出:
Result is 2
Result is 3
public class WhileLoopMain { public static void main(String args[]){ int i = 4; System.out.println("While loop:"); while(i < 4){ System.out.println(i); i++; } i = 4; System.out.println("Do While loop:"); do{ System.out.println(i); i++; }while(i < 4); } } 上边是一个很极端的例子,先看输出,然后再分析结果: While loop: Do While loop: 4 这里可以知道的是在while语句里面,先判断i<4,因为i的初始值是4,所以该条件不成立,所以while语句里面直接跳过语句执行块,不打印任何内容;但是针对do-while语句而言,虽然i的初始值也是4,但是i<4是在进行了一次运行过后才比较的,实际上细心的读者会发现,两个循环返回false的条件不一样。第一个while语句是因为4<4返回false的,而do-while语句却是因为5<4返回false的,这里可以看出while和do-while的细微差异 for循环: 语法规则为: for(初始条件;判断条件;变化条件){执行语句块;} for(每一项:包含项的列表)【*:等效于C#里面的foreach语句,而且是JDK1.5里面才支持的功能】 同样用一段代码来说明用法: package org.susan.java.basic;
public class ForLoopMain { public static void main(String args[]){ String[] arrayStrings = {"A","B","C"}; //进行arrayStrings的遍历 for( int i= 0; i < arrayStrings.length; i++){ System.out.println(arrayStrings[i]); } System.out.println("---------------"); for(String item:arrayStrings){ System.out.println(item); } } } 上边代码的输出为: A B C --------------- A B C 这两种都可以进行循环遍历操作,简单总结以下前两种循环:
- 一般情况下,while和do-while循环主要用于无限循环,就是不知道循环次数,但是当终止条件满足的时候就退出循环
- for循环一般是用于有限循环,就是已经知道了循环次数,当到达某种条件的时候退出
public class BreakContinueMain { public static void main(String args[]){ System.out.println("Break Loop"); for( int i = 0; i < 4; i++){ if( i == 2) break; System.out.println("Loop " + i); } System.out.println("Continue Loop"); for( int i = 0; i < 4; i++){ if( i == 2) continue; System.out.println("Loop " + i); } } } 上边这段代码的输出为: Break Loop Loop 0 Loop 1 Continue Loop Loop 0 Loop 1 Loop 3 【*:读者仔细思考一下,如果没有break和continue语句,应该依次打印i的值为0,1,2,3的每一句话,第一个循环是Break循环,当i=2的时候直接跳出了该循环,按照break的语法是直接跳出循环,所以只打印了i=0和1的时候的情况。而第二个循环是continue循环,当i=2的时候使用了continue,一旦使用了continue过后,该次循环就不执行了,直接进入下一轮循环,所以在continue循环的语句里面只有i=2的语句没有打印出来。】 ——[$]使用标签—— package org.susan.java.basic;
public class LabelLoopMain { public static void main(String args[]){ outer:for(int i=1; i < 4; i++){ inner:for(int j =0; j < 5; j++){ if( j == 2 ) continue inner; System.out.println("i + j = " + (i+j)); if( j == 4) break outer; } } other:for(int i = 0; i < 4; i++){ if( i == 3){ break other; } System.out.println("i = " + i); } } } 上边这段代码定义了三个循环标签分别为outer,inner,other,输出为: i + j = 1 i + j = 2 i + j = 4 i + j = 5 i = 0 i = 1 i = 2 这里仅仅讲解一下简单的标签的语意,这段程序的详细逻辑留给读者自行去分析
- break labelname:跳出该标签指代的循环,标签指代的循环就是标签的:后边的内容
- continue labelname:继续标签指代的循环,标签指代的循环就是标签的:后边的内容
- 关键字:关键字就是目前正在使用的Java语法
- 字面量:在Sun公司的官方规范里面有明确的说明,false、true和null这三个虽然IDE提供了语法着色,但是这三个不能称为关键字,而是使用的时候提供的字面量
- 保留字:保留字在Java里面称为以后可能会用到的语法,就是定义标识符的时候不能使用,主要有const和goto
- [3]访问控制:private、protected、public
- [13]类、方法和变量修饰符:abstract、class、extends、final、implements、interface、native、new、static、strictfp、synchronized、transient、volatile
- [12]程序控制语句:break、continue、return、do、while、if、else、for、instanceof、switch、case、default
- [5]错误处理:catch、finally、throw、throws、try
- [2]包相关:import、package
- [8]基本类型:boolean、byte、char、double、float、int、long、short
- [3]变量引用:super、this、void
- [1]JDK1.5新关键字:enum
- [2]其他:new、assert
【*:需要说明的是const和goto从概念上来将属于Java里面的保留字,不应该列入关键字之列;true、false和null三个属于字面量,虽然属于Java里面的关键字,但是在规范里面不列入关键字之列。这里解释一下,很多资料都说Java里面有51个关键字,数数上边就会发现只有49个,51个关键字的来历是什么呢?51个关键在是在上边关键字的基础上计算了false、true和null,而且是JDK1.4的说法,还去掉了新关键字enum,所以很多资料都记载了51个关键字。】
7.小节
到这里,整个Java语言最基础的语法、流程、关键字、标识符等各种前边的基础知识就讲完了,没有设计到的内容就是JDK的工具以及JDK环境的配置,基本上用这一个章节直接替代掉原来写的Java基础类型的章节应该方便初学者进行学习,至于目前还没有涉及的IO、格式化、序列化、多线程以及JavaSE的基础部分的内容我会在后边的章节慢慢写上来,只是完成这样一篇BLOG可能花费的时间很长,所以产量比较慢希望读者能够见谅。而且每一次写完了我自己要阅读几遍才能帮着读者标注整个这篇文章的重点以及代码着色。若有什么笔误,请来Email告知,谢谢:silentbalanceyh@126.com
相关文章
- 暂无相关文章
用户点评