在面向对象语言中万物皆对象,一切都围绕对象来进行,找对象、建对象,用对象等。
类:把具有相同特征和行为的一组对象抽象为类,类是抽象概念,如人类、车类等,无法具体到每个实体。
对象:某个类的一个实体,当有了对象后,这些特征便有了相应的值,行为也就有了相应的意义。
类是描述某一对象的统称,对象是这个类的一个实例而已。有类之后就能根据这个类来产生具体的对象。一类对象所具备的共同特征和行为(方法)都在类中定义。
类可以看成是创建对象的模板。
从上图中可以看出,类是很多具体实体的共同特征,例如三只犬,都有共同的品种、大小、颜色和年龄等特征,也有吃饭、睡觉、跑和姓名的行为。只不过它们的值不一样而已。
public class 类名{
// 属性
// 方法
}
定义一个类时,可以在类里设置两种类型的元素:属性和方法。
属性是对一个类的描述,属性的值是这个类中不同对象的区分。
属性是直接定义在类中的变量,它还有一个名字叫做成员变量,它的定义和“普通变量”的定义基本一致。
[修饰符] 属性类型 属性名 [ = 默认值];
修饰符:可以省略,可以是 public
、protected
、private
、static
、final
等,也可以相互组合起来修饰属性。
属性类型:可以是 Java
语言允许的任何数据类型,包括基本数据类型和引用数据类型。
属性名:和普通变量的命名原则一致,但若可读性角度来说:属性名应该是由一个或多个有意义的单词连缀而成,第一个单词首字母小写,后面每个单词首字母大写,其他字母全部小写,单词与单词之间不需使用任何分隔符。
默认值:定义属性还可以定义一个默认值,如果未赋值,系统会赋予合适的初值。基本数据类型是 0
,引用数据类型是 null
。
public class Person{
private int id;
public String name;
char gender;
}
修饰符:可以省略,也可以是 public
、protected
、private
、static
、final
、abstract
等, 其中 public
、protected
、private
三个最多只能出现一个,abstract
和 final
最多只能出现其中之一,它们可以与 static
组合起来修饰属性。
方法返回值类型:返回值类型可以是 Java
语言允许的任何数据类型,包括基本类型和引用类型。若声明了返回值类型,则方法体内必须有一个有效的 return
语句,该语句返回一个变量或一个表达式(变量或表达式类型必须与此处声明的类型匹配)。若方法没有返回值,必须使用 void
来声明。
方法名:与属性名规则基本相同,通常建议方法名以英文字的动词开头。
形参列表:用于定义该方法可以接受的参数,由零组或多组“参数类型 形参名”组合而成,多组参数之间以英文逗号(,)隔开,形参类型和形参名之间英文空格隔开。
注意:方法体里多条可执行性语句之间有严格的执行顺序,排在方法体前面的语句总是先执行,排在方法体后面的语句总是后执行。
public class Student{
private String name;
private int age = 33;
public int getAge(){
System.out.println("获取当前对象的年龄");
return age;
}
public void readBook(String bookName){
System.out.println(name + "正在读:" + bookName);
}
}
刚才在定义类时,我们会创建一个 .java
结尾的源文件,类的名称和源文件的名称一致。那么定义的类和源文件有什么关系么?
一个源文件中只能有一个 public
类,源文件的名称应该和 public
类的类名保持一致。一个源文件可以有多个非 public
类。
如果一个类定义在某个包中,那么 package
语句应该在源文件的首行。
如果源文件包含 import
语句,那么应该放在 package
语句和类定义之间。如果没有 package
语句,那么 import
语句应该在源文件中最前面。
import
语句和 package
语句对源文件中定义的所有类都有效。在同一源文件中,不能给不同的类不同的包声明。
现在让我们深入了解什么是对象。看看周围真实的世界,会发现身边有很多对象,车,狗,人等等。
所有这些对象都有自己的状态和行为。
拿一条狗来举例,它的状态有:名字、品种、颜色,行为有:叫、摇尾巴和跑。对比现实对象和软件对象,它们之间十分相似。
软件对象也有状态和行为,软件对象的状态就是属性,行为通过方法体现。
在软件开发中,方法操作对象内部状态的改变,对象的相互调用也是通过方法来完成。
对象是根据类来创建的。在 Java
语言中,使用关键字 new
来创建一个新的对象。创建对象需要以下三步:
声明:声明一个对象,包括对象名称和对象类型。
实例化:使用关键字 new
来创建一个对象。
初始化:使用 new
创建对象时,会调用构造方法初始化对象。
创建对象使用一个关键字 new
来完成。创建对象的语法如下:
类名称 对象名 = new 类名称();
产生了一个类的具体实现,我们把它称之为对象,这个产生的对象其实就是一个变量,这个变量的数据类型就是这个类。所以,我们类就是我们自定义的一个数据类型,而这个类所产生的对象,都是这个类型的变量。例如:
int a = 9;
String b = "Hello,GuanWei";
Person person = new Person();
我们刚才学习了怎么在类中定义属性,那么很多同学都在想,这个属性定义好后如何使用?
对于属性的调用我们分为两种情况:
本类调用
它类调用
在本类中的方法内,可以直接调用本类定义的属性,无需额外操作。
public class Person{
int id = 1;
String name;
public void show(){
name = "张三";
System.out.println("我是:" + name + ",编号:" + id);
}
}
其他类需要通过刚产生出来的对象来使用这些属性。
person.id = 1;
String name = person.name;
我们可以通过 对象名.属性
的方式来操纵它。对于属性而言:
获取属性的值
设置属性的值
和调用属性相似的是,调用方法也有如下的几种情况:
本类调用
它类调用
递归
在本类中的方法内,可以直接调用本类定义的其他,无需额外操作。
public class Person{
public void eat(){
String name = getName();
System.out.println(name + "准备吃饭!");
}
public String getName(){
return "张三";
}
}
其他类需要通过产生出来的对象来使用这些方法。
person.eat();
String name = person.getName();
递归调用是一种特殊的嵌套调用,是某个方法调用自己或者是调用其他方法后再次调用自己的,只要方法之间互相调用能产生循环的则一定是递归调用。
public class Person{
public void eat(){
String name = getName();
System.out.println(name + "准备吃饭!");
// 递归调用
eat();
}
public String getName(){
return "张三";
}
}
形式参数列表:在声明方法时的参数列表,即形参列表,声明调用该方法时需要传递什么类型的数据。
实际参数列表:在调用方法时,需要实际传给方法的数据,即实参列表。按照方法的形参列表的要求传递给方法一系列具体数据。
public class Count{
// 形式参数列表
public void sum(int a,int b){
System.out.println(a + "+" + b + "=" + (a + b));
}
public void show(){
int num1 = 23;
int num2 = 21;
// 实际参数列表
sum(num1,num2);
}
}
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时由编译器自动调用,并且在整个对象的生命周期内只调用一次。
方法名称与类名称完全相同。
构造方法没有返回值声明(不是void)。
一个类中至少存在一个构造方法,若没有显示定义,编译器会生成一个默认的无参构造。
public class Person{
// 构造方法
public Person(){
// 构造方法的实现
}
public Person(int a){
// 构造方法的实现
}
public Person(int a,String b){
// 构造方法的实现
}
public Person(String a){
// 构造方法的实现
}
// ....
}
显式构造器
无参数构造器
有参数构造器
隐式构造器
当一个类中没有提供任何构造方法,系统默认提供一个无参数的构造方法。这个无参数的构造方法叫做隐式构造器,也叫做缺省构造器。
显式构造器就是程序员自己书写的构造器,这些构造器的名称必须和类名一致。通过形式参数列表的不同来区分这些构造器。
构造方法的作用只有一个:初始化对象,会在产生对象时,给对象未赋值的属性赋值。
使用 new
运算符来调用构造方法。从这里我们可以看到,构造器会在产生对象时被调用。
new 构造方法名(实际参数列表);
public class Person{
int id;
String name;
char gender;
public Person(){
id = 1;
name = "张三";
gender = '男';
}
public Person(int id1,String name1,char gender1){
id = id1;
name = name1;
gender = gender1;
}
}
// 其他类的main方法
// 调用了 Person 类的无参数构造器给属性赋值
Person p1 = new Person();
// 调用了 Person 类的有参数构造器给属性赋值
Person p2 = new Person(3,"关为",'男');
public class Person{
int id;
String name;
char gender;
public Person(){
id = 1;
name = "张三";
gender = '男';
}
// ❌的写法
public Person(int id,String name,char gender){
// 这里定义的形式参数id、name和gender和成员变量重名了,如果还是这样赋值是错误的
id = id; // ❌
name = name; // ❌
gender = gender; // ❌
}
// ✔的写法
public Person(int id,String name,char gender){
// 这里我们要使用到 this 关键字,
this.id = id; // 将参数id值赋予成员变量id
this.name = name;
this.gender = gender;
}
}
this
关键字表示对当前对象的引用,一般在重名时使用。
隐式构造器会给未赋值的属性赋予合适的初值(原始类型都是 0
,引用类型是 null
)。
public class Person{
int id;
String name;
char gender;
public void show(){
System.out.println("id:" + id);
System.out.println("name:" + name);
System.out.println("gender:" + gender);
}
}
// 其他类的main方法
Person person = new Person();
person.show();
输出的结果是:
当我们书写了显式构造器后,系统的无参数隐式构造器就无法被程序员调用了。
public class Person{
int id;
String name;
char gender;
public Person(int id){
// ...
}
}
// 其他类的main方法
Person person = new Person(); // ❌ 没有定义无参数构造器
如果我们自己定义的构造器并没有给所有属性赋值,那么这些属性的值是什么呢?
public class Person{
int id;
String name;
char gender;
public Person(int id){
this.id = id;
}
public void show(){
System.out.println("id:" + id);
System.out.println("name:" + name);
System.out.println("gender:" + gender);
}
}
// 其他类的main方法
Person person = new Person(2);
person.show();
我们可以看到的是,id
属性的值是我们填充的,其他属性的值仍然是系统默认初值。也就是说,未赋值的属性,即使我们的构造器也没有赋值,系统仍然会赋予合适的初值。
变量声明的位置决定了变量的作用域,变量作用域确定了可在程序中按变量名访问该变量的区域。
类中直接定义的变量成为成员变量。 对于成员变量也有叫作实例变量(instance variable
)。这里通常指非 static
进行修饰的变量。
public class Person{
private int id ;
private String name ;
}
需要注意的是, 成员变量最好使用私有修饰符, 进行权限的控制, 保证成员属性不往外暴漏属性值。 而是给一个行为或者动作去触发修改属性。
局部变量分为两种, 一种是方法内的局部变量, 另外一种是代码块中的变量。 不管哪一种,局部变量都只能在当前定义的区域中使用。
public class Person{
public void eat(){
String name = "GuanWei";
for(int i=1;i<11;i++){
System.out.println(i + ":" + name);
}
}
}
局部变量的作用域仅限于定义它的程序体。
成员变量的作用域在整个类内部都是可见的,如果作用域允许,其他类也可以使用。
Java
会给成员变量一个初始值。
Java
不会给局部变量赋予初始值。
在同一个方法中,不允许有同名局部变量;在不同的方法中,可以有同名局部变量。
两类变量同名时,局部变量具有更高的优先级。
可以使用 this
关键字来调用成员变量。