java类的实现

来源:互联网 发布:倩女幽魂手游mac版 编辑:程序博客网 时间:2024/06/06 07:20

(有不妥的地方还请亲们指教)

一个类中可能有方法(构造器,以及某些特殊的方法(toString finalize等))、字段(成员对象)、代码快构成。

一、方法:

1)方法重载:方法重载描述在一个类中同一个行为不同的实现的现象。(比如洗车、洗狗、洗衣服 都是洗 但是洗的内容不一样,但都是人的行为)

1、区分重载的方法:

     每个重载的方法都必须有一个独一无二的参数类型列表,甚至参数顺序的不同也足以区分这两个方法,不过,一般情况下别这么做,因为这会使得代码难以维护。Java中不会存在多个参数的重载函数与调用地点配对的问题,因为编译器强制严格配对。

为什么不用返回类型来判断重载方法?

     只要编译器可以根据语境明确判断出语义,比如在int x = f()中,那么的确可以根据此区分重载方法,但是,我们一般使用的是方法的“副作用”,并不是确切的使用返回值,所以不能通过返回值来区分重载,也就是说返回值并不是主要因素,只要方法名或者参数列表不同就可以 其他无所谓。

2、涉及基本类型的重载:

当传入的实际参数小于重载方法声明的形式参数:

基本类型要从一个小类型自动提升到一个较大的类型。

Byte--->short-------> int ----->long ---->float------->double

              Char-------|

当传入的实际参数大于重载方法声明的形式参数:强制类型转化。

  最后想说的是,重载的方法编译后是 invokevirtual的,也就是说重载方法是后期绑定的。

2)构造器:

      创建对象时,如果有构造器,java必须确保用户在使用对象之前自动调用构造器,确保初始化的进行。对构造器如何命名?一所选取的名字不能与类成员名字相同,第二,必须让编译器知道调用那个构造器。所以让构造器的名字与类名相同。这显然违背“方法名首字母小写”。构造器是一种特殊的方法,构造器分为了无参构造器和有参构造器,没有返回类型,如果有返回类型势必要让编译器知道如何处理这个类型。用构造器初始化比起在定义时初始化域可以提高灵活性。

默认构造器:

      如果你写的类中没有构造器,则编译器会自动帮你创建一个默认构造器,同时,也可以手动的创建一个默认构造器。如果没有它的话,就没有方法可调用,就无法创建对象。 如果已经定义了一个编译器就不会帮你自动创建默认构造器了。

      虽然从概念上讲初始化和创建是分开的,但是在java中两者是捆绑在一起不能分开。这种实现机制是有jvm实现的,在创建一个对象的时候同时也调用了构造器。但是在这之前所有工作都是编译器完成。

      编译器会将所以的构造器把你成invokespecial类型,所以当你想在一个构造器中调用另一个构造器必须指明参数列表才能进行前期的绑定,所以必须要有this(参数)形式,并且放在首位。同时在继承关系中,基类也要通过super(参数列表)的形式指明调用那个基类的构造器,也要位于首位,但是如果没有显示的加上super(),那么为了安全性的考虑,编译器会隐式的调用无参的基类构造器,这个无参构造器可能是编译器为基类合成的,也可能是显示编写上的。

 

3)This关键字:

  

   当编译器编译一个类的时候,遇到在类中用了this来调用某些成员数据或方法,编译器就会判断this后面调用对象是什么,如果是一个字段,那么就会在编译的时候确定其值就是类中的某个成员数据,当调用是一个方法的时候要去判断这个方法是什么类型了。对于一般的方法,编译器不知道在使用这个类的时候引用指向的对象还是本类类型还是导出类类型那么会认为他是invokevirtual,这需要在运行的时候由jvm调用,因为jvm知道该调用那个类中的方法。对于private、static、final方法,因为不存在重写,所以是唯一能够确定的,编译器认为他们是invokespecial的,所以在编译的时候就进行了绑定。再者如果再编译System.out.println(this)这种语句的时候这个this没有调用对象,那么也是在运行的时候判断。但是必须在有对象创建的时候this才能放回作用,这也就是为什么说this本质是一个指向对象的指针。

 

作用:

(1)通过返回当前对象这个引用,所以很容易在一个对象上做多次操作。

(2)在方法找那个出现局部变量与类成员名字冲突,可以同this。指明这个为成员对象。

(3)将当前对象传递给其他方法也很有用。

(4)通过在构造器首位this(参数列表)可以实现在一个构造器中调用另一个构造器,以避免重复代码。

 

4)清理Finalize():

  如果一个对象并不是通过new获得的一块特殊的内存空间,由于垃圾回收期只知道释放那些由new分配的内存,所以不知道该如何释放该对象的这块特殊的内存,为了应对这种情况,java允许在类中定义一个名为finalize()方法。其工作原理“假定”是这样的:一旦垃圾回收器准备好释放对象占用的存储空间,就会现调用finalize方法并且在下一次垃圾回收器动作发生的时候才会真正回收占用的内存。

  Java里的对象可能不总是被垃圾回收,所以finalize可能也不会执行,直接程序运行完毕将存储空间交还给操作系统。 finally并不是合适的清理工作的场所。

Finalize()的用处:

对象回收条件的验证:当对某个不再感兴趣也就是它可以被清理了,这个对象应该处于某种状态,使得它占用的内存可以被安全的释放。比如:必须在回收对象空间前,关闭某个被打开的文件。这是可以用finalize()虽然并不是总被调用,但是每次垃圾回收的时候都会先判断一下。确保安全。

 

5)static:

    Static修饰的方法在编译的时候与构造器一样是被编译成invokespecial,也就是说是在前期绑定的,那么按照这个说法,在静态方法中可以使用this.成员数据 了, 因为他们都是在编译的时候就确定的啊。但是在静态方法中使用某个方法或变量,可以在仅仅用类来调用,意味着这些东西可以不需要通过创建对象来确定,也就是说,如果在静态方法中使用了this.XXX的话,前提是要有对象,是矛盾的。

      所以非静态的函数是可以直接访问静态与非静态的成员,反之,静态的函数可以直接访问静态的成员,但是不能直接访问非静态的成员。如果一个函数没有直接访问到非静态的成员时,那么就可以使用static修饰了。 一般用于工具类型的方法。

静态的成员变量与非静态的成员变量的区别:

不同之一在于调用方式上, 静态成员可以通过类或者对象来调用静态成员,也就是说可以不需要通过对象调用。所以静态成员的生命周期优于对象存在。

不同之二在于静态成员变量是存储方法区(静态数据共享区)内存中,而且只会存在一份数据。非静态的成员变量是存储在堆内存中,有n个对象就有n份数据。

不同之三在于静态的成员变量数据是随着类的加载而存在,随着类文件的消失而消失。非静态的成员数据是随着对象的创建而存在,随着 对象被垃圾回收器回收而消失。

 

二、代码快:

构造代码块:大括号必须位于成员位置上,解决了构造函数方法中重复代码的问题。对于初始化功能来讲,一旦一个类创建一个对象,将先执行直接初始化和构造代码块,最后才执行构造器。

静态代码块:类的任何static成员加载时(构造器本质上也是static方法)就执行,一般作用是初始化静态数据成员。否则没有初始化,静态数据成员就会执行默认初始化,对于基本数据将获得标准的初始值。

 

三、字段:

成员变量与局部变量的区别:

从作用上说:成员变量描述了一类事物的公共属性,即类的字段。局部变量仅仅为方法提供一个变量而已。

从生命周期上来说:成员变量是跟随对象的创建而分配内存的,分配在堆上,随着对象的创建而存在,随着对象的消失而消失。局部变量在调用了对应的方法时执行到了创建该变量的语句时存在,局部变量分配在栈区,局部变量一旦出了自己的作用域那么马上从内存中消失。在java中在一个作用域内中的所有变量(包括子作用域中的对象)都不能重名。

从初始值上来说:成员变量基本类型和引用类型都有默认初始值,但是局部变量则没有。

 

四、代码快,成员对象,构造器编译的时候顺序问题:
1、如以下程序:
class f{
int i=200000;
{
i =600000;
System.out.println("yello");
}
{
System.out.println("hello");
}
f(){
i = 400000;
}
{
System.out.println("am");
}
}
反编译得到:

在执行构造器之前先将类中相关赋值的构造代码块和方法初始化按照顺序执行一边,然后在执行构造器。
2、如下程序应该为多少呢(这就关系到成员变量的声明语句的位置)
class f{
{
i =600000;
}
f(){
i = 400000;
}
int i=200000;

}

可以看出java编译器编译一个java源文件的时候,会把成员变量的声明语句提前至一个类的最前端。

3、如果这样呢(仅仅有成员变量的显示初始化与构造代码块)
class f{
{
i =600000;
}
int i=200000;

}

可以看出仅仅是成员变量的显示初始化与构造代码块 的代码在一起时(没有构造器) 是按照当前代码的顺序执行的。

五、实例分析:


0 0