Java类和对象
Q7nl1s admin

Java面向对象概述

对Java而言,一切皆是对象。

对象就是客观存在的实体,对象和实体是一一对应的,也就是说现实世界中每一个实体都是一个对象,它是一种具体的概念。

面向对象的三大核心特征

  • 继承性:程序中的继承性是指子类拥有父类全部特征和行为,这时类之间的一种关系。Java只支持单继承
  • 封装性:Java的基本封装单位是类,封装的目的在于保护信息。Java提供了私有和公有的访问模式,类的公有接口用来接收和获取外部信息或者和外部进行信息交换。类的私有方法数据只能通过该类的成员代码来访问。
  • 多态性:多态性指“一个接口,多个方法”,它允许一个接口被多个同类使用。具体表现在父类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方法。

面向对象程序设计的三大优点

  • 可重用性:一段代码重复使用,减少代码量。
  • 可扩展性:新的功能可以很容易的加入到原系统中。
  • 可管理性:功能和数据结合方便管理。

Java认识类和对象

类是一个客观世界中某类群体的共性特征的基本抽象,而对象表示的是具体的某个事物。所以说类是对象的抽象,对象是类的具体。

类是描述了一组有相同特征(属性)和相同行为(方法)的一组对象的集合,对象或实体所拥有的特征在类中表示时称为类的属性,对象执行的操作称为类的方法。

类是构造面向对象程序的基本单位。

Java类的定义及定义类时可用的关键字

类是Java中一种重要的引用数据类型,也是组成Java程序的基本要素,因为所有的Java程序都是基于类的。

定义Java类的完整语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
[public][abstract|final]class<class_name>[extends<class_name>][implements<interface_name>] {
// 定义属性部分
<property_type><property1>;
<property_type><property2>;
<property_type><property3>;

// 定义方法部分
function1();
function2();
function3();

}
  • 上述语法中”[]”内的部分可以省略
  • 竖线”|”表示”或关系”,例如abstract|final,说明可以使用abstract或final关键字,但是两个关键字不能同时出现。

Java类中关键字的描述

  • public:表示“共有”的意思。如果使用public修饰,则可以被其他类和程序访问。每个Java程序的主类都必须是public类,作为公共工具给其它类和创新使用的类定义为public类。
  • abstract:如果类被abstract修饰,则该类为抽象类,抽象类不能被实例化,但抽象类可以有抽象方法(使用abstract修饰的方法)和具体方法(没有使用abstract修修饰的方法)。继承该抽象类的所有子类都必须实现该抽象类中的所有抽象方法(除非子类也是抽象类)。
  • final:如果类被final修饰,则不允许被继承。它常被称为“最终类”。
  • class:声明类的关键字。
  • class_name:类的名称。
  • extends:表示继承其它类。
  • implements:表示实现某些接口。
  • property_type:表示成员变量的类型。
  • property:表示成员变量名称。
  • function:表示成员方法名称。

Java命名规则

  • 类名由一个下划线(_)或字母开头,最好字母开头。
  • 第一个字母最好大写,如果类名由多个字母组成,则每个字母的首字母最好都大写
  • 类名不能为Java中的关键字,例如:booleanthisint
  • 类名不能包含任何嵌入的空格或点号以及除了下滑线(_)和美元符号($)还有人民币符号(¥)之外的特殊字符。

Java类的属性

成员变量的声明

声明成员变量的语法如下:

1
[public|default|protected|private][static][final]<type><variable_name>
  • public、protected、private:由于表示成员变量的访问权限。
  • static:表示成员变量为类变量,也称其为静态变量。
  • final:表示将该成员变量声明为常量,其值无法更改。
  • type:表示成员变量的类型。
  • variable_name:表示变量名称。

成员变量的定义

可以在声明成员变量的同时对其进行初始化,如果声明变量时没有对其初始化,则系统会质疑默认初始值对其进行初始化。

初始化默认值如下:

  • 整数型(byte、short、int 和 long)的基本类型变量的默认值为 0。
  • 单精度浮点型(float)的基本类型变量的默认值为 0.0f。
  • 双精度浮点型(double)的基本类型变量的默认值为 0.0d。
  • 字符型(char)的基本类型变量的默认值为 “\u0000”。
  • 布尔型的基本类型变量的默认值为 false。
  • 数组引用类型的变量的默认值为 null。如果创建了数组变量的实例,但没有显式地为每个元素赋值,则数组中的元素初始化值采用数组数据类型对应的默认值。

下面给出简单案例

1
2
3
4
5
public class Student{
public String name; // 姓名
final int sex = 0; // 性别:0表示女,1表示男,默认访问权限为default
private int age; // 年龄
}

注:Java中有四种成员变量的访问权限,当一个类的某个成员变量前面不带任何修饰(private、protected、public),这种成员变量在Java中,会默认一种访问权限——default(包内访问权限),即对与该类同一包内的其他类来说,该成员变量是可见的,但对包外的类来说,该成员变量是不可见的。

Java成员方法

一个方法的组成元素如图8-0所示

java-8-0

图8-0 方法组成元素

方法的声明语法如下:

1
2
3
4
5
public class Test{
[public|private|protected][static]<void|return_type><method_name>([paramList]){
// 方法体
}
}

需要注意的是,方法的名称要遵循标识符的命名规则,除此之外,方法名称第一个单词的第一个字母是小写,第二个单词的第一个字母是大写,依此类推。

在方法中各修饰符含义如下:

  • public、private、protected:表示成员方法的访问权限。
  • static:表示限定该成员方法为静态方法。
  • final:表示限定该成员方法不能被重写或重载。
  • abstract:表示限定该成员方法为抽象方法。抽象方法不提供具体的实现,并且所属类型必须为抽象类。

方法体中的局部变量

局部变量只能在本方法内有效或课件,离开本方法则这些变量将被自动释放。

在方法体内定义变量时,变量前不能加修饰符。局部变量在使用前必须明确赋值,否则编译时会出现错误。

Java this关键字

Java中的this关键字可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用。

this.属性名

大部分时候,普通方法访问其他方法、成员变量时无需使用this前缀,但如果方法里有个局部变量和成员变量同名,但程序有需要再该方法里访问这个被遮蔽的成员变量,则必须用this前缀。

this.方法名

this关键字最大的作用就是让类中的一个方法访问该类里的另一个方法或实例变量。

举个例子来说

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 第一种定义Dog类方法
**/
public class Dog {
// 定义一个jump()方法
public void jump() {
System.out.println("正在执行jump方法");
}
// 定义一个run()方法,run()方法需要借助jump()方法
public void run() {
Dog d = new Dog();
d.jump();
System.out.println("正在执行 run 方法");
}
}

像这样在run()方法内部再创建一个Dog实例的定义方式实在有些复杂和多余了,而且它调用的并不是当前对象的jump()方法。this关键字帮我们很好的解决了这个问题,代码如下

1
2
3
4
5
6
7
8
9
/**
* 第二种定义Dog类方法
**/
// 定义一个run()方法,run()方法需要借助jump()方法
public void run() {
// 使用this引用调用run()方法的对象
this.jump();
System.out.println("正在执行run方法");
}

在此定义方式中,run()方法和jump()方法同属一一个对象,并且执行run()方法会自动调用该对象的jump方法。当然Java允许对象的一个成员直接调用另一个成员(在该方法中没有同名的变量出现时),可以省略this前缀,因为它是隐式的,也就是说将上面的run()方法改为如下形式也是正确的。

1
2
3
4
public void run(){
jump();
System.out.println("正在执行run方法");
}

注意:对于static修饰的方法而言,可以使用类来直接调用该方法,如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象。所以static修饰的方法中不能使用this引用。并且Java语法规定,静态成员不能直接访问非静态成员。

this()访问构造方法

this()用来访问本类的构造方法(构造方法是类的一种特殊方法,方法名和类名相同,没有返回值)

下面给出一个简单案例,定义一个Student类,使用this()调用构造方法给name赋值,Student类的代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Student{
String name;

// 无参构造方法
public Student(){
this("张三");
}

// 有参构造方法
public Student(String name){
this.name = name;
}

// 输出name和age
public void print(){
System.out.println("姓名:" + name);
}

public static void main(String[] args){
Student stu = new Student();
stu.print();
}
}

输出结果为:

1
姓名:张三

注意:

  • this()不能在普通方法中石油,只能在构造方法中使用。
  • 在构造方法中使用时,必须是第一条语句。

Java对象的创建

显示创建

  1. 使用new关键字创建对象
  2. 调用java.lang.Class或者java.lang.reflect.Constuctor类的newInstance()实例方法
  3. 调用对象的clone()方法
  4. 调用java.io.ObjectInputStream对象那个的readObject()方法
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
34
35
public class Student implements Cloneable {   
// 实现 Cloneable 接口
private String Name; // 学生名字
private int age; // 学生年龄
public Student(String name,int age) {
// 构造方法
this.Name = name;
this.age = age;
}
public Student() {
this.Name = "name";
this.age = 0;
}
public String toString() {
return"学生名字:"+Name+",年龄:"+age;
}
public static void main(String[] args)throws Exception {
System.out.println("---------使用 new 关键字创建对象---------");

// 使用new关键字创建对象
Student student1 = new Student("小刘",22);
System.out.println(student1);
System.out.println("-----------调用 java.lang.Class 的 newInstance() 方法创建对象-----------");

// 调用 java.lang.Class 的 newInstance() 方法创建对象
Class c1 = Class.forName("Student");
Student student2 = (Student)c1.newInstance();
System.out.println(student2);
System.out.println("-------------------调用对象的 clone() 方法创建对象----------");

// 调用对象的 clone() 方法创建对象
Student student3 = (Student)student2.clone();
System.out.println(student3);
}
}

程序执行如下:

1
2
3
4
5
6
---------使用 new 关键字创建对象---------
学生名字:小刘,年龄:22
-----------调用 java.lang.Class 的 newInstance() 方法创建对象-----------
学生名字:name,年龄:0
-------------------调用对象的done()方法创建对象----------
学生名字:name,年龄:0

要注意的是:

  • 使用new关键字或Class对象的newInstance()方法创建对象时,都会调用类的构造方法。
  • 使用Class类的newInstrance()方法创建对象时,会调用类的默认构造方法,即无参构造方法。
  • 使用Object类的clone()方法创建对象时,不会调用类的构造方法,它会创建一个复制的对象,这个对象和原来的对象具有不同的内存地址,但它们的属性值相同。
  • 如果类没有实现Cloneable接口,则clone方法会抛出java.lang.CloneNotSupportedException一场,所以应该让类实现Cloneable接口。

隐式创建

一共有三种情况

  • String strName = "strValue";,其中"strValue"就是一个String对象,由Java虚拟机隐含创建。

  • 字符串的”+“运算符的结果为一个新的String对象,实例如下:

    1
    2
    3
    String str1 = "Hello";
    String str2 = "Java";
    String str3 = str1 + str2; // str3引用一个新的String对象
  • 当Java虚拟机加载一个类时,会隐含创建描述这个类的Class实例。

Java new运算符

堆是用来存放new创建的对象和数组,即动态申请的内存都放在堆区。栈是用来存放在方法中定义的一些基本类型的变量和对象的引用变量。

基本语法如下

1
classname obj = new classname();

new运算符是在运行期间为对象分配内存的,这使得内存的分配更加灵活和高效。但是内存是有限的,一次new有可能由于内存不足而无法给一个对象分配内存。如果出现这样的情况,就会发生运行时异常。

Java匿名对象

创建对象的标准格式上面已经提及

1
类名称 对象名 = new 类名称();

这相当于每new一次就开辟一个新的对象,并开辟了一块新的内存空间。如果一个对象只需要使用唯一一次,就可以使用匿名对象,匿名对象还可作为实际参数传递。

所谓的匿名对象,就是没有明确给出名字的对象,是对象的一种简写形式。一般匿名对象只使用一次而且匿名对象只在堆中开辟空间,而不存在栈内存的引用。

简单案例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Person{
public String name;
public int age;

// 定义构造方法,为属性初始化
public Person(String name,int age){
this.name = name;
this.age = age;
}

// 获取信息的方法
public void tell(){
System.out.println("姓名:" + name + ",年龄:" + age);
}

public static void main(String[] args){
new Person("张三",30).tell(); // 匿名对象
}
}

程序运行结果

1
姓名:张三,年龄:30

可以发现,new Person("张三",30)语句,这实际上就是一个匿名对象,在使用一次后就等待被GC回收。

Java访问对象的属性和行为

在Java中,要引用对象的属性和行为,需要使用(.)操作符来访问。对象名在圆点左边,而成员变量或成员方法在圆点的右边。语法格式如下:

1
2
对象名.属性(成员变量) // 访问对象的属性
对象名.成员方法名() // 访问对象的方法

对没有被实例化的对象(即给了空引用null),如果调用对象中的属性和方法,会抛出异常

1
Exception in thread "main" java.lang.NullPointerException

此异常时开发中最常见的异常,也会伴随着每位开发者,使用了未实例化的对象则肯定会出现此异常。

Java对象的销毁

我们知到C++的对象是通过delete语句手动释放的。Java语言的一大特色是在清除对象时,由系统自动进行内存回收不需要用户额外处理。这被称为垃圾回收(Garbage Collection)机制,简称GC。

Java语言并不要求JVM有GC,也没有规定GC如何工作。不管常用的JVM都有GC,而且大多数GC都是以类似的算法管理内存和执行回收操作。具体的垃圾回收实现策略有好多种,在此不再赘述。

一般一个对象被当作垃圾回收的情况主要有如下两种:

  • 对象的引用超过其作用范围

    1
    2
    3
    {
    Object o = new Object(); // 对象o的作用范围,超过这个范围u第项将被是为垃圾
    }
  • 对象被赋值为null

    1
    2
    3
    4
    {
    Object o = new Object();
    o = null; // 对象被赋值为null将被视为垃圾
    }

需要注意的是,在Java中的Object类还提供了一个protected类型的finalize()方法,因此任何Java类都可以覆盖这个方法,在这个方法中进行释放对象所占有的相关资源的操作。

在Java虚拟机的堆区,每个对象都可能出于以下三种状态之一。

  • 可触及状态:当一个对象被创建之后,中i要程序中还有引用变量引用它,那么他就始终出于可触及状态。
  • 可复活状态:当程序不再有任何引用变量引用该对象时,该对象就进入客服或状态。在这个状态,垃圾回收器会准备释放它所占用的内存,在释放之前,会调用它及其他出于可复活状态的对象的finalize()方法,这些finalize()方法有可能使该对象重新转到可触及状态。
  • 不可触及状态:当Java虚拟机执行完所有可复活的对象的finalize()方法后,如果这些方法都没有使该对象赚到可触及状态,垃圾回收器才会真正回收它占用的内存。

注意:调用System.gc()Runtime.gc()方法也不能保证回收操作一定执行,它们只是提高了Java垃圾回收其尽快回收垃圾的可能性。

Java中的空对象(null)

为了目前表示那些仅有名字而没有内存空间的变量的具体内容,Java引入了关键字null。null表示“空”的意思,是绝对意义上的空,这个空指的是不存在。

当一个引用变量(当一个变量指向一个对象时,这个变量就被称为引用变量)没有通过new分配空间,这个对象就是空对象,Java使用关键字null表示空对象,实例如下:

1
2
String str1 = null;
str1 = "Hello World!";

注意:null大小写敏感。

若试图调用一个空对象的属性或方法时,会怕出空指针异常(NullPointerException),代码如下:

1
2
3
4
5
String str1; // 相当于 String str1 = null;
// 输出null字符串
System.out.println(str1);
// 调用length()方法
int len = str1.length();

其中第五行代码会发生编译错误,系统会抛出空指针异常。因为调用了空对象的length()方法。

在项目实战中的规避措施

空指针异常可以通过盘对一个对象是否为null来规避

1
2
3
4
// 判断对象是否为null
if(str1 != null){
int len = str1.length();
}

Java注释

注意:本节注释使用文档注释。多行注释的内容不能用于生成一个开发者文档(文档提供类、方法和变量的解释,也可称为帮助文档),而文档注释可以。

类注释

类注释一般必须放在所有的”import”语句之后,类定义之前,类注释模板如以下所示

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @projectName(项目名称): project_name
* @package(包): package_name.file_name
* @className(类名称): type_name
* @description(类描述): 一句话描述该类的功能
* @author(创建人): user
* @createDate(创建时间): datetime
* @updateUser(修改人): user
* @updateDate(修改时间): datetime
* @updateRemark(修改备注): 说明本次修改内容
* @version(版本): v1.0
*/

以上以@开头的标签为javadoc标记,由@和标记类型组成,缺一不可。@和编辑类型之间有时可以用空格符分割,但是不推荐用空格符分隔,这样容易出错。

一个类注释的创建人、创建时间和描述是不可缺少的。下面是一个类注释的例子。

1
2
3
4
5
6
7
8
/**
* @author: zhangsan
* @createDate: 2018/10/28
* @description: this is the student class.
*/
public class student{
.................
}

需要注意的是没有必要在每一行的开头用*,也就是说@前的*是可以省略的。

方法注释

方法注释必须仅靠在方法定义的前面,主要声明方法参数、返回值、异常信息等。

方法注释处理可以使用通用标签外,还可以使用下列以@开始的标签。

  • @param 变量描述:对当前方法的参数部分添加一个说明,可以占据多行。一个方法的所有@param标记必须放在一起。
  • @return 返回类型描述:对当前方法添加返回值部分,可以跨越多行。
  • @throw 异常类描述:表示这个方法有可能抛出异常。

需要注意的是在为类的构造当打添加注释时,一般声明该方法的信息参数,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student{
String name;
int age;
/**
* @description: 构造方法
* @param name: 学生姓名
* @param age: 学生年龄
*/
public Student(String name,int age{
this.name = name;
this.age = age;
}

}

字段注释

字段注释在定义字段前面,用来描述字段的含义。下面是一个字段注释的例子。

1
2
3
4
5
6
7
8
9
/**
* 用户名
*/
public String name;

// 也可以使用以下格式

/**用户名*/
public String name;

Java访问控制修饰符

Java语言中又对个作用域修饰符,其中常用的由public、private、protected、final、abstract、static、transient和volatile,这些修饰符有类修饰符、变量修饰符和方法修饰符。

信息隐藏是OOP最重要的功能之一,也是使用访问修饰符的原因。

类的访问控制符只能是空或者public,方法和属性的访问控制符有4个,分别是public、private、protected和default,其中friendly是一种没有定义专门的访问控制符的默认情况。如表8—0所示。

访问范围 private default(默认) protected public
同一个类 可访问 可访问 可访问 可访问
同一包中的其他类 不可访问 可访问 可访问 可访问
不同包中的子类 不可访问 不可访问 可访问 可访问
不同包中的非子类 不可访问 不可访问 不可访问 可访问

表8-0 各种访问修饰符发可访问性

  • private

    用private修饰的类成员,只能被该类自身的方法访问和修改,而不能被其他任何类(包括该类的子类)访问和引用。

  • default(默认)

    如果一个类没有访问控制符,说明它具有默认的访问控制特征——只能被同一包中的类访问和引用。这种访问特征被称为包访问性(package private)。

    同样的,如果类内的成员没有访问控制符,也说明它们具有包访问性。

  • protected

    用保护访问控制权protected修饰的类成员可以被三种类所访问:该类自身、与它在同一包中的其他类以及在其他包中的该类的子类。

  • public

    当一个类被声明为public时,它就具有了被其他包中的类访问的可能性。每个Java重新的朱磊都必须时public类,因为主类必须拥有对外的接口。

Java static关键字

在类中用static修饰符修饰的属性(成员变量)被称为静态变量(类变量),静态成员不依赖于类的特定实例,被类的所有实例共享。

静态变量

类的成员变量可以分为两种:静态变量、实例变量。两者的区别是是否被static修饰。

静态变量相较于实例变量的特征如下:

  • 运行时,Java虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配。
  • 在类的内部,可以在任何方法内直接访问静态变量。
  • 在其他类中,可以通过类名访问该类中的静态成员。

静态变量在类中的作用如下:

  • 静态变量可以被类所有实例共享,异常静态变量可以作为实例之间的共享数据,增加实例之间的交互性。

  • 如果类的所有实例都包含一个相同的常量属性,则可以把这个属性i的那个以为常量类型,从而节省内存空间。例如,在类中定义一个静态常量PI。

    1
    public static double PI = 3.14159256

静态方法

与成员变量类似,成员方法也可以分为两种:静态方法(类方法)、实例方法。两者的区别也是是否被static修饰。

在访问非静态方法时,需要通过实例对象来访问,儿子啊访问静态方法时,可以直接访问,也可以通过类名来访问,还可以通过实例化对象来访问。

静态代码块

静态代码块值Java类中的static{}代码块,注意用于初始化类,为静态变量赋初始值,提升程序性能。

静态代码块有以下特点:

  • 类似于一个方法,但不可以存在于任何方法体内。
  • 可以置于类中的任何地方,类中可以有多个静态初始化块。
  • Java虚拟机在加载类时执行静态代码块,所以很多时候会将一些只需要执行一次的初始化操作都放在static代码块中进行。
  • 如果类中包含多个静态代码库奥,则Java虚拟机将按它们子啊类中出现的顺序一次执行它们,每个静态代码块只会被执行一次。
  • 静态代码块和静态方法一样,不能直接访问类的实例变量和实例方法,而需要通过类的实例对象来访问。

Java静态引入

JDK 1.5之后加入了一种静态导入的语法。,用于导入指定类的某个静态成员变量、方法或全部的静态成员变量、方法。

静态导入使用import static语句,其也有两种语法,分别用于导入指定类的单个静态成员变量、方法或全部静态成员变量、方法,其中导入单个静态成员变量、方法的格式如下:

1
import static package.ClassName.fieldName|methodName;

导入指定类全部静态成员变量、方法的语法格式如下:

1
import static package.ClassName.*;

上面语法中的星号只能代表静态成员变量或方法名。

可以用一句话来归纳 import 和 import static 的作用:使用 import 可以省略写包名,而使用 import static 可以省略类名

Java static常见的问题和使用误区

常见问题

  • 为什么要用”static“关键字?

    通常来说,用 new 创建类的对象时,数据存储空间才被分配,方法才供外界调用。有时候我们只想为特定域分配单一存储空间,不考虑要创建多少对象或者说根本就不创建任何对象,有时候我们想在没有创建对象的情况下也想调用方法。在这两种情况下,static 关键字,满足了我们的需求。

  • ”static“关键字是什么意思?Java 中是否可以覆盖(子类中如果创建了一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写,又称为方法覆盖。这里了解即可,教程后面我们会详细讲解)一个 private 或者是 static 的方法?

    “static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。

    Java 中 static 方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。

  • 是否可以在 static 环境中访问非 static 变量?

    static 变量在 Java 中是属于类的,它在所有的实例中的值是一样的。当类被 Java 虚拟机载入的时候,会对 static 变量进行初始化。如果你的代码尝试不用实例来访问非 static 的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。

  • static 静态方法能不能引用非静态资源?

    不能,new 的时候才会产生的东西,对于初始化后就存在的静态资源来说,不能引用它。

  • static 静态方法里面能不能引用静态资源?

    可以,因为都是类初始化的时候加载的。

  • 非静态方法里面能不能引用静态资源?

    可以,非静态方法就是实例方法,那是 new 之后才产生的,那么属于类的内容它都认识。

使用误区

  1. static 关键字会改变类中成员的访问权限吗?

    不会

  2. 能通过 this 访问静态成员变量吗?

    可以,静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。

  3. 静态成员变量虽然独立于对象,但是不代表不可以通过对象去访问,所有的静态方法和静态变量都可以通过对象访问(只要访问权限足够)。

  4. static 能作用于局部变量么?

    在 C/C++ 中 static 是可以作用域局部变量的,但是在 Java 中切记,Java 语法规定 static 是不允许用来修饰局部变量

Java final修饰符

final在Java中有完结器的作用,它在应用于类、方法和变量时意义是不同的,但本质是一样的,都表示不可改变。

  • final用在变量前面表示此变量的值一旦初始化就不可改变,此时该变量就可以被称为常量。
  • final用在方法前面表示方法不可以被重写(子类中如果创建类一个与父类相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写,又称为方法覆盖)。注意:与重载不同
  • final用在类前面表示该类为最终类,不能继续拥有子类,即该类不可被继承。

final修饰变量

final修饰局部变量和成员变量有所不同

  • final修饰的局部变量必须使用之前被赋值一次才能使用。
  • final修饰的成员变量在声明时没有赋值的叫“空白final变量”。空白final变量必须在构造方法或静态代码块中初始化。

下面给出案例

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
public class FinalDemo{
void doSomething(){
// 没有在声明的同时赋值
final int e;
// 只能赋值一次
e = 100;
System.ot.print(e);
// 声明的同时赋值
final int f = 100;
}

// 实例常量
final int a = 5; // 直接赋值
final int b; // 空白final变量
// 静态常量
final static int c = 12; // 直接赋值
final static int d; // 空白final变量
// 静态代码块
static{
// 初始化静态变量
d = 32;
}

// 构造方法
FinalDemo(){
// 初始化实例变量
b = 3;
// 第二次赋值,会发生编译错误
// b = 4;
}
}

final修饰基本变量和引用变量的区别

当final修饰基本变量时,指的是不能对基本类型进行二次赋值。但对于引用变量而言,它保存的仅仅时一个引用,final值保证这个引用中的地址不会改变,即指向的对象不会改变,但对象本身的值可以改变。

注意:在使用final声明变量时,要求全部的字母大写,这点在开发中是非常重要的。

final修饰方法

对于不希望子类在父类中重写的方法,可以使用final修饰。

下面给出案例

1
2
3
4
5
6
7
8
public class FinalMethodTest{
public final void test(){}
}

class Sub extends FinalMethodTest{
// 下面方法定义将出现编译错误,不能重写final方法
public void test();
}

但值得思考的是,对于一个private方法,因为它仅在当前类可见,其子类无法访问该方法,所以子类也无法重写该方法————如果子类中定义一个与父类private方法有相同方法名、相同形参列表、相同返回值的 方法,也不是方法重写,而是重新定义了一个新方法。因此,即使使用final修饰一个private访问权限的方法,依然可以在其子类中第一与更改方法具有相同方法名、相同形参列表、相同返回值类型的方法。

下面演示了如何在子类“重写”父类private final的方法

1
2
3
4
5
6
7
8
public class PrivateFinalMethodTest{
private final void test(){}
}

class Sub extends PrivateFinalMethodTest{
// 下面的方法定义不会出现问题
public void test(){}
}

final修饰的方法仅仅是不能被重写,并不是不能被重载,因此下面重写完全没有问题

1
2
3
4
5
public class FInalOverload{
// final修饰的方法只是不能被重写,但完全可以被重载
public final void test(){}
public final void test(String arg){}
}

final修饰类

final修饰的类不能被继承,它常常被称为“最终类”。

下面给出演示

1
2
3
final class SuperClass{}

class SubClass extends SuperClass{} // 编译错误

一旦一个类被final修饰,这意味着此类在一棵继承树中是一个叶子类,并且此类的设计已被认为很完美而不需要进行修改或扩展。

对于final类中的成员,可以定义其为final,也可以不是final。而对于方法,由于所属类为final的关系,自然也就成了final型。所以给final的成员方法加上一个final是没有意义的。

Java main()方法

在Java中,main()方法是Java应用程序的入口方法,重写运行的时候,第一个执行的方法就是main()方法。

下面看一个最简单的HelloWorld的示例main()方法

1
2
3
4
5
public class HelloWorld{
public static void main(String args[]){
System.out.println("Hello World!");
}
}

使用main()方法时有以下几点注意事项

  • 访问控制权是公有的(public)
  • main()方法是静态的。如果要在main()方法中调用其他方法,则该方法也必须是静态的,否则需要先创建本类的实例对象,然后再通过对象调用成员方法。
  • main()方法没有返回值,只能使用void
  • main()方法具有一个字符串数组参数,用来接收执行Java重写命令行参数。命令行参数作为字符串,按照顺序依次对应字符串数组中的元素。
  • 字符串中的数组名(代码中的args)可以任意设置,但根据习惯,这个字符串数组的名字一般和Java规范范例中main()参数名保持一致,命名为args,而方法中的其他内容都是固定的。
  • main()方法定义必须是“public static void main(String[] 字符串参数名)”。
  • 一个类只能有一个main()方法,这时一个常用于对类进行单元测试(对软件中的最小可测试单元进行检查和验证,简称TDD)的技巧

下面演示如何在main()方法中调用本类的静态和非静态方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Student{
public void Speak1{
System.out.println("你好");
}
public static void Speak2{
System.out.println("Java!");
}
public static void main(String[] args){
// Speak1(); // 错误调用
Speak2(); // 可以直接调用静态方法Speak2()
Student t = new Student();
t.Speak1(); // 调用非静态方法,需要通过类的对象来调用
}
}

可以发现,如果要调用非静态方法,需要将当前类实例化,然后通过类的对象来调用。

下面给出一个执行时统计传递的参数及每个参数值的程序:

1
2
3
4
5
6
7
8
9
10
11
12
public class TestMain{
public static void main(String[] args){
int n = args.length; // 获取参数数量
System.out.println("一共有" + n + "个参数");
if(n > 0){
// 判断参数个数是否大于0
for(int i = 0;i < n;i++){
System.out.println(args[i]);
}
}
}
}
  1. 将代码保存到TeatMain.java文件中,然后用如下Java命令对程序进行编译

    1
    javac TestMain.java
  2. 程序编译成功后用如下Java命令执行Test程序

    1
    java TestMain 参数列表 // 多个之间用空格隔开

Java中的main()方法格式为什么时固定不变的?

上面已经提到在Java中main()方法的定义必须是

1
public static void main(String[] listname)

它有四个关键参数,我们来一一分析

  • public:这个字段决定了方法是可以被外部方法调用的。如果不限定public,JVM就不能调用main方法。
  • static:在C语言中所有函数都可以被直接调用,main函数相当于全局方法,没有类的概念,但在Java中一般要先创建一个类的对象,再通过对象调用方法,但是再执行main方法之前创建一个对象显然是不可能的(因为main()函数最早执行,比构造方法更早),因为JVM规定main方法为入口方法,从main开始执行。static关键字就解决了这个问题,static可以视为类方法,不需要创建对象就可以直接调用该方法。
  • void:由于Java的主方法是栈最底层的方法,所以不存再能够接收的值,没有能接收main方法的返回值,所以用void。再Java中可以认为是操作系统->JVM->main方法这样的调用国产,JVM直接调用main方法,所以如果main方法有返回值,即需要JVM接收。但是JVM被设计为不接受任何返回值,所以main方法不能有返回值。
  • **String[]**:类似于C语言中的char**int,Java中数组可以记录数目,所以省去了int类型的参数来表示字符串个数。

Java方法的可变参数

在开发中有时候会出现参数个数不确定的情况,为此J2SE 5.0版本中引入了可变参数的概念

声明可变参数的语法格式如下

1
methodName({paramList},paramType...paramName)
  • methodName:方法名称
  • paramList:方法的固定参数列表
  • paramType:可变参数的类型
  • …:声明可变参数的标识
  • paramName:可变参数名称

注意:可变参数必须定义再参数列表的最后。

下面给出一个案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class StudentTestMethod{
// 定义输出考试学生的人数及姓名的方法
public void print(String...names){
int count = name.length; // 获取总人数
System.out.println("本次参加考试的有" + count + "人,名单如下:");
for(int i = 0;i < names.length;i++){
System.out.println(names[i]);
}
}

public static void main(String[] args){
// TODO Auto-generated method stub
StudentTestMethod student = new StudentTestMethod();
student.print("Liwei","Marry","Sa"); // 传入3个值
student.print("Mali","Lihua");
}
}

Java构造方法

Java的构造方法再创建对象后自动调用。

Java构造方法有以下特点

  • 方法名必须与类名相同
  • 可以有0个、1个或多个参数
  • 没有任何返回值,包括void
  • 默认返回类型就是对象类型本身
  • 只能与new运算符结合使用

值得注意的是,如果为构造方法定义了返回值类型或使用void声明构造方法没有返回值,编译时不会出错,但Java会把这个所谓的构造方法当初普通方法来处理。

由于Java语法上的规定,编写构造方法的时候不需要写返回值,当使用new关键字来调用构造方法时,构造方法返回该类的实例,可以把这个类的实例当初构造器的返回值,一次构造器的返回值是当前类,无需定义返回值类型。大必须注意不要在构造方法里使用return来返回当前类的对象,因为构造方法的返回值是隐式的。

注意:构造方法不能被static、final、synchronized、abstract和native(类似于abstract)修饰。构造方法用于初始化一个新的对象,所以用static修饰没有意义。构造方法不能被子类继承,所以用final和abstract修饰没有意义。多个线程不会同时创建内存地址相同的一个对象,所以用synchronized修饰没有必要。

构造方法语法格式如下:

1
2
3
4
5
6
class calss_name{
public class_name(){} // 默认无参构造方法
public class_name([paramList]){} // 定义构造方法
...
// 类主题
}

在一个类中,与类名相同的方法就是构造方法。

在一个类中定义多个具有不同参数的同名方法,这就是方法的重载。

还需需要注意的是:如果在类中没有定义任何一个狗仔方法,则Java会自动为该类生成一个默认的构造方法。默认的构造方法不包含任何参数,并且方法体为空。

tips:无参数的构造方法也被称为Nullary构造方法。只有比编译程序自动加入的构造方法,才称为默认构造函数。如果自行编写无参数、没有内容的构造函数,就不称为默认构造函数了(只是Nullary构造函数)。虽然只是名词定义,不过认证考试时要区别以下两者的不同。

Java构造器的工作方式和C++一样。但是,要记住所有的Java对象都是在堆中构造的,构造器总是伴随着new操作符一起使用。

1
Worker worker("张三",12);

这条语句在C++中能够正常运行,但在Java中却不行。

Java析构方法

析构方法与构造方法相反,当对象脱离其作用域时(例如对象所在的方法已调用完毕),系统自动执行析构方法。

在Java的Object类中还提供了一个protected类型的finalize()方法,因此任何类都可以覆盖这个方法,在这个方法中进学校释放对象所占有的相关资源的操作。

finalized()方法的特点如下:

  • 垃圾回收器是否会执行该方法以及何时执行该方法都是不确定的。
  • finalized()方法有可能使对象复活,是对象恢复到可触及状态。
  • 垃圾回收器在执行finalized()方法时,如果出现异常,垃圾回收器不会报告异常,程序继续正常运行。
1
2
3
protected void finalized(){
// 对象的清理工作
}

tips:由于finalize()方法的不确定性,所以在程序中可以调用System.gc()或者Runtime.gc()方法提示垃圾回收器尽快执行垃圾回收操作。

Java包(package)

在Java中随着程序架构的扩大,维护类名也变得愈来愈麻烦,为了解决同名问题的产生,Java引入了包(package)机制,提供了类的多层命名空间,用于解决命名冲突、类文件管理等问题。

包的作用有以下三点:

  1. 区分相同名称的类
  2. 能够较好地管理大量的类
  3. 控制访问范围

包定义

定义包的语法格式如下:

1
package 包名;

Java包的命名规则如下:

  • 包名全部由小写字母(多个单词字母也全部小写)。
  • 如果包名包含多个层次,每个层次用”.“分割。
  • 包名一般由倒置的域名开头,比如com.baidu,不要有www。
  • 自定义包不能用java开头。

注意:如果在源文件中没有定义包,那么类、接口、枚举和注释类型文件将会被放进一个无名的包中,也称为默认包。在实际企业开发中,通常不会把类定义在默认包下。

包导入

如果使用不同包中的其他类,需要使用该类的全名(包名+类名),代码如下:

1
example.Test test = new example.Test();

为了简化编程,Java引入了import关键字,import可卡因像某个Java文件中导入指定包层次下的某个类或全部类。import语句位于package语句之后,类定义之前。一个Java源文件只能包含package语句,但可以包含多个import语句。

使用import导入单个类

1
import 包名.类名

使用import导入指定包下的全部类

1
import example.*

其中import语句中的星号(*)只能代表类,不能代表包,表明导入example包下的所有类。

在一些极端的情况下,import语句也帮不了我们,此时只能在源文件中使用类的全名。例如,需要在程序中使用java.sql包下的类,也需要使用java.util包下的类,则可以使用如下两行import语句:

1
2
import java.sql.*;
import java.util.*;

如果接下来在程序中需要使用Date类,则会硬气如下编译错误:

1
2
Test.java:25:对Date的引用不明确,
java.sql中的类java.sql.Date和java.util中的类java.util.Date都匹配

上面的错误提醒:在Test.java文件的第25行使用了Date类,而import语句导入的java.sql和java.util包下都包含了Date类,系统不知道使用哪个包下的Date类。在这种情况下,如果需要指定包下的Date类,则只能使用该类的全名,代码如下:

1
2
// 为了让引用更加明确,即使使用了 import 语句,也还是需要使用类的全名
java.sql.Date d = new java.sql.Date();

系统包

Java中常用的系统包如表8-1所示

说明
java.lang Java 的核心类库,包含运行 Java 程序必不可少的系统类,如基本数据类型、基本数学函数、 字符串处理、异常处理和线程类等,系统默认加载这个包
java.io Java 语言的标准输入/输出类库,如基本输入/输出流、文件输入/输出、过滤输入/输出流等
java.util 包含如处理时间的 Date 类,处理动态数组的 Vector 类,以及 Stack 和 HashTable 类
java.awt 构建图形用户界面(GUI)的类库,低级绘图操作 Graphics 类、图形界面组件和布局管理 (如 Checkbox 类、Container 类、LayoutManger 接口等),以及用户界面交互控制和事 件响应(如 Event 类)
java.awt.image 处理和操纵来自网上的图片的 Java 工具类库
java.wat.peer 很少在程序中直接用到,使得同一个 Java 程序在不同的软硬件平台上运行
java.net 实现网络功能的类库有 Socket 类、ServerSocket 类
java.lang.reflect 提供用于反射对象的工具
java.util.zip 实现文件压缩功能
java.awt.datatransfer 处理数据传输的工具类,包括剪贴板、字符串发送器等
java.sql 实现 JDBC 的类库
java.rmi 提供远程连接与载入的支持
java. security 提供安全性方面的有关支持

表8-1 Java中常用的系统包

Java使用自定义包

包的声明和使用非常简单,下面给出声明和使用一个Java包的案例

  1. 创建一个名为com.dao的包

  2. 向com.dao包中添加一个Student类,该类包含一个返回String类型数组的GetAll()方法。Student类代码如下:

    1
    2
    3
    4
    5
    6
    7
    package com.dao;
    public class Student{
    public static String[] GetAll(){
    String[] nameList = {"Daming","LIuer","Zhangsan","Lisi","Wangwu"};
    return nameList;
    }
    }
  3. 创建com.test包,在该包里创建带main()方法的Test类。

  4. 在main()方法中遍历Student类的GetAll()方法中的元素内容,在遍历前,使用import引入com.dao整个包。完整代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.test;
    import com.dao.Student;
    public class Test{
    public static void main(String[] args){
    System.out.println("学生信息如下:");
    for (String str:Student.GetAll()){
    System.out.println(str);
    }
    }
    }
 Comments
Comment plugin failed to load
Loading comment plugin