10-面向对象

一、 类与对象

Java是一门纯面向对象的语言(Object Oriented Program,简称OOP),在面向对象的世界里,一切皆为对象。

一个程序就是一个世界,有很多事物(对象[属性,行为])

1.1 什么是面向对象?什么是面向过程?

面向对象:面向对象是解决问题的一种思想,主要依靠对象之间的交互完成一件事情。

面向过程:面向过程是以过程为中心的编程思想,其原理就是将问题分解成一个一个详细的步骤,然后通过函数实现每一个步骤,并依次调用。

1.2 什么是类?

类是用来对一个实体(对象)来进行描述的,主要描述该实体(对象)具体有哪些属性,哪些功能,描述完成之后计算机就可以识别了。

这里需要注意:类和对象的区别和联系

a) 类是抽象的,概念的,代表一类事物。比如人类、猫咪等等,即它是数据类型

b) 对象是具体的,实际的,代表一个具体事物。即它是实例

c) 类是对象的模版,对象是类的一个个体,对应一个实例

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

        /**
         * 张老太养了两只猫猫:
         * 一只名字叫小白,今年三岁,白色。还有一只叫小花,今年100岁,花色。
         * 请编写一个程序,当用户输入小猫的名字时,则显示该猫的名字,年龄,颜色。
         * 如果用户输入的小猫名字错误,则显示"张老太没有这只猫"
         */
        
        //单独变量来解决 => 不利于数据的管理
        //第一只猫的信息
        String cat1Name = "小白";
        int cat1Age = 3;
        String cat1Color = "白色";

        //第二只猫的信息
        String cat2Name = "小花";
        int cat2Age = 100;
        String cat2Color = "花色";

        //数组 => 数据类型体现不出来;只能通过下标获取信息,容易造成变量名字和内容的对象关系不明确;不能体现猫的行为
        //第一只猫的信息
        String[] cat1 = {"小白","3","白色"};
        //第二只猫的信息
        String[] cat2 = {"小花","100","花色"};


        //使用OOP面向对象解决
        //实例化一只猫【创建一只猫对象】
        //1. new Cat() 创建一只猫
        //2. Cat cat3 = new Cat(); 把创建的猫赋给 cat3
        Cat cat3 = new Cat();
        cat3.name = "小白";
        cat3.age = 3;
        cat3.color = "白色";
        //创建第二只猫
        Cat cat4 = new Cat();
        cat4.name = "小花";
        cat4.age = 100;
        cat4.color = "花色";

        //访问对象的属性
        System.out.println("第一只猫的信息:" + cat3.name + "\t" + cat3.age + "\t" + cat3.color);
        System.out.println("第二只猫的信息:" + cat4.name + "\t" + cat4.age + "\t" + cat4.color);
        
    }
}

//使用面向对象的方式来解决养猫问题
//定义一个猫类 Cat -> 自定义的数据类型
class Cat{

    //属性
    String name; //名字
    int age; //年龄
    String color; //颜色
    
}

二、属性(成员变量)

2.1 基本介绍

从概念或叫法上看:成员变量 = 属性 = field(字段)。即成员变量是用来表示属性的。

属性是类的一个组成部分,一般是基本数据类型,也可以是引用类型(对象,数组)。

2.2 属性的注意事项

a) 属性的定义语法同变量,示例:访问修饰符 属性类型 属性名;

b) 属性的定义类型可以为任意类型,包含基本类型或引用类型

c) 属性如果不赋值,有默认值,规则和数组一致

扩展:访问修饰符可以控制属性的访问范围。总共有四种访问修饰符 public , protected , default(默认) , private

public:表示公共访问级别,可以被任何类访问。

protected:表示受保护访问级别,可以被类本身、子类和同一包中的类访问。

default(缺省):表示默认访问级别,即如果没有使用访问修饰符,默认是此级别,可以被同一包中的类访问。

private:表示私有访问级别,只能在类内部访问。

2.3 如何创建对象

a) 先声明再创建

Cat cat;

cat = new Cat();

b) 直接创建

Cat cat = new Cat();

2.4 如何访问对象

基本语法:对象名.属性名;  例如 cat.name; cat.age;

public class Object03 {
    public static void main(String[] args){
        Person p1 = new Person();
        p1.age = 10;
        p1.name = "小明";

        //把p1赋给p2,让p2指向p1
        Person p2 = p1;
        p2.age = 20;

        System.out.println("p1的年龄:" + p1.age); //p1的年龄:20
        System.out.println("p2的年龄:" + p2.age); //p2的年龄:20
    }
}

class Person{
    String name;
    int age;
}

2.5 Java 创建对象的流程简单分析

a) 先加载Person类信息(属性和方法信息,只会加载一次)

b) 在堆中分配空间,进行默认初始化(看规则)

c) 把地址赋给p1,p1就指向对象

d) 进行指定初始化,例如 p1.age = 10;  p1.name = "小明";

三、成员方法

3.1 基本介绍

在某些情况下,我们需要定义成员方法(简称方法)。比如人类除了有一些属性外(年龄、姓名...),还需要有一些行为,可以说话、跑步,还可以做算术题。这时就要用成员方法才能完成

3.2 成员方法的定义格式:

访问修饰符 返回数据类型 方法名(形参列表){

  语句;

  return 返回值;

}

a) 参数列表:表示成员方法输入

b) 返回数据类型:表示成员方法输出,void:表示没有返回值

c) 方法主体:表示为了实现某一功能代码块

d) return 语句不是必须的,有的方法不一定有返回值

e) 调用方法前需要先创建对象,再调用方法  

public class Method01 {
    public static void main(String[] args){
        //方法使用
        //1.方法写好后,不去调用是没有效果的
        //2.调用方法前需要先创建对象,再调用方法
        Person1 p1 = new Person1();
        p1.speak();

        //把调用getSum方法返回的值,赋给变量sum
        int sum = p1.getSum(10,20);
        System.out.println("sum = " + sum);
    }
}

class Person1{
    //属性(成员变量)
    String name;
    int age;
    //方法(成员方法)
    //1.public: 表示方法是公开的,可以被任何类访问
    //2.void: 表示方法没有返回值
    //3.speak(): speak是方法名,()是形参列表
    //4.{}是方法体,可以写我们要执行的代码
    public void speak(){
        System.out.println("我是马铃薯");
    }
    //getSum成员方法
    //int:表示方法执行后,返回一个int值;return res;表示把res的值返回
    //(int num1,int num2)形参列表,表示当前有两个形参可以接收用户传入
    public int getSum(int num1,int num2){
        int res = num1 + num2;
        return res;
    }
}

 3.3 方法的好处:

a) 提高了代码的复用性

b) 将实现的细节封装起来,供其他用户调用就行

例如:遍历一个数组,输出数组的各个元素值

import java.net.SocketTimeoutException;

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

        //请遍历一个数组,输出数组的各个元素值
        int[][] map = {{0,0,1},{1,1,1},{1,1,3}};

        //传统的解决方法就是直接遍历
        for(int i = 0; i < map.length; i++){
            for(int j = 0; j < map[i].length; j++){
                System.out.print(map[i][j] + "\t");
            }
            System.out.println();
        }

        //使用方法遍历数组
        MyTools tool = new MyTools();
        tool.printArr(map);
    }
}

//定义一个类MyTools,把输出的功能写到一个类的方法中,然后调用该方法即可
class MyTools{
    //方法,接收一个二维数组
    public void printArr(int[][] map){
        //对传入的map数组进行遍历输出
        for(int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j] + "\t");
            }
            System.out.println();
        }
    }
}

 3.4 方法的注意事项和使用细节

1> 一个方法最多有一个返回值 【当要返回多个结果,可以返回数组】

2> 返回数据类型

a) 返回类型可以为任意类型,包括基本类型或引用类型(数组,对象)

b) 如果方法要求有返回数据类型,方法体中最后的执行语句必须为return值,并且要求返回值类型必须和return的值类型一致或兼容

3> 方法名遵循驼峰命名法,多单词组成时,第一个单词的首字母小写,第二个单词开始,每个单词的首字母大写。例如 tankShotGame

4> 形参列表

a) 一个方法可以有零个参数,也可以有多个参数,中间用逗号隔开

b) 参数类型可以是任意类型,包含基本类型或引用类型

c) 调用带参数的方法时,一定对应着参数列表传入相同类型或兼容类型的参数

d) 方法定义时的参数为形式参数又称形参,方法调用时传入的参数称为实际参数,又称实参,实参和形参的类型要一致或兼容即个数、顺序必须一致

5> 方法体,完成功能的具体语句,可以是输入、输出、变量、运算、分支、循环、方法调用,但不允许方法里面再定义方法

6> 方法的调用

a) 同一个类中的方法调用,直接调用就行

b) 跨类中的方法A类调用B类的方法,需要通过对象名调用。

这里需要注意:跨类的方法调用也和方法的访问修饰符相关

public class MethodDetail {
    public static void main(String[] args){
        //跨类中的方法A类调用B类的方法,需要通过对象名调用
        A a = new A();
        a.a2();
    }
}

class A{
    public void a1(){
        System.out.println("a1方法被调用");
    }
    public void a2(){
        //同一个类中的方法调用,直接调用就行
        a1();
        System.out.println("a2方法执行");
    }
}

3.5 成员方法的传参机制

a) 基本数据类型传参

基本数据类型,传递的是值(值拷贝),形参的任何改变不影响实参。

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

        int a = 10;
        int b = 20;
        //创建AA对象
        AA2 aa = new AA2();
        aa.swap(a,b);
        System.out.println("a = " + a + "\tb = " + b); //a = 10    b = 20
    }
}

class AA2{
    public void swap(int a,int b){
        System.out.println("交换前:a = " + a + "\tb = " + b); //交换前:a = 10    b = 20

        //完成 a 和 b 的值交换
        int temp  = a;
        a = b;
        b = temp;
        System.out.println("交换后:a = " + a + "\tb = " + b); //交换后:a = 20    b = 10
    }
}

b) 引用类型传参

引用数据类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参。

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

        //测试
        BB bb = new BB();
        int[] arr = {1,2,3};
        bb.test100(arr);
        //遍历数组
        System.out.println("====main方法的 arr数组 输出====");
        for(int i= 0; i < arr.length; i++){
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
    }
}

class BB{
    //BB类中编写一个方法,test100
    //可以接收一个数组,在方法中修改该数组,看看原来的数组是否发生变化
    public void test100(int[] arr){
        //修改数组 arr[0] 的值
        arr[0] = 200;
        //遍历数组
        System.out.println("====test100的 arr数组 输出====");
        for(int i = 0; i < arr.length; i++){
            System.out.print(arr[i] + "\t");
        }
        System.out.println();
    }
}

public class MethodParameter03 {
    public static void main(String[] args){
        //测试
        Person2 p = new Person2();
        p.name = "马铃薯";
        p.age = 25;

        BB2 b = new BB2();
        b.test200(p);
        System.out.println("main方法的 p.age = " + p.age); //main方法的 p.age = 10000 这是因为对象也是存储在堆里
    }
}

class Person2{
    String name;
    int age;
}
class BB2{
    public void test200(Person2 p){
        //修改对象属性
        p.age = 10000;
    }
}

 

思考一个问题:我们前面学习到,String是一种引用类型,那么JAVA中String类传参的话,还是以地址进行传递吗,形参会影响实参吗

public class Test04 {
    public static void main(String[] args){
        Person p1 = new Person();

        String a1 = "10";
        p1.test01(a1);
        System.out.println("test01:" + a1); // test02:10

        String a2 = new String("10");
        p1.test02(a2);
        System.out.println("test02:" + a2); // test02:10

        p1.test03(a2);
        System.out.println("test03:" + a2); // test02:10
    }
    public static void sleep(){
        System.out.println("睡觉");
    }
}

class Person{

    public void test01(String a){
        a = a + "abc";
        System.out.println("a = " + a);
    }

    public void test02(String a){
        a = new String(a + "abc");
        System.out.println("a = " + a);
    }

    public void test03(String a){
        a = a.concat("abc");
        System.out.println("a = " + a);
    }

}

从上面的结果可以看到,String类型传递的形参,不论形参如何改变,都无法影响到实参。

但这跟我们前面学习到的内容,基本类型传参是值传递,引用类型传参是地址传递,似乎有些矛盾。

通过分析String的源码,我们发现:

这个问题真正原因,是因为String类的存储是通过final修饰的char[]数组来存放结果的,也就是说是不可更改的。

所以每次当外部一个String类型的引用传递到方法内部时候,只是把外部String类型变量的引用传递给了方法参数变量。外部String变量和方法参数变量都是实际char[]数组的引用而已,所以当我们在方法内部改变这个参数的引用时候,因为char[]数组不可改变,所以每次新建变量都是新建一个新的String实例。很显然外部String类型变量没有指向新的String实例。所以也就不会获取到新的更改。

 也就是说,字符串一旦被创建,就不可更改(如果想更改,只能使用新的对象做替换)

// 字符串一旦被创建,就不可更改(如果想更改,只能使用新的对象做替换)
s = "def";
System.out.println(s); // def,字符串一旦被创建,就应该不可更改,这里为什么改变了,这是因为s指向了新的字符串对象"def"

// 等价于上面的,s指向了新的字符串对象"def"
Student stu = new Student("张三", 20);
stu = new Student("李四", 21);
System.out.println(stu.getName() + "\t" + stu.getAge()); // 李四    21

 

扩展:final修饰基本类型变量,和引用类型变量的区别

1、final修饰基本类型变量时,不能对基本类型变量重新赋值,因为基本类型变量不能被改变

2、对于引用类型变量而言,保存的仅仅是一个引用地址,final只是保证这个引用地址不被改变,而引用指向的对象完全可以改变

 

 

 

 

posted @ 2023-07-31 22:17  马铃薯1  阅读(6)  评论(0编辑  收藏  举报