0%

Java - 类与对象(一)

Core Java 也是厚得不行……

参考资料

《Java核心技术 卷1(第十一版)》

《Java基础核心总结》

菜鸟教程


类与对象

对象变量与对象

对象变量并没有实际包含一个对象,而只是引用一个对象。

在Java中,任何对象变量的值都是对存储在另外一个地方的某个对象的引用new操作符的返回值也是一个引用。

1
Date deadline = new Date();

上述语句包含两个部分,new Date()构造了一个Date类型的对象,该表达式的返回值是对这个新创建对象的引用,这个引用存储在变量deadline中。

可以显式地将对象变量设置为null表示不引用任何对象。

可以把Java中的对象变量看作类似于C++中的对象指针

属性

类最基本的要素是属性和方法。属性又称为字段

方法

方法参数

程序设计语言中,将参数传递给方法(或函数)的方式主要有按值调用按引用调用两种。

Java总是采用按值调用(无论是对基本类型还是对象,或者说,对象引用是按值传递的)。

方法能对参数进行的操作:

  • 方法不能修改基本数据类型的参数(int, double, boolean,…, 因为没有引用参数&)
  • 方法可以改变对象的状态
  • 方法不能改变对象参数的引用情况(不能让一个对象参数引用一个新的对象)

更改器方法和访问器方法

可以理解为C++中的一般的成员方法和const成员方法。在Java语言中,两者在语法上没有明显区别。


类的声明和定义

构造器方法/构造函数

类似C++中类的构造函数,构造器与类同名。不能对已经存在的对象重复调用构造器。

每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认的无参构造方法。如果你已经定义了别的构造方法,JVM就不再提供这个默认构造器(与C++类似)。

默认构造方法的访问修饰符和类的访问修饰符相同(类为 public,构造函数也为 public;类改为 protected,构造函数也改为 protected)。

构造器的特点(除了最后一个,其他跟C++构造函数没啥区别):

  • 与类同名
  • 构造器可以有多个,构造器的参数也可以有多个
  • 构造器没有返回值和参数类型
  • 所有的Java对象都在堆中构造,构造器总是搭配 new 运算符来调用

在 Java 中,普通成员方法可以与构造方法同名,在构造实例时,new 操作符后面调用类的构造方法,.运算符优先选择同名的普通方法。

类方法的定义

与C++不同,在Java中,所有的方法都必须在类的内部定义。在C++中,如果在类的内部定义方法,该方法会自动成为内联(inline)方法。Java中则不然,是否将某个方法设置为内联方法是Java虚拟机的任务。


方法的重载和重写

方法的重载

方法重载也类似于C++。Java允许重载任何方法。重载的条件包括:

  • 方法名称必须相同
  • 参数列表必须不同,方法返回类型、修饰符的有无和类型可相同可不相同
  • 仅仅返回类型不同不足以成为方法的重载
  • 方法的重载发生在编译时

方法的重写

方法重载的描述是同一类中的,重写是子类对父类的允许访问的方法的实现过程进行重新编写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A {
public void func1() {
System.out.println("类A的函数func1");
}
}

class B extends A {
public void func1() {
System.out.println("类B的函数func1");
}

public void func2() {
System.out.println("类B的函数func2");
}
}

上面的代码中,子类B中的 func1 方法是对父类A同名方法的重写。

父类对象变量引用子类对象时执行被重写的方法时,执行的是子类中的方法。

1
2
3
4
5
6
7
public class test {

public static void main(String[] args) {
A a = new B();
a.func1(); //执行的是B中的func1
}
}

(好像C++……)

当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

1
2
3
4
5
6
class B extends A {
public void func1() {
// System.out.println("类B的函数func1");
super.func1(); //A::func1()
}
}

对于static修饰的方法不能使用super。

重写的原则为:

  • 重写的方法除内容外应与父类保持一致,包括方法名称、参数列表
  • 重写的方法的返回值类型一般与父类方法相同;Java 7之后返回值类型可以不相同,但是必须是父类返回值类型的派生类
  • 父类的方法只能被子类重写
  • 子类中的重写方法访问权限不能低于父类中同名方法的访问权限
  • 声明为 final 或者 static 的方法不能被重写,但是静态方法可以被多次声明:子类定义与父类中同名的静态函数称为隐藏,此时类实例只会调用自己类定义的静态方法,即静态方法不具有多态性
  • 构造方法不能被重写
  • 重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。

类的初始化

除了调用对于构造方法之外,Java中可以直接在类定义中就给任何属性赋初值或者通过方法赋初值(在C++中就得依靠相应的构造函数),如:

1
2
3
4
5
6
7
8
9
10
class A 
{
private static a;
private int b = 10; //直接赋值
private int c = assign(); //通过方法赋值
...
private static int assign() {
return a++;
}
}

对于final类型属性,直接在定义时初始化,或者声明后在所有构造方法中都记得赋初值就行了。

默认构造器的初始化

JVM提供的默认无参构造方法会把基础数据成员的值都设置为00.0false,其他对象类型则会初始化为null

但是方法中的局部变量必须明确地赋初值。

静态属性的初始化

静态属性可以通过直接在类定义时赋初值完成初始化。

如果静态属性的初始化需要比较复杂的代码,可以使用静态的初始化块:将初始化代码放在一个块中,并标记关键字static。

1
2
3
4
5
6
7
8
//静态初始化块示例
private static int nextId;
...
static
{
var generater = new Random();
nextId = generater.nextInt(10000);
}

在类第一次加载时,会进行静态属性的初始化。

无基类时的初始化顺序

静态属性初始化→静态方法初始化→普通属性初始化→普通方法初始化→构造函数其他部分


对象的销毁

虽然Java是基于C++的,但是Java中不支持析构方法,不需要手动管理对象的销毁,当某个对象的生命周期结束时,JVM会负责其销毁。

某些对象会使用内存以外的资源(比如文件),在这种情况下,资源的及时回收和再利用显得比较重要。如果一个资源一旦使用完就需要立即关闭,那么应当提供一个 close 方法来完成必要的清理工作。

引入Java中对象作用域的概念。

对象作用域

作用域(scope)决定了其内部定义的变量的可见性和生命周期。一个变量的生命周期结束于其作用域(一般用 {} 决定位置)终点处。

方法的参数范围涵盖整个方法,参数实际上是一个局部变量

循环体内声明的变量其适用范围是从它声明到循环体结束(比如for循环里面的变量)。

Java不允许在嵌套的作用域中定义同名的局部变量,但是可以在一个方法里,不同的非嵌套块中多次声明一个具有相同的名称局部变量

1
2
3
4
5
6
{
int x = 1;
{
int x = 2; //C and C++ is ok
}
}

this和super

this“指针”

this表示对象本身,即指向对象本身的“指针”

this.xxx用来引用成员或者指向对象自己。

有时候类方法的参数于与成员名字重名时,就用 this. 来区分。

super

super可以理解为指向离类自己继承关系最近的一个父类的指针。不过,super本质上是Java的一个关键字。

super指向当前的父类,super.xxx可以引用父类成员或者调用父类的方法。

this和super也可以用来引用构造方法,格式为 this(参数)super(参数),分别用来调用本类中其他构造方法以及父类中的构造方法(C++里一个构造函数不能调用同类的另一个构造函数)。

注意

  • this(参数)super(参数)要放在构造函数的第一行
  • 在一个构造函数里,这种调用方法只能使用一次,且this 和 super 不能同时出现在一个构造函数里面
  • this() 和 super() 都指的是对象,所以均不可以在 static 环境中使用,包括static 变量、static 方法、static 语句块。
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
class A {
public A(){
System.out.println("父类无参构造函数A()");
}

public A(int x){
System.out.println("父类构造函数A(int x)");
}
}

class B {
public B(){
super(); //调用了A()
System.out.println("子类无参构造函数B()");
}

public B(int x){
super(x); //调用了A(int x)
System.out.println("子类构造函数B(int x)");
}

public B(int x, int y){
this(x); //调用了B(int x)以及包含的A(int x)
System.out.println("子类构造函数B(int x, int y)");
}
}

访问修饰符

Java中的访问控制符有default(什么也不写)、publicprivateprotected四种。它们都可以用来修饰类的成员,defaultpublic还可以修饰类(包括外部类)和接口。

default

对同一个包内的类是可见的,变量默认为public static final,方法成员默认为public

public

对于所有类都是可见的(所以main方法是public的),类所有的公有方法和变量都能被其子类继承。

如果几个相互访问的 public 类分布在不同的包中,则需要导入相应 public 类所在的包。

private

类成员仅限其所属类访问。声明为私有访问类型的变量只能通过类中公共的 getter 方法被外部类访问。

protected

子类与基类在同一包中时,被声明为 protected 的变量、方法和构造方法能被同一个包中的子类以及任何其他类访问;

子类与基类不在同一包中,那么在子类中,子类实例可以访问其从基类继承而来的 protected 方法,而不能访问基类实例的protected方法。

访问控制与继承

访问控制的继承遵循以下规则

  • 父类中声明为public的方法,在子类中必须声明为public
  • 父类中声明为protected的方法,在子类中声明为public或者protected
  • 父类中声明为private的方法,子类不可继承
-------------本文结束感谢您的阅读-------------