对象,即对事物的抽象。面向对象,就是把现实世界的事物抽象成一个对象,这些对象具有自己的属性和行为,而我们利用这些属性和行为去解决问题。
举个简单的例子,我们使用洗衣机洗衣服时通常分为四步,首先,把衣服放进洗衣机;然后,设置洗衣机的参数如时长等;再然后,启动洗衣机;最后,把衣服取出来。所谓面向过程编程,就是为每一步编写一个函数,然后依次执行这些函数,从而解决问题。而在面向对象编程中,我们会将洗衣机和衣服分别抽象成一个对象,衣服有清洁度这个属性,洗衣机有时长、水温等属性以及设置参数、洗衣服等行为,这些属性由变量表示,而行为由函数表示,我们可以使用洗衣机的这些行为函数来解决问题。
接下来,我来阐述我对Java面向对象编程的理解
类与对象
什么是类
在Java中,类与对象可以看作是“蓝图”和“实物”的关系,类描述的是一类事物所共有的一些属性和行为,而对象则是这类事物的某个具体实例。类可以使用关键字class来声明。
举个简单的例子,通常我们说的猫指的是一种动物,一种概念,而不是指某个具体的个体。所有的猫都有颜色,品种,体重等属性,也都有说话,走路,吃饭等行为。因此,我们可以定义一个类来表示猫
class Cat{
public String variety; //品种
public String color; //颜色
public double weight; //体重
public void speak(){
System.out.println("喵喵喵~~~");
} //说话
public void walk(){
System.out.println("我在散步......");
} //走路
public void eat(String food){
System.out.print("我在吃"+food);
} //吃饭
}
注意,在Java中,一个类可同时定义许多同名的方法,这些方法的形式参数个数、类型或顺序各不相同,传回的值也可以不相同,这种特性称为重载(Overloading)。
构造方法和对象的创建
有了“蓝图”,就可以创建“实物”了。我们知道了如何表示猫,那么现在我有一只橘色的波斯猫叫小白,我该如何表示它呢?这里就需要一种特殊的方法叫构造方法。
每个类都必须拥有一个构造方法,构造方法的名字必须和类的名字相同。构造方法的作用是在创建对象时使用,主要是用来初始化各个成员变量,以便给类所创建的对象一个合理的初始状态。
需要注意的有几点:
- 如果类中没有编写构造方法,系统会默认该类只有一个构造方法,该默认的构造方法是无参数的,且方法体中没有语句。如果类里定义了一个或多个构造方法,那么Java不提供默认的构造方法 。
- 构造方法没有类型
现在,我们给Cat类里编写一个构造函数
class Cat{
public String variety; //品种
public String color; //颜色
public double weight; //体重
//构造函数
Cat(String pinzhong,String yanse,double tizhong){
variety = pinzhong;
color = yanse;
weight = tizhong;
}
public void speak(){
System.out.println("喵喵喵~~~");
} //说话
public void walk(){
System.out.println("我在散步......");
} //走路
public void eat(String food){
System.out.println("我在吃"+food);
} //吃饭
}
现在我就可以创建一个对象来表示我的小白了,创建对象需要使用关键字new,在new后面使用我们刚刚编写的构造函数
Cat xiaobai = new Cat("PersianCat","orange",50);
小白的类型是Cat,那么Cat类拥有的行为小白当然也有,因此我们可以用 . 来调用类里的函数
xiaobai.speak();
xiaobai.walk();
xiaobai.eat("小鱼干");
构造函数也可以利用重载特性,创建对象时根据传入的参数不同,调用不同的构造函数。如下,我创建了一个无参构造函数,设置不传参时的默认值。
class Cat{
public String variety; //品种
public String color; //颜色
public double weight; //体重
//构造函数
Cat(String pinzhong,String yanse,double tizhong){
variety = pinzhong;
color = yanse;
weight = tizhong;
}
Cat(){
variety = "默认";
color = "默认";
weigth = 0;
}
public void speak(){
System.out.println("喵喵喵~~~");
} //说话
public void walk(){
System.out.println("我在散步......");
} //走路
public void eat(String food){
System.out.println("我在吃"+food);
} //吃饭
}
类变量和类方法
在上述例子中,我们在类里创建的变量和方法都是实例变量和实例方法。这些都是对象所具有的变量和方法。什么意思呢?我们可以看到,虽然所有的猫都有品种、颜色和体重,但是每只猫的品种、颜色和体重都不相同,即每个属性的具体信息是具体的个体才拥有的,比如我的小白拥有品种为波斯猫,颜色为橘色,体重为50斤。这些都不是猫这一概念所拥有的,我们不能说猫的体重为50公斤。而类变量和类方法则不同,它们是类所拥有的并且在定义类时一般就会给出具体的内容。比如猫都有4条腿,我们可以定义一个类变量legnum,值为4。定义类变量和类方法需要在前面加上static关键字。
class Cat{
static public int legnum = 4; //腿的数目
public String variety; //品种
public String color; //颜色
public double weight; //体重
//构造函数
Cat(String pinzhong,String yanse,double tizhong){
variety = pinzhong;
color = yanse;
weight = tizhong;
}
public void speak(){
System.out.println("喵喵喵~~~");
} //说话
public void walk(){
System.out.println("我在散步......");
} //走路
public void eat(String food){
System.out.println("我在吃"+food);
} //吃饭
}
所有Cat类创建的对象共享同一个类变量和类方法,当某一个对象修改类变量时,另一个对象调用类变量时就是修改后的值
另外,除了用对象调用类变量和类方法,也可以直接用类名调用。因为类对象和类方法在类创建时就已经存在具体的内容
Cat.legnum
需要注意的是,实例方法可以操作类变量,调用实例方法和类方法(构造方法除外),但是类方法不能操作实例变量,也不能调用实例方法,这是显而易见的,因为在创建对象前,实例变量和实例方法并不存在具体内容,只是“蓝图”而已。
THIS关键字
this是Java的一个关键字,表示某个对象。this可以出现在实例方法和构造方法中,但不可以出现在类方法中
- this关键字出现在类的构造方法中时,代表使用该构造方法所创建的对象
- this关键字出现实例方法中时,this就代表正在调用该方法的当前对象
例如,可以在上面例子的构造函数中引入this关键字来区分变量
Cat(String variety,String color,double weight){
this.variety = variety;
this.color = color;
this.weight = weight;
}
可以看到this.variety是对象的成员变量,而variety是在调用函数时传进来的参数。
面向对象三大特性
面向对象三大特性分别时封装、继承、多态,下面我来依次进行介绍
封装
在面向对象编程中,封装是指将数据(属性)和操作数据的方法(行为)捆绑在一起的过程。这样做的目的是隐藏对象的内部状态和实现细节,只通过一个清晰的接口与外界交互。
现实生活中实现封装的例子有很多,比如汽车,生产汽车的厂家将汽车的功能部件如引擎等封装起来,只留下一些接口像方向盘、油门、刹车等供我们这些用户使用。这样做不仅能够简化汽车的使用方法,还能够防止一些不懂汽车的人胡乱修改内部结构而导致汽车报废。
在JAVA中,我们通过改变成员变量和方法的访问权限来实现封装。对象只能使用其具有访问权限的成员变量和方法。一般我们可以通过三个关键字来改变访问权限,分别是private、protected、public,在定义变量和方法时使用。
- private:用关键字private修饰的成员变量和方法称为私有变量和私有方法。对于私有成员变量或方法,只有在本类中创建该类的对象时,这个对象才能访问。也就是说,想要访问私有变量和私有方法,只能在类里的成员方法中使用,或者像俄罗斯套娃那样,在类里创建本类的一个对象,这个对象也可以访问私有变量和方法。
- protected:用protected修饰的成员变量和方法被称为受保护的成员变量和受保护的方法。该成员变量和方法只能被与该类在同一个包内的类里所创建的对象使用。
- public:用public修饰的成员变量和方法被称为共有变量和共有方法 。在任何类里创建的对象都可以使用该对象的共有变量和共有方法
- 不用private,public,protected修饰的成员变量和方法被称为友好变量和友好方法。友好变量和友好方法不能被在子孙类和其他包的类里创建的对象访问。关于子孙类,我们会在继承中说到。
我用一张表来总结上述内容
作用域 | 当前类 | 同一包 | 子孙类 | 其他包 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
friendly | √ | √ | × | × |
private | √ | × | × | × |
还是猫的例子,猫的品种和颜色是不应该被更改的,因此我们应当设置它们为私有变量
class Cat{
static public int legnum = 4; //腿的数目
private String variety; //品种
private String color; //颜色
public double weight; //体重
//构造函数
Cat(String variety,String color,double weight){
this.variety = variety;
this.color = color;
this.weight = weight;
}
public void speak(){
System.out.println("喵喵喵~~~");
} //说话
public void walk(){
System.out.println("我在散步......");
} //走路
public void eat(String food){
System.out.println("我在吃"+food);
} //吃饭
}
除了成员变量和方法,类也分public类和友好类。类声明时,如果在关键字class前面加上public关键字,就称这样的类是一个public类。可以在任何另外一个类中,使用public类创建对象。如果一个类不加public修饰,这样的类被称作友好类。在另外一个类中使用友好类创建对象时,要保证它们是在同一包中。
继承
继承,指获得前人留下来的东西。在JAVA中,一个类可以继承另外一个类,被继承的类称为父类,而继承父类的类称为子类。子类通过继承可以直接获得父类中定义好的成员变量和方法而不用重新定义。子类只能继承一个父类。
就像父亲从零开始创业打拼,创办了许多公司,留下了许多资产给儿子。这样儿子创业时就有大笔初始资金,不需要从零开始。
在JAVA中,继承通过关键字extends进行,例如,我先定义一个动物类,动物都会动、会叫、会吃饭,动物都有体重、身高等属性。
class Animal{
public double height; //身高
public double weight; //体重
//构造函数
Animal(double height,double weight){
this.height = height;
this.weight = weight;
}
Animal(){
this.height = 10;
this.weight = 50;
}
public void speak(){
System.out.println("我在叫");
} //叫
public void move(){
System.out.println("我在动");
} //动
public void eat(){
System.out.println("我在吃");
} //吃
}
然后我可以创建猫的类继承动物类,猫除了上述属性和行为外,还有毛的长度的属性,和撒娇等行为
class Cat extends Animal{
public double hairLength; //毛的长度
//构造函数
Cat(double hairLength){
this.hairLength = hairLength;
}
public void sajiao(){
System.out.println("喵~喵喵喵~~喵喵~~~");
} //撒娇
}
现在,虽然Cat类里没有创建体重、身高属性和叫、动、吃行为,但是因为继承了Animal类,所以可以直接使用
Cat xiaobai = new Cat(5);
xiaobao.weight;
xiaobao.speak();
需要注意的是,子类无法继承父类的私有成员变量和方法。除此之外,当子类和父类不在同一个包中时,子类也无法继承父类的友好成员变量和方法。但是子类在创建对象时也会为那些未继承的变量和方法分配内存。因此,子类可以通过继承的方法来操作未继承的变量。例如,父类中有一个private变量和public方法,在这个public方法中对private变量进行了操作,而子类继承了这个public方法,也可以调用这个方法来操作那个无法继承的private变量。
可以看到,我上面在创建Cat类的对象时,并没有为Animal类的成员变量进行初始化操作。这并不需要担心,因为在JAVA中,子类的构造方法会先调用父类的构造方法,完成父类部分的创建,再创建自己的部分。如果没有指明调用父类哪个构造方法,就调用没有参数的那个。想要指明父类的构造方法,可以用super关键字,如下
Cat(double height,double weight,double hairLength){
super(height,weigth); //调用父类的构造函数
this.hairLength = hairLength;
}
有人可能会有疑问,如果在子类中定义的变量与从父类继承过来的变量同名会怎么样,答案是这时从父类继承的变量会被隐藏(override),成员方法也是一样,从父类继承的方法会被重写。
例如,我在创建Cat类时不想要父类的speak方法,因为它输出”我在叫“,猫应该是喵喵叫的,这时我就可以进行方法重写
class Cat extends Animal{
public double hairLength; //毛的长度
//构造函数
Cat(double hairLength){
this.hairLength = hairLength;
}
public void sajiao(){
System.out.println("喵~喵喵喵~~喵喵~~~");
} //撒娇
public void speak(){
System.out.println("喵喵喵");
} //叫
}
需要注意的是,重写方法不能降低方法的访问权限,但可以提高
之所以称为隐藏是因为父类的成员变量和方法并没有消失,在子类中可以利用super关键字调用被隐藏变量和被重写的方法
super.speak();
值得一提的是,除了重写,子类也可以重载从父类继承的成员方法
多态
多态,指对于同一种行为,不同的子类对象具有不同的表现形式。可以看出,多态是基于继承特性的。乍一看,前面这句定义不就指的是我们上面在继承中所说的方法重写吗?没错,但不仅仅如此,除了方法重写,实现多态还需要一个关键概念:上转型对象。
什么是上转型对象呢?我们可以看如下代码
Animal xiaobai = new Cat()
是不是感觉很奇怪,我们创建了一个Animal类的引用,却让这个引用指向了Cat类创建的对象,当然,这里的Cat类必须是Animal类的子类。这就是上转型对象。
这个例子的上转型对象xiaobai无法使用Cat类新增的成员变量和方法,只能使用从Animal类继承的成员变量和方法,那么这和直接创建一个Animal类的对象有什么区别呢?关键在于上转型对象可以使用子类重写的方法。如下
public class main{
public static void main(String[] args){
say(new Cat());
say(new Dog());
}
public static void say(Animal a){
a.speak();
}
}
//动物类
class Animal{
public void speak(){
System.out.println("我在叫");
}
}
//猫类
class Cat extends Animal{
public void speak(){
System.out.println("喵喵喵");
} //方法重写
}
//狗类
class Dog extends Animal{
public void speak(){
System.out.println("汪汪汪");
} //方法重写
}
运行结果
喵喵喵
汪汪汪
可以看到,即使say()函数的内容不改变,通过传入不同的子类对象,上转型对象a调用不同子类对象重写的不同方法,使最后输出的结果不同,这就是多态。
抽象和接口
抽象
从上面的例子中,我们会发现一些问题,那就是Animal类里的speak、move、eat函数里的内容并没有什么存在的必要,因为在子类中基本都会进行重写(我只重写了speak方法是因为这对说明来说足够了)。并且在实际应用中我们基本不会使用Animal类直接创建对象。
这时我们就可以将Animal类声明为抽象类了,并将speak、move、eat声明为抽象方法,在JAVA中,使用关键字abstract
abstract class Animal{
public double height; //身高
public double weight; //体重
//构造函数
Animal(double height,double weight){
this.height = height;
this.weight = weight;
}
Animal(){
this.height = 10;
this.weight = 50;
}
public abstract void speak(); //叫
public abstract void move(); //动
public abstract void eat(); //吃
}
抽象方法并没有函数体,因此无法使用抽象类创建实例
可以看出,抽象类是制定了一种行为准则,它只告诉了”做什么“,而”怎么做“是由继承抽象类的子类来完成的。如果非抽象子类继承抽象父类,那么子类必须重写父类的所有抽象方法。
需要注意的是,抽象类中可以同时存在抽象方法和非抽象方法,但抽象方法只能存在于抽象类中
抽象类声明的对象也可以成为其子类对象的上转型对象
接口
接口和抽象类的存在相似,但它们都有各自的用途和优势。抽象类通常用于表示具有共同属性和方法的一组类,而接口通常用于定义类必须遵守的契约或行为。
接口使用interface关键字声明,类可以通过implements关键字实现接口,一个类可以实现多个接口,接口中不存在构造函数,下面是一个接口的例子
interface Animal{
public static final double height; //身高
public static final double weight; //体重
public abstract void speak(); //叫
public abstract void move(); //动
public abstract void eat(); //吃
}
这里只是简单介绍了下抽象和接口的概念,想要深入了解的话可以查看面向抽象编程和面向接口编程
结语
本文通俗地阐述了我对JAVA面向对象编程的具体理解,若有不足的地方希望多多指正,希望看过的人也能明白什么是面向对象编程,并能学会用面向对象的思路解决问题。