> 文档中心 > Java 面向对象之类和对象

Java 面向对象之类和对象


1. 类的定义

  • 类:是一组相关属性和行为的集合。可以看成是一类事物的模板,使用事物的属性特征和行为特征来描述该类事物;如:人类
  • 实例对象:是一类事物的具体体现。对象是类的一个实例(对象并不是找个女朋友),必然具备该类事物的属性和行为;如:小红、小明、小南

面向过程和面向对象

  • 面向过程:当需要实现一个功能时,每个步骤都要亲自实现,详细处理每个细节
  • 面向对象:当需要实现一个功能时,不需要关心具体的步骤,而是找一个已经具有该功能的人(或事物)来帮我实现

类定义格式

// 类可以实现对数据的封装,类由成员变量和成员方法组成class className {    // 成员变量    成员变量是直接定义在类当中的,在方法外边    // 成员方法     成员方法不要写static关键字}

示例:

class Student {    // 成员变量    String name;    int age;    // 成员方法    public void getName() { System.out.println(this.name);    }}

1.1 对象的使用

通常类不能直接使用,需要根据类创建一个对象来使用,使用步骤:

  • 导包: import 包名称.类名称
  • 创建对象: 类名称 对象名 = new 类名称() ,如: Student s1 = new Student();
  • 使用
    对象名.成员变量名对象名.成员方法名(参数)
// 实例化一个对象类名 对象名 = new 类名();对象名.成员变量;对象名.成员方法();

示例:

public class ClassTest {    public static void main(String[] args) { Student s1 = new Student(); System.out.println(s1.getName()); // 设置年龄 s1.setAge(18); System.out.println(s1.getAge());    }}class Student {    // 成员变量    String name = "rose";    int age;    // 成员方法    public String getName() { return this.name;    }    // 设置年龄    public void setAge(int age) { this.age = age;    }    // 获取年龄    public int getAge() { return this.age;    }}

1.2 成员变量

若成员变量没有进行赋值,那么将会有一个默认值:

数据类型 默认值
基本类型 整数( byte,short,int,long ) 0
浮点数( float,double ) 0.0
字符( char ) '\u0000'
布尔( boolean ) false
引用类型数组 类,接口 null

一个类也可以定义多个成员变量和方法,有 public、private 之分:

  • public :类外部可以访问
  • private :私有,类外部不可访问,格式: private 数据类型 变量名;
    • 权限修饰符,代表最小权限
    • 可以修饰成员变量和成员方法
    • 被修饰后的成员变量和成员方法,只能在本类中才能访问
public class ClassTest {    public static void main(String[] args) { Person p1 = new Person(); p1.name = "rose";    }}class Person {    public String name;    public int age;}

以上成员变量 name、age 是公共的,类外部可以访问,这就破坏了类的封装性,导致类外部可以修改,正确的写法应该是:

class Person {    // 当外部修改成员变量值的时候,会编译报错 p1.name = "rose";    private String name;    private int age;}

1.3 成员变量和局部变量

  • 成员变量
    • 位置:在方法外部,直接写在类当中
    • 作用范围:整个类全都可以通用
    • 默认值:有默认值
    • 内存位置:位于堆内存
    • 生命周期:随着对象创建而诞生,随着对象被垃圾回收而消失
  • 成员变量
    • 位置:在方法内部
    • 作用范围:只有方法当中才可以使用,出了方法就不能再用
    • 默认值:无默认值
    • 内存位置:位于栈内存
    • 生命周期:随着方法进栈而诞生,随着方法出栈而消失

示例:

class Person {    public String name;     // 成员变量    public int age;    public void setName(String name) { String gender = "男";    // 局部变量 this.name = name;    }

2. 方法

2.1 private 修饰符

在类外部可以通过对象来修改成员变量,但是却 无法阻止不合理的数据 ,比如: age 有可能被设置为负数,解决方案:

  • private 关键字修饰要修改的成员变量, 被修饰后的成员变量和成员方法,只能在本类中才能访问
  • 再通过 Getter/Setter 方法实现外部也可以访问和修改成员变量,同时也可以校验数据的合理性
  • 那么可以通过 private 关键字和 Getter/Setter 方法来实现间接修改,这样也可以在 Getter/Setter 方法中校验数据的合理性
public class ClassTest {    public static void main(String[] args) { Person p1 = new Person(); p1.setName("rose"); System.out.println(p1.getName());    }}class Person {    private String name;    private int age;    private boolean male; // bool 类型成员变量    // 设置名字    public void setName(String name) { this.name = name;    }    // 访问名字    public String getName() { return this.name;    }    public void setMale(boolean b) { male = b;    }    // bool 类型 Getter 方式是 isXXX()    public boolean isMale() { return male;    }}

注意: bool 类型 Getter 方式是 isXXX() ,而非 Getter()

2.2 方法定义

语法格式:

修饰符 方法返回类型 方法名(方法参数列表) {    若干方法语句;    return 方法返回值;}
  • 修饰符: public、private

  • 方法返回类型: String、int、void 等

注意:若没有返回值,可设置返回类型为 void ,可省略 return

2.3 private 方法和 this 变量

有 public 方法就有 private 方法,同理它不允许外部调用,只能在类内部调用:

// private 方法private int calcAge(int currentYear) {    return currentYear - this.age;}

this 变量

this 代表所在类的当前对象的引用(地址值),即对象自己的引用,可解决 成员变量和局部变量、参数的命名冲突 ,如:成员变量和局部变量名字一致

使用格式:

this.成员变量

示例:

// 成员变量和 setName 的参数名冲突,this.name 表示调用成员变量 nameclass Person {    private String name;    // 设置名字    public void setName(String name) { this.name = name;    }    // 访问名字    public String getName() { return this.name;    }}

若没有命名冲突可省略 this ,但是最好是显示地写上。

注意 :方法被哪个对象调用,方法中的 this 就代表那个对象。即谁在调用, this 就代表谁。

2.4 方法参数

方法可以接收 0 个或任意个参数,定义参数时需要数据类型,传递参数必须严格按照参数数据类型传递:

class Person {    public String name;    public int age;    // 执行需传入的参数必须为 String 类型    public void setName(String name) { this.name = name;    }    // 无参数    public String getName() { return this.name;    }}// 参数传递Person p1 = new Person();p1.setName("rose");

可变参数

当传入的参数不固定或不确定数目时,可使用可变参数:

// 格式  类型...class Person {    public String[] names;    // 接收的是一个 String 数组    public void setName(String... names) { this.names = names;    }}// 参数传递Person p1 = new Person();p1.setName();p1.setName("rose");p1.setName("rose", "lila");

2.5 参数绑定

  • 基本类型参数的传递:是调用方 值的复制 。双方各自的后续修改,互不影响
  • 引用类型参数的传递:调用方的变量,和接收方的参数变量, 指向的是同一个对象 。双方任意一方对这个对象的修改,都会影响对方
  • 注意 : String的值是不可变的 ,对 String 重新赋值的时候,会重新创建一个变量的引用;

2.5.1 基本类型参数传递

public class ClassTest {    public static void main(String[] args) { Person p1 = new Person(); int n = 20; p1.setAge(n); System.out.println(p1.getAge());    // 20 n = 18; System.out.println(p1.getAge());    // 20    }}class Person {    private int age;    public int getAge() { return this.age;    }    public void setAge(int age) { this.age = age;    }}

修改外部的局部变量 n ,不影响实例 p 的 age 字段,原因是 setAge() 方法获得的参数,复制了 n 的值,因此, p.age 和局部变量 n 互不影响。

2.5.2 引用类型参数传递

public class ClassTest {    public static void main(String[] args) {// Person p1 = new Person();// int n = 20;// p1.setAge(n);// System.out.println(p1.getAge());    // 20// n = 18;// System.out.println(p1.getAge());    // 20 Person p2 = new Person(); String[] full_name = {"li", "xiaoer"}; p2.setName(full_name); System.out.println(p2.getName());   // li xiaoer full_name[1] = "da"; System.out.println(p2.getName());   // li da    }   }class Person {    private int age;    private String[] names;    public int getAge() { return this.age;    }    public void setAge(int age) { this.age = age;    }    public String getName() { return this.names[0] + " " + this.names[1];    }    public void setName(String[] names) { this.names = names;    }}

数组 full_name 第 1 个元素修改, getName() 方法获取的 names 也跟着改变,这是因为两者指向的是同一变量,其内存地址是同一个。

2.5.3 引用类型之 String 类型参数传递(绑定)

public class ClassTest {    public static void main(String[] args) { Person p2 = new Person(); String n1 = "rose"; p2.setName(n1); System.out.println(p2.getName());   // rose n1 = "lila"; System.out.println(p2.getName());   // rose    }   }class Person {    private int age;    private String name;    public int getAge() { return this.age;    }    public void setAge(int age) { this.age = age;    }    public String getName() { return this.name;    }    public void setName(String name) { this.name = name;    }}

上面说引用类型的参数传递,调用方的变量,和接收方的参数变量, 指向的是同一个对象 。双方任意一方对这个对象的修改,都会影响对方;但是 String 类型是个特例。

String的值是不可变的 ,对String 重新赋值的时候,会重新创建一个新的变量的引用;所有当变量 n1 = "lila"; 时,它会创建一个新的变量的引用,而 setName() 方法时获取的原先的变量引用,所以修改不会改变。

3. 构造方法

构造方法:可以实现在创建实例的时候, 初始化实例字段 ,特性:

  • 构造方法名称即为类名,无返回值,无 void

  • 调用构造方法,必须用 new 操作符
  • 可以有多个构造方法(有参数、无参数、不同参数), 若没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数 ,也没有执行语句
public class StructMethod {    public static void main(String[] args) { Person1 p = new Person1("rose", 18); System.out.println(p.getAge()); System.out.println(p.getName()); Person1 p2 = new Person1("lila"); Person1 p3 = new Person1();    }}class Person1 {    private String name;    private int age;    // 默认构造方法    public Person1() {    }    // 自定义构造方法 1    public Person1(String name, int age) { this.name = name; this.age = age;    }    // 自定义构造方法 2    public Person1(String name) { this.name = name;    }    public String getName() { return this.name;    }    public int getAge() { return this.age;    }}

多个构造方法调用:

Person1 p = new Person1("rose", 18);Person1 p2 = new Person1("lila");Person1 p3 = new Person1();

一个构造方法调用另一个构造方法:

this("john", 19);   // 便于代码复用

4. 方法重载

在 Java 类中可以定义 多个名称相同的方法 ,若只是参数不同,功能类似,那么可以将这类方法称为同名方法,也可以叫做 方法重载 ,一般而言这类方法 返回类型应该相同 :

class Book {    public void hello() { System.out.println("Hello World!");    }    public void hello(String name) { System.out.println("Hello " + name);    }    public void hello(String name, int age) { System.out.println("Hello " + name + "You're age " + age);    }}

方法重载的目的是,功能类似的方法使用同一名字,更容易记住,调用起来更简单,如 String 类提供了多个重载方法 indexOf()

5. Java 对象内存图

【JavaSE】Java面向对象的思想(类、对象、对象调用内存图)

一个 Java 对象的创建 --> 使用在内存中总共大概会开辟三块内存区域:栈内存 Stack 、堆内存 Heap 、方法区 Method Area

  • 方法调用的时候,该方法所需的内存空间在 栈内存 中分配,这个过程叫 压栈 ,方法结束后,该方法所属的内存空间释放,成为 出栈
  • 栈中主要存储的是方法体当中的局部变量
  • 方法的代码段以及整个类的代码段都被存储到方法区内存当中,在类加载的时候这些代码片会载入
  • 程序执行 new 创建对象时,存储在 堆内存 当中,对象内部的实例变量

1、只有一个对象的内存图

  • 方法区:总共有两个类 Phone、Demo1PhoneOne ,方法有: main、call、send
  • 栈内存:当执行主函数时,会在栈内存开辟一块空间;当使用对象去修改或访问成员变量时,会直接在堆内存中找到成员变量进行修改,而当调用成员方法时,通过堆内存中的 成员方法内存地址 找到相应的成员方法,此时成员方法会从方法区进入栈空间(压栈),执行完毕后,就立马出栈
  • 堆内存:当 new 创建对象时,会在堆内存开辟一块空间

2、两个对象使用同一个方法的内存图

创建两个对象情况和一个对象类似:

  • 创建第二个对象是在堆内存中新建一个对象 new Phone(); 赋给第二个对象名 two 的地址也是一个新的地址值
  • 对象访问成员变量,访问的是该对象自己的成员变量。
  • 两个对象使用同一个方法,会根据地址值指向同一个方法。因为两个对象中的成员方法,保存的都是方法区中 Phone.class 中对应成员方法的地址值
  • 两个对象访问成员变量,调用成员方法不会产生任何关联

3、两个引用指向同一个对象的内存图

  • Phone two =one; 是将 one 中保存的对象地址值赋给的 two , two 也指向了该对象。

  • 根据 one 和 two 的地址值都可以访问到该同一对象的成员变量、成员方法