相关概念
类:类是整体抽象的集合,通过定义属性和方法(行为)来表示整体的公共特性。
对象:对象是类中一个具体的实例,每个对象都有其确定的属性。
例如学生是一个抽象的集合,因此学生就是一个类。而描述学生的属性有很多如学号、姓名、年龄、性别等等。同样与学生相关的方法(行为)也有很多如学习等。而学生张三其各个属性都是确定的,因此张三是一个学生对象。同理,李四也是一个学生对象,同一个类中的对象通过各属性的不同来区分,如张三和李四可以通过名字这个属性的不同来进行区分。
类与对象的定义和使用
类通过调用关键字class来定义,格式如下:
class 类名称 {
属性
方法
}
其中属性的定义与基本数据类型的定义类似,其可以是基本数据类型,也可以是引用类型。而方法的定义与正常方法定义一致。例如定义一个学生类:
class Student {
String ID; // 定义学号属性
String Name; // 定义名字属性
int Age; // 定义年龄属性
void Print () { // 定义方法
System.out.println(Name+"今年"+Age+"岁了");
}
}
如此便定义好了一个学生类。
类是一个抽象的集合,并不能直接使用。如果需要使用类,必须通过对象来调用,所以下面需要建立对象。
建立一个对象首先需要声明,与其他数据类型的声明类似,其格式如下:
类名称 对象名称;
例如声明一个学生对象:
public class Text {
public static void main(String[] args) {
Student ZhangSan; // 声明对象
}
}
如此便是声明了一个学生对象。此时对象ZhangSan只是声明了其是一个Student类中的一个对象,计算机并没有为其分配内存单元,也就是说ZhangSan并没有任何学号、姓名等属性,因此我们需要为其分配内存单元,该操作通过new关键字调用,格式如下:
对象名称 = new 类名称();
例如给学生ZhangSan分配内存:
public class Text {
public static void main(String[] args) {
Student ZhangSan; // 声明对象
ZhangSan = new Student(); // 分配内存
}
}
这是系统自动生成的构造方法,当调用这个方法时系统会给对象分配内存空间,然后将类里的各个属性初始化然后存到相应的内存单元,如果没有初始化,则系统会默认初始化,char类型默认为 ‘0’ ,boolean类型默认为false,其他基本类型默认为0,其他引用类型默认为null,其中String为引用类型。
前面所说的构造方法为系统自动生成的构造方法,当类中没有定义构造方法时系统会自动生成。如果类中定义了相应的构造方法则调用上述构造方法时会调用类中定义的构造方法。另外,构造方法在建立时其其他方法类似,但是其方法名必须与类名保持一致,而且没有返回值类型。例如:
class Student {
String ID; // 定义学号属性
String Name; // 定义名字属性
int Age; // 定义年龄属性
Student(String ID,String Name,int Age) { // 重新定义构造方法
this.ID = ID;
this.Name = Name;
this.Age = Age;
}
void Print () { // 定义方法
System.out.println(Name+"今年"+Age+"岁了");
}
}
public class Text {
public static void main(String[] args) {
Student ZhangSan;
ZhangSan = new Student("0001","张三",18);
}
}
这里面值得注意的一点是方法中的this,由于形参变量与属性变量名称一致,如果没有this说明系统会根据就近原则认为该变量是形参,而我们要将传进来的参数值赋给属性变量,因此需要在参数前面加this,this指向该对象的地址,通过this可以调用属性变量。
这时在建立一个Student对象的时候,调用的构造方法就是在Student类中定义的构造方法,因此构造时就会根据该方法传进来的参数给对象属性初始化。但有时候在建立一个新对象的时候有些属性不能立即确定,即建立时不需全部初始化,这时候可以在类中重载构造方法,然后根据需要进行调用。
另外,也可以在声明时直接给对象分配内存空间,其格式如下:
类名 对象名 = new 构造方法名(相应参数);
例如建立学生对象张三并分配内存空间:
class Student {
String ID; // 定义学号属性
String Name; // 定义名字属性
int Age; // 定义年龄属性
void Print () { // 定义方法
System.out.println(Name+"今年"+Age+"岁了");
}
}
public class Text {
public static void main(String[] args) {
Student ZhangSan = new Student(); // 声明对象并分配内存
}
}
这里没有重新定义构造方法,因此调用的是系统默认生成的构造方法。如此便算完全定义好了一个对象。通过对象可以去调用该对象的属性和方法。而各属性和方法的调用通过关键字 . 来进行操作,其调用属性和方法的基本格式如下:
对象名.属性变量名 // 调用属性
对象名.方法名 // 调用方法
同样以学生ZhangSan为例,由于我们各个属性没有初始化,因此其各属性值分别对应前面提到的默认值。而我们可以通过调用对象的属性为其初始化,然后调用对象的方法进行输出,其代码如下:
class Student {
String ID; // 定义学号属性
String Name; // 定义名字属性
int Age; // 定义年龄属性
void Print () { // 定义方法
System.out.println(Name+"今年"+Age+"岁了");
}
}
public class Text {
public static void main(String[] args) {
Student ZhangSan = new Student();
ZhangSan.ID = "0001";
ZhangSan.Name = "张三";
ZhangSan.Age = 18;
ZhangSan.Print();
}
}
这样便通过调用属性的方法为对象ZhangSan进行了初始化。然后通过调用方法打印出了“张三今年18岁了”。另外,当需要对对象的属性进行赋值时,值的数据类型必须跟属性的数据类型一致。
数据类型的调用还涉及到访问权限的问题,Java中有四种访问权限,分别用public、private、protected和缺省(默认)修饰。其基本格式如下:
权限修饰字 数据类型 数据名; // 数据权限设置
权限修饰字 class 类名{} // 类权限设置
权限修饰字 返回值类型 方法名(形式参数){} // 方法权限设置
其中类的权限只有public和缺省(默认)。
先说缺省(默认)的权限,例如我们Student类中的属性String ID等等,前面都没有权限修饰,因此都是默认的访问权限。而对与默认的访问权限其特点是只能在类内部和本包内进行访问。也就是说如果我在另一个包下创建了一个学生对象(这里是不可能的,因为类定义时类的访问权限也是默认,如果要完成该操作需在class前用public修饰,这里只是为了举例说明。),那么该对象将不能直接调用这些默认权限的属性。同理,其他访问权限类似,private权限最小,只能在该类内部进行访问,默认权限其次,protected权限稍大,能在该类内部,本包里和该类的子类(子类的相关内容在后面类的继承里将会介绍)里进行访问,而public权限最大,不仅能在类内部、本包内和子类里进行访问,外部包里也能进行访问。
如果我们的成员属性定义的访问权限较小,这可以有效地保护内容被外界修改,但权限较小时如果我们需要对其进行赋值修改或者读取该属性内容的时候我们该如何解决呢?
就如前面所提到的,不仅数据有访问权限,类和方法同样有访问权限,假如我们定义了private权限的属性,那么该属性只能在该类内部进行访问,这时我们可以在该类内部定义一个方法去修改该属性或者获取该属性内容,而该方法的权限可以根据需要进行设置。这样我们通过在外部调用该方法就就能够实现对private属性进行访问。
例如我们设置Student类中的ID为private权限,则我们可以通过如下方式进行访问:
class Student {
private String ID;
String Name;
int Age;
void setID(String ID){
this.ID = ID;
}
String getID(){
return ID;
}
void Print () { // 定义方法
System.out.println(Name+"今年"+Age+"岁了");
}
}
public class Text {
public static void main(String[] args) {
Student ZhangSan = new Student();
ZhangSan.setID("0001"); // 调用方法给private属性赋值
ZhangSan.Name = "张三";
ZhangSan.Age = 18;
System.out.println(ZhangSan.Name+"的ID为:"+ZhangSan.getID()); // 调用方法获取private属性内容
}
}
这样我们就通过调用类内部的方法对private属性进行了访问。
类的继承
我们生活中有着各种各样的类,这种类下面又可以细分为其他许许多多的子类。例如我们这有一个学生类,而该类下面又可以细分为小学生、中学生、大学生等等。这些类与类之间的关系在我们日常生活中我们称之为包含关系,而在Java中与之对应的关系我们称之为继承。例如学生包含中学生,在Java中我们称中学生继承了学生,对于包含的类我们称之为父类,被包含的类称之为子类,子类可以调用父类的所有属性和方法。类的继承用关键字extends来操作,其基本格式如下:
class 子类名 extends 父类名 {}
例如我们在新建一个MiddleStudent类继承Student类:
class Student {
String ID;
String Name;
int Age;
void Print () {
System.out.println(Name+"今年"+Age+"岁了");
}
}
class MiddleStudent extends Student { // 建立子类继承父类
}
public class Text {
public static void main(String[] args) {
MiddleStudent ZhangSan = new MiddleStudent();
ZhangSan.ID = "0001";
ZhangSan.Name = "张三";
ZhangSan.Age = 18;
ZhangSan.Print();
}
}
从中我们可以看出MiddleStudent类中我们并没有定义任何属性与方法,我们建立了一个MiddleStudent类对象ZhangSan,通过对象ZhangSan我们却可以调用属性和方法,而这些属性和方法就是从父类Student中继承过来的。
类的继承可以是多层次的,例如学生包含中学生,而中学生又包含高三学生,Java类的继承也是类似。
一个父类可能对应多个子类,而对于每个子类,父类的方法不一定都适用于每个子类,每个子类对应的方法可能或多或少有点区别。要解决这个问题我们需要在子类中重写该方法,这样子类对象在调用该方法时会优先调用子类中重写的方法。例如:
class Student {
String ID;
String Name;
int Age;
void Print () {
System.out.println(Name+"今年"+Age+"岁了");
}
}
class MiddleStudent extends Student {
String Grade; // 定义年级属性
void Print() { // 重写Print方法
System.out.println(Name+"今年"+Grade+"了");
}
}
public class Text {
public static void main(String[] args) {
MiddleStudent ZhangSan = new MiddleStudent();
ZhangSan.ID = "0001";
ZhangSan.Name = "张三";
ZhangSan.Age = 18;
ZhangSan.Grade = "高三";
ZhangSan.Print();
}
}
这样我们调用Print方法时会优先调用子类中重写的方法,即输出“张三今年高三了”而不是“张三今年18岁了”。
但是我们有些时候并不需要完全舍弃父类中的方法,而是在父类原有方法的基础上继续增加新的内容。要解决这个问题我们子需要在子类重写的方法中重新调用父类的方法即可。子类调用父类的方法可以用关键字super来实现。与this类似,this指向本对象,而super指向父类。例如:
class Student {
String ID;
String Name;
int Age;
void Print () {
System.out.println(Name+"今年"+Age+"岁了");
}
}
class MiddleStudent extends Student {
String Grade; // 定义年级属性
void Print() { // 重写Print方法
super.Print(); // 调用父类中的Print方法
System.out.println(Name+"今年"+Grade+"了");
}
}
public class Text {
public static void main(String[] args) {
MiddleStudent ZhangSan = new MiddleStudent();
ZhangSan.ID = "0001";
ZhangSan.Name = "张三";
ZhangSan.Age = 18;
ZhangSan.Grade = "高三";
ZhangSan.Print();
}
}
这样通过调用父类的方法,当我们调用Print方法时的输出就是“张三今年18岁了”,然后换行后继续输出“张三今年高三了”。
父类对象和子类对象可以相互转换,子类对象可以自动转型成父类对象,但是父类对象不能自动转型成为子类对象,但是可以通过强制转型。强制转型的基本格式如下:
(要强制转型成为的子类名)父类对象名;
例如我们建立了一个Student对象可以强制转型成为MiddleStudent对象:
Student ZhangSan = new Student();
MiddleStudent Zhang = (MiddleStudent)ZhangSan;
这样我们就将Student对象强制转成MiddleStudent对象并赋给MiddleStudent对象Zhang。
根据上面的继承关系,自动转型可以如下体现:
MiddleStudent ZhangSan = new MiddleStudent();
Student Zhang = ZhangSan;
这样MiddleStudent对象ZhangSan会自动转型成为Student对象赋给Zhang。
自动转型通常用于函数调用时,如果该函数的形式参数是父类对象,那么调用该函数时实际参数可以是子类对象,因为子类对象会自动转型成为父类对象。
值得注意的是,如果子类中重写的父类的方法,当该子类对象自动转型成为父类对象时,通过该对象调用该方法会调用子类的方法,子类中不是重写的方法而是其特有的方法不能调用。
另外Java中所有的类都默认共同继承同一个父类Object类。因此所有类的对象都可以调用该类的方法。