Java是一门纯面向对象的语言,在面向对象的世界里,一切皆为对象。面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。用面向对象的思想来涉及程序,更符合人们对事物的认知,对于大型程序的设计、扩展以及维护都非常友好
那么什么是类,什么是对象呢?
通俗一点理解就是,类就是一个模板,用来对一个实体进行描述,限定了对象有哪些属性和行为.我们按照这个模板来实例化出许多个对象,
在java中,我们描述一个对象是通过描述这个对象的属性和行为进行描述的,举个例子
当以汽车为例进行描述时,可以将汽车的属性和行为抽象为一个类的属性和方法。
属性(属性描述了汽车的特征):
行为(行为描述了汽车能够执行的动作):
通过定义汽车类,并在类中定义相应的属性和方法,就能够对汽车进行描述和操作。这样的描述方式能够更清楚地表示汽车对象的特征和能力,使我们可以更好地理解和模拟现实生活中的汽车行为。
因为类是一个模板,它是一个抽象的东西,但是由他产生的对象则是具体的,所以我们经常说实例化一个这个类的对象
class ClassName{
field; // 属性 即 成员变量
method; // 行为 即 成员方法
}
class为定义类的关键字,ClassName为类的名字,{ }中为类的主体。
下面通过举例来说明类的定义
当我们描述一个狗狗的时候可以通过狗狗的名字和颜色,以及狗狗狗叫和摇尾巴的行为来进行描述
所以我们可以定义一个这样的狗狗类:
class PetDog {
// 狗的属性
public String name;//名字
public String color;//颜色
// 狗的行为
public void barks() {
System.out.println(name + ": 旺旺旺~~~");
}
public void wag() {
System.out.println(name + ": 摇尾巴~~~");
}
}
注意成员变量和局部变量是两个不同的概念
成员变量是定义在类中的变量,可以在类的任何方法中使用。它们的作用域是整个类,可以通过对象来访问。通常,成员变量用于存储对象的状态或属性,例如对象的名称、年龄等。成员变量在对象创建时被初始化,并且可以在整个对象的生命周期内被访问和修改。
局部变量是在方法、代码块或构造函数中定义的变量。它们的作用域限定在声明它们的方法、代码块或构造函数的范围内。局部变量只在其定义的范围内可见,并且只能在该范围内访问。局部变量在方法执行时被创建,并在方法结束后被销毁。它们通常用于存储临时数据和方法内部的计算结果。
以下是类使用过程中的两个注意点:
在Java中,公有类是指被声明为public的类,公有类可以让其他类对这个公有类进行访问。
main方法是Java程序的入口点,是程序执行的起点。Java程序必须包含一个main方法。当程序运行时,Java虚拟机(JVM)会根据这个方法开始执行程序。
main一定要在公有类的主要原因是为了让Java虚拟机能够找到程序的入口,Java虚拟机需要通过反射来调用main方法,而反射只能访问公有方法。如果将main方法定义为非公有的,虚拟机将无法找到并调用该方法,导致程序无法执行。
此外,将main方法置于公有类中还有助于程序的可读性和维护性。公有类作为程序的入口点,可以更方便地理解整个程序的结构和功能。公有类也可以在其他程序中直接被引用,使程序更具可重用性和扩展性。
我们已经有了类了,那么我们该如何通过类来实例化一个对象呢?
class PetDog {
// 狗的属性
public String name;//名字
public String color;//颜色
// 狗的行为
public void barks() {
System.out.println(name + ": 旺旺旺~~~");
}
public void wag() {
System.out.println(name + ": 摇尾巴~~~");
}
}
我们可以通过
- PetDog dogh = new PetDog();
这行代码来完成对类的示例化,下面我来详细解释一下这段代码的意思:
new是java中的一个关键字,用来创建一个空间,而new PetDog就是创建一个PetDog类型的空间,并且new PetDog会返回创建好空间的地址,而new PetDog后的( )则是传给构造方法的参数
接着dogh是一个引用,我们通过赋值符号 = 将创建好空间的地址赋给了dogh,所以dogh指向了这个空间,PetDog dogh就表示dogh这个引用的类型是PetDog的为什么引用也有类型呢?主要是想说明你这个引用指向的空间应该是PetDog类型的或者子类空间类型的空间(还没有讲到子类,这里简单有个印象即可),并说明dogh能管理的空间只有PetDog类型这么大的空间
下面举一个简单的代码例子来演示一下:
class PetDog {
// 狗的属性
public String name;//名字
public String color;//颜色
// 狗的行为
public void barks() {
System.out.println(name + ": 旺旺旺~~~");
}
public void wag() {
System.out.println(name + ": 摇尾巴~~~");
}
}
public class Main{
public static void main(String[] args) {
PetDog dogh = new PetDog(); //通过new实例化对象
dogh.name = "阿黄";
dogh.color = "黑黄";
dogh.barks();
dogh.wag();
}
}
这段代码的输出结果是:
阿黄: 旺旺旺~~~
阿黄: 摇尾巴~~~
首先我们来思考一下为什么要有this引用,下面通过一个代码来引出:
public class Person {
String name;
int age;
public void setPerson(String n, int a) {
name = n;
age = a;
}
public void printDetails() {
System.out.println("姓名:" + this.name);
System.out.println("年龄:" + this.age);
}
public static void main(String[] args) {
Person person = new Person();
person.setPerson(张三,20);
person.printDetails();
}
}
再这段代码中,我们先实例化了一个Person对象,接着通过person这个对象中的setPerson方法对name和age进行了实例化,接着通过person这个对象中的printDetails方法对信息进行打印,运行结果如下:
姓名:张三
年龄:20
但是万一我们不小心将setPerson方法错误的写成了
public void setPerson(String name, int age) {
name = name;
age = age;
}
这样我们就分不清楚我们传入的name是哪个了,并且根据局部优先的原则,程序会将name = name 被解析成了,将成员变量name赋值给形参变量name,这和我们的本意相反
那么我们该如何避免这种情况呢?答案是通过this引用
this引用有三个很重要的作用:
我们可以通过this.name来告诉编译器,这个name是成员变量中的name,而不是局部变量的name
我们可以在Person类中通过this.printDetails()来调用printDetails()这个方法
我们可以通过this()来在构造方法中调用其他构造方法,并且这一行代码应该在第一行
所以我们可以对上述的代码进行改进
public class Person {
String name;
int age;
public void setPerson(String name, int age) {
this.name = name;
this.age = age;
}
public void printDetails() {
System.out.println("姓名:" + this.name);
System.out.println("年龄:" + this.age);
}
public static void main(String[] args) {
Person person = new Person();
person.setPerson(张三,20);
person.printDetails();
}
}
this.name = name;表示将局部变量name赋值给成员变量name,这样就可以避免了冲突,而且方便我们更加直观的去理解代码
public class Person {
String name;
int age;
public void setPerson(String name, int age) {
this.name = name;
this.age = age;
}
public void printDetails() {
System.out.println("姓名:" + this.name);
System.out.println("年龄:" + this.age);
}
public static void main(String[] args) {
Person person = new Person();
person.setPerson(张三,20);
person.printDetails();
}
}
在这段代码中,我们每创建一个对象都要通过setPerson这个方法来对对象进行初始化,在java中,创建对象是一个很频繁的事情,所以我们没创建一个对象就要去调用一次setPerson方法是不是有点太麻烦了?
所以java给我们提供了一个方法,名为构造方法,构造方法每创建一次对象,就会为这个对象调用一次构造方法,所以我们就可以通过构造方法来完成对对象的初始化
所以上述代码我们可以改成:
public class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public void printDetails() {
System.out.println("姓名:" + this.name);
System.out.println("年龄:" + this.age);
}
public static void main(String[] args) {
Person person = new Person();
person.setPerson(张三,20);
person.printDetails();
}
}
构造方法的特性:
下面我来对这些特性进行说明:
构造方法的名字为什么要和类名相同?
为什么构造方法没有返回值类型,void也不可以?
注意:当一个类实例化了一个对象,那么就一定要调用一次构造方法,如果用户没有定义构造方法,那么系统会默认生成一份没有参数的构造方法供我们使用,但是如果我们写了任意一个构造方法,那么系统就不会帮我们生成这个无参的构造方法了
构造方法在实例化对象的时候一定要被调用,如果没有调用则会报错
PetDog dogh = new PetDog(张三,20);我们可以在实例化对象的时候通过()来将参数传递给构造方法,记得顺序和类型要对应上哦
在构造方法中,我们可以通过this()来调用其他的构造方法,其中this中的括号要放入对应的参数比如:this (1900,1,1);且this (1900,1,1);必须是第一条语句
为什么局部变量在使用的时候必须要初始化,而成员变量可以不用呢?
要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情:
在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单介绍下:
检测对象对应的类是否加载了,如果没有加载则加载
为对象分配内存空间
处理并发安全问题
比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突
设置对象头信息(关于对象内存模型后面会介绍)
调用构造方法,给对象中各个成员赋值
所以当我们实例化一个对象之后,就已经给成员变量赋值了,那些值都是0的变式,当然我们也可以在类中对成员变量进行赋值比如:
public class Date {
public int year = 1900;
public int month = 1;
public int day = 1;
}