3.1面向对象概述
面向对象的特点可以概括为继承、封装、多态。
-
封装:是面向对象的核心思想,将对象的属性封装起来,不需要让外界知道具体实现细节,这就是封装思想。
-
继承:主要描述的是类与类之间的关系,通过集成,可以在无需重新编写原有类的情况下,对原有类的功能进行扩展。
-
多态:指的是在一个类中定义的属性和功能被其他类继承后,当把子类对象直接赋值给父类引用变量时,相同引用类型的变量调用同一个方法所呈现出来的多种不同行为特征。
3.2Java中的类与对象
3.2.1类与对象的关系
面向对象提出两个概念:类和抽象。
类是对某一类事物的抽象描述,而对象用于表现现实中该类事物的个体。
3.2.2类的定义
类是对象的抽象,用于描述一组对象的共同特征和行为。
把某一类中共同的特征和行为封装起来作为类的属性(也叫成员变量),把共同行为作为类的方法(也叫成员方法)。
1.类的定义格式(通过class关键字来定义)
[修饰符] class 类名 [extends 父类名] [implements 接口名]{
//类体,包括类的成员变量和成员方法
}
上述语法中,class前面的修饰符可以是public,也可不些(默认),类名首字母要大写,并且要符合标识符命名规则,extends和implements是可选项,均为Java中关键字,extends用于说明所定义的类继承于哪个父类,implements关键字用于说明当前类实现了哪些接口,大括号内是类体,即需要在类中编写的内容,主要包括类的成员变量和成员方法。
2.声明(定义)成员变量
类的成员变量被称作类的属性,它主要用于描述对象的特征。声明(定义)成员变量的语法格式如下:
[修饰符] 数据类型 变量名 [=值]
其中:修饰符为可选项,用于指定变量的访问权限,其值可以是public,private等;数据类型可以为Java中任意数据类型;变量名是变量的名称,要符合标识符的命规则,可以赋初值,也可以不赋值。通常,将未赋值(没有被初始化)的变量称为声明变量,而赋值(初始化)的变量称为定义变量。
private String name;//声明一个String类型的name
pricate int age=20;//定义一个int类型的age,并赋值为20
3.声明(定义)成员方法
成员方法也被称为方法,类似于C语言中的函数,主要用于描述对象的行为。定义一个方法的语法格式如下:
[修饰符] [返回值类型] 方法名([参数类型 参数名1,参数类型 参数名2,...]){
//方法体
...
return 返回值;//当方法的返回值类型为void时,return及其返回值可省略
}
其中:
-
修饰符,为可选项,有对访问权限进行限定的(public、protected、private),有静态修饰符(static),有最终修饰符(final);
-
返回值类型:用于限定方法返回值的数据类型,不需要返回值时使用void关键字;
-
参数类型:用于限定调用方法时传入参数的类型;
-
return关键字:用于结束方法以及返回方法指定类型的值,方法返回值类型是void时,return及返回值可省略;
-
返回值:被return语句返回的值,该值会返回给调用者。
-
{}前的内容被称方法签名(或方法头),而{}中的执行语句被称为方法体。
-
参数列表可以为空。
public class Person {
int age;//类的成员变量
//定义apeak()方法
void speak(){//类的成员方法
System.out.println("我今年"+age+"岁了!");//成员方法speak()中可以直接访问成员变量age
}
}
定义在类中的变量称为成员变量,定义在方法中的变量称为局部变量,如果再某一个方法中定义的局部变量与成员变量通明,这种情况是允许的,此时方法中通过方法名访问的是局部变量而非成员变量。
public class Person {
int age = 10; //类中定义的成员变量
void speak(){
int age = 20; //方法内部定义的局部变量
System.out.println("我今年"+age+"岁了!");//输出为20而非10
}
}
3.2.3对象的创建与使用
在Java程序中可用使用new关键字来创建对象,具体语法格式如下:
类名 对象名称 = new 类名();
//例如
Person p = new Person();
其中,new Person()用于创建Person类的一个实例对象,Person P则是声明了一个Person类型的变量p。中间的等号用于将Person对象在内存中的地址赋值给变量p,这样变量p便持有了对象的引用。
在创建Person对象时,程序会占用两块内存区域,分别栈内存和堆内存。其中Person类型的变量p被存放在占内存中,它是一个引用,会指向真正的对象;通过new Person()创建的对象则放在堆内存中,这才是真正的对象。
即:Java将内存分为栈内存和堆内存。其中栈内存用于存放基本类型的变量和对象的引用变量,堆内存存放由new创建的对象和数组。
创建Person对象后,通过对象的引用来访问对象的所有成员,格式如下:
对象引用.对象成员
访问对象的成员
public class Example02 {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person();
p1.age = 18;
p1.speak();
p2.speak();
}
}
/*运行结果如下:
我今年18岁了!
我今年0岁了!
*/
//前者为18是赋值为18,后者未赋值,实例化对象时,Java虚拟机会自动为成员变量进行初始化。
在实际情况中,还可以直接使用创建的对象本身来引用对象成员
new 类名().对象成员
这种方式通过new关键字创建实例对象的同时就访问了对象的某个成员,并且在创建后只能访问其中某一个从恒源,而不能像对象引用可以方位多个对象成员。同时由于没有对象引用的存在,在完成一某一个对象成员访问后,该对象就会变成垃圾对象。所以实际开发中,创建实例对象时多数会使用对象引用。
对象是如何变成垃圾对象呢?
{
Person p1 = new Person();
...
}
变量p1引用了一个Person类型的对象,这段代码运行完毕后变量p1就超出作用域而被销毁,这时Person类型的对象将因为没有被任何变量所引用而变成垃圾。
{
Person p2 = new Person();
...
p2 = null;
}
变量p2引用了一个Person类型的对象,接着将变量p2的值置为null,则表示该变量不指向任何一个对象,被pe所引用的Person对象就会失去引用,成为垃圾对象。
3.2.4访问控制符
java针对类、成员方法和属性提供了4中访问类别,private、default、protected、public。(由小到大排列)
-
private(当前类访问级别):这个成员只能被该类的其他成员访问,其他类无法直接访问。类的良好封装就是通过private关键字来实现的。
-
default(包访问级别):如果一个类或者类的成员不适用任何访问控制符修饰,则称它为默认访问控制级别,这个类或者类的成员只能被本包中的其他类访问。
-
protected(子类访问级别):既能被同一包下的其他类访问,也能被不同包下该类的子类访问。
-
public(公共访问级别):最宽松的访问控制级别,这个类或者类的成员能被所有的类访问,不管访问类与被访问类是否在同一个包中。
访问范围 | private | default | protected | public |
---|---|---|---|---|
同一类中 | √ | √ | √ | √ |
同一包中 | √ | √ | √ | |
子类中 | √ | √ | ||
全局范围 | √ |
注意:如果一个Java源文件中定义的所有类都没有使用public修饰,那么这个Java源文件的文件名可以是一些合法的文件名;如果一个源文件中定义了一个public修饰的类,那么这个源文件的文件名必须与public修饰的类的类名相同。
3.3类的封装
3.3.1为什么需要封装
class Person{
String name;
int age;
public void speak(){
System.out.println("我叫"+name+",今年"+age+"岁了");
}
}
public class Example03 {
public static void main(String[] args) {
Person p = new Person();
p.name = "张三";
p.age = -18;
p.speak();
}
}
此处将年龄赋值为负数显然在现实生活中是不合理的,为了避免出现这种问题,设计一个Java类时,应该对成员变量的访问做出一些限定,不允许外界随意访问, 这就需要类的封装。
3.3.2如何实现封装
类的封装,是指将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象的内部信息,而是通过该类所提供的方法来是实现对内部信息的操作访问。
具体实现过程是,在定义一个类时,将类中的属性私有化(使用private关键字来修饰),私有属性只能在它所在的类中被访问。外界需要访问,需要提供一些使用public修饰的共有方法,包括用于获取属性值的getXxx()和设置属性值的setXxx()方法。
class Person{
private String name;
private int age;
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<=4){
System.out.println("您输入的年龄不正确!");
}else{
this.age = age;
}
}
public void speak(){
System.out.println("我叫"+name+",今年"+age+"岁了");
}
}
public class Example04 {
public static void main(String[] args) {
Person p = new Person();
p.setName("张三");
p.setAge(-18);
p.speak();
}
}
/** 输出结果为:
* 您输入的年龄不正确!
* 我叫张三,今年0岁了
* 使用private关键字将属性声明为私有白能量,并对外界提供共有的访问方法,
* 程序中setAge()方法会对参数的值进行检查,
* 数值不正确,age属性未赋值,仍然为初始值0
*/
3.4方法的重载和递归
3.4.1方法的重载
public class Example05 {
//1.实现两个整数相加
public static int add01(int x, int y) {
return x + y;
}
//2.实现三个整数相加
public static int add02(int x, int y, int z) {
return x + y + z;
}
//3.实现两个小数相加
public static double add03(double x,double y){
return x+y;
}
public static void main(String[] args) {
//针对求和方法的调用
int sum1 = add01(1,2);
int sum2 = add02(3,4,5);
double sum3 = add03(0.2,0.5);
System.out.println("sum1="+sum1);
System.out.println("sum2="+sum2);
System.out.println("sum3="+sum3);
}
}
//需要针对每种求和情况都定义方法,如果每个方法名称都不相同,调用时很难分清
Java允许在一个程序中定义多个名称相同、但是参数类型或个数不同的方法,这就是方法的重载。
public class Example06 {
//1.实现两个整数相加
public static int add(int x,int y){
return x+y;
}
//2.实现三个整数相加
public static int add(int x,int y ,int z){
return x+y+z;
}
//3.实现两个小数相加
public static double add(double x,double y){
return x+y;
}
public static void main(String[] args) {
//针对求和方法的调用
int sum1 = add(1,2);
int sum2 = add(1,2,3);
double sum3 = add(1.2,2.3);
System.out.println("sum1="+sum1);
System.out.println("sum2="+sum2);
System.out.println("sum3="+sum3);
}
}
以上定义了三个同名的add()方法,但是它们的参数个数或者参数类型不同,从而是实现了方法的重载。
方法的重载与返回值类型无关,只需要满足两个条件:方法名相同,参数个数或参数类型不同。
3.4.2方法的递归
方法的递归是指在一个方法的内部调用自身的过程。递归必须要有结束条件,不然就会无限递归,永远无法结束调用。
public class Example07 {
//使用递归来实现求1-n的和
public static int getSum(int n) {
if (n == 1) {
//满足条件,递归结束
return 1;
}
int temp = getSum(n-1);//getSum()方法内部调用自身
return temp+n;
}
public static void main(String[] args) {
int sum = getSum(4);
System.out.println("sum="+sum);
}
}
3.5构造方法
需要在实例化对象的同时就为这个对象的属性进行赋值,可以通过构造方法是实现。构造方法(也称为构造器)是类的一个特殊成员,会在类实例化对象时被自动调用。
3.5.1构造方法的定义
语法格式
[修饰符] 方法名([参数列表]){
//方法体
}
注意此语法格式定义的构造方法需同时满足:
-
方法法名与类名相同;
-
在方法名前面没有返回值类型的声明;
-
在方法中不能使用return语句返回一个值,但可以单独写return语句作为方法的结束。
class Person{
//类的构造方法
public Person(){
System.out.println("调用了无参的构造方法");
}
}
public class Example08 {
public static void main(String[] args) {
Person p' = new Person();
}
}
class Person{
//声明int类型的变量age
int age;
//定义有参的构造方法
public Person(int a){
age = a;
}
//定义speak()方法
public void speak(){
System.out.println("我今年"+age+"岁了");
}
}
public class Example09 {
public static void main(String[] args) {
Person p = new Person(18);
p.speak();
}
}
3.5.2构造方法的重载
构造方法也可以重载,在一个类中可以定义多个构造方法, 只需要每个构造方法的参数类型或者参数个数不同即可。创建对象时,可以通过调用不同的构造方法来为不同的属性进行赋值。
class Person{
String name;
int age;
public Person(int a){
age = a;
}
public Person(String n,int a){
name = n;
age = a;
}
//定义speak()方法
public void speak(){
System.out.println("我今年"+age+"岁了!");
}
//定义say()方法
public void say(){
System.out.println("我叫"+name+",今年"+age+"岁了!");
}
}
public class Example10 {
public static void main(String[] args) {
Person p1 = new Person(18);
Person p2 = new Person("小王",19);
p1.speak();
p2.say();
}
}
注意:
-
在Java中的每个类都至少有一个构造方法, 如果没有显式地定义,系统会自动为这个类创建一个默认的构造方法,这个默认的构造方法没有参数,方法体中没有任何代码,即什么也不做。
class Person{
}class Person{
public Person(){
}
}上面两种写法效果相同
-
一旦为该类定义了构造方法,系统将不再提供默认的无参构造方法。在一个类中如果定义了有参的构造方法,最好再定义一个无参的构造方法。
-
为方便实例化对象,构造方法通常使用public来修饰
3.6this关键字
this关键字用来指代当前对象,用于在方法中访问对象的其他成员。
this关键字3种常见用法
-
通过this关键字调用成员变量,解决与局部变量名称冲突问题
class Person{
int age; //成员变量age
public Person(int age){ //局部变量age
this.age = age; //将局部变量age的值赋给成员变量age
}
}//在构造方法中,age就是访问局部变量,this.age就是访问成员变量
-
通过this关键字调用成员方法
class Person{
pulic void openMouth(){
...
}
public void speak(){
this.openMouth();//这里不写this也是一样的
}
}
-
通过this关键字调用构造方法
class Person{
public Person(){
System.out.println("无参的构造方法被调用了...");
}
public Person(int age){
this();
System.out.println("有参的构造方法被调用了...");
}
}
public class Example11 {
public static void main(String[] args) {
Person 彭 = new Person(18);
}
}
/**
* 输出结果是
* 无参的构造方法被调用了...
* 有参的构造方法被调用了...
* 实例化Person对象时调用了有参的构造方法,
* 有参构造方法中有通过this()调用了午餐的构造方法,
* 因此运行结果中显示两个构造方法都被调用
*/
-
使用this调用类的构造方法时,注意:
-
只能在构造方法中使用this调用其他的构造方法,不能在成员方法中使用;
-
在构造方法中,使用this调用构造方法的语句必须是该方法的第一条执行语句,且只能出现一次;
-
不能再一个类的两个构造方法中使用this互相调用。
-
3.7static关键字
用于修饰类的成员,如成员的变量、成员方法以及diamante亏啊等,被stati修饰的成员具备一些特殊性。
3.7.1静态变量
定义一个类时,只是在描述某类事物的特征和行为,并没有产生具体的数据。只有通过new关键字创建该类的实例对象后, 系统才会为每个对象分配内存空间,存储各自的数据。
静态变量可以被所有实例所共享。
语法格式
类名.变量名
class Student{
static String schoolName;//声明静态变量
}
public class Example12 {
public static void main(String[] args) {
Student stu1 = new Student();//创建第一个学生对象
Student stu2 = new Student();//创建第二个学生对象
Student.schoolName = "清华大学";//为静态变量赋值
System.out.println("我是"+stu1.schoolName+"的学生");
System.out.println("我是"+stu2.schoolName+"的学生");
}
}
注意:static关键字只能用于修饰成员白变量,不能用于修饰局部变量,否则编译报错。
public class Student{
public void study(){
static int num = 10;//这行代码是非法的
}
}
3.7.2静态方法
实际开发时,有时希望不创建对象的情况下就可以调用某个方法,这种情况就可以使用静方法。
静态方法的定义只需在类中定义的方法前加上static关键字即可。
访问静态方法的两种方式
类名.方法
或
实例对象.方法
静态方法的使用
class Person{
public static void say(){
System.out.println("Hello!");
}
}
public class Example13 {
public static void main(String[] args) {
//用”类名.方法“的方式调用静态方法
Person.say();
//实例化对象
Person person = new Person();
//用“实例化对象名.方法"的方式来调用静态方法
person.say();
}
}
注意:在一个静态方法中只能访问用static修饰的成员,原因在于没有被static修饰的成员需要先创建对象才能访问,而静态方法在被调用时可以不创建任何对象。
3.7.3静态代码块
在Java类中,使用一对大括号包围起来的若干行代码被称为一个代码块,用static关键字修饰的代码块被称为静态代码块。语法如下:
static{
...
}
类被加载时,静态代码块会执行,由于类只加载一次,因此静态代码块也只执行一次。在程序中,通常会使用静态代码来对类的成员变量进行初始化。
class Person{
static{
System.out.println("执行了Person类中的静态代码块");
}
}
public class Example14 {
static{
System.out.println("执行了测试类中的静态代码块");
}
public static void main(String[] args) {
//实例化2个Person对象
Person p1 = new Person();
Person p2 = new Person();
}
}
/**
* 输出结果:
* 执行了测试类中的静态代码块
* 执行了Person类中的静态代码块
*/
程序中两个静态代码都执行了。运行程序,Java虚拟机首先加载类Example4,加载类的同时会执行该类的静态代码块,接着调用main()方法。main()方法中创建了两个Person对象,但在两次实例化对象的过程中,静态代码块的内容都只输出了一次,说明静态代码第一次使用时才会被加载,且只会加载一次。