目录
3.2 图解RiceCooker riceCooker = new RiceCooker();
前言
Java是一门面向对象的编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。下面就针对Java语言中的类和对象展开学习。
一、图解面向对象与面向过程
1.1 面向过程
面向过程(Procedure Oriented)是一种以过程为中心的编程思想。就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现。

传统做饭都要先生火,降锅中的水烧开,将米洗净放进桶中,水开之后将桶放进锅中将饭煮熟。在此期间我们还要不但往灶内放燃料,对火候要有所把握,中间少了那一个环节饭都会做不好。如果按照这种方式来写代码,这对于以后程序的扩展或维护都会有着不小的麻烦。
1.2 面向对象
Java是一门纯面向对象的语言(Object Oriented Program,继承OOP),面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。从人们的角度出发来设计程序,这会更符合人们对事物的认知,这对于程序的开发、维护会更加的友好。
当代做饭过程:
当代的做饭将洗净的米放进锅中,选择功能后就没有了。以面向对象的方式来处理,用户就不会关注做饭的过程,只需要开关即可,通过对象(人和电饭煲)之间的交互即可。
二、类的定义和使用
2.1 现实中的对象与实体
面向对象的程序设计只关注对象,而对象反映的是生活中的实体,比如上面的电饭煲。但是呢,计算机不是人,不会自动识别,所以需要编程人员告诉计算机什么是电饭煲。
上图左侧就是对电饭煲简单的描述,该过程称为对电饭煲对象(实体)进行抽象(对一个复杂事物的重新认知),但是 这些简化的抽象结果计算机也不能识别,开发人员可以采用某种面相对象(Java语言)的编程语言来进行描述。
2.2 认识类的定义
Java中的类是用来对一个对象(实体)进行细节的描述的,就拿电饭煲来说,队电饭煲的功能、大小等方面进行描述,然后计算机能够识别(计算中的识别:完成自定义的类后,经过javac编译之后形成.class文件,在JVM的基础上面识别。)
2.2.1 类的定义
Java中的类是用关键词class来定义的,而类中包含了成员方法(也叫做行为;描述电饭煲的功能)和成员变量(也叫做字段(属性);描述电饭煲的属性有哪些,比如价格,大小等等)。
代码如下:
// 创建类
class ClassName{
field; // 字段(属性) 或者 成员变量
method; // 行为 或者 成员方法
}
class RiceCooker{//命名采用大驼峰(首字母大写)
public String name; //姓名
public double price;//价格
public double weight;//重量
public void cook(){
System.out.println("功能是做饭!");
}
}
//电饭煲类在计算机中定义完成,经过javac编译之后形成.class文件,在JVM的基础上计算机就可以识别了。
类定义的注意事项:
🚀类名注意采用大驼峰定义,首字母大写
🚀成员前的public为访问限定符(后面会详解)🚀此处写的方法不带 static 关键字(被 static 修饰的成员方法称为静态成员方法,是类的方法,不是某个对象所特有的,后面会详解)🚀一般一个文件当中只定义一个类,方面使用。🚀 main方法所在的类一般要使用 public修饰🚀public修饰的类必须要和文件名相同
三、类的实例化
🚀上面我们自定义的电饭煲类就是在计算机中新定义的一种类型,就类似我们自定义的方法一样。我们可以使用这些自定义的类来定义实例(或者称为对象)。🚀用类这种类型创建对象的过程,称为类的实例化,在java中采用new关键字,配合类名来实例化对象。
代码如下:
//为了方便观察,所以一个文件夹里定义了两个类
class RiceCooker{//命名采用大驼峰
public String name; //姓名
public double price;//价格
public double weight;//重量
public void cook(){
System.out.println(name +"功能是做饭!");
}
}
public class Test {
public static void main(String[] args) {
RiceCooker riceCooker1 = new RiceCooker();//一个类可以创建多个实例
RiceCooker riceCooker = new RiceCooker();//riceCooker是一个引用,存了new RiceCooker()的地址
riceCooker.name = "电饭煲";//使用.号来访问RiceCooker类中的成员变量
riceCooker.cook();//使用.号来访问RiceCooker类中的成员方法
System.out.println(riceCooker.name);//打印的是电饭煲,如果没有赋初值打印null
}
}
//输出结果
//电饭煲功能是做饭!
3.1 类和对象的说明
🚀类就好似汽车图纸模型,我们可以根据这个图纸造出小汽车,小汽车就是我们的对象;而多个类可以实例化多个对象就好似我们可以对造好的小汽车选择不同的喷漆或者造出不同品牌的汽车等等。(看下面图解)
🚀类是自定义的类型,可以根据实际定义需要的变量
🚀一个类可以实例化多个对象,实例化的对象,占用实际的物理空间,存储类的成员变量,而类的成员方法是在栈区上开辟栈帧(堆栈的缓存方式:栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收),由此可知一个方法引用了对象的时候,方法销毁时,对象不会马上销毁,编译器会判断是否还有人在引用这个对象后再做处理)。
3.2 图解RiceCooker riceCooker = new RiceCooker();
🚀一个类可以实例化多个对象,而每一个对象都包含类中的成员变量(对象),类中的方法是在调用的时候才会在栈区上开辟栈帧,是不会占用堆上的内存的。
🚀我们都知道在Java中局部变量是需要赋初值的,不然编译器会报错,但是呢我们自定义的类中的成员变量不是局部变量,是成员变量,所以可以不赋初值;成员变量没赋初值是由默认值的:引用类型(String):null ;基本数据类型(int):0;boolean:false;char:\u0000。
🚀从JVM的角度出发,对象的调用先要加载这个类,在JVM中生成这个字节码对象(文件);然后为对象分配空间(堆上),生成多个对象的时候,JVM会并发的处理多个线程,以保证每个对象分配到的空间不会冲突;初始化对象里面的成员。
四、this的使用
4.1 为什么会有this
先看一个日期类的代码:
//两个方法之间的参数的不同带给我们的结果也是不同
//还可以调用构造方法,下面详解
class Date{
public int year;
public int month;
public int day;
//设置日期的第一种方法
public void setDate(int y,int m,int d){
year = y;
month = m;
day = d;
}
//设置日期的第二种方法
public void setDate1(int year,int month,int day){
year = year;
month = month;
day = day;
}
//设置日期的第三种方法
public void setDate2(int year,int month,int day){//this表示当前对象的引用
this.year = year;
this.month = month;
this.day = day;
}
public void printDate(){
System.out.println(year+"年"+month+"月"+day+"日");
//System.out.println(this.year+"年"+this.month+"月"+this.day+"日");//习惯用this
}
}
public class Test1 {
public static void main(String[] args) {
Date date = new Date();
date.setDate(2022,7,8);
date.printDate();//打印2022年7月8日
Date date1 = new Date();
date1.setDate1(2022,7,8);
date1.printDate();//打印0年0月0日
Date date2 = new Date();
date2.setDate2(2022,7,8);
date2.printDate();//打印2022年7月8日
}
}
图解:
🚀第二种方法为什么第二种方法打印0年0月0日呢?
因为这里的year,month,day是形参,由于局部变量优先(编译器的就近原则),形参year赋值给了成员变量year,那么成员变量year也变成了形参。而date1.printDate();函数调用的是Date日期类的成员变量,而类的成员变量默认值为0,所以打印0年0月0日。成员变量(没有改变)与局部变量无关。🚀第三种方法为什么加了this就正常打印了呢?
因为this引用指向当前对象(成员方法运行时调用该成员方法的对象),在成员方法中所有成员变量的操作,都是通过该引用去访问。当执行date2.setDate2(2022,7,8);时,date2调用setDate2的方法,那么setDate2方法中的this就相当于是date2,总的一句句话,就是谁调用了setDate2方法,谁就是引用,也可以说谁就等于this(这里date2=this)
我们通过打断点来调式可知,this所指向的就是date2。
4.2 this的注意事项
🚀this引用即哪个对象调用就是哪个对象的引用类型
🚀this 只能在 " 成员方法 " 中使用,不能在成员方法外部使用🚀在 " 成员方法 " 中, this 只能引用当前对象(上述的代码:当前对象为Date),不能再引用其他对象。
五、对象的初始化和构造方法
5.1 对象的初始化
对象的初始化有两种方法:
🚀通过set方法初始化(就行上面的setDate()一样)
🚀通过构造方法进行初始化(创建对象的时候会调用构造方法,不需要每次都是用set方法来初始化,减少不必要的时间)
5.2 构造方法
5.2.1 构造方法的概念
构造方法(也称为构造器)是一个特殊的成员方法,名字必须与类名相同,在创建对象时,由编译器自动调用,并且 在整个对象的生命周期内只调用一次,没有返回值。
代码如下:
class Date{
public int year;
public int month;
public int day;
//public int day = 30;//就地初始化,不推荐使用
public Date(){//默认的构造方法
this(2022,19,8);//调用带有三个参数的构造方法必须放在构造方法的第一行
//编译器会默认提供一个构造方法(默认是无参的)
//默认的构造方法,当出现了自己定义的构造方法后,编译器就不会自己提供了,以自己提供的为准
}
public Date(int year,int month,int day){//重写后的构造方法(重写后,编译器默认的构造方法就会消失)
this.year = year;
this.month = month;
this.day = day;
//this.printDate();
}
public void printDate(){
System.out.println(this.year+"年"+this.month+"月"+this.day+"日");
}
}
public class Test2 {
public static void main(String[] args) {
Date date = new Date();//不带参数的构造方法
date.printDate();/打印2022年19月8日
Date date1 = new Date(2022,7,8);//带参数的构造方法,调用带有三个参数的构造方法
date1.printDate();//打印2022年7月8日
}
}
实例化一个对象可以分为重要的两步:
🚀调用合适的构造方法
🚀为这个对象分配内存空间
5.2.2 方法的重载
public Date(){
}
public Date(int year,int month,int day){
this.year = year;
this.month = month;
this.day = day;
}
//下面的方法在编译器下会报错,为什么呢?因为两个方法的变量都是整形,编译器识别不了
public Date(int year,int month){
this.year = year;
this.month = month;
this.day = day;
}
public Date(int month,int day){
this.year = year;
this.month = month;
this.day = day;
}
方法的重载需满足三个条件:
🚀方法名相同
🚀参数的个数,顺序,定义的类型不同
🚀和返回值没有关系
六、封装
6.1 封装的概念
🚀面向对象程序三大特性:封装、继承、多态。何为封装呢?那计算机来说,计算机厂商在出厂时,在计算机的外部套上壳 子,将内部实现细节隐藏起来,仅仅对外提供键盘、鼠标等操作,让用户可以与计算机进行交互即可。总的来说就说就是 套壳屏蔽细节。🚀封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。我们不必关心实现的细节,只需要通过公开的接口来进行交互就行了。
6.2 访问限定符
public:在任何地方都能够访问
default:默认的访问限定,只能在同一个包下访问
protected:不同包中的子类都能访问,主要在继承中体现,后面会详解
private:私有的,只能在自己类里面访问
下面我们就用private举例说明:
如下代码:
class Date{
private int year;//私有的
int month;//默认为default权限
protected int day;//受保护的
}
//另一个类;两个类都是在同一个包下
public class TestDemo {
public static void main(String[] args) {
Date date = new Date();
date.year;//year是私有的,只能在自己的类中访问,所以会出错
date.month = 7;
date.day = 8;
System.out.println(date.year+"年"+date.month+"月"+date.day+"日");//err
}
}
结果如下:
由上述可知道,private只能在自己的类中访问,想要在类外访问我们就必须提供两个接口才行(setYear和getYear)
如下代码:
class Date{
private int year;//私有的
int month;//默认为default权限
protected int day;//受保护的
//private Date(){//不能实例化了
//}
public int getYear() {
return this.year;
}
public void setYear(int year) {
this.year = year;
}
}
public class TestDemo {
public static void main(String[] args) {
Date date = new Date();
date.setYear(2022);//需要一个接口来设置日期(封装:不需要关心实现的细节,有结果就行)
date.getYear();//需要一个接口来返回日期(我们不能直接得到)
date.month = 7;
date.day = 8;
System.out.println(date.getYear()+"年"+date.month+"月"+date.day+"日");
}
}
6.3 包的概念
🚀在面向对象体系中,提出了一个软件包的概念,为了更好的管理类,把多个类收集在一起成为一组,称为软件包。就好比文件夹,我们将类似的东西一块放在一个文件夹里面,这可以让我们更快速的找到我需要的内容。🚀在Java 中也引入了包, 包是对类、接口等的封装机制的体现,是一种对类或者接口等的很好的组织方式 ,比如:一 个包中的类不想被其他包中的类使用(继承)。包还有一个重要的作用:在同一个工程中允许存在相同名称的类,只要处在 不同的包中即可 。
七、详解static
7.1 static修饰成员变量、成员方法并且访问的方式
代码如下:
class Student{
public String name;
public int age;
public String sex;
//public static final String classes = "高一三班";//final修饰--常量(密封的)必须初始化
public static String classes = "高一三班";//final修饰--常量(密封的)必须初始化
//成员方法分为静态的和非静态的,直接用 类名.func() 访问,不需要实例化对象
public static void func(){
System.out.println("静态的成员方法!");
//System.out.println(name+"静态的成员方法!");//err 静态方法不依赖对象
}
public static void funcStatic(){//访问非静态的成员变量
Student student = new Student();
student.age = 10;
System.out.println(student.age);//打印10
}
//成员方法里面都是默认有Student this,除了静态的方法和构造方法
//这也再次解释了静态方法中不能调用任何非静态方法,因为非静态方法有this参数,在静态方法中调用时候无法传递this引用
public void func1(Student this){
System.out.println(student.age);
}
}
public class Test {
public static void main(String[] args) {
Student.func();//打印:静态的成员方法!
System.out.println(Student.classes);//打印高一三班
}
}
图解:
用public static String classes = "高一三班";说明:
🚀静态成员变量最大的特性:不属于某个具体的对象,是所有对象所共享的。所以他不存在对象里面,他单独的存放在一块方法区里面,仅且只有一份。
🚀我们访问静态的成员变量直接用 类名.成员变量访问,因为静态的成员变量与对象无关。(静态的成员方法也是如此)
🚀生命周期伴随类的一生(JVM:随类的加载而创建,随类的卸载而销毁,上面的认识JVM有讲解)
🚀在静态的成员方法里面是不能出现非静态的成员变量,因为静态的成员变量和方法是不依赖对象的;但是呢,如果你想要在静态的成员方法里面访问非静态的成员变量需要在方法的内部定义一个对象,通过对象的引用去访问。
通过一段自定义代码再次解释static:
//常见的自定义方法
public class Test {
public static void func(){
System.out.println("static");
}
public static void main(String[] args) {
func();//打印static
}
}
//没有static修饰
public class Test {
public void func(){
System.out.println("static");
}
public static void main(String[] args) {
//func();//err
Test test = new Test();
test.func();//打印static
}
}
在Java自定义的方法里面为什么要加static呢?通过上面的代码我们可以知道main方法是静态的(static修饰的),通过学习后我们也知道了静态的成员方法和变量是不依赖对象的,所以当我们自定义的方法没有static修饰时,我们想要访问的话,就需要在方法的内部定义一个对象,通过对象的引用来访问。
7.2 static初始化
🚀静态成员变量一般不会放在构造方法中来初始化,构造方法中初始化的是与对象相关的实例属性🚀静态成员变量的初始化分为两种:就地初始化 和静态代码块初始化。
八、代码块详解
🚀用{}括起来定义的一段代码就叫做代码块。
🚀分为普通代码块(定义在main函数里面) ;构造代码块 ;静态代码块 ;同步代码块(后面文章详解)
代码如下:
class Student{
//构造代码块:一般用于初始化实例成员变量
public Student(){
System.out.println("构造代码块");
}
//实例代码块
{
System.out.println("实例代码块");
}
//静态代码块:一般用于初始化静态成员变量。
static{
System.out.println("静态代码块");
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();//按照静态——>实例——>构造的顺序打印
System.out.println("=========");
Student student1 = new Student();//按照实例——>构造的顺序打印(静态的代码块只会执行一次,不会管你实例化多少个对象)
}
}
//打印结果
静态代码块
实例代码块
构造代码块
=========
实例代码块
构造代码块
由上面的代码可知:
🚀类是最先被加载的,实例化对象的时候(类加载完成了)就会按照静态——>实例——>构造的顺序打印;二次实例化对象后,按照实例——>构造的顺序打印(静态的代码块只会执行一次,不会管你实例化多少个对象)
🚀如果有多个相同类型的代码块,就看定义的次序。实例代码块只有创建对象的时候才会调用。
🚀静态成员变量是类的属性,因此是在JVM加载类时开辟空间并初始化的
九、内部类详解
9.1 实例内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务(内部类就相当于公司的每一个部门,少了哪一个部门,就去定义那一个部门,而外部类就相当于整个公司)。在 Java 中, 可以将一个类定义在另一个类或者一个方法的内部, 前者称为内部类,后者称为外部类 。内部类也是封装的一种体现。
代码如下:
//外部类
class OutClass{
public int age = 3;
public static final String sex = "男";
//实例内部类
class InnerClass{
public static final String sex = "女";//必须加final才行
public int age = 6;//与外部类的变量名相同
public String name = "小明";
public void func(){
//与外部类的变量名相同如何访问呢
System.out.println(this.age);//打印6;这里的this表示当前对象InnerClass对象的引用
System.out.println(OutClass.this.age);//打印3
System.out.println(sex);//打印女
System.out.println(OutClass.sex);//打印男 访问外部类的静态的成员变量
}
public InnerClass(){
}
//不能定义静态的方法
}
}
public class Test {
public static void main(String[] args) {
OutClass outClass = new OutClass();//需要拿到外部类的对象才行
OutClass.InnerClass innerClass = outClass.new InnerClass();//产生了两个对象
//OutClass.InnerClass innerClass1 = new OutClass().new InnerClass();//合二为一
innerClass.func();
}
}
由上面的代码我们可以提出几个问题:
🚀如何访问外部类与内部类同名的变量呢?访问的顺序是什么?
通过外部类名.this.变量名访问外部类的成员变量(访问同名的时候优先访问自己内部的,再去看外部类的)
🚀内部类中不能定义静态的成员方法和变量;要定义静态的变量需要加final修饰才可以
🚀如何实例化内部类的对象?通过 :
OutClass outClass = new OutClass();
OutClass.InnerClass innerClass = outClass.new InnerClass();
注意事项:
🚀外部类中的任何成员都可以被在实例内部类方法中直接访问🚀实例内部类所处的位置与外部类成员位置相同,因此也受 public 、 private 等访问限定符的约束。🚀实例内部类对象必须在先有外部类对象前提下才能创建🚀实例内部类的非静态方法中包含了一个指向外部类对象的引用(OuterClass.this)🚀外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
9.2 静态内部类
🚀被static 修饰的内部成员类称为静态内部类。🚀在静态内部类中只能访问外部类中的静态成员🚀创建静态内部类对象时,不需要先创建外部类对象🚀与上面详解static结合。🚀类是最先被加载的
//外部类
class OutClass{
public int age = 3;
public static final String sex = "男";
//静态内部类
static class InnerStaticClass{
public static final String sex = "女";//必须加final才行
public int age = 6;//与外部类的变量名相同
public String name = "小明";
public OutClass outClass;
public InnerStaticClass(){
System.out.println("不带参数的");
}
public InnerStaticClass(OutClass outClass){
this.outClass = outClass;//set get
}
public static void func(){
System.out.println(sex);
//不能直接访问非静态的成员,需要一个对象来引用
OutClass outClass = new OutClass();
System.out.println(outClass.sex);
System.out.println(outClass.age);
}
}
}
public class Test1 {
public static void main(String[] args) {
//访问外部类的对象需要外部类的对象来引用才行
OutClass.InnerStaticClass innerStaticClass = new OutClass.InnerStaticClass(new OutClass());
//通过下面的代码来创建静态内部类的引用,因为静态不需要对象,所以不用先创建外部类的对象
//OutClass.InnerStaticClass innerStaticClass = new OutClass.InnerStaticClass();
innerStaticClass.func();
}
}
9.3 局部内部类
局部内部类有点鸡肋,用处不多,他是定义局部方法里面的,只能在所定义的方法内部使用。
代码如下:
public class Test2 {
public static void func(){
class LocalClass{ //不能被public、static等修饰符修
public LocalClass(){
System.out.println("构造方法");
}
}
LocalClass localClass = new LocalClass();//打印构造方法
}
public static void main(String[] args) {
func();
}
}
9.4 匿名内部类
下面简单说明,后面的接口会详细介绍:
代码如下:
class Student{
public String name;
public int age;
public void func(){
System.out.println("匿名对象");
}
}
public class Test3 {
public static void main(String[] args) {
new Student().func();//匿名对象,只能用一次,需要的时候还要匿名一次
new Student().func();
new Student(){//匿名内部类
public void test(){
System.out.println("匿名内部类");
}
}.test();
//Student student = new Student();
}
}
十、 打印对象
直接上代码:
class Student{
public String name;
public int age;
public double weight;
public Student(String name, int age, double weight) {
this.name = name;
this.age = age;
this.weight = weight;
}
@Override //注解 重写了头toString()
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", weight=" + weight +
'}';
}
}
public class Test3 {
public static void main(String[] args) {
Student student = new Student("小明",10,36.3);
//System.out.println(student);//打印demo1.Student@1b6d3586(demo1.Student表示在那个包下,@后面的字符串表示地址的哈希值)
System.out.println(student);//打印Student{name='小明', age=10, weight=36.3}
}
}
🚀在没有重写toString()方法的时候会打印一个地址,这是为什么呢?我们通过查看println的原码可以得出,我们原本调用的是Object(所有类的祖先)类的toString()方法,而这toString()方法的返回值就如图第3副的返回值。
🚀当我们从写了toString()方法方法后,编译器就会调用我们自己重写后的方法,打印我们初始化好了的值。
总结
类和对象
🚀通过class创建类,通过new创建对象,一个类可以是实例化多个对象
🚀通过对象的引用来访问创建的类的属性和方法
🚀实例化对象会调用构造方法,编译器会提供构造方法,多个构造方法形成方法的重载(如果两个构造方法的有两个变量,变量定义的类型是一样的,那编译器识别不了)
🚀构造方法有多个,可以重载,名字跟类名相同,没有返回值,实例化对象的时候就会调用构造方法(默认不带参数的构造方法),可以对成员变量初始化
this的注意事项
🚀this.成员方法或成员变量(访问当前对象的属性,只能在方法的内部使用)
🚀this();必须在构造方法里面,且是第一行,与super()不能共存,不能形成环的调用
赋值的方法
🚀就地初始化(在定义成员变量的时候就初始化)
🚀不初始化(对应的默认值)
🚀通过对象的引用进行赋值
🚀通过构造方法进行初始化
🚀通过set、get来初始化(private)
static注意事项
🚀静态的方法是不能直接访问非静态的成员方法和变量