文章目录
- 一,面向对象概述
- 二,类和对象
- (一)类和对象的概述
- (二)定义类的补充注意事项
- 三,封装
- 四,就近原则和this关键字
- (一)就近原则
- (二)this关键字
- 五,构造方法
- (一)构造方法的概述
- (二)构造方法的作用
- (二)构造方法的种类及各自的作用
- (三)构造方法的注意事项
- 六,标准的JavaBean类
- 七,三种情况的对象内存图
- (一)一个对象的内存图
- (二)多个对象的内存图
- (三)两个变量指向同一个对象的内存图
- 八,基本数据类型和引用数据类型
- (一)基本数据类型
- (二)引用数据类型
- 九,this的内存原理
- 十,成员变量和局部变量
- (一)成员变量
- (二)局部变量
- (三)成员变量和局部变量的区别
- 十一,综合练习
- (一)文字版格斗游戏
- (二)对象数组练习
- 1,对象数组1
- 2,对象数组2
- 3,对象数组3
- 4,定义数组4
- 5,对象数组5
一,面向对象概述
Java是一种面向对象的编程语言,面向对象是一种编程范式,它将程序设计看作是对象的集合,每个对象都有自己的状态和行为,并通过相互之间的消息传递来进行交互。
在面向对象的编程中,可以通过定义类来创建对象。类是一种模板或者蓝图,描述了对象的属性和行为。对象是类的实例化,它具有类所定义的属性和行为。
面向对象的编程有以下几个核心概念:
-
类(Class):类是对象的抽象,描述了一类具有相同属性和行为的对象。它定义了对象的结构和行为。
-
对象(Object):对象是类的实例化,是具体的实体,具有类定义的属性和行为。
-
属性(Attribute):属性是对象的状态,描述了对象的特征。
-
方法(Method):方法是对象的行为,描述了对象可以做什么。
-
封装(Encapsulation):封装是将对象的属性和方法封装在一起,隐藏内部实现细节,只对外提供必要的接口。
-
继承(Inheritance):继承是一种机制,允许创建一个新的类,从已有的类派生出来。子类继承父类的属性和方法,并可以添加自己特有的属性和方法。
-
多态(Polymorphism):多态是指同一个类的对象在不同的情况下表现出不同的行为。通过继承和方法重写实现了多态。
面向对象的编程具有封装性、继承性和多态性等特点,可以提高代码的可重用性、可维护性和可扩展性。它使得程序的设计更加模块化,更易于理解和修改。面向对象的编程在Java中得到了广泛应用,并且是Java语言的基本特性之一。
二,类和对象
(一)类和对象的概述
在Java中,类是面向对象编程的基本单位,它是对象的抽象描述,定义了对象的属性和行为。而对象是类的实例化,具体的实体,拥有类所定义的属性和行为。
类的定义通常包括以下几个部分:
-
类声明:使用关键字
class
来声明一个类,后面跟类的名称。 -
属性(成员变量):类的属性是描述对象特征的变量,可以是基本数据类型或者其他类的对象。属性定义在类的内部,可以通过访问修饰符来控制访问权限。
-
方法:类的方法是描述对象行为的函数,定义了对象可以做什么。方法也定义在类的内部,可以通过访问修饰符来控制访问权限。
-
构造方法:构造方法是一种特殊的方法,用于创建对象时进行初始化操作。构造方法的名称与类的名称相同,没有返回类型,并且可以重载。
-
访问修饰符:访问修饰符用于控制类、属性和方法的访问权限,包括public、protected、private和默认(不声明访问修饰符)。
-
类的关系:类与类之间可能存在继承关系、实现关系和关联关系,这些关系可以通过关键字
extends
、implements
和引用类型变量来表示。
对象是类的实例化,通过使用new
关键字来创建类的对象。创建对象后,可以使用对象的引用变量来访问对象的属性和调用对象的方法。
例如,下面是一个简单的Java类和对象的示例:
// 定义一个类
class Person {
// 属性
String name;
int age;
// 构造方法
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 方法
public void sayHello() {
System.out.println("Hello, my name is " + name + ", I'm " + age + " years old.");
}
}
// 创建对象并使用
public class Main {
public static void main(String[] args) {
// 创建Person对象
Person person = new Person("John", 25);
// 访问对象的属性
System.out.println(person.name); // 输出:John
System.out.println(person.age); // 输出:25
// 调用对象的方法
person.sayHello(); // 输出:Hello, my name is John, I'm 25 years old.
}
}
在上面的示例中,我们定义了一个Person类,它有两个属性(name和age)和一个方法(sayHello)。然后在主函数中,我们创建了一个Person对象,并通过对象的引用变量来访问对象的属性和调用对象的方法。
(二)定义类的补充注意事项
在Java中定义类时,除了遵循标准的JavaBean规范外,还有一些补充的注意事项:
- 类名的命名规范:类名应该采用驼峰命名法,首字母大写,例如"Person"。
- 文件名的命名规范:Java源文件的文件名应该与类名相同,并以.java为扩展名,例如"Person.java"。
- 包的命名规范:如果类位于一个包中,包名应该全部小写,并使用点(.)作为分隔符,例如"com.example"。包名应该反映类的层次结构和所属项目的唯一性。
- 访问修饰符的使用:类可以使用四种不同的访问修饰符:public、protected、default(即不写任何修饰符)和private。根据需要,选择适当的访问修饰符,以控制类的可见性和访问权限。
- 继承和接口实现:Java中的类可以继承其他类,并实现一个或多个接口。继承和接口实现通过关键字extends和implements来实现。
- 成员变量和局部变量的命名规范:成员变量应该使用驼峰命名法,首字母小写,例如"age"。局部变量应该使用驼峰命名法或者简短的命名,例如"i"。
- 类的设计原则:在定义类时,可以考虑一些设计原则,如单一职责原则、开放封闭原则、依赖倒置原则等,以提高代码的可维护性、可扩展性和可重用性。
在编写Java代码时,遵循这些注意事项可以使代码更加规范、易读和易于维护。同时,也可以提高代码的可移植性,并与其他开发人员的代码保持一致。
三,封装
在Java中,封装是一种面向对象编程的重要概念,它将类的属性和方法封装在一起,对外部使用者隐藏了类的内部实现细节,只暴露必要的接口供外部访问。
封装的目的是为了保护类的内部数据不被直接访问和修改,只能通过类提供的公开方法进行操作。这样可以防止外部代码对内部数据的错误修改和意外访问,提高代码的安全性和可维护性。
在Java中,通过访问修饰符来实现封装:
-
private:私有访问修饰符,只能在当前类中访问,外部类无法直接访问。
-
public:公共访问修饰符,可以在任何地方访问。
-
protected:受保护的访问修饰符,可以在当前类、同一包内的类和子类中访问。
-
默认(不声明访问修饰符):默认访问修饰符,只能在当前包内访问。
通常情况下,类的属性都应该声明为私有(private)访问修饰符,然后通过公共(public)的setter和getter方法来访问和修改属性的值。这样可以在setter和getter方法中加入额外的逻辑操作,实现对属性的控制和保护。
下面是一个示例代码:
class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
// 可以在setter方法中添加逻辑判断
if (name != null && !name.isEmpty()) {
this.name = name;
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
// 可以在setter方法中添加逻辑判断
if (age >= 0 && age <= 100) {
this.age = age;
}
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person();
person.setName("John");
person.setAge(25);
System.out.println(person.getName()); // 输出:John
System.out.println(person.getAge()); // 输出:25
}
}
在上面的示例中,Person类的属性name和age都被声明为私有(private)访问修饰符,外部无法直接访问。通过公共(public)的setter和getter方法来访问和修改属性的值,并在setter方法中加入了逻辑判断,保证属性值的有效性。
这样做的好处是,Person类的内部实现细节对外部是隐藏的,外部只能通过公共接口来操作属性,提高了代码的安全性和可维护性。同时,如果需要对属性的访问和修改进行额外的处理,只需要在setter和getter方法中添加相应的逻辑即可。
四,就近原则和this关键字
(一)就近原则
在Java中,就近原则(Lexical Scoping)是指在程序中变量的作用域是由它在代码中声明的位置决定的。当程序中存在同名的变量时,就近原则规定在给定作用域内,优先使用最近声明的变量。
例如,考虑以下示例代码:
public class Main {
public static void main(String[] args) {
int x = 5;
{
int x = 10;
System.out.println(x); // 输出:10
}
System.out.println(x); // 输出:5
}
}
在上面的代码中,有两个同名的变量x。第一个x是在main方法中声明的,第二个x是在一个代码块内部声明的。根据就近原则,当我们在代码块内部访问变量x时,应该使用最近声明的变量,即x = 10。而当我们在代码块外部访问变量x时,应该使用最近的作用域中声明的变量,即x = 5。
就近原则在Java中非常重要,它确保变量的作用域是可控的,并避免了命名冲突和变量混淆的问题。在编写代码时,我们应该养成良好的命名习惯,避免使用相同的变量名,以免造成误解和错误。同时,合理使用代码块和方法等,可以控制变量的作用域,遵循就近原则,使代码更加清晰易懂。
(二)this关键字
在Java中,this是一个关键字,它代表当前对象的引用。this关键字可以在类的成员方法中使用,用于引用当前对象的成员变量和成员方法。
使用this关键字可以解决以下两种常见的问题:
- 解决命名冲突:当成员变量与方法参数或局部变量同名时,使用this关键字可以明确指定要引用的是成员变量还是方法参数或局部变量。例如:
public class Person {
private String name;
public void setName(String name) {
this.name = name;
}
}
// 使用示例
Person person = new Person();
person.setName("John");
在上面的例子中,setName方法的参数名是name,而成员变量也是name。为了解决命名冲突,我们使用this关键字来引用成员变量,即this.name = name。
- 在构造方法中调用其他构造方法:在一个类中,可以定义多个构造方法(重载),当我们在一个构造方法中调用其他构造方法时,可以使用this关键字。例如:
public class Person {
private String name;
private int age;
public Person(String name) {
this.name = name;
}
public Person(String name, int age) {
this(name); // 调用另一个构造方法
this.age = age;
}
}
// 使用示例
Person person = new Person("John", 25);
在上面的例子中,第二个构造方法调用了第一个构造方法,使用this(name)的方式传递name参数,这样可以避免代码的重复。
总之,this关键字在Java中用于引用当前对象的成员变量和成员方法,并且可以在构造方法中调用其他构造方法。它的使用可以帮助解决命名冲突和代码重复的问题。
五,构造方法
(一)构造方法的概述
构造方法(Constructor)是一种特殊的方法,用于创建对象并初始化对象的属性。在Java中,每个类都可以有一个或多个构造方法。
构造方法的概述如下:
- 构造方法的名称必须与类名完全相同,且没有返回类型(包括void)。
- 构造方法可以有零个或多个参数,用于接收外部传入的值。
- 构造方法可以重载,即一个类可以有多个构造方法,只要它们的参数列表不同。
- 如果类没有显式定义构造方法,系统会自动提供一个默认的无参构造方法。但如果在类中定义了带参数的构造方法,系统不会自动提供默认构造方法,需要手动定义。
- 构造方法可以有访问修饰符,如public、protected、private或默认(即没有修饰符)。根据需要,可以选择适当的访问修饰符来控制构造方法的访问权限。
- 构造方法通常用于在创建对象时进行属性的初始化。在构造方法中,可以使用传入的参数对对象的属性进行赋值。
- 构造方法在使用new关键字创建对象时被调用,创建对象并分配内存空间。构造方法会在对象创建完成后立即执行。
- 构造方法不能通过对象来调用,只能通过new关键字来调用。
- 构造方法可以使用this关键字来调用同一个类中的其他构造方法,称为构造方法链。通过构造方法链,可以在创建对象时使用不同的构造方法来进行属性初始化。
构造方法的主要作用是初始化对象的属性,并提供不同的方式来创建对象。合理地使用构造方法可以确保对象的属性被正确初始化,并满足不同场景下的需求。
(二)构造方法的作用
Java构造方法是一种特殊的方法,它的作用是用于创建对象并初始化对象的属性。它的名称必须与类名相同,并且没有返回类型(包括void)。构造方法在使用new关键字时自动调用,用于在内存中分配对象的空间,并对对象的属性进行初始化。
构造方法的作用包括:
-
创建对象:构造方法用于在内存中创建对象的实例。当使用new关键字创建对象时,系统会自动调用对应类的构造方法,通过构造方法分配对象所需的内存空间。
-
初始化对象属性:构造方法可以对对象的属性进行初始化,为对象的属性赋予初始值。通过在构造方法中设置参数,可以在创建对象时为属性传递初始值。
-
提供默认构造方法:如果类没有显式定义构造方法,系统会自动提供一个默认的无参构造方法。默认构造方法可以在创建对象时进行调用。如果类中有自定义的构造方法,但没有提供无参构造方法,那么在创建对象时如果没有传递参数,编译器会报错。
-
初始化对象的状态:构造方法可以在创建对象时对对象的状态进行初始化,例如设置对象的初始状态、连接数据库等操作。
-
控制对象的创建过程:通过构造方法的重载和访问修饰符的使用,可以控制对象的创建过程,限制对象的访问权限等。
总结来说,构造方法在创建对象时起到了至关重要的作用,它不仅用于分配对象的内存空间,还可以对对象的属性进行初始化,提供默认构造方法,初始化对象的状态,以及控制对象的创建过程。构造方法在类的定义中起到了重要的作用,是面向对象编程中的一个基本概念。
(二)构造方法的种类及各自的作用
在Java中,构造方法可以分为以下几种类型:
-
默认构造方法(无参构造方法):如果类没有显式定义构造方法,系统会自动提供一个默认的无参构造方法。默认构造方法没有任何参数,并且什么都不做。它的作用是创建对象并分配内存空间,但不对对象的属性进行初始化。
-
带参数的构造方法:带参数的构造方法可以接受一个或多个参数,用于在创建对象时传递初始值。通过在构造方法中设置参数,可以对对象的属性进行初始化。带参数的构造方法可以根据不同的参数组合进行重载,以满足不同的需求。
-
重载构造方法:在一个类中,可以定义多个构造方法,只要它们的参数列表不同。重载构造方法的作用是提供多种创建对象的方式,根据不同的参数组合来初始化对象的属性。
-
私有构造方法:私有构造方法是指访问修饰符为private的构造方法,它仅在本类中可见,外部无法直接访问。私有构造方法的作用是限制对象的创建,通常用于实现单例模式或工具类,确保只能通过特定的方式创建对象。
-
构造方法链:在一个构造方法中,可以使用this关键字调用同一个类中的其他构造方法,称为构造方法链。构造方法链的作用是在创建对象时,可以通过不同的构造方法来进行属性初始化,避免代码重复。
总结来说,构造方法的种类包括默认构造方法、带参数的构造方法、重载构造方法、私有构造方法和构造方法链。它们的作用分别是创建对象并分配内存空间、对对象的属性进行初始化、提供多种创建对象的方式、限制对象的创建、以及在构造方法中调用其他构造方法来避免代码重复。不同的构造方法可以根据需求灵活地使用。
(三)构造方法的注意事项
在使用构造方法时,需要注意以下几点:
-
构造方法与类名相同:构造方法的名称必须与类名完全相同,并且没有返回类型(包括void)。如果构造方法的名称与类名不一致,它会被当作普通方法,而不是构造方法。
-
构造方法的重载:一个类可以有多个构造方法,只要它们的参数列表不同。构造方法的重载可以提供多种创建对象的方式,根据不同的参数组合进行初始化。
-
默认构造方法的提供:如果类没有显式定义构造方法,系统会自动提供一个默认的无参构造方法。但是,如果在类中定义了带参数的构造方法,系统不会自动提供默认构造方法,需要手动定义。
-
构造方法的访问修饰符:构造方法可以使用public、protected、private或默认(即没有修饰符)这些访问修饰符。根据需要,可以选择适当的访问修饰符来控制构造方法的访问权限。
-
构造方法的调用:通过使用new关键字来调用构造方法,创建对象并分配内存空间。构造方法可以在创建对象时进行属性的初始化,但不能在创建对象后再次调用。
-
构造方法的链式调用:在一个构造方法中,可以使用this关键字调用同一个类中的其他构造方法,称为构造方法链。通过构造方法链,可以在创建对象时使用不同的构造方法来进行属性初始化。
-
私有构造方法的使用:私有构造方法的访问权限仅限于类内部,外部无法直接访问。私有构造方法通常用于实现单例模式或工具类,限制对象的创建。
-
构造方法不能被继承:构造方法不会被继承,子类不能直接调用父类的构造方法。子类中可以使用super关键字来调用父类的构造方法,用于初始化继承自父类的属性。
以上是在使用构造方法时需要注意的几点。合理地使用构造方法可以正确初始化对象的属性,并控制对象的创建和访问权限。
六,标准的JavaBean类
在Java中,标准的JavaBean类是一种遵循特定规范的类,用于封装数据并提供访问和操作这些数据的方法。JavaBean类通常用于在不同层之间传递数据,例如在Web开发中存储和传递表单数据。
标准的JavaBean类应该满足以下要求:
- 类必须具有一个公共的无参构造方法:这允许使用默认的构造方法实例化JavaBean对象。
- 属性必须是私有的:JavaBean类通常将属性声明为私有,并通过公共的getter和setter方法提供对属性的访问。
- 提供公共的getter和setter方法:JavaBean类应该为每个属性提供公共的getter和setter方法,以允许其他类访问和修改属性的值。
- 实现Serializable接口(可选):如果JavaBean类需要被序列化或者需要在网络上传输,可以实现Serializable接口。
以下是一个示例的标准JavaBean类:
public class Person implements Serializable {
private String name;
private int age;
public Person() {
// 默认的无参构造方法
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
在上面的示例中,Person类是一个标准的JavaBean类。它满足了以下要求:拥有一个公共的无参构造方法、私有的属性、公共的getter和setter方法,并可选择实现Serializable接口。
标准的JavaBean类可以被用于各种用途,例如在Spring框架中作为数据传输对象(DTO),在Hibernate中作为持久化实体类等。通过遵循JavaBean规范,可以更好地封装和管理数据,并提供对数据的访问和操作。
七,三种情况的对象内存图
(一)一个对象的内存图
下面是一个Java对象的内存图示例:
+---------------------------+
| |
| Object Header |
| |
+---------------------------+
| Instance Variables |
| |
+---------------------------+
| |
| Padding |
| |
+---------------------------+
Java对象的内存图主要包括以下几个部分:
- Object Header:对象头,包含一些与对象相关的元数据,如对象的哈希码、锁状态、GC标记等信息。
- Instance Variables:实例变量,存储对象的属性值。每个实例变量占用一定的内存空间。
- Padding:内存对齐填充,用于保持对象的对齐性和性能优化。
在内存中,Java对象被分配在堆(Heap)中。当使用new关键字创建对象时,会在堆中分配一块连续的内存空间来存储对象的实例变量。对象头和内存对齐填充不计入对象的实例变量空间。
实例变量可以是基本数据类型(如int、double等)或者引用类型(如String、数组等)。对于引用类型的实例变量,实际存储的是对对象的引用,而不是对象本身。
通过给对象的实例变量赋值,可以修改对象的属性值。不同的对象拥有独立的内存空间,因此它们的属性可以独立地进行修改和访问。
在Java中,对对象的内存管理由垃圾回收器(Garbage Collector)负责。当对象不再被引用时,垃圾回收器会自动回收对象所占用的内存空间,以供后续的对象分配使用。
(二)多个对象的内存图
下面是一个Java程序中多个对象的内存图示例:
+------------------------------+
| Object Header |
+------------------------------+
| Instance Variables |
+------------------------------+
| Padding |
+------------------------------+
+------------------------------+
| Object Header |
+------------------------------+
| Instance Variables |
+------------------------------+
| Padding |
+------------------------------+
...
每个Java对象都有自己的内存图,对象之间互相独立存储,并且拥有相同的结构。每个对象的内存图都包含了对象头、实例变量和内存对齐填充。
在内存中,每个对象都被分配在堆(Heap)中的不同的内存空间上。当创建多个对象时,Java会在堆中分配多个连续的内存块来存储每个对象的实例变量。每个对象的实例变量可以独立地进行修改和访问。
不同对象的内存图之间可以有共享的实例变量,即多个对象的同一实例变量指向同一个内存位置。这通常发生在引用类型的实例变量中,当多个对象引用同一个对象时,它们的实例变量指向同一个对象的引用。
Java中的垃圾回收器(Garbage Collector)会自动回收不再被引用的对象所占用的内存空间。当对象不再被引用时,垃圾回收器会将其标记为垃圾,并在适当的时候回收它们占用的内存,以供后续的对象分配使用。
总结来说,多个对象的内存图在堆中是独立分配的,每个对象拥有自己的对象头、实例变量和内存对齐填充。对象之间可以共享引用类型的实例变量,而垃圾回收器会负责回收不再被引用的对象。
(三)两个变量指向同一个对象的内存图
下面是一个Java程序中两个变量指向同一个对象的内存图示例:
+------------------------+
| Variable 1 |
+------------------------+
| Memory Address |
+------------------------+
|
v
+------------------------+
| Variable 2 |
+------------------------+
| Memory Address |
+------------------------+
|
v
+------------------------------+
| Object Header |
+------------------------------+
| Instance Variables |
+------------------------------+
| Padding |
+------------------------------+
在上面的示例中,Variable 1
和Variable 2
是两个Java变量,它们都存储了同一个对象的内存地址。这意味着两个变量实际上指向了同一个对象。
在堆中,有一个对象被分配了内存空间,它有一个对象头和实例变量。Variable 1
和Variable 2
存储了该对象在内存中的地址,通过这个地址,它们可以共享访问该对象的实例变量。
修改一个变量所指向的对象的状态,将会影响到通过另一个变量访问的对象状态。这是因为两个变量指向的是同一个对象,它们共享同一个对象的内存空间。
在Java中,对象的引用是一个指向对象的内存地址的值,而不是对象本身。这允许多个变量引用同一个对象,从而实现对象之间的共享和相互影响。垃圾回收器会负责回收不再被引用的对象所占用的内存空间,但只有当所有引用该对象的变量都不再引用它时,对象才会被认为是不再被引用的垃圾。
八,基本数据类型和引用数据类型
(一)基本数据类型
Java基本数据类型是指Java语言中的基本数据类型,它们是预定义的,不是由类定义的。Java的基本数据类型包括以下几种:
- byte:表示8位有符号整数,范围为-128到127。
- short:表示16位有符号整数,范围为-32,768到32,767。
- int:表示32位有符号整数,范围为-231到231-1。
- long:表示64位有符号整数,范围为-263到263-1。
- float:表示32位单精度浮点数,范围为正负3.40282347e+38F(有效位数为6-7位)。
- double:表示64位双精度浮点数,范围为正负1.79769313486231570e+308(有效位数为15位)。
- boolean:表示布尔值,只有两个取值:true和false。
- char:表示16位Unicode字符。
基本数据类型在内存中占用固定大小的空间,它们被直接存储在栈中,而不是在堆中(与引用类型不同)。每种基本数据类型都有对应的包装类,用于提供与该数据类型相关的操作和方法。
基本数据类型的主要优点是效率高,占用的内存空间较小。然而,它们无法直接调用方法,因为它们不是对象,所以需要使用包装类来操作基本数据类型的一些特殊需求,例如进行比较、转换等操作。
(二)引用数据类型
Java引用数据类型是指Java语言中的非基本数据类型(也称为对象类型),它们是由类或接口创建的。Java的引用数据类型包括以下几种:
- 类(Class):引用数据类型最常见的形式就是类。类是一种自定义的数据类型,它可以包含属性(成员变量)和方法(成员方法),并可以创建该类的对象。
- 接口(Interface):接口是一种定义一组方法签名的引用数据类型,它可以被类实现,从而使类具有接口中定义的行为。
- 数组(Array):数组是一种用来存储相同类型数据的容器,可以存储基本数据类型或引用数据类型的元素。
- 枚举(Enum):枚举是一种特殊的引用数据类型,它限制变量只能取特定的值,这些值在枚举类型中预先定义。
- 字符串(String):字符串是一种引用数据类型,它表示一串字符序列。在Java中,字符串是不可变的,即一旦创建,就不能被修改。
引用数据类型的对象存储在堆内存中,而引用(变量)本身存储在栈内存中。与基本数据类型不同,引用数据类型的变量可以调用方法,并且可以通过引用来操作对象的属性和行为。当对象没有被任何引用变量引用时,它将成为垃圾,并由Java的垃圾回收机制自动回收内存。
九,this的内存原理
在Java中,关键字"this"代表当前对象的引用。它可以在对象的方法中使用,用来访问当前对象的成员变量和成员方法。
内存原理方面,每当我们创建一个对象时,Java会在堆内存中为该对象分配一块内存空间,并将对象的成员变量存储在这个内存空间中。同时,Java会为这个对象创建一个指向该内存空间的引用。
当我们在对象的方法中使用"this"关键字时,编译器会将"this"关键字解析为对当前对象的引用。具体地说,编译器会通过将"this"关键字作为方法参数传递给方法,从而使方法能够访问当前对象的成员变量和成员方法。
在内存中,每个线程都会有一个栈,称为"线程栈"。当一个方法被调用时,Java会在当前线程的栈中创建一个"栈帧",栈帧包含了方法的局部变量、方法参数以及方法返回值等信息。此时,"this"关键字被存储在栈帧中的一个特殊位置,用于指向当前对象的内存空间。
总结起来,Java中的"this"关键字是一个指向当前对象的引用,它在对象的方法中使用。它的内存原理是通过在当前线程的栈帧中存储一个指向当前对象的引用,从而使方法能够访问当前对象的成员变量和成员方法。
十,成员变量和局部变量
(一)成员变量
Java中的成员变量也称为实例变量,是定义在类中的变量。它们是属于对象的,每个对象都有自己的一份成员变量副本,用来存储对象的状态和属性。
成员变量有以下特点:
- 作用域:成员变量的作用域是整个类,可以在类的任何方法、构造方法、代码块中访问。
- 生命周期:成员变量的生命周期与对象的生命周期一致。当对象被创建时,成员变量就分配相应的内存空间;当对象被销毁时,成员变量的内存空间也被释放。
- 默认值:如果没有显式赋值,成员变量会有默认值。例如,整型的成员变量默认值为0,布尔型的成员变量默认值为false。
- 访问性:成员变量可以使用访问修饰符进行限制,例如public、private、protected等。根据访问修饰符的不同,成员变量可以被不同的类和方法访问。
成员变量通常用来存储对象的属性和状态信息,可以在类的各个方法中使用和修改。它们在类中声明,但在方法中不进行初始化赋值,而是在对象创建时随着对象的初始化而赋予初始值。
(二)局部变量
Java中的局部变量是在方法、构造方法或代码块中声明的变量。它们只在声明的方法、构造方法或代码块中可见,并且只在方法、构造方法或代码块的执行期间存在。
局部变量有以下特点:
- 作用域:局部变量的作用域限定在声明它的方法、构造方法或代码块中。超出该作用域,局部变量将不再可见。
- 生命周期:局部变量的生命周期与方法、构造方法或代码块的执行周期一致。在方法、构造方法或代码块执行结束后,局部变量的内存空间将被释放。
- 必须显式初始化:局部变量在使用前必须进行显式初始化。否则,编译器会报错。
- 不具有默认值:与成员变量不同,局部变量没有默认值。在使用前必须赋值。
局部变量通常用于临时存储和处理方法、构造方法或代码块中的数据。它们在方法、构造方法或代码块内部声明,只在所声明的方法、构造方法或代码块中有效。一旦执行离开该方法、构造方法或代码块,局部变量的内存空间会被释放。
(三)成员变量和局部变量的区别
Java中的成员变量和局部变量是两种不同类型的变量,它们在作用域、生命周期和访问性等方面有所不同。
-
作用域:
- 成员变量(也称为实例变量)是定义在类中、方法之外的变量。它的作用域是整个类,可以被类中的所有方法访问。
- 局部变量是定义在方法、构造方法、代码块中的变量。它的作用域只在声明的方法中,只能在方法内部访问。
-
生命周期:
- 成员变量的生命周期与对象的生命周期一致。当对象被创建时,成员变量就分配相应的内存空间;当对象被销毁时,成员变量的内存空间也被释放。
- 局部变量的生命周期在方法执行时开始,在方法执行结束后结束。当方法执行完毕时,局部变量的内存空间也会被释放。
-
初始化值:
- 成员变量可以有默认值,如果没有显式赋值,它们会根据数据类型而有所不同。例如,整型的成员变量默认值为0,布尔型的成员变量默认值为false。
- 局部变量没有默认值,必须在使用之前显式赋值,否则会编译错误。
-
访问性:
- 成员变量可以使用访问修饰符进行限制,例如public、private、protected等。根据访问修饰符的不同,成员变量可以被不同的类和方法访问。
- 局部变量没有访问修饰符,它们只能在声明的方法内部访问。
总结起来,成员变量是定义在类中和方法之外的变量,作用域是整个类,生命周期与对象一致;而局部变量是定义在方法、构造方法和代码块中的变量,作用域只在声明的方法内部,生命周期在方法执行期间。
十一,综合练习
(一)文字版格斗游戏
需求:格斗游戏,每个游戏角色的姓名,血量,都不相同,在选定人物的时候(new对象的时候),这些信息就应该被确定下来。
下面是一个使用面向对象的Java代码示例,实现带有姓名和血量的文字版格斗游戏:
import java.util.Random;
import java.util.Scanner;
public class FightGame {
private static final int MAX_HEALTH = 100;
private static final int MAX_ATTACK_DAMAGE = 20;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Random random = new Random();
System.out.println("欢迎来到文字版格斗游戏!");
System.out.print("请输入你的角色名字:");
String playerName = scanner.nextLine();
System.out.print("请输入你的角色生命值(1-" + MAX_HEALTH + "):");
int playerHealth = scanner.nextInt();
Player player = new Player(playerName, playerHealth);
Enemy enemy = new Enemy("敌人", MAX_HEALTH);
System.out.println("你的角色名字是:" + player.getName());
System.out.println("你的初始生命值为:" + player.getHealth());
System.out.println("敌人的生命值为:" + enemy.getHealth());
System.out.println("游戏开始!");
while (player.getHealth() > 0 && enemy.getHealth() > 0) {
System.out.print("请输入你的攻击力(1-" + MAX_ATTACK_DAMAGE + "):");
int playerAttack = scanner.nextInt();
int enemyAttack = random.nextInt(MAX_ATTACK_DAMAGE) + 1;
enemy.takeDamage(playerAttack);
player.takeDamage(enemyAttack);
System.out.println("你对敌人造成了" + playerAttack + "点伤害!");
System.out.println("敌人对你造成了" + enemyAttack + "点伤害!");
System.out.println("你的剩余生命值为:" + player.getHealth());
System.out.println("敌人的剩余生命值为:" + enemy.getHealth());
}
if (player.getHealth() <= 0) {
System.out.println("你输了游戏!");
} else {
System.out.println("你赢了游戏!");
}
System.out.println("游戏结束!");
}
}
class Character {
private String name;
private int health;
public Character(String name, int health) {
this.name = name;
this.health = health;
}
public void takeDamage(int damage) {
health -= damage;
if (health < 0) {
health = 0;
}
}
public String getName() {
return name;
}
public int getHealth() {
return health;
}
}
class Player extends Character {
public Player(String name, int health) {
super(name, health);
}
}
class Enemy extends Character {
public Enemy(String name, int health) {
super(name, health);
}
}
在这个示例中,我们创建了一个Character类作为角色的基类,其中包含了姓名和血量两个属性,以及受伤的方法。Player类和Enemy类继承自Character类,分别表示玩家和敌人角色。在实例化Player和Enemy对象时,需要传入姓名和血量参数来初始化对象。
在主方法中,我们首先根据玩家输入的姓名和血量创建了Player对象和Enemy对象。然后,通过调用对象的方法来获取和修改角色的属性。游戏过程中,玩家和敌人轮流攻击,并根据攻击造成的伤害来更新角色的血量。最后,根据玩家和敌人的血量判断游戏的胜负,并输出相应的结果。
(二)对象数组练习
1,对象数组1
需求:定义数组存储3个商品对象。
商品的属性:商品的id,名字,价格,库存。
创建三个商品对象,并把商品对象存入到数组当中。
下面是一个使用面向对象的Java代码示例,实现创建商品对象并存储到对象数组中:
import java.util.Scanner;
public class ProductArray {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Product[] products = new Product[3];
for (int i = 0; i < products.length; i++) {
System.out.println("请输入第" + (i + 1) + "个商品的信息:");
System.out.print("商品ID:");
int id = scanner.nextInt();
System.out.print("商品名称:");
String name = scanner.next();
System.out.print("商品价格:");
double price = scanner.nextDouble();
System.out.print("商品库存:");
int stock = scanner.nextInt();
Product product = new Product(id, name, price, stock);
products[i] = product;
}
System.out.println("三个商品的信息如下:");
for (int i = 0; i < products.length; i++) {
System.out.println("商品ID:" + products[i].getId());
System.out.println("商品名称:" + products[i].getName());
System.out.println("商品价格:" + products[i].getPrice());
System.out.println("商品库存:" + products[i].getStock());
System.out.println("---------------------------");
}
}
}
class Product {
private int id;
private String name;
private double price;
private int stock;
public Product(int id, String name, double price, int stock) {
this.id = id;
this.name = name;
this.price = price;
this.stock = stock;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public double getPrice() {
return price;
}
public int getStock() {
return stock;
}
}
在这个示例中,我们创建了一个Product类来表示商品,包含了商品的ID、名称、价格和库存四个属性。在ProductArray类的主方法中,我们首先定义了一个长度为3的Product数组用于存储商品对象。
然后,通过循环从用户输入中获取每个商品的信息,并使用构造方法创建Product对象。将每个创建的Product对象存储到数组中。
最后,通过遍历数组,将每个商品对象的属性打印出来展示给用户。
2,对象数组2
需求:定义数组存储3部汽车对象。
汽车的属性:品牌,价格,颜色。
创建三个汽车对象,数据通过键盘录入而来,并把数据存入到数组当中。
下面是一个使用面向对象的Java代码示例,实现创建汽车对象并存储到对象数组中:
import java.util.Scanner;
public class CarArray {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Car[] cars = new Car[3];
for (int i = 0; i < cars.length; i++) {
System.out.println("请输入第" + (i + 1) + "部汽车的信息:");
System.out.print("汽车品牌:");
String brand = scanner.next();
System.out.print("汽车价格:");
double price = scanner.nextDouble();
System.out.print("汽车颜色:");
String color = scanner.next();
Car car = new Car(brand, price, color);
cars[i] = car;
}
System.out.println("三部汽车的信息如下:");
for (int i = 0; i < cars.length; i++) {
System.out.println("汽车品牌:" + cars[i].getBrand());
System.out.println("汽车价格:" + cars[i].getPrice());
System.out.println("汽车颜色:" + cars[i].getColor());
System.out.println("---------------------------");
}
}
}
class Car {
private String brand;
private double price;
private String color;
public Car(String brand, double price, String color) {
this.brand = brand;
this.price = price;
this.color = color;
}
public String getBrand() {
return brand;
}
public double getPrice() {
return price;
}
public String getColor() {
return color;
}
}
在这个示例中,我们创建了一个Car类来表示汽车,包含了汽车的品牌、价格和颜色三个属性。在CarArray类的主方法中,我们首先定义了一个长度为3的Car数组用于存储汽车对象。
然后,通过循环从用户输入中获取每辆汽车的信息,并使用构造方法创建Car对象。将每个创建的Car对象存储到数组中。
最后,通过遍历数组,将每辆汽车对象的属性打印出来展示给用户。
3,对象数组3
需求:定义数组存储3部手机对象。
手机的属性:品牌,价格,颜色。
要求:计算出三部手机的平均价格。
下面是一个使用面向对象的Java代码示例,实现创建手机对象并计算平均价格:
import java.util.Scanner;
public class PhoneArray {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Phone[] phones = new Phone[3];
for (int i = 0; i < phones.length; i++) {
System.out.println("请输入第" + (i + 1) + "部手机的信息:");
System.out.print("手机品牌:");
String brand = scanner.next();
System.out.print("手机价格:");
double price = scanner.nextDouble();
System.out.print("手机颜色:");
String color = scanner.next();
Phone phone = new Phone(brand, price, color);
phones[i] = phone;
}
double total = 0;
for (int i = 0; i < phones.length; i++) {
total += phones[i].getPrice();
}
double average = total / phones.length;
System.out.println("三部手机的平均价格为:" + average);
}
}
class Phone {
private String brand;
private double price;
private String color;
public Phone(String brand, double price, String color) {
this.brand = brand;
this.price = price;
this.color = color;
}
public String getBrand() {
return brand;
}
public double getPrice() {
return price;
}
public String getColor() {
return color;
}
}
在这个示例中,我们创建了一个Phone类来表示手机,包含了手机的品牌、价格和颜色三个属性。在PhoneArray类的主方法中,我们首先定义了一个长度为3的Phone数组用于存储手机对象。
然后,通过循环从用户输入中获取每部手机的信息,并使用构造方法创建Phone对象。将每个创建的Phone对象存储到数组中。
接着,我们使用一个变量total来累加所有手机的价格。然后,通过除以数组的长度来计算平均价格。
最后,我们将计算出的平均价格打印出来展示给用户。
4,定义数组4
需求:定义数组存储4个女朋友对象。
女朋友的属性:姓名,年龄,性别,爱好。
要求1:计算出四个女朋友的平均年龄。
要求2:统计年龄比平均值低的女朋友有几个?并把她们的所有信息打印出来。
下面是一个使用面向对象的Java代码示例,实现创建女朋友对象并计算平均年龄,以及统计年龄低于平均值的女朋友数量并打印她们的信息:
import java.util.Scanner;
public class GirlfriendArray {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Girlfriend[] girlfriends = new Girlfriend[4];
for (int i = 0; i < girlfriends.length; i++) {
System.out.println("请输入第" + (i + 1) + "个女朋友的信息:");
System.out.print("姓名:");
String name = scanner.next();
System.out.print("年龄:");
int age = scanner.nextInt();
System.out.print("性别:");
String gender = scanner.next();
System.out.print("爱好:");
String hobby = scanner.next();
Girlfriend girlfriend = new Girlfriend(name, age, gender, hobby);
girlfriends[i] = girlfriend;
}
int totalAge = 0;
for (int i = 0; i < girlfriends.length; i++) {
totalAge += girlfriends[i].getAge();
}
int averageAge = totalAge / girlfriends.length;
System.out.println("四个女朋友的平均年龄为:" + averageAge);
int count = 0;
System.out.println("年龄低于平均值的女朋友有:");
for (int i = 0; i < girlfriends.length; i++) {
if (girlfriends[i].getAge() < averageAge) {
count++;
System.out.println(girlfriends[i].toString());
}
}
System.out.println("共有" + count + "个女朋友的年龄低于平均值。");
}
}
class Girlfriend {
private String name;
private int age;
private String gender;
private String hobby;
public Girlfriend(String name, int age, String gender, String hobby) {
this.name = name;
this.age = age;
this.gender = gender;
this.hobby = hobby;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getGender() {
return gender;
}
public String getHobby() {
return hobby;
}
@Override
public String toString() {
return "姓名:" + name + ",年龄:" + age + ",性别:" + gender + ",爱好:" + hobby;
}
}
在这个示例中,我们创建了一个Girlfriend类来表示女朋友,包含了女朋友的姓名、年龄、性别和爱好四个属性。在GirlfriendArray类的主方法中,我们首先定义了一个长度为4的Girlfriend数组用于存储女朋友对象。
然后,通过循环从用户输入中获取每个女朋友的信息,并使用构造方法创建Girlfriend对象。将每个创建的Girlfriend对象存储到数组中。
接着,我们使用一个变量totalAge来累加所有女朋友的年龄。然后,通过除以数组的长度来计算平均年龄。
最后,我们遍历数组,判断每个女朋友的年龄是否低于平均值,如果是,则将其打印出来并统计数量。
同时,我们在Girlfriend类中重写了toString方法,用于返回女朋友对象的信息。
5,对象数组5
需求:定义一个长度为3的数组,数组存储1~3名学生对象作为初始化数据,学生对象的学号,姓名各不相同。
学生的属性:学号,姓名,年龄。
要求1:再次添加一个学生对象,并在添加的时候进行学号的唯一判断。
要求2:添加完毕之后,遍历所有学生信息。
要求3:通过id删除学生信息,如果存在,则删除,如果不存在则表示删除失败。
要求4:删除完毕之后,遍历所有学生信息。
要求5:查询数组id为 “2023002” 的学生,如果存在,则将他的年龄+1岁。
下面是一个使用面向对象的Java代码示例,实现创建学生对象数组,并实现添加、删除、遍历和查询学生信息的功能:
import java.util.Scanner;
public class StudentArray {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Student[] students = new Student[3];
students[0] = new Student("2023001", "张三", 18);
students[1] = new Student("2023002", "李四", 19);
students[2] = new Student("2023003", "王五", 20);
System.out.println("当前学生信息如下:");
printStudents(students);
System.out.println("请输入要添加的学生信息:");
System.out.print("学号:");
String id = scanner.next();
System.out.print("姓名:");
String name = scanner.next();
System.out.print("年龄:");
int age = scanner.nextInt();
boolean isExist = false;
for (int i = 0; i < students.length; i++) {
if (students[i].getId().equals(id)) {
isExist = true;
break;
}
}
if (isExist) {
System.out.println("学号已存在,添加失败!");
} else {
Student newStudent = new Student(id, name, age);
Student[] newStudents = new Student[students.length + 1];
for (int i = 0; i < students.length; i++) {
newStudents[i] = students[i];
}
newStudents[newStudents.length - 1] = newStudent;
students = newStudents;
System.out.println("添加成功!");
System.out.println("当前学生信息如下:");
printStudents(students);
}
System.out.print("请输入要删除的学生学号:");
String deleteId = scanner.next();
boolean isDeleted = false;
for (int i = 0; i < students.length; i++) {
if (students[i].getId().equals(deleteId)) {
students[i] = null;
isDeleted = true;
break;
}
}
if (isDeleted) {
Student[] newStudents = new Student[students.length - 1];
int newIndex = 0;
for (int i = 0; i < students.length; i++) {
if (students[i] != null) {
newStudents[newIndex] = students[i];
newIndex++;
}
}
students = newStudents;
System.out.println("删除成功!");
System.out.println("当前学生信息如下:");
printStudents(students);
} else {
System.out.println("学号不存在,删除失败!");
}
System.out.print("请输入要查询的学生学号:");
String queryId = scanner.next();
boolean isFound = false;
for (int i = 0; i < students.length; i++) {
if (students[i].getId().equals(queryId)) {
students[i].setAge(students[i].getAge() + 1);
isFound = true;
break;
}
}
if (isFound) {
System.out.println("年龄已加1岁!");
System.out.println("当前学生信息如下:");
printStudents(students);
} else {
System.out.println("学号不存在!");
}
}
public static void printStudents(Student[] students) {
for (int i = 0; i < students.length; i++) {
System.out.println(students[i].toString());
}
}
}
class Student {
private String id;
private String name;
private int age;
public Student(String id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "学号:" + id + ",姓名:" + name + ",年龄:" + age;
}
}
在这个示例中,我们创建了一个Student类来表示学生,包含了学生的学号、姓名和年龄三个属性。在StudentArray类的主方法中,我们首先定义了一个长度为3的Student数组,并初始化了数组的前3个元素。
然后,我们通过输入获取要添加的学生信息,并进行学号的唯一判断。如果学号已存在,则添加失败;否则,创建一个新的Student对象,并将其添加到新的数组中。
接着,我们获取要删除的学生学号,并进行判断。如果学号存在,则将对应的数组元素置为null,并创建一个新的数组来存储非null的元素,从而实现删除。
然后,我们获取要查询的学生学号,并进行判断。如果学号存在,则将对应的学生对象的年龄加1岁。
最后,我们通过调用printStudents方法来遍历并打印所有学生信息。
同时,我们在Student类中重写了toString方法,用于返回学生对象的信息。