学习面向对象内容的三条主线

  • Java类及类的成员:(重点)属性、方法、构造器;(熟悉)代码块、内部类
  • 面向对象的特征:封装、继承、多态、(抽象)
  • 其他关键字的使用:this、super、package、import、static、final、interface、abstract等

面向对象编程概述

程序设计的思路

面向对象,是软件开发中的一类编程风格、开发范式。除了 面向对象 ,还有 面向过程 、 指令式编程 和 函数式编程 。在所有的编程范式中,我们接触最多的还是面向过程和面向对象两种。

  1. 面向过程的程序设计思想(Process-Oriented Programming),简称 POP

    关注的焦点是 过程 :过程就是操作数据的步骤。如果某个过程的实现代码重复出现,那么就可以 把这个过程抽取为一个 函数 。这样就可以大大简化冗余代码,便于维护。

    典型的语言:C语言

    代码结构:以函数 为组织单位。

    是一种“ 执行者思维 ”,适合解决简单问题。扩展能力差、后期维护难度较大。

  2. 面向对象的程序设计思想( Object Oriented Programming),简称 OOP

    关注的焦点是 类 :在计算机程序设计过程中,参照现实中事物,将事物的属性特征、行为特征抽 象出来,用类来表示。

    典型的语言:Java、C#、C++、Python、Ruby和PHP等

    代码结构:以 为组织单位。每种事物都具备自己的 属性 和 行为/功能 。

    是一种“ 设计者思维 ”,适合解决复杂问题。代码扩展性强、可维护性高。

造车太复杂,需要 很多协作 才能完成。此时我们思考的是“ 车怎么设计? ”,而不是“怎么按特定步骤造 车的问题”。这就是思维方式的转变,前者就是面向对象思想。所以,面向对象(Oriented-Object)思想更 契合人的思维模式。车由很多结构组成我们找轮胎厂完成制造轮胎的步骤,发动机厂完成制造发动机的步骤,…;这样,大家可以同时进行车 的制造,最终进行组装,大大提高了效率。但是,具体到轮胎厂的一个流水线操作,仍然是有步骤的, 还是离不开面向过程思维! 因此,面向对象可以帮助我们从宏观上把握、从整体上分析整个系统。 但是,具体到实现部分的微观操 作(就是一个个方法),仍然需要面向过程的思路去处理。

人把大象装进冰箱

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
面向过程

1.打开冰箱
2.把大象装进冰箱
3.把冰箱门关住

面向对象
{
	打开冰箱{
		冰箱.开门();
	}
	操作(大象){
		大象.进入(冰箱);
	}
	关闭(冰箱){
		冰箱.关门();
	}
}

冰箱{
	开门(){ }

	关门(){ }
}

大象{
	进入(冰箱){ }
}

Java语言的基本元素:类和对象

抽象出来的美人鱼的特征,可以归纳为一个 美人鱼类 。而图 片中的都是这个类呈现出来的 具体的对象 。

类和对象概述

类(Class) 和 对象(Object) 是面向对象的核心概念。

  • 类:具有相同特征的事物的抽象描述,是 抽象的 、概念上的定义。
  • 对象:实际存在的该类事物的 每个个体 ,是 具体的 ,因而也称为 实例(instance) 。
  • 可以理解为: 类 => 抽象概念的人 ; 对象 => 实实在在的某个人

类的成员概述

面向对象程序设计的重点是 类的设计

类的设计,其实就是 类的成员的设计

类,是一组相关 属性行为 的集合,这也是类最基本的两 个成员。

  • 属性:该类事物的状态信息。对应类中的 成员变量

    成员变量 <=> 属性 <=> Field

  • 行为:该类事物要做什么操作,或者基于事物的状态能做什么。对应类中的 成员方法

    (成员)方法 <=> 函数 <=> Method

面向对象完成功能的三步骤

  • 类的定义

    创建类,并设计类的内部成员(属性、方法)

  • 创建类的对象

    类的实例化 <=> 创建类的对象 <=> 创建类的实例对象

  • 调用属性或方法

    通过对象,调用其内部声明的属性或方法,完成相关的功能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Person {

    //属性
    String name;//姓名
    int age;//年龄
    char gender;//性别


    //方法
    public void eat(){
        System.out.println("人吃饭");
    }

    public void sleep(int hour){
        System.out.println("人至少保证明天" + hour + "小时的睡眠");
    }

    public void interests(String hobby){
        System.out.println("我的爱好是:" + hobby);
    }
    
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class PersonTest {
    public static void main(String[] args) {

        //创建对象、类的实例化
        Person p1 = new Person();

        //通过对象调用属性或方法
        p1.name = "杰克";
        p1.age = 24;
        p1.gender = '男';

        System.out.println("name = " + p1.name + ",age = " + p1.age +
                ", gender = " + p1.gender);

        p1.eat();
        p1.sleep(8);
        p1.interests("画画");

        //再创建Person类的一个实例
        Person p2 = new Person();
        p2.name = "露丝";
        p2.age = 18;
        p2.gender = '女';


        System.out.println("name = " + p2.name + ",age = " + p2.age +
                ", gender = " + p2.gender);

        System.out.println("name = " + p1.name + ",age = " + p1.age +
                ", gender = " + p1.gender);
        new  Person().sleep(8);

    }
}
name = 杰克,age = 24, gender = 
人吃饭
人至少保证明天8小时的睡眠
我的爱好是画画
name = 露丝,age = 18, gender = 
name = 杰克,age = 24, gender = 
人至少保证明天8小时的睡眠

内存解析

堆(Heap) :此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一 点在Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。new出来的结构 ,包括对象中的属性

栈(Stack) :是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的各 种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类 型,它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。

方法区(Method Area) :用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

创建类的多个对象时,每个对象在堆空间中有一个对象实体。每个对象实体中保存着一份类的属性。 如果修改某一个对象的某属性值时,不会影响其他对象此属性的值。

p1,p3 两个变量指向了堆空间中的同一个对象实体。(或p1,p3保存的地址值相同) 如果通过其中某一个对象变量修改对象的属性时,会影响另一个对象变量此属性的值。

对象名中存储的是什么呢? 答:对象地址

直接打印对象名和数组名都是显示“类型@对象的hashCode值",所以说类、数组都是引用数据类型,引 用数据类型的变量中存储的是对象的地址,或者说指向堆中对象的首地址。

1
2
3
4
5
6
7
		Person p1 = new Person();
        System.out.println(p1);
        int[] arr =new int[5];
        System.out.println(arr);

com.kong.core.Person@4eec7777
[I@3b07d329

类的成员之一:属性field

属性的几个称谓:成员变量、属性、field(字段、域)

变量的分类:

  • 角度一:按照数据类型来分:基本数据类型(8种)、引用数据类型(数组、类、接口、枚举、注解、记录)
  • 角度二:按照变量在类中声明的位置的不同:成员变量(或属性)、局部变量(方法内、方法形参、构造器内、构造器形参、代码块内等)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class FieldTest {
    public static void main(String[] args) {

        Person p1 = new Person();

        System.out.println(p1.name + "," + p1.age);


        p1.sleep(8);

        p1.eat();

    }
}

class Person{
    //属性(或成员变量)
    String name;
    int age;
    char gender;


    //方法
    public void eat(){
        String food = "宫保鸡丁"; //局部变量
        System.out.println("我喜欢吃" + food);
    }

    public void sleep(int hour){ //形参:属于局部变量
        System.out.println("人不能少于" + hour + "小时的睡眠");

        //编译不通过,因为超出了food变量的作用域
//        System.out.println("我喜欢吃" + food);

        //编译通过。
        System.out.println("name = " + name);
    }

}


一般需要一个类就新建一个class文件

区分成员变量 vs 局部变量

相同点:

  • 变量声明的格式相同:数据类型 变量名 = 变量值
  • 变量都有其有效的作用域。出了作用域,就失效了。
  • 变量必须先声明,后赋值,再使用。

不同点:

① 类中声明的位置的不同: 属性:声明在类内,方法外的变量 局部变量:声明方法、构造器内部的变量

② 在内存中分配的位置不同: 属性:随着对象的创建,存储在堆空间中。 局部变量:存储在栈空间中

③ 生命周期: 属性:随着对象的创建而创建,随着对象的消亡而消亡。 局部变量:随着方法对应的栈帧入栈,局部变量会在栈中分配;随着方法对应的栈帧出栈,局部变量消亡。

④ 作用域: 属性:在整个类的内部都是有效的 局部变量:仅限于声明此局部变量所在的方法(或构造器、代码块)中

⑤ 是否可以有权限修饰符进行修饰:(难) 都有哪些权限修饰符:public、protected、缺省、private。(用于表明所修饰的结构可调用的范围的大小)

属性,是可以使用权限修饰符进行修饰的。 暂时还未讲封装性,所以大家先不用写任何权限符。 而局部变量,不能使用任何权限修饰符进行修饰的。

⑥ 是否有默认值:(重点) 属性:都有默认初始化值。意味着,如果没有给属性进行显式初始化赋值,则会有默认初始化值。

​ 局部变量:都没有默认初始化值。意味着,在使用局部变量之前,必须要显式的赋值,否则报错。注意:对于方法的形参而言,在调用方法时,给此形参赋值即可。

对象属性的默认初始化赋值

当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。

角度二:从下而上

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class MyDate {

    int year; //年
    int month; //月
    int day; //日


}
public class Employee {

    int id; //编号
    String name; //姓名
    int age; //年龄
    double salary; //薪资
    MyDate birthday; //生日,自己生命的类作为变量

}
public class EmployeeTest {
    public static void main(String[] args) {

        //创建Employee的一个实例
        Employee emp1 = new Employee();

        emp1.id = 1001;
        emp1.name = "杰克"; //emp1.name = new String("杰克");
        emp1.age = 24;
        emp1.salary = 8900;

        emp1.birthday = new MyDate();//emp1.birthday存放的mydate类的地址
        emp1.birthday.year = 1998;
        emp1.birthday.month = 2;
        emp1.birthday.day = 28;
        /*
        另一种写法:
        * MyDate mydate1 = new MyDate();
        * emp1.birthday = mydate1;
        * */

        //打印员工信息
        System.out.println("id = " + emp1.id + ",name = " + emp1.name +
                ", age = " + emp1.age + ", salary = " + emp1.salary +
                ", birthday = [" + emp1.birthday.year + "年" + emp1.birthday.month +
                "月" + emp1.birthday.day + "日]");


    }
}

类的成员之二:方法(method)

  • 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为 函数 或 过程 。
  • 将功能封装为方法的目的是,可以 实现代码重用,减少冗余,简化代码
  • Java里的方法不能独立存在 ,所有的方法必须定义在类里。

声明方法

1
2
3
[修饰符] 返回值类型 方法名([形参列表])[throws 异常列表]{
	方法体的功能代码
}
  • 一个完整的方法 = 方法头 + 方法体。
  • 方法头就是 [修饰符] 返回值类型 方法名([形参列表])[throws 异常列表] ,也称为 方法签名 。通 常调用方法时只需要关注方法头就可以,从方法头可以看出这个方法的功能和调用格式。
  • 方法体就是方法被调用后要执行的代码。对于调用者来说,不了解方法体如何实现的,并不影响方 法的使用。
  1. 权限修饰符: private \ 缺省 \ protected \ public

  2. 返回值类型:描述当调用完此方法时,是否需要返回一个结果。

    无返回值类型:使用void表示即可。

    比如:System.out.println(x)的println(x)方法、Arrays的sort() 有具体的返回值类型:需要指明返回的数据的类型。

    可以是基本数据类型,也可以引用数据类型 需要在方法内部配合使用"return + 返回值类型的变量或常量" 如:Math.random()、new Scanner(System.in).nextInt()等

  3. 方法名:属于标识符。需要满足标识符的规定和规范。“见名知意”

  4. 形参列表:形参,属于局部变量,且可以声明多个。 格式:(形参类型1 形参1,形参类型2 形参2,…) 无形参列表:不能省略一对()。比如:Math.random()、new Scanner(System.in).nextInt()

    有形参列表:根据方法调用时,需要的不确定的变量的类型和个数,确定形参的类型和个数。 比如:Arrays类中的binarySearch()方法、sort()方法、equals()方法

  5. 方法体: 当我们调用一个方法时,真正执行的代码。体现了此方法的功能

Java里的方法不能独立存在,所有的方法必须定义在类里。 Java中的方法不调用,不执行。每调用一次,就执行一次。 方法内可以调用本类中的(其它)方法或属性 方法内不能定义方法。

方法区(Method Area):用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的 代码等数据。

Person p1 = new Person();

创建对象p1之前,person类的信息已经先加载到方法区中了,所有方法内可以调用其他本类中的方法。

关键字:return

return的作用 - 作用1:结束一个方法 - 作用2:结束一个方法的同时,可以返回数据给方法的调用者 (方法声明中如果有返回值类型,则方法内需要搭配return使用)

使用注意点: return后面不能声明执行语句。

方法调用的内存解析

  • 形参:方法在声明时,一对()内声明的一个或多个形式参数,简称为形参。
  • 实参:方法在被调用时,实际传递给形参的变量或常量,就称为实际参数,简称实参。

每一个方法对应一个栈帧,栈帧是栈中的基本结构,调用一个main方法,就是一个栈帧入栈。

方法执行完毕之后就出栈。

引用类型的变量存放的是地址,string数据放在堆中的字符串常量池中。

GC:垃圾回收器->回收堆中的空间。GC会自动识别和回收程序中不再需要的对象所占用的内存空间,使得开发人员无需手动管理内存,减轻了内存管理的负担。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public class Circle {
    //属性
    double radius;//半径
    //方法
    public void findArea(){
        System.out.println("面积为:" + 3.14 * radius * radius);
    }
    //或
    public double findArea2(){
        return 3.14 * radius * radius;
    }

    //错误的:
//    public void findArea1(double r){
//        System.out.println("面积为:" + 3.14 * r * r);
//    }



}

求圆的面积,定义r为圆类的属性,而不是传入的参数。更适合面向对象的思想

ctr+f12:显示当前类的成员变量和成员方法

对象数组

数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用类型中的类时,我们称为对象数组。

String[],Person[],Student[],Customer[]

对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建, 数组的元素的默认值就是 null ,所以很容易出现 空指针异常NullPointerException 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public class Student {

    //属性
    int number;//学号
    int state;//年级
    int score;//成绩

    //声明一个方法,显示学生的属性信息
    public String show(){
        return "number : " + number + ",state : " +
                state + ", score : " + score;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class StudentTest {
    public static void main(String[] args) {

        //创建Student[]
        Student[] students = new Student[20]; //String[] strs = new String[20];

        //使用循环,给数组的元素赋值
        for (int i = 0; i < students.length; i++) {
            students[i] = new Student();
            //给每一个学生对象的number、state、score属性赋值
            students[i].number = i + 1;
            students[i].state = (int)(Math.random() * 6 + 1);
            students[i].score = (int)(Math.random() * 101);
        }

        //需求1:打印出3年级(state值为3)的学生信息
        for (int i = 0; i < students.length; i++) {
            if(3 == students[i].state){
                Student stu = students[i];
                System.out.println(stu.show());
            }
        }


        //需求2:使用冒泡排序按学生成绩排序,并遍历所有学生信息
        //排序前遍历
        for (int i = 0; i < students.length; i++) {
            System.out.println(students[i].show());
        }

        System.out.println("********************");
        for (int i = 0; i < students.length - 1; i++) {
            for (int j = 0; j < students.length - 1 - i; j++) {
                if(students[j].score > students[j + 1].score){
                    //错误的,不满足实际需求!
//                    int temp = students[j].score;
//                    students[j].score = students[j + 1].score;
//                    students[j + 1].score = temp;

                    //正确的
                    Student temp = students[j];
                    students[j] = students[j + 1];
                    students[j + 1] = temp;
                }
            }
        }

        //排序后遍历
        for (int i = 0; i < students.length; i++) {
            System.out.println(students[i].show());
        }

    }
}

提供封装Student相关操作的工具类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class StudentUtil {

    /*
    * 打印出指定年级的学生信息
    * */
    public void printStudentsWithState(Student[] students,int state){
        for (int i = 0; i < students.length; i++) {
            if(state == students[i].state){
                Student stu = students[i];
                System.out.println(stu.show());
            }
        }
    }
    /*
    * 遍历指定的学生数组
    * */
    public void printStudents(Student[] students){
        for (int i = 0; i < students.length; i++) {
            System.out.println(students[i].show());
        }
    }

    /*
    * 针对于学生数组,按照score属性从低到高排列
    * */
    public void sortStudents(Student[] students){
        for (int i = 0; i < students.length - 1; i++) {
            for (int j = 0; j < students.length - 1 - i; j++) {
                if(students[j].score > students[j + 1].score){
                    Student temp = students[j];
                    students[j] = students[j + 1];
                    students[j + 1] = temp;
                }
            }
        }
    }
}

内存解析

方法重载

  • 方法重载:在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。

    参数列表不同,意味着参数个数或参数类型的不同

  • 重载的特点:与修饰符、返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。

  • 重载方法调用:JVM通过方法的参数列表,调用匹配的方法。

    先找个数、类型最匹配

    再找个数和类型可以兼容的,如果同时多个方法可以兼容将会报错

注意:方法的重载与形参的名、权限修饰符、返回值类型都没有关系。

如何判断两个方法是相同的呢?(换句话说,编译器是如何确定调用的某个具体的方法呢?)

方法名相同,且形参列表相同。(形参列表相同指的是参数个数和类型都相同,与形参名没关系)

要求:在一个类中,允许存在多个相同名字的方法,只要他们的形参列表不同即可。

编译器是如何确定调用的某个具体的方法呢?先通过方法名确定了一波重载的方法,进而通过不同的形参列表,确定具体的某一个方法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public int max(int i,int j){
        return (i >= j)? i : j;
    }

    public double max(double d1,double d2){
        return (d1 >= d2)? d1 : d2;
    }

    public double max(double d1,double d2,double d3){
//        double tempMax = max(d1,d2);
//        return max(tempMax,d3);

        return (max(d1,d2) > d3)? max(d1,d2) : d3;
    }
1
2
3
4
5
6
7
8
		int[] arr = new int[]{1, 2, 3};
        System.out.println(arr);//地址值[I@776ec8df

        char[] arr1 = new char[]{'a', 'b', 'c', 'd', 'e'};
        System.out.println(arr1);//abcde

        boolean[] arr2 = new boolean[]{false, true, true};
        System.out.println(arr2);//地址值[Z@4eec7777

println的重载

数组是引用类型,调用的是println(Object x),默认打印的是地址。

唯独当数组是char类型数组的时候,打印的是数组的遍历

可变个数的形参

在JDK 5.0 中提供了Varargs(variable number of arguments)机制。即当定义一个方法时,形参的类型可以 确定,但是形参的个数不确定,那么可以考虑使用可变个数的形参。

1
2
3
4
5
格式(参数类型 ... 参数名)
    
public void print(int ... nums){
        
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class ArgsTest {

    public static void main(String[] args) {
        ArgsTest test = new ArgsTest();

        test.print();
        test.print(1);
        test.print(1,2);

        test.print(new int[]{1,2,3});
        
        //会报错分不出是int ... nums还是,int i,int ... nums
        //两个方法实际上是一样的,后者的int i 改为double i就不错
//        test.print(1,2,3);
    }


    public void print(int ... nums){
        System.out.println("1111");
		//遍历
        for (int i = 0; i < nums.length; i++) {
            System.out.println(nums[i]);
        }
    }
//不构成重载,有冲突,等价于上述方法
//    public void print(int[] nums){
//        for (int i = 0; i < nums.length; i++) {
//            System.out.println(nums[i]);
//        }
//    }
    
    //构成重载
    public void print(int i){
        System.out.println("2222");
    }
	//构成重载,不报错,但是调用时会引起多个方法同时匹配
    public void print(int i,int j){
        System.out.println("3333");
    }
	
    public void print(int i,int ... nums){

    }
    
    //编译不通过
//    public void print(int ... nums,int i){
//
//    }

}

说明: ① 可变个数形参的方法在调用时,针对于可变的形参赋的实参的个数可以为:0个、1个或多个 ② 可变个数形参的方法与同一个类中,同名的多个方法之间可以构成重载–>先找最适配的 ③ 特例:可变个数形参的方法与同一个类中方法名相同,且与可变个数形参的类型相同的数组参数不构成重载。–>5.0以前,不确定参数就是数组表示,所以会冲突–>数组可调用可变形参方法 ④ 可变个数的形参必须声明在形参列表的最后 ⑤ 可变个数的形参最多在一个方法的形参列表中出现一次

1
2
3
4
5
6
7
8
//场景举例:
String sql = "update customers set name = ?,email = ? where id = ?";

String sql1 = "update customers set name = ? where id = ?";

//不确定sql语句中的参数有几个,sql有几个问号,objs就有几个参数 
public void update(String sql,Object ... objs);

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class StringConCatTest {

    public static void main(String[] args) {

        StringConCatTest test = new StringConCatTest();
        String info = test.concat("-","hello","world");
        System.out.println(info);//hello-world

        System.out.println(test.concat("/", "hello"));

        System.out.println(test.concat("-"));

    }

//n个字符串进行拼接,每一个字符串之间使用某字符进行分割,如果没有传入字符串,那么返回空字符串""
    public String concat(String operator,String ... strs){
        String result = "";

        for (int i = 0;i < strs.length;i++){
            if(i == 0){
                result += strs[i];
            }else{
                result += (operator + strs[i]);
            }
        }

        return result;
    }
}

方法的参数传递机制

对于方法内声明的局部变量来说:如果出现赋值操作

​ 如果是基本数据类型的变量,则将此变量保存的数据值传递出去。 ​ 如果是引用数据类型的变量,则将此变量保存的地址值传递出去。

方法的参数的传递机制:值传递机制

形参:在定义方法时,方法名后面括号()中声明的变量称为形式参数,简称形参。 实参:在调用方法时,方法名后面括号()中的使用的值/变量/表达式称为实际参数,简称实参。

规则:实参给形参赋值的过程 如果形参是基本数据类型的变量,则将实参保存的数据值赋给形参。 如果形参是引用数据类型的变量,则将实参保存的地址值赋给形参

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class ValueTransferTest1 {
    public static void main(String[] args) {
        ValueTransferTest1 test = new ValueTransferTest1();

        //1. 对于基本数据类型的变量来说,传递值
        int m = 10;

        test.method1(m);//将m的值赋给了method1方法的形参

        System.out.println("m = " + m);//10

        //2. 对于引用数据类型的变量来说,传递地址
        Person p = new Person();
        p.age = 10;

        test.method2(p);//将p的地址值传给了method2方法的形参

        System.out.println(p.age); //11
    }

    public void method1(int m){
        m++;//仅修改了方法内的参数的值,main方法中的m不变
    }
    public void method2(Person p){
        p.age++;//传递的地址,可以修改main中的p
    }
}

class Person{
    int age;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ValueTransferTest2 {
    public static void main(String[] args) {
        ValueTransferTest2 test = new ValueTransferTest2();


        int m = 10;
        int n = 20;

        System.out.println("m = " + m + ", n = " + n);

        //交换两个变量的值
        //操作1:
//        int temp = m;
//        m = n;
//        n = temp;

        //操作2:
        //调用方法---并没有交换
        test.swap(m,n);
        System.out.println("m = " + m + ", n = " + n);//10,20
    }

    public void swap(int m ,int n){
        int temp = m;
        m = n;
        n = temp;
    }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class ValueTransferTest3 {
    public static void main(String[] args) {

        ValueTransferTest3 test = new ValueTransferTest3();

        Data data = new Data();
        data.m = 10;
        data.n = 20;

        System.out.println("m = " + data.m + ", n = " + data.n);

        //操作1:
//        int temp = data.m ;
//        data.m = data.n;
//        data.n = temp;

        //操作2:
        test.swap(data);
        System.out.println("m = " + data.m + ", n = " + data.n);

    }

    public void swap(Data data){
        int temp = data.m ;
        data.m = data.n;
        data.n = temp;
    }


}

class Data{
    int m;
    int n;
}

排序

asc:升序 desc:降序

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
     * 针对于数组进行排序操作
     * @param arr 待排序的数组
     * @param sortMethod  asc:升序   desc:降序
     */
    public void sort(int[] arr,String sortMethod){
        ////if(sortMethod.equals("asc")){ 
        // 当sortmethod为null是会空指针异常acs放前面就可以避免
        if("asc".equals(sortMethod)){//ascend:升序
            for(int j = 0;j < arr.length - 1;j++){
                for (int i = 0; i < arr.length - 1 - j; i++) {
                    if(arr[i] > arr[i + 1]){
                        //交互arr[i] 和 arr[i + 1]
//                        int temp = arr[i];
//                        arr[i] = arr[i + 1];
//                        arr[i + 1] = temp;
                        //错误的:
//                        swap(arr[i],arr[i + 1]);

                        //正确的:
                        swap(arr,i,i+1);
                    }

                }
            }
        }else if("desc".equals(sortMethod)){
            for(int j = 0;j < arr.length - 1;j++){
                for (int i = 0; i < arr.length - 1 - j; i++) {
                    if(arr[i] < arr[i + 1]){
                        //交互arr[i] 和 arr[i + 1]
//                        int temp = arr[i];
//                        arr[i] = arr[i + 1];
//                        arr[i + 1] = temp;
                        //错误的:
//                        swap(arr[i],arr[i + 1]);

                        //正确的:
                        swap(arr,i,i+1);
                    }

                }
            }
        }else{
            System.out.println("你输入的排序方式有误");
        }

    }
//    public void swap(int i,int j){//实参接收的数值
//        int temp = i;
//        i = j;
//        j = temp;
//    }

    private void swap(int[] arr,int i,int j){//接收的地址
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    } 

asc,desc字面量写前面,判断相等,规避空指针异常

“asc”.equals(sortMethod)

交换数组两个位置,硬传递数组–(对应地址)和位置

递归方法

递归方法调用:方法自己调用自己的现象就称为递归。

递归的分类:直接递归、间接递归。

  • 直接递归:方法自身调用自己。
  • 间接递归:可以理解为A()方法调用B()方法,B()方法调用C()方法,C()方法调用A()方法。

说明:

  • 递归方法包含了一种隐式的循环
  • 递归方法会重复执行某段代码,但这种重复执行无须循环控制。
  • 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,停不下来,类似于死循环。最终发生栈内存溢出

注意:

  1. 递归调用会占用大量的系统堆栈,内存耗用多,在递归调用层次多时速度要比循环慢的多, 所以在使用递归时要慎重。
  2. 在要求高性能的情况下尽量避免使用递归,递归调用既花时间又耗内存。考虑使用循环迭代
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
	/*
    * 举例1:计算1-100内自然数的总和
    * */
    public int getSum(int num){
        int sum = 0;
        for(int i = 1;i <= num;i++){
            sum += i;
        }
        return sum;
    }

    public int getSum1(int num){
        if(num == 1){//已知方向
            return 1;
        }else{
            return getSum1(num - 1) + num;
        }

    }

汉诺塔游戏

斐波那契数列

1 1 2 3 5 8 13 21 34 55 … f(n) = f(n - 1) + f(n - 2)

1
2
3
4
5
6
7
8
9
	public int f(int n){
        if(n == 1){
            return 1;
        }else if(n == 2){
            return 1;
        }else{
            return f(n - 1) + f(n - 2);
        }
    }

关键字:package、import

  1. 说明
  • package:包
  • package用于指明该文件中定义的类、接口等结构所在的包
  • 一个源文件只能有一个声明包的package语句
  • package语句作为Java源文件的第一条语句出现。若缺省该语句,则指定为无名包。
  • 包名,属于标识符,满足标识符命名的规则和规范(全部小写)、见名知意
    • 包通常使用所在公司域名的倒置:com.atguigu.xxx。
    • 大家取包名时不要使用"java.xx“包
  • 包对应于文件系统的目录,package语句中用 “.” 来指明包(目录)的层次,每.一次就表示一层文件目录。
  • 同一个包下可以声明多个结构(类、接口),但是不能定义同名的结构(类、接口)。不同的包下可以定义同名的结构(类、接口)
  1. 包的作用
  • 包可以包含类和子包,划分项目层次,便于管理
  • 帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
  • 解决类命名冲突的问题
  • 控制访问权限
  1. JDK中主要的包

java.lang—-包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能 java.net—-包含执行与网络相关的操作的类和接口。 java.io —-包含能提供多种输入/输出功能的类。 java.util—-包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。 java.text—-包含了一些java格式化相关的类 java.sql—-包含了java进行JDBC数据库编程的相关类/接口 java.awt—-包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。

MVC设计模式

MVC是一种软件构件模式,目的是为了降低程序开发中代码业务的耦合度。

MVC设计模式将整个程序分为三个层次: 视图模型(Viewer)层 , 控制器(Controller)层 ,与 数据模型 (Model)层 。这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
视图层viewer:显示数据,为用户提供使用界面,与用户直接进行交互。
>相关工具类 view.utils
>自定义view view.ui

控制层controller:解析用户请求,处理业务逻辑,给予用户响应
>应用界面相关 controller.activity
>存放fragment controller.fragment
>显示列表的适配器 controller.adapter
>服务相关的 controller.service
>抽取的基类 controller.base

模型层model:主要承载数据、处理数据
>数据对象封装 model.bean/domain
>数据库操作类 model.dao
>数据库 model.db

import关键字的使用

  • import : 导入

  • import语句来显式引入指定包下所需要的类。相当于import语句告诉编译器到哪里去寻找这个类

  • import语句,声明在包的声明和类的声明之间。

  • 如果需要导入多个类或接口,那么就并列显式多个import语句即可

  • 如果使用a.*导入结构,表示可以导入a包下的所有的结构。举例:可以使用java.util.*的方式,一次性导入util包下所有的类或接口。

  • 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。

  • 如果已经导入java.a包下的类,那么如果需要使用a包的子包下的类的话,仍然需要导入

  • 如果在代码中使用不同包下的同名的类,那么就需要使用类的全类名的方式指明调用的是哪个类。

  • (了解)import static组合的使用:调用指定类或接口下的静态的属性或方法

    1
    2
    3
    4
    
    import static java.lang.System.out;
    import static java.lang.Math.PI;
    
    out.println(PI);
    

封装性

面向对象特征之一:封装性(encapsulation)

面向对象的开发原则要 遵循 “高内聚、低耦合”。

高内聚、低耦合是软件工程中的概念,也是UNIX 操作系统设计的经典原则。

内聚,指一个模块内各个元素彼此结合的紧密程度;

耦合指一个软件结构内不同模块之间互连程度 的度量。

内聚意味着重用和独立,耦合意味着多米诺效应牵一发动全身。

-高内聚:类的内部数据操作细节自己完成,不允许外部干涉; -低耦合:仅暴露少量的方法给外部使用,尽量方便外部调用。

所谓封装,就是把客观事物封装成抽象概念的类,并且类可以把自己的数据和方法只向可信的类或者对 象开放,向没必要开放的类或者对象隐藏信息。

通俗的说:把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。

权限修饰符

实现封装就是控制类或成员的可见性范围。这就需要依赖访问控制修饰符,也称为权限修饰符来控制。

权限修饰符: public 、 protected 、 缺省 、 private 。我们可以使用4种权限修饰来修饰类及类的内部成员。当这些成员被调用时,体现可见性的大小

类:只能使用public、缺省修饰 类的内部成员:可以使用4种权限修饰进行修饰。

操作修饰符 本类内部 本包内 其他包的子类 其他包非子类
private
缺省
protected
public

封装性的体现:

场景1:私有化(private)类的属性,提供公共(public)的get和set方法,对此属性进行获取或修改 场景2:将类中不需要对外暴露的方法,设置为private. 场景3:单例模式中构造器private的了,避免在类的外部创建实例。

成员变量/属性私有化

概述:私有化类的成员变量,提供公共的get和set方法,对外暴露获取和修改属性的功能。

  • ① 使用 private 修饰成员变量
  • ② 提供 getXxx 方法 / setXxx 方法,可以访问成员变量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class Animal{ //动物
    //属性
    String name; //名字
    private int legs;//腿的个数
    //方法
    //设置legs的属性值
    public void setLegs(int l){
        if(l >= 0 && l % 2 == 0){
            legs = l;
        }else{
            System.out.println("你输入的数据非法");
        }
    }
    //获取legs的属性值
    public int getLegs(){
        return legs;
    }
}

我们知道legs不能赋值为负数的。但是如果直接调用属性legs,是不能加入判断逻辑的。那怎么办呢?

将legs属性私有化(private),禁止在Animal类的外部直接调用此属性 提供给legs属性赋值的setLegs()方法,在此方法中加入legs赋值的判断逻辑if(legs >= 0 && legs % 2 ==0)将此方法暴露出去,使得在Animal类的外部调用此方法,对legs属性赋值。 提供给legs属性获取的getLegs()方法,此方法对外暴露。使得在Animal类的外部还可以调用此属性的值。

成员变量封装的好处:

让使用者只能通过事先预定的方法来 访问数据 ,从而可以在该方法里面加入控制逻辑,限制对成员 变量的不合理访问。还可以进行数据检查,从而有利于保证对象信息的完整性。

便于修改 ,提高代码的可维护性。主要说的是隐藏的部分,在内部修改了,如果其对外可以的访问 方式不变的话,外部根本感觉不到它的修改。例如:Java8->Java9,String从char[]转为byte[]内部实 现,而对外的方法不变,我们使用者根本感觉不到它内部的修改。

类的成员之三:构造器(Constructor)

构造器的作用: new对象,并在new对象的时候为实例变量赋值。

说明:

构造器名必须与它所在的类名必须相同

它没有返回值,所以不需要返回值类型,也不需要void。

构造器的修饰符只能是权限修饰符,不能被其他任何修饰。比如,不能被static、final、 synchronized、abstract、native修饰,不能有return语句返回值。

创建类以后,在没有显示提供任何构造器的情况下,系统会默认提供一个空参的构造器,且构造器的权限 与类声明的权限相同。 一旦类中显示声明了构造器,则系统不再提供默认的空参的构造器。 一个类中可以声明多个构造器,彼此之间构成重载。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
	//默认的空参构造器
    public Person(){
        System.out.println("Person()....");
    }

    //声明其它的构造器
    public Person(int a){
        age = a;
    }

	//构造器的使用
	Person p1 = new Person();
	Person p2 = new Person(1);

匿名对象 (anonymous object)

  • 我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
  • 如:new Person().sleep(8);
  • 使用情况 如果一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
  • 我们经常将匿名对象作为实参传递给一个方法调用。
1
customer.setAccount(new Account(1000,2000,0.0123));

类中属性赋值过程

实例变量

  1. 在类的属性中,可以有哪些位置给属性赋值?

① 默认初始化; ② 显式初始化; ③ 构造器中初始化; ④ 通过"对象.方法"的方式赋值; ⑤ 通过"对象.属性"的方式赋值;

  1. 这些位置执行的先后顺序是怎样? ① - ② - ③ - ④/⑤

  2. 以上操作在对象创建过程中可以执行的次数如何?

只能执行一次:①、②、③ 可以多次执行:④、⑤

JavaBean

所谓JavaBean,是指符合如下标准的Java类:

  • 类是公共的
  • 有一个无参的公共的构造器
  • 有属性,且有对应的get、set方法

JavaBean是一种符合特定约定的Java类,通常用于封装数据和提供访问这些数据的方法。其主要作用是作为一种轻量级的可重用组件,用于在Java应用程序中管理数据。

UML类图

在软件开发中,使用 UML类图 可以更加直观地描述类内部结构(类的属性和操作)以及类之间的关系(如关联、依赖、聚合等)。

  • +表示 public 类型, - 表示 private 类型,#表示protected类型
  • 方法的写法: 方法的类型(+、-) 方法名(参数名: 参数类型):返回值类型
  • 斜体表示抽象方法或类。