java面向对象
面向对象的概念
类是对一组具有相同属性(或称状态,用数据表示)和行为(或称操作,用方法实现)的对象的抽象,一个类所包含的方法和数据描述了一组对象的共同属性和行为。对象则是类的具体化,是类的实例。
在面向对象的程序设计中,总是先声明类,再由类生成其对象,类是建立对象的“模板”,按照这个模板所建立的一个个具体的对象,就是类的实际例子,通常称为实例。编写程序,实际上是定义类,而真正用的是类的对象,这个过程称为类的实例化。
面向对象程序的设计方法具有如下3个特点:
①封装性:封装是一种信息隐蔽技术,通过类的说明实现封装,即将程序中数据和基于数据的操作方法封装起来,使之成为一个整体,其载体就是类,它是程序中的最小模块。这样可以保护数据和操作方法的安全,是对象的重要特性。
②继承性:主要描述的是类与类之间的关系,通过继承,可以在无需重新编写原有类的情况下,对原有类的功能进行扩展。
③多态性:一般类中定义的属性或方法,被子类继承之后,可具有不同的数据类型或不同的行为,这样,一个程序可以存在同名的不同方法,不同类的对象可以响应同名的方法,具体的实现方法却不同。多态性使得语言具有灵活性、抽象、行为共享和代码共享的优势,很好地解决了应用程序方法同名问题。
类
类是Java的核心,任何一个Java程序至少包含一个类,类中封装了数据和基于数据的操作,这种操作在Java类中称为方法。
类定义的格式
[public][abstract|final] class 类名{
数据类型 成员变量1;
...
数据类型 成员变量M;
数据类型 成员方法名1(参数列表) { 方法体 }
...
数据类型 成员方法名N(参数列表) { 方法体 }
}
public:定义类的访问权限,如果一个类被声明为public,则与其不在同一个包中的其它类可以通过引用它所在的包来使用这个类,否则这个类就只能被同一包中的其他类使用。
abstract:表示该类为抽象类,不能创建对象实例。
final:表示该类为最终类,不能再派生出新的子类。
成员变量的声明
成员变量的声明格式:
[访问控制修饰符][其它修饰符] 数据类型 变量名称;
在类的定义中,当一个变量的声明不属于任何方法时,则该变量为类的成员变量。成员变量描述了类和对象的状态(或属性)
class person{
int age=0; //年龄
boolean gender; //性别
String name; //姓名
void setinfor(int x, boolean y, String z){
age=x; gender=y; name=z;
}
….//其它成员方法的定义
}
成员变量是类定义的一部分,成员变量可以是任何数据类型的数据。
成员变量在定义时可赋初始值。若在定义成员变量时没有指明初始值,则默认如下:
数值型变量初始值为0
对象类变量初始值为null
布尔型为false
在同一类中,各成员变量不能同名。
成员变量在整个类的定义中都有效,即类定义中的成员方法和成员变量都可以操作某个成员变量。
访问控制修饰符
public:公开的/公有的,只要能使用这个类,就可以直接存取这个变量的值。
protected:受限的/保护的,仅能被其子类或与其处于同一包的其他类直接存取。
private:私有的,除了声明它们的类外,不能被包括其子类在内的任何其他类直接存取。
默认情况:成员变量前不加访问控制符,则其只能被同一包中的类直接存取,即为包可见性。
成员方法的声明
成员方法的声明格式:
[访问控制修饰符][其它修饰符] 返回类型 方法名称(参数列表)
{
方法体语句
}
成员方法也是类的一部分,大部分的类定义中都包含成员方法。成员方法的返回值可以是任何数据类型的数据。如果没有返回值,返回值类型写成void。
成员方法的参数列表是使用逗号隔开的参数,放在圆括号中,参数也可以是任何数据类型的数据,即使没有参数,圆括号也不可省略。
方法重载
同一类中的方法名可以相同,前提是名称相同的各方法之间在参数个数或参数类型等方面要存在差异。
类中的变量
分类 | 局部变量 | 成员变量 |
---|---|---|
定义位置 | 方法体中和方法的参数列表 | 类属性定义部分所定义的 |
声明格式 | 方法体: 类型 变量名;方法参数列表:类型1 变量1,类型 变量2,…. | [访问控制修饰符]类型 变量名 |
作用域 | 方法体内有效 | 无论何种访问权限,成员变量可在该类的所有方法中使用 |
初始化与赋值 | 不可自动初始化,要求在程序中显式地给其赋值,只有当方法被调用执行时,局部变量才被分配内存空间,调用完毕后,所占空间被释放。 | 可自动初始化,数值型为0,逻辑型为false,引用型为null.可在声明时进行,也可在方法中实现,但不能在声明和方法之间进行赋值 |
构造方法
构造方法是与类同名的特殊的成员方法。 Java中的每个类都有构造方法,在创建类对象时,系统将自动调用构造方法以初始化该类的一个新的对象(实现对数据成员的初始化)。
构造方法的定义格式:
类名(形参列表)
{ 方法体 }
- 构造方法的方法名与类名相同,可以是无参的,也可以是有参的,而且不返回任何数据类型,即没有返回值,与返回值为空(void)不同,即不能加任何返回类型,包括void。
- 构造方法通常使用public修饰,以确保可以在类的外部创建该类的实例对象。
- 在类中没有构造方法时,使用默认的构造方法,按照默认的方式对变量进行初始化,即数值型初始化为0,引用型初始化为null,逻辑型初始化为false。如果用户自定义了构造方法,则不能再使用默认的构造方法。
- 一个类中存在一个或多个构造方法。
main()方法
Java独立应用程序(Application程序)都从main开始执行,一个程序中只有一个main方法,其使用形式为:
public static void main(String args[]){
……
}
- main方法中参数args[]是用来传递命令行参数的。
- args[i-1]:存储所传递的第i个参数。
- args.length:存储所传递参数的个数。
- main(){}和main(String args[]){}是两个不同的方法,计算机要调用的是带参的main()方法。
对象
对象的创建
对象是类的实例,类是对象的模板。
对象的创建:
格式1: 类名 变量名= new 构造方法(实参列表);
格式2:
类名 变量名; //声明对象
变量名=new 构造方法(实参列表); //创建对象
构造方法只能在创建对象时使用,创建完对象后不能再通过“对象.方法()”的方式来使用构造方法。
new 表达式确实返回了对新建对象的引用,但构造方法本身并没有任何返回值。
例如:求矩形面积
package test_java;
import java.util.Scanner;
public class hello {
public static void main(String[] args) {
Scanner sc=new Scanner(System.in);
System.out.println("请输入矩形的宽");
double w=sc.nextDouble();
System.out.println("请输入矩形的高");
double h=sc.nextDouble();
Rect r=new Rect();
r.height=h;
r.width=w;
r.area();
}
}
class Rect{
double height;
double width;
void area(){
System.out.println("面积="+height*width);
}
}
对象的引用
- 对象的引用并不是对象实例本身,而是对象的一个引用地址,该内存地址用来定位该对象实例在堆中的位置。对象实例本身和对象的引用分别保存在堆和栈中。
- 可以定义一个对象引用,暂时不和一个对象实例关联。多个对象引用也可以指向同一个对象实例。
对象的使用
- 对象的使用:通过一个引用类型的变量使用运算符“
.
”来实现对成员变量的访问和成员方法的调用。
对象的消亡
- 一个引用型变量在其作用域内有效,在作用域外将被清除,此时,该引用类型的对象就没有被任何变量引用,变成垃圾。
- 可以通过赋值null(特殊常量,表示引用变量不指向任何一个对象)给引用变量来显示地清除对象的引用。
r=null;
- 对象的消亡:Java通过垃圾回收器来收集并释放那些不再有引用的对象所在的空间。
- 程序中同一个对象可以有多个引用,一个对象在作为“垃圾”被垃圾回收器清除之前必须清除所有该对象的引用。
this关键字
Java中提供this关键字指代当前对象,用于在方法中访问对象的其他成员。其常用方法:
①通过this可以明确地去访问一个类的成员变量,解决与局部变量名冲突问题。
class person{
int age; //年龄
boolean gender; //性别
String name; //姓名
void setinfor(int age, boolean gender, String name){
this.age=age;
this.gender=gender;
this.name=name;
}
….//其它成员方法的定义
}
②通过this调用成员方法,this可以省略不写。
class person{
int age; //年龄
boolean gender; //性别
String name; //姓名
void setinfor(int age, boolean gender, String name){
this.age=age;
this.gender=gender;
this.name=name;
}
int get_age(){ return this.age; }
void show(){
int x=this.get_age();
System.out.println(“年龄为:”+x);
}
}
③在一个构造方法中使用this(参数列表)的形式调用其他的构造方法。
public Person(){ System.out.println(“无参构造方法被调用了...”); }
public Person(int age){
this();
System.out.println(“有参构造方法被调用了”);
}
}
public class Test{
public static void main(String[] args){
Person p=new Person(20);
}
}
使用this调用类的构造方法时应注意:
- 只能在构造方法中使用this调用其他的构造方法,不能在其他成员方法中使用。
- 在构造方法中,使用this调用构造方法的语句必须位于第一行,且只能出现一次。
- 不能在一个类的两个构造方法中使用this互相调用。
访问权限
类的访问控制
类的访问控制修饰符只有public(公共类)及无修饰符(缺省类)两种。
类型 | 无修饰(缺省类) | public(公共类) |
---|---|---|
同一包中的类 | 是 | 是 |
不同包中的类 | 否 | 是 |
一个Java源程序(扩展名为.java的文件)中只能有一个public类,因此,为了使一个包中饱含多个public类,可将一个Java程序拆分成几个程序,只要包的名称相同即可。
类成员的访问控制
类型 | private | public | protected | 无修饰 |
---|---|---|---|---|
同一类 | 是 | 是 | 是 | 是 |
同一包中的子类 | 否 | 是 | 是 | 是 |
同一包中的非子类 | 否 | 是 | 是 | 是 |
不同包中的子类 | 否 | 是 | 是 | 否 |
不同包中的非子类 | 否 | 是 | 否 | 否 |
能对类访问,并不意味着能访问该类中的成员。即要访问类的成员,必须要求类可以访问且成员可访问。
当类可访问时,对不可直接访问的成员变量,可以通过可访问的方法来访问它。
static关键字
在Java中定义了一个static关键字,用于修饰类的成员,被static修饰的成员具备一些特殊性。
静态变量
成员变量可分为实例变量和静态变量
分类 | 实例变量 | 静态变量 |
---|---|---|
声明 | 不使用关键字static | 使用关键字static |
内存空间分配 | 同一个类的不同对象的实例变量被分配不同的内存空间 | 类的静态成员变量由该类的所有对象共有,一个类对象的空间内不包含静态数据成员的空间,其所占的空间不会随对象的产生而分配,或随对象的撤销而消失。它是在类被加载时被分配的,即使没有创建类的一个对象。 |
初始化 | 在对象初始化时完成相应的初始化工作,并由某一个对象独自拥有 | 在类被加载时完成相应的初始化工作 |
作用域 | 某一个类具体创建的实例对象 | 整个类 |
访问方式 | 只能由对象调用 | 可由对象访问,也可直接由类名访问 |
- 类加载器:负责加载类的字节码文件,并完成类的链接和初始化工作。首先将要加载的类名转换为类的字节码文件名,并在环境变量CLASSPATH指定的每个目录中搜索该文件,把字节码文件读入缓冲区。其次将类转换为JVM内部的数据结构,并使用校验器检查类的合法性。如果类是第一次被加载,则对类中的静态数据进行初始化。加载类中所引用的其他类,把类中的某些方法编译为本地代码。
- 字节码解释器:它是整个JVM的核心部件,负责解释执行由类加载器加载的字节码文件中的字节码指令集合,并通过Java运行环境(JRE)由底层的操作系统实现操作。
- 垃圾收集器:用于检测不再使用的对象,并将它们所占用的内存回收。
class Student {
static String schoolName="***大学"; // 定义静态变量schoolName
}
public class Example12 {
public static void main(String[] args) {
System.out.println("默认学校名称为:"+Student.schoolName);//默认学校名称为:***大学
Student stu1 = new Student(); // 创建学生对象
Student stu2 = new Student();
Student.schoolName = "河北工业大学"; // 为静态变量赋值
System.out.println("我的学校是" + stu1.schoolName); // 打印第一个学生对象的学校 我的学校是河北工业大学
System.out.println("我的学校是" + stu2.schoolName); // 打印第二个学生对象的学校 我的学校是河北工业大学
}
}
静态方法
方法是类的动态属性。
对象的行为是由其方法来实现的。
格式:
[访问控制修饰符][其它修饰符] 返回类型 方法名称(参数列表)
{
方法体语句
}
一定条件下,同一类中,不同的方法之间可以相互调用。
在方法声明时,通过修饰符可以对方法访问实施控制。在方法中,可以对类的成员变量进行访问,但不同类型的方法对不同类型的成员变量的访问是有限制的。
类中的方法可分为实例方法和静态方法
分类 | 实例方法 | 静态方法 |
---|---|---|
声明 | 不使用关键字static | 使用关键字static修饰 |
操作对象 | 静态变量和实例变量 | 静态变量 |
调用方式 | 只能由对象调用 | 可由对象调用,也可直接由类名调用 |
方法间的调用 | 可相互调用 | 只能调用静态方法,不能调用实例方法 |
在创建对象之前,实例变量没有分配内存,实例方法也没有入口地址。
静态代码块
在Java类中,使用一对大括号包围起来的若干行代码被称为一个代码块,也称为自由块。Java中的自由块分为两种:静态块和非静态块。
静态块:用static关键字修饰的代码块称为静态代码块。
当类被加载时,静态代码块会执行,由于类只加载一次,因此静态代码块只执行一次,多个静态块的时候,按出现的顺序执行。
在程序中,通常会使用静态代码块来对类的成员变量进行初始化。
class Person{
static{
System.out.println(“Person的静态代码块执行了”);
}
}
非静态块:未用static关键字修饰的代码块。
每次初始化一个对象,都会导致一次非静态块的执行;
在构造函数执行之前执行;
继承关系: 父类的非静态块->父类的构造函数->自己的自由块->自己的构造函数
public class Test {
{
System.out.println("before");
}
}
static导入
在通过import关键字导入包的时候可以加上static修饰符,则可导入包中的静态变量和静态方法,并且调用静态变量和静态方法的时候不需要类的点运算符操作,格式如下:
import static 包名.类名.*;
import static 包名.类名.成员变量;
import static 包名.类名.成员方法;
例如: import static java.lang.System.*;
则可以直接使用System类的静态方法和静态变量
例如:
out.println("hello");
这行代码相当于 System.out.println("hello");
方法调用中的数据传递
值传递
- 将调用方法的实参的值计算出来赋予被调用方法对应形参的一种数据传递方法。
- 在这种数据传递方法下,被调用方法对形参的计算、加工与对应的实参已完全脱离关系。当被调方法执行结束,形参中的值可能发生变化,但是返回后,这些形参中的值将不会带到对应的实参中。
- 具有“数据的单向传递”的特点。
- 形参一般是基本类型的变量,实参可以是变量或常量,也可以是表达式。
引用传递
方法的参数类型一般是复合类型(引用类型)。复合类型变量中存储的是对象的引用,所以在参数转送中是传送引用,方法接收参数的引用,因此任何对形参的改变都会影响到对应的实参。“引用的单向传送,数据的双向传送”。
返回值
- 返回值不是在形参和实参之间传递数据,而是被调方法通过方法调用后直接将返回值送到调用方法中。
- 使用返回值方法时,方法的返回值类型不能为void,且方法体中必须有带表达式的return语句,其中表达式的值就是方法的返回值。
实例变量和静态成员变量传递
实例变量和静态成员变量传递方式也不是在形参和实参之间传递数据,而是利用在类中定义的实例变量和静态成员变量是类中诸方法共享的变量的特点来传递数据。
垃圾回收
在Java中,当一个对象成为垃圾后仍会占用内存空间,时间一长,就会导致内存空间的不足,因此,Java中引入了垃圾回收机制,使得程序员不需要过多关心垃圾对象回收的问题,JVM会启动垃圾回收器将垃圾对象从内存中释放。
- 正常情况下,垃圾回收器Garbage Collection(以下简称gc)周期性地检查内存中不再被使用的对象,然后将它们回收,释放它们占用的空间。
- 除了等待JVM进行自动垃圾回收外,还可以通过
System.gc()
或Runtime.getRuntime().gc()
方法通知JVM可以进行垃圾回收。调用 gc() 方法暗示着JVM做了一些努力来回收未用对象,以便能够快速地重用这些对象当前占用的内存。当控制权从方法调用中返回时,虚拟机已经尽最大努力从所有丢弃的对象中回收了空间。 - gc只能清除在堆上分配的内存(纯java语言的所有对象都在堆上使用new分配内存),而不能清除栈上分配的内存。因此,如果某些对象被分配了栈上的内存区域,那gc就管不着了,对这样的对象进行内存回收就要靠finalize()。
- 当使用JNI(Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++))技术时,可能会在栈上分配内存,例如java调用c程序,而该c程序使用malloc()函数分配内存时,需调用C中的free()函数释放内存。这个时候要进行释放内存的工作,gc是不起作用的,因而需要在finalize()中调用free()。
- 一旦垃圾收集器准备好释放对象占用的存储空间,它首先调用finalize(),而且只有在下一次垃圾收集过程中,才会真正回收对象的内存。所以如果使用finalize(),就可以在垃圾收集期间进行一些重要的清除或清扫工作。
使用finalize的方式非常简单,只需在类中增加finalize方法。finalize格式为:
class 类名{
……
protected void finalize() throws Throwable
{……}
}
垃圾回收举例:
class Person {
// 下面定义的finalize方法会在垃圾回收前被调用
public void finalize() {
System.out.println("对象将被作为垃圾回收...");
}
}
public class Test {
public static void main(String[] args) {
// 下面是创建了两个Person对象
Person p1 = new Person();
Person p2 = new Person();
// 下面将变量置为null,让对象成为垃圾
p1 = null;
p2 = null;
// 调用方法进行垃圾回收
System.gc();
}
}
在以下三种情况将使用finalize:
①所有对象被Garbage Collection时自动调用,比如运行System.gc()的时候。
②程序退出时为每个对象调用一次finalize方法。
③显式的调用finalize方法。
除此以外,正常情况下,当某个对象被系统收集为无用信息的时候,finalize()将被自动调用,但是JVM不保证finalize()一定被调用,也就是说,finalize()的调用是不确定的,这也就是为什么sun不提倡使用finalize()的原因。
如果在程序终止之前垃圾回收器始终没有执行垃圾回收操作,那么垃圾回收器将始终不会调用无用对象的finalize()方法。因为finalize()只有在垃圾回收器工作的时候才会被调用,也就是说,通过它进行资源释放并不能确保马上被释放,甚至可能根本不会被释放(因为垃圾回收器可能不会工作)。程序即使显式调用System.gc()或Runtime.gc()方法,也不能保证垃圾回收操作一定执行,因此不能保证无用对象的finalize()方法一定被调用。