Java语法-类和对象(上)
1. 面向对象的初步认识
1.1 什么是面向对象
概念: Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。
1.2 面向对象VS面向过程
如:洗衣服
面向过程: 注重的是洗衣服的过程,少了一个环节也不行.而且不同衣服洗的方式,时间长度,拧干方式都不同,处理起来就比较麻烦。如果将来要洗鞋子,那就是另一种放方式。按照该种方式来写代码,将来扩展或者维护起来会比较麻烦。
面向对象: java中一切皆对象
1. 找对象
2. 创建对象
3. 使用对象
总共四个对象:人,衣服,洗衣机,洗衣粉
整个过程: 把衣服倒进洗衣机,放入洗衣粉,启动洗衣机
总:可以看出该过程是人,衣服,洗衣机,洗衣粉四个对象之间交互完成的,不需要关注具体某个过程是什么
2. 类的定义和类的实例化
面相对象程序设计关注的是对象,而对象是现实生活中的实体,比如:洗衣机。但是洗衣机计算机并不认识,需要开发人员告诉给计算机什么是洗衣机。
上图就是对洗衣机简单的描述,该过程称为对洗衣机对象(实体)进行抽象(对一个复杂事物的重新认知),洗衣机有哪些属性如:品牌,型号,功率...有哪些功能(方法): 干洗,快洗...
2.1 认识类
概念: 类就是一个用来描述一个事物的东西,描述该事物有哪些属性,有哪些功能.
2.2 定义类
java中定义类需要使用class关键字:
class 类名 { 属性;
方法 }
类中包含的内容称为类的成员。属性主要是用来描述类的,称之为类的成员属性或者类成员变量。方法主要说明类具有哪些功能,称为类的成员方法。
2.3 实例化类
定义了一个类,就相当于在计算机中定义了一种新的类型(和int double类似,不过我更倾向于引用类型String),用类类型创建对象的过程,称为类的实例化.
我们采用new关键字配合类名字来实例化对象
首字母大写类名 自定义名字 = new 首字母大写类名();
好了,我们来定义一个狗类:
//TODO 每个类会产生一个字节码文件
class PetDog {
public String name;//名字
public String color;//颜色
public static int ID;
// 狗的属性
public void barks() {
System.out.println(name + ": 旺旺旺~~~");
}
//狗的行为
public void wag() {
System.out.println(name + ": 摇尾巴~~~");
}
public static void main(String[] args) {
//实例化 实例化出来的东西就是一个真正的实体
//用类类型创建对象的过程叫类的实例化
//TODO 定义了一个类,就相当于在计算机中定义了一种类型
PetDog dog = new PetDog();
PetDog dog2 = new PetDog();
PetDog dog3 = new PetDog();
System.out.println(dog);//存的是这个对象的地址
dog.name = "小白";
System.out.println(dog.name);
dog.barks();
dog.wag();
dog2.name = "小黑";
dog2.barks();
dog2.wag();
System.out.println(PetDog.ID);//静态的要用类名来调用
}
}
注意事项:
- 类名注意采用大驼峰定义
- 成员前写法统一为public,后面会详细解释
- 此处写的方法不带 static 关键字. 后面会详细解释
- 使用.来访问对象中的属性和方法
- 同一个类可以创建多个实例
2.4 类和对象的说明
1. 类只是一个模型一样的东西,用来对一个实体进行描述,限定了类有哪些成员.
2. 类是一种自定义的类型,可以用来定义变量.
3. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
4. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间
总之这么理解: 类其实就是一种事物的模板,对象就是具体根据整个模板构造出来的东西.
3 .this引用
this表示当前对象的引用
3.1 为什么要有this?
我们来看一个代码
public void setDay(int year, int month, int day){
year = year;
month = month;
day = day;
}
如上述代码可知,我们不晓得这个方法里面的形参和成员变量谁是是谁.谁给谁赋值,并且后续我们创建多个对象之后,如果都使用setDay这个成员方法,就更加分不清谁是形参谁是当前对象的成员变量了.这时候this的作用就出来了.
3.2 什么是this引用
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
像上述代码,我们就可以这么写:
package Class_Object;
public class Class_Object_This {
public int year;
public int month;
public int day;
public void setDate(int year,int month,int day) {//方法只有一个,你怎么知道在给谁赋值?
this.year = year;
this.month = month;
this.day = day;
//year = year 这个表示局部变量对自己赋值
}
public static void main(String[] args) {
Class_Object_This date = new Class_Object_This();
date.setDate(1999,1,1);
System.out.println(date.year + " " +date.month + " " + date.day);
Class_Object_This date1 = new Class_Object_This();
date1.setDate(1990,2,1);
System.out.println(date1.year + " " +date1.month + " " + date1.day);
}
}
在setDate这个方法里面我们就能够分清楚形参和成员变量了,因为this是当前对象的引用,每个创建出来的对象都有一块自己的地址空间,因此this也是唯一的,也就能够区分出来了,this.year就是创建出来的对象的成员变量.
3.3 this引用的特性
1. this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
2. this只能在"成员方法"中使用
3. 在"成员方法"中,this只能引用当前对象,不能再引用其他对象
4. this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收
4. 对象的构造以及初始化
4.1 构造方法
这个方法很特殊,就是在创建对象的时候会把对象里面的属性一起赋值,不需要再这么赋值 "对象名.属性=赋值",或者调用自己的方法来进行赋值,而是在创建对象的时候就会自动调用该方法,把赋值的东西通过参数传过去.
好了介绍一下构造方法的写法:名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次.即: public 类的名字(){}
public class Date {
public int year;
public int month;
public int day;
// 构造方法:
// 名字与类名相同,没有返回值类型,设置为void也不行
// 一般情况下使用public修饰
// 在创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次
public Date(int year, int month, int day){
this.year = year;
this.month = month;
this.day = day;
System.out.println("Date(int,int,int)方法被调用了");
}
public void printDate(){
System.out.println(year + "-" + month + "-" + day);
}
public static void main(String[] args) {
// 此处创建了一个Date类型的对象,并没有显式调用构造方法
Date d = new Date(2021,6,9); // 输出Date(int,int,int)方法被调用了
d.printDate(); // 2021-6-9
}
}
注意: 构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间
4.2 构造方法的特性:
1. 名字必须与类名相同
2. 没有返回值类型,设置为void也不行
3. 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
4. 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
构造方法不止一个,可以有多个构造方法构成重载(名字相同,参数列表不同)
public class Date {
public int year;
public int month;
public int day;
// 无参构造方法
public Date(){
this.year = 1900;
this.month = 1;
this.day = 1;
}
// 带有三个参数的构造方法
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public void printDate(){
System.out.println(year + "-" + month + "-" + day);
}
public static void main(String[] args) {
Date d = new Date();
d.printDate();
}
}
注意:如果你自己没写构造方法,系统会给你默认生成一份不带参数的构造方法.一旦你自己写了,就不生成.(救急不救穷)
并且构造方法里面也可以调用其他的构造方法(使用this)
public class Date {
public int year;
public int month;
public int day;
// 无参构造方法--内部给各个成员赋值初始值,该部分功能与三个参数的构造方法重复
// 此处可以在无参构造方法中通过this调用带有三个参数的构造方法
// 但是this(1900,1,1);必须是构造方法中第一条语句
public Date(){
//System.out.println(year); 注释取消掉,编译会失败
this(1900, 1, 1);
//this.year = 1900;
//this.month = 1;
//this.day = 1;
} /
/ 带有三个参数的构造方法
public Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
}
其中this()必须是构造方法的第一条语句(但是不能成环,也就是俩个构造方法不能相互调用)
5. 封装和访问权限
5.1 封装的概念
面向对象的三大特性: 封装,继承,多态.继承和多态我们后期再讲,先说封装.
封装: 将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互.差不多就是套壳屏蔽细节.比如:对于电脑这样一个复杂的设备,提供给用户的就只是:开关机、通过键盘输入,显示器,USB插孔等,让用户来和计算机进行交互,完成日常事务。但实际上:电脑真正工作的却是CPU、显卡、内存等一些硬件元件
对于用户而言,我们只需要使用鼠标键盘,即可,不需要关注具体电脑里面的部件和构成
5.2 访问修饰限定符:
在具体了解封装之前我们需要看一下访问修饰限定符.ava中主要通过类和访问权限来实现封装:类可以将数据以及封装数据的方法结合在一起,更符合人类对事物的认知,而访问权限用来控制方法或者字段能否直接在类外使用。Java中提供了四种访问限定符:
像之前我们定义类或者方法前面就有这个玩意,public就是一个访问修饰限定符,它规定了这个类或者方法的访问范围是多少.是否能够在当前包内访问,是否支持跨包访问....
public: 表示无论你在哪,是谁都可以访问到
default: 表示在创建自己的这个包内可以访问到(当你什么都不写的时候就是dedault)
private: 表示这个只有自己晓得,其他人都不晓得
protected: 后续讲了继承再说
public class Computer {
private String cpu; // cpu
private String memory; // 内存
public String screen; // 屏幕
String brand; // 品牌---->default属性
public Computer(String brand, String cpu, String memory, String screen) {
this.brand = brand;
this.cpu = cpu;
this.memory = memory;
this.screen = screen;
}
public void Boot(){
System.out.println("开机~~~");
}
public void PowerOff(){
System.out.println("关机~~~");
}
public void SurfInternet(){
System.out.println("上网~~~");
}
}
public class TestComputer {
public static void main(String[] args) {
Computer p = new Computer("HW", "i7", "8G", "13*14");
System.out.println(p.brand); // default属性:只能被本包中类访问
System.out.println(p.screen); // public属性: 可以任何其他类访问
// System.out.println(p.cpu); // private属性:只能在Computer类中访问,不能被其他类访问
}
}
被private修饰的属性,然后你试图在其他类通过 "对象.这个属性"来访问它,但是访问不到,这个过程体现的就是封装.
那有没有方法来访问被private修饰的属性呢,是有的,我们可以通过get和set方法来对被private修饰的属性进行设置和访问.
package Class_Object;
public class People {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public People(String name, int age) {
this.name = name;
this.age = age;
}
}
class Test{
public static void main(String[] args) {
People people = new People("小白",18);
// people.name = "小黑";name被private修饰,我们无法在不同类里面访问它
people.setName("小黑");//我们可以通过set方法来设置这个private修饰的属性
System.out.println(people.getName());//再调用getName这个方法来获得这个属性的值
}
}
6. static成员
被static修饰的成员方法或者成员变量是属于类的,是类方法,类变量,是独一份的.像普通成员方法和变量,我们创建一个新的对象就会开辟新的空间,每个对象就都会有一份成员方法和成员变量.而被static修饰的类方法和类变量是属于类的,只有一份.
6.1 学生类例子:
public static void main(String[] args) {
Student s1 = new Student("Li leilei", "男", 18, 3.8);
Student s2 = new Student("Han MeiMei", "女", 19, 4.0);
Student s3 = new Student("Jim", "男", 18, 2.6);
}
}
假设三个同学是同一个班的,那么他们上课肯定是在同一个教室,那既然在同一个教室,那能否给类中再加一个成员变量,来保存同学上课时的教室呢?答案是不行的。
之前在Student类中定义的成员变量,每个对象中都会包含一份(称之为实例变量),因为需要使用这些信息来描述具体的学生。而现在要表示学生上课的教室,这个教室的属性并不需要每个学生对象中都存储一份,而是需要让所有的学生来共享。在Java中,被static修饰的成员,称之为静态成员,也可以称为类成员,其不属于某个具体的对象,是所有对象所共享的。
6.2 static修饰的成员方法和变量
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。
【静态成员变量特性】
1. 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
2. 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
3. 类变量存储在方法区当中
4. 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
public class Student{
public String name;
public String gender;
public int age;
public double score;
public static String classRoom = "Bit306";
// ...
public static void main(String[] args) {
// 静态成员变量可以直接通过类名访问
System.out.println(Student.classRoom);
Student s1 = new Student("Li leilei", "男", 18, 3.8);
Student s2 = new Student("Han MeiMei", "女", 19, 4.0);
Student s3 = new Student("Jim", "男", 18, 2.6);
// 也可以通过对象访问:但是classRoom是三个对象共享的
System.out.println(s1.classRoom);
System.out.println(s2.classRoom);
System.out.println(s3.classRoom);
}
}
static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的
public class Student{
// ...
private static String classRoom = "Bit306";
// ...
public static String getClassRoom(){
return classRoom;
}
}
public class TestStudent {
public static void main(String[] args) {
System.out.println(Student.getClassRoom());
}
输出:Bit306
【静态方法特性】
1. 不属于某个具体的对象,是类方法
2. 可以通过对象调用,也可以通过类名.静态方法名(...)方式调用,更推荐使用后者
3. 不能在静态方法中访问任何非静态成员变量
总之:我个人认为,当某个属性的值是确定的时候,比如班级,我们就可以使用static修饰这个属性,同一样,当这个方法也是不需要变动的时候,我们也使用static来修饰这个方法.这样就可以节省很多的空间.
7. 代码块
使用 {} 定义的一段代码称为代码块。根据代码块定义的位置以及关键字,又可分为以下四种:
7.1 普通代码块: 定义在方法中的代码块(少见)
public class Main{
public static void main(String[] args) {
{ //直接使用{}定义,普通方法块
int x = 10 ;
System.out.println("x1 = " +x);
} in
t x = 100 ;
System.out.println("x2 = " +x);
}
} /
/ 执行结果
x1 = 10
x2 = 100
7.2 构造块: 定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。
public class Student{
//实例成员变量
private String name;
private String gender;
private int age;
private double score;
public Student() {
System.out.println("I am Student init()!");
} /
/实例代码块
{
this.name = "bit";
this.age = 12;
this.sex = "man";
System.out.println("I am instance init()!");
}
public void show(){
System.out.println("name: "+name+" age: "+age+" sex: "+sex);
}
}
public class Main {
public static void main(String[] args) {
Student stu = new Student();
stu.show();
}
} /
/ 运行结果
I am instance init()!
I am Student init()!
name: bit age: 12 sex: man
public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom;
//实例代码块
{
this.name = "bit";
this.age = 12;
7.3 静态块: 使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。
public class Student{
private String name;
private String gender;
private int age;
private double score;
private static String classRoom;
//实例代码块
{
this.name = "bit";
this.age = 12;
this.gender = "man";
System.out.println("I am instance init()!");
} /
/ 静态代码块
static {
classRoom = "bit306";
System.out.println("I am static init()!");
}
public Student(){
System.out.println("I am Student init()!");
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
}
}
同步代码块(后续讲解多线程部分再谈)
7.4 整体的实例:
package Class_Object;
public class Student {
public String name;
public int age;
public int ID;
public static String classRoom ;//静态的成员变量不属于对象,属于类
public Student(){
//重载
//在构造方法里面调用其他构造方法,不能自己调用自己
this("小黑",89);
}
public Student(String name,int age) {
this.name = name;
this.age = age;
System.out.println("我是构造方法");
}
static {
//静态代码块
//一般用来初始化静态成员变量
//一般在类加载被调用
classRoom = "337";
System.out.println("这个是静态代码块");
}
{
//对普通成员变量进行初始化
this.ID = 2022011;
System.out.println("这个是构造代码块/实例代码块");//实例化的时候调用
}
public void eat() {
System.out.println(this.name + "正在吃饭");
//alt +insert这个可以快速生成构造方法
}
public static void fuc(){//func这个静态方法直接通过类名就能调用,不依赖对象
System.out.println("静态的方法");
// System.out.println(this.name);//name的调用需要对象的引用来调用,依赖对象,所以会报错
}
//TODO toString
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", ID=" + ID +
'}';
}
public static void main(String[] args) {
//静态先执行(只执行一次)
//执行构造代码块
//构造方法
Student student = new Student("小白",19);
// student.eat();
System.out.println("==============");
Student student1 = new Student("小黑",19);
{
System.out.println("我是普通代码块");
}
System.out.println(student1.toString());//打印对象内容
System.out.println(student1);//打印对象内容
}
}
//执行结果:
这个是静态代码块
这个是构造代码块/实例代码块
我是构造方法
==============
这个是构造代码块/实例代码块
我是构造方法
我是普通代码块
Student{name='小黑', age=19, ID=2022011}
Student{name='小黑', age=19, ID=2022011}
由上述代码我们可以知道:
静态代码块不管生成多少个对象,其只会执行一次
静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)实例代码块只有在创建对象时才会执行
8. 对象的打印
在上一个代码中,我们就用到了对象的打印方法:toString(), 这个方法就是来打印对象的属性,但是如果不重写(后续讲)就只会打印地址:
class Student {
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("小白",18);
//我们没有重写toString方法,直接调用
System.out.println(student.toString());
}
}
//运行结果:
Student@1540e19d
Process finished with exit code 0
如果我们重写了toString()方法,就可以成功打印出对象的属性值了
class Student {
public String name;
public int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {//这个就是重写的toString方法
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student("小白",18);
//重写了之后再调用
System.out.println(student.toString());
}
}
//运行结果:
Student{name='小白', age=18}
Process finished with exit code 0