初识java - 类和对象
一,面向对象的初步认知
1.1 什么是面向对象
Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。用面向对象的思想来涉及程序,更符合人们对事物的认知,对于大型程序的设计、扩展以及维护都非常友好。
1.2 面向对象与面向过程
一开始的时候,并没有面向对象,只有面向过程的概念。面向过程很好理解,指的是程序员接到需求,会把它拆成一个一个的命令,然后串起来交给计算机去执行(例如c中如何实现函数)。
而面向对象就有一点不同了,他并不是要将实现这个过程的细节给列举,而是将目标实现的过程给列举。
举个例子,产品经理说要把大象装进冰箱里。程序员列了几个步骤:
- 把冰箱门儿打开。
- 把大象装进去。
- 把冰箱门儿关上。
上面每一个步骤,程序员都会用一个「函数」来实现。「函数」是一些代码的集合体,每个函数可以实现一个功能,而这就是面向对象。
二, 类定义和使用
2.1 简单认识类
类是用来对一个实体(对象)来进行描述的,主要描述该实体(对象)具有哪些属性(外观尺寸等),哪些功能(用来干
啥),描述完成后计算机就可以识别了。
比如:洗衣机,它是一个实体,在Java中可以将其看成是一个类。
属性:产品品牌,型号,产品重量,外观尺寸,颜色…
功能:洗衣,烘干、定时…
那在Java语言中,如何对上述的洗衣机类来进行定义呢?
2.2 类的定义格式
在java中定义类时需要用到class关键字,具体语法如下
class为定义类的关键字,ClassName为类的名字,{}中为类的主体。
// 创建类
class ClassName{
field; // 字段(属性) 或者 成员变量
method; // 行为 或者 成员方法
}
类中包含的内容称为类的成员。属性主要是用来描述类的,称之为类的成员属性或者类成员变量。方法主要说明类具有哪些功能,称为类的成员方法。
2.3类的注意事项
- 类名注意采用大驼峰定义
- 一般一个文件当中只定义一个类
- main方法所在的类一般要使用public修饰(注意:Eclipse默认会在public修饰的类中找main方法)
- public修饰的类必须要和文件名相同
- 不要轻易去修改public修饰的类的名称,如果要修改,通过开发工具修改。
- 一个.java文件中只能有一个public类。
2.4局部变量,成员变量及成员变量和全局变量的关系
public class ClassName {
//这个a为成员变量,同时也叫字段(属性)因为是在内中定义的
public int a;
//这个c也为成员变量(字段,属性)(但同时也有人叫他为全局变量,因为他被由static关键字所修饰)
//当然不要纠结这是为什么,在后面的static成员中会作出解释
public static int c;
public static void main(String[] args) {
//这个b叫做局部变量,因为他是定义在方法中的
int b = 0;
}
}
- 成员变量可以不用初始化(默认:基本类型大多为0 ,引用类型为null)
- 局部变量必须初始化(不然编译会报错)
三, 类的实例化
3.1 什么是实例化
用类类型创建对象的过程,称为类的实例化,在java中采用new关键字,配合类名来实例化对象。
//定义了一个人类
class Person {
//人的属性
public String name;//名字
public int age;//年龄
//人的行为(成员方法)
public void eat(){//吃饭
System.out.println("年龄为" + age + "的" + name + " 正在吃饭");
}
public void sleep(){//睡觉
System.out.println("年龄为" + age + "的" + name + " 正在睡觉");
}
}
public class Test {
public static void main(String[] args) {
//这就是实例化对象
Person person = new Person();
//用对象实例加 . 来调用对象中的属性和方法
person.name = "张三";
person.age = 20;
person.eat();
person.sleep();
}
}
----------------------
年龄为20的张三 正在吃饭
年龄为20的张三 正在睡觉
四, this引用
4.1 什么是this引用
this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
- 注意:this引用的是调用成员方法的对象。
4.2this引用的作用
在编程过程中我们会遇到这样的情况,以构造函数举例(构造函数下面会有讲解):在初识化成员变量时会有以下情况
public class Test {
public static void main(String[] args) {
//这就是实例化对象
Person person = new Person("张三",20);
person.sleep();
person.eat();
}
}
//定义了一个人类
class Person {
//人的属性
public String name;//名字
public int age;//年龄
//无参构造方法
public Person() {
}
//带参构造方法
public Person(String name, int age) {
name = name;
age = age;
}
//人的行为(成员方法)
public void eat(){//吃饭
System.out.println("年龄为" + age + "的" + name + " 正在吃饭");
}
public void sleep(){//睡觉
System.out.println("年龄为" + age + "的" + name + " 正在睡觉");
}
}
-------------------
预想结果:
年龄为20的张三 正在睡觉
年龄为20的张三 正在吃饭
实际结果:
年龄为0的null 正在睡觉
年龄为0的null 正在吃饭
我们在实例化的时候已经赋值了但是结果却和我们预想的不同,而之所以会出现这种情况,原因就在带参构造方法那里:
//带参构造方法
public Person(String name, int age) {
name = name; //这里的name产生了歧义
age = age; //这里的age产生了歧义
}
我们想象的
但实际上的
可是计算机却不知道谁是谁的name,age计算机在执行这种代码时遵循一个原则(就近原则),谁近就用谁,所以就导致带参构造方法中的代码成了无效代码,而类中的成员变量也未得到初始化,但是成员变量未初始化的情况下,系统会给成员变量默认初始化,基本数据类型为0(bool除外),引用数据类型为null。
这就造成了我们说看到的和分析的不同:
预想结果:
年龄为20的张三 正在睡觉
年龄为20的张三 正在吃饭
实际结果:
年龄为0的null 正在睡觉
年龄为0的null 正在吃饭
而我们要如何来改变这种情况呢!这就使用到了this关键字:
public class Test {
public static void main(String[] args) {
//这就是实例化对象
Person person = new Person("张三",20);
person.sleep();
person.eat();
}
}
//定义了一个人类
class Person {
//人的属性
public String name;//名字
public int age;//年龄
//无参构造
public Person() {
}
//带参构造
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//人的行为(成员方法)
public void eat(){//吃饭
System.out.println("年龄为" + age + "的" + name + " 正在吃饭");
}
public void sleep(){//睡觉
System.out.println("年龄为" + age + "的" + name + " 正在睡觉");
}
}
-------------------------
预想结果:
年龄为20的张三 正在睡觉
年龄为20的张三 正在吃饭
实际结果:
年龄为20的张三 正在睡觉
年龄为20的张三 正在吃饭
而这就是加了this关键字后其中name,age的指向
4.3 this引用的特性
-
- this的类型:对应类类型引用,即哪个对象调用就是哪个对象的引用类型
-
- this只能在"成员方法"中使用
-
- 在"成员方法"中,this只能引用当前对象,不能再引用其他对象
- 4. this(…)必须是构造方法中第一条语句
-
- this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收。
在代码层面来简单演示—>注意:下图右侧中的Date类也是可以通过编译的
- this是“成员方法”第一个隐藏的参数,编译器会自动传递,在成员方法执行时,编译器会负责将调用成员方法对象的引用传递给该成员方法,this负责来接收。
4.4this调用
- 调用成员属性 – this.data
- 调用成员方法 – this.func()
- 调用构造方法 – this()
//无参构造
public Person() {
this("张三",20);//调用有参
}
//带参构造
public Person(String name, int age) {
this.name = name;
this.age = age;
}
【注意】
1).this()必须放在第一行
2).this()只能存在构造方法中
3).this不能调用静态成员和静态方法
五, 构造方法及对对象的初始化
5.1构造方法
5.1.1概念
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且在整个对象的生命周期内只调用一次。
注意:构造方法的作用就是对对象中的成员进行初始化,并不负责给对象开辟空间。
5.1.2 特性
-
- 名字必须与类名相同
-
- 没有返回值类型,设置为void也不行
-
- 创建对象时由编译器自动调用,并且在对象的生命周期内只调用一次(相当于人的出生,每个人只能出生一次)
-
- 构造方法可以重载(用户根据自己的需求提供不同参数的构造方法)
- 5. 如果没有自己写构造方法,编译器会自动帮你形成一个不带参数的构造方法,但是如果自己写了构造方法,编译器就不会自动生成无参构造方法。
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
}
}
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(){
this(1900,1,1);
}
public Date(int year, int month, int day) {
this();
}
/*
无参构造器调用三个参数的构造器,而三个参数构造器有调用无参的构造器,形成构造器的递归调用
编译报错:Error:(19, 12) java: 递归构造器调用
*/
5.2对象的初始化
5.2.1 默认初始化
在上文中提出的第二个问题:为什么局部变量在使用时必须要初始化,而成员变量可以不用呢?
要搞清楚这个过程,就需要知道 new 关键字背后所发生的一些事情:
在程序层面只是简单的一条语句,在JVM层面需要做好多事情,下面简单介绍下:
-
检测对象对应的类是否加载了,如果没有加载则加载
-
为对象分配内存空间
-
处理并发安全问题
比如:多个线程同时申请对象,JVM要保证给对象分配的空间不冲突 -
初始化所分配的空间
即:对象空间被申请好之后,对象中包含的成员已经设置好了初始值,比如:
-
设置对象头信息(关于对象内存模型后面会介绍)
-
调用构造方法,给对象中各个成员赋值
5.2.2一个简单题,带你初步了解jvm在实例化对象时的代码执行情况
class X{
Y y=new Y();//1
public X(){//2
System.out.print("X");
}
}
class Y{
public Y(){//3
System.out.print("Y");
}
}
public class Z extends X{
Y y=new Y();//4
public Z(){//5
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
//-----------------------
//输出:YXYZ
5.2.3 就地初始化
在声明成员变量时,就直接给出了初始值。
public class Date {
public int year = 1900;
public int month = 1;
public int day = 1;
//无参构造
public Date(){
}
//带参构造
public Date(int year, int month, int day) {
}
public static void main(String[] args) {
Date d1 = new Date(2021,6,9);
Date d2 = new Date();
}
}
六, 封装
面向对象程序三大特性:封装、继承、多态。而类和对象阶段,主要研究的就是封装特性。何为封装呢?简单来说就是套壳屏蔽细节。
6.1 封装的概念
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
6.2 访问限定符
public:可以理解为一个人的外貌特征,谁都可以看得到。
default: 对于自己家族中(同一个包中)不是什么秘密,对于其他人来说就是隐私了。
private:只有自己知道,其他人都不知道。
【说明】
protected主要是用在继承中,继承部分详细介绍。
default权限指:什么都不写时的默认权限。
访问权限除了可以限定类中成员的可见性,也可以控制类的可见性。
注意:一般情况下成员变量设置为private,成员方法设置为public。
6.3 包的概念
这里的testDemo,Testone,testtwo,都为互不相同的包,中和上面(6.2访问限定符表)就可以理解访问限定修饰符的作用范围了。
在用到其他包的类时就需要导包,用import关键字来进行导包。
【注意】
- 只能导入指定的类
import testDemo.Testtwo.Test2;
- 不能导入指定的包
import testDemo.Testtwo;
这里编译器会报错
七, static成员
7.1 static修饰成员变量
static修饰的成员变量,称为静态成员变量,静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。
【静态成员变量特性】
1). 不属于某个具体的对象,是类的属性,所有对象共享的,不存储在某个对象的空间中
2). 既可以通过对象访问,也可以通过类名访问,但一般更推荐使用类名访问
3). 类变量存储在方法区当中
4). 生命周期伴随类的一生(即:随类的加载而创建,随类的卸载而销毁)
7.2 static修饰成员方法
Java中,被static修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的。静态成员一般是通过静态方法来访问的。
【静态方法特性】
1). 不属于某个具体的对象,是类方法
2). 可以通过对象调用,也可以通过类名.静态方法名(…)方式调用,更推荐使用后者
3). 不能在静态方法中访问任何非静态成员变量
4). 静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用
5). 静态方法无法重写,不能用来实现多态(此处大家暂时不用管,后序多态位置详细讲解)。
7.3 static成员变量初始化
注意:静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性
静态成员变量的初始化分为两种:就地初始化 和 静态代码块初始化。
-
就地初始化
就地初始化指的是:在定义时直接给出初始值
-
静态代码块初始化
那什么是代码块呢?继续往后看 😃 😦
八, 代码块
8.1 代码块概念以及分类
使用 {} 定义的一段代码称为代码块(只执行一次)。根据代码块定义的位置以及关键字,又可分为以下四种:
- 普通代码块
- 构造块
- 静态块
- 同步代码块
8.2 普通代码块
普通代码块:定义在方法中的代码块(用法较少见)
public class Test3 {
public static void main(String[] args) {
Person person = new Person("张三",20);
System.out.println(person.getName() +" "+ person.getAge());
{
//这是普通代码块
}
}
}
8.3 构造块
构造块:定义在类中的代码块(不加修饰符)。也叫:实例代码块。构造代码块一般用于初始化实例成员变量。(用到较多)
//实例成员变量
private String name;
private int age;
//这是示例代码块
{
this.name = "李华";
this.age = 20;
}
8.4 静态块
使用static定义的代码块称为静态代码块。一般用于初始化静态成员变量。
//静态成员变量
public static int num;
//这是静态代码块
static {
num = 20;
}
【注意事项】
- 静态代码块不管生成多少个对象,其只会执行一次
- 静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
- 如果一个类中包含多个静态代码块,在编译代码时,编译器会按照定义的先后次序依次执行(合并)
- 实例代码块只有在创建对象时才会执行
8.5成员变量,代码块与类实例化时的一些先后实现顺序
**优先静态成员和静态代码块(先于实例化对象前)(谁在前谁先被调用)-> 构造代码块 -> 构造方法 **
- 当实例化对象时,构造代码块会被拷贝到构造方法里面最上面执行。
九, 内部类
顾名思义,在类里面定义的类。
【注意事项】
1). 定义在class 类名{}花括号外部的,即使是在一个文件里,都不能称为内部类
2). 内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
根据内部类定义的位置不同,一般可以分为以下几种形式:
1). 成员内部类(普通内部类:未被static修饰的成员内部类 和 静态内部类:被static修饰的成员内部类)
2). 局部内部类(不谈修饰符)、匿名内部类
注意:内部类其实日常开发中使用并不是非常多,大家在看一些库中的代码时候可能会遇到的比较多,日常开始中使用最多的是匿名内部类。
十, 对象的打印(cv程序猿)
public class Person {
String name;
String gender;
int age;
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
//如果想要默认打印对象中的属性该如何处理呢?答案:重写toString方法即可。
}
public static void main(String[] args) {
Person person = new Person("Jim","男", 18);
System.out.println(person);
}
}
// 打印结果:day20210829.Person@1b6d3586
public class Person {
String name;
String gender;
int age;
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
@Override//方法重写
public String toString() {
return "[" + name + "," + gender + "," + age + "]";
}
public static void main(String[] args) {
Person person = new Person("Jim","男", 18);
System.out.println(person);
}
}
// 输出结果:[Jim,男,18]
11. 每日一问
类和对象:
- 什么类? 什么是对象? 什么是面向对象? 什么是面向过程?
- 如何定义一个类?
- 如何实例化一个对象?
- 通过对象如何访问对象的成员属性/成员变量 对象的成员方法通过对象.访问
- this引用?
- 构造方法?
- 引用指向null代表什么? 引用指向引用可以吗? 一个引用是否可以指向多个对象?
- 什么是封装?
- 对类的细节进行隐藏? 提供公开的接口来进行和数据进行交互!!
- 包的概念? 如何导包?
- 对成员变量,局部变量,由static关键字修饰的成员变量的 关系 和 内存 分布知道?
- 代码块? 作用?
- 程序运行时的 成员变量 代码块 构造方法 及由static修饰后的 成员变量 代码块 的运行顺序?