首页 > 编程语言 > 带你入门Java的类与对象
2021
10-12

带你入门Java的类与对象

类和对象

在面向对象中,类和对象是最基本、最重要的组成单元。类实际上是表示一个客观世界某类群体的一些基本特征抽象。对象就是表示一个个具体的东西。所以说类是对象的抽象,对象是类的具体。

“人类”只是一个抽象的概念,它仅仅是一个概念,是不存在的实体!但是所有具备“人类”这个群体的属性与方法的对象都叫人!这个对象“人” 是实际存在的实体!每个人都是“人”这个群体的一个对象。

类的属性

在 Java 中类的成员变量定义了类的属性。声明成员变量的语法如下:

[public|protected|private][static][final]<type><variable_name>

各参数的含义如下。

  • public、protected、private:用于表示成员变量的访问权限。
  • static:表示该成员变量为类变量,也称为静态变量。
  • final:表示将该成员变量声明为常量,其值无法更改。
  • type:表示变量的类型。
  • variable_name:表示变量名称。

可以在声明成员变量的同时对其进行初始化,如果声明成员变量时没有对其初始化,则系统会使用默认值初始化成员变量。

  • 初始化的默认值如下:
  • 整数型(byte、short、int 和 long)的基本类型变量的默认值为 0。
  • 单精度浮点型(float)的基本类型变量的默认值为 0.0f。
  • 双精度浮点型(double)的基本类型变量的默认值为 0.0d。
  • 字符型(char)的基本类型变量的默认值为 “\u0000”。
  • 布尔型的基本类型变量的默认值为 false。
  • 数组引用类型的变量的默认值为 null。如果创建了数组变量的实例,但没有显式地为每个元素赋值,则数组中的元素初始化值采用数组数据类型对应的默认值。

成员方法

声明成员方法可以定义类的行为,行为表示一个对象能够做的事情或者能够从一个对象取得的信息。类的各种功能操作都是用方法来实现的,属性只不过提供了相应的数据。一个完整的方法通常包括方法名称、方法主体、方法参数和方法返回值类型。若方法有返回值,则在方法体中用 return 语句指明要返回的值。

形参和实参

关于方法的参数,经常会提到形参与实参,形参是定义方法时参数列表中出现的参数,实参是调用方法时为方法传递的参数

方法的形参和实参具有以下特点:

  • 形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在方法内部有效,方法调用结束返回主调方法后则不能再使用该形参变量。
  • 实参可以是常量、变量、表达式、方法等,无论实参是何种类型的量,在进行方法调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值、输入等办法使实参获得确定值。
  • 实参和形参在数量、类型和顺序上应严格一致,否则会发生“类型不匹配” 的错误。
  • 方法调用中发生的数据传送是单向的,即只能把实参的值传送绐形参,而不能把形参的值反向地传送给实参。因此在方法调用过程中,形参的值发生改变,而实参中的值不会变化。
  • 实参变量对形参变量的数据传递是“值传递”,即只能由实参传递给形参,而不能由形参传递给实参。程序中执行到调用成员方法时,Java 把实参值复制到一个临时的存储区(栈)中,形参的任何修改都在栈中进行,当退出该成员方法时,Java 自动清除栈中的内容。

局部变量

在方法体内可以定义本方法所使用的变量,这种变量是局部变量。它的生存期与作用域是在本方法内,也就是说,局部变量只能在本方法内有效或可见,离开本方法则这些变量将被自动释放。

在方法体内定义变量时,变量前不能加修饰符。局部变量在使用前必须明确赋值,否则编译时会出错。另外,在一个方法内部,可以在复合语句(把多个语句用括号{}括起来组成的一个语句称复合语句)中定义变量,这些变量只在复合语句中有效。

可变参数

在具体实际开发过程中,有时方法中参数的个数是不确定的。为了解决这个问题,在 J2SE 5.0 版本中引入了可变参数的概念。

声明可变参数的语法格式如下:

methodName({paramList},paramType…paramName)

其中,methodName 表示方法名称;paramList 表示方法的固定参数列表;paramType 表示可变参数的类型;… 是声明可变参数的标识;paramName 表示可变参数名称。

注意:可变参数必须定义在参数列表的最后。

public class StudentTestMethod {
    // 定义输出考试学生的人数及姓名的方法
    public void print(String...names) {
        int count = names.length;    // 获取总个数
        System.out.println("本次参加考试的有"+count+"人,名单如下:");
        for(int i = 0;i < names.length;i++) {
            System.out.println(names[i]);
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        StudentTestMethod student = new StudentTestMethod();
        student.print("张强","李成","王勇");    // 传入3个值
        student.print("马丽","陈玲");
    }
}

构造方法

构造方法是类的一种特殊方法,用来初始化类的一个新的对象,在创建对象(new 运算符)之后自动调用。Java 中的每个类都有一个默认的构造方法,并且可以有一个以上的构造方法。

Java 构造方法有以下特点:

  • 方法名必须与类名相同
  • 可以有 0 个、1 个或多个参数
  • 没有任何返回值,包括 void
  • 默认返回类型就是对象类型本身
  • 只能与 new 运算符结合使用

值得注意的是,如果为构造方法定义了返回值类型或使用 void 声明构造方法没有返回值,编译时不会出错,但 Java 会把这个所谓的构造方法当成普通方法来处理。

这时候大家可能会产生疑问,构造方法不是没有返回值吗?为什么不能用 void 声明呢?

简单的说,这是 Java 的语法规定。实际上,类的构造方法是有返回值的,当使用 new 关键字来调用构造方法时,构造方法返回该类的实例,可以把这个类的实例当成构造器的返回值,因此构造器的返回值类型总是当前类,无须定义返回值类型。但必须注意不要在构造方法里使用 return 来返回当前类的对象,因为构造方法的返回值是隐式的。

注意:构造方法不能被 static、final、synchronized、abstract 和 native(类似于 abstract)修饰。构造方法用于初始化一个新对象,所以用 static 修饰没有意义。构造方法不能被子类继承,所以用 final 和 abstract 修饰没有意义。多个线程不会同时创建内存地址相同的同一个对象,所以用 synchronized 修饰没有必要。

在一个类中定义多个具有不同参数的同名方法,这就是方法的重载。

如果在类中没有定义任何一个构造方法,则 Java 会自动为该类生成一个默认的构造方法。默认的构造方法不包含任何参数,并且方法体为空。

无参构造方法和有参构造方法如下:

public class MyClass {
    private int m;    // 定义私有变量
    MyClass() {
        // 定义无参的构造方法
        m = 0;
    }
    MyClass(int m) {
        // 定义有参的构造方法
        this.m = m;
    }
}

this关键字

this.属性名

大部分时候,普通方法访问其他方法、成员变量时无须使用 this 前缀,但如果方法里有个局部变量和成员变量同名,但程序又需要在该方法里访问这个被覆盖的成员变量,则必须使用 this 前缀。

// 创建构造方法,为上面的3个属性赋初始值
public Teacher(String name,double salary,int age) {
    this.name = name;    // 设置教师名称
    this.salary = salary;    // 设置教师工资
    this.age = age;    // 设置教师年龄
}

this.方法名

this 关键字最大的作用就是让类中一个方法,访问该类里的另一个方法或实例变量。

public class Dog {
    // 定义一个jump()方法
    public void jump() {
        System.out.println("正在执行jump方法");
    }
    // 定义一个run()方法,run()方法需要借助jump()方法
	public void run() {
    // 使用this引用调用run()方法的对象
    	this.jump();
    	System.out.println("正在执行run方法");
	}
}

this( )访问构造方法

public class Student {
    String name;
    // 无参构造方法(没有参数的构造方法)
    public Student() {
        this("张三");
    }
    // 有参构造方法
    public Student(String name) {
        this.name = name;
    }
    // 输出name和age
    public void print() {
        System.out.println("姓名:" + name);
    }
    public static void main(String[] args) {
        Student stu = new Student();
        stu.print();
    }
}

注意:

  • this( ) 不能在普通方法中使用,只能写在构造方法中。
  • 在构造方法中使用时,必须是第一条语句。

static关键字

在类中,使用 static 修饰符修饰的属性(成员变量)称为静态变量,也可以称为类变量,常量称为静态常量,方法称为静态方法或类方法,它们统称为静态成员,归整个类所有。

静态成员不依赖于类的特定实例,被类的所有实例共享,就是说 static 修饰的方法或者变量不需要依赖于对象来进行访问,只要这个类被加载,Java 虚拟机就可以根据类名找到它们。

调用静态成员的语法形式如下:

类名.静态成员

注意:

  • static 修饰的成员变量和方法,从属于类。
  • 普通变量和方法从属于对象。
  • 静态方法不能调用非静态成员,编译会报错。

静态变量

类的成员变量可以分为以下两种:

  • 静态变量(或称为类变量),指被 static 修饰的成员变量。
  • 实例变量,指没有被 static 修饰的成员变量。

静态变量与实例变量的区别如下:

1)静态变量

  • 运行时,Java 虚拟机只为静态变量分配一次内存,在加载类的过程中完成静态变量的内存分配。
  • 在类的内部,可以在任何方法内直接访问静态变量。
  • 在其他类中,可以通过类名访问该类中的静态变量。

2)实例变量

  • 每创建一个实例,Java 虚拟机就会为实例变量分配一次内存。
  • 在类的内部,可以在非静态方法中直接访问实例变量。
  • 在本类的静态方法或其他类中则需要通过类的实例对象进行访问。

静态变量在类中的作用如下:

  • 静态变量可以被类的所有实例共享,因此静态变量可以作为实例之间的共享数据,增加实例之间的交互性。
  • 如果类的所有实例都包含一个相同的常量属性,则可以把这个属性定义为静态常量类型,从而节省内存空间。例如,在类中定义一个静态常量 PI。
public class StaticVar {
    public static String str1 = "Hello";
    public static void main(String[] args) {
        String str2 = "World!";
        // 直接访问str1
        String accessVar1 = str1+str2;
        System.out.println("第 1 次访问静态变量,结果为:"+accessVar1);
        // 通过类名访问str1
        String accessVar2 = StaticVar.str1+str2;
        System.out.println("第 2 次访问静态变量,结果为:"+accessVar2);
        // 通过对象svt1访问str1
        StaticVar svt1 = new StaticVar();
        svt1.str1 = svt1.str1+str2;
        String accessVar3 = svt1.str1;
        System.out.println("第3次访向静态变量,结果为:"+accessVar3);
        // 通过对象svt2访问str1
        StaticVar svt2 = new StaticVar();
        String accessVar4 = svt2.str1+str2;
        System.out.println("第 4 次访问静态变量,结果为:"+accessVar4);
    }
}

运行该程序后的结果如下所示。

第 1 次访问静态变量,结果为:HelloWorld!

第 2 次访问静态变量,结果为:HelloWorld!

第 3 次访向静态变量,结果为:HelloWorld!

第 4 次访问静态变量,结果为:HelloWorld!World!

静态方法

与成员变量类似,成员方法也可以分为以下两种:

  • 静态方法(或称为类方法),指被 static 修饰的成员方法。
  • 实例方法,指没有被 static 修饰的成员方法。

静态方法与实例方法的区别如下:

  • 静态方法不需要通过它所属的类的任何实例就可以被调用,因此在静态方法中不能使用 this 关键字,也不能直接访问所属类的实例变量和实例方法,但是可以直接访问所属类的静态变量和静态方法。另外,和 this 关键字一样,super 关键字也与类的特定实例相关,所以在静态方法中也不能使用 super 关键字。
  • 在实例方法中可以直接访问所属类的静态变量、静态方法、实例变量和实例方法。
public class StaticMethod {
    public static int count = 1;    // 定义静态变量count
    public int method1() {    
        // 实例方法method1
        count++;    // 访问静态变量count并赋值
        System.out.println("在静态方法 method1()中的 count="+count);    // 打印count
        return count;
    }
    public static int method2() {    
        // 静态方法method2
        count += count;    // 访问静态变量count并赋值
        System.out.println("在静态方法 method2()中的 count="+count);    // 打印count
        return count;
    }
    public static void PrintCount() {    
        // 静态方法PrintCount
        count += 2;
        System.out.println("在静态方法 PrintCount()中的 count="+count);    // 打印count
    }
    public static void main(String[] args) {
        StaticMethod sft = new StaticMethod();
        // 通过实例对象调用实例方法
        System.out.println("method1() 方法返回值 intro1="+sft.method1());
        // 直接调用静态方法
        System.out.println("method2() 方法返回值 intro1="+method2());
        // 通过类名调用静态方法,打印 count
        StaticMethod.PrintCount();
    }
}

运行该程序后的结果如下所示。

在静态方法 method1()中的 count=2

method1() 方法返回值 intro1=2

在静态方法 method2()中的 count=4

method2() 方法返回值 intro1=4

在静态方法 PrintCount()中的 count=6

静态代码块

指 Java 类中的 static{ } 代码块,主要用于初始化类,为类的静态变量赋初始值,提升程序性能。

静态代码块的特点如下:

  • 静态代码块类似于一个方法,但它不可以存在于任何方法体中。
  • 静态代码块可以置于类中的任何地方,类中可以有多个静态初始化块。
  • Java 虚拟机在加载类时执行静态代码块,所以很多时候会将一些只需要进行一次的初始化操作都放在 static 代码块中进行。
  • 如果类中包含多个静态代码块,则 Java 虚拟机将按它们在类中出现的顺序依次执行它们,每个静态代码块只会被执行一次。
  • 静态代码块与静态方法一样,不能直接访问类的实例变量和实例方法,而需要通过类的实例对象来访问。
public class StaticCode {
    public static int count = 0;
    {
        count++;
        System.out.println("非静态代码块 count=" + count);
    }
    static {
        count++;
        System.out.println("静态代码块1 count=" + count);
    }
    static {
        count++;
        System.out.println("静态代码块2 count=" + count);
    }
    public static void main(String[] args) {
        System.out.println("*************** StaticCode1 执行 ***************");
        StaticCode sct1 = new StaticCode();
        System.out.println("*************** StaticCode2 执行 ***************");
        StaticCode sct2 = new StaticCode();
    }
}

如上述示例,为了说明静态代码块只被执行一次,特地添加了非静态代码块作为对比,并在主方法中创建了两个类的实例对象。上述示例的执行结果为:

静态代码块1 count=1

静态代码块2 count=2

*************** StaticCode1 执行 ***************

非静态代码块 count=3

*************** StaticCode2 执行 ***************

非静态代码块 count=4

对象的创建

对象是对类的实例化。对象具有状态和行为,变量用来表明对象的状态,方法表明对象所具有的行为。Java 对象的生命周期包括创建、使用和清除。在 Java 语言中创建对象分显式创建与隐含创建两种情况。

显式创建对象

对象的显式创建方式有 4 种。

使用 new 关键字创建对象

这是常用的创建对象的方法,语法格式如下:

类名();

调用 java.lang.Class 或者 java.lang.reflect.Constuctor 类的 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() 方法创建对象。

调用对象的 clone() 方法

该方法不常用,使用该方法创建对象时,要实例化的类必须继承 java.lang.Cloneable 接口。 调用对象的 clone() 方法创建对象的语法格式如下:

类名对象名 = (类名)已创建好的类对象名.clone();

下面创建一个示例演示常用的前三种对象创建方法。示例代码如下:

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 接口。

程序执行结果如下:

---------使用 new 关键字创建对象---------

学生名字:小刘,年龄:22

-----------调用 java.lang.Class 的 newInstance() 方法创建对象-----------

学生名字:name,年龄:0

-------------------调用对象的done()方法创建对象----------

学生名字:name,年龄:0

调用 java.io.ObjectlnputStream 对象的 readObject()

方法隐含创建对象

除了显式创建对象以外,在 Java 程序中还可以隐含地创建对象,例如下面几种情况。

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 都相当于开辟了一个新的对象,并开辟了一个新的物理内存空间。如果一个对象只需要使用唯一的一次,就可以使用匿名对象,匿名对象还可以作为实际参数传递。

匿名对象就是没有明确的给出名字的对象,是对象的一种简写形式。一般匿名对象只使用一次,而且匿名对象只在堆内存中开辟空间,而不存在栈内存的引用。

public class Person {
    public String name; // 姓名
    public int age; // 年龄
    // 定义构造方法,为属性初始化
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 获取信息的方法
    public void tell() {
        System.out.println("姓名:" + name + ",年龄:" + age);
    }
    public static void main(String[] args) {
        new Person("张三", 30).tell(); // 匿名对象
    }
}

程序运行结果为:

姓名:张三,年龄:30

在以上程序的主方法中可以发现,直接使用了“new Person("张三",30)”语句,这实际上就是一个匿名对象,与之前声明的对象不同,此处没有任何栈内存引用它,所以此对象使用一次之后就等待被 GC(垃圾收集机制)回收。

匿名对象在实际开发中基本都是作为其他类实例化对象的参数传递的,在Java 应用部分的很多地方都可以发现其用法,匿名对象实际上就是个堆内存空间,对象不管是匿名的还是非匿名的,都必须在开辟堆空间之后才可以使用。

总结

本篇文章就到这里了,希望能给你带来帮助,也希望您都能够多多关注自学编程网的更多内容!

编程技巧