写点什么

javaSE 基础复习之面向对象(多态,内部类,内存回收)

作者:Java高工P7
  • 2021 年 11 月 11 日
  • 本文字数:5252 字

    阅读完需:约 17 分钟

举例说明


//判断 对象 ooo 他的类型是否是 来自于 Brother 类


if(ooo instanceof Brother){


//进行向下转型


Brother bbb = (Brother) ooo;


//调用子类特有的成员


System.out.println(bbb.ageZi);


bbb.methodBro();


}

第 04 节 指鹿为马

案例代码


动物接口


//动物接口


public interface Animal {


//吃


public abstract void eat();


}


马类


//马类


public class Ma implements Animal{


@Override


public void eat() {


System.out.println("马吃草...");


}


public void pao(){


System.out.println("马儿跑.");


}


}


鹿类


//鹿类


public class Lu implements Animal{


@Override


public void eat() {


System.out.println("鹿吃艹");


}


public void tiao(){


System.out.println("鹿儿跳.");


}


}


测试类


//测试类


public class Test {


public static void main(String[] args) {


//创建对象


Ma make = new Ma();


use(make);


//直接调用方法(匿名对象的写法)


use(new Lu());


}


// 一个方法,既可以接收马的对象, 又可以接收鹿的对象


// 这个方法的参数应该怎么写呢?


// 这里就可以写 父类或者是父接口,采用多态传参


public static void use(Animal aa){ //Animal aa = new Ma();


//调用方法【共性方法父亲调用】


aa.eat();


//调用方法【如果想要访问特有的,应该向下转型】


//如果说 aa 对象来自于 Ma 类。 则向下转型成为 Ma


if (aa instanceof Ma){


//向下转型


Ma make = (Ma) aa;


//调用子类特有方法


make.pao();


}


//再次判断 如果说 aa 对象来自于 Lu 类。


if (aa instanceof Lu){


//向下转型


Lu luhan = (Lu) aa;


//调用子类特有方法


luhan.tiao();


}


}


}

第 05 节 优点缺点

优点


  1. 提高代码的扩展性


解释: 如果说,我们将方法的参数定义为 父类或者是父接口。那么就可以传递 子类或者实现类。


以前的写法是 每一个子类都需要作为方法的参数,可能写很多的方法。


  1. 提高代码的复用性


解释:根据多态的前提条件,有父子关系。在继承的优点当中,就是提高代码的复用性。


我们将共性的代码交给父类。共性的方法交给父接口,这类写法,可以提高复用性。


父亲里面定义一份,多个儿子都可以使用。


缺点


无法访问子类 特有的成员(成员变量和成员方法)


如果想要访问,则需要判断和转型


第二章 内部类



第 01 节 基础理论

什么是内部类呢?


内部类就是说在一个类大括号里面,包含着另外一个类。


注意: 这里的包含关系,不是继承关系。(继承关系存在 is A 关系,子类可以看做父类)


生活实例:


身体和心脏的关系,就是一种包含关系。 在身体里面包含的有心脏。


内部类的分类有哪些?


(1)成员内部类。独立的分支:静态成员内部类


(2)局部内部类。独立的分支:匿名内部类


内部类的应用场景?什么时候会使用内部类


以前在学习 界面编程当中,有点击事件。(JavaScript 讲过点击的效果) 这里就会使用到内部类。


在 Android 编程当中使用较多。


有些 Java 底层源代码当中,会使用到内部类。

第 02 节 成员内部类

位置


在一个类的成员变量或者成员方法的地方,编写类。(类当中,方法外)


代码


外部类和内部类


//身体(外部类)


public class Body {


String name = "身体";


private int age = 18;


public void exercise(){


System.out.println("身体成员方法...锻炼");


}


//-------------------


class Heart{


String name = "心脏";


public void jump(){


System.out.println("心脏跳...蹦蹦蹦..");


}


public void show(){


String name = "嘿嘿嘿";


System.out.println(name); //嘿嘿嘿


System.out.println(this.name); //心脏


System.out.println(Body.this.name); //身体


System.out.println(Body.this.age); //18


}


}


//-------------------


}


测试类


//测试类


public class Test {


public static void main(String[] args) {


//如果想要创建,内部类的对象,应该怎么使用呢?


//公式是: 外.内


Body.Heart bh = new Body().new Heart();


//调用成员变量和成员方法


System.out.println(bh.name); //心脏


bh.jump(); //心脏跳...蹦蹦蹦..


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


bh.show();


}


}

第 03 节 静态成员内部类

理论


  1. 位置: 类中方法外的类。保证他是一个成员内部类

  2. 添加 static 关键字。

  3. 效果: 可以直接使用类名称打点调用


代码


外部类和内部类


//外部类


@SuppressWarnings("all")


public class Outer {


//静态变量


static String name = "外部类";


//内部类:静态内部类


static class Inner {


String name = "内部类";


public void inNoStatic() {


System.out.println("内部类...非静态方法");


}


public static void inYesStatic() {


System.out.println("内部类...静态方法");


}


}


}


测试类


public class Test {


public static void main(String[] args) {


//创建对象。访问非静态方法


Outer.Inner one = new Outer.Inner();


//调用方法


one.inNoStatic(); //内部类...非静态方法


//如果想要调用静态内部类当中的静态方法。


Outer.Inner.inYesStatic(); //内部类...静态方法


}


}

第 04 节 局部内部类

理论


什么是局部内部类啊?


  1. 位置:


定义在方法当中的类,叫做局部内部类。


  1. 范围:


只能在方法里面使用,出了方法,无法使用。


代码


外部类和内部类


//外部类:演示局部内部类


@SuppressWarnings("all")


public class Outer {


String name = "外部类";


//成员方法


public void show() {


//局部变量


int age = 18;


//打印输出


System.out.println(age);


}


//成员方法


public void method() {


//-------------------


class Inner {


String name = "内部类";


public void say() {


System.out.println("我是局部内部类成员方法");


}


}


//-------------------


//创建对象


Inner iii = new Inner();


iii.say();


}


}


测试类


//测试类


public class Test {


public static void main(String[] args) {


//只能创建外部类的对象


Outer ooo = new Outer();


//调用方法


ooo.method();


}


}


面试问题


第 05 节 匿名内部类

理论


  1. 介绍:


匿名内部类指的是没有名字的内部类,他是局部内部类的一种。


  1. 写法:


new 类名称/接口名称(参数){


//...方法...


};


由来


动物的接口


//动物的接口


@SuppressWarnings("all")


public interface Animal {


//吃的方法


public abstract void eat();


}


实现类


//实现类


@SuppressWarnings("all")


public class Dog implements Animal {


@Override


public void eat() {


System.out.println("狗吃 SHI");


}


}


测试类


//测试类


public class Test {


//思考问题:我们能不能省略 实现类(Dog) 不写呢?


public static void main(String[] args) {


//版本 1:原始的用法,创建实现类的对象,去调用方法


Dog one = new Dog();


one.eat(); //狗吃 SHI


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


//版本 2: 多态的写法,左父右子


Animal two = new Dog();


two.eat(); //狗吃 SHI


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


//版本 3: 匿名内部类的写法


Animal three = new Animal() {


@Override


public void eat() {


System.out.println("狗吃 SHISHI");


}


};


three.eat();


}


}


应用场景


如果方法的参数写的是接口,传递的是接口的实现类。(匿名内部类他也是接口的实现类)


拓展点:反编译的指令 javap 类名称.class



接口代码


//定义接口


public interface Usb {


//定义抽象方法: 连接


public abstract void connect();


}


测试类


public class Test {


public static void main(String[] args) {


//直接调用方法


useUsb(new Usb() {


@Override


public void connect() {


System.out.println("连接电脑");


}


});


}


//定义一个使用 USB 接口的方法


public static void useUsb(Usb sb){


//调用方法


sb.connect();


}


}


//小结:


//如果我们方法的参数写的是接口或者是类


//那么传递参数的时候,写的是实现类或者是子类。(也可以是匿名内部类)


//本质上面来说,匿名内部类就是子类或者实现类。 也就是多态的用法


//反编译操作: javap 类名称.class


第三章 内存回收



第 01 节 内存管理

内存划分图区域



五个区域介绍


1、程序计数器


  1. 较小


程序计数器是一块较小的内存空间,它的作用是当前线程执行指向字节码的行号指示器。


简单一点说,就是指挥程序执行那一条指令。(分支、循环、跳转、异常)


  1. 线程私有


Java 虚拟机的执行,是由多条线程轮流切换处理器的时间来执行,


在任何一个时刻当中,一个处理器只会执行一条指令。


那么每个线程,都会存在一个独立的程序计数器,各个线程之间的程序计数器,不会受到影响的,独立存储。


  1. 无异常


如果线程正在执行的是一个 Java 的方法,这个计数器记录的是正在执行的虚拟机字节码地址。


如果正在执行的是 Native 形式的方法,则计数器的值记录为空 Undefined


此时此刻内存区域是唯一一个在 Java 规范当中没有任何的 OutOfMemoryError 情况区域。


2、虚拟机栈


  1. 线程私有


与程序计数器一样,Java 虚拟机栈也是线程私有的,他的生命周期与线程相同。


  1. 描述 Java 方法执行的内存模型


每个方法被执行的时候,都会同时创建一个栈帧(Stack Frame 栈帧是方法运行时的基础数据结构)


用于存储局部变量表、操作栈、动态链接、方法出口灯信息。


每个方法被调用直到执行完毕的整个过程,都会对应一个栈帧在虚拟机栈,从入栈到出栈的过程。


  1. 异常


在 Java 虚拟机当中,规定了下面的两种异常情况。


【1】StackOverflowError JVM 规定了栈的最大深度,如果执行方法超过最大深度,则出现栈溢出


【2】OutOfMemoryError JVM 在扩展时候,无法申请到足够的内存,则出现内存溢出


3、本地方法栈


  1. 为 native 服务


虚拟机栈是为 Java 服务的


本地方法栈是为 native 方法服务的(native 方法底层是 C\C++调用的是操作系统底层,例如声卡)


  1. 异常


与虚拟机栈一样,本地方法栈也会抛出 StackOverflowError 和 OutOfMemoryError


4、Java 堆


  1. 最大


对于大多数应用来说,Java 堆是 Java 虚拟机管理的内存当中,最大的一块


  1. 线程共享


Java 堆是被所有线程共享的一块内存区域,在虚拟机启动的时候创建


  1. 存放实例


堆区域,唯一的目的就是存放对象的实例,几乎所有的对象实例,都是在这里分配。


另外说明:在 Java 虚拟机规范当中描述,所有的对象和数组都是在堆上面分配,


但是随着 JIT 编译器发展,在堆上分配的方式,渐渐地已经不再是那么的绝对了。


  1. GC


Java 堆是垃圾收集器管理的主要区域。


如果从内存的回收的角度来看,现在收集器采用的是分代收集算法。在 Java 的堆当中,细分为 新生代和老年代。


如果从内存分配的角度来看,线程共享的 Java 堆可以划分多个线程私有分配缓冲区。


不过,无论如何划分,都与存放内容无关,无论哪个区域,存储的都依然是对象实例。


进一步的划分,目的只是为了更好的回收内存,或者更快的分配内存。


5、方法区


方法区,存储的是已经被虚拟机加载的数据,他有以下三个特点:


  1. 线程共享。 方法区和 Java 堆一样,也是被各个线程共享的内存区域

  2. 存储数据类型。 类信息、常量、静态变量、即时编译器编译后的代码

  3. 异常。 如果系统定义太多的类,导致方法区溢出,虚拟机同样会出现内存溢出溢出 OutOfMemeryError


方法区,又可以划分成为两个区域:


  1. 运行时常量池。


存储的是编译期间生产的各类字面量和符号引用,这部分内容加载后会进入方法运行时常量池存放。


运行时常量池,受到方法区内存限制,当常量池无法申请到内存空间的时候,则出现 OutOfMemeryError


  1. 直接内存, 存在以下四个特点


A. 在虚拟机外的数据 (说明:直接内存,不是虚拟机当中的内容,而是虚拟机之外的内存)


B. 直接分配


【一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义】
浏览器打开:qq.cn.hn/FTf 免费领取
复制代码


(说明:在 JDK1.4 之后的 NIO,引入通道 Channel 缓冲区 Buffer,可以操作直接内存)


C. 受到设备内存大小限制 (说明:受到设备总大小限制,而不是 Java 堆大小限制)


D. 异常(说明:如果超过内存容量,也会出现 OutOfMemeryError)

第 02 节 垃圾回收

基础理论


对于 Java 而言,好处: 系统帮我们去控制垃圾回收的。(系统自动完成的,不由程序员控制)


对于 C++ 而言,不是这样的,他的垃圾需要手动清理。(程序员自己去控制)


一个对象生命周期


  1. 刚刚出世的阶段

  2. 青年期

  3. 老年期


配图



标记清除算法



说明


生活实例:


相当于先把货架上面的货物,标记记录下来,(有人买的、没人买的、空着的商品和位置记录)


然后再把没人买的商品统一的进行下架处理,这是垃圾收集器的 早期策略


工作原理:


  1. 标记所有需要回收的对象

  2. 标记完毕后,统一回收所有标记的对象


缺点:


  1. 效率低:


标记和清除的效率都不高


  1. 内存碎片:


标记清除后,会产生大量的不连续的内存碎片。


导致程序需要分配较大对象时,无法找到足够连续空间,不得不提前触发 GC 垃圾收集器


复制算法



说明


目的:


为了解决效率的问题,复制(copying)收集算法就出现了


工作原理:


  1. 将内存按照容量大小,划分成为大小相同的两个区域。(例如 A 区域和 B 区域)

  2. 每次使用 A 区域的时候,B 区域空闲。

  3. 当 A 区域使用完毕之后,将存活的对象,复制到 B 区域当中,一次性清理掉 A 区域的内容。

  4. 这样的操作,每次都是回收半块内存,内存分配过程当中,无需考虑内存碎片的问题。


优点:


  1. 每次针对于半个内存区域进行内存的回收。

  2. 分配内存的过程当中不需要考虑内存碎片的复杂情况,只需要移动堆顶指针,按照顺序分配内存即可。


缺点:


  1. 浪费空间。把内存缩小一半用,太浪费空间。

  2. 有时候效率低。在对象存活率高的时候,需要进行较多的复杂操作,这时候,效率就低了。


标记整理算法



说明


目的:


在复制算法当中,如果不想浪费 50% 的空间,就需要有额外的空间进行分配担保。

用户头像

Java高工P7

关注

还未添加个人签名 2021.11.08 加入

还未添加个人简介

评论

发布
暂无评论
javaSE基础复习之面向对象(多态,内部类,内存回收)