Java面向对象(上)

来源:互联网 发布:淘宝包包特卖 编辑:程序博客网 时间:2024/04/29 19:08

这几天在家看了一些关于java面向对象基础的书籍,于是总结一下自己学到的东西。

一:类,对象,属性,方法,构造器的概念:

类:用于描述客观世界里某一类对象的共同特征。

对象:可以看成是静态特殊(属性)和动态特征(方法)的封装体(简单的理解:类可以看成对象的模版,对象可以看成类的具体实例)。

属性:用于定义类或类的实例所包含的数据。

方法:定义类或类的实例的行为特征或功能实现。

构造器:构造器是一个特殊的方法,主要用于创建类的实例。java语言中器是创建对象的重要途径。


二:this和super关键字

this关键字:是一个对象的默认引用,this总是指向调用该方法的对象。

super关键字:super是直接父类对象的默认引用 。

this作为对象的默认引用有一下两种情况:

1:构造函数中引用该构造函数执行初始化的对象。

2:在方法中引用调用该方法的对象。

this关键字最大的作用就是让类中一个方法,访问该类的另一个方法或属性。

this可以代表任何对象,当this出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的,它所代表的对象只能是当前类;只是当这个方法被调用时,它所代表的对象才能确定下来:谁在调用这个方法,this就代表谁。

如果在static修饰的方法中使用this关键字,则这个关键字就无法指向合适的对象。所以,static修饰的方法中不能使用this引用。由于static修饰的方法不能使用this应用。所以static修饰的方法不能访问不使用static修饰的普通成员。

如果需要在子类方法中调用父类被覆盖的实例方法,可使用super关键字作为调用者来调用父类被覆盖的实例方法。

super也不能出现在static修饰的方法中,static修饰的方法是属于类的,该方法的调用者可能是一个类,而不是对象,也就不存在对应的父类对象了,因而super引用也就失去了意义。

特别注意:Java程序创建某个类的对象时,系统会隐式创建该类父类的对象。只要有一个子类对象存在,则一定存在一个与之对应的父类对象。在子类方法中使用super引用时,super总是指向作为该方法调用者的子类对象所对应的父类对象。这就和this很类似。this总是指向调用该方法的对象。而super则指向this指向对象的父对象。


三:深入构造器:

构造器是一个特殊的方法,该方法主要用于创建类的实例,Java语言里构造器是创建对象的重要途径。因此,Java类必须包含一个或一个以上的构造器。   同一个类里具有多个构造器,多个构造器的形参列表不同,这就叫构造器的重载。

构造器最大的用处就是在创建对象时执行初始化操作。

如果程序员没有为Java类提供任何构造器,则系统会为这个类提供一个无参数的构造器,这个构造器的执行体为空,不做任何事情,无论如果,Java类至少包含一个构造函数。一旦程序员提供了自定义的构造器,则系统不再提供默认的无参数构造器。如果希望该类保留无参数的构造器,此时就需要程序员自己提供。

注意:构造器是创建Java对象的重要途径,通过new关键字调用构造器时,构造器也确实返回了该类的对象,但这个对象并不是完全由构造器负责创建的。实际上 ,当程序员调用构造器时,系统会先为该对象分配内存空间,并为这个对象执行默认的初始化,这个对象已经产生了,这些操作都是在构造器执行之前就完成了。也就是说,当系统开始执行构造器的执行体之前,系统已经创建了一个对象,只是这个对象还不能被外部程序访问,只能在该构造器中通过this来引用它。当执行构造器的执行体执行结束后,这个对象作为构造器的返回值被返回,通常还会赋给另外一个引用类型的变量,从而让外部程序可以访问该对象。       

使用this调用另外一个重载的构造器值能在构造器中使用,而且必须作为构造器执行体的第一条语句。使用this调用重载的构造器时,系统会根据this后括号里的实参来调用形参列表与之对应的构造器。

构造器是否有返回值?

解答:实际上,类的构造器是有返回值的,当我们用new关键字来调用构造器时,构造器是返回该类的实例,可以说这个类的实例就是构造器的返回值,因为构造器的返回值类型总是当前类。因此无须定义返回值类型。但必须注意不能在构造器中显示使用ruturn来返回当前类的对象,因为构造器的返回值是隐式的。

四:类的封装,继承,多态:

封装:封装保证软件部件具有优良的模块性的基础封装的目标就是要实现软件部件的“高内聚、低耦合”,防止程序相互依赖性而带来的变动影响。在面向对象的编程语言中,对象是封装的最基本单位面向对象的封装比传统语言的封装更为清晰、更为有力。简单的理解就是,将类中的数据都隐藏起来,只是提供一个接口,让外部通过提供的接口进行访问类中的数据。

继承:在java中使用extends关键字来实现类的继承。实现继承的类叫做子类,被继承的类叫做父类,基类或者叫超类。

Java只支持单继承,不允许多继承。一个子类只能有一个直接的父类,一个父类可以派生出多个子类。当子类继承了父类,则子类将获得父类的全部属性(privarte属性除外)和方法。

子类重写父类的方法时:必须和父类方法具有相同的名字,参数列表和返回值类型,访问权限必须高于或等于父类的访问权限(方法访问修饰符)。

 多态:同一类型的引用调用同一方法但效果不同。

构成多态的条件:1.要有继承,2.要有重写,3.父类应用指向子类对象。

注意:Java引用变量有两个类型,一个是编译时的类型,一个是运行时的类型,编译时的类型是由声明该类变量时使用的类型决定,运行时的类型是由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就会出现所谓的多态。

精典实例:

public class Test1 extends BaseClass{ public int book=10; public void test(){ System.out.println("重写父类的方法"); } public void sub(){ System.out.println("子类的普通方法"); } public static void main(String[] args) {//编译时类型和运行时类型不一致BaseClass base=new Test1();//将打印父类的6System.out.println("book="+base.book);base.base();//打印子类重写父类的方法内容base.test();}}class BaseClass{public int book=6;public void base(){System.out.println("我是父类的普通方法");}public void test(){System.out.println("将被子类重新的方法");}}
打印结果:book=6
    我是父类的普通方法
    重写父类的方法

解析:引用变量在编译阶段只能调用其编译时类型所具有的方法,但运行时则执行它运行时类型所具有的方法。所以BaseClass base=new Test1();代码中BaseClass是编译时类型,Test1是运行时类型。编译时只能调用BaseClass类的方法,而不能调用Test1类中的方法。而在运行时,调用的是Test1类中的方法,所以当执行base.test();这句代码时,打印的是子类重写父类的方法内容。System.out.println("book="+base.book);执行这句代码时,为什么会打印父类的值呢? 属性与方法就不同了,对象的属性是不具备多态性。通过引用变量来访问其包含的实例属性时,系统总是试图访问它编译时类所定义的属性,而不是它运行时类所定义的属性。


 五:方法的重载和重写:

方法重载:Java允许同一个类中定义多个同名的方法,如果同一个类中包含两个或两个以上方法的方法名相同,但形参列表不同,这就叫方法的重载。

重载的要求:同一类中方法名相同,参数列表不同。方法的返回值,修饰等等与方法重载没有关系。

方法重写:子类包含父类同名方法的现象叫方法的重写。

重写的要求:子类重写父类的方法时,必须和父类方法具有相同的方法名字,参数列表和返回值类型,子类重写父类的方法时,访问权限必须高于或等于父类的访问权限(方法访问修饰符)。

注意:如果被重载的方法中包含了长度可变的形参。比如同一类中包含test(int a)和test(int...nums),当执行对象.test(2)方法时,将会执行test(int a)方法。


六:成员变量和局部变量:

Java语言中,根据定义变量的位置不同,将变量分为了两类:成员变量和局部变量。

成员变量:是指在类范围里定义的变量,也就是常说的属性。

局部变量:是指在一个方法内定义的变量。


成员变量:

成员变量分为类属性和实例属性两种,定义一个属性时不使用static修饰的就是实例属性,反之则是类属性。其中类属性从这个类的准备阶段开始存在,直到系统完全销毁这个类,类属性的作用域 与这个类的生存范围相同;而实例属性则从创建实例开始存在,直到系统完全销毁这个实例,实例属性的作用域与对应实例的生存范围相同。简单的说实例属性随实例的存在而存在,而类属性则随类的存在而存在。

局部变量:

形参:在定义方法时在括号中定义的变量,形参的作用域在整个方法内有效。

方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量时生效,到该方法结束是失效。

代码块局部变量:在代码块中定义的局部变量,这个局部变量的作用域从定义该变量时生效,代码块结束时失效。

注意:局部变量除了形参之外,都必须显示初始化。

Java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可以使用this(实例属性)或类名(类属性)作为调用者来访问成员变量。

成员变量和局部变量的初始化和在内存中的运行机制:

1:成员变量

当系统加载类或创建该类实例时,系统自动为成员变量分配内存空间,并自动为成员变量指定初始值。

成员变量的使用情况:

(1)如果需要定义的变量是用于描述某个类或者某个对象的固有信息,比如人的身高,体重等建议使用实例变量。如果这种信息对这个类的所有实例完全相同,比如人的眼睛,所有人的眼睛都是两个,像这样的属性建议定义为类属性。

(2)如果需要定义一个变量用于保存该类或者实例运行时的状态信息,则建议使用成员变量。

(3)如果某个信息需要某个类的方法之间进行共享,建议使用成员变量。

2:局部变量

局部变量定义后,必须显示的初始化。系统不会为局部变量执行初始化。这说明定义局部变量后,系统并没有为这个变量分配内存空间,只有当程序为这个局部变量赋初始值时,系统才会为该变量分配内存空间,并将初始化值保存到内存中。

局部变量总是保存在方法的栈内存中,栈内存中的变量无须系统垃圾回收,栈内存中的变量往往是随方法或代码块的运行结束而结束的。

七:方法参数传递机制:

形参:定义方法时,方法名后的括号中的参数。

实参:调用方法时实际传入的参数。

问题:Java的实参值是怎么传入方法的呢?

解答:这是由Java方法的参数传递机制来控制的,Java里方法的参数传递方式只有一种:值传递。所谓值传递,就是将实际参数值的副本(复制品)传入方法内,而参数本事不会受到任何影响。【Java里的参数传递类似与《西游记》里的孙悟空,孙悟空复制了一个假孙悟空,这个假孙悟空具有和孙悟空同样的能力,可以降妖除魔。但不管这个假孙悟空遇到什么事,真孙悟空不会受到任何影响。呵呵这里打了一个简单的比方】

简单实例:

public static void main(String[] args) {//定义两个整型变量并初始化int a=5,b=10;//调用交换变量值的方法swap(a, b);//打印变量a和变量b的值System.out.println(" 交换后,实参a="+a+"   ,b="+b);}/** * 交换两个整型变量的值 * @param a  * @param b */private static void swap(int a,int b){//定义一个临时变量,用来存放a变量的值int temp=a;//把b赋值给aa=b;//把temp的值赋值给bb=temp;System.out.println("swap方法中的a="+a+"  ,b="+b);}

控制台打印的结果是:

swap方法中的a=10  ,b=5
交换后,实参a=5   ,b=10

解析:当程序执行main方法中的swap()方法时,系统进入swap方法,并将mian方法中的变量a和变量b作为参数值传入swap方法,此时传入swap方法的是a,b变量的副本,而不a,b变量的本身。进入swap方法后,开始执行变量值的交换工作。执行完swap方法体后,实质只是交换了副本的值,并没有交换a,b变量本身。  从内存的角度分析该程序:在main方法中调用swap方法时,main方法还为结束。因此,系统分别为main方法和swap方法分配两块栈区,分别用于保存mian方法和swap方法的局部变量。main方法中的a,b变量作为参数传入swap方法,实际上是在swap方法栈区中重新生产了两个变量a、b,并将main方法栈区中a、b变量的值分别赋给swap方法栈区中的a、b参数。此时,系统存在两个a变量,两个b变量。只是存在于不同的方法栈区中。所以对swap中的变量a和变量b进行任何操作,对main方法中的a、b变量没有任何影响。

Java对于引用类型的参数传递,一样采用的是值传递方法。这样说或许很多人都会对引用类型的参数传递产生误解。

引用类型的参数传递实例:

public class TestRefenceTransfer {public static void main(String[] args) {//创建Person对象Person person=new Person();//给Person对象赋值person.age=23;person.weight=50;//调用交换值方法swap(person);//打印输出System.out.println(" 交换后,age="+person.age+"  ,weight="+person.weight);}/** * 交换对象的值 * @param person  */private static void swap(Person person){int temp=person.age;person.age=person.weight;person.weight=temp;System.out.println("swap方法中 ,age="+person.age+" ,weight="+person.weight);}}
public class Person {//定义两变量public int age;public int weight;}

打印结果:

swap方法中 ,age=50 ,weight=23
交换后,age=50  ,weight=23

程序解析:程序从main方法开始执行,main方法开始创建了一个Person对象,并定义了一个person引用变量来指向Person对象,这个与基本类型不同。创建对象时,系统内存中有两个实体:堆内存中保存了对象本身,栈内存中保存了该对象的引用。紧接着给person对象赋值。  接下来,main方法中开始调用swap方法,此时main方法并没有结束,系统会分别开辟出main和swap两个栈区,分别用于保存mian方法和swap方法的局部变量。调用swap方法时,person变量作为实参,传入swap方法,同样采取值传递方式,把main方法中的person变量的值赋给swap方法里的person形参,从而完成swap方法的person形参初始化。这里需要指出的是,main方法中的person是一个引用(也就是指针),它保存的是Person对象的地址值,当person的值赋值给swap方法中的person形参后,swap方法中的person也是Person对象的地址值,同样也是指向堆内存中Person对象的地址。此时,不管是操作main方法中的person变量还是操作swap方法中的person变量,其实质都是操作它所引用的Person对象,它们操作的是同一个对象。当swap中将person对象的两个变量值进行了交换,那么mian方法中输出的person对象的属性同样也交换了的。


八:递归方法:

在一个方法体内调用它本身,被称为方法的递归,方法的递归包含一个中隐式的循环,它会重复执行某一段代码,但这种重复执行无须循环控制(递归一定要向已知方向递归)。


  

原创粉丝点击