JAVA基础,面向对象
面向对象
最早的程序开发使用的是结构化程序设计语言,随着时间的流逝,软件的规模逐渐扩大,使用结构化语言会出现各种弊端,导致无休止地拖延开发周期,产品的质量也不尽如人意。这一切都体现了结构化语言不再适合当前的软件开发。现在程序设计者们将另一种开发思想引入程序中,那就是面向对象开发思想。
面向对象最关键的两个词汇是类与对象,实质上可以将类看作对象的抽象,它定义了对象所具有的属性和方法。学习 Java 语言必须掌握类与对象,这样可以从深层次理解 Java 这种面向对象语言的幵发理念。因此,掌握类与对象是学习 Java 语言的基础,可以使开发人员更好、更快地掌握 Java 编程思想与编程方式。
面向过程 & 面向对象
面向过程思想
- 步骤清晰简单,第一步做什么,第二步做什么,属于线性处理
- 面对过程适合处理一些较为简单的问题
- 物以类聚,分类的思维模式, 思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考,最后,才对某个分类下的细节进行面向过程的思考
- 面向对象适合处理复杂的问题,适合处理需要多人协作的问题。
什么是面向对象 面向对象简称 OO(Object Oriented),20 世纪 80 年代以后,有了面向对象分析(OOA)、 面向对象设计(OOD)、面向对象程序设计(OOP)等新的系统开发方式模型的研究。
对 Java 语言来说,一切皆是对象。把现实世界中的对象抽象地体现在编程世界中,一个对象代表了某个具体的操作。一个个对象最终组成了完整的程序设计,这些对象可以是独立存在的,也可以是从别的对象继承过来的。对象之间通过相互作用传递信息,实现程序开发。
对象的概念
Java 是面向对象的编程语言,对象就是面向对象程序设计的核心。所谓对象就是真实世界中的实体,对象与实体是一一对应的,也就是说现实世界中每一个实体都是一个对象,它是一种具体的概念。对象有以下特点:
- 对象具有属性和行为。
- 对象具有变化的状态。
- 对象具有唯一性。
- 对象都是某个类别的实例。
- 一切皆为对象,真实世界中的所有事物都可以视为对象。
面向对象的三大核心特性 面向对象开发模式更有利于人们开拓思维,在具体的开发过程中便于程序的划分,方便程序员分工合作,提高开发效率。
面向对象程序设计有以下优点。
- 可重用性:代码重复使用,减少代码量,提高开发效率。
- 可扩展性:指新的功能可以很容易地加入到系统中来,便于软件的修改。
- 可管理性:能够将功能与数据结合,方便管理。
继承性
就如同生活中的子女继承父母拥有的所有财产,程序中的继承性是指子类拥有父类的全部特征和行为,这是类之间的一种关系。
- 继承的本质,是对某一批类的抽象,从而实现对现实世界更好的建模;
- 继承的关键字是extands ,意思是“扩展”。就是说子类是父类的扩展;
- java中类只有单继承,没有多继承;
- 继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。
- 继承关系的两个类,一个为子类(派生类),一个为父类(基类)。子类继承父类,使用关键字extends来标示
文章图片
img 从图中能够看出,语文老师类和数学老师类中的许多属性和方法相同,这些相同的属性和方法可以提取出来放在一个父类中,这个父类用于被语文老师类和数学老师类继承。当然父类还可以继承别的类,如下图所示。
文章图片
img 总结上图的继承关系,可以用概括的树形关系来表示,如图下图所示。
文章图片
img 从上图中可以看出,学校主要人员是一个大的类别,老师和学生是学校主要人员的两个子类,而老师又可以分为语文老师和数学老师两个子类,学生也可以分为班长和组长两个子类。
使用这种层次形的分类方式,是为了将多个类的通用属性和方法提取出来,放在它们的父类中,然后只需要在子类中各自定义自己独有的属性和方法,并以继承的形式在父类中获取它们的通用属性和方法即可。
提示:C++ 支持多继承,多继承就是一个子类可有多个父类。例如,客轮是轮船也是交通工具,客轮的父类是轮船和交通工具。多继承会引起很多冲突问题,因此现在很多面向对象的语言都不支持多继承。Java 语言是单继承的,即只能有一个父类,但 Java 可以实现多个接口(接口类似于类,但接口的成员没有执行体),可以防止多继承所引起的冲突问题。
封装性
封装是将代码及其处理的数据绑定在一起的一种编程机制,该机制保证了程序和数据都不受外部干扰且不被误用。封装的目的在于保护信息,使用它的主要优点如下。
- 保护类中的信息,它可以阻止在外部定义的代码随意访问内部代码和数据。
- 隐藏细节信息,一些不需要程序员修改和使用的信息,比如取款机中的键盘,用户只需要知道按哪个键实现什么操作就可以,至于它内部是如何运行的,用户不需要知道。
- 有助于建立各个系统之间的松耦合关系,提高系统的独立性。当一个系统的实现方式发生变化时,只要它的接口不变,就不会影响其他系统的使用。例如 U 盘,不管里面的存储方式怎么改变,只要 U 盘上的 USB 接口不变,就不会影响用户的正常操作。
- 提高软件的复用率,降低成本。每个系统都是一个相对独立的整体,可以在不同的环境中得到使用。例如,一个 U 盘可以在多台电脑上使用。
多态性
面向对象的多态性,即“一个接口,多个方法”。多态性体现在父类中定义的属性和方法被子类继承后,可以具有不同的属性或表现方式。多态性允许一个接口被多个同类使用,弥补了单继承的不足。多态概念可以用树形关系来表示,如图所示。
文章图片
img 从图 中可以看出,老师类中的许多属性和方法可以被语文老师类和数学老师类同时使用,这样也不易出错。
Java类和对象 在面向对象中,类和对象是最基本、最重要的组成单元。类实际上是表示一个客观世界某类群体的一些基本特征抽象。对象就是表示一个个具体的东西。所以说类是对象的抽象,对象是类的具体。
让我们来看看人类所具有的一些特征,这些特征包括属性(一些参数、数值)以及方法(一些行为,他能干什么)。
每个人都有身高、体重、年龄、血型等属性,人会劳动、会直立行走、会用自己的头脑去创造工具等方法。人之所以能区别于其他类型的动物,是因为每个人都具有“人”这个群体的属性与方法。
“人类”只是一个抽象的概念,它仅仅是一个概念,是不存在的实体!但是所有具备“人类”这个群体的属性与方法的对象都叫人!这个对象“人” 是实际存在的实体!每个人都是“人”这个群体的一个对象。
老虎为什么不是人?因为它不具备“人”这个群体的属性与方法,老虎不会直立行走,不会使用工具等,所以说老虎不是人!也就是说,类是概念模型,定义对象的所有特性和所需的操作,对象是真实的模型,是一个具体的实体。
由此可见,类是描述了一组有相同特性(属性)和相同行为(方法)的一组对象的集合。
对象或实体所拥有的特征在类中表示时称为类的属性。例如,每个人都具有姓名、年龄和体重,这是所有人共有的特征。但是每一个对象的属性值又各不相同,例如,小明和小红都具有体重这个属性,但是他们的体重值是不同的。
对象执行的操作称为类的方法。比如,“人”这个对象都具有的行为是“吃饭”,因此,吃饭就是“人”类的一个方法。
综上所述,类是描述实体的“模板”和“原型”,它定义了属于这个类的对象所应该具有的状态和行为。比如一名学生在上课。一名正在上课的学生是类,它定义的信息有:姓名、上课。
使用该类定义的不同姓名的人在上课是对象,他们可能是小明、小红、小丽、张会等。在 Java 面向对象编程中,用自定义的类模型可以创建该类的一个实例,也就是对象。
类是实体对象的概念模型,因此通常是笼统的、不具体的。
类是构造面向对象程序的基本单位,是抽取了同类对象的共同属性和方法所形成的对象或实体的“模板”。而对象是现实世界中实体的描述,对象要创建才存在,有了对象才能对对象进行操作。类是对象的模板,对象是类的实例。
Java类的定义
类是 Java 中的一种重要的引用数据类型,也是组成 Java 程序的基本要素,因为所有的 Java 程序都是基于类的。。
在 Java 中定义一个类,需要使用 class 关键字、一个自定义的类名和一对表示程序体的大括号。完整语法如下:
[public][abstract|final]class[extends][implements] {
// 定义属性部分;
;
;
…
// 定义方法部分
function1();
function2();
function3();
…
}
提示:上述语法中,中括号“[]”中的部分表示可以省略,竖线“|”表示“或关系”,例如 abstract|final,说明可以使用 abstract 或 final 关键字,但是两个关键字不能同时出现。
上述语法中各关键字的描述如下。
-
public
:表示“共有”的意思。如果使用 public 修饰,则可以被其他类和程序访问。每个 Java 程序的主类都必须是 public 类,作为公共工具供其他类和程序使用的类应定义为 public 类。 -
abstract
:如果类被 abstract 修饰,则该类为抽象类,抽象类不能被实例化,但抽象类中可以有抽象方法(使用 abstract 修饰的方法)和具体方法(没有使用 abstract 修饰的方法)。继承该抽象类的所有子类都必须实现该抽象类中的所有抽象方法(除非子类也是抽象类)。 -
final
:如果类被 final 修饰,则不允许被继承。 -
class
:声明类的关键字。 -
class_name
:类的名称。 -
extends
:表示继承其他类。 -
implements
:表示实现某些接口。 -
property_type
:表示成员变量的类型。 -
property
:表示成员变量名称。 -
function()
:表示成员方法名称。
(1) 声明类。编写类的最外层框架,声明一个名称为 Person 的类。
public class Person {// 类的主体}
(2) 编写类的属性。类中的数据和方法统称为类成员。其中,类的属性就是类的数据成员。通过在类的主体中定义变量来描述类所具有的特征(属性),这里声明的变量称为类的成员变量。
(3) 编写类的方法。类的方法描述了类所具有的行为,是类的方法成员。可以简单地把方法理解为独立完成某个功能的单元模块。
下面来定义一个简单的 Person 类。
public class Person {
private String name;
// 姓名
private int age;
// 年龄
public void tell() {
// 定义说话的方法
System.out.println(name+"今年"+age+"岁!");
}
}
如上述代码,在 Person 类中首先定义了两个属性,分别为 name 和 age,然后定义了一个名称为 tell() 的方法。
java类属性 在 Java 中类的成员变量定义了类的属性。
例如,一个学生类中一般需要有姓名、性别和年龄等属性,这时就需要定义姓名、性别和年龄 3 个属性。声明成员变量的语法如下:
[public|protected|private][static][final]
各参数的含义如下。
- public、protected、private:用于表示成员变量的访问权限。
- static:表示该成员变量为类变量,也称为静态变量。
- final:表示将该成员变量声明为常量,其值无法更改。
- type:表示变量的类型。
- variable_name:表示变量名称。
【JAVA基础,面向对象】初始化的默认值如下:
- 整数型(byte、short、int 和 long)的基本类型变量的默认值为 0。
- 单精度浮点型(float)的基本类型变量的默认值为 0.0f。
- 双精度浮点型(double)的基本类型变量的默认值为 0.0d。
- 字符型(char)的基本类型变量的默认值为 “\u0000”。
- 布尔型的基本类型变量的默认值为 false。
- 数组引用类型的变量的默认值为 null。如果创建了数组变量的实例,但没有显式地为每个元素赋值,则数组中的元素初始化值采用数组数据类型对应的默认值。
public class Student {
public String name;
// 姓名
final int sex = 0;
// 性别:0表示女孩,1表示男孩
private int age;
// 年龄
}
上述示例的 Student 类中定义了 3 个成员变量:String 类型的 name、int 类型的 sex 和 int 类型的 age。其中,name 的访问修饰符为 public,初始化值为 null;sex 的访问修饰符为 friendly(默认),初始化值为 0,表示性别为女,且其值无法更改;age 的访问修饰符为 private,初始化值为 0。
下面以一个简单的例子来介绍成员变量的初始值,代码如下所示。
public class Counter {
static int sum;
public static void main(String[] args) {
System.out.println(sum);
}
}
在这里用静态的方法来修饰变量 sum,输出结果是 int 类型的初始值,即:0。
Java成员方法 声明成员方法可以定义类的行为,行为表示一个对象能够做的事情或者能够从一个对象取得的信息。类的各种功能操作都是用方法来实现的,属性只不过提供了相应的数据。一个完整的方法通常包括方法名称、方法主体、方法参数和方法返回值类型,其结构如图所示。
文章图片
img 成员方法一旦被定义,便可以在程序中多次调用,提高了编程效率。声明成员方法的语法格式如下:
public class Test {
[public|private|protected][static]([paramList]) {
// 方法体
}
}
注意:上述语法中,中括号“[]”中的部分表示可以省略,竖线“|”表示“或”,例如 public|private,说明可以使用 public 或 private 关键字,但是两个关键字不能同时出现。
上述代码中一个方法包含 4 部分:方法的返回值、方法名称、方法的参数和方法体。其中 retum_type 是方法返回值的数据类型,数据类型可以是原始的数据类型,即常用的 8 种数据类型,也可以是一个引用数据类型,如一个类、接口和数组等。
除了这些,一个方法还可以没有返回值,即返回类型为 void,像 main() 方法。method_name 表示自定义的方法名称,方法的名称首先要遵循标识符的命名约定,除此之外,方法的名称第一个单词的第一个字母是小写,第二单词的第一个字母是大写,依此类推。
paramList 表示参数列表,这些变量都要有自己的数据类型,可以是原始数据类型,也可以是复杂数据类型,一个方法主要依靠参数来传递消息。方法主体是方法中执行功能操作的语句。其他各修饰符的含义如下。
- public、private、protected:表示成员方法的访问权限。
- static:表示限定该成员方法为静态方法。
- final:表示限定该成员方法不能被重写或重载。
- abstract:表示限定该成员方法为抽象方法。抽象方法不提供具体的实现,并且所属类型必须为抽象类。
return 表达式
或者
return (表达式)
其中,表达式可以是常量、变量、对象等。表达式的数据类型必须与声明成员方法时给出的返回值类型一致。
2. 形参、实参及成员方法的调用 一般来说,可以通过以下方式来调用成员方法:
methodName({paramList})
关于方法的参数,经常会提到形参与实参,形参是定义方法时参数列表中出现的参数,实参是调用方法时为方法传递的参数。
下面 retumMin() 方法中的 m 和 n 是形参,调用 retumMin() 方法时的 x 和 y 是实参。
public int returnMin(int m,int n) {
return Math.min(m,n);
// m和n是形参
}public static void main(String[] args) {
int x = 50;
int y = 100;
Test t = new Test();
int i = t.returnMin(x,y);
// x和y是实参
System.out.println(i);
}
方法的形参和实参具有以下特点:
- 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在方法内部有效,方法调用结束返回主调方法后则不能再使用该形参变量。
- 实参可以是常量、变量、表达式、方法等,无论实参是何种类型的量,在进行方法调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值、输入等办法使实参获得确定值。
- 实参和形参在数量、类型和顺序上应严格一致,否则会发生“类型不匹配” 的错误。
- 方法调用中发生的数据传送是单向的,即只能把实参的值传送绐形参,而不能把形参的值反向地传送给实参。因此在方法调用过程中,形参的值发生改变,而实参中的值不会变化。
public int add(int x) {
x += 30;
System.out.println("形参 x 的值:"+x);
return x;
}public static void main(String[] args) {
int x = 150;
System.out.println("调用 add() 方法之前 x 的值:"+x);
Test t = new Test();
int i = t.add(x);
System.out.println("实参 x 的值:"+x);
System.out.println("调用 add() 方法的返回值:"+i);
}
运行上述程序,输出结果如下:
调用 add() 方法之前 x 的值:150
形参 x 的值:180
实参 x 的值:150
调用 add() 方法的返回值:180
从输出结果可以看出,形参 x 值的改变,并没有影响实参 x。
在调用成员方法时应注意以下 4 点:
- 对无参成员方法来说,是没有实际参数列表的(即没有 paramList),但方法名后的括号不能省略。
- 对带参数的成员方法来说,实参的个数、顺序以及它们的数据类型必须与形式参数的个数、顺序以及它们的数据类型保持一致,各个实参间用逗号分隔。实参名与形参名可以相同,也可以不同。
- 实参也可以是表达式,此时一定要注意使表达式的数据类型与形参的数据类型相同,或者使表达式的类型按 Java 类型转换规则达到形参指明的数据类型。
- 实参变量对形参变量的数据传递是“值传递”,即只能由实参传递给形参,而不能由形参传递给实参。程序中执行到调用成员方法时,Java 把实参值复制到一个临时的存储区(栈)中,形参的任何修改都在栈中进行,当退出该成员方法时,Java 自动清除栈中的内容。
在方法体内定义变量时,变量前不能加修饰符。局部变量在使用前必须明确赋值,否则编译时会出错。另外,在一个方法内部,可以在复合语句(把多个语句用括号
{}
括起来组成的一个语句称复合语句)中定义变量,这些变量只在复合语句中有效。Java this关键字 this 关键字是 Java常用的关键字,可用于任何实例方法内指向当前对象,也可指向对其调用当前方法的对象,或者在需要当前类型对象引用时使用。下面我们根据示例分别讲解 this 关键字的作用。
this.属性名 大部分时候,普通方法访问其他方法、成员变量时无须使用 this 前缀,但如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用 this 前缀。
假设有一个教师类 Teacher 的定义如下:
public class Teacher {
private String name;
// 教师名称
private double salary;
// 工资
private int age;
// 年龄
}
在上述代码中 name、salary 和 age 的作用域是 private,因此在类外部无法对它们的值进行设置。为了解决这个问题,可以为 Teacher 类添加一个构造方法,然后在构造方法中传递参数进行修改。代码如下:
// 创建构造方法,为上面的3个属性赋初始值
public Teacher(String name,double salary,int age) {
this.name = name;
// 设置教师名称
this.salary = salary;
// 设置教师工资
this.age = age;
// 设置教师年龄
}
在 Teacher 类的构造方法中使用了 this 关键字对属性 name、salary 和 age 赋值,this 表示当前对象。
this.name=name
语句表示一个赋值语句,等号左边的 this.name 是指当前对象具有的变量 name,等号右边的 name 表示参数传递过来的数值。提示:当一个类的属性(成员变量)名与访问该属性的方法参数名相同时,则需要使用 this 关键字来访问类中的属性,以区分类的属性和方法中的参数。
this.方法名 this 关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或实例变量。
假设定义了一个 Dog 类,这个 Dog 对象的 run( ) 方法需要调用它的 jump( ) 方法,Dog 类的代码如下所示:
/** * 第一种定义Dog类方法 **/
public class Dog {
// 定义一个jump()方法
public void jump() {
System.out.println("正在执行jump方法");
}
// 定义一个run()方法,run()方法需要借助jump()方法
public void run() {
Dog d = new Dog();
d.jump();
System.out.println("正在执行 run 方法");
}
}
使用这种方式来定义这个 Dog 类,确实可以实现在 run( ) 方法中调用 jump( ) 方法。下面再提供一个程序来创建 Dog 对象,并调用该对象的 run( ) 方法。
public class DogTest {
public static void main(String[] args) {
// 创建Dog对象
Dog dog = new Dog();
// 调用Dog对象的run()方法
dog.run();
}
}
在上面的程序中,一共产生了两个 Dog 对象,在 Dog 类的 run( ) 方法中,程序创建了一个 Dog 对象,并使用名为 d 的引用变量来指向该 Dog 对象。在 DogTest 的 main() 方法中,程序再次创建了一个 Dog 对象,并使用名为 dog 的引用变量来指向该 Dog 对象。
下面我们思考两个问题。
1)在 run( ) 方法中调用 jump( ) 方法时是否一定需要一个 Dog 对象?
答案是肯定的,因为没有使用 static 修饰的成员变量和方法都必须使用对象来调用。
2)是否一定需要重新创建一个 Dog 对象?
不一定,因为当程序调用 run( ) 方法时,一定会提供一个 Dog 对象,这样就可以直接使用这个已经存在的 Dog 对象,而无须重新创建新的 Dog 对象了。因此需要在 run() 方法中获得调用该方法的对象,通过 this 关键字就可以满足这个要求。
this 可以代表任何对象,当 this 出现在某个方法体中时,它所代表的对象是不确定的,但它的类型是确定的,它所代表的只能是当前类的实例。只有当这个方法被调用时,它所代表的对象才被确定下来,谁在调用这个方法,this 就代表谁。
将前面的 Dog 类的 run( ) 方法改为如下形式会更加合适,run( ) 方法代码修改如下,其它代码不变。
/** * 第二种定义Dog类方法 **/// 定义一个run()方法,run()方法需要借助jump()方法
public void run() {
// 使用this引用调用run()方法的对象
this.jump();
System.out.println("正在执行run方法");
}
从第一种 Dog 类定义来看,在 Dog 对象的 run( ) 方法内重新创建了一个新的 Dog 对象,并调用它的 jump( ) 方法,这意味着一个 Dog 对象的 run( ) 方法需要依赖于另一个 Dog 对象的 jump( ) 方法,这不符合逻辑。
第二种 Dog 类定义是当一个 Dog 对象调用 run( ) 方法时,run( ) 方法需要依赖它自己的 jump( ) 方法,与第一种定义类的方法相比,更符合实际情形。
在现实世界里,对象的一个方法依赖于另一个方法的情形很常见,例如,吃饭方法依赖于拿筷子方法,写程序方法依赖于敲键盘方法。这种依赖都是同一个对象两个方法之间的依赖。因此,Java 允许对象的一个成员直接调用另一个成员,可以省略 this 前缀。也就是说,将上面的 run( ) 方法改为如下形式也完全正确。
public void run() {
jump();
System.out.println("正在执行run方法");
}
大部分时候,一个方法访问该类中定义的其他方法、成员变量时加不加 this 前缀的效果是完全一样的。
注意:对于 static 修饰的方法而言,可以使用类来直接调用该方法,如果在 static 修饰的方法中使用 this 关键字,则这个关键字就无法指向合适的对象。所以,static 修饰的方法中不能使用 this 引用。并且 Java 语法规定,静态成员不能直接访问非静态成员。
省略 this 前缀只是一种假象,虽然程序员省略了调用 jump() 方法之前的 this,但实际上这个 this 依然是存在的。
this( )访问构造方法 this( ) 用来访问本类的构造方法(构造方法是类的一种特殊方法,方法名称和类名相同,没有返回值。括号中可以有参数,如果有参数就是调用指定的有参构造方法。
注意:this( ) 不能在普通方法中使用,只能写在构造方法中。在构造方法中使用时,必须是第一条语句。
Java对象的创建
对象是对类的实例化。对象具有状态和行为,变量用来表明对象的状态,方法表明对象所具有的行为。Java 对象的生命周期包括创建、使用和清除,在 Java 语言中创建对象分显式创建与隐含创建两种情况。
显式创建对象 对象的显式创建方式有 4 种。
1. 使用 new 关键字创建对象 这是常用的创建对象的方法,语法格式如下:
类名 对象名 = new 类名();
2. 调用newlnstance() 实例方法 在 Java 中,可以使用 java.lang.Class 或者 java.lang.reflect.Constuctor 类的 newlnstance() 实例方法来创建对象,代码格式如下:
java.lang.Class Class 类对象名称 = java.lang.Class.forName(要实例化的类全称);
类名 对象名 = (类名)Class类对象名称.newInstance();
调用 java.lang.Class 类中的 forName() 方法时,需要将要实例化的类的全称(比如 com.mxl.package.Student)作为参数传递过去,然后再调用 java.lang.Class 类对象的 newInstance() 方法创建对象。
3. 调用对象的 clone() 方法 该方法不常用,使用该方法创建对象时,要实例化的类必须继承 java.lang.Cloneable 接口。语法格式如下:
类名对象名 = (类名)已创建好的类对象名.clone();
4. 调用 readObject() 方法 java中可以通过调用 java.io.ObjectlnputStream 对象的 readObject() 方法创建,下面创建一个示例演示常用的前三种对象创建方法。
public class Student implements Cloneable {
// 实现 Cloneable 接口
private String Name;
// 学生名字
private int age;
// 学生年龄
public Student(String name,int age) {
// 构造方法
this.Name = name;
this.age = age;
}public Student() {
this.Name = "name";
this.age = 0;
} public String toString() {
return"学生名字:"+Name+",年龄:"+age;
}public static void main(String[] args)throws Exception {
System.out.println("---------使用 new 关键字创建对象---------");
// 使用new关键字创建对象
Student student1 = new Student("小刘",22);
System.out.println(student1);
System.out.println("-----------调用 java.lang.Class 的 newInstance() 方法创建对象-----------");
// 调用 java.lang.Class 的 newInstance() 方法创建对象
Class c1 = Class.forName("Student");
Student student2 = (Student)c1.newInstance();
System.out.println(student2);
System.out.println("-------------------调用对象的 clone() 方法创建对象----------");
// 调用对象的 clone() 方法创建对象
Student student3 = (Student)student2.clone();
System.out.println(student3);
}
}
对上述示例的说明如下:
- 使用 new 关键字或 Class 对象的 newInstance() 方法创建对象时,都会调用类的构造方法。
- 使用 Class 类的 newInstance() 方法创建对象时,会调用类的默认构造方法,即无参构造方法。
- 使用 Object 类的 clone() 方法创建对象时,不会调用类的构造方法,它会创建一个复制的对象,这个对象和原来的对象具有不同的内存地址,但它们的属性值相同。
- 如果类没有实现 Cloneable 接口,则 clone。方法会抛出 java.lang.CloneNotSupportedException 异常,所以应该让类实现 Cloneable 接口。
1)String strName = "strValue",其中的“strValue”就是一个 String 对象,由 Java 虚拟机隐含地创建。
2)字符串的“+”运算符运算的结果为一个新的 String 对象,示例如下:
String str1 = "Hello";
String str2 = "Java";
String str3 = str1+str2;
// str3引用一个新的String对象
3)当 Java 虚拟机加载一个类时,会隐含地创建描述这个类的 Class 实例。
提示:类的加载是指把类的 .class 文件中的二进制数据读入内存中,把它存放在运行时数据区的方法区内,然后在堆区创建一个 java.lang.Class 对象,用来封装类在方法区内的数据结构。
无论釆用哪种方式创建对象,Java 虚拟机在创建一个对象时都包含以下步骤:
- 给对象分配内存。
- 将对象的实例变量自动初始化为其变量类型的默认值。
- 初始化对象,给实例变量赋予正确的初始值。
java匿名对象
每次实例化对象都需要通过new关键字,每次 new 都相当于开辟了一个新的对象,并开辟了一个新的物理内存空间。如果一个对象只需要使用唯一的一次,就可以使用匿名对象,匿名对象还可以作为实际参数传递。
匿名对象就是没有明确的给出名字的对象,是对象的一种简写形式。一般匿名对象只使用一次,而且匿名对象只在堆内存中开辟空间,而不存在栈内存的引用。
直接使用了“new Person("张三",30)”语句,这实际上就是一个匿名对象,与之前声明的对象不同,此处没有任何栈内存引用它,所以此对象使用一次之后就等待被 GC(垃圾收集机制)回收。
匿名对象在实际开发中基本都是作为其他类实例化对象的参数传递的,在后面的 Java应用部分的很多地方都可以发现其用法,匿名对象实际上就是个堆内存空间,对象不管是匿名的还是非匿名的,都必须在开辟堆空间之后才可以使用。
java对象访问
每个对象都有自己的属性和行为,这些属性和行为在类中体现为成员变量和成员方法,其中成员变量对应对象的属性,成员方法对应对象的行为。
在 Java 中,要引用对象的属性和行为,需要使用点(.)操作符来访问。对象名在圆点左边,而成员变量或成员方法的名称在圆点的右边。语法格式如下:
对象名.属性(成员变量)// 访问对象的属性
对象名.成员方法名()// 访问对象的方法
例如,定义一个 Student 类,创建该类的对象 stu,再对该对象的属性赋值,代码如下:
Student stu = new Student();
// 创建 Student 类的对象
stustu.Name = "李子文";
// 调用stu对象的Name属性并赋值
stu.Sex = true;
// 调用stu对象的Sex属性并赋值
stu.Age = 15;
// 调用stu对象的Age属性并赋值
如果一个对象要被使用,则对象必须被实例化,如果一个对象没有被实例化而直接调用了对象中的属性或方法,如下代码所示:
Student stu = null;
stu.Name = "李子文";
stu.Sex = true;
stu.Age = 15;
则程序运行时会出现以下异常:
Exception in thread "main" java.lang.NullPointerException
此异常是开发中最常见的异常,也会始终伴随着每位开发人员,使用了未实例化的对象则肯定会出现此异常。
Java对象的销毁
对象使用完之后需要对其进行清除。对象的清除是指释放对象占用的内存。在创建对象时,用户必须使用 new 操作符为对象分配内存。不过,在清除对象时,由系统自动进行内存回收,不需要用户额外处理。这也是 Java 语言的一大特色,某种程度上方便了程序员对内存的管理。
Java 语言的内存自动回收称为垃圾回收(Garbage Collection)机制,简称 GC。垃圾回收机制是指 JVM 用于释放那些不再使用的对象所占用的内存。
Java 语言并不要求 JVM 有 GC,也没有规定 GC 如何工作。不过常用的 JVM 都有 GC,而且大多数 GC 都使用类似的算法管理内存和执行回收操作。具体的垃圾回收实现策略有好多种,在此不再赘述。
注意:C++语言对象是通过 delete 语句手动释放。如果回收内存的任务由程序负责,也就是说必须在程序中显式地进行内存回收,这无疑会增加程序员的负担,而且存在很多弊端。Java 语言对象是由垃圾回收器收集然后释放,程序员不用关系释放的细节。自动内存管理是现代计算机语言发展趋势,例如:C#语言的垃圾回收,Objective-C 和 Swift 语言的 ARC(内存自动引用计数管理)。
一个对象被当作垃圾回收的情况主要如下两种。
1)对象的引用超过其作用范围。
{
Object o = new Object();
// 对象o的作用范围,超过这个范围对象将被视为垃圾
}
2)对象被赋值为 null。
{
Object o = new Object();
o = null;
// 对象被赋值为null将被视为垃圾
}
在 Java 的 Object 类中还提供了一个 protected 类型的 finalize() 方法,因此任何 Java 类都可以覆盖这个方法,在这个方法中进行释放对象所占有的相关资源的操作。
在 Java 虚拟机的堆区,每个对象都可能处于以下三种状态之一。
1)可触及状态:当一个对象被创建后,只要程序中还有引用变量引用它,那么它就始终处于可触及状态。
2)可复活状态:当程序不再有任何引用变量引用该对象时,该对象就进入可复活状态。在这个状态下,垃圾回收器会准备释放它所占用的内存,在释放之前,会调用它及其他处于可复活状态的对象的 finalize() 方法,这些 finalize() 方法可能使该对象重新转到可触及状态。
3)不可触及状态:当 Java 虚拟机执行完所有可复活对象的 finalize() 方法后,如果这些方法都没有使该对象转到可触及状态,垃圾回收器才会真正回收它占用的内存。
注意:调用 System.gc() 或者 Runtime.gc() 方法不能保证回收操作一定执行,它只是提高了 Java 垃圾回收器尽快回收垃圾的可能性。
封装 封装是将类的某些信息隐藏在类内部,不允许外部程序直接访问,只能通过该类提供的方法来实现对隐藏信息的操作和访问。例如:一台计算机内部极其复杂,有主板、CPU、硬盘和内存, 而一般用户不需要了解它的内部细节,不需要知道主板的型号、CPU 主频、硬盘和内存的大小,于是计算机制造商将用机箱把计算机封装起来,对外提供了一些接口,如鼠标、键盘和显示器等,这样当用户使用计算机就非常方便。
封装的特点:
- 只能通过规定的方法访问数据。
- 隐藏类的实例细节,方便修改和实现。
- 修改属性的可见性来限制对属性的访问,一般设为 private。
- 为每个属性创建一对赋值(setter)方法和取值(getter)方法,一般设为 public,用于属性的读写。
- 在赋值和取值方法中,加入属性控制语句(对属性值的合法性进行判断)。
一个员工的主要属性有姓名、年龄、联系电话和家庭住址。假设员工类为 Employee,示例如下:
public class Employee {
private String name;
// 姓名
private int age;
// 年龄
private String phone;
// 联系电话
private String address;
// 家庭住址public String getName() {
return name;
}public void setName(String name) {
this.name = name;
}public int getAge() {
return age;
}public void setAge(int age) {
// 对年龄进行限制
if (age < 18 || age > 40) {
System.out.println("年龄必须在18到40之间!");
this.age = 20;
// 默认年龄
} else {
this.age = age;
}
}public String getPhone() {
return phone;
}public void setPhone(String phone) {
this.phone = phone;
}public String getAddress() {
return address;
}public void setAddress(String address) {
this.address = address;
}
}
如上述代码所示,使用 private 关键字修饰属性,这就意味着除了 Employee 类本身外,其他任何类都不可以访问这些属性。但是,可以通过这些属性的 setXxx() 方法来对其进行赋值,通过 getXxx() 方法来访问这些属性。
在 age 属性的 setAge() 方法中,首先对用户传递过来的参数 age 进行判断,如果 age 的值不在 18 到 40 之间,则将 Employee 类的 age 属性值设置为 20,否则为传递过来的参数值。
编写测试类 EmployeeTest,在该类的 main() 方法中调用 Employee 属性的 setXxx() 方法对其相应的属性进行赋值,并调用 getXxx() 方法访问属性,代码如下:
public class EmployeeTest {
public static void main(String[] args) {
Employee people = new Employee();
people.setName("王丽丽");
people.setAge(35);
people.setPhone("13653835964");
people.setAddress("河北省石家庄市");
System.out.println("姓名:" + people.getName());
System.out.println("年龄:" + people.getAge());
System.out.println("电话:" + people.getPhone());
System.out.println("家庭住址:" + people.getAddress());
}
}
运行该示例,输出结果如下:
姓名:王丽丽
年龄:35
电话:13653835964
家庭住址:河北省石家庄市
通过封装,实现了对属性的数据访问限制,满足了年龄的条件。在属性的赋值方法中可以对属性进行限制操作,从而给类中的属性赋予合理的值, 并通过取值方法获取类中属性的值(也可以直接调用类中的属性名称来获取属性值)。
继承 继承是面向对象的三大特征之一。继承和现实生活中的“继承”的相似之处是保留一些父辈的特性,减少代码冗余,提高程序运行效率。
Java 中的继承就是在已经存在类的基础上进行扩展,从而产生新的类。已经存在的类称为父类、基类或超类,而新产生的类称为子类或派生类。在子类中,不仅包含父类的属性和方法,还可以增加新的属性和方法。
Java 中子类继承父类的语法格式如下:
修饰符 class class_name extends extend_class {
// 类的主体
}
其中,class_name 表示子类(派生类)的名称;extend_class 表示父类(基类)的名称;extends 关键字直接跟在子类名之后,其后面是该类要继承的父类名称。例如:
public class Student extends Person{}
Java 的继承通过 extends 关键字来实现,extends 的英文意思是扩展,而不是继承。extends 很好的体现了子类和父类的关系,即子类是对父类的扩展,子类是一种特殊的父类。从这个角度看,使用继承来描述子类和父类的关系是错误的,用扩展更恰当。
那么为什么国内把 extends 翻译为“继承”呢?子类扩展父类之后就可以获得父类的属性和方法,这与汉语中的继承(子类从父类获得一笔财富称为继承)具有相似性。
类的继承不改变类成员的访问权限,也就是说,如果父类的成员是公有的、被保护的或默认的,它的子类仍具有相应的这些特性,并且子类不能获得父类的构造方法。
单继承
Java 语言摒弃了 C++ 中难以理解的多继承特征,即 Java 不支持多继承,只允许一个类直接继承另一个类,即子类只能有一个直接父类,extends 关键字后面只能有一个类名。例如,如下代码会导致编译错误:
class Student extends Person,Person1,Person2{…}
class Student extends Person,extends Person1,extends Person2{…}
很多地方在介绍 Java 的单继承时,可能会说 Java 类只能有一个父类,严格来讲,这种说法是错误的,应该是一个类只能有一个直接父类,但是它可以有多个间接的父类。例如,Student 类继承 Person 类,Person 类继承 Person1 类,Person1 类继承 Person2 类,那么 Person1 和 Person2 类是 Student 类的间接父类。
文章图片
img 从图 中可以看出,三角形、四边形和五边形的直接父类是多边形类,它们的间接父类是图形类。图形类、多边形类和三角形、四边形、五边形类形成了一个继承的分支。在这个分支上,位于下层的子类会继承上层所有直接或间接父类的属性和方法。如果两个类不在同一个继承树分支上,就不会存在继承关系,例如多边形类和直线。
如果定义一个 Java 类时并未显式指定这个类的直接父类,则这个类默认继承 java.lang.Object 类。因此,java.lang.Object 类是所有类的父类,要么是其直接父类,要么是其间接父类。因此所有的 Java 对象都可调用 java.lang.Object 类所定义的实例方法。
使用继承的注意点:
- 子类一般比父类包含更多的属性和方法。
- 父类中的 private 成员在子类中是不可见的,因此在子类中不能直接使用它们。
- 父类和其子类间必须存在“是一个”即“is-a”的关系,否则不能用继承。但也并不是所有符合“is-a”关系的都应该用继承。例如,正方形是一个矩形,但不能让正方形类来继承矩形类,因为正方形不能从矩形扩展得到任何东西。正确的继承关系是正方形类继承图形类。
- Java 只允许单一继承(即一个子类只能有一个直接父类),C++ 可以多重继承(即一个子类有多个直接父类)。
- 实现代码共享,减少创建类的工作量,使子类可以拥有父类的方法和属性。
- 提高代码维护性和可重用性。
- 提高代码的可扩展性,更好的实现父类的方法。
- 继承是侵入性的。只要继承,就必须拥有父类的属性和方法。
- 降低代码灵活性。子类拥有父类的属性和方法后多了些约束。
- 增强代码耦合性(开发项目的原则为高内聚低耦合)。当父类的常量、变量和方法被修改时,需要考虑子类的修改,有可能会导致大段的代码需要重构。
对面向对象来说,多态分为编译时多态和运行时多态。其中编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法。通过编译之后会变成两个不同的方法,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是大家通常所说的多态性。
Java 实现多态有 3 个必要条件:继承、重写和向上转型。只有满足这 3 个条件,开发人员才能够在同一个继承结构中使用统一的逻辑实现代码处理不同的对象,从而执行不同的行为。
- 继承:在多态中必须存在有继承关系的子类和父类。
- 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。
- 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才既能可以调用父类的方法,又能调用子类的方法。
Java 允许同一个类中定义多个同名方法,只要它们的形参列表不同即可。如果同一个类中包含了两个或两个以上方法名相同的方法,但形参列表不同,这种情况被称为方法重载(overload)。
方法重载的要求是两同一不同:同一个类中,方法名字相同,而参数列表不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载。
重写(Override)
子类中如果创建一个与父类中相同名称、相同返回值类型、相同参数列表的方法,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写(override),又称为方法覆盖。当父类中的方法无法满足子类需求或子类具有特有功能的时候,需要重写。
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。
在重写方法时,需要遵循下面的规则:
- 参数列表必须完全与被重写的方法参数列表相同。
- 返回的类型必须与被重写的方法的返回类型相同(Java1.5 版本之前返回值类型必须一样,之后的 Java 版本放宽了限制,返回值类型必须小于或者等于父类方法的返回值类型)。
- 访问权限不能比父类中被重写方法的访问权限更低(public>protected>default>private)。
- 重写方法一定不能抛出新的检査异常或者比被重写方法声明更加宽泛的检査型异常。例如,父类的一个方法声明了一个检査异常 IOException,在重写这个方法时就不能抛出 Exception,只能拋出 IOException 的子类异常,可以抛出非检査异常。
- 重写的方法可以使用 @Override 注解来标识。
- 父类的成员方法只能被它的子类重写。
- 声明为 final 的方法不能被重写。
- 声明为 static 的方法不能被重写,但是能够再次声明。
- 构造方法不能被重写。
- 子类和父类在同一个包中时,子类可以重写父类的所有方法,除了声明为 private 和 final 的方法。
- 子类和父类不在同一个包中时,子类只能重写父类的声明为 public 和 protected 的非 final 方法。
- 如果不能继承一个方法,则不能重写这个方法。
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- 方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同
- 方法重写是在子类存在方法与父类的方法的名字,参数和返回值都相同
- 方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
文章图片
img 包机制 在编写 Java 程序时,随着程序架构越来越大,类的个数也越来越多,这时就会发现管理程序中维护类名称也是一件很麻烦的事,尤其是一些同名问题的发生。有时,开发人员还可能需要将处理同一方面的问题的类放在同一个目录下,以便于管理。
为了解决上述问题,Java 引入了包(package)机制,提供了类的多层命名空间,用于解决类的命名冲突、类文件管理等问题。
包允许将类组合成较小的单元(类似文件夹),它基本上隐藏了类,并避免了名称上的冲突。包允许在更广泛的范围内保护类、数据和方法。你可以在包内定义类,而在包外的代码不能访问该类。这使你的类相互之间有隐私,但不被其他世界所知。
包的 3 个作用如下:
- 区分相同名称的类。
- 能够较好地管理大量的类。
- 控制访问范围。
Java 中使用 package 语句定义包,package 语句应该放在源文件的第一行,在每个源文件中只能有一个包定义语句,并且 package 语句适用于所有类型(类、接口、枚举和注释)的文件
包语句的语法格式:
package 包名;
一般利用公司域名倒置作为包名;如 www.baidu.com 包名:com.baidu
Java 包的命名规则如下:
- 包名全部由小写字母(多个单词也全部小写)。
- 如果包名包含多个层次,每个层次用“.”分割。
- 包名一般由倒置的域名开头,比如 com.baidu,不要有 www。
- 自定义包不能 java 开头。
包导入
如果使用不同包中的其它类,需要使用该类的全名(包名+类名)。代码如下:
example.Test test = new example.Test();
其中,example 是包名,Test 是包中的类名,test 是类的对象。
为了简化编程,Java 引入了 import 关键字,import 可以向某个 Java 文件中导入指定包层次下的某个类或全部类。import 语句位于 package 语句之后,类定义之前。一个 Java 源文件只能包含一个 package 语句,但可以包含多个 import 语句。
使用 import 导入单个类的语法格式如下:
import 包名路径 + 类名;
提示:使用星号(*)可能会增加编译时间,特别是引入多个大包时,所以明确的导入你想要用到的类是一个好方法,需要注意的是使用星号对运行时间和类的大小没有影响。
通过使用 import 语句可以简化编程,但 import 语句并不是必需的,如果在类里使用其它类的全名,可以不使用 import 语句。
Java 默认为所有源文件导入 java.lang 包下的所有类,因此前面在 Java 程序中使用 String、System 类时都无须使用 import 语句来导入这些类。但对于数组的 Arrays 类,其位于 java.util 包下,则必须使用 import 语句来导入该类。
在一些极端的情况下,import 语句也帮不了我们,此时只能在源文件中使用类全名。例如,需要在程序中使用 java.sql 包下的类,也需要使用 java.util 包下的类。
系统包
Java SE 提供了一些系统包,其中包含了 Java 开发中常用的基础类。
在 Java 语言中,开发人员可以自定义包,也可以使用系统包,常用的系统包如表所示。
包 | 说明 |
---|---|
java.lang | Java 的核心类库,包含运行 Java 程序必不可少的系统类,如基本数据类型、基本数学函数、 字符串处理、异常处理和线程类等,系统默认加载这个包 |
java.io | Java 语言的标准输入/输出类库,如基本输入/输出流、文件输入/输出、过滤输入/输出流等 |
java.util | 包含如处理时间的 Date 类,处理动态数组的 Vector 类,以及 Stack 和 HashTable 类 |
java.awt | 构建图形用户界面(GUI)的类库,低级绘图操作 Graphics 类、图形界面组件和布局管理 (如 Checkbox 类、Container 类、LayoutManger 接口等),以及用户界面交互控制和事 件响应(如 Event 类) |
java.awt.image | 处理和操纵来自网上的图片的 Java 工具类库 |
java.wat.peer | 很少在程序中直接用到,使得同一个 Java 程序在不同的软硬件平台上运行 |
java.net | 实现网络功能的类库有 Socket 类、ServerSocket 类 |
java.lang.reflect | 提供用于反射对象的工具 |
java.util.zip | 实现文件压缩功能 |
java.awt.datatransfer | 处理数据传输的工具类,包括剪贴板、字符串发送器等 |
java.sql | 实现 JDBC 的类库 |
java.rmi | 提供远程连接与载入的支持 |
java. security | 提供安全性方面的有关支持 |
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 事件代理
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- 数组常用方法一
- Python基础|Python基础 - 练习1
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- Java|Java基础——数组
- RxJava|RxJava 在Android项目中的使用(一)
- java之static、static|java之static、static final、final的区别与应用