这几天在家看了一些关于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();
//将打印父类的6
System.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赋值给a
a=b;
//把temp的值赋值给b
b=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对象的属性同样也交换了的。
八:递归方法:
在一个方法体内调用它本身,被称为方法的递归,方法的递归包含一个中隐式的循环,它会重复执行某一段代码,但这种重复执行无须循环控制(递归一定要向已知方向递归)。