Java面向对象(基础)
面向对象(基础篇)
类和对象
类:可以被理解为一种自定义的数据类型,可以使用类定义变量,所有使用类定义的变量都是引用类型。类是一系列具有相同行为和属性的对象的集合,用于描述客观世界中一类对象的共同特征。
对象:是具体的,是类的实例,类是对象的模板,对象是类的实例
定义类
面向对象的程序设计中有两个重要的概念:类(class)和对象(Object,也叫做实例)。其中类是对一批对象的抽象,可以把类理解成某个群体,对象则是具体的存在。
Java中定义类的简单语法如下:
1 | [修饰符] class 类名{ |
对于一个类来说,最常见的可以包含:构造器、成员变量和方法,并且这个组成部分都可以包含0个或者多个。一般来说这3个组成部分至少会包含1个组成部分,如果3部分都不包含实际上定义了一个空类,这样做没有任何意义。
定义成员变量语法如下:
1 | [修饰符] 数据类型 成员变量 [=默认值] |
修饰符:修饰符可以省略,也可以是public、protected、private、static、.final、transient,其中public、protected、private.只能选一个,可以与final、static组合来修饰成员变量
类型:类型可以是Java允许的任意类型,可以是基本类型、也可以是引用类型
成员变量名:成员变量名只要是一个合法的标识符即可,成员变量的命名一般使用camel命名法,第一个单词首字母小写,其余单词的首字母均大写,单词间不用任何分隔符,并且要做到见名知意,尽量避免用单个字母命名
默认值:成员变量可以指定默认值,也可以不指定,当不指定时在创建对象时,Java会提供默认值,其中整型默认值为0,浮点型默认值为0.0,布尔型默认值为false,字符型默认值为’\u0000’。
定义成员方法语法如下:
1 | [修饰符] 方法返回值类型 方法名(形参列表){ |
方法返回值类型:返回值类型可以是v允许的任意数据类型、包括基本类型和引用类型;如果声明了返回值类型、则方法体中必须有一个有效的return语句,该语句返回一个变量或者表达式,这个变量或表达式的类型必须与声明的类型匹配,此外,如果一个方法没有返回值,则返回值类型用void代替,表明该方法没有返回值。
方法名:方法名的命名规则同成员变量的命名规则基本一致。
形参列表:形参用于定义该方法可以接受的参数,形参列表可以由0个或者多个参数组成,参数之间用逗号隔开。一旦方法定义时定义了形参列表,则调用该方法时必须传入对应类型的参数值。即谁调用,谁传参。
构造体(构造方法)的定义
1 | [修饰符] 方法名(形参列表){ |
构造器则是一种特殊的方法,其作用是用于创建对象,Java语言通过new关键字来调用构造方法,从而返回该类的实例。构造器是一个类创建对象的基本方法,如果一个类没有构造器,这个类也就无法创建实例了。因此Java语言提供了一个功能:如果开发者没有为类编写构造器,编译器会为该类提供一个默认无参数的构造器,一旦开发者提供了构造器,则编译器不在提供构造器。
需要注意的是构造器是一种特殊的方法,其方法名和类名相同,但没有方法返回值,也不用void修饰。
修饰符:修饰符可以省略,也可以是public、protected、private其中之一,如果构造器的修饰符为private,则不能通过new调用,也就是说当一个类的构造器被private修饰,该类则不能通过new来创建对象
方法名:必须与类名相同
形参列表:和方法中的形参格式完全相同
陷阱:给定一个方法名称和类名相同,但是有返回值或者使用了void修饰,要求你判断是否是构造方法。有void的方法就不是构造方法!!!
创建对象和使用对象
创建对象最根本的途径是调用构造器,Java中通过new关键字来调用构造器创建对象。
1 | //创建一个Student类型的对象,也可以说是定义了一个Student类型的变量 |
创建好对象后,可以对对象进行如下操作:
1、访问对象的实例变量
2、调用对象的方法
基本类型和引用类型
基本类型:int i = 10; 执行该代码时,其在内存中的结构如下图所示:
引用类型:Student s = new Student(); 执行该代码时,其在内存的示意图如下所示:
从上图中我们可以看出,Studenty对象包含两个实例变量,而变量是需要内存来存储的,当创建Student对象时,必然要有对应的内存来存储对象的实例变量,Student对象由多块内存组成,不同的内存分别存储着Student对象的不同成员变量,当把Student对象赋值给一个引用变量时,
Java会将对象的地址保存在变量中,也就是保存在栈中,也就是说变量中仅仅保存的是一个引用或者说地址,而不是真实的对象,由该变量中的引用指向该对象。对象的成员变量数据实际存放在堆中,
当访问对象的成员变量和方法时,实际上访问的是变量所指向的对象的成员变量和方法。
综上所述,可以简单的理解为基本类型在栈中保存的是变量真实的值,而引用类型保存的并不是对象而是一个地址或者说引用,这就是基本类型和引用类型的根本区别,这也就是说我们通常判断基本类型相等时,用双等号即可,而判断引用类型则不能用双等号。
this关键字
Java提供了一个this关键字,this关建字可以指代当前对象,根据this出现的位置不同,this作为当前对象的默认引用有两种使用方式
1.构造器中使用this可以调用其他构造器
2.方法中使用this可以方问其他方法或者实例变量,通常this可以省咯。
this关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或实例变量
this关键字最大的作用就是上类中的方法访问类中的另一个方法或者实例变量,假设定义了一个Animal类,这个Animal对象的run()方法需要调用它的jump方法,就可以使用this关键字。
1 | public class Animal { |
在Java中一个成员直接调用另一个成员时,this可以省略。也就是说上述的代码可以修改为如下形式
1 | public class Animal { |
这是Java中规定,当在构造方法中调用其它构造方法时,不需要些构造方法的名字,因为所有构造方法的名称都是一样的,唯一不同的就是参数列表,JVM会通过this后传入的参数列表来确定究竟调用哪一个构造方法.需要注意的是,this调用构造方法只能在构造方法中使用,不能写在实例方法中,并且要放在构造方法中代码的第一行。
1 | public class Dog { |
注意:this不能出现在类方法中,因为类方法在类加载后,创建对象前就已经准备完毕,此时还没有对象,也就不存在this
方法详解
方法是类或对象的行为特征的抽象,方法是类或对象最重要的组成部分。但从功能上来看,方法完全类似于传统结构化程序设计里的函数。值得指出的是,Java 里的方法不能独立存在,所有的方法都必须定义在类里。方法在逻辑上要么属于类,要么属于对象。
实例方法和类方法
·被static修饰的方法叫做类方法,方法属于类,调用时不依赖于对象,通过类名.方法名即可调用。
·不被static修饰的方法叫做实例方法,方法属于对象,调用时依赖对象,必须先创建对象才能调用,因此其调用方式是对象名.方法名
**这里可能产生一个问题;同一个类里不同方法之间相互调用时,不就可以直接调用吗?**这里需要指出; 同一个类的一个方法调用另外一个方法时,如果被调方法是普通方法,则默认使用 this 作为调用者;如果被调方法是静态方法,则默认使用类作为调用者。也就是说,表面上看起来某些方法可以被独立执行,但实际上还是使用 this 或者类来作为调用者。
·永远不要把方法当成独立存在的实体,正如现实世界由类和对象组成,而方法只能作为类和对象的附属,Java语言里的方法也是一样。Java语言里方法的所属性主要体现在如下几个方面。方法不独立定义,方法只能在类体里定义。
·从逻辑意义上来看,方法要么属于该类本身,要么属于该类的一个对象。
·永远不能独立执行方法,执行方法必须使用类或对象作为调用者。
1 | public class Person { |
方法的参数传递
前面已经介绍了Java里的方法是不能独立存在的,调用方法也必须使用类或对象作为主调者。如果声明方法时包含了形参声明,则调用方法时必须给这些形参指定参数值,调用方法时实际传给形参的参数值也被称为实参。那么,Java的实参值是如何传入方法的呢?这是由java方法的参数传递机制来控制的,Java方法的参数传递方式只有一种值传递。所谓值传递,就是将实际参数值的副本(复制品)传入方法内,而参数本身不会受到任何影响。
1 | public class SwapDemo { |
从结果可以看出在swap方法中交换了变量a和变量b的值,但是在main方法中变量a和变量b的值并没有发生变化,这是因为在方法传递参数时传递的是实参的副本,也就是说在main方法中调用swap方法传递参数时,会将变量保存在钱内存中的真实值复制一份传递给swap方法,这样并不影响原来变量的值。
以上情况是基本类型的,那么引用类型的又是怎样的呢,现在我们定义一个Teacher类,类中只包含一个int类型的age变量。
1 | public class Teacher { |
当代码执行到12行时,此时调用了change()方法,并传入了t1对象,此时并不是将对象直接传递,传递的是t1中保存的引用或地址。
在change方法中修改了对象的年龄,也就是说在change方法中修改的也是t1指向的对象,所以当在main方法中打印对象的年龄时,t1的年龄也变成了20。所以在Java中不存在引用传递,所谓的引用传递本质上都是值传递
可变参数
JDK 1.5 以后Java允许定义形参个数可变的参数,从而允许为方法指定数量不确定的参数,如果在定义方法时,最后一个参数的类型后添加三点,则表明该形参可以接收多个参数指,多个参数值被当做数组传入。
1 | public class Varargs { |
null和空的区别,int[] a;不初始化数组,则它为null;int[] a = {};初始化数组,但不为它赋值,则为空。
例如有一张白纸,上面不写东西则为空;没有白纸,则为null
从上面的示例可以看出,当一个方法中有可变参数时,参数可以分开传递,也可以放入数组,然后将数组传递给可变参数,也可以不传递,因此,我们在使用可变参数时,首先要判断可变参数是否为null,其次因为可变参数的本质是数组,因此还需要判断数组中是否存在元素。
注意的是:可变参数只能处于参数列表的最后一个参数,一个方法中只能包含一个可变参数,可变参数的本质就是一个数组。
方法重载
在Java中允许同一个类中定义多个同名的方法,但是要保证参数列表不同,也就是说同一个类中包含了两个或者两个以上方法名相同,但是参数列表不同,则被称为方法重载。这里需要注意的是方法重载与访问修饰符无关、方法返回值无关、方法抛出异常无关,只与方法的参数列表有关。
那么什么叫做参数列表不同呢,参数列表不同需要注意以下几点:
1.个数不同
2.数据类型不同
例如下面的两个方法就不构成方法重载,因为两个方法方法名相同,但是参数数据类型相同,都是一个double类型和一个int类型。
1 | public class CalcArea { |
那么,当我们调用方法时,如果区分究竟调用的是哪个方法呢,如果你还记得我们使用this调用构造方法时,这个问题就会迎刃而解,当我们调用重载的方法时,也是通过传入的参数由JVM决定调用哪个方法。这个过程叫做重载解析。
成员变量和局部变量
在 Java 语言中,根据定义变量位置的不同,可以将变量分成两大类; 成员变量和局部变量。成员变量和局部变量的运行机制存在较大差异
成员变量和局部变量是什么
成员变量指的是在类里定义的变量,也就是前面所介绍的filed;局部变量指的是在方法里定义的变量。
成员变量被分为类变量和实例变量两种,定义成员变量设有static修饰的就是实例变量,有static修饰的就是类变量。其中类变量从该类的准备阶段起开始存在,直到系统完全销毁这个类,类变量的作用域与这个类的生存范围相同:而实例变量则从该类的实例被创建起开始存在,直到系统完全销毁这个实例,实例变量的作用域与对应实例的生存范围相同。
一个类在使用之前要经过类加载、类验证、类准备、类解析、类初始化等几个阶段。
正是基于这个原因,可以把类变量和实例变量统称为成员变量,其中类变量可以里解为类成员变量,它作为类本身的一个成员,与类本身共存亡实例变量侧可理解为实例成员变量,它作为实例的一个成员,与实例共存亡。只要类存在,程序就可以访问该类的类变量。在程序中访问类
变量通过如下语法:类.类变量、实例.实例变量、实例.类变量
1 | public static void main(String[] args) { |
从上例的实例可以看出当修改s1的number属性时,s2的number属性也发生了变化,这是因为,static修饰的变量属于对象所属的类,被所有对象所共享。但是我们并不推荐使用对象来访问类变量,类变量应该通过类名访问。
局部变量根据定义形左式的不同,又可以被分为如下三种。
形参:在定义方法签名时定义的变量,形参的作用域在整个方法内有效。
方法局部变量:在方法体内定义的局部变量,它的作用域是从定义该变量的地方生效,到该方法结束时失效。
代码块局部变量:在代码块中定义的局部变量,这个局部变量的作用域从定义该变量的地方生效,到该代码块结束时失效。与成员变量不同的是,局部变量除形参之外,都必须显式初始化。也就是说,必须先给方法局部变量和代码块局部变量指定初始值,否则不可以访问它们。
在同一个类里,成员变量的作用范围是整个类内有效。一个类里不能定义两个同名的成员变量,即使一个是类变量,一个是实例变量也不行;一个方法里不能定义两个同名的方法局部变量,方法局部变量与形参也不同名:同一个方法中不同代码块内的代码块局部变量可以同名:如果先定义代码块局部变量,后定义方法局部变量,前面定义的代码块局部变量与后面定义的方法局部变量也可以同名。Java允许局部变量和成员变量同名,如果方法里的局部变量和成员变量同名,局部变量会覆盖成员变量,如果需要在这个方法里引用被覆盖的成员变量,则可使用this(对于实例变量)或类名(对于类变量)作为调用者来限定问成员变量
成员变量初始化机制
当系统加载类或创建该类的实例时,系统自动为成员变量分配内存空间,并在分配内存空间后,自动为成员变量指定初始值,下面我们通过图示来学习。
1 | public static void main(String[] args){ |
当代码执行到第3行,我们发现打印了0
当代码执行到第2行时,JVM在堆中分配了一块内存区域,这块内存区域中保存这实例变量āge,并为其赋默认值。此时内存结构如下:
当执行第4行代码时,JVM将堆内存中age的值修改为18.
局部变量的初始化机制
局部变量定义后,必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。这意味着定义局部变量后,系统并未为这个变量分配内存空间,直到等到程序为这个变量赋初始值时,系统才会为局部变量分配内存,并将初始值保存到这块内存中。
与成员变量不同,局部变量不属于任何类或实例,因此它总是保存在其所在方法的浅内存中。如果局部变量是基本类型的变量,则直接把这个变量的值保存在该变量对应的内存中,如果局部变量是一个引用类型的变量,则这个变量里存放的是地址,通过该地址引用到该变量实际引用的对
象或数组。栈内存中的变量无需系统垃圾回收,往往随方法或代码块的运行结束而结束。因此,局部变量的作用域是从初始化该变量开始,直到该方法或该代码块运行完成而结束。因为局部变量只保存基本类型的值或者对象的引用,因此局部变量所占的内存区通常比较小。
static关键字
在前面的内容中我们可以总结出static用于修饰方法和示例变量,使用static修饰的方法叫做类方法,使用static修饰的变量叫放类变量,static还可以修饰代码块,无论是类变量还是类方法,都属于类,而不属于对象,是所有对象共享的。
类变量属于整个类,当系统第一次准备使用该类时,系统会为该类变量分配内存空间,类变量开始生效,直到该类被卸载,该类的类变量所占有的内存才被系统的垃圾回收机制回收。类变量生存范围几乎等同于该类的生存范围。当类初始化完成后,类变量也被初始化完成。类变量既可通
过类来访问,也可通过类的对象来访问。但通过类的对象来访问类变量时,实际上并不是访问该对象所拥有的变量,因为当系统创建该类的对象时,系统不会再为类变量分配内存,也不会再次对类变量进行初始化,也就是说,对象根本不拥有对应类的类变量。通过对象访问类变量只是一
种假象,通过对象访问的依然是该类的类变量,可以这样理解:当通过对象来访问类变量时,系统会在底层转换为通过该类来访问类变量。
由于对象实际上并不特有类变量,类变量是由该类持有的,同一个类的所有对象问类变量时,实际上方间的都是孩类所特有的变量。因此,从程序运行表面来看,即可看到同一类的所有实例的类变量共享同一块内存区。
类方法也是类成员的一种,类方法也是属于类的,通常直接使用类作为调用者来调用类方法,但也可以使用对象来调用类方法。与类变量类似,使使用对象来调用类方法,其效果也与用类来调用类方法完全一样。