Java基础学习1-声明和初始化与修饰符
Made by Mike_Zhang
Java主题:
本文将记录Java基础学习的两大内容 - 声明 (Declarations) 和初始化 (Initialization) 与 修饰符 (Modifiers)。
先上代码:
Book.java1
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69package ultrafsih; // package named "ultrafish"
public class Book{
public String name; // public field
public int page; // public field
public boolean safe = false; // public field
public int bookAmount; // public field
private int id = 000000; // private field
protected int subBookId = 000000; // protected field
public static int totalBookUnm; // static field
Book(){
this("NoBookName"); // "this" keyword used to Calling constructors from constructors
}
Book(String initName){
this(1);
name = initName;
}
Book(int initBookAmount){
bookAmount = initBookAmount;
System.out.println("成功创建一个Book类对象,请修改书名!");
}
public Book addAmount(){
bookAmount++;
return this; // "this" keyword used to refer to the current abject
}
public void setName(String name){
this.name = name; // "this" keyword used to solve the ambiguity
}
public void seal(){
System.out.println(Sealer.getSealed(this));
// "this" keyword used to passing the current object to another method "Sealer.getSealed"
}
static void totalBookUnmAdd(){ // static method
totalBookUnm++;
}
public String getName(){
return name;
}
private void setId(int idNum){
id = idNum;
System.out.println("id set, is"+id);
}
public static void main(String[] args) {
Book myBook = new Book();
myBook.setId(000001);
System.out.println(myBook.name);
myBook.setName("MyFirstBook");
System.out.println(myBook.getName());
System.out.println(Book.totalBookUnm);
myBook.addAmount().addAmount().addAmount();
myBook.seal();
}
}
// foreign class and method
class Sealer{
static String getSealed( Book book){
book.safe = true;
return ("Sealed!");
}
}
class textBook extends Book{
textBook(){
textBook myTextBook = new textBook();
myTextBook.subBookId++;
myTextBook.bookAmount++;
}
}
Tool.java
1 |
|
1. 声明 (Declarations)和初始化 (Initialization)
1.1 属性声明与初始化
在之前文章的章节中有大致介绍了类的属性。
属性又称成员变量,有不同的数据类型:
数据类型名称 | 描述 | 内存大小 | 取值范围 | 默认初始值 |
---|---|---|---|---|
boolean |
布尔类型 | 1b | true false |
false |
char |
字符类型 | 16b(2B) | 0x0000~0xffff (Unicode) | \u0000 |
byte |
整数类型 | 8b(1B) | -128~127(2^8) | 0 |
short |
整数类型 | 16b(2B) | -32768~32767(2^16) | 0 |
int |
整数类型 | 32b(4B) | -2147483648~2147483647(2^32) | 0 |
long |
整数类型 | 64b(8B) | -9223372036854775808~9223372036854775807(2^64) | 0L |
float |
单精度浮点类型 | 32b(4B) | 1.4E-45~3.4028235E+38 | 0.0F |
double |
双精度浮点类型 | 64b(8B) | 4.9E-324~1.7976931348623157E+308 | 0.0D |
1 |
|
1.2 方法声明与初始化
参考之前的文章章节。
1.3 Initialization type
- static field initializer
- constructor
- instance initializer block
- instance vairable initializer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Hero{
static int x = 1; // static field initializer
String name = ""; // instance vairable initializer
int health = 1; // instance vairable initializer
{ //instance initializer block
health = 100;
}
Hero(String a, int h){ // constructor
name = a;
health = h;
}
}
1.4 Initialization order
- static field initializer in textual order
- instance initializer block and instance vairable initializer in textual order
- constructor
1.5
this
关键字
看以下案例:
1 |
|
思考:
setId(int idNum)
只有一个参数,可是在对象调用的时候只把参数值000001
传递给了setId(int idNum)
方法,并没有告知方法所对应的具体对象 (myBook)。
实际上,Java在编译此代码时,会编译成以下代码:1
2private void setId(Book this, int idNum){/*...*/} // 声明一个方法
myBook.setId(myBook, 000001); // 调用此方法
解释:
编译后,会在自动添加一个参数this
,通过此参数把目前操作的对象传递到相应的方法中去。
this
关键字可以用在非静态方法中,使其指向正在调用此方法的对象。
通常用在这4种场景中:
1.当语句出现重名导致语义不清(ambiguity)时,使用
this
使其指向正在调用的对象,例如:
看一个案例:
1 |
|
setName()
方法是用来设定 name
属性的值,使其被赋值成方法中的String参数。
可是第二行的语句 name = name;
是有问题的。对象的属性名和方法的参数重名了,造成了ambiguity(模糊不清)。
这时就需要 this
关键字了,使第一个 name
清楚的代表对象的属性名。修改如下:
1 |
|
2.用于return中,返回当前正在调用的对象,可以实现类似于递归的功能,例子如下:
1 |
|
3.当一个方法内部需要访问到外部类的其他方法时,可以使用
this
传递现在指向的对象,例子如下:
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
public class Book{
/*
...
*/
public void seal(){
System.out.println(Sealer.getSealed(this));
// "this" keyword used to passing the current object to another method "Sealer.getSealed()"
}
public static void main(String[] args) {
Book myBook = new Book();
myBook.setId(000001);
System.out.println(myBook.name);
myBook.setName("MyFirstBook");
System.out.println(myBook.getName());
System.out.println(Book.totalBookUnm);
myBook.addAmount().addAmount().addAmount();
myBook.seal(); //调用方法
}
}
// foreign class and method
class Sealer{
static String getSealed( Book book){
book.safe = true;
return ("Sealed!");
}
}
4.
this
关键字可以在构造函数中调用另一个构造函数,例子如下:注:当一个类有多个构造函数时,即包含了无参构造方法
Book()
和有参构造方法Book(String initName)
,会根据new
时调用的构造函数来选择。即:new Book()
调用Book()
,new Book("MyBook")
调用Book(String initName)
,new Book(100)
调用Book(int initBookAmount)
,参数的数据类型需要一一对应。
1 |
|
注意:
当一个构造方法有两个参数时,如:
Book(String initName,int initBookAmount)
, 使用this
关键字只能传递一个参数,即this("NoBookName");
或this(1);
, 结合体this("NoBookName",1);
是不合法的。
2. 修饰符 (Modifiers)
2.1 static
修饰符
通常来说,当一个类创建完成后,我们就完成了这个类静态部分和动态部分的描述。但是只有当我们使用new
关键字创建后才会真正得到一个此类的对象,并且储存这个对象的属性以及激活这个对象的方法。
但是如果
- 用户只想把这一类的某一个属性或者方法储存在同一个内存中,并且想统一管理,不管此类创建了多少对象;
- 再或者 想在创建此类的的对象前就能访问类的静态和动态部分,不关联任何一个此类的对象。
举个实例:
创建一个名为
纸质书
的类, 并创建一个名为材质
的属性。那通常来说纸质书
类的材质
属性值就为纸
,适用于所有的纸质书
对象,不需要为每一位对象单独添加一个材质
属性,只需要统一管理。并且不需要创建任何对象,就可以访问或者修改材质
属性值。
那我们可以说这一类属性或方法是统一的或者是静态的(static),那在Java中这一类的就用static
修饰符来修饰其静态的特征。
举例:1
public static int totalBookUnm; // static field
上述例子中,在Book类中创建了一个名为totalBookUnm
的静态属性。并在下面对其进行访问。
1 |
|
注意:
- 类的静态属性和方法不会联系到类所创建的任何一个特定的对象;
- 无须创建一个此类的对象即可访问到此类的静态属性和方法;
- 对于非静态的属性和方法,必须创建一个此类的对象才能访问。
静态属性和方法最大的用处就是,它允许我们在不创建然后此类的对象的条件下都可以访问其属性和方法。
主函数(主方法)main()
就是一个静态方法,无须创建对象即可访问:
1 |
|
static
看起来有如此高的访问权限,当它遇到访问权限修饰符 public
, protected
和 private
的时候会发生什么呢,下文会提到。
2.2 访问权限修饰符 (Access Control Modifiers)
Java是一门面向对象的编程语言 (Object Oriented Programming),封装性 (Encapsulation)是其一大特征, 旨在把过程抽象并隐藏起来,并只允许客户访问必要的要素,这样可以保护项目的安全,不会担心外部客户影响内部功能,也可以避免内部功能的改变影响到客户对项目的使用。因此访问控制 (Access Control) 就起到了看门人的作用,设置了三道拥有不同权限的门,即public
, protected
和 private
,称为访问权限修饰符 (Access Control Modifiers)。
2.2.1 public
public
的属性和方法是对所有人开放的,包括 本类,子类,同包内的类,不同包内的类。例如:
Book.java
1 |
|
Tool.java
1 |
|
2.2.2 protected
protected
的属性和方法相比于public
减少了外部类(不同包的类)的访问权限,强调了继承类的访问,权限包括 本类,子类,同包内的类,以及不同包中的子类*。例如:
1 |
|
*Notice:
In different package, superclass’s protected number can be accessed by it’s subclass instance, but NOT the superclass’s instance in the subclass.
1 |
|
1 |
|
Tool.java
1 |
|
2.2.3 default-access
若不加访问权限修饰符,则为默认访问权限。其类似于protected
的访问范围,但只能在同一个包内访问,不能访问不同包内的子类。权限包括 本类,子类,同包内的类,但不包括 不同包中的子类。
2.2.4 private
private
的属性和方法限制最多,只有其所在的类可以访问。其子类,或者同一包内的类都不能访问。例如:
Book.java
1 |
|
Tool.java
1 |
|
private
成员甚至限制了开发者对于项目的访问权限,但是这也减小了在修改此成员时对处于同一包内其他类的影响。
2.2.4 static
与 public
, protected
和 private
上文提过,static
看起来有如此高的访问权限,到那时访问权限修饰符(access specifiers)的等级还是高于static
,当遇到private static
时, 修饰的对象还是只有其所在的类可以访问。
2.3 final
修饰符
final
修饰符是用来修饰某一成员是一个常量,是不能被修改的。final
修饰符可以用来修饰类的属性,参数,方法以及一整个类。例如:
1 |
|
2.3.1 final
属性
属性(field)常量指的是类的某一属性值为常量,分为编译时常量( compile-time constant)和空白常量(blank finals)
2.3.1.1 编译时常量
编译时常量( compile-time constant)需要在声明时被初始化,即给定属性值,例如:1
public final boolean safe = false; // safe 属性被final修饰,为编译时常量,需要被赋值
当 final
和 static
修饰符同时修饰一个属性时,此属性名通常由大写字母组成,并且词间用下划线(_)分割,如下:
1 |
|
注:当
static final
修饰一个属性时,此属性值不会随着对象的创建而改变,这也是static
修饰符自带的特性。而当仅仅一个final
修饰属性时,其属性值可能会随着对象的创建而修改,但是在对象创建后,此属性值不能被修改,
例子如下:
1 |
|
两个属性INT_ONE
和int_two
都被赋值成随机数。
1 |
|
创建两个对象t1和t2,假设创建后t1两个属性为:
1 |
|
则:
1 |
|
同时,这个例子也表明了,编译时常量( compile-time constant)并不代表在表达式内就需要明确属性值,INT_ONE
和int_two
都是在运行程序时被随机数函数赋值的。
Notice:
final
protects only the variable itself.
If the variable is a reference, final does not protect the object that reference points to.
1 |
|
protect obj
instance itself;
not including the object it points to(e.g. name
)
2.3.1.2 空白常量
空白常量 (blank final) 不需要需要在声明时被初始化,即不需要给定属性值,例如:
1 |
|
但是空白常量在使用前必须被赋值,通常会在构造方法中进行赋值。
总之,不管是 编译时常量( compile-time constant) 还是 空白常量 (blank final) 都需要在使用前被赋值,即在声明中或者构造方法中被赋值。
2.3.2 final
参数
final
参数可以修饰某一方法的形式参数为常量,在这个方法内,不能对此形式参数所指向的参数进行修改,只能读取此参数值,例如:
1 |
|
或:1
2
3
4
5public void setId(final int idNum){
idNum++; // Error, illegal to rewrite "idNum"
id = idNum + 1; // OK, can only read "idNum"
System.out.println("id set, is"+id);
}
2.3.3 final
方法
final
方法用来修饰某一类的方法为常量,如下:
1 |
|
final
方法主要有两个目的:
- 保护一个类的某一方法被重写,尤其是保护此方法被此类的继承类(子类)重写,例如:
1 |
|
- 提高代码的运行效率。
编译时,编译器会优先运行被final修饰的方法,因此程序员可以根据需求,告知编译器需要优先运行那一部分的方法。然而在Java SE 5/6中,编译器以及JVM会处理程序的编译优先级,自动提高运行效率。
总之,目前只有当程序员需要保护某些方法不被重写才会使用到 final
。
注意:
当
final
和private
同时修饰一个方法:
事实上任何private
方法都是final
,因为不能从一个类的外部访问private
方法,因此当然不能对其进行修改,成为final
。所以给一个private
方法加上final
修饰符并没有什么意义。但是当在重写一个
final private
方法时,情况会变得不一样,举例:
1 |
|
解释:
子类不能重写父类中的
final
方法是因为每当子类运行重写父类的方法时,都会回溯到父类,与父类中的被继承方法进行连接,但是因为final
而被阻止。
但是当子类“重写父类private
方法”时,子类并不能访问到父类的private
方法,这也正是private
修饰的特性。因此这个看似“重写父类private
方法”方法,并没有重写父类的方法,只是在子类中创建了一个属于子类的新方法,只不过与父类方法刚好重名了,所以并无关联。
而例子中不能运行的代码,是因为没有重写父类方法而导致不能使用多态(Polymorphism)。
3.3.4 final
类
final
类修饰的是整个类,修改整个类或者对此类进行继承都是不允许的。final
类可以保障一个类的安全,适用于一个不用被修改的类,例如:
1 |
|
当一个类被 final
修饰后,其属性值还是可以被改变,也可以选择其属性是否被 final
进行修饰。
但是其方法就同样为final
,不能被修改,这样才保证了 final
类不会被继承,其方法不会被重写。final
类中的方法前可选加final
修饰符,但是不会有任何其他意义。
2.4 package
语句
在Java中,每个独立的public
类都被安排在分开的文件夹中,每个文件夹中可能还包括其他的非public
的类。为了更好的管理这些有关联的类,可以应用到package
。package
包含了一组class,并且被管理在统一的namespace
(命名空间)下。
这些有关系的类必须放在一个名packageName
的文件夹中,并且每个类的源文件必须把以下代码写在开头:1
package packageName;
例如,定义一个名为triangle
的 package,并包含RightTriangle
,IsoscelesTriangle
两个类,那必须在这两个类文件开头写出 package 语句,否则这两个文件就被放在 default package 中。如果需要引用package中的类,就需要使用fully qualified name,例如:
triangle.RightTriangle
:引用RightTriangle
类triangle.IsoscelesTriangle
:引用IsoscelesTriangle
类
同时,在 package 文件夹中也可以定义子文件夹,并通过fully qualified name去引用,以Scanner
类举例:
Scanner
类位于java.util这一子文件夹中,可以用以下语句引用到Scanner
类:java.util.Scanner
如果要引用Scanner
类创建对象,则可以使用它的fully qualified name:1
java.util.Scanner input = new java.util.Scanner(System.in);
但是每次都使用fully qualified name会十分麻烦,那就可以使用import语句导入某个类或者一整个package,如下:
import packageName.className;
或者import packageName.*;
以Scanner
类为例:1
2import java.util.Scanner; // 导入一个类
import java.util.*; // 导入一整个package
则在创建对象时就可以使用类名:
1 |
|
注意:如果从两个不同的package中import两个相同名字的类,或者import的类与现有现有的类重名的话,单单使用一个unqualified name是不合法的,如:
1 |
|
例如:
此语句同时写在Book.java和Tool.java两个文件中,因此两个文件可以互相访问对方的class。
Tool.java1
2
3
4
5
6
7
8
9
10
11
12
13package ultrafsih;
public class Tool {
public static void main(String[] args) {
Book myBook = new Book("My Book Name");
System.out.println(myBook.name); //访问Book类的属性
System.out.println(myBook.page); //访问Book类的属性
//System.out.println(myBook.id); // Error
System.out.println(myBook.subBookId); //
System.out.println(Book.totalBookUnm); //
}
}
Tool类因为包含在ultrafish package
中,因此它可以直接访问同样在package中Book类属性以及方法。因此使用package可以很方便的管理需要访问不同Java文件的Java开发工程。
注意:
package
语句必须写在Java文件除注释以外的第一行;package
语句中的包名一般使用小写字母。- 建议把包文件放在同一个子目录下,可以解决 重名 以及 寻找class不易 的问题
参考
B. Eckel, Thinking in java. Upper Saddle River, N.Y. Prentice Hall, 2014.
M. Goodrich, R. Tamassia, and A. O’reilly, Data Structures and Algorithms in Java, 6th Edition. John Wiley & Sons, 2014.
写在最后
Java相关的基础知识十分重要,会继续学习,继续更新.
最后,希望大家一起交流,分享,指出问题,谢谢!
原创文章,转载请标明出处
Made by Mike_Zhang