05、面向对象—基本认识


前言

      去年四月份大一下半学期正式开始学习Java,一路从java基础、数据库、jdbc、javaweb、ssm以及Springboot,其中也学习了一段时间数据结构。

      在javaweb期间做了图书商城项目、ssm阶段做了权限管理项目,springboot学了之后手痒去b站看视频做了个个人博客项目(已部署到服务器,正在备案中)。期间也不断进行做笔记,总结,但是越学到后面越感觉有点虚,觉得自己基础还有欠缺。

      之后一段时间我会重新回顾java基础、学习一些设计模式,学习多线程并发之类,以及接触一些jvm的相关知识,越学到后面越会感觉到基础的重要性,之后也会以博客形式输出学习的内容。

      现在整理的java知识基础点是在之前学习尚硅谷java课程的笔记基础之上加工汇总,部分图片会引用尚硅谷或网络上搜集或自己画,在重新回顾的过程中也在不断进行查漏补缺,尽可能将之前困惑的点都解决,让自己更上一层楼吧。

      博客目录索引博客目录索引(持续更新)



一、面向过程与面向对象

面向过程(POP)、面向对象(OOP)

面向过程:针对于要做的事情一步一步封装到一个方法中,但若是提出要修改不同的规格与事情就需要不断的重复更改方法中的过程及内容。其更强调的是功能行为,以函数为最小单位。

面向对象:适应性与扩展性强,可根据要做的事情抽象成一个个实体类,每个实体类都有对应其自己的方法,若是有公共特征的可以使用继承,方便调用、修改。以类为最小单位,考虑谁来做

  • 加入新功能不会搞乱以前写好的代码。
  • 数据与操作数据的方法摆在同一个类内。
  • 可吃重复运用在别的程序中,并使该类具有足够的扩展性,正因为如此能够有如此之多的工具包。
  • 所有java程序都定义在类中。

以对象来思考,对象可以是已知的事物,对象会执行的动作。对象已知的事物成为实例变量,执行动作为方法

三个特性:封装、继承、多态


例子说明两者区别

例如:人要打开冰箱,抬起大象塞进冰箱,关闭冰箱

面向过程:直接一个方法具体完成功能行为

void func(){
	①人打开冰箱
	②抬起大象,塞进冰箱
	③关闭冰箱
}

面向对象:相关事进行抽象成一个个类,相应类执行其自己的事,最后进行相互调用

class 人{
    大象进冰箱(大象,冰箱){
        冰箱.打开;
        大象.进入(冰箱);
        冰箱.关闭;
    }
}

class 冰箱{
    打开冰箱();
  	关闭冰箱();
}

class 大象{
    进入(物体)
}


二、类与对象

介绍类与对象

类不是对象,但是类可以创造多个对象(新的实例)。

创建对象时,对象会被存放到称为中的内存区域里。此区域是可回收的堆(Garbage-Collectible Heap),Java会根据对象的大小来分配内存空间,对象引用也只是个变量值。

那么对于内存空间如何释放呢?Java会主动帮你管理内存,当某个对象被JVM观察到不再被使用时,该对象会被标记为可回收的。若是内存开始不足,垃圾收集器会启动来清理垃圾,回收空间。


创建类与对象

一般来说先会创建类,类中包含相应的属性、构造器、方法。

有了类之后,我们就可以创建对象了(前提有对象构造器),不同的对象其属性都可以不相同(属性并非为static情况下)。

这里再次温习一下关于初始赋值问题

  • 类与一维数组中定义的基本类型与引用类型属性:自动赋初值(各个初始值见之前数组章节),引用类型属性默认为null。
  • 方法中定义的基本类型与引用类型:不会自动赋初值,直接输出或使用会报空指针。

我们先创建一个Person类:包含几个私有属性以及几个公共方法

class Person{
    private int age;
    private String name;
    private boolean isMale;
    public void setAge(int age) {
        this.age = age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setMale(boolean male) {
        isMale = male;
    }
}

此时类模板有了,我们就可以创建对象了,直接Person person = new Person();即可创建对象了。

  • 类有构造器的情况下,才能够通过new来创建对象

  • 创建一个类时会自动创建一个隐藏的空参构造器,如下:

    • //这里与上面Person类相关,这里只做演示,默认是隐藏存在的
      public Person(){
      }
      

对象创建过程:对象声明、创建、赋值,以上面对象为例子

  1. 声明一个引用变量=》Person person
  2. 创建对象,java虚拟机中分配堆空间给新建的Person对象=》new Person()
  3. 连接对象和引用,将内存地址值赋值给引用变量=》=

Person对象的内存解析:这里借用尚硅谷演示的图展示其在内存中的情况

image-20210124232638687

说明:通过new创建的数组以及对象都是在中创建的,中存放变量名及指向的内存地址,大致与之前数组相同。



对象引用

摘自《head first java 2.0》,问题描述

:引用变量多大?

答:不知道。除非你跟某个java虚拟机开发团队有交情,否则是不会知道引用是如何表示的。其内部有指针,但是你无法也不需要去存取。若是想要讨论内存分配问题,最需要关心的就是需要创建多少个对象和引用,以及对象的实际大小。

:是否意味着所有的对象引用都具有相同的大小,而不管它实际上所引用的对象大小?

答:是的,对于java虚拟机来说,所有的引用大小都一样。但是不同的java虚拟机都会以不同的形式来表示引用,因此某个java虚拟机的引用大小可能会大于或小于另一个java虚拟机的引用。

:是否可以对引用变量进行运算,就像c语言一样?

答:不行,Java不是C。

关于引用变量:引用变量可以被看做是遥控器,可以设定来控制不同的对象。一旦其引用变量被标记为final,就不能再赋值除了第一次引用之外的对象了。


对象在堆上的生与死

  • 当一个对象不与一个引用变量产生联系,相关时,能够作垃圾回收器(GC),会等待回收。
  • 当一个引用变量为null时,表示它不再引用任何事物,但还是可以被指定引用其他对象的引用变量。


构造器

构造器:用来创建实例,或叫构造方法(constructor)的使用。

作用:用来创建对象及初始化信息。

构造器例子如下:

class person{
    //显示定义空参构造器
    public person() {
    }
}

注意点

  1. 创建一个类,会默认提供空参构造器,自己也可以显示定义一个空参构造器。
  2. 默认的空参构造器权限与本身类的权限相同。
  3. 也可以定义多参构造器,但是一旦定义多参构造器,系统将不会定义空参构造器,所以还应当重载一个无参数列表的构造器(空参构造器)。
  4. 开发时,操作数据库时,大多使用的是空参构造器,使用其中set方法进行赋值,需要注意一下。


JavaBean说明

JavaBean:是一种Java语言写成的可重用组件。

包含以下特征

  • 类是公共的
  • 包含无参构造器
  • 有属性,并且包含对应的set/get方法

满足上述特征及条件的就叫做JavaBean。



UML类图

类似如下

  • 上面框没有()的表示为属性,:后的表示返回类型。
  • 下面框中若是有下划线的表示为构造器,没有的表示为方法,:后表示返回类型。

可以根据UML图来进行编写Java代码



this关键字

this:表示当前对象或者当前正在创建的对象。

使用方式:

  • 在类内可以调用属性及方法,例如:this.属性this.方法
  • 有参构造器中使用this();方式调用构造器。
  • this可以作为当前实例返回,也可将其传到方法参数中。


三、内存区域

java的内存区域见下图

image-20210124233037784

  • 堆(Heap):内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存空间。在Java虚拟机规范中的描述是:所有的对象实例以及数组都在堆上分配
  • 栈(Stack):指虚拟机用栈存储局部变量等,局部变量表中存放了编译器可知长度的各种基本数据类型(就是这些值直接在栈中存放)对象引用(对象在堆内存放的首地址)。方法执行完之后自动释放。
  • 方法区(Method Area):包含了常量池与静态域,用来存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
    • image-20210124234116822


四、对象数组

对象数组中也只会保存引用,而不是对象本身。无论数组包含什么,它一定是对象!

一旦数组被声明出来,就只能装入所声明的类型,但是对于基本类型byte可以放到int数组中。—《head first java 2.0》 P59

对象数组:创建对象数组后,仅为数组开辟内存空间,其每个对象都没有都没有分配地址都为null,与二维数组不指定后面[]中数字相同(例如:int[][] ids = new int[5][]),没有给实际位置分配空间。

见下面例子:

class Person{
    private int age;
    private String name;
}

Person[] pers = new Person[2];,内存空间如下:

image-20210125093526028

说明:只创建数组时是没有开辟内存空间的,我们需要进行额外开辟对象的内存空间才能对其中对象进行操作。例如:pers[0] = new Person();


对象数组的内存解析

这边直接使用尚硅谷课件中的一张图:还是比较明确的

image-20210125094059945



五、匿名对象

匿名对象:没有名的实例对象

【1】创建一次性匿名对象

class person{
    public void eat(){
        System.out.println("吃饭啦");
    }
}

public class Main{
    public static void main(String[] args){
        new Person().eat();//匿名对象
    }
}

说明:为什么要叫做匿名对象呢?对象在堆中创建好之后,栈中没有变量指向这个内存空间地址,自然而然下次就无法找到并操作这个对象了。

【2】方法传参匿名参数:

public class Main {
    public static void main(String[] args) throws UnknownHostException {
        //传递匿名参数
        Main.usePerson(new Person());
    }
    
    public static void usePerson(Person person){
        person.eat();
    }
}

class Person{
    public void eat(){
        System.out.println("吃饭啦");
    }
}

实际上就是将这个内存地址传递给了方法参数中的person来接收,但仅局限在方法中进行使用该对象。一般是我只想调用一次方法并不想额外创建变量名称,就可以使用这种方式传参。



六、值传递与地址传递

概念介绍

方法中的参数叫做形式参数,调用方法进行传参的叫做实际参数。

  • 形式参数:又叫形参,方法定义时,声明的小括号内的参数。
  • 实际参数:又叫实参,实际传递给形参的数据。

方法中传参包含值传递与地址传递

  • 值传递:方法中传递参数为基本数据类型,进行值拷贝
  • 地址传递:方法中传递参数为引用数据类型

实际案例说明

值传递案例

传递内容:基本数据类型int

public class Main {
    public static void main(String[] args) throws UnknownHostException {
        int i = 10;
        int j = 20;
        System.out.println("值传递前:i="+i+" "+"j="+j);//地址传递前:i=10 j=20
        Main.swap(i,j);
        System.out.println("值传递后:i="+i+" "+"j="+j);//地址传递后:i=10 j=20
    }
	
    //交换i与j值
    public static void swap(int i,int j){
        int sum = i+j;
        i = sum - i;
        j = sum - i;
    }
}

结果:可以看到方法中进行值传递调用前后的值都没有进行交换。

详细说明

      基本数据类型存放的值称为自动变量,自动变量存储的值是字面值,与在堆中创建类的实例与数组开辟内存空间不同,每当栈中定义了一个自动变量时,会直接赋一个字面值,下一次若定义一个自动变量时会首先去栈中找是否有相同的字面值,若是有直接指向这个字面值。与引用类型引用地址有所不同。

      回到上面代码中,这里是值传递,仅仅是传递字面值(非地址引用),那么在其方法中对i与j操作就与外面操作i与j就无任何关系。所以前后值肯定没有变。


地址传递案例

传递内容:引用数据类型,数组

public class Main {
    public static void main(String[] args){
       int[] arr = {1,2};
   	   System.out.println("地址传递前:arr[0]="+arr[0]+" "+"arr[1]="+arr[1]);//地址传递前:arr[0]=1 arr[1]=2
       Main.swap(arr,0,1);
       System.out.println("地址传递后:arr[0]="+arr[0]+" "+"arr[1]="+arr[1]);//地址传递后:arr[0]=2 arr[1]=1
    }

    //交换数组arr中下标为i与j的值
    public static void swap(int[] arr,int i,int j){
        int x = arr[i];
        arr[i] = arr[j];
        arr[j] = x;
    }
}

结果:使用地址传递,数组中指定i与j下标位置值发生了调换。

详细说明

      方法中传递的是引用数据类型,传到形参中的arr中的实际上就是实参arr的引用地址,那么顺理成章形参就能够进行操控这个数组进行指定位置的交换操作,回到main方法中,因为引用的都是相同地址,确确实实进行了交换,那么就如结果一样了。


地址传递(特殊),包装类

传递内容:包装类,为啥说特殊呢,因为传递过去进行交换值,最终结果并不是我所看到的

public static void main(String[] args){
    Integer i = 11;
    Integer j = 12;
    int a = 10;
    System.out.println("地址传递前:i="+i+" "+"j="+j);//地址传递前:i=11 j=12
    Main.swap(i,j);
    System.out.println("地址传递后:i="+i+" "+"j="+j);//地址传递后:i=11 j=12
}

public static void swap(Integer i,Integer j){
    Integer sum = i+j;
    i = sum-i;
    j = sum-i;
}

结果:Integer明明是引用数据类型,最终输出展示却并没有交换。

详细说明

针对于包装类,首先得知道装箱拆箱,下面直接上例子:

  • Integer i = 11:将一个字面值直接赋值给Integer包装类,会有装箱操作,其中包含了隐含的操作就是Integer i = Integer.valueOf(11);
  • int j = i:直接使用上面包装类i给基本类型赋值,会有拆箱的操作,包含隐含的操作就是i.intValue();

      接着我们进行分析方法中的操作,首先第10行,我们将引用数据类型传递给形参i与形参j,当前状态下形参i、j与实参i、j都引用相同的内存地址,接下来注意看下面旁边的注释:

public static void swap(Integer i,Integer j){
    Integer sum = i+j;
    i = sum-i;//隐藏装箱操作:i = Integer.valueOf(sum-i);
    j = sum-i;//隐藏装箱操作:i = Integer.valueOf(sum-i);
}

      这里我们能看到此时形参i与j又进行了不同的内存地址引用,简单说就是它换了个内存地址,并不是说它将以前是实参的引用内存地址中的value值进行更改了。(况且Integer源码中value值为final,常量无法修改)。

Debug

      说了一堆,我们再进行debug就更加明了,首先我们看下在main方法中包装类i与j的引用地址:

image-20210125114206085

      此时是刚进入到swap方法中,看一下形参i与j的引用地址,发现引用地址与实参的相同,确实是地址传参

image-20210125114611108

      我们执行到第28行,可以看到形参i与j仅仅是引用内存地址发生了改变,此时更加明朗了。

image-20210125145310236

此时我又抛出一个疑问,这个内存地址咋这么眼熟啊,咋Integer包装类相同数值对应的引用内存地址一样呢???

难道是字面值相同,引向同一个地址嘛,不对不对,字面值又没有在堆中开辟空间,又哪里来的内存地址呢,有了这些个疑问,我们点开源码一探究竟,点开Integer的valueOf()方法:

image-20210125145940463

  • IntegerCache:Integer包装类的静态内部类
  • Integer.cache:static final Integer cache[];内部类的一个静态常量一维数组,数组会提前存放好对象。下标0开始依次放置-128的包装类到127。
  • low:static final int low = -128;静态整型常量值为-128。

这里我简单描述一下即可,当调用valueOf()方法时,会先判断传入的int数字是否在-128-127之间,如果是的话直接从cache数组中拿即可,若超过范围,就new一个对象出去。

      此时我们解开了为啥内存引用地址与之前相同的疑惑,本来只是想记录一下地址传递的知识点,偶然使用了Integer包装类,偶然发现了自动装箱过程同样是new对象,又偶然发现Integer的缓存数组,让我体会到每个知识点都能够深挖,就看你有没有这个耐心与不断钻研的兴趣。



MVC设计模式

经典MVC模式中,M是指业务模型,V是指用户界面,C则是控制器,使用MVC的目的是将M和V的实现分离。

不同层做的事以及定义的包名

image-20210125171132998

整个流程

框架内容介绍

强制性的使应用程序的输入、处理与输出分开,将MVC应用程序分为三个核心部件:模型、视图、控制器。

最经典的MVC就是JSP+servlet+javaBean,也就是初始阶段学习的javaweb。

  • JSP相当于view视图(前端视图显示)
  • servlet相当于controller控制器(获取请求,处理业务)
  • javabean相当于model(会向数据库查询封装数据到javabean中)

框架包含模式:工厂模式、适配器模式、策略模式等。

常见框架如:ssm(spring,springmvc,mybatis)、ssh(spring,struts,hibernate)、springboot(简化spring与springmvc,提供各种应用场景及启动器)。

简而言之:框架是大智慧,用来对软件设计进行分工;设计模式是小技巧,对具体问题提出解决方案,以提高代码复用率,降低耦合度。

优缺点

  • 优点:耦合度低、重用性高、部署快、生命周期成本低、可维护性高
  • 缺点:理解MVC复杂、调试困难、不适合小型,中等规模的应用程序、系统结构与实现的复杂度高


参考资料

[1]. Java虚拟机(JVM)的方法区(Method Area)存储了什么内容? 详细信息可查阅博客

[2]. Java基本数据类型与引用数据类型内存分配 对基本数据类型与引用数据类型的产生与内存分配有很好的总结,建议一看

[3]. Integer的自动装箱过程

[4]. 包装类,通过自动装箱后部分对象地址值相同的问题

[5]. 百度百科—MVC设计模式


我是长路,感谢你的阅读,如有问题请指出,我会听取建议并进行修正。
欢迎关注我的公众号:长路Java,其中会包含软件安装等其他一些资料,包含一些视频教程以及学习路径分享。
学习讨论qq群:891507813 我们可以一起探讨学习
注明:转载可,需要附带上文章链接

posted @ 2021-02-20 19:48  长路  阅读(105)  评论(0编辑  收藏  举报