JavaSE之面向对象

前言

也是回顾了一遍面向对象的一些基础吧,用自己的话讲出来了。可能最好还是画图能更清晰一点描述出来,但目前来说我还是不太方便,也没有太多心思去花时间补充自己基础的东西,权且是用自己的话去描述这些思想吧。还好有室友一起学习,平时倒也能经常去巩固这些基础的东西吧,能比较方便的去查漏补缺,不至于出现问题。

1 面向对象基础

1.1 面向对象思想

1.1.1 概述

谈到面向对象思想的话就得对比一下面向过程思想了。

面向过程的思想就是做什么事必须要有过程,一步步做,比如做饭,面向过程要淘米、浸水、煮饭这样一步步执行完。

而面向对象的思想就不同,做饭的时候,只需要两个对象:米饭和会做饭的人,让会做饭的人使用他做饭的方法将米饭的属性从生变成熟就可以了。

虽然看起来复杂了许多,但如果是像做菜这样步骤非常多,做菜的方式也都不相同的情况,用面向过程的思想就会变得越来越复杂,必须一步步的按顺序执行下来;

而使用面向对象的思想只需要使用一个方法,再拿上对应的对象就行了,只要对做菜的方法稍作修改,就能适应绝大多数情况。

1.1.2 面向对象三大思想

面向对象思想从概念上讲分为以下三种:OOA、OOD、OOP 
OOA:面向对象分析(Object Oriented Analysis) 
OOD:面向对象设计(Object Oriented Design) 
OOP:面向对象程序(Object Oriented Programming

1.1.3 三大特征

封装性:所有的内容对外部不可见 
继承性:将其他的功能继承下来继续发展 
多态性:方法的重载本身就是一个多态性的体现

1.2 类与对象

1.2.1 两者的关系

类是对象的抽象,而对象是类的实例。
比如我是一个人,那么人就是一种类,而我就是一个具体的对象。

1.2.2 类的成员

类由属性和方法构成:

  • 属性:相当于人的一个个的特征,比如我是男的,我的年龄是多少岁。
  • 方法:相当于人的行为,比如我会说话,会吃饭,会睡觉。

附:类里面还有内部类,代码块,构造方法,之后会讲到。

1.2.3 属性与方法

属性定义格式: 
	数据类型 属性名; 
属性定义并赋值的格式: 
	数据类型 属性名 = 初始化值; 
	
方法定义格式: 
权限修饰符 返回值类型 方法名(形式参数列表){ 
    //方法体 
    return 返回值; 
}

1.2.4 对象的创建与使用

一个类要想真正的进行操作,则必须依靠对象,对象的定义格式如下: 

类名称 对象名称 = new 类名称() ; 

如果要想访问类中的属性或方法(方法的定义),则可以依靠以下的语法形式: 

访问类中的属性: 对象.属性 ; 

调用类中的方法: 对象.方法(实际参数列表) ; 

1.3 创建对象内存分析

1.3.1 栈

Java栈的区域很小 , 大概2m左右 , 特点是存取的速度特别快

栈的特点:先进后出

存储速度快的原因:

栈内存, 通过 ‘栈指针’ 来创建空间与释放空间 ,指针向下移动, 会创建新的内存, 向上移动, 会释放这些内存 ,

这种方式速度特别快 , 仅次于PC寄存器。

但是这种移动的方式, 必须要明确移动的大小与范围 , 明确大小与范围是为了方便指针的移动 ,

这是一个对于数据存储的限制, 存储的数据大小是固定的 , 影响了程序的灵活性,所以我们把更大部分的数据 存储到了堆内存中。

栈中存储的是:

基本数据类型的数据 以及 引用数据类型的引用,

例如:

int a =10; 

Person p = new Person(); 

10存储在栈内存中 , 第二句代码创建的对象的引用§存在栈内存中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aL3cafcQ-1617717880431)(https://i.loli.net/2021/04/06/ghM52sOzSqGeH9c.png)]

1.3.2 堆

存放的是类的对象 。

Java是一个纯面向对象语言, 限制了对象的创建方式:

所有类的对象都是通过new关键字创建 ;

new关键字, 是指告诉JVM , 需要明确的去创建一个新的对象 , 去开辟一块新的堆内存空间:

堆内存与栈内存不同, 优点在于我们创建对象时 , 不必关注堆内存中需要开辟多少存储空间 , 也不需要关注内存占用

时长 !

堆内存中内存的释放是由GC(垃圾回收器)完成的 ;

垃圾回收器 回收堆内存的规则:

当栈内存中不存在此对象的引用时,则视其为垃圾 , 等待垃圾回收器回收 !

例如:

Person p0 = new Person(0); 
Person p1 = new Person(1);
Person p1 = p0;
// 此时p1原先指向的new Person(1)仍然在堆中,但没有栈引用了,因此会被视为垃圾,会被垃圾回收掉。

1.3.3 方法区

存放的是

- 类信息

- 静态的变量

- 常量

- 成员方法

方法区中包含了一个特殊的区域 ( 常量池 )(存储的是使用static修饰的成员)

1.3.4 PC寄存器

PC寄存器保存的是 当前正在执行的 JVM指令的 地址 ,

在Java程序中, 每个线程启动时, 都会创建一个PC寄存器。

1.3.5 本地方法栈

保存本地(native)方法的地址。

1.4 构造方法

1.4.1 构造方法概述

平时new 的一个对象就是使用的构造方法,如:Person p = new Person(); Person()就是用了构造方法。

作用:用来初始化对象。

特点:所有的Java类中都会至少存在一个构造方法

如果一个类中没有明确的编写构造方法, 则编译器会自动生成一个无参的构造方法, 构造方法中没有任何的代

码! (这里没讲继承,实际上第一行还有super()的,后续再讲。)

注意

1、如果自行编写了任意一个构造器, 则编译器不会再自动生成无参的构造方法!所以以后写了有参构造一定要补无参构造!

2、构造方法必须跟类同名,而普通方法不行。

其他的东西构造方法跟普通方法基本上差不多。后面的话还会需要结合继承来讲构造方法。

1.4.2 方法重载

方法的重载指的是方法能够有不同的参数和返回值。调用同名方法的时候能根据传入参数的类型来使用对应的方法。

注意:仅有返回值不同的同名方法不能重载!必须参数不同的同名方法才能重载!

1.4.3 构造方法重载

构造方法也是能重载的,和普通方法一样。

1.4.4 匿名对象

没有对象名称的对象 就是匿名对象。

匿名对象只能使用一次,因为没有任何的对象引用,所以将称为垃圾,等待被G·C回收。

只使用一次的对象可以通过匿名对象的方式完成,这一点在以后的开发中将经常使用到

2 面向对象进阶

2.1 封装

封装的意义在于保护或者防止代码(数据)被我们无意中破坏;

保护成员属性,不让类以外的程序直接访问和修改。

封装原则:隐藏对象的属性和细节,仅对外公开访问方法,并且控制访问级别。

2.2 this关键字

作用:当成员变量与方法局部变量重名时,使用this来指代成员变量。

2.3 静态static

static修饰的属性/方法在类加载时就会加载,会加载到方法区中;可以使用类名.属性/方法直接调用static修饰的属性/方法。
注意:static修饰的属性/方法不能被对象调用!只能类名调用!

2.4 包

包就是用来import的= =。

好吧,包实际上就是一个文件夹,为了防止class文件越来越多,就有了包这种东西,可以把很多相似功能的类放到一起。

另外的话也是为了更好的封装类,便于解耦。

解耦就是尽量使类与类之间不相互影响,比如说删掉一个类另外的类也能运行。当然一般是不会直接删掉类的。

2.5 权限修饰符

封装四种修饰符的不同作用域:private(类内部),缺省(同包下),protected(不同包的子类),public(公共的)

优先级:private < 缺省 < protected < public

优先级高的作用域包括优先级低的作用域。

2.6 代码块

静态代码块和代码块。就是用来写语句的,相当于没有参数没有名字的方法,但是只会执行一次。

代码块是基于对象的,而静态代码块是基于类的

每构造一个对象就会执行一次代码块,而加载一个类的时候就会执行静态代码块。

后续还要结合继承来继续介绍。

2.7 main方法

public static void main(String[] args)

大体顾名思议吧,公共的静态的无返回值的方法。在运行前就会被加载。

String[] args参数是我们在命令行使用Java命令运行Java程序时传入的参数。

3 面向对象高级

3.1 继承

3.1.1 概述

格式:

修饰符 class 子类 extends 父类{
}

子类能继承并使用父类所有非private修饰的属性和方法。

在Java中只能单继承,一个或多个子类只能继承一个父类。

继承能够多级继承,即孙子类继承子类,子类继承父类。

另外,Java里所有没有继承的类都会默认继承Java的Object类,而继承又能多级继承,也就是说,Java里面所有的类都是Object的子类!

3.1.2 子类实例化分析

子类在加载的时候,如果子类继承了一个父类,那么就会先加载父类,等父类完全加载完才会加载子类。

3.1.3 super

在Java中,super关键字指代父类。如果父类和子类有重名的方法或属性,就可以用super来区分父类和子类的属性,和this的作用类似。

在子类的构造器中,编译器默认会在构造器的第一句加上super(),这一句会率先调用父类的构造方法,这也就是为什么在构造的时候先会执行父类的构造方法。注意,要区分构造和类加载。构造是基于对象的,而类加载是基于类的。

如果父类写了有参的构造方法,那么子类必须写一个构造方法,构造方法第一句加上super(参数),否则会报错!

因为编译器默认的无参构造方法被覆盖了!导致子类默认的super()找不到父类的无参构造!所以必须写出来!

如果父类补上了无参构造就不会报错,这就是为什么一定要注意补无参。

3.1.4 重写和重载

重写指的是子类对于父类方法的重写,是继承的多态;多态简单说就是一个事物有不同的形态,详细后续会讲到

1、子类重写父类的方法必须保证方法名字、方法的参数类型、参数个数完全相同! 返回参数类型也必须相同!

2、访问权限不能比父类中被重写的方法的访问权限更低,例如,如果父类的一个方法声明为public,则子类重写该方法不能用protected修饰

3、声明为static和private的方法不能被重写,但是能被再次声明。

4、final方法也不能被重写,详见final的描述。

重载指的是方法的重载,同名的方法可以有不同的参数,是方法的多态;

同名方法不能仅通过返回值类型不同实现重载!必须方法参数不同才能重载!

3.1.5 final

final顾名思议,是最终的,不可改变的;

可以用来修饰类、属性和方法,final修饰的变量会变成常量。

  • final修饰的类不可被继承;
  • final修饰的属性只能赋值一次,不可被再次赋值;
    • 如果final修饰的是类的成员变量,则必须初始化(直接赋值或构造方法赋值)
    • 如果final修饰的是局部变量,可以先声明再赋值,但依然只能赋值一次
  • final修饰的方法不能被子类重写;

static final修饰的变量视为常量!常量通常用大写字母来表示!

3.1.6 代码块与继承

在加载类时,先会加载父类的静态代码块,再加载子类的静态代码块!

如果使用了子类的构造方法,如: Person p = new Son();

每次构造对象时,先会执行父类的构造方法,在执行父类构造方法之前,先执行代码块!

等父类构造完,才会构造子类,当子类构造方法执行前,先执行子类的代码块!

public class Father {
    static {
        System.out.println("父类静态代码块");
    }
    {
        System.out.println("父类代码块");
    }
}
public class Son extends Father {
    static {
        System.out.println("son静态代码块");
    }
    {
        System.out.println("son代码块");
    }
}
public class AbsClass {
    public static void main(String[] args) {
        Father father = new Son();
        Father father2 = new Son();
    }
}

3.2 抽象

3.2.1 抽象类

如果一个类中需要有暂时不确定怎么做的方法,那么就可以定义一个抽象方法,有抽象方法的类就叫抽象类。

  • 使用abstract修饰的类就是抽象类
  • 抽象方法:只声明但未实现的方法就是抽象方法,没有方法体,必须使用abstract来声明。
  • 抽象类中可以没有抽象方法,但有抽象方法的类一定是抽象类!

抽象类不能被实例化,即不能通过new 构造方法()来创建抽象类的对象。

注意:抽象类中有构造方法,只是抽象类不能通过构造方法来创建对象!

抽象类就是专门用来继承的!所以抽象类只能被public或protected修饰!同理抽象类也不能被final修饰!

如果一个类继承了抽象类,那么必须重写抽象类中所有的抽象方法!(当然没有抽象方法就不用重写了)

3.2.2 接口

接口其实就是特殊的抽象类。

接口中所有的方法都是抽象方法,默认使用public abstract修饰。

接口中可以也只能定义全局常量,默认使用public static final 修饰。

接口可以实现,跟继承一样,但不同的是接口可以多实现。

实现一个接口必须实现接口中定义的(默认为抽象)方法!

3.3 多态

多态简单说就是一个事物有不同的形态,包括继承的多态和方法的多态。方法多态有的人说不算多态,嘛,仁者见仁,智者见智。

方法多态就不讲了,就是重载,之前已经讲过了。

下面是继承的多态:

举个例子:交通工具作为父类,飞机、轮船、汽车作为子类继承了交通工具类。

正常来讲,交通工具应该都有一个行驶速度 v,这就是这个类的属性;而且也应该有一个移动方式,可以设为 move 方法;

而飞机、轮船、汽车都是交通工具,这就是多态的体现了。另外这三个都应该重写move方法对吧

利用多态这种特性,我们就可以灵活的切换不同的事物,不用重新再造一个对象了!!

文字还是比较难描述,上代码吧:

// 交通工具
abstract class Transport {

    public double v;

    public abstract void move();
}
// 车
class Car extends Transport{

    public double v = 1.0;

    @Override
    public void move() {
        System.out.println("车的移动方式");
    }
}
// 飞机
class Plan extends Transport{

    @Override
    public void move() {
        System.out.println("飞机的移动方式");
    }
}

public class Demo {
    public static void main(String[] args) {
        // 以往,每个类我们只能造不同的对象:
        Car a = new Car();
        a.move();
        Plan b = new Plan();
        b.move();

        // 现在,我们可以利用多态,直接用一个对象就能满足我们的需要了
        // 这里c是Transport类,但是使用的是car类的构造方法赋值
        Transport c = new Car();
        // 因为Car重写了move方法,虽然c是Transport类,但move方法其实是Car类中重写的方法!
        c.move();
        // 直接赋值!再使用move方法就是Plan类中重写的move方法了!
        c = new Plan();
        c.move();

    }
}

多态其实还是比较好理解的,但多态结合继承就有点难了,比如说Transport c = new Car();这个赋值可能新手都会看的有点懵,

而且怎么就用到子类重写的方法了?这就是Java以及面向对象的特性了,我只能意会,实在能力有限,不能言传了= =。不同人都会有不同的理解,具体只能看自己了,或许我以后能找到恰当的例子讲出来吧。而且后续的学习慢慢就能理解这种思想了。

3.4 Object

所有类的父类!原因继承那讲过。目前主要的就是 equals()方法和toString()方法

3.4.1 equals()

equals()是一个方法,作用是判断两个对象是否相等,返回true或false。 Object里面的equals()就是用的 ==比较,比较的是对象的引用,如果是基本数据类型,那么就是比较常量池里的值,而如果是对象,则比较的是对象指向的堆内存地址。如果想要比较两个对象,让他们的属性相同就能相等,那么可以重写equals()方法,比如:

public class Demo2 {
    public static void main(String[] args) {
        Man man1 = new Man();
        Man man2 = new Man();

        man1.a = 1;
        man1.b = 2;

        man2.a = 1;
        man2.b = 2;
        
        // ==比较的是堆内存地址,所以不相同;
        System.out.println(man1==man2);
        // 重写了equals方法,所以相同
        System.out.println(man1.equals(man2));
    }
}
class Man{
    int a;
    int b;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Man man = (Man) o;
        return a == man.a && b == man.b;
    }
}

重写equals方法也是多态与继承机制的体现~

3.4.2 toString()

toString()方法是用来显示一个对象的,

我们平常的System.out.println(a);就是用的a这个对象的toSting()方法来显示

重写toString()就能更好的显示一个对象!

3.5 内部类

四种:

1、成员内部类(类的成员)

2、局部内部类(方法内部)

3、匿名内部类(new 构造(){类},必须继承类或实现接口)

4、静态内部类

如果局部内部类要使用外部变量,只能使用外部的 final 变量!1.7以前必须加 final 才能使用,而1.8时使用变量会默认加上 final ,此时该变量不能再被赋值!否则会报错!

为什么?因为所有类都会编译成字节码文件,包括内部类,在编译时,内部类使用的外部变量会备份一份到内部类的字节码文件中。为了保证内部备份的变量和外部变量一致(不一致的话运行时会有问题,外部的类改了但内部的类变量没改),直接在规则上限制了变量必须不能更改。

3.6 包装类

Java提供了包装类,用来表示基本数据类型:

int ——> Integer ,char ——>:Character,double ——>Double,其他都是类似double这样首字母大写。

注意!:String 不是基本数据类型!它是 Java 特殊的类!char 的包装类是 Character!

包装类的作用:

1、能用利用面向对象的思想去处理基本数据类型,我们可以使用包装类内部的方法来快速的处理基本数据类型!

2、在泛型中(后续会讲)只能用包装类来表示基本数据类型!

3.7 可变参数

方法的参数加…表示可以输入零至多个值,如:

public int asd (String... a){
    ...
 };

使用时a相当于一个数组。用a[0]来取单个值!

注意:

1、可变参数只能出现在参数列表的最后! 原因:可变参数长度不确定,后面的参数不一定能接收的到

2、要注意因为可以不传入参数,所以使用时一定要记得验证是否为null !

3、同样因为传入参数数量不确定,所以要注意是不是会报数据越界异常!java.lang.ArrayIndexOutOfBoundsException

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值