Java面向对象基础

类与对象

引入

单独变量解决:不利于数据的管理(把一只猫的信息拆解了,当信息量庞大时更加的麻烦)

//第一只猫信息
string cat1Name = "小白"
int cat1Age = 3;
string cat1Color = "白色"
//第二只猫信息
string cat1Name = "小红"
int cat1Age = 3;
string cat1Color = "红色"

n只猫.......

数组解决:

(1)数据类型体现不出来

(2)只能通过下标获取信息,造成变量名和内容的关系不对应

(3)猫的行为体现不出来(方法):如猫喜欢吃鱼这一行为体现不出来

String cat1[] = {"小白","3","白色"};
String cat1[] = {"小小红","3","红色"}

引入类与对象的原因:现有的技术不能完美的解决新的要求

类与对象

类分为两部分:变量的声明(属性)和方法的定义

【类的定义格式】

class 类名 {
 类体内容
}

属性概念

// 创建一个猫类——自定义的数据类型
Class Cat {
// 属性/成员变量
String name; // 姓名
int age; // 年龄
String color //颜色
String[] master;// 属性可以是基本数据类型,也可以是是引用类型(数组,对象)

// 行为
}

1.从概念上或叫法上看:成员变量 = 属性 = field(字段)

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

属性注意细节

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

    访问修饰符:public、private、protected、默认——控制属性的访问范围

  • 属性的定义类型可以是任意类型,包含基本类型和引用类型

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

    boolean初始值:false
    int初始值:0
    short初始值:0
    float初始值:0.0
    double初始值:0.0
    char初始值: \u0000
    long初始值:0
    byte初始值:0
    Value初始值:null

    【参考代码】

    	public static void main(String[] args) {
            Person p1 = new Person();
            System.out.println(p1.name);//null
            System.out.println(p1.age);//0
            System.out.println(p1.sal);//0.0
            System.out.println(p1.isPass);//false
        }
        static class Person {
            String name;
            int age;
            double sal;
            boolean isPass;
            ....
        }
    
    
    
    

对象

创建对象

1. 先声明在创建:
	Cat cat ; // 声明对象
	cat = new Cat(); // 创建
2. 直接创建
	Cat cat = new Cat();

访问属性

基本语法:

对象名.属性名;

如:cat.name;

用类建立多个属性(自定义数据类型),通过实例化对象管理多个属性

	public static void main(String[] args) {
		// 创建Person对象
		// p1 是对象名(对象引用)
		// new Person() 创建的对象空间(数据) 才是真正的对象
        Person p1 = new Person();

    }
    static class Person {
        String name;
        int age;
        double sal;
        boolean isPass;
        ....
    }
   		 p1 是对象名(对象引用)
   		 new Person() 创建的对象空间(数据) 才是真正的对象
   	
   		 就如:小明是人名 它 指向 人(本质——对象)

对象的内存与分配机制

【看个例子】

Person p1 = new Person();

p1.age = 10;

p1.name = "小明";

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

System.out.println(p2.age);

问p2.age = ? 并画出内存图

image

image

image

很显然最后p2.age = 10

Java内存结构的分析

  1. 栈:一般存储基本数据类型(局部变量)、调用方法时开辟新栈
  2. 堆:存放引用类型(对象,如Cat cat 、数组等)
  3. 方法区:常量池(常量,比如字符串),类加载信息

对象创建过程总结:

  1. 先加载类(Person类)信息(属性和方法信息,只会加载一次)
  2. 在堆中分配空间,进行默认初始化(看规则)
  3. 把地址赋给p,p就指向对象
  4. 进行指定初始化(如:p1.age = 10;)

【再看个例子】

image

类与对象的区别及联系

  • 类是抽象的,概念的,代表一类事物,比如猫类、人类......,即 它是数据类型
  • 对象是具体的,实际的,代表一个具体事物,即 是实例
  • 类是对象的模板,对象是类的一个个体,对应一个实例

img

成员方法

在某些情况下我们需要定义成员方法(简称方法),就如Person类中出了age、name等属性外,我们还想给人加一些行为,例如:说话、跑步、做计算等等行为,这时就要用成员方法才能完成。

成员方法定义

【语法格式】

                权限修饰符 返回值类型 方法名(形参列表) {

                  // 方法体

                  // 返回值

                }

             1. 形参列表:表示成员方法输入,getSum(int num1 , int num2)
         2. 返回数据类型:表示成员方法输出,void表示没有返回值
             3. 方法体:表示为实现某一功能的代码块
         4. return语句不是必须的

【例子】

  1. 添加speak成员方法,输出我是一个好人
  2. 添加cal01成员方法,计算1+...1000结果
  3. 添加cal01成员方法,计算1+...n结果
  4. 添加getSum成员方法,计算两个数的和
public class detail {
    public static void main(String[] args) {
        Person p1 = new Person();

        p1.speak();

        int ans = p1.cal01();
        System.out.println(ans);

        int ans2 = p1.cal02(1000);
        System.out.println(ans2);

        int ans3 = p1.getSum(5, 5);
        System.out.println(ans3);
    }

}

class Person {
        String name;
        int age;
        public void speak() {   // public:该方法是公开的
            System.out.println("我是一个好人");
        }

        int cal01() {
            int sum = 0;
            for (int i = 1; i <= 1000 ; i++) {
                sum += i;
            }
            return sum;
        }

        int cal02(int n){
            int sum = 0;
            for (int i = 1; i <= n ; i++) {
                sum += i;
            }
            return sum;
        }

        int getSum(int a, int b){
            return a+b;
        }

    }

方法的调用机制

image

方法调用小结:

  1. 当程序执行到方法时,就会开辟一个独立的空间(栈空间)
  2. 当方法执行完毕,或者执行到return语句时,就会返回
  3. 返回到调用方法的地方
  4. 返回后,继续执行方法后的代码
  5. 当main栈执行完毕后,整个程序也就退出了

方法使用细节

【返回类型】

  1. 一个方法最多有一个返回值

    【如何返回多个结果】——返回数组

    public class detail {    public static void main(String[] args) {        AA a = new AA();        int[] res = a.getSumAndSub(3,2);        System.out.println("和为"+ res[0]);        System.out.println("差为"+ res[1]);    }}class AA {    public int[] getSumAndSub(int num1, int num2){        int[] resArr = new int[2];        resArr[0] = num1 + num2;        resArr[1] = num1 - num2;        return resArr;    }}
    
  2. 返回类型可以是任意类型,包括基本类型或引用类型(数组,对象)

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

  4. 如果方法是void,则方法体中可以没有return语句,或者写成return ;

  5. 开发规范:方法名遵循驼峰命名法,getSum

【形参列表】

  1. 一个方法可以有0个参数,也可以有多个参数,中间用逗号隔开,比如getSum(int num1 , int num2)
  2. 参数类型可以是任意类型,包含基本类型或引用类型,比如printArr(int[] [] map)
  3. 调用带参数的方法时,一定对应着参数列表传入相同的类型或者兼容类型的参数
  4. 方法定义时的参数称为形式参数,即形参;方法调用时的传入参数为实际参数,即实参。形参和实参的类型要一致或兼容、个数顺序必须相同。

【方法调用细节】

  1. 同一个类中的方法调用:可以直接调用

    演示:A类 sayOk()调用print()

    public class detail {    public static void main(String[] args) {        A a = new A();        a.sayOk();    }}class A {    // 同一个类中的方法调用:直接调用即可    public void print(int n){        System.out.println("print()方法被调用 n=" + n);    }    public void sayOk(){ // sayOk()直接调用print()        print(10);        System.out.println("继续执行sayOk()方法");    }}
    
  2. 跨类中的方法A类调用B类方法:不能直接调用,需要通过对象名调用

    public class detail {
        public static void main(String[] args) {
            A a = new A();
            a.mi();
        }
    }
    class A {
    // 跨类调用方法
        public void mi(){
            // 创建B类对象,然后用对象调用B类方法即可
            System.out.println("mi()方法被调用");
            B b = new B();
            b.hi();
            System.out.println("mi()方法 继续执行");
        }
    }
    
    class B {
        public void hi(){
            System.out.println("B类中的 hi()被调用");
        }
    }
    
  3. 跨类的调用和方法的访问修饰符有关!

方法传参机制

基本数据类型传参机制:

【分析输出结果是什么】

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

        int a = 10;
        int b = 20;
        // 创建AA对象
        AA obj = new AA();
        obj.swap(a,b); // 调用swap()
        System.out.println("a=" + a + " b=" + b); //?
    }
}
    class AA {
    public void swap(int a, int b){
        System.out.println("\na和b交换前的值\na=" + a + "\tb=" + b);
        // 完成 a 和 b 的交换
        int temp = a;
        a = b;
        b = temp;
        System.out.println("\na和b交换后的值\na=" + a + "\tb=" + b);
    }
}

输出结果:

a和b交换前的值
a=10 b=20

a和b交换后的值
a=20 b=10
a=10 b=20

image

【结论】

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

引用类型传参机制:

B类中编写一个test100方法,可以接收一个数组,在方法中修改该数组,看看原来的数组是否发生变化?

public class MethodParameter01 {    public static void main(String[] args) {        B b = new B();        int[] arr = {1,2,3};        b.test100(arr); // 调用方法        // 遍历数组        System.out.println("\nmain方法里的 arr数组");        for (int i = 0; i < arr.length; i++) {            System.out.print(arr[i] + " ");        }            }}// B类中编写一个test100方法//可以接收一个数组,在方法中修改该数组,看看原来的数组是否发生变化?    class B {    public void test100(int[] arr){        arr[0] = 100; // 修改元素        // 遍历数组        System.out.println("test100的 arr数组");        for (int i = 0; i < arr.length; i++) {            System.out.print(arr[i] + " ");        }    }}

输出结果:

test100的 arr数组
100 2 3
main方法里的 arr数组
100 2 3

image

【再看一个案例】

B类中编写一个test200()方法,可以接收一个Person(age,sal)对象,在方法中修改对象属性,看看原来对象是否有变化。

public class MethodParameter02 {
    public static void main(String[] args) {
        B b = new B();
    //  测试
        Person p = new Person();
        p.name = "jack";
        p.age = 10;
        b.test200(p);
        System.out.println("main里的 p.age=" + p.age); // 2000

    }
}
   class Person {
    String name ;
    int age ;
   }
    class B {
    public void test200(Person p){
        p.age = 2000; // 修改对象属性
    }
}

image

p=null和p = new Person()会对原来的对象有影响吗?

public class MethodParameter01 {
    public static void main(String[] args) {
        B b = new B();
    //  测试
        Person p = new Person();
        p.name = "jack";
        p.age = 10;
        b.test200(p);
        // 测试 : 如果test200 执行的是 p = null 下面的输出结果是?
        System.out.println("main里的 p.age=" + p.age);   // 10

    }
}
   class Person {
    String name ;
    int age ;
   }
    class B {
    public void test200(Person p){
//      	p.age = 2000; // 修改对象属性
// 思考:
//        p = null;

        p = new Person();
        p.name = "tom";
        p.age = 99;
    }
}

p=null情况分析:

image

p = new Person()....情况分析:

image

注:被闲置的对象最终会被当成垃圾被回收

总结:p在哪里调用它就造哪个栈里的p

【结论】

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

方法重载(Overload)

方法重载:一个类中可以有多个方法具有相同的名字,但这些方法的参数必须不同,及或者是参数的个数不同,或者是参数的类型不同。(方法名必须相同,但要求形参列表不一致)

重载的好处:

  • 减轻了起名的麻烦
  • 减轻了记名的麻烦

使用细节:

  • 方法名:必须相同
  • 形参列表:必须不同(形参类型或个数或顺序,至少有一样不同,参数名无要求
  • 返回类型:无要求

【示例】

下面的Area类中,getArea方法是一个重载方法

class Area {	float getArae(float r){	return 3.14f * r * r;	}	double getArae(float x, int y){	return x * y;	}	float getArae(int  x, float y){	return x * y;	}	double getArae(float  x, float y, float y){	return (x*x + y*y + z*z) * 2.0;	}}

下面的案例不是方法的重载而是方法的重复定义!——参数名无要求,同理返回类型也无要求

	double getArae(int x, int y){	return x * y;	}	double getArae(int a1, int a2){	return x * y;	}

可变参数

Java中允许将同一个类中多个同名同功能参数个数不同的方法,封装成一个方法

【基本语法】

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

}

【示例】

可计算两个数的和 , 3个数的和 , 4个数的和....

public class VarParameter01 {
    public static void main(String[] args) {
        Hspmethod m = new Hspmethod();
        int ans = m.sum(1,2,3,4);
        System.out.println(ans);
    }
}

class Hspmethod {
//    可计算两个数的和 , 3个数的和 , 4个数的和....
//    可以使用方法重载
//    public int sum(int n1, int n2){
//        return n1 + n2;
//    }
//    public int sum(int n1, int n2, int n3){
//        return n1 + n2 + n3;
//    }
//    public int sum(int n1, int n2, int n3, int n4){
//        return n1 + n2 + n3 + n4;
//    }
    //.....
    // 上面的三个方法名相同,功能相同,但参数不同 -> 可使用可变参数优化
    
    //解读:
//    1、 int ... 表示接受的是可变参数 , 类型是 int ,即可接收多个int(0~多)
//	  2、 使用可变参数时,可以当作数组来使用 , 即 nums 可以当作数组
    public int sum(int... nums){
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            res += nums[i];
        }
        return res;
    }
}

【可变参数细节】

1、 可变参数的实参可以为0个或任意多个
2、 可变参数的实参可以为数组

3、可变参数的本质就是数组

4、可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后

5、一个形参列表中只能出现一个可变参数

public class VarParameter02 {
    public static void main(String[] args) {
        Hspmethod m = new Hspmethod();
        int[] arr = {1,2,3};
        m.f1(arr);
        
    }
}

class Hspmethod {
//  可变参数的实参可以为数组
    public void f1(int... nums){
    System.out.println(nums.length); // 3
    }
//  可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后   
     public void f2(String str, int... nums){

    }  
//	一个形参列表中只能出现一个可变参数    下面的写法是错误的!
     public void f3(double... nums, int... nums){

    }  
    
}

作用域

注意事项和使用细节:

  1. 属性(全局变量)和局部变量可以重名,访问时遵循就近原则

  2. 在同一个作用域中,比如在同一个成员方法中,两个局部变量不能重名。

  3. 属性生命周期较长,伴随着对象的创建而创建,伴随着对的销毁而销毁。局部变量,生命周期较短,伴随着代码块的执行而创建,伴随着代码块的结束而销毁。即在一次方法的调用过程中。

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

        Person01 p1 = new Person01();
        /*
        属性生命周期较长,伴随着对象的创建而创建,伴随着对的销毁而销毁。
        局部变量,生命周期较短,伴随着代码块的执行而创建,伴随着代码块的结束而销毁。
         */
        p1.say();// 当执行say()方法时 say()方法的局部变量比如name就会被创建,当name执行完毕后
        // name局部变量就销毁,但属性(全局变量)仍然可以使用

    }
}

class Person01 {
    String name = "tom";

    public void say(){
        // 细节:属性和局部变量可以同名,访问时遵循就近原则
        String name = "King";
        System.out.println("say() name=" + name);
    }

    public void hi(){
        String address = "北京";
       // String address = "上海"; // 错误:重复定义变量
        String name = "lwt";
    }

}

  1. 作用域范围不同

    全局变量/属性:可以在本类中使用,也可以在其它类中使用(通过对象调用)

    public class VarParameter01 {
        public static void main(String[] args) {
    
            Person01 p1 = new Person01();
            T t1 = new T();
            t1.test(); // 第一种跨类访问对象的属性方式
            t1.test2(p1); // 第二种跨类访问对象的属性方式
    
        }
    }
    
    class Person01 {
        String name = "tom";
    
        public void say(){
            // 细节:属性和局部变量可以同名,访问时遵循就近原则
            String name = "King";
            System.out.println("say() name=" + name);
        }
    }
    
    class T{
        // 使用Person01类中的“tom”
        public void test(){
            Person01 p1 = new Person01();
            System.out.println(p1.name);// tom
        }
    
        public void test2(Person01 p){
            System.out.println(p.name);// tom
        }
    
    }
    

    局部变量:只能在本类中对应的方法中使用

  2. 修饰符不同

    全局变量/属性可以加属性

    局部变量不可以加属性

构造方法/构造器(construcor)

构造方法是一种特殊的成员方法,它的主要作用是完成新对象的初始化,节省代码。当创建对象的时候,其实就是在调用构造器。

idea快捷键:alt + insert

【语法格式】

修饰符  类名(传参){


}

【特点】

  • 方法名必须和类名相同
  • 没有返回值,也不能写void
  • 在创建对象时,系统会自动调用该类的构造方法完成对对象的初始化。

构造器/构造方法使用细节

  1. 一个类可以定义多个不同的构造器,即构造方法的重载

    public class Details {
        public static void main(String[] args) {
        
        Person p = new Person("tom",18);// 第一个构造器
        Person p2 = new Person("jack");// 第二个构造器
        
        }
    }
    
    class Person {
        String name;
        int age;
        // 第一个构造器
        public Person(String pName, int pAge){
            name = pName;
            age = pAge;
        }
        // 第二个构造器(构造器也是一种方法,当然可以重载)
        public Person(String pName){
            name = pName;
        }
    }
    
  2. 构造器名必须和类名相同

  3. 没有返回值,也不能写void

  4. 构造器是完成对象的初始化,并不是创建对象

  5. 在创建对象时,系统会自动调用该类的构造方法。

  6. 如果我们没有定义构造器,系统会自动给类生成一个默认无参构造器(默认构造器),比如Car () {}

    public class Car {
        //成员变量----属性
        String color;
        int speed;
        int seat = 5;
        // Java会自动赠送每一个类一个无参的构造方法
        //在创建对象的时候,自己调用方法
        public Car() { // 默认构造器
            System.out.println("你好,我是构造方法");
        }
        public static void main(String[] args) {
    
            Car c1 = new Car();//默认调用的是构造方法------你好,我是构造方法
        }
    
    }
    
  7. 一旦定义了自己的构造器,默认的构造器就被覆盖了,就不能再使用默认的无参构造器,除非显式定义一下,即:Person(){} (这点很重要)

    image

【对象创建的流程分析】

class Person {
	int age = 90;
    String name;
    // 第一个构造器
    public Person(String pName, int pAge){
       
        age = pAge; // 给属性赋值
        name = pName; 
    }
}

Person p = new Person("小明", 18);

image

  1. 加载 Person类信息(Person.class),只会加载一次
  2. 在堆中分配地址空间(地址)
  3. 完成对象初始化【3.1、 默认初始化 age=0 name=null 3.2、显式初始化 age = 90,name = null,3.3、构造器的初始化 age = 20 name = "小明" 】
  4. 在对象堆中的地址,返回给p(p是对象名,也可以理解成对象的引用)——完成最终的初始化后才将地址赋给p,因此构造器是用来初始对象而不是创建对象(对象早已在堆中创建了)

this

什么是this:

Java虚拟机会给每个对象分配this,代表当前对象。

image

Java虚拟机会给每个对象分配this,代表当前对象,因此this与创建的对象的地址是一样的!

public class Details {
    public static void main(String[] args) {
    Person p = new Person("tom",18);
        System.out.println("当前对象的hashcode值:" + p.hashCode());
    }
}
class Person {
    String name;
    int age;
    public Person(String name, int age){
        this.name = name;
        this.age = age;
        System.out.println("this所代表的hashcode值:" + this.hashCode());
    }
}

输出结果:

this所代表的hashcode值:460141958
当前对象的hashcode值:460141958

小结:简单地说,哪个对象调用,this就代表哪个对象。

【this关键字使用细节】

  • this关键字可以用来访问本类的属性、方法、构造器

    package Constructor;
    
    public class Details {
        public static void main(String[] args) {
        T t = new T();
        t.f3();
    
        }
    }
    class T {
        String name = "tom";
        int num = 100;
        // this关键字可以用来访问本类的属性
        public void f3() {
        //传统方式
            System.out.println("name= " + name +" " + "num= " + num);
            //也可以用this
            System.out.println("name= " + this.name + " " + "num= " + this.num);
        }
    }
    
  • this用于区分当前类的属性(全局变量)和局部变量

    ​ 【主要是看有无局部变量】

    package Constructor;
    
    public class Details {
        public static void main(String[] args) {
        T t = new T();
        t.f3();
    
        }
    }
    class T {
        String name = "tom";
        int num = 100;
        // this用于区分当前类的属性(全局变量)和局部变量
        public void f3() {
            String name = "jack";
            int num = 999;
        //传统方式:先根据就近原则(作用域)来找,看方法体中有无局部变量
            System.out.println("name= " + name +" " + "num= " + num);// name= jack num= 999
            //this:明确访问当前对象的属性(全局变量)
            System.out.println("name= " + this.name + " " + "num= " + this.num);// name= tom num= 100
        }
    }
    
  • 访问成员方法的语法:this.方法名(参数列表)

    public class ThisDetails {
        public static void main(String[] args) {
        T t = new T();
        t.f2();
        
        }
    }
    class T {
        public void f1(){
            System.out.println("f1()方法...");
        }
        public void f2(){
            System.out.println("f2()方法...");
            // 调用本类f1
            // 第一种方式
            f1();
            // 第二种方式
            this.f1();
        }
    }
    
    输出结果:
            f2()方法...
            f1()方法...
            f1()方法...	
    
  • 访问构造器的语法:this(参数列表);注意只能在构造器中使用(只能在构造器中去访问另外一个构造器,必须放在第一条语句)

    public class Details {
        public static void main(String[] args) {
        T t = new T();
        }
    }
    
    class T { 
     /*
     注意: 访问构造器语法:this(参数列表);必须放置在第一条语句
      */
        public T(){
            //在这里去访问 T(String name, int age)构造器
            this("tom",18);
            System.out.println("T() 构造器");
        }
        public T(String name, int age){
            System.out.println("T(String name, int age) 构造器");
        }
    }
    
    
  • this不能在类定义的外部使用,只能在类定义的方法中使用

    学习内容源自视频:b站韩顺平老师说Java

posted @ 2021-10-14 19:46  时间最考验人  阅读(55)  评论(0编辑  收藏  举报