详解面向对象编程

发布于:2022-12-20 ⋅ 阅读:(688) ⋅ 点赞:(0)


一、初识类和对象

示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
类就是一类对象的统称。对象就是这一类具体化的一个实例。
这一类对象所具备的行为和方法都在类中定义。拿我和彭于晏举例子,首先我们两都是实实在在存在的人类,各自都能被当成一个对象,那我和彭于晏有啥相同和区别呢?首先我们都有五官,他有的东西,我也有,其他任何人类也是都有的,他能呼吸,我也能呼吸,其他人类也能呼吸,但是区别是在我们都有的属性但是我们的属性值不同,我们虽然都有五官,但是我的五官比彭于晏帅,但是我们呼吸这种行为的属性值是不同的。

1.1类的实例化

在这里插入图片描述

基本语法如下:

// 创建类
class <class_name>{  
    field;//成员属性
    method;//成员方法
}
// 实例化对象
<class_name> <对象名> = new <class_name>();

具体实例如图,并且注意成员变量在类中有默认值
默认值规则:
对于各种数字类型, 默认值为 0.
对于 boolean 类型, 默认值为 false.
对于引用类型(String, Array, 以及自定制类), 默认值为 null
在这里插入图片描述

1.2类和对象以及存储情况

类是如何产生对象的:
所有类的对象都是引用数据类型
在这里插入图片描述
如何使用对象的属性和方法:

在这里插入图片描述
在这里插入图片描述
调用per1时因为改变了类中的属性值,所以调用.print
方法时是打印属性值。per2没有改变属性值,所以打印出对应的默认值。

存储情况(了解即可)
第一步:在堆中开辟空间,Person类new了一个对象。每个成员变量,在每一个new的对象中都有,都在堆中存储。
第二步:per1是在主方法中定义的局部变量,所以在栈中存储。存储的是一个堆中对象内存地址。
第三步:per1访问了堆内存中的地址,将属性值给改变了。
在这里插入图片描述
注意:如果此时执行per1=per2,相当于是栈中指向per1的地址变为指向per2

1.3类的引用

关于特殊值null

null 在Java中表示 “ 空引用 ” ,只有名字没有去保存任何堆内存中的地址,如果直接使用值为 null 的引用去操作 “.” 任何成员属性或方法,将会产生编译错误。

class Person {
    public String name;
    public int age; }
class Test {
    public static void main(String[] args) {
        Person person = new Person();
        System.out.println(person.name.length());   // 获取字符串长度
   }
}
// 执行结果
Exception in thread "main" java.lang.NullPointerException
        at Test.main(Test.java:9)

引用它的成员方法name.length时,因为name此时是默认值null,所以空指针异常。类似C 语言中的空指针. 如果对 null 进行 . 操作就会引发异常.

注意,但也不是所有null都报错,如图所示

class Test {
	public static void hello() {
	    System.out.println("hello");
	}
}
public class MyApplication {
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Test test=null;
		test.hello();
	}

这个代码运行起来,并不会发生错误,为什么呢,因为hello这个方法是静态的,所以是属于Test这个类的,test.hello,发挥作用的还是Test.hello(),所以并不会发生空指针异常。

二、构造方法

2.1 定义

构造方法是非常特殊的一类方法,使用关键字new实例化对象实际上就是调用的该类的构造方法。

使用关键字new产生一个对象时,大概分为以下两步:
1.为对象在堆中分配空间。
2.调用对象的构造方法为对象成员变量赋值。

构造方法语法规则
1.方法名与类名称完全相同
2.构造方法没有返回值声明(不是void)
3.一个类至少存在一个构造方法,若没有显示定义,编译器会生成一个默认的无参构造。

无参构造

注意只是new了一个对象,并没有调用该方法。说明当我们new对象时默认产生一个构造方法。
在这里插入图片描述
当我们没有定义无参构造时(实质就是给成员变量赋值)
在这里插入图片描述
在没有进入无参构造时,成员变量还是默认值
在这里插入图片描述

有参构造

调用new Person()中的无参构造时没有定义变量,但是类中只有一个有参构造,所以默认的无参构造没了
在这里插入图片描述
在这里插入图片描述

2.2构造方法的内存分析

实质就是给成员变量赋值(方便理解)
1.先在堆上开辟一块空间(大小由该类中成员变量的属性决定)

2.调用对象的构造方法为所有成员变量赋值
成员变量的就是构造方法中进行赋值的,还没进入构造方法之前是默认值。
有初值的成员变量同样是在构造方法中赋值。其实new Person()就是调用了一下构造方法。(下图中红色方框内可以理解为一回事)
在这里插入图片描述

2.3构造方法重载

定义:构造方法是为了类中的成员变量赋值的,此时的重载只可能是参数的个数不同,不可以是类型不同。

代码如下

class Person { 
 
 private String name;//实例成员变量
 private int age; 
 private String sex; 
 //默认构造函数 构造对象 
 public Person() { 
 this.name = "xx"; 
 this.age = 10; 
 this.sex = "男"; 
 } 
 //带有3个参数的构造函数
 public Person(String name,int age,String sex) { 
 this.name = name; 
 this.age = age; 
 this.sex = sex;
 } 
 public void show(){ 
 System.out.println("name: "+name+" age: "+age+" sex: "+sex); 
 } 
 
} 
public class Main{ 
 public static void main(String[] args) { 
 Person p1 = new Person();//调用不带参数的构造函数 如果程序没有提供会调用不带参数的构造函数
 p1.show(); 
 Person p2 = new Person("wyx",18,"男");//调用带有3个参数的构造函数
 p2.show(); 
 } 
} 
// 执行结果
name: xx age: 10 sex: 男
name: wyx age: 18 sex:

切记都是默认先赋值!!!!!

思考:能否在对象初始化赋值完成后用对象调用构造方法?

在这里插入图片描述
解析:自己调用自己?构造方法就是为了产生对象,而后对象又来调用自己?JVM产生对象时调用构造方法,对象实例化结束,无法在程序中手动调用构造方法再次实例化对象。综上所述,不可行!

思考:成员变量在定义时赋初始值如何运行

在这里插入图片描述
解析:首先,new Person()时,产生对象会先在堆区开辟空间。而后就调用了这个构造方法,这些成员变量的赋值就是在构造方法中进行的,只要进入了构造方法就会赋初识定义的值。

三、static关键字

看见static,本能反应,与对象无关!!!!!!!!
静态的含义:表示所有对象共有在这里插入图片描述

为什么引入static来修饰属性?

在这里插入图片描述

在上面的例子我们可以看到country这个变量,很多人的属性值是一样的,比如中国人的国籍都是中国,所以我们可以用static来修饰,表示这个属性是很多对象的共同拥有的,只要修改一次就可以将所有对象相应的类属性都改变。

3.1static修饰类属性

static修饰属性时的性质

1.static修饰的属性,在JVM的方法区存储,所有该类的所有的对象共享此属性
2.static修饰的属性,表示是这个类的属性,所以无需使用对象访问,直接用类名称调用即可,但是也可以用对象来调用,但是最好用类名称调用,因为不规范
3.修改一些类属性的值,属于该类所有的对象的该属性值都会变
在这里插入图片描述

3.2static修饰类属性的内存分析(了解即可)

static修饰的属性在JVM的方法区中存储,所有该类对象共享该属性。
在这里插入图片描述

在这里插入图片描述
先要有Person类才能产生对象(堆中),首先将Person类加载到主类中,Person类中的所有static变量就会被加载到方法区中。所以方法区中只有一个country属性,所有对象访问的都是同一块内存。

3.3静态方法

如果在任何方法上应用 static 关键字,此方法称为静态方法。

静态方法属于类,而不属于类的对象。
可以直接调用静态方法,而无需创建类的实例。
静态方法可以访问静态数据成员,并可以更改静态数据成员的值。

class TestDemo{
    public int a;
    public static int count;
    
    public static void change() {
        count = 100;
        //a = 10; error 不可以访问非静态数据成员
   }
}
public class Main{
public static void main(String[] args) {
        TestDemo.change()//无需创建实例对象 就可以调用
        System.out.println(TestDemo.count);   
   }
}
输出结果:a=100

注意事项1: 静态方法和实例无关, 而是和类相关. 因此这导致了两个情况:
静态方法不能直接使用非静态数据成员或调用非静态方法(非静态数据成员和方法都是和实例相关的).
this和super两个关键字不能在静态上下文中使用(this 是当前实例的引用, super是当前实例父类实例的引用, 也
是和当前实例相关).
注意事项2:我们曾经写的方法为了简单, 都统一加上了 static. 但实际上一个方法具体要不要带 static, 都需要是情形而定.
main 方法为 static 方法.

在这里插入图片描述

3.3final和static关键字区别

一个变量一个是常量
final修饰后就不能再修改属性值了。
在这里插入图片描述

内存分析(了解即可)

在这里插入图片描述

通常会用static和final定义一个成员常量

在这里插入图片描述
命名规则如下:
在这里插入图片描述
static常考笔试题(关于static关键字的理解):
只要有类存在就可以调用country属性,包含该类的null引用,这个属性是静态属性即便没有对象也可以访问。属性归类所有,有没有对象都可以用。
在这里插入图片描述
在这里插入图片描述

总结一下

在这里插入图片描述

3.4static修饰方法(面试常考问题)

思考:可以在一个方法中定义static吗?

在这里插入图片描述
局部变量是保存在栈中的,而静态变量保存于方法区,局部变量出了方法就被栈回收了,而静态变量不会,所以在局部变量前不能加static关键字。
笔试题中如果出现在一个方法中中定义一个static变量属于编译错误

思考:为什么主方法是静态方法呢?

 public static void main(String[] args) {

因为主方法是程序的入口,如果主方法也是成员方法也就需要对象调用。
如果一个程序连入口都没有又从哪产生对象?
所以程序从主方法执行,要用静态方法直接调用才能调用起来,无需产生对象。

静态方法是否能访问成员方法和成员变量?

答案是不行的,因为静态方法的调用不需要对象,哪怎么可能可以调用成员域的成员变量和成员方法,成员变量和方法需要对象去调用。(static静态,是不用对象去访问属性和方法)

成员方法是否能访问静态变量和静态方法?

调用成员方法,肯定已经有对象了,没有对象都能调用静态方法,现在有对象,肯定能调用。

因此得出结论:
1.在静态方法只能调用静态方法或者静态属性,static家族之间可以互相调用,不能直接调用成员方法和成员属性,必须通过对象来调用。
2.在成员方法中既可以调用成员方法,也可以调用静态方法(此时都已经产生了对象,一定是可以访问静态域)

普通的类能否使用static修饰

在这里插入图片描述
在这里插入图片描述

在类中final和static的区别和联系

final是修饰成员常量,在堆区存储
static修饰静态变量,在方法区存储
通常我们在类中用final定义一个成员常量都是需要将static final共同使用,成为类的常量

小结

class Person {
    public int age;//实例变量   存放在对象内
    public String name;//实例变量
    public String sex;//实例变量
    public static int count;//类变量也叫静态变量,编译时已经产生,属于类本身,且只有一份。存放在方法区
    public final int SIZE = 10;//被final修饰的叫常量,也属于对象。被final修饰,后续不可更改
    public static final int  COUNT = 99;//静态的常量,属于类本身,只有一份 被final修饰,后续不可更//实例成员函数
    public void eat() {
       int a = 10;//局部变量
       System.out.println("eat()!");  
   }
    //实例成员函数
    public void sleep() {
       System.out.println("sleep()!");  
   }
    //静态成员函数
     public static void staticTest(){
         //不能访问非静态成员
        //sex = "man"; error
        System.out.println("StaticTest()");
     }
}
public class Main{
 public static void main(String[] args) {
 //产生对象 实例化对象
        Person person = new Person();//person为对象的引用
        System.out.println(person.age);//默认值为0
        System.out.println(person.name);//默认值为null
        //System.out.println(person.count);//会有警告!
        //正确访问方式:
        System.out.println(Person.count);
        System.out.println(Person.COUNT);
        Person.staticTest();
        //总结:所有被static所修饰的方法或者属性,全部不依赖于对象。
        person.eat();
        person.sleep();
 }
}

四、封装

4.1定义

在我们写代码的时候经常会涉及两种角色: 类的实现者和类的调用者.
封装的本质就是让类的调用者不必太多的了解类的实现者是如何实现类的, 只要知道如何使用类就行了.这样就降低了类使用者的学习和使用成本, 从而降低了复杂程度

封装带来的特性是:保护性,易用性。(封装有很多中表现形式)

类比例子:(便于理解)
拿银行卡这个类来说,银行卡有卡号,余额,密码三个属性,如果这三个属性都直接暴露在外部肯定是不合理的(直接就在卡上贴着)——>不安全,不能让这些属性可以通过对象直接调用

对于汽车这个类来说,车真正发动起来,我们需要启动发动机,变速箱,等等属性,但是现实中,我们只需要一键启动就行,对于这些具体的属性是不可见的,也是我们不关注的——>易用性

注意:private只是封装的其中一种,并不是封装就等于privata

4.2权限修饰符

指的是修饰的属性,方法,类,可见的范围有多大,一共有四大访问修饰符,可见范围从小到达

private<default<protected<public

private 表示私有的,被private修饰的属性和方法,只在当前类内部可见,出来类的范围,对外部就完全隐蔽了,外部不知道其的存在
default 不写任何修饰权限的修饰符,表示的就是default,包访问权限
protected
public 公开的,被public修饰的,当前整个程序(项目)都是可以使用的

4.3private实现封装

private/ public 这两个关键字表示 “访问权限控制” 。

被 public 修饰的成员变量或者成员方法, 可以直接被类的调用者使用. 被 private 修饰的成员变量或者成员方法, 不能被类的调用者使用。
在这里插入图片描述
bank.cardNum就会报错

在这里插入图片描述
解决方案:
在这里插入图片描述

那如何在外部去实现这些私有属性呢?

需要使用这个类提供的getter(取值)和setter(修改值)的方法,而且哪些属性要提供setter,哪些属性只提供getter,或者都提供,需要由这些属性来决定。
当我们使用 private 来修饰字段的时候, 就无法直接使用这个字段了.
在这里插入图片描述

解决方案(实例代码)

class Person { 
 private String name;//实例成员变量
 private int age; 
 
 public void setName(String name){ 
 //name = name;//不能这样写
 this.name = name;//this引用,表示调用该方法的对象
 } 
 public String getName(){ 
 return name; 
 } 
 
 public void show(){ 
 System.out.println("name: "+name+" age: "+age); 
 } 
} 
public static void main(String[] args) { 
 Person person = new Person(); 
 person.setName("wyx"); 
 String name = person.getName(); 
 System.out.println(name); 
 person.show(); 
} 
// 运行结果
wyx 
name: wyx age: 0

当set方法的形参名字和类中的成员属性的名字一样的时候,如果不使用this, 相当于自赋值. this 表示当前实例的引用.

关于易用性和保护性的两个具体实例(仅供参考)
银行修改密码和汽车一键启动功能

// 公共访问权限,主类,这个类在当前项目中都是可见的
public class PrivateTest {
    public static void main(String[] args) {
        Car car = new Car();
        // 易用性
        car.start();
        Bank bank = new Bank();
        // 当password属性被private之后,对于这个属性就是一个保护
        // 类的外部要想使用这个属性,必须按照我的规则去使用(getter和setter)
        bank.setPassword();
        System.out.print("修改后的密码为:");
        System.out.println(bank.getPassword());
    }
}
// 银行类,缺省修饰符,包访问权限
class Bank {
    // 卡号
    private int cardNum;
    // 余额
    private double sal;
    // 密码
    private String password = "123456";
    // alt + insert 快速生成getter和setter方法
    // get+属性名称 = 方法命名
    public int getCardNum() {
        return cardNum;
    }

    public double getSal() {
        return sal;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword() {
        Scanner scanner = new Scanner(System.in);
        int count = 0;
        // 验证当前密码是否正确才能修改
        while (true) {
            System.out.println("请输入您现在的旧密码:");
            String oldPass = scanner.nextLine();
            count ++;
            // 所有引用类型的对象比较使用equals方法
            if (oldPass.equals(password)) {
                // 密码输入正确,修改密码
                System.out.println("密码正确,正在改密码~~");
                System.out.println("请输入您的新密码:");
                String newPass = scanner.nextLine();
                password = newPass;
                System.out.println("密码修改成功!");
                break;
            }else {
                // 输入密码有误
                System.out.println("密码错误,请查证后再试");
                if (count == 3) {
                    System.out.println("尝试次数过多,银行卡已经锁定");
                    break;
                }
            }
        }
    }
}

class Car {
    private String engine;
    private String bianSuXiang;
    private String guLu;
    // 一键启动
    public void start() {
        engine = "启动引擎";
        bianSuXiang = "启动变速箱";
        guLu = "轮胎开始转";
        System.out.println("BMW 启动了~~");
    }
}

五、this关键字

1、在本类的成员方法中,访问本类的成员变量; (重名变量时使用)
2、在本类的成员方法中,访问当前对象方法(包括普通成员方法和构造方法);
3.表示当前对象的引用;

5.1调用当前对象的成员变量

class Person { 
 private String name;//实例成员变量
 private int age; 
 private String sex; 
 
 //默认构造函数 构造对象
 public Person() { 
 //this调用构造函数
 this("wyx", 18, "male");//必须放在第一行进行显示
 } 
 
 //这两个构造函数之间的关系为重载。
 public Person(String name,int age,String sex) { 
 this.name = name; 
 this.age = age; 
 this.sex = sex; 
 } 
 public void show() { 
 System.out.println("name: "+name+" age: "+age+" sex: "+sex); 
 } 
} 
public class Main{ 
 public static void main(String[] args) { 
 Person person = new Person();//调用不带参数的构造函数
 person.show(); 
 }
 // 执行结果
name: bit age: 18 sex: male

如果在有参构造中不加this关键字,输出的还是默认值,没有指明给哪个对象赋值。上述方法还可以new Person(“wyx”,18,“male”),打印出来是一样的效果

5.2this调用类中的成员方法

先调用了test方法,可以不加this
在这里插入图片描述

5.3this在构造方法之间相互调用

会发现下面的代码有重复部分

在这里插入图片描述
若不同参数的构造方法之间出现了重复的调用,可以使用this(参数)调用其他的构造方法。
解决办法:遵循代码简洁性
在这里插入图片描述

5.4this调用需要注意的两个点

this调用其他构造方法必须放在首行
在这里插入图片描述
this调用构造方法不能形成“死循环”
编译报错
在这里插入图片描述

思考:this关键字能否出现在静态方法中

在这里插入图片描述

六、代码块

代码块就是括号{}括起来的代码部分
在这里插入图片描述

6.1普通代码块

a的作用域仅限于{}内部,所以输出20
在这里插入图片描述

6.2成员代码块(构造块)

直接定义在类中,不加任何修饰符,使用{}扩起来的代码块。优先于构造方法执行,并且每次产生一个对象,就执行一次构造块。有几个对象就执行几次构造块。
在这里插入图片描述
例如:此时先执行代码块(序号1),name=test。结束后再执行有参构造(序号2),name又等于赋值进来的“小狗”。相当于赋值了两次。
在这里插入图片描述

6.3静态代码块

定义:定义在类中,使用static修饰的代码块。静态代码块在类加载时只执行一次
具体代码实例如下:调用顺序是 3 21 21。主方法要使用animal这个类时就会把这个类加载到方法区中,并且不管new多少对象只加载一次。animal1首先执行构造块2,再执行有参构造1。animal2同理。
在这里插入图片描述
主类中的静态代码块优先于主方法。因为JVM要执行主方法首先执行主类,主类加载后,静态代码块就开始执行了。
在这里插入图片描述

这个类一加载所有静态变量都是其默认值,所以此时age=0。然后进入类中age才等于初始值10,再进入静态块等于100.
在这里插入图片描述
当类中存在静态变量,静态代码块[1…N]则在类加载时,这些静态变量的赋值以及静态代码块,最终都会合并为一个大的静态代码块由JVM执行。最后等于100.
在这里插入图片描述

七、匿名对象

定义

匿名只是表示没有名字的对象,没有引用的对象称为匿名对象,匿名对象只能在创建对象时使用。
如果一个对象只是用一次, 后面不需要用了, 可以考虑使用匿名对象。
例如new Person()
只是用来测试类中的某些功能,用一次就被JVM的方法区给销毁了。

class Person { 
 private String name; 
 private int age; 
 public Person(String name,int age) { 
 this.age = age; 
 this.name = name; 
 } 
 public void show() { 
 System.out.println("name:"+name+" " + "age:"+age); 
 } 
} 
public class Main { 
 public static void main(String[] args) { 
 new Person("wyx",19).show();//通过匿名对象调用方法
 } 
} 
// 执行结果
name:wyx age:19

八、toString方法

println方法传入一个引用对象时,默认调用的就是toString方法。
在这里插入图片描述
最后打印出的内容就是aaabbb,println打印的内容是toString的返回值,但是toString方法内部打印出了aaa
在这里插入图片描述

本文含有隐藏内容,请 开通VIP 后查看