类型系统
高级语言都有自己的类型系统。
类型系统可以划分为:强类型,弱类型
或者:静态类型,动态类型。
通俗地说,强类型就是语言比较在意不同类型的区别,会对某个类型所能作的动作进行严格审查,
而弱类型就睁一眼闭一眼,想做什么就做什么,比如c是弱类型,你本来定义了一个int,待会儿可以拿它当double来用,虽然c语言也会抱怨一下,但绝不阻止你。
(她的意思是,你一定要玩火,那就好自为之吧,我管不了)
如果是在编译期间进行这样的检查,就是静态类型。运行时才检查就是动态类型。
java是强类型的,静态类型的。
也就是说,java中很受限制,你定义了某个类型,那就一定按该类型的约束来行事,不能开后门。
如果你想仿照c那样,来点小小的变换技巧什么的,都不用等到运行,java的编译器直接把你毙掉。因为她是静态类型的。
类型系统提供了安全保障,无视类型也能带来灵活性,这取决于你的需求。
比方说,洗衣机能放入什么?当然是衣服了!
被单行不行? 可以。
袜子行不行?可以。
苹果行不行?不行!
如果是c语言,对话是这样的:
你:把苹果放入洗衣机!
c:恐怕不好吧。
你:少废话,让你放就放。
c:那就放了,出什么事你自己负责,别说我不提醒你。
如果是java,对话很简单:
你:把苹果放入洗衣机!
java:办不到!
但编译器必定是傻乎乎的,你可以哄骗它。
你:把苹果放入洗衣机!
java:办不到!
你:把苹果当“物品”看,记为x
java: 没问题
你:现在x是物品了,把它放入洗衣机。
java: 这不行啊,只有“被服”类型的才能放入。
你:那先把 x 转为需要的类型,再放入。
java: 这不好吧,你肯定x是"被服"吗?
你:我肯定,放吧
java: 好吧。
看到了?我们可以说服编译器做一些危险的事。但最好别这样。
编译器是来帮我们防止逻辑错误的,你这样狠心地骗它太残忍了。。。。
所以说,类型还是有用的。
java 的类型系统可以分为两个大类:基本类型和复合类型。
基本类型又叫:简单类型或原生类型。包含:整数,浮点数,字符,布尔型四大类。
而复合类型也叫复杂类型,涉及:类,接口,数组。
我们先从最简单的类型说起。
整型
为了适应不同的场合,java中的整型有4种: byte short int long
它们表示的数的大小范围不同。如果没有特殊的用途,用 int 就好了。这是 4 个字节的类型,可以表示大约20亿左右的有符号的整数。
我们可以在整型上进行 + - * / 和取模(%)运算
例一:
已知一个数字的百位,十位,个位,求这个数字。
public class A
{
public static void main(String[] args){
int a = 5;
int b = 6;
int c = 9;
int n = a * 100 + b * 10 + c;
System.out.println(n);
}
}
这会输出: 569
例二:
已知一个整数,分别输出它的百位,十位,个位。
1 public class A1
2 {
3 public static void main(String[] args){
4 int n = 2736;
5 int a = n % 1000 / 100;
6 int b = n % 100 / 10;
7 int c = n % 10;
8
9 System.out.println(a);
10 System.out.println(b);
11 System.out.println(c);
12 }
13 }
这会输出:7,3 和 6
println 输出一项后会回车换行。如果不想换行,可以用 print 代替。
也可以先把若干信息拼成一个大串,再输出这个串。
String s = a + "," + b + "," + c;
System.out.println(s);
通过这样的代码来取出各个位,并不会去影响 n 的数值。
如果用一下面的方法,会影响 n 的值。
1 public class A2
2 {
3 public static void main(String[] args){
4 int n = 2736;
5 int c = n % 10; n = n / 10;
6 int b = n % 10; n /= 10;
7 int a = n % 10;
8 System.out.println(a);
9 System.out.println(b);
10 System.out.println(c);
11 }
12 }
一般情况下,我们都是每一行代码一个分号。然而也可以在一行放多条语句。
n % 10 会取出n的最末位来。
n / 10 会求出 n 除以 10 的商来,它的结果还是整数,不会得出类似273.6 的结果来。
n /= 10 与 n = n / 10 的含义是一样的,不过这样写更酷一些。
很多运算符都可以有对应的反身运算,表示对一个变量本身做的某个动作。
n = n 运算 a, 一般都能写成:
n 运算= a;
n *= 100;
就是把 n 乘以 100,再放回到 n 中。
注意,这里的 *= 是一个整体,是一个运算符,中间不能有空格。
浮点数
浮点类型常用的是 double(8位64bit), 必要的时候当然也可以用float(4位32bit)
浮点数与其它类型的数字的运算结果还是浮点数。
所以可能自动把一个整数数转为浮点数。
double a = 10; //正确
System.out.println(10/3); //结果是整数
System.out.println(10/3.0); //结果是浮点数
浮点数可以被强制转为整数,这样会丢失它的小数部分。
以下代码分别取得一个浮点数的整数部分和小数部分。
1 public class A3
2 {
3 public static void main(String[] args){
4 double x = 5.81236;
5 int a = (int)x;
6 double b = x - a;
7 System.out.println(a);
8 System.out.println(b);
9 }
10 }
浮点数因为是用有限的空间来存储的,所以存在一个表示精度的问题。
在任何时候都要避免对两个浮点数进行相等性比较。
不信的话,请执行如下代码:
System.out.println(0.1+0.2 == 0.3);
这会输出 false。
但可以这样表达:
System.out.println(Math.abs(0.1+0.2-0.3) < 1e-6);
这意思是说:01+0.2的值与0.3相差不足10的负6次方。就是几乎相等的意思。
这里的1e-6 是科学记数表示法。
java提供了许多数学函数,比如:sin, log 等方便我们运算,都在Math对象中,是静态方法。
System.out.println(Math.sin(30 * Math.PI/180)); //单位度需要转为弧度。
浮点数的输出一般要求保留到小数后的多少位。
有很多方法,比如,可以通过String 类的format方法,类似于c语言的 printf
这里的需求其实是2种:
一是不希望改变那个浮点数的真实值,只是在显示的时候,不要显示那么啰嗦冗长的小数,希望简短点而已。
这代表了绝大多数的需求,正是用String.format的时机。
1 public class A4
2 {
3 public static void main(String[] args){
4 double a = Math.sin(0.2);
5 System.out.println(a);
6 System.out.println(String.format("%.3f",a));
7 }
8 }
结果为:
二是需要真实地修改那个浮点数,丢弃多余的小数部分。比如,银行存折上的余额计入利息后,只能四合五入到分,不能有更多的小数。
这时,我们只需要一个小技巧,你自己研究一下吧。
double a = Math.sin(0.15);
System.out.println(a);
double b = (int)(a * 100 + 0.5)/100.0;
System.out.println(b);
字符类型
java中的字符类型存储的是它的unicode码。每个字符用了2个字节,这点与c语言是不同的。
既然是unicode码,那它与整数就有天然的关系。
字符类型可以直接赋值给整数,整数也可以赋值给字符类型(不过需要一个强制转换)
char x = 'A';
System.out.println(x);
int n = x;
System.out.println(n);
System.out.println((char)97);
字符如果是一个十进制的数码,可以把它转换为对应的真值,这个技巧十分常用。
char x = '5';
int k = x - '0';
System.out.println(k);
这是因为十进制的数码字符,它们的unicode码是连续的,并且是有规律的。
布尔型
布尔型表示是否的概念,只有两个值:true和false
布尔值一般作为循环和分支的判断条件。
有些运算会产生出布尔值。
boolean x = 5 >= 3;
boolean y = 1+2==3;
boolean z = "abcd".equals("ABCD");
布尔值本身也会通过逻辑运算符产生出新的布尔值。
&& 逻辑与
|| 逻辑或
! 逻辑非
逻辑运算符具有一个十分重要的特性---短路求值。
if(x>0 && f(x)==5) ....
这句话在 x<=0 的情况下, f(x) 是不会去执行的。
因为,x>0 是 false, 而 false && 任何东西都是false,那么表达式的值就一定是false,也就没有必要再计算下去了。
字符串
字符串并非是基本类型,它属于java的面向对象体系。
String 是类名,new String() 创建出字符串对象。
"abc" 是字符串常量。
String s = "abc";
这里的 s 并不是字符串本身,它是指向字符串的引用,相当于c语言中的指针。
所以,如果:
String s2 = s;
这时,并非有两个字符串,而是有两个引用指向了同一个字符串。
字符串是不可变的对象,也就是说,字符串一旦形成,它的内容不可更改!
但,我们可以生生成新的字符串,并把旧的字符串丢弃。
例如:
String s = "abc";
s = s + "de";
System.out.println(s);
这里,s开始的时候指向了字符串对象,内容是"abc",后来,把 "abc" 与 "de"进行了拼接操作,生成了新的字符串对象 "abcde"。
然后,s 引用放弃了先前的对象,指向了新生成的对象,内容是:"abcde"
字符串类型与字符类型的关系当然最为密切。
因为字符串正是由字符构成的。
字符串类中提供了丰富的方法,可以对串进行各种常见的操作。
String s = "123456";
int n = s.length();
char x = s.charAt(n-1);
如此操作,则 n 中存储了字符串的长度,
x 中存储了最后一个字符,即:'6'
String s1 = s.substring(0,3);
String s2 = s.substring(3);
s1 指向了新串,它是 s 的一个子串,内容: "123"
substring方法的两个参数是:开始位置的下标,结束位置的下标。
这里要注意,规则是:包含开始,不包含结束。
我们也可以判断一个串是否包含某个子串:
int k = s.indexOf("34");
这会返回子串 "34" 在母串中的第一次出现的位置。
如果找不到,就会返回-1。
字符串可能是我们最常用的复合类型了,它还有一个很有趣的特性:
任何类型都可以和一个串做加法,其动作就是和那个串拼接起来。
String s = "" + 1 + 2 + (5>3) + 'A';
System.out.println(s);