数据类型
java中一共有8种数据类型,其中4种整形,2种浮点类型,1种用于表示编码的字符类型char,1种用于表示真值的boolean类型。
整形
为了保证移植性,数据类型的取值范围是固定的。
- int 4字节 -2^32 ~ 2^32-1 最常用
- short 2字节 -2^16 ~ 2^16-1 需要控制占用存储空间量的大数组
- long 8字节 -2^64 ~ 2^64-1 比如计算全球的人数
- byte 1字节 -2^8 ~ 2^8-1 底层的文件处理
长整形有个后缀 L 如(40000L),十六进制前缀 0x ,八进制前缀 0 ,java 7 开始有二进制前缀 0b 如(0b111_1010_1000
)下划线会自动去除。
public class DataType {
public static void main(String[] args){
int a = 0b10;
int b = 010;
int c = 10;
int d = 0x10;
System.out.println(a);//2
System.out.println(b);//8
System.out.println(c);//10
System.out.println(d);//16
}
}
浮点类型
表示有小数部分的数值
- floate 4字节 符号位1bit 指数位8bit 尾数位23bit
- double 8字节 符号位1bit 指数位11bit 尾数位52bit
大多数使用double类型,
floate后缀为F或f,没有后缀F的浮点数值默认为doublie,doublie后缀为D或d可以省略。
float e1 = 10;//默认为 int 转为 float 可以
float e2 = 10.0;//出错,默认为 double 转为 float 不行
float e3 = (float)10.0;//默认为 double 强制转换为 float 可以
float e4 = 10.0f;//加上 f ,就为 float 类型
double f = 10.0;//默认为 double
注意:JDK 5.0 中,可以使用十六进制表示浮点数。如0.125 = 0x1.0p-3
,十六进制中使用 p(基数为2) 表示 e(基数为10) 。尾数采用十六进制,指数采用十进制。
用于表示溢出和出错情况的三个特殊的值:
- 正无穷大 Double.POSITIVE_INFINITY
- 负无穷大 Double.NEGATIVE_INFINITY
- NaN Double.NaN
注意:所有值与 Double.NaN 比较都是不相同的
注意:浮点数值不适用禁止出现误差的金融计算中。如2.0-1.1
打印出的是0.8999999999999999
,而不是0.9
。原因是浮点类型采用的是二进制计算,好比十进制计算无法精确表示分数1/10
。如果需要无误差,就是用BigDecimal
类
char 类型
表示单个字符,二个字节,一个char类型可以表示 Unicode 代码单元,它可以为16进制,范围是\u0000~\uFFFF。
转义字符\u
可以表示 Unicode 代码单元。
有很多特殊的转义字符,常用的有:
- \n 换行
- ' 单引号
- " 双引号
- \ 反斜杠
由于 Unicode 两个字节的代码宽度不足以包含世界上全部的字符,JDK 5.0 开始,采用代码点(指一个编码表中的某个字符对应的代码值),Unicode 分为17个代码点,第一个代码点为U+0000~U+FFFF
包括了经典的 Unicode 代码;其余 16 个附加级别从U+10000~U+10FFFF
。
UTF-16 编码采用不同长度的编码表示所有 Unicode 代码点,每个字符用16位表示,被称为代码单元。
强烈建议不要在程序中使用 char 类型,除非需要对 UTF-16 代码单元进行操作。
boolean 类型
有两个值:true 和 false ,整形值和布尔值之间不能相互转换。
变量
每一个变量都属于一种类型。变量声明:
double salary;
int vacattion;
long earthPopulation;
boolean done;
变量必须定义类型,命名只能是字母、数字、下划线以及$
,也可以是某种语言任何代表字母的字符,变量名的开头不能是数字。$
不要在你的代码中使用这个字符,它只用在 java 编译器或者其他工具生成的名字中。
变量对大小写敏感。
变量初始化
- 变量声明后,想要调用必须初始化。不能使用未初始化的变量,编译器会报错未初始化。
- 变量的声明尽可能地靠近第一次使用的地方,这是一种良好的编程习惯。
int vacationDays;
vacationDays = 12;
int vacationDays = 12;
常量
利用关键字final
指示常量。
final bouble CM_PER_INCH = 2.54;
- 只能被赋值一次
- 赋值后不能更改
- 常量名尽量用大写
经常希望一个常量在类中的多个方法使用,这些常量称为类常量,可以使用关键字static final
设置。
static 和 final 结合使得这个常量只有一个,不会跟随新的实例生成,可以通过类名直接访问。
类常量的定义位于main方法的外部。因此,在同一个类的其他方法中也可以使用这个常量。而且,如果一个常量被声明为public,那么其他类的方法也可以使用这个常量。
public static final double CM_PER_INCH = 2.45;
public static void main(String[] args)
Class.CM_PER_INCH //其他类调用
运算符
特性:
- 两个整数相除表示整数除法,否则是浮点除法
- 比较运算符,一旦左边能确定结果,右边就不执行,也就是短路
- 位运算符,不存在短路,两边都要执行完
常用运算符
自增运算符 ++
、自减运算符 --
:改变变量的值,操作数不能是数值,不要再其他运算符内使用这个,防止出现 bug
关系运算符a==b
a!=b
a<b
a>b
a<=b
a>=b
!
a&&b
a||b
a?b:c
&& 与 || 按照短路方式求值,第一个数确定了结果就不会指定第二个数。
位运算符
&
:按位与。只有两个操作数对应位同为1时,结果为1,其余全为0
|
:按位或。只有两个操作数对应位同为0时,结果为0,其余全为1
~
:按位非。一元操作符,每位取反
^
:按位异或。两边的结果不同(true 和 false),结果为真
<<
:左位移运算符。符号位不变,低位补0
>>
:右位移运算符。符号位不变,并用符号位补溢出的高位
>>>
:无符号右移运算符。符号位不变,并用0补溢出的高位
适用于 整形 与 布尔型
整形会转为二进制进行位运算
布尔型直接进行位运算
左位移相当于乘以2^n,n为位移的个数
右位移相当于除以2^n,n为位移的个数
数学函数
Math.sqrt(4);//平方根
Math.abs(-4);//绝对值
Math.pow(2,3);//幂运算
Math.PI;//帕尔常量近似值
Math.E;//e常量近似值
Math.ceil(12.5);//向上取整
Math.floor(12.5);//向下取整
Math.round(12.5);//四舍五入
Math.random();//0 <= x < 1 的double类型的数
如果在源文件顶部加上一下代码,就可以省略前缀Math.
import static java.lang.Math.*;
a = sqrt(PI);
数值类型之间的转换
转换,存在无信息丢失的转换以及有精度损失的转换。
无信息丢失的转换:
- byte -> short 1位到2位
- short -> int 2位到4位
- char -> int 2位到2位
- int -> long 4位到8位
- int -> double 4位到尾数52bit(6.5位)
损失进度的转换:
- int -> float 如果int的取值超过了float的尾数23位,就会损失精度,如123456789会准换为1.23456792E8
- long -> float 如果long的取值超过了float的尾数23位,就会损失精度,如123456789会准换为1.23456792E8
- long -> double 如果long的取值超过了float的尾数52位,就会损失精度,如123456789123456789L会转换为1.23456789123456784E17
在进行二元操作时:
- 如果两个操作符有一个是double类型,另一个转换为double类型
- 否则,如果其中有一个是float类型,另一个转换为float类型
- 否则,如果其中有一个是long类型,另一个转换为long类型
- 否则,两个操作符都转换为int类型
精度低的自动提升为精度高的。
注意,如果 int 类型的数据在 byte 内,可以赋值给 byte。
//byte 范围 -128~127
byte b1 = 3;//正确,在范围内
byte b2 = 300;//错误,超过范围
byte b3 = 3 + 7;//正确,在范围内
byte b4 = 3;
byte b5 = 7;
byte b6 = b4 + b5;//错误,变量无法确定范围
final byte b7 = 3;
final byte b8 = 7;
byte b9 = b7 + b8;//正确,常量可以确定范围
赋值语句会自动转换
short s1 = 10;
s1 = s1 + 10;//错误,10 为 int 类型
s1 += 10;//正确,赋值语句会自动强制类型转换
s1 += 10.9;//正确,赋值语句会自动强制类型转换
强制类型转换
在不能自动转换类型的情况下,可以使用强制类型转换。
当然,会存在丢失一些信息的可能。
double x = 9.997;
int nx = (int) x;
//结果 nx = 9;
double类型数据截断小数部分转换为int类型数据,如果想要四舍五入可以使用Math.round(x)
数学函数。
如果试图将一个类型强制类型转换为另一个类型,而又超出了目标类型的表示范围,结果就会截断成一个完全不同的值。
运算符级别
运算符 | 结合性 |
---|---|
[].()(方法调用) | 从左向右 |
! ~ ++ -- +(一元运算正号)- (一元运算负号)()(强制类型转换)new | 从右向左 |
*/% | 从左向右 |
+- | 从左向右 |
<< >> >> | 从左向右 |
< <= > >= instanceof | 从左向右 |
== != | 从左向右 |
& | 从左向右 |
^ | 从左向右 |
| | 从左向右 |
&& | 从左向右 |
|| | 从左向右 |
?= | 从右向左 |
= += -= *= /= %= &= |= ^= <<= >>= >>>= | 从右向左 |
枚举类型
变量取值在一个有限的集合内,如服装的尺寸。
enum Size{SMALL, MEDIUM, LARGE, EXTRA_LARGE};
声明这种类型的变量:
Size s = Size.MEDIUM;
所以,他是一个类,详细介绍在第5章。
字符串
java字符就是Unicode字符,java没有内置的字符串类型,java类库中提供了一个预定一类String
,每个用双引号括起来的字符串都是String类的一个实例。
String a = "b";
子串
复制较大字符串中的一段字符串,参数一为起始位置,参数二为结束位置。
String greeting = "Hello";
String s = greeting.substring(0,3);
//结果 s = "Hel";
拼接
java语言允许使用+
来连接字符串。
String a = "a";
String b = "b";
String ab = a + b;//"ab"
String c = a + "c";//"ac"
字符串和非字符串连接时,非字符串会转换为字符串
String a = "a";
String b = 1 + "a";//"1a"
不可变字符串
String类没有提供修改字符的方法,只能重复赋值。
String greeting = "hello";
greeting = greeting.substring(0,3) + "p!";
//结果 greeting = "help!";
java中的字符串存放在公共的储存池中,如果复制一个字符串,原始字符串与赋值字符串共享相同的字符。
很少需要修改字符串,而是往往需要对字符串进行比较。
检测字符串是否相等
对于字符串s.equals(t)
,如果字符串s与t相等返回true,否则返回false,s与t可以是字符串变量也可以是字符串常量。
"Hello".equals(t);
"Hello".equalsIgnoreCase("hello");//不区分大小写
不能使用==
判断字符串是否相等,它的作用是确定两个字符串是否放置在同一个位置上,基本没有这个场景。
如果虚拟机将相同的字符串共享,就可以使用==
运算符检测是否相等。实际上只有字符串常量是共享的,而+
和substring
等操作产生的结果并不是共享的。
String greeting = "Hello";
System.out.println(greeting == "Hello");//true
System.out.println(greeting.substring(0, 3) == "hel");//false
空串与Null串
空串""是长度为0的字符串,以下用来检察字符串是否为空。
if(str.length() == 0)
if(str.equals(""))
检察一个字符串是否为null
if(str == null)
既不是null也不是空串
if(str != null && str.length() != 0)
代码点与代码单元
java 字符串由 char 序列组成,char 类型数据是一个采用 UTF-16 编码表示 Unicode 代码点的代码单元。
常用 Unicode 字符一个代码单元即可表示,而辅助字符需要一对代码单元表示。
获取代码单元数量:
String greeting = "中国";
int n = greeting.length();
获取代码点数量:
int cpCount = greeting.codePointCount(0, greeting.length());
获取某个位置上的代码单元:
char first = greeting.charAt(0);
获取某个位置上的代码点:
int i = 0;
int index = greeting.offsetByCodePoints(0,i);
int cp = greeting.codePointAt(index);
遍历字符串查看每一个代码点:
int i = 0;
while (i<greeting.length()) {
int index = greeting.offsetByCodePoints(0, i);
int cp = greeting.codePointAt(index);
if (Character.isSupplementaryCodePoint(cp)) {
i += 2;
System.out.println(cp);
} else {
i++;
System.out.println(cp);
}
}
字符串 API
String greeting = "Hello World!";
char a = greeting.charAt(0);//返回给定位置的代码单元
int b = greeting.codePointAt(2);//返回给定位置开始或结束的代码点
int c = greeting.offsetByCodePoints(0,5)//从0开始,位移5后的代码点索引
int d = greeting.compareTo("g");//按照字典排序,位于g前返回负数,后返回整数,否则返回0
boolean e = greeting.endsWith("ld!");//以"ld!"结尾,返回true
boolean f = greeting.equals("H");//与"H"相等返回true
boolean g = greeting.equalsIgnoreCase("H");//与"H"相等(忽略大小写)返回true
int h1 = greeting.indexOf("Hel");
int h2 = greeting.indexOf("Hel",5);
int h3 = greeting.indexOf(1);
int h4 = greeting.indexOf(1,5);//返回 与字符串"H"或代码点1匹配 的第一个字串的开始位置。这个位置从索引0或5开始计算。如果不存在"H"返回-1
int i1 = greeting.lastIndexOf("!");
int i2 = greeting.lastIndexOf("!",4);
int i3 = greeting.lastIndexOf(3);
int i4 = greeting.lastIndexOf(3,8);//返回 与字符串"H"或代码点3匹配 的最后一个字串的开始位置。这个位置从原始串尾端或8开始计算。如果不存在"H"返回-1
int j = greeting.length();//返回字符串长度
int k = greeting.codePointCount(2,5);//返回2-4的代码点数量
String l = greeting.replace("new","old");//用new替换源字符串所有的old
boolean m = greeting.startsWith("H");//以H开始返回true
String n1 = greeting.substring(2);
String n2 = greeting.substring(2,5);//返回一个新的字符串,从2到末尾或5
String o = greeting.toLowerCase();//大写改小写
String p = greeting.toUpperCase();//小写改大写
String q = greeting.trim();//去掉前后空格
String r = String.join("+", "a", "b", "c"));//a+b+c,使用指定定界符连接所有元素
构建字符串
当用到大量短字符串,可以使用StringBuilder
实现追加和插入,避免老是构建新的String对象,耗时又浪费空间,操作完成后调用.toString()
方法变为String对象。
StringBuilder builder = new StringBuilder();
builder.append(ch);
builder.append(str);//追加
builder.setCharAt(2,'s');//第2个单元设置为s
builder.insert(2,'g'/"ggg");//在第2个单元插入
builder.delete(2,3);//删除2-2,返回this
String com = builder.toString();//转换为String类型
输入输出
利用控制台的输入输出,熟悉java程序设计语言。
读取输入
首先构造一个Scanner对象,与标准输入流System.in关联。
然后通过方法接受用户的输入。
Scanner 类在包 java.util 中
import java.util.*;
//java.util.Scanner 5.0
Scanner in = new Scanner(System.in);
String name = in.nextLine();//读取一行
String name2 = in.next();//读取一个单词(空格分隔)
String name3 = in.nextInt();//读取一个整数
String name4 = in.nextDouble();//读取一个浮点数
boolean page1 = in.hasNext();//检测输入中是否有其他单词
boolean page2 = in.hasNextInt();//检测下一个字符序列是否是整数
boolean page3 = in.hasNextDouble();//检测下一个字符序列是否是浮点数
//java.lang.System 1.0
Console console = System.console();
//返回与当前 Java 虚拟机关联的唯一 Console 对象(如果有),否则返回null。以Javaw所执行的应用程式(eclipse)没有主控制台(console),所以取不到console物件,System.console()只能是null了。
//java.io.Console 6
Console console = System.console();
console.readPassword();//从控制台读取密码,禁用回显。
console.readLine();//从控制台读取单行文本。
格式化输出
java的格式化输出延续了C。
System.out.print(x);//输出x不换行
System.out.println(x);//输出x换行
System.out.printf("%8.2f\n",x);//格式化输出,不换行,可以用转义字符
格式化输出printf
有转换符指示被每一个以%
字符开始的格式化说明。
还可以给控制格式化输出各种标志。如0
-
(
,
。
String.format("%s",a)
可以用格式化创建字符串。
还提供了日期和时间的格式化选择。
格式化的字符串可以指出要被格式化的参数索引。索引必须在%
后面,并以$
终止。
System.out.println("%1$s %2$tB %2$te, %2$tY", "Due date:", new Date())
结果
Due date: May 11, 2018
索引值从1开始,1代表第一个参数,2代表第二个参数。
文件输入输出
文件的读取
Scanner in = new Scanner(Paths.get("myfile.txt");
Scanner in = new Scanner(Paths.get("c:\\mydir\\myfile.txt");//含反斜杠需要额外加反斜杠
文件的写入
PrintWriter out = new PrintWriter("myfile.txt");
//若文件不存在自动创建,位置为Java虚拟机启动路径的相对位置。
//如果使用的是集成开发环境,启动路径由IDE控制
//位置为 String dir = ystem.getPropery("user.dir");
文件的路径
- 相对路径:启动路径就是命令解释器的当前路径,列如,"mydirectory/myfile.txt"
- 绝对路径:
c:/mydirectory/myfile.txt
如果用一个不存在的文件构建 Scanner 或用一个不能被创建的文件名构造一个 PrintWriter 抛出异常。
如果知道这个异常可以在 main 方法后面用 throws 字句:
public static void main(String[] args) throws FileNotFoundException{
Scanner in = new Scanner(Paths.get("myfile.txt"));
}
控制流程
java用条件语句和循环结构确定控制流程。
块作用域
块(即复合语句)是指由一对花括号括起来的若干条简单的java语句。
块确定了变量的作用域
块可以嵌套在另一个块中
不能在嵌套的两个块中声明同名的变量,会导致无法通过编译。这与 C++ 和 js 不同
java public static void main(String[] args){ int n; { int k; int n;// ERROR -- can`t redefine n in inner block } }
条件语句
if (c != 3) {
a = 1;
} else {
b = 2;
}
循环语句
while (a < b) {
a += b;
}
//至少执行一次循环
do {
a += b;
} while (a < b);
确定循环
for 循环是 while 的简化形式
for (int i = 0; i < args.length; i++) {
;
}
i
在for
循环内定义,在外部就无法访问i
的最终结果,需要访问最终结果需要在外部定义。
如果循环不嵌套,i
就可以无限使用:
for (int i = 0; i < args.length; i++) {
;
}
for (int i = 0; i < args.length; i++) {
;
}
警告:再循环中,检测两个浮点数是否相等需要格外小心。
for(double x = 0;x != 10;x += 0.1) ...
由于0.1
无法用精确的二进制表示,由于舍入的误差,最红会导致 x 不会等于 10 导致循环一直进行下去。
多重选择
switch (key) {
case value:
//语句
break;
default:
break;
}
value 可以为:
- char byte short 或 int
- 枚举常量
- 字符串字面量 java se 7
执行流程:
- 从选项值相匹配处开始执行知道遇到 break 语句或者执行到语句结束处。
- 没有相匹配的 case 就执行 default
中断控制流程语句
break:退出当前循环语句
continue:中断每次循环进入下次循环
他们是可选的可以不使用。
中断标号
可以给语句块加标号赋予它们名称,标号位于语句之前。标号只能被continue和break引用。
label:statement
语句前只允许加一个标号,标号后面不能跟大括号。通过用break后加标号对处于标号中的语句进行控制。往往标号后是for.while.do-while等循环。
通过用标号,我们可以对外层循环进行控制
flag1:for (int i = 0;i < 3;i++){
flag2:for (int j = 0;j <4;j++){
System.out.println("x=" + j);
continue flag1;
}
}
x=0
x=0
x=0
大数值
BigInteger 和 BigDecimal 可以处理包含任意长度数字序列的数值,实现了任意精度的整数运算和任意精度的浮点数运算。
注意:大数值不能使用运算符,要使用自带的方法进行算术运算。
BigInteger a = BigInteger.valueOf(1000);//将普通数值转为大数值
BigInteger c = a.add(b);//和
BigInteger c = a.subtract(b);//差
BigInteger c = a.multiply(b);//积
BigInteger c = a.divide(b);//商
BigInteger c = a.mod(b);//余数
int c = a.compareTo(b);//对比,相等返回0,小于b返回负数,大于b返回正数
数组
java中的数组是一组数据结构,用来存储同一类型的集合。通过一个整形下标可以访问数组中的每一个值。
声明及初始化:
int[] a;
int[] a = new int[100];//创建了一个长度为100的数组
初始化后的数组值为 null 。
获取某个数组的长度使用 length :
int len = a.length;
调用 toString() 方法可以按格式输出所有元素。
Arrays.toString(smallPrimes)
数组一旦创建就不能改变它的大小,如果要在运行中扩展数据,使用另一种数据结构——数组列表(Arraylist)
for each 循环
格式:
for (variable : collection ){
statement;
}
int arr[] = {2, 3, 1};
for (int x : arr) {
System.out.println(x); //逐个输出数组元素的值
}
不必为集合的初始值和终止值操心。
数组初始化以及匿名数组
初始化简化方式:
int[] smallPrimes = { 2, 3, 4, 5 };
new int[] { 2, 3, 4, 5 };//匿名数组
smallPrimes = new int[] { 2, 3, 4, 5 };//重新初始化
简化初始化数组可以省略 new ,数组重新初始化要用匿名数组。
数据长度为 0 的方法:
int[] a1 = new int[0];
int[] a2 = {};
System.out.println(a1.length);
System.out.println(a2.length);
数组拷贝 copyOf
讲一个数组变量拷贝到另一个数组变量,这时,两个变量将引用同一个数组。
其中一个改变值时,另一个也改变对应的值。
int[] a = {1,2,3};
int[] b = a;
b[0] = 4;//此时a[0]也变为4
提供一个方法让数组拷贝后不会一起改变,可以拷贝数组并且变大或变小,变大时,整数类型变为0,布尔类型变为false,变小时,只拷贝最前面的数据元素。
int[] smallPrimes = {1,2,3,4,5};
smallPrimes = Arrays.copyOf(smallPrimes, 8);
System.out.println(Arrays.toString(smallPrimes));
// [1, 2, 3, 4, 5, 0, 0, 0]
命令行参数
每一个 java 应用程序都有一个带有 String arg[] 参数的 main 方法。
public static void main(String[] args)...
我们通过命令行参数执行 java 代码时,main 方法将接收一个字符串数组,也就是命令行参数。
如以这种形式运行程序:
java Message -g cruel world
此时,args 数组将包含下列内容:
arg[0]: "-g"
arg[1]: "cruel"
arg[2]: "world"
注意,args 数组不包含程序名。
数组排序和常用 API
可以使用自己的算法,或者自带优化的快速排序算法。
Arrays.sort(smallPrimes);
常用的数组 API:
//java.util.Arrays 1.2
int[] a = {1, 2, 3, 4, 5};
String name1 = Arrays.toString(a);
System.out.println(name1);//[1, 2, 3, 4, 5]
int[] name2 = Arrays.copyOf(a, 4);
System.out.println(Arrays.toString(name2));//[1, 2, 3, 4],拷贝函数
Arrays.sort(a);//无返回值,采用优化的快速排序算法对数组进行排序
int name3 = Arrays.binarySearch(a,3);//返回查找到的下标
System.out.println(name3);//2
int name4 = Arrays.binarySearch(a, 1, 3, 2);//查找起点、末尾、查找的值
System.out.println(name4);//1
Arrays.fill(a, 1);//所有值设置为1
System.out.println(Arrays.toString(a));//[1, 1, 1, 1, 1]
多维数组
跟一维数组差不多:
int[][] smallPrimes = new int[4][4];
int[][] smallPrimes = {
{1, 2, 3, 4},
{1, 2, 3, 4},
{1, 2, 3, 4},
{1, 2, 3, 4}
};
快速打印二维数组的方法:
String a = Arrays.deepToString(smallPrimes);
System.out.println(a);
//[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]]
不规则数组
Java 实际上没有多维数组只有一维数组。多维数组解释为数组的数组。
数组可以单独地存取数组的某一行,可以让两行交换,当然他们的类型要一样。
double[] temp = balances[i];
balances[i] = balances[i+1];
balances[i+1] = temp;
不规则数组,即第二维的数组长度不一样。
首先分配数组:
int[][] odds = new int[NMAX + 1][];//先确定一维
for(int n = 0;n <= NMAX;n++){
odds[n] = new int[n+1]//在确定二维
}
只能单独的创建不规则的数组。
总结:
- 多维数组的类型要一致
- 多维数组的长度可以不同
- 多维数组的行可以交换
- 只能单独创建不规则的数组