20-506暑期集训面向对象思想(一)
类和对象
关系
Java中的类和对象是是实现一切程序功能的基本工具,脱离了类和对象我们就无法做事情。
类的对象的关系大致可以描述成:
- 对象:对象是类的一个实例,有状态和行为。
- 类:类是一个模板,它描述一类对象的行为和状态。
比如汽车为类(class),而具体的每辆车为该汽车类的对象(object),对象包含了汽车的颜色、品牌、名称等。
每一个类都有自己的特征属性、功能,而每个类的对象又分别在这些属性功能上有着不同
拿一条狗来举例,它的状态有:年龄、品种、颜色,行为有:吃、睡和跑。
通过狗的例子我们就可以创建一个简单的类来理解下 Java 中类的定义:
class Dog {
String breed;
int size;
String colour;
int age;
void eat() {
}
void run() {
}
void sleep(){
}
}
构造方法
每个类都有构造方法。如果没有显式地为类定义构造方法,Java 编译器将会为该类提供一个默认的不带参数、不做任何事情的构造方法。
在创建一个对象的时候,至少要调用一个构造方法。构造方法的名称必须与类同名,一个类可以有多个构造方法。
如下列示例
- 不自行定义构造方法
class Dog {
String breed;
int size;
String colour;
int age;
/**
* 没有构造函数就相当于程序为我们添加了一个这样的构造函数
* public Dog(){
*
* }
*/
public static void main(String[] args) {
Dog dog=new Dog();
}
}
- 构造多个构造方法
class Dog {
String breed;
int size;
String colour;
int age;
Dog(String breed){
System.out.println("小狗的品种是 : " + breed );
}
Dog(String breed,int size){
System.out.println("小狗的品种是 : " + breed + "大小是" + size);
}
public static void main(String[] args) {
Dog dog1=new Dog("牧羊犬");
Dog dog2=new Dog("哈士奇",20);
//输出
//小狗的品种是 : 牧羊犬
//小狗的品种是 : 哈士奇大小是 : 20
}
}
另外提一点,构造函数不能有返回类型,如果有了返回类型它就变成了一个普通的方法
变量初始化
构造函数一般拿来做该对象参数的初始化赋值,我们把这种初始化叫做构造初始化,那么我们怎么在构造函数中得知“该对象”到底是哪一个对象呢。
- this关键字:当对象在执行某一个方法期间需要用到自己的一些属性时,可以用this关键字确定特定的属性,如在构造函数做变量初始化
public class Dog { String breed; int size; String colour; int age; Dog(String breed){ //breed=breed; × this.breed=breed; } Dog(String breed,int size){ this.breed=breed; this.size=size; } public static void main(String[] args) { Dog dog1=new Dog("牧羊犬"); Dog dog2=new Dog("哈士奇",20); } }
另外的我们有时候还会在写类的时候直接给属性赋值,我们把这种初始化叫做定义初始化,那如果定义初始化和构造初始化都存在的时候,先执行哪一个呢,例如;
public class Dog {
String breed;
int size=10;
String colour;
int age;
Dog(String breed,int size){
this.breed=breed;
this.size=size;
}
public static void main(String[] args) {
Dog dog2=new Dog("哈士奇",20);
System.out.println(dog2.size);
}
}
尝试后得出结论:在构造对象时,先执行定义初始化,在执行构造初始化
static修饰符
相信大家之前肯定都见过static修饰符,表示“静态”的意思,在C语言中表示全局变量,能够在整个C文件中使用,我们之前在讲函数的时候也讲到过如果要在main函数中调用方法,那么那个方法也得是static的,但是当我们定义类的时候我们在其中写的方法又都不带static修饰符,Java中的static到底是什么意思?我们可以通过下面这个例子来理解。
class Dog {
static String type="哺乳类";
String breed;
int size=10;
String colour;
int age;
Dog(){
}
Dog(String breed,int size){
this.breed=breed;
this.size=size;
}
public static void main(String[] args) {
Dog dog1=new Dog();
Dog dog2=new Dog();
System.out.println(dog1.type);
System.out.println(dog2.type);
dog1.type="爬行动物";
System.out.println(dog1.type);
System.out.println(dog2.type);
Dog.type="脊椎动物";
System.out.println(dog1.type);
System.out.println(dog2.type);
}
}
类是描述,对象是实体。在类里所描述的成员变量,是位于这个类的每一个对象中的。
而如果某个成员有static关键字做修饰,它就不再属于每一个对象,而是属于整个类的了,通过每个对象都可以访问到这些类变量,也可以通过类的名字来访问它们,类函数也是同理。但是由于类函数不属于任何对象,因此类函数也没有办法建立与调用它们的对象的关系,就不能访问任何非static的成员变量和成员函数,也不能使用this。
封装
上述我们写的代码其实都存在一个小问题
dog1.breed="哈士奇";
dog1.size=10;
dog1.age=5;
dog1.colour="黄色";
我们可以看到对象的所有数据我们都是可以直接接触到的,可以任意的操作,虽然语法上没有错误,但是这样是违反了面向对象的设计理念的,是不安全的。在面向对象的设计理念中,所有的对象都应该满足下面这样的模型:
我们可以把它想象成一个剖开的鸡蛋,外面的蛋白比作对象的操作(函数),里面的蛋黄比作对象的数据(属性变量),数据是封闭着的,你不能直接进行访问,如果需要对数据进行操作,应该由对象自己通过外层的“操作蛋白”对内部的数据进行更改——这就是封装
封装,就是把数据和对这些数据的操作放在一起,并且用这些操作把数据掩盖起来,使他人不能直接接触到数据,只将对数据相关的操作公开给使用者,这是面向对象的基本概念之一,也是最核心的概念。
那么从具体的代码实现的角度来看,Java做这件事情主要采用的手段就是为成员变量和方法设置相应的访问属性修饰符。
封闭的访问属性private
我们有一个非常直截了当的手段来保证在类的设计的时候做到封装:所有的成员变量必须是private的,这样就避免别人任意使用你的内部数据,所以我们应该把上面的类改成这样
public class Dog {
static String type="哺乳类";
private String breed;
private int size=10;
private String colour;
private int age;
Dog(){
}
Dog(String breed,int size){
this.breed=breed;
this.size=size;
}
public static void main(String[] args) {
}
}
这样对象的属性就只有自己可以访问了
但是还有另外一种情况——
假如我现在在这个Dog类里面写了一个成员方法makeFriend,参数也是一个Dog对象,那么情况会变成怎样呢?
public void makeFriend(Dog d){
System.out.println("我跟一只"+d.breed+"成为了朋友");
}
我们会发现并没有出错,是允许访问的,说明只要是在对象所属的类的范围里,私有变量都可以访问的。
总结:
用private修饰的变量或者方法只有在类内部可以被访问,这个限制是对类的而不是对对象的,所以换句话说,我们看有没有突破私有的边界,是从代码的角度来看的,而不是从运行的角度来看的,这一点是个很小的细节,但是值得注意。
开放的访问属性public
我们将对象的数据隐藏起来之后,相应的,我们就应该把对数据的相关操作公开化,供他人必要间接操作数据。
要想声明某个方法或某个变量是公开的,我们一般使用public访问修饰符。
最常见的,我们会为类的各个私有变量分别设定一个读函数get()和一个写函数set(),方便在其他地方对对象的成员变量做读取修改,并将他们设置为public类型
在idea中我们可以采用alt+insert快捷键的方式让idea帮我们生成这些getter和setter
public String getBreed() {
return breed;
}
public void setBreed(String breed) {
this.breed = breed;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
public String getColour() {
return colour;
}
public void setColour(String colour) {
this.colour = colour;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
需要注意的是,如果既不写private也不写public修饰符,那么它将被认为是friendly访问属性,指在同一个包下的其他类可以访问
抽象
Java中有一般有两个地方会提到“抽象”这个词:
一个是当我们讲到抽象类的时候,我们会用abstract来声明抽象类,这时候抽象与具体相对,他指的是这个类表达一种概念而不是一个具体的模型。
另一个是跟封装一起讲的时候,这时候抽象与细节相对,表示在一定程度上忽略细节而着眼大局,就好比我们要实现某个较大的功能时,不需要追究某个类的某个方法具体是怎么实现的,我们只用知道它是干什么用的,能拿来做搭建的砖头就好。举个很简单的例子,比如你找对象的时候你总不会去考虑人体的心肝肾脏肺这些内部构造吧🐶,因为你知道那些东西不是应该考虑的重点。