Java构造方法、对象创建、继承(4.5-4.6)

 
 

继承

 
 

一、 构造方法

📒定义: 构造方法是一种特殊的、与类同名的方法,专门用于创建对象,完成初始化工作。

 

**📒构造方法的特殊性:** **(1)构造方法的方法名与类名相同。** **(2)构造方法没有返回类型,也不能写void。** **(3)构造方法的主要作用是完成对类对象的初始化工作。** **(4)构造方法一般不能由编程人员显式地直接调用,而是用new来调用。** **(5)在创建一个类的新对象的同时,系统会自动调用该类的构造方法为新对象初始化。** **(6)在Java中,每个类都至少有一个构造方法,如果没有显示地定义构造方法,Java 会自动提供一个缺省的构造方法。**

 
 

1.1 构造方法说明

📒构造方法说明形式如下:
[构造方法修饰符] 方法名([形式参数列表])[throws异常列表] {方法体}

**📒当构造一个类的实例时,编译器主要完成以下3件事情:** (1) 为对象分配内存空间(堆); (2) 按缺省值初始化对象中的实例变量的值; (3) 调用对象的构造方法(可以在构造方法中初始化其他的值); (4) 对象变量都是引用型变量。

 
 

1.2 构造方法的重载

(1) 缺省的构造方法:如果没有定义构造方法,则Java自动提供了一个缺省的构造方法,如下: public Person(){};//对象成员变量的初值按Java规定 (2) 带参数的构造方法:按需要将一些指定的参数传递给构造方法 例如:public Person(String n){Name=n;} **📒定义构造方法注意:**  只要类中显式定义了一个或多个构造方法,而且所有显式定义的构造方法都带参数,那么将失去缺省 构造方法。
1
2
3
4
5
6
7
8
9
10
PersonC(String n ,int a){
name=n;
age=a;
System.out.println("The Person's name is "+name+",age is "+age);}
public static void main(String args[]){
PersonC per1=new PersonC();
PersonC per2=new PersonC("wang li");
PersonC per3=new PersonC("li ming",24);
}
}

 
 

1.3 对象的创建

**1.3.1 📒对象说明:** 对象说明的语法形式为:    类名 对象名; **1.3.2. 📒对象的实例化和初始化:** **对象构造的语法形式为:**    对象名=new 类构造方法名([实参表]); **由new操作符和相应的构造方法完成:** new以类为模板,开辟空间并执行相应的构造方法,完成对象的实例化和初始化,并返回该对象的一个引 用(即该对象所在的内存首地址)

 
 

1.4 this的使用

**1.4.1 📒使用this来访问成员变量及方法** 在方法及构造方法中,可以使用this来访问对象的属性和方法 . **1.4.2. 📒构造方法中,用this调用另一构造方法:** **1.4.3. 📒使用this的注意事项:** (1)通过this不仅可以引用该类中定义的变量和方法,还可以引用该类的父类中定义的成员变量和方法。 (2)在所有的非static方法中,都隐含了一个参数this。而static方法中,不能使用this。
1
2
3
4
5
6
7
8
9
10
1.4.1
Person(String n,int a){
name=n;
age=a;
}
等价于如下:
Person(String name,int age)
{this.name=name;
this.age=age;
}
1
2
3
4
5
6
7
8
9
10
11
12
1.4.2 
Desk(){ Color="";
Length=0;
Width=0;
Height=0;}
Desk(String C,int L,int W,int H)
{Color=C;
Length=L;
Width=W;
Height=H;}
Desk(){
this("",0,0,0);}

 
 

二、 继承

2.1 类继承语法形式

**2.1.1 📒类继承语法形式如下:**   class SubClassName extends SupperClassName extends:继承关键词 SubClassName:新的子类名 SupperClassName:继承的父类名,必须有且只能一个(即每个子类有且只能有一个父类) **2.1.2 📒子类的特性:** (1) 子类拥有其父类的所有属性和方法。但父类中说明为private的属性和方法,子类不可直接访问。 (2) 子类可以对父类的方法覆盖或重载。

 
 

2.2 属性的继承、隐藏和添加

注意:属性叫隐藏,方法叫覆盖
2.2.1 📒属性的继承:
• 子类可以继承父类的所有属性(只要该属性没有private修饰)
• 通过属性的继承,子类不需要把父类属性的定义部分重复定义一遍,这样做的好处是减少程序维护的工作量。
例如:ColorPoint类自动具有Point类的坐标a,b属性

 
2.2.2 📒属性的隐藏:
子类重新定义一个与父类那里继承来的成员变量完全相同的变量,就称作属性的隐藏。

1
2
3
4
5
6
7
 class living
{ public int age; }
class human extends living
{ public int age; //将父类的age隐藏
public static void main(String args[])
{ human h1=new human(); h1.age=20;}
}

 
2.2.3 📒属性的添加:
在定义子类时,加上的新的属性变量,就可以使子类比父类多一些属性

例如:ColorPoint类比父类Point多了一个属性,颜色(color)

 
 

2.3 方法的继承、覆盖、重载和添加

2.3.1 📒方法的继承:
父类的非私有方法也可以被子类自动继承。例如类A具有method()方法,那它的子类B不定义该方法也同样拥有method()。

 
2.3.2 📒方法的覆盖:

方法的覆盖(方法用覆盖这个词修饰,属性用隐藏这个词来修饰)
覆盖:在子类中定义的方法和父类中的方法的首部是一样的,包括方法名、参数列表、返回类型和异常抛出。但方法体的实现改变了。
1.png

例如:

1
2
3
4
5
6
7
8
class A{
……
int method(int x, int y)
throws Exception{
……
}
……
}
1
2
3
4
5
6
7
8
class B extends A{
……
int method(int x, int y) throws //覆盖父类的方法method(int x, int y)
IOException{
……
}
……
}

 
覆盖的效果:当一个B类对象调用method方法时它只会使用B类的method版本,而父类A的method版本被覆盖掉了
在覆盖时要注意以下几点:
(1)覆盖的方法的首部必须要和被覆盖的方法的首部完全匹配,才能达到覆盖的效果;
(2)覆盖的方法的返回值类型必须和被覆盖的方法的返回值类型一致;
(3)覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类;
(4)被覆盖的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖

 
2.3.3 📒方法的重载:
含义:方法名相同,但参数列表不同
通过方法的覆盖,能够修改对象的同名方法的具体实现体,相同的方法在各个子类中实现的版本可以不一样.
       — —实现多个类中多态性的基础之一
重载的效果:父类A的method版本不会被覆盖掉,相当于在子类B中有两个method方法,一个来自父类,一个自己加的,它们之间的参数列表不一样.
注意:
(1)在使用重载时只能通过不同的参数表样式。
(2)不能通过访问权限、返回类型、抛出的异常进行重载;(但是同一类里面的成员方法可以这样重载)
(3)被重载的方法不能为private,否则在其子类中只是新定义了一个方法,并没有对其进行重载。

特别注意:在继承机制中,不允许在子类中降低成员(包括变量和方法)的访问权限(访问权限大小关系是private < 缺省 < protected < public)。即如果一个方法在父类中是protected的,那么在子类中要重载或覆盖该方法时,就不能把该方法改成是缺省或private,否则会出现编译错误。
&emsp;
习题1:分析下列程序输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A
{ public void PrintNumber(int a)
{ System.out.println(++a);
}
}
class B extends A
{ public void PrintNumber(int a,int b)
{ System.out.println(a);
}
public static void main(String args[])
{
B b=new B();
b.PrintNumber(100);
}
}
输出结果:101

&emsp;
习题2:分析下列程序输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A
{ public void PrintNumber(int a)
{ System.out.println(++a);
}
}
class B extends A
{ public void PrintNumber(int a)
{ System.out.println(--a);
}
public static void main(String args[])
{
B b=new B();
b.PrintNumber(100);
}
}
输出结果:99

&emsp;
&emsp;
2.3.4 📒方法的添加:
子类可以新加一些方法,以针对子类实现相应的功能.

&emsp;
&emsp;

2.4 静态成员变量的继承

(1)某类的静态成员为该类及该类的所有子类所共有;
(2)如果子类中新定义的静态成员变量与父类中的某个静态成员变量同名,则这两个静态成员变量相互独立。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(1)
class a
{
public static int b=10; //静态属性
}
class test extends a
{
public static void main(String args[])
{
test.b=20;
System.out.println(a.b);
}
}
输出结果:20
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(2)
class living
{
public static int age; //静态属性
}
class human extends living
{
public static int age; //静态属性
public static void main(String args[])
{
human.age=20;
System.out.println(living.age);
}
}
输出结果:0

&emsp;
&emsp;

2.5 super的使用

super:代表父类对象,在继承中有重要的作用
📒使用情况:
(1) 子类隐藏了超类中的变量或方法,而在程序中又要使用超类中被隐藏的变量或方法时
&emsp;&emsp;格式:super.变量 ; super.方法([参数表])
(2) 在子类的构造方法中引用超类的构造方法时
&emsp;&emsp;格式: super([参数表])
📒注意: 构造方法不能被继承,但是子类的构造方法能确保它的直接父类
和间接父类的构造方法都被调用,但是注意一点:子类构造方法中调用父类构造方法时,调用语句必须放在子类构造方法中的第一句.

&emsp;
&emsp;

1. 📒使用super访问父类的属性和方法
1.png
&emsp;
&emsp;
2. 📒使用super在子类中调用父类的构造方法
2.png

&emsp;
3. 📒使用super的注意事项
(1)通过super不仅可以访问直接父类中定义的属性和方法,还可以访问间接父类中定义的属性和方法。
(2)由于它指的是父类对象,所以super不能在static环境中使用,包括类变量、类方法和static语句块。
(3)使用super不能访问本类定义的属性和方法。
(4)在构造方法中使用super时,super语句必须放在第一句
(5)在子类的构造方法中,super可以不明确使用,也可以明确使用。

&emsp;
4. 📒明确使用super调用父类构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Point{
int x=0;
Point(int x){
this.x=x;
}


class MapPoint extends Point{
MapPoint(){
super(3); //明确使用
 System.out.println("MapPoint()");
}
public static void main(String[] args){
  new MapPoint();
  }


&emsp;
&emsp;
5. 📒不明确使用super调用父类构造方法
&emsp;当父类重载自己的构造函数时,在子类的构造函数中要明确写出到底是使用父类的哪一个构造函数!!!否则编译器不知道在子类中到底是调用父类的哪一个构造方法!

如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point{
int x=0;
Point(int x){
this.x=x;
}


class MapPoint extends Point{
MapPoint(){
 System.out.println("MapPoint()");
  }
public static void main(String[] args){
  new MapPoint();
  }

//编译会出错

答案:因为子类中没有明确使用super,系统会自动在构造方法中加上super(),来调用直接父类的不带参数的构造方法,由于Point类中没有定义不带参数的构造方法,所以编译通不过。以上代码相当于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point{
int x=0;
Point(int x){
this.x=x;
}


class MapPoint extends Point{
MapPoint(){
 super(); //此处不能缺省
 System.out.println("MapPoint()");
  }
public static void main(String[] args){
  new MapPoint();
  }
}

&emsp;
解决方法:
(1)在MapPoint的构造方法中,明确使用super来调用父类已有的构造方法,如super(3);
(2)在Point中加入一个不带参数的构造方法,如Point{}      
(3)去掉Point中全部的构造方法,则编译器会自动加入一个不带参数的构造方法,称为缺省构造方法。

&emsp;
6. 📒构造方法的调用顺序
一个复杂对象的构造方法调用顺序如下:

(1)首先调用父类的构造方法。这个步骤会反复递归,使继承阶层的根源最先被构建,然后是次一层的子类,直至最末一层子类为止;(即先调用最根部的父类,然后依次调用该父类的子类的子类的……..)
(2)根据各个成员的声明顺序,执行成员变量的初始化赋值;
(3)执行该构造方法中的各语句

&emsp;
&emsp;

三、 抽象类与抽象方法abstract

类似于它是一个模板!!!

(1)abstract关键字修饰的类和方法 (2)抽象类不能创建任何对象,抽象类必须产生其子类,由子类创建对象。 (3)抽象类中可以包含抽象方法,也可以不包含抽象方法,但如果类中的某一方法是抽象的,整个类就必须被说 明成抽象的。 (4)抽象方法在子类中必须被实现,否则子类仍是抽象的。 (5)抽象类不是可有可无的。 (6)(类、成员方法、成员属性)访问权限修饰符为abstract。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[例4-17]  抽象类举例
abstract class Shape
{ abstract double area();
abstract void draw(); }
//上述方法对一般图形无法定义其实现
class Rectangle extends Shape {
int width;
int length;
public double area() {
//矩型类实现了Shape类的抽象方法area()
return width*length;
}
public void draw () { … …
//矩型类实现了Shape类的抽象方法draw(),详细可由自己完成}
}

&emsp;
&emsp;

四、 final类和final方法

4.1 final类

如果一个类被final修饰符所修饰和限定,说明这个类不能被继承,即不可能有子类——最终类 例如:*java.lang.System* **📒作用:** final修饰符通常是出于安全的目的而使用的,因为不能继承final类,人们就不能重载或覆盖它的任何方法。 如果允许一个类被继承,其允许被重载或覆盖的方法可能会被改写。保证某个特定的方法在类层次关系上 的某层以后只有一个定义,这对于那些安全性非常关键的类是非常必要的。

&emsp;
&emsp;

4.2 final方法

final修饰符所修饰的方法,是不能被子类所覆盖的方法。 📒**作用:**固定了这个方法所对应的具体操作,可以防止子类对父类关键 方法的错误的重定义,保证了程序的安全性和正确性。 📒**注意:**所有已被private修饰符限定为私有的方法,以及所有包含在final类中的方法,都被默认为是 final的。 因为这些方法不可能被子类所继承,所以不可能被重载,自然都是最终的方法。

&emsp;
&emsp;
&emsp;
&emsp;