Java面向对象编程之类与对象总结(下)

前言

本文主要总结了代码块和内部类的相关知识,解释了覆写,简要说明了final关键字的作用以及向上/向下转型和继承,多态在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
34
class HelloA {
//构造方法
public HelloA(){
System.out.println("1. Hello A!父类构造方法");
}
//非静态代码块
{
System.out.println("2. i'm A class.父类非静态代码块");
}
//静态代码块
static{
System.out.println("3. static A 父类静态代码块");
}
}
public class HelloB extends HelloA {
//构造方法
public HelloB(){
System.out.println("4.Hello B! 构造方法");
}
//非静态代码块
{
System.out.println("5.i'm B class.非静态代码块");
}
//静态代码块
static{
System.out.println("6.static B 静态代码块");
}
public static void main(String[] args) {
System.out.println("7.---start---");
new HelloB();
new HelloB();
System.out.println("8.---end---");
}
}

执行结果: 3,6,7,2,1,5,4,2,1,5,4,8
总结: 静态代码块>非静态代码块>构造方法>普通代码块
先去执行父类的静态代码块
先去执行父类的构造方法
每次new一个类的对象的时候非静态代码块都会加载一遍,而静态代码块在整个程序运行的过程中只能加载一次

代码块

代码块: 使用 { } 定义的一段代码
根据修饰代码块的关键字以及代码块的位置划分代码块有四种:
1.普通代码块
定义在方法中的代码块

1
2
3
4
5
6
7
8
9
10
11
public class Main{

public static void main(String[] args) {
{
int i = 500;
System.out.println("i= "+i);
}
int i = 200;
System.out.println("i=" + i);
}
}

普通代码块具有隔离的作用,在普通代码块中的变量i和外部的i不是一个i,普通代码块的作用就是为了避免在一个较长的方法中变量名的重复.
在这里需要注意一个问题:如果想要使用相同变量名称要把普通代码块放在方法中第一次出现变量名的前面! 放在后面的话会出现命名重复的错误
2.构造块: 定义再类中的代码块(不加修饰符)

1
2
3
4
5
6
7
8
9
10
11
12
class Persion{
private int age;
private String name;

{
System.out.println("Persion的构造代码块");
}

public Persion(){
System.out.println("构造方法");
}
}

当new一个Persion对象的时候,会先执行构造代码块
务必记住:构造代码块的执行优先于构造方法!!每产生一个新的对象就执行一次构造代码块!!
3.静态代码块: 别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
    27
    28
    29
    30
    31
    32
    class Persion{
    private int age;
    private String name;

    static{
    System.out.println("Persion类的静态代码块");
    }

    {
    System.out.println("Persion类的构造块");
    }

    public Persion(int age, String name) {
    thi.age = age;
    this.name = name;
    System.out.println("这里是构造方法");
    }


    public void getInfo(){
    System.out.println("姓名: "+this.age+" 姓名: "+this.name);
    }
    }

    public class Main{
    public static void main(String[] args){
    System.out.println("----------开始-------------");
    Persion persion = new Persion(18, "张三");
    Persion persion2 = new Persion(19,"李四");
    System.out.println("----------结束-------------");
    }
    }

执行结果 : 静态代码块> 构造块 >构造方法
所以不在主类中的静态代码块,在被创建对象的时候优先执行静态代码块,然后才是构造代码块,其次才是构造方法
无论产生多少实例对象,静态代码块都是只执行一次
非主类中的静态块是在main方法开始之后创建对象之后执行的

  • 在主类中的静态代码块
    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
    class Persion{
    private int age;
    private String name;

    static{
    System.out.println("Persion类的静态代码块");
    }

    {
    System.out.println("Persion类的构造块");
    }

    public Persion(int age, String name) {
    thi.age = age;
    this.name = name;
    System.out.println("这里是构造方法");
    }


    public void getInfo(){
    System.out.println("姓名: "+this.age+" 姓名: "+this.name);
    }
    }

    public class Main{
    static{
    System.out.println("在主类中的静态方法!");
    }
    public static void main(String[] args){
    System.out.println("----------开始-------------");
    Persion persion = new Persion(18, "张三");
    Persion persion2 = new Persion(19,"李四");
    System.out.println("----------结束-------------");
    }
    }

在主类中的静态方法优先于所有的代码,他是在类加载的时候就执行的! 所以 他优先于主方法执行,是最先打印的!!

内部类的定义与使用

所谓内部类就是在一个类中进行其他类的嵌套操作

内部类的作用:

1.内部类可以很方便的拿自己外部类的属性,包括私有属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Outter {

private int outterNum=19;
private String outterName="Lisa";

class Inner{
public void getOutterMesg(){
System.out.println(outterName+" :"+outterNum); //内部类可以直接使用外部类的属性
}
}

public void getInner(){
Inner inner = new Inner();
inner.getOutterMesg();
}

public static void main(String[] args) {
Outter outter = new Outter();
outter.getInner();
}
}

运行结果: Lisa :19
内部类可以直接访问外部类的属性,所以我们从内部类中可以直接获取到外部类的私有信息,然后再在外部类创建一个获得内部类对象并调用内部类方法以此达到不使用setter或getter方法就可以获得外部类私有属性的效果

如果不使用内部类,代码将复杂很多,如下:

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
public class NoOutter {
public static void main(String[] args) {
Outter1 outter1 = new Outter1();
outter1.fun();
}
}

class Outter1{
private int outterNum=19;
private String outterName="Lisa";

public void fun(){
Inner1 inner1 = new Inner1(this);
inner1.print();
}

public String getMsg(){
return this.outterName+" :"+this.outterNum;
}
}

class Inner1{
private Outter1 outter1;

public Inner1(Outter1 outter1) {
this.outter1 = outter1;
}

public void print(){
System.out.println(outter1.getMsg());
}
}

运行结果相同
这里只是说明内部类的一个功能,他能够很方便的访问外部类的所有属性及方法. 当然我们平时写完一个类想要访问他的私有属性,还是使用setter和getter方法较好!!
2. 内部类可以对同一包中其他类隐藏起来
我们在一个类中写一个内部类,使用者只能够看到外部类而看不到外部类中的内部类,这就起到了一个很好的封装隐藏作用
3.内部类可以实现Java单继承的缺陷
具体做法: 我们先定义两个类,然后在一个外部类中分别定义两个内部类,让两个内部类分别继承A,B,这样就可以调用A,B类中的方法,然后在外部类中创建这个两个内部类的实例对象,便能够调用内部类的方法以此达到同时调用A,B方法的目的,代码如下:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class MoreExends {

public static void main(String[] args) {
OutterAB outterAB = new OutterAB();
System.out.println(outterAB.readA());
System.out.println(outterAB.readB());

}

}


class OutterAB{

class Inner1 extends A{
public String methed1(){
return super.getName();
}
}

class Inner2 extends B{
public int methed2(){
return super.getAge();
}
}

public String readA(){
return new Inner1().methed1();
}

public int readB(){
return new Inner2().methed2();
}

}

class A{
private String name = "A类的私有属性";

public String getName(){
return this.name;
}
}

class B{
private int age = 20;

public int getAge(){
return this.age;
}
}

4.当我们想要实现一个回调函数但是又不想写大量的代码,那么也可以考虑内部类

内部类和外部类的关系

1.内部类可以直接访问外部类的元素,包括私有属性,但是外部类不可以直接访问内部类的元素
一个类的私有属性外部类只能通过getter或setter方法进行访问或者修改,内部类也可以实现这种效果
2.外部类可以通过内部类引用间接访问内部类
3.对于非静态内部类,内部类的创建是依赖于外部类的实例对象的,在没有外部类实例对象之前是无法创建内部类的
4.内部类是一个相对独立的个体,他和外部类并不是is-a关系
代码演示内部类和外部类的如上四个关系

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
public class Outter {

private int outterNum;
private String outterName;

class Inner{

//如果想想从内部类中修改外部类属性,必须在内部类构造方法中进行
public Inner(){
Outter.this.outterName = "给外部类属性取名字";
Outter.this.outterNum = 20;
}

public void getOutterMesg(){
System.out.println(outterName+" :"+outterNum); //内部类可以直接使用外部类的属性
}
}

public void getInner(){
//在外部类内部创建内部类
Inner inner = new Inner();
inner.getOutterMesg(); //外部类通过内部类引用间接访问内部类
}

public static void main(String[] args) {
Outter outter = new Outter();
outter.getInner();
//在外部类外部创建内部类
Outter.Inner in = new Outter().new Inner();
in.getOutterMesg();
}
}

至此,我们已经对内部类有了一定了解,但是,内部类还有不同的类型,一般,内部类分为四种: 成员内部类,静态内部类,方法内部类,匿名内部类,我们上面一直说的都是成员内部类!!
1.成员内部类
成员内部类需要注意两点:
① 成员内部类中不能有静态变量或者静态方法
② 成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类
2.静态内部类
static修饰的内部类称之为静态内部类.静态内部类有如下特点:
1.静态内部类不能使用外部类中的非静态属性;
2.静态内部类对象的创建不依赖于外部类的对象,所以在外部类的外部可以不需要外部类的对象便可以直接创建.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class StaticInner {

public static void main(String[] args) {
//可以直接创建内部类对象而不需要依赖于外部类对象
Outer.Iner in = new Outer.Iner();
in.method();
}
}

class Outer{
private static String name = "外部类name";
private int age = 20;

static class Iner{
public void method(){
System.out.println(name);//只能使用外部类中的静态属性
}
}
}

3.方法内部类
方法内部类与成员内部类用法基本相似,但是,方法内部类的作用域要小的多,方法内部类只能在该方法内使用.出了该方法就会失效.举例:

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
public class StaticInner {

public static void main(String[] args) {
Outer outer = new Outer();
outer.method();
}
}

class Outer{
private static String name = "外部类name";
private int age = 20;

//静态内部内
// static class Iner{
// public void method(){
// System.out.println(name);
// }
// }

public void method(){
class Inner{
private int num;
public Inner(int num){
this.num = num;
}

public void method(){
this.num++;
System.out.println(num);
}
}
new Inner(20).method();
}
}

此处方法内部类Inner只能在method方法中使用
使用方法内部类需要注意的问题:
1.方法内部类不能加任何权限修饰符,包括public,protected,private
2.方法内部类只有创建他的方法可以访问,出了这个方法就会无法使用
3.方法内部类想要使用方法形参,该形参必须用final修饰
4.匿名内部类
匿名内部类其实就是一个没有名字的方法内部类,他符合所有方法内部类的约束条件
但是,匿名内部类有以下特殊要求:
1.匿名内部类必须实现一个接口或者抽象类
2.匿名内部类没有构造方法,因为他本身就没有名字
3.匿名内部类中不能有静态成员或者方法
4.匿名内部类没有访问权限控制符
5.与局部内部相同匿名内部类也可以引用方法形参。此形参也必须声明为 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
public class StaticInner {

public static void main(String[] args) {
Outer outer = new Outer();
outer.method(25);
}
}

interface MyInterface{
void test();
}

class Outer{
private static String name = "外部类name";
private int age = 20;

//匿名内部类:匿名内部类其实就是没有名称的方法内部类

public void method(int num){

new MyInterface(){
@Override
public void test() {
System.out.println("匿名内部类实现接口"+num);
}

}.test();
}
}

匿名内部类: 是一个实现了一个接口或者抽象方法的方法内部类!!!
内部类小结: 破坏了程序的结构; 方便外部类的私有属性的访问; 如果发现类名称上出现了.应该及时想到内部类

继承

在一些书上,父类也被叫做超类,子类被叫做派生类

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
public class ExtendsTest {

public static void main(String[] args) {
Student student = new Student();
student.setAge(18);
student.setName("笨蛋");
System.out.println(student.getName()+" :"+student.getAge());
}
}

class Persion{
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

class Student extends Persion{ }

以上便是一个最简单的继承关系,挡子类继承了父类之后,可以直接使用父类的方法,并且具有父类的属性,从而实现代码的复用.子类最低也能保持和父类相同的功能,并且能够进行方法的扩充

1
2
3
4
5
6
7
8
9
10
11
class Student extends Persion{
private String school;

public String getSchool() {
return school;
}

public void setSchool(String school) {
this.school = school;
}
}

对子类进行扩充
继承的主要作用是对类进行扩充以及代码的重用

继承的限制

  • 子类在实例化对象之前一定会先进行父类的实例化对象.默认先调用父类的构造方法后调用子类的构造方法. 在子类的构造方法中如果没有显式的使用super()去调用父类的构造方法,那么jvm会自动的去调用父类的无参的默认构造方法.如果父类不存在无参的默认构造方法,那么我们必须要自己使用super去调用我们想要调用的父类构造方法
    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
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    public class ExtendsTest {

    public static void main(String[] args) {
    Student student = new Student();
    System.out.println(student.getName()+" :"+student.getAge()+" "+student.getSchool());
    }
    }

    class Persion{
    private String name;
    private int age;

    //父类只有这一个带有参数的构造方法
    public Persion(String name, int age){
    System.out.println("这里是父类的构造方法");
    this.age =age;
    this.name = name;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }
    }

    class Student extends Persion{
    private String school;

    public Student(){
    super("占山",12);//必须要显式的指定调用父类的某一个构造方法
    System.out.println("这里是子类的构造方法");
    }

    public Student(String school){
    super("lily",20);
    this.school = school;
    }

    public String getSchool() {
    return school;
    }

    public void setSchool(String school) {
    this.school = school;
    }
    }

当父类存在无参的构造方法,我们没有在子类构造方法中调用他时,其实是相当于隐藏了 super() 实际上还是执行了这一步的!!!

  • Java只允许单继承,不支持多继承,但是支持多层继承
    1
    2
    3
    class A{}
    class B extends A{}
    class C extends B{}

C继承了B,而B继承了A,这样C就相当于继承了A和B,他能够获得A和B的中间操作

  • 在继承时,子类继承了父类的所有结构(包含私有属性,构造方法,普通方法)但是这个时候需要注意,所有非私有属性属于显式继承,可以之间调用,所有的私有属性属于阴式继承,必须通过一些其他方法进行访问(例如父类中的私有属性值我们需要通过父类定义的setter,getter方法来进行访问)
    继承总结:继承的语法以及继承的目的(代码的复用以及属性的扩充);继承只允许单继承,但是可以多层继承;子类对象的实例化过程:子类实例化之前一定会先进行父类对象的实例化,即调用子类构造器之前一定会先调用父类的构造器

    覆写

    覆写:子类定义了和父类相同的方法或属性

    方法的覆写

    子类定义了和父类方法名,参数类型及个数都相同的方法.被覆写不能拥有比父类更严格的访问控制权限
    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
    36
    37
    38
    39
    40
    41
    public class ExtendsTest {

    public static void main(String[] args) {
    Student student = new Student();
    student.print();
    System.out.println(student.getName()+" :"+student.getAge()+" "+student.getSchool());
    }
    }

    class Persion{
    private String name;
    private int age;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public void print(){
    System.out.println("父类中的print方法");
    }
    }

    class Student extends Persion{

    //方法覆写
    public void print(){
    System.out.println("子类中的print方法");
    }
    }

覆写需要注意
当前使用对象是那个类new来的.如果对象调用的方法被覆写过,那么这个方法调用的一定是被覆写过的方法
一定注意被覆写方法不能够拥有比父类还严格的访问控制权限,也就是说子类覆写的方法的权限要比父类大
如果父类的方法访问权限修饰符是private,那么子类覆写时使用public对吗?
答案是不对的!!! 既然父类已经使用private修饰了自己的方法,那么就表表名他不想要其他类的成员随意看到或者使用这个方法,这个时候子类即使创建了和父类此方法相同名称,参数类型,个数的方法,这也不叫做覆写!! 只是在子类中的另一个与父类无关的新方法而已!!!
在说重载时说过为了编码规范,重载的方法返回值必须相同.在覆写时,方法的返回值可以不相同,但是返回值最少也得是父类返回值的派生类型!!!

属性的覆写

由于一般属性都是private修饰的,所以子类覆写父类的属性实际上是没有什么意义的,因为他们都是互相看不到对方的属性

super关键字

在前面讲构造方法时说到子类可以通过super()来调用父类的某一个构造方法.
在覆写中,子类可以使用super.方法名来明确指定调用父类中的某一个方法. 子类也可以使用super.属性名来调用父类的某一个属性

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
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
69
70
71
72
73
74
public class ExtendsTest {

public static void main(String[] args) {
Student student = new Student();
System.out.println(student.msg);
student.getMsg();
}
}

class Persion{
private String name;
private int age;
public String msg = "这里是父类的信息";

public Persion(String name, int age){
System.out.println("这里是父类的构造方法");
this.age =age;
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public void print(){
System.out.println("父类中的print方法");
}
}

class Student extends Persion{
private String school;
public String msg = "这里是子类的信息";

//在这里直接调用父类的属性
public void getMsg(){
System.out.println(super.msg);
}

public Student(){
super("占山",12);
System.out.println("这里是子类的构造方法");
}

public Student(String school){
super("lily",20);
this.school = school;
}

public String getSchool() {
return school;
}

public void setSchool(String school) {
this.school = school;
}

public void print(){
//在这里直接调用父类的方法
super.print();
System.out.println("子类中的print方法");
}
}

需要注意的是,当前使用super调用方法,只能是在子类中使用super调用父类中的方法或者属性

final关键字

final关键字 可以修饰 类, 方法, 属性

final修饰类

**final修饰类,则这个类不能够被继承(即该类没有子类,String类就是被final修饰的,所以我们不能对String进行修改),且被final修饰过后的类中所有的方法都是默认被final修饰的

final修饰方法

final修饰的方法不能被覆写

final修饰属性

final不论是修饰变量还是属性,一经修饰那么这个属性就是一个常量值,是不能被更改的,且常量必须在声明的时候赋值
定义全局常量的时候(public static final)变量名称全部使用大写,中间以 _ 隔开.

1
public static final MAX_VALUE = 200;

向上/向下转型

向上转型

向上转型的对象可以调用父类中的所有方法,但是如果调用的父类中的方法被子类所覆写,那么调用的就是子类中的这个覆写过的方法.(即观察这个对象到底是被谁new出来的),需要注意的是,此对象是不能够调用自己所在类(被谁new)的方法的,因为他现在的类型已经是父类的那个类型了,如果想要调用自己类的方法,则需要进行向下转型

1
2
3
//Persion是Student的父类,这样的就叫做向上转型
Persion persion = new Student();
//此时persion对象能够调用父类中的所有方法,但是,如果父类中的方法被他的子类覆写了,那么调用的就是子类覆写过的方法,因为创造这个类的构造器还是Student();persion是不能够调用student中的方法的,因为他现在的类型已经是Persion了,如果想要调用Student中的方法就需要进行向下转型

向上转型的核心功能:统一操作数

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
class Persion{
public void print(){
System.out.print("1.父类中的print");
}
}

class Student extends Persion{
public void print(){
System.out,print("2.Student中的print");
}
}

class Worker extends Persion{
public void print(){
System.out.print(){
System.out.print("3.Worker中的printf");
}
}
}

public class Main{

public static void printf(Persion per){
per.print();
}

public static void main(String[] args){
printf(new Student());
printf(new Woker));
}
}

如上例子,我们创建了三个类,其中两个都是继承自Persion,每个类都有自己print方法,我们创建一个方法便可以接收Persion的所有子类实例对象.分别打印对应的方法.
以此达到了参数统一的目的

向下转型

向下转型是指将对象的类型转换为子类的类型.

1
2
3
4
5
6
//此时persion只能够调用父类中定义好的方法,他不能够调用子类中的方法
Persion persion = new Student();
persion.perinFa();
//向下转型
Student stu = (Student)persion;
//此时stu对象就能够调用子类中定义的方法了

为什么要有向下转型? : 当你需要子类扩充操作的时候就要采用向下转型
转型一定要注意: 只能是构造器为子类,对象类型为父类,然后进行的是向上转型或者向下转型,并不是每种转型都是对的

1
2
3
4
//错误转型示范
Persion persion = new Persion();
Student stu = (Student)persion;
//父类是不知道自己还有子类的 !! 这种转型是错误的

如果想要发生向下的操作,一定要先有向上转型的过程
所以,向下转型是存在隐患的,为了安全的进行转型,我们一般都要使用instanceof类

instanceof

instanceof是用来判断等号左边的对象是否是等号右边类的实例化对象!!
如果构造器使用的是子类的构造器,那么这个对象既是子类的实例化对象也是父类的实例化对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ExtendsTest {

private static int x =100;

public static void main(String[] args) {
Persion student = new Student();

System.out.println(student instanceof Persion); // true
System.out.println(student instanceof Student); //true

if(student instanceof Student){
//判断是该类的实例化对象后进行向下转型
Student stu = (Student)student;
stu.printSon();
}
}
}

多态

多态性体现在两个方面

  • 方法的多态性: 重载,覆写
  • 对象的多态性: 向上/向下转型
    1. 向上转型可以实现接收参数的统一;  
    2. 向下转型可以实现子类扩充方法的调用(一般不操作向下转型有安全隐患)  
    3.两个没有关系的类是不能进行转型的,否则一定会出错