运用数据抽象的思想编写代码(定义和使用数据类型,将数据类型的值封装在对象中)的方式称为面向对象编程。
数据类型指的是一组值和一组对值的操作的集合。对象是能够存储任意该数据类型的值的实体,或数据类型的实例。

类与对象的关系

image.png
从类创建对象,有好几种说法——1. 创建一个对象。2. 实例化一个对象。3. 把类实例化。
image.png

对象内存布局

image.png
首先会在方法区里加载类的信息。然后对象的地址存放在栈中,真正的对象放在堆里如果对象里的属性是基本数据类型,那么就存放在堆里,如果是引用数据类型,那么在堆中存放地址,数据存放在方法区里
从概念或叫法上看:成员变量 = 属性 = 字段(field),属性是类的一个组成部分,一般是基本数据类型,也可是引用数据类型(对象,数组等)。
类里的方法也叫实例方法,类里的属性也叫实例变量。
image.png

内存

堆区只存放类对象,线程共享,类中的成员变量都存放在堆区。
方法区:又叫静态存储区,存放class文件和静态数据,线程共享。
栈区存放方法的局部变量,基本类型变量区。线程不共享。

对象创建流程

  1. class Person {
  2. int age = 90;
  3. String name;
  4. Person(String n, int a) {
  5. name = n;
  6. age = a;
  7. }
  8. }
  9. //主函数 Person p = new Person("小王",20);
  1. 加载Person类信息,只会加载一次(具体可以在反射中查看)。
    2. 在堆中分配空间(地址)。
    3. 完成对象初始化——(1)默认初始化,age = 0,name = null。(2)显式初始化,age = 90,name = null。(3)构造器的初始化,age = 20,name = 小王。
    4. 把对象在堆中的地址,返回给p(p是对象名,也可以理解成是对象的引用)。
    image.png name中的值在常量池中

    对象的三个主要特性

    行为:可以对对象完成哪些操作,或者可以对对象应用哪些方法。同一个类的所有对象实例,由于支持相同的行为而具有家族式的相似性。
    状态:描述当前状况的信息。对象状态的改变必须通过调用方法实现。
    标识:每个对象都有一个唯一的标识,用于区分具有相同行为与状态的不同对象(比如在订单系统中,即使两个订单货物完全相同,那也是不同的订单)。

    类之间的关系

    依赖(uses-a)如果一个类的方法使用或操纵另一个类的对象,我们就说一个类依赖于另一个类。应该尽可能地将相互依赖的类减至最少。
    聚合(has-a)类A的对象包含类B的对象。
    继承(is-a)如果类A继承类B,那么类A不但包含从类B继承的方法,还会有一些额外功能。(比如有一个RushOrder类,继承自Order类,包含了一些用于优先处理的特殊方法,而其他的一般性方法都继承自Order类)

    包的作用

    1. 区分相同名字的类,可以在不同的包中定义相同名字的类。
    2. 当类很多时,可以很好的管理类(类似Java API文档)。
    3. 控制访问范围。

    基本语法

    1. package com.hspedu;
    其中package为关键字,表示引入包。com.hespedu表示包名。

    本质分析

    包实际上就是创建不同的文件夹来保存类文件。
    image.png
    创建一个包:
    image.png
    image.png
    image.png com.xiaoming,其中这个 ‘.’ 表示分级,com为一级目录,xiaoming为二级目录。
    image.png
    举例:
    1. import com.shang.Person;
    2. public class HelloWorld {
    3. public static void main(String[] args) {
    4. Person a = new Person(); //导入的可以直接使用
    5. com.xiaoming.Person b = new com.xiaoming.Person();
    6. //没有导入就要写全名字
    7. a.showName();
    8. b.showName();
    9. }
    10. }
    image.png

    去除import后自动隐藏

    image.png
    把 “Optimize imports on the fly” 取消即可。

    命名

    命名规则:
    只能包含数字、字母、下划线、小圆点,但不能用数字开头,不能是关键字或保留字。
    命名规范:
    com.公司名.项目名.业务模块名,全部用小写字母。
    1. com.sina.crm.uese; //用户模块
    2. com.sina.crm.order; //订单模块
    3. com.sina.crm.utils; //工具类

    常用包

    image.png

    包的使用细节

  2. package语句的作用是声明当前类所在的包需要放在类的最上面,一个类中最多只有一句package
    2. import指令放在package的下面,在类定义前面,可以有多句并且没有顺序要求。我们用 import 引入包,主要是为了使用包中的类。不建议使用 * 引入全部的类。

    访问修饰符

    访问范围

    image.png
    image.png
    注意子类说的是不同包里的子类,同一个包里的子类是可以访问父类的默认属性的。
    1. public class A{
    2. protected String name = "ss"; //保护
    3. String age = "18"; //默认
    4. }
    5. //在不同包
    6. public class B extends A{
    7. public static void main(String[] args){
    8. B b = new B(); //可以访问 name, 但是不可以访问 age
    9. }
    10. }

    注意事项

  3. 修饰符可以用来修饰类中的 属性,成员方法以及类。成员方法与属性都符合上图的范围。
    2. 只有默认和public才能修饰类默认类只有在同一个包中才能调用,在不同包中,即使使用了import导入该类,也不能用而public修饰的类不仅在本包中可以调用,也可以在不同包中进行调用,但是需要import导入类

    按值调用与按引用调用

    按值调用表示方法接收的是调用者提供的值按引用调用表示方法接收的是调用者提供的变量地址。 方法可以修改按引用传递变量的值,而不能修改按值传递变量的值(其实就是C++的形参与实参的关系,引用就是加上了&)。

按值调用

假如在Employee类中加入一个方法——

  1. public void add(int x) {
  2. x += 10;
  3. }

然后在主函数中调用,分析一下过程:1.x初始化为num的一个副本(形参,也就是10) 2.x加上10后等于20,但是num仍为10。 3.方法结束后,参数变量x不再使用。

  1. public static void main(String[] args) {
  2. int num = 10;
  3. Employee a = new Employee(10);
  4. System.out.println("调用add函数前为:"+num);
  5. a.add(num);//这个就是按值调用
  6. System.out.print("调用add函数后为:"+num);
  7. }

image.png

按引用调用

给Employee类增加另外一个方法,这时传入一个类。

  1. public void add(Employee one) {
  2. one.id += 10;
  3. }

在主函数进行调用——1.one初始化为a的一个副本,这里就是一个对象引用(其实就是one初始化为与a相同的指针)。 2.add方法应用于这个对象引用。one和a同时引用一个Employee对象。 3.方法结束后,参数变量one不再使用,但是a做出了修改。

  1. public static void main(String[] args) {
  2. Employee a = new Employee(10);
  3. System.out.print("调用add函数前——");
  4. a.showId();
  5. a.add(a);
  6. System.out.print("调用add函数后——");
  7. a.showId();
  8. }

image.png
下面这个例子更能体会到按引用调用:传入实参x,y,swap会创造形参 x,y,与实参指向的对象相同,然后执行代码。我们可以发现,全程就是形参xy在互换指向的对象(理解为指针),最终形参x指向实参y指向的对象,形参y指向实参x指向的对象。但是完全没有影响到实参。

  1. public static void swap(Employee x,Employee y) {
  2. Employee temp = x;
  3. x = y;
  4. y = temp;
  5. }

其实只要把对象参数理解为指针就OK了,它并不是真正的对象,只是指向对象,因此可以修改对应的属性,但是不能交换等。

类的设计技巧

1.一定要保证数据私有。2.一定要对数据进行初始化(最好不要依赖于系统的默认值)。 3.不要在类中使用过多的基本类型。 4.不是所有的字段都需要单独的字段访问器和字段更改器。5.分解有过多职责的类。 6.类名和方法名要能够体现它们的职责。 7.优先使用不可变的类。

this关键字

图示

  1. Dog dog1 = new Dog("大壮",3);
  2. Dog dog2 = new Dog("大黄",2);

image.png
简单地说,哪个对象调用this,this就代表哪个对象。

使用细节

  1. this关键字可以用来访问本类的属性、方法、构造器
    2. this可以用于区分当前类的属性和局部变量(起到一个定位作用)。
    1. class Person {
    2. String name = "milan";
    3. public void showName() {
    4. String name = "shang";
    5. System.out.println(name);
    6. //输出局部变量 "shang" (就近原则)
    7. System.out.println(this.name);
    8. //输出属性 "milan"
    9. }
    10. }
    3. 访问成员方法的语法:this.方法名(参数列表); (在方法中访问)。
    4. 访问构造器语法:this(参数列表),注意只能在一个构造器中访问另一个构造器,并且必须放在第一句中。
    1. class Person {
    2. Person() {
    3. this("wang");
    4. //调用另一个构造器
    5. }
    6. Person(String name) {
    7. this.name = name;
    8. }
    9. }
    10. //下面的构造器会报错
    11. Person() {
    12. System.out.println("不放在第一句");
    13. this("wang");
    14. }
    image.png
    5. this不能在类定义的外部(比如本类的main方法)使用,只能在类定义的方法中使用。 ```java public static void main(String[] args) { Person a = new Person(); this.name; //在主函数中调用this,报错
    }
  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/23175776/1641709219331-a0a6f735-b32b-4ae3-aea3-986ca77f8937.png#clientId=uc103dc5c-cf76-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=ufbf1e9ae&margin=%5Bobject%20Object%5D&name=image.png&originHeight=286&originWidth=1153&originalType=url&ratio=1&rotation=0&showTitle=false&size=49070&status=done&style=none&taskId=u9ebaad25-c790-455c-a183-7fda85607b0&title=)
  2. <a name="dfgu0"></a>
  3. ## **super关键字**
  4. <a name="mJ2T4"></a>
  5. ### **基本介绍**
  6. super代表父类的引用,用于访问父类的属性、方法、构造器。
  7. <a name="yNS7G"></a>
  8. ### 基本语法
  9. **1. 访问父类的属性,但不能访问父类的private属性:super.属性名;**<br />**2. 访问父类的方法,但不能访问父类的private方法:super.方法名(参数列表);**<br />3. 访问父类的构造器(前面介绍过):super(参数列表); 只能放在构造器的第一句,只能出现一句
  10. <a name="vfZBC"></a>
  11. ### 细节/好处
  12. 1. **super调用父类构造器,可以使分工明确**。父类属性由父类初始化,子类的属性由子类初始化。 <br />2. **当子类中由和父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super**。**如果没有重名,使用superthis、直接访问是一样的效果(只不过this和直接访问是从本类开始找,super是从父类开始找)。 调用类的属性的规则在上面已经说明过了,调用类的方法的规则与其相同,只不过把属性换成了方法。**<br />**子类确实可以写一个与父类完全相同的方法,虽然左边会有隐患提示,但是编译没有问题。**<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/23175776/1641715307453-4c1c47de-d718-4d2a-96a8-4d26cab0ceee.png#clientId=u4749a98d-e8f1-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=uca9155c9&margin=%5Bobject%20Object%5D&name=image.png&originHeight=234&originWidth=1277&originalType=url&ratio=1&rotation=0&showTitle=false&size=47830&status=done&style=none&taskId=uac8946e5-f9e3-4084-b1cf-e5810566163&title=)<br />**下面对于子类调用父类的方法举一个例子(属性因为很简单就不举例了)**<br />定义一个父类Person:
  13. ```java
  14. public class Person { //有四个不同修饰符的方法
  15. public void showName1(){
  16. System.out.println("调用Person的public方法");
  17. }
  18. void showName2(){
  19. System.out.println("调用Person的无修饰符方法");
  20. }
  21. protected void showName3(){
  22. System.out.println("调用Person的protected方法");
  23. }
  24. private void showName4(){
  25. System.out.println("调用Person的private方法");
  26. }
  27. }

定义一个子类Student,先测试在main方法中调用方法(必须用声明的变量调用):

  1. public class Student extends Person{
  2. public static void main(String[] args) {
  3. Student student = new Student();
  4. student.showName1(); //成功调用
  5. student.showName2(); //成功调用
  6. student.showName3(); //成功调用
  7. student.showName4(); //报错,不能调用父类的private方法
  8. }
  9. }

image.pngimage.png
然后在子类的方法中继续测试,由于 this 和 super 都不能在主方法中调用,因此声明一个test方法,在该方法中测试this和super。

  1. public void test(){
  2. this.showName1(); //成功调用
  3. this.showName2(); //成功调用
  4. this.showName3(); //成功调用
  5. this.showName4(); //报错,不能调用private方法
  6. super.showName1(); //成功调用
  7. super.showName2(); //成功调用
  8. super.showName3(); //成功调用
  9. super.showName4(); //报错,不能调用private方法
  10. }

当然,该方法还是需要用main方法中声明的变量调用。
image.png 同样的,最重要的一点——在子类调用一个方法时,如果子类没有该方法,会一直向上寻找如果找到Object类还没有找到,就会报错(super直接忽略本类)如果在中间找到了一个private的方法,即使上面还有该方法,也不会再寻找了,而是直接报错
举一个例子:

  1. private void showName1(){ //在Student类中加入一个与Person类同名但不同修饰符的函数 System.out.println("调用Student的private函数");
  2. }
  1. public class Pupil extends Student{ //继承Student
  2. public static void main(String[] args) {
  3. Pupil pupil = new Pupil();
  4. pupil.showName1(); //直接报错,寻找到Student就停了,而不是继续向Person寻找
  5. }
  6. }

image.png
3. 如果多个父类中有相同的成员,那么super的访问根据就近原则。
4. super和this的区别
image.pngimage.png

方法重写/覆盖(override)

基本介绍

方法覆盖就是子类有一个方法和父类的某个方法 名称、返回类型、参数一样,这样我们就说子类的这个方法覆盖了父类的那个方法。

使用细节

1. 子类方法的 形参列表,方法名称 要和父类的完全一样。
2. 子类方法的 返回类型 可以和父类方法的返回类型一样,或者是父类返回类型的子类(必须是自己写的继承关系,Object那种自带的继承不算)。

  1. public Object showName(){
  2. System.out.println("返回值为Object的父类方法");
  3. return "OK";
  4. } //父类的方法
  5. public String showName(){
  6. System.out.println("返回值为String的子类方法");
  7. return "OK";
  8. } //子类的方法
  9. public static void main(String[] args) {
  10. Student student = new Student();
  11. student.showName(); //调用
  12. }

image.pngimage.png

  1. public String showName(){
  2. System.out.println("返回值为Object的父类方法");
  3. return "OK";
  4. }
  5. public Object showName(){
  6. System.out.println("返回值为String的子类方法");
  7. return "OK";
  8. } //报错

image.png
3.子类方法不能缩小父类方法的访问权限(但扩大可以)public > protected > 默认 > private

  1. public void showName(){
  2. System.out.println("修饰符为public的父类方法");
  3. }
  4. protected void showName(){
  5. System.out.println("修饰符为protected的子类方法");
  6. } //报错,减小了范围

image.pngimage.png
4. 重载和重写的比较
image.pngimage.png