变量和数据类型
Java提供了两种变量类型:基本类型和引用类型。基本类型包括整型,浮点型,布尔型,字符型。变量可重新赋值,等号是赋值语句,不是数学意义的等号。常量在初始化后不可重新赋值,使用常量便于理解程序意图。
基本数据类型
- 整数类型:byte,short,int,long
- 浮点数类型:float,double
- 字符类型:char
- 布尔类型:boolean
数据类型 | 大小(二进制位数) | 范围 | 默认值 | 包装类 |
byte(字节) | 8 | -128 - 127 | 0 | Integer |
shot(短整型) | 16 | -32768 - 32768 | 0 | Short |
int(整型) | 32 | -2147483648-2147483648 | 0 | Long |
long(长整型) | 64 | -9233372036854477808-9233372036854477808 | 0 | Byte |
float(浮点型) | 32 | -3.40292347E+38-3.40292347E+38 | 0.0f | Float |
double(双精度) | 64 | -1.79769313486231570E+308-1.79769313486231570E+308 | 0.0d | Double |
char(字符型) | 16 | \u0000 - u\ffff | \u0000 | Character |
boolean(布尔型) | 1 | true/false | false | Boolean |
Java有一个能够表示任意精度的算书包,通常称为大数值(big number)。虽然被称为大数值,但它并不是一种Java类型,而是一个Java对象。 如果基本的整数和浮点数精度不能够满足需求,那么可以使用java.math包中的两个很有用的类:BigInteger,BigDecimal(Android SDK中也包含了java.math包以及这两个类)这两个类可以处理包含任意长度数字序列的数值。BigInteger类实现了任意精度的整数运算,BigDecimal实现了任意精度的浮点数运算
常量
定义变量的时候,如果加上final修饰符,这个变量就变成了常量。常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误。 1
final double PI = 3.14
var关键字
有些时候,类型的名字太长,写起来比较麻烦。这个时候,如果想省略变量类型,可以使用var关键字,编译器会根据赋值语句自动推断出变量sb的类型是StringBuilder。 1
2StringBuilder sb = new StringBuilder();
var sb = new StringBuilder();
引用类型
数组类型 定义一个数组类型的变量,使用数组类型类型[],例如,int[]。和单个基本类型变量不同,数组变量初始化必须使用new int[5]表示创建一个可容纳5个int元素的数组。
- 数组所有元素初始化为默认值,整型都是0,浮点型是0.0,布尔型是false;
- 数组一旦创建后,大小就不可改变。
1 | int[] ns = new int[5]; |
字符串数组
如果数组元素不是基本类型,而是一个引用类型,那么,修改数组元素会有哪些不同? 1
2
3String[] names = {
"ABC", "XYZ", "zoo"
};
变量的作用范围
在Java中,多行语句用{ }括起来。很多控制语句,例如条件判断和循环,都以{ }作为它们自身的范围。而在语句块中定义的变量,它有一个作用域,就是从定义处开始,到语句块结束。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34if (...) { // if开始
...
while (...) { while 开始
...
if (...) { // if开始
...
} // if结束
...
} // while结束
...
} // if结束
{
...
int i = 0; // 变量i从这里开始定义
...
{
...
int x = 1; // 变量x从这里开始定义
...
{
...
String s = "hello"; // 变量s从这里开始定义
...
} // 变量s作用域到此结束
...
// 注意,这是一个新的变量s,它和上面的变量同名,
// 但是因为作用域不同,它们是两个不同的变量:
String s = "hi";
...
} // 变量x和s作用域到此结束
...
} // 变量i作用域到此结束
修饰符
Java语言提供了很多修饰符,主要分为以下两类:
- 访问修饰符
- 非访问修饰符
访问修饰符
修饰符 | 当前类 | 同一包内 | 子孙类(同一包) | 子孙类(不同包) | 其他包 |
---|---|---|---|---|---|
public | Y | Y | Y | Y | Y |
protected | Y | Y | Y | Y/N(说明) | N |
default | Y | Y | Y | N | N |
private | Y | N | N | N | N |
非访问修饰符
- static 修饰符,用来修饰类方法和类变量。
静态变量: static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。对类变量和方法的访问可以直接使用 classname.variablename 和 classname.methodname 的方式访问。
静态方法: static 关键字用来声明独立于对象的静态方法。静态方法不能使用类的非静态变量。静态方法从参数列表得到数据,然后计算这些数据。
1 | public class InstanceCounter { |
- final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
- abstract 修饰符,用来创建抽象类和抽象方法。
- synchronized 和 volatile 修饰符,主要用于线程的编程。
运算
算术运算符
操作符 | 描述 | A=10,B=20 |
---|---|---|
+ | 加法: 相加运算符两侧的值 | A + B 等于 30 |
- | 减法: 左操作数减去右操作数 | A – B 等于 -10 |
* | 乘法: 相乘操作符两侧的值 | A * B等于200 |
/ | 除法: 左操作数除以右操作数 | B / A等于2 |
% | 取余: 左操作数除以右操作数的余数 | B%A等于0 |
++ | 自增: 操作数的值增加1 | B++ 或 ++B 等于 21 |
-- | 自减: 操作数的值减少1 | B-- 或 --B 等于 19 |
关系运算符
运算符 | 描述 | 例子 |
---|---|---|
== | 检查如果两个操作数的值是否相等,如果相等则条件为真。 | (A == B)为假。 |
!= | 检查如果两个操作数的值是否相等,如果值不相等则条件为真。 | (A != B) 为真。 |
> | 检查左操作数的值是否大于右操作数的值,如果是那么条件为真。 | (A> B)为假。 |
< | 检查左操作数的值是否小于右操作数的值,如果是那么条件为真。 | (A <B)为真。 |
>= | 检查左操作数的值是否大于或等于右操作数的值,如果是那么条件为真。 | (A> = B)为假。 |
<= | 检查左操作数的值是否小于或等于右操作数的值,如果是那么条件为真。 | (A <= B)为真。 |
位运算符
运算符 | 描述 | 例子 |
---|---|---|
& | 如果相对应位都是1,则结果为1,否则为0 | (A&B),得到12,即0000 1100 |
| | 如果相对应位都是0,则结果为0,否则为1 | (A | B)得到61,即 0011 1101 |
^ | 如果相对应位值相同,则结果为0,否则为1 | (A ^ B)得到49,即 0011 0001 |
〜 | 按位取反运算符翻转操作数的每一位,即0变成1,1变成0。 | (〜A)得到-61,即1100 0011 |
<< | 按位左移运算符。左操作数按位左移右操作数指定的位数。 | A << 2得到240,即 1111 0000 |
>> | 按位右移运算符。左操作数按位右移右操作数指定的位数。 | A >> 2得到15即 1111 |
>>> | 按位右移补零操作符。左操作数的值按右操作数指定的位数右移,移动得到的空位以零填充。 | A>>>2得到15即0000 1111 |
短路逻辑运算符
当使用与逻辑运算符时,在两个操作数都为true时,结果才为true,但是当得到第一个操作为false时,其结果就必定是false,这时候就不会再判断第二个操作了。
1 | public class LuoJi{ |
逻辑运算符
运算符 | 描述 | 例子 |
---|---|---|
&& | 称为逻辑与运算符。当且仅当两个操作数都为真,条件才为真。 | (A && B)为假。 |
|| | 称为逻辑或操作符。如果任何两个操作数任何一个为真,条件为真。 | (A || B)为真。 |
! | 称为逻辑非运算符。用来反转操作数的逻辑状态。如果条件为true,则逻辑非运算符将得到false。 | !(A && B)为真。 |
条件运算符(?:)
1 | var x = (expression) ? value if true : value if false |
instanceof 运算符
如果运算符左侧变量所指的对象,是操作符右侧类或接口(class/interface)的一个对象,那么结果为真。 1
2
3
4( Object reference variable ) instanceof (class/interface type)
String name = "James";
boolean result = name instanceof String; // 由于 name 是 String 类型,所以返回真
类型自动提升与强制转型
- 在运算过程中,如果参与运算的两个数类型不一致,那么计算结果为较大类型的整型。例如,short和int计算,结果总是int,原因是short首先自动被转型为int
- 如果参与运算的两个数其中一个是整型,那么整型可以自动提升到浮点型。
1
2int n = 5;
double d = 1.2 + 24.0 / n; // 6.0 - 可以将浮点数强制转型为整数。在转型时,浮点数的小数部分会被丢掉。如果转型后超过了整型能表示的最大范围,将返回整型的最大值。
1
int n4 = (int) 1.2e20; // 2147483647
字符和字符串
字符类型
char是基本数据类型,它是character的缩写。一个char保存一个Unicode字符。因为Java在内存中总是使用Unicode表示字符,所以,一个英文字符和一个中文字符都用一个char类型表示,它们都占用两个字节。 1
2char c1 = 'A';
char c2 = '中';
要显示一个字符的Unicode编码,只需将char类型直接赋值给int类型即可: 1
2int n1 = 'A'; // 字母“A”的Unicodde编码是65
int n2 = '中'; // 汉字“中”的Unicode编码是20013
还可以直接用转义字符Unicode编码来表示一个字符: 1
2char c3 = '\u0041'; // 'A',因为十六进制0041 = 十进制65
char c4 = '\u4e2d'; // '中',因为十六进制4e2d = 十进制20013
字符串类型
字符串类型String是引用类型,使用双引号"..."表示开始和结束。可以使用+连接任意字符串和其他数据类型。 1
2
3
4
5
6
7String s = ""; // 空字符串,包含0个字符
String s1 = "A"; // 包含一个字符
String s2 = "ABC"; // 包含3个字符
String s3 = "中文 ABC"; // 包含6个字符,其中有一个空格
int age = 25;
String s = "age is " + age;
字符串不可变。
空值null
引用类型的变量可以指向一个空值null,它表示不存在,即该变量不指向任何对象。注意要区分空值null和空字符串"",空字符串是一个有效的字符串对象,它不等于null。 1
String s1 = null; // s1是null
流程控制
格式化输出
1 | System.out.printf("%.2f\n", d); // 显示两位小数3.14 |
占位符 | 说明 |
---|---|
%d | 格式化输出整数 |
%x | 格式化输出十六进制整数 |
%f | 格式化输出浮点数 |
%e | 格式化输出科学计数法表示的浮点数 |
%s | 格式化字符串 |
输入
首先,我们通过import语句导入java.util.Scanner,import是导入某个类的语句,必须放到Java源代码的开头,后面我们在Java的package中会详细讲解如何使用import。然后,创建Scanner对象并传入System.in。System.out代表标准输出流,而System.in代表标准输入流。直接使用System.in读取用户输入虽然是可以的,但需要更复杂的代码,而通过Scanner就可以简化后续的代码。有了Scanner对象后,要读取用户输入的字符串,使用scanner.nextLine(),要读取用户输入的整数,使用scanner.nextInt()。Scanner会自动转换数据类型,因此不必手动转换。
1 | import java.util.Scanner; |
if判断
1 | if (条件) { // (true or false) |
判断引用类型相等
判断值类型的变量是否相等,可以使用==运算符。但是,判断引用类型的变量是否相等,==表示引用是否相等,或者说,是否指向同一个对象。
1 | String s1 = "hello"; |
要判断引用类型的变量内容是否相等,必须使用equals()方法。
1 | String s1 = "hello"; |
switch多重选择
switch语句根据switch (表达式)计算的结果,跳转到匹配的case结果,然后继续执行后续语句,直到遇到break结束执行。可以给switch语句加一个default,当没有匹配到任何case时,执行default。
1 | public class Main { |
循环
while 循环 与 do... while 循环
1 | while (条件表达式) { |
for 循环
for循环的功能非常强大,它使用计数器实现循环。for循环会先初始化计数器,然后,在每次循环前检测循环条件,在每次循环后更新计数器。计数器变量通常命名为i。for循环还可以缺少初始化语句、循环条件和每次循环更新语句。 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18for (初始条件; 循环检测条件; 循环后更新计数器) {
// 执行语句
}
// 不设置结束条件:
for (int i=0; ; i++) {
...
}
// 不设置结束条件和更新语句:
for (int i=0; ;) {
...
}
// 什么都不设置:
for (;;) {
...
}
Java还提供了另一种for each循环,它可以更简单地遍历数组。for each循环能够遍历所有“可迭代”的数据类型,包括后面会介绍的List、Map等。 1
2
3
4
5
6
7
8public class Main {
public static void main(String[] args) {
int[] ns = { 1, 4, 9, 16, 25 };
for (int i=?; ?; ?) {
System.out.println(ns[i]);
}
}
}
break and continue
- 在循环过程中,可以使用break语句跳出当前循环。
- continue可以提前结束本次循环,直接继续执行下次循环。
对象和类
Java作为一种面向对象语言。支持以下基本概念:
- 多态 - 继承 - 封装 - 抽象 - 类 - 对象 - 实例 - 方法 - 重载
类
类可以看成是创建Java对象的模板。
- 一个类可以包含以下类型变量:
- 局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。
- 成员变量:成员变量是定义在类中,方法体之外的变量。这种变量在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
- 类变量:类变量也声明在类中,方法体之外,但必须声明为static类型。
- 一个类可以拥有多个方法,在上面的例子中:barking()、hungry()和sleeping()都是Dog类的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13public class Dog{
String breed;
int age;
String color;
void barking(){
}
void hungry(){
}
void sleeping(){
}
}
对象构造和初始化
- 声明:声明一个对象,包括对象名称和对象类型。
- 实例化:使用关键字new来创建一个对象。
- 初始化:使用new创建对象时,会调用构造方法初始化对象。
1 | public class Puppy{ |
实例变量和调用成员方法
1 | public class Puppy{ |
继承
1 | class 父类 { |
继承类型
继承的特性
- 子类拥有父类非 private 的属性、方法。
- 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
- 子类可以用自己的方式实现父类的方法。
继承关键字
继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
extends关键字 在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。
1
2
3
4
5
6
7
8
9
10
11
12public class Animal {
private String name;
private int id;
public Animal(String myName, String myid) {
//初始化属性值
}
public void eat() { //吃东西方法的具体实现 }
public void sleep() { //睡觉方法的具体实现 }
}
public class Penguin extends Animal{
}implements关键字 使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
1 | public interface A { |
- super 与 this 关键字
- super:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
- this:指向自己的引用。
1 | class Animal { |
final关键字 final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写。
1
2final class 类名 {类体}
修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
重写(Override)与重载(Overload)
重写(Override)规则
- 参数列表必须完全与被重写方法的相同;
- 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同); 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
- 父类的成员方法只能被它的子类重写。
- 声明为final的方法不能被重写。
- 声明为static的方法不能被重写,但是能够被再次声明。
- 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法。
- 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
- 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
- 构造方法不能被重写。
- 如果不能继承一个方法,则不能重写这个方法。
- 当需要在子类中调用父类的被重写方法时,要使用super关键字。
1 | class Animal{ |
重载(Overload)规则
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
1 | public class Overloading { |
重写与重载之间的区别
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
多态
现实中,比如我们按下 F1 键这个动作: 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档; 如果当前在 Word 下弹出的就是 Word 帮助; 在 Windows 下弹出的就是 Windows 帮助和支持。 同一个事件发生在不同的对象上会产生不同的结果。
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象
1 | public class Test { |
抽象类与抽象方法
- 如果一个类包含抽象方法,那么该类必须是抽象类。
- 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。
- 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
- 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
- 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
1 | public abstract class Employee |
封装
修改属性的可见性来限制对属性的访问(一般限制为private),例如:
1
2
3
4public class Person {
private String name;
private int age;
}对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21// 采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。
public class Person{
private String name;
private int age;
public int getAge(){
return age;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public void setName(String name){
this.name = name;
}
}
接口
- 接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。 除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
- 接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。
1 | [可见度] interface 接口名称 [extends 其他的接口名] { |
接口与类的区别:
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
抽象类和接口的区别
- 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
源文件声明规则
- 一个源文件中只能有一个public类
- 一个源文件可以有多个非public类
- 源文件的名称应该和public类的类名保持一致。例如:源文件中public类的类名是Employee,那么源文件应该命名为Employee.java。
- 如果一个类定义在某个包中,那么package语句应该在源文件的首行。
- 如果源文件包含import语句,那么应该放在package语句和类定义之间。如果没有package语句,那么import语句应该在源文件中最前面。
- import语句和package语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。
Java包
包主要用来对类和接口进行分类。当开发Java程序时,可能编写成百上千的类,因此很有必要对类和接口进行分类。
1 | package pkg1[.pkg2[.pkg3…]]; |
1 | /* 文件名: Animal.java */ |
Import语句
在Java中,如果给出一个完整的限定名,包括包名、类名,那么Java编译器就可以很容易地定位到源代码或者类。Import语句就是用来提供一个合理的路径,使得编译器可以找到某个类。
1 | import package1[.package2…].(classname|*); |