【JavaLearn】(5)final关键字、抽象类、接口、内部类、虚拟机内存结构、垃圾回收

1. final 关键字

  • 修饰变量:使用 final 修饰后,变量的值不可改变,就成了常量
    • 修饰基本数据类型时:值只能赋值一次,后续不能再赋值 final int NUM = 5;
    • 修饰引用类型时:引用变量的值(地址) 不能变,但是对象的属性可以改变
  • 修饰方法:不能被子类重写,但可以重载
  • 修饰类:不能被继承

常用的 final 类System, Math, String

public final class Maths {
    public static final double PI = 3.1415926;  // 不加 final 的时候还可以修改

    // 如果类已经用 final 修改了,方法可以不加 final
    public static final double abs(double d) {
        if (d >= 0) {
            return d;
        } else {
            return -d;
        }
    }

    public static void main(String[] args) {
        final int NUM = 5;
        // NUM = 6;   只允许赋值一次,不允许再修改
        final Dog dog = new Dog("旺财");
        dog.name = "来福";  // 可以修改对象的属性
        // dog = new Dog("小强");  不允许改变
    }
}

2. 抽象类

问题1:Animal an = new Animal(); 没有一种动物,名称是 Animal,所以 Animal 不能被实例化

解决抽象类

问题2:要求子类必须重写父类的某个方法,否则报错

解决抽象方法

  • 有抽象方法的类,必须定义成抽象类
  • 抽象类不能实例化,即不能new
  • 抽象类必须有构造方法,构造方法不能使用 abstract 修饰
  • 一个抽象类可以有 0 或多个抽象方法
  • 子类必须重写父类的抽象方法
public abstract class Animal {
    private String color;

    // 抽象类,虽然不能 new,必须有构造方法
    public Animal() {

    }

    public Animal(String color) {
        this.color = color;
    }

    // 抽象类可以有普通方法
    public void shout() {
        System.out.println("----发出声音----");
    }

    // 要求子类必须重写的方法,用 abstract 定义
    abstract public void eat();
}

3. 接口

接口是规范,定义的是一组规则,体现了现实世界中 “如果你是…则必须能…” 的思想。

[访问修饰符] interface 接口名 [extends 父接口1, 父接口2.....] {
    常量定义;
    方法定义;
}
  • 访问修饰符:只能是 public 或者 默认
  • 接口名:和类名一样的命名规则
  • extends:接口可以多继承,弥补了Java单继承的不足(必须先 extends,再 implements)
  • 常量:接口中的属性只能是常量,总是用 public static final 修饰
  • 方法:接口中的方法,默认总是用 public abstract 修饰(JDK1.8之前)

示例需求 1:飞机、鸟、超人、导弹参加飞行表演

  • 思路1:定义一个父类 Fly,让它们都去继承 Fly。 ==》 不可以,继承是 is-a 的关系,显然不是
  • 思路2:定义一个接口 Flyable,让它们都去实现 Flyable接口 ==》 可以, 接口是 has-a 的关系

Flyable 接口:

public interface Flyable {
    // 变量:默认用 public static final 修饰
    double PI = 3.14;

    // 接口不能 new,也没有构造方法,不会自动继承某个类

    // 成员方法,默认用 public abstract 修饰
    void fly();

    // JDK1.8之后方法可以有实现体,但必须用 static 修饰
    static void testMethod() {
        System.out.println("飞飞飞");
    }
}

Bird 类:

public class Bird implements Main {
    // 必须实现接口中定义的方法(没有方法体的)
    @Override
    public void fly() {
        System.out.println("鸟飞。。。。");
    }
    
    public void shout() {
        System.out.println("-----鸟叫-------");
    }
}

SuperMan类:

public class SuperMan implements Main {
    @Override
    public void fly() {
        System.out.println("超人飞飞飞。。。。");
    }
}

Test 类:

public class Test {
    public static void main(String[] args) {
        Bird bird = new Bird();
        fly(bird);

        SuperMan superMan = new SuperMan();
        fly(superMan);
    }

    /**
     * 多态:形参是一个接口,实参可以是该接口的任意一个实现类的对象
     * @param main
     */
    public static void fly(Flyable flyable) {
        flyable.fly();
        // 不能调用实现类中定义的方法  flyable.shout();
    }
}

示例需求2:内部比较器 Comparable

图书类、学生类、新闻类、商品类等都是不同的类,但是都需要比较的功能。共同的父类显然不可以,可以定义一个比较接口 Comparable,在其中定义一个实现比较的方法 compareTo(Object obj)。让各个类实现该接口即可(模拟Java的Comparable接口)。

Comparable 接口:

public interface Comparable {
    int compareTo(Object obj);
}

Book 类:

public class Book implements Comparable{
    private String name;
    private int price;

    public Book() {
    }

    public Book(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }

    /**
     * 实现了接口,要实现比较的方法,从而达到比较 对象属性大小 的目的
     * @param obj
     * @return
     */
    @Override
    public int compareTo(Object obj) {
        Book book = (Book) obj;
        int res = this.price - book.getPrice();
        // 先比较年龄,如果年龄相同,再比较名称
        if (res == 0) {
            // 此处调用的是 String 的CompareTo,已经实现了Java的 Comparable 接口
            return this.name.compareTo(book.name);
        } else {
            return res;
        }
    }
}

Test测试类:

public class TestBook {
    public static void main(String[] args) {
        Book book1 = new Book("白鹿原", 90);
        Book book2 = new Book("平凡的世界", 90);

        // 比较两本书的 价格、名称 的大小
        System.out.println(book1.compareTo(book2));
    }
}

4. JDK1.8的接口新特性

JDK7 及之前:

  • 接口的变量都是 public final static修饰的
  • 接口中都是 抽象(abstract) 方法,不能有 static 方法
JDK1.8 及其之后
  • 接口中可以添加非抽象方法(static),实现类不能重写,只能通过接口名调用
  • 如果实现类中定义了相同名字的静态方法,不是重写,是直接从属于实现类的
  • 接口中可以用 default修饰方法,在实现类中,可以直接使用,也可以重写(不能有default关键字),且只能通过实现类的对象名调用
  • 调用实现的接口的上级接口中的 default 方法:MyInterface.super.method2()

MyInterface类:

public interface MyInterface {
    // 常量没有变化,还是用 public static final 修饰

    // 抽象方法没有变化  默认用 public abstract 修饰
    void method1();

    // 变化1:可以有非抽象方法 ==》 static 修饰【实现类不可以重写,只能通过接口名调用】
    static void method2() {
        System.out.println("JDK1.8新增的特性,实现类不可以重写,只能通过接口名调用");
    }

    // 变化2:可以有非抽象方法 ==》 default 修饰【实现类可以选择是否重写(重写时要去掉default),只能通过实现类的对象名调用】
    default void method3() {
        System.out.println("JDK1.8新增的,实现类可以选择是否重写(重写时要去掉default),只能通过实现类的对象名调用");
    }
}

MyClass类:

public class MyClass implements MyInterface{
    // 实现接口,要实现它的抽象方法
    @Override
    public void method1() {

    }

    // 不是重写了接口的方法,是实现类自己定义了一个新的方法
    static void method2() {
        System.out.println("这是实现类自己定义的方法");
    }

    @Override
    public void method3() {
        System.out.println("实现类可以选择是否重写接口中 default 修饰的方法,重写时要【去掉 default】");
    }

    public static void main(String[] args) {
        // 接口中 static 修饰的方法,只能通过【接口名】调用
        MyInterface.method2();

        // 实现类中,定义了和接口中相同的static修饰的方法,不是重写,是实现类自己的
        MyClass.method2();

        // 接口中 default 修饰的方法,只能通过【实现类的对象名】调用
        MyClass mc = new MyClass();
        mc.method3();
    }
}

提供非抽象方法的目的:

  • 为了解决实现类中代码重复的问题(如果一个接口中有10个抽象方法,20个类实现该接口,那么这20个类中都需要重写10个方法,也就是200次,但是用 default 修饰后,实现类可以选择是否重写)
  • 如果一个接口新增了一个功能,不必对那些实现类重新设计。
    • 为了解决实现类中代码重复的问题(如果一个接口中有10个抽象方法,20个类实现该接口,那么这20个类中都需要重写10个方法,也就是200次,但是用 default 修饰后,实现类可以选择是否重写)
    • 如果一个接口新增了一个功能,不必对那些实现类重新设计。

5. 内部类

内部类是一类特殊的类,是定义在一个类的内部的类。实际开发中,是为了方便的使用外部类的相关属性和方法

在Java中,内部类分为非静态成员内部类静态成员内部类局部内部类匿名内部类

// 外部类
public class OuterClass {
    // 成员变量
    private String color = "red";
    private int num = 10;

    // 构造方法
    public OuterClass() {
    }
    public OuterClass(int num) {
        this.num = num;
    }

    // 静态代码块
    static {

    }

    // 成员方法
    public void outerMethod() {
        // 局部变量
        int num = 20;

        // ==================== 局部内部类 ========================
        class LocalClass {

        }
    }

    // ==================== (非静态)成员内部类 ========================
    class InnerClass {

    }

    // ==================== 静态成员内部类 ========================
    static class StaticInnerClass {

    }
}

5.1 非静态成员内部类

  • 非静态成员内部类可以直接访问外部类的非静态成员
  • 外部类不能直接访问非静态成员内部类的成员,需要先创建非静态成员内部类的对象,再通过对象名访问
  • 非静态成员内部类访问外部类同名的成员变量:OuterClass.this.num
  • 创建非静态成员内部类对象:必须先创建外部类对象,再通过外部类对象创建非静态成员内部类的对象
  • 内部类是一个编译时的概念,一旦编译成功,就会变成两个完全不同的类
  • 非静态成员内部类中,不能有静态方法、变量、代码块
  • 外部类的静态方法、静态代码块,不能访问非静态成员内部类

外部类及非静态成员内部类:

// 外部类
public class OuterClass {
    // 成员变量
    private String color = "red";
    private int num = 10;
    // 构造方法
    // 静态代码块
    // 成员方法

    // ======================== 非静态成员内部类(可以使用权限修饰符) =========================
    class InnerClass {
        // 成员变量
        private int num = 30;

        // 构造方法
        public InnerClass() {
        }

        // 成员方法
        public void innerMethod() {
            // 【1. 非静态成员内部类可以直接访问外部类的成员】
            System.out.println(color);
            outerMethod();

            int num = 20;
            // 【3. 如果内部类,外部类有同名的变量,如何访问】
            System.out.println(num);                  // 局部变量的
            System.out.println(this.num);             // 内部类的
            System.out.println(OuterClass.this.num);  // 外部类的!!!
        }
    }

    public void outerMethod2() {
        // 【2. 外部类不能直接访问非静态成员内部类的成员,需要内部类的对象】
        InnerClass in = new InnerClass();
        in.innerMethod();
    }
}

Test类:

public class Test {
    public static void main(String[] args) {
        // 创建外部类对象
        OuterClass out = new OuterClass();
        out.outerMethod2();

        // 创建 非静态成员内部类对象 【4. 必须使用外部类的对象创建,非静态成员内部类属于外部类的对象】
        OuterClass.InnerClass inner = out.new InnerClass();    // = new OutClass().new InnerClass();
    }
}

5.2 静态成员内部类

  • 静态内部类,只能访问外部类的静态成员
  • 静态内部类访问外部类的同名的变量:OutClass.num
  • 静态内部类是属于外部类的。创建静态内部类的对象,使用外部类创建 new OutClass.StaticInnerClass()
  • 外部类可以通过静态内部类的类名直接访问内部类的静态成员,访问非静态成员依旧需要先创建内部类对象

外部类及静态成员内部类:

// 外部类
public class OuterClass {
    private String color = "red";
    // 静态成员变量
    static private int num = 10;

    // ====================== 静态成员内部类(可以使用权限修饰符) ==========================
    static class StaticInnerClass {
        // 成员变量
        private int num = 30;

        // 构造方法
        public InnerClass() {
        }

        // 成员方法
        public void innerMethod() {
            // 【1. 静态成员内部类,只能访问外部类的静态成员】
            // System.out.println(color);
            // outerMethod();

            int num = 20;
            System.out.println(num);                  // 局部变量的
            System.out.println(this.num);             // 内部类的
            // 【3. 如果内部类,外部类有同名的【【静态】】变量,通过外部类的类名调用】
            System.out.println(OuterClass.num);       // 外部类的!!!
        }

        // 静态成员方法
        public static void staticInnerMethod() {
        }
    }

    public void outerMethod2() {
        // 【2. 外部类不能直接访问静态内部类的非静态成员,需要静态内部类的对象】
        StaticInnerClass in = new StaticInnerClass();
        in.innerMethod();
        
        // 外部类可以通过静态内部类的类名,直接访问内部类的【【静态方法】】
        StaticInnerClass.staticInnerMethod();
    }
}

Test类:

public class Test {
    public static void main(String[] args) {
        // 创建静态成员内部类对象【4. 使用外部类创建,静态成员内部类属于外部类】
        OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
    }
}

5.3 局部内部类

  • 局部内部类的作用范围是 当前方法
  • 要想访问匿名内部类所在方法的变量(JDK1.8默认使用 final 修饰,JDK7及之前,要想访问,需要用 final 修饰)
public class OutClass {
    private int num = 10;

    public void outMethod() {
        int num2 = 20;

        // ====================== 局部内部类(不能使用权限修饰符) =======================
        class LocalInnerClass {
            private int num = 30;

            public void localInnerMethod() {
                int num = 40;
                System.out.println(num);                    // 局部变量
                System.out.println(this.num);               // 匿名内部类的
                System.out.println(OutClass.this.num);      // 外部类的

                // 匿名内部类所在方法的变量(JDK1.8默认使用 final 修饰,JDK7及之前,要想访问,需要用 final 修饰)
                System.out.println(num2);
            }
        }

        // 【1. 局部内部类的作用范围是 当前方法 】
        LocalInnerClass lic = new LocalInnerClass();
        lic.localInnerMethod();
    }
}

5.4 匿名内部类

  • 匿名内部类是一种特殊的局部内部类,在方法中定义
  • 匿名内部类可以实现一个接口(只能实现一个),也可以继承一个类
  • 必须实现所有的方法,匿名内部类不能是抽象类
  • 匿名内部类不可能有构造方法,因为类是匿名的
  • 匿名内部类没有访问修饰符
  • 如果想实现构造方法形式的一些初始化功能,可以通过代码块实现
  • 如果要访问所在方法的局部变量,该变量需要使用 final 修饰(JDK1.8可以省略)

还可以创建外部比较器 Comparator,用于内部比较器定义了比较规则后(比如用 price 比较),想更改比较规则时(不使用 price 了,使用 name 比较)使用。

Comparator接口:

public interface Comparator {
    int compare(Object obj1, Object obj2);
}

新增的比较规则类:

public class BookNameComparator implements Comparator {
    @Override
    public int compare(Object obj1, Object obj2) {
        Book book1 = (Book)obj1;
        Book book2 = (Book)obj2;
        return book1.getName().compareTo(book2.getName());
    }
}

使用新增的比较规则类:

public class Test {
    public static void main(String[] args) {
        Book book1 = new Book("abc", 80);
        Book book2 = new Book("abcd", 80);
        // 内部比较器
        book1.compareTo(book2);
        // 外部比较器
        BookNameComparator comparator = new BookNameComparator();
        comparator.compare(book1, book2);
    }
}

====》太过于繁琐,每次修改规则都需要新增一个类,且新增的规则类只创建一个对象,用于调用比较方法

解决:【使用匿名内部类】

Comparator接口:

public interface Comparator {
    int compare(Object obj1, Object obj2);
}

使用匿名内部类:

public class Test {
    public static void main(String[] args) {
        Book book1 = new Book("abc", 80);
        Book book2 = new Book("abcd", 80);
        // 内部比较器
        book1.compareTo(book2);
        // 【匿名内部类】--- 匿名内部类的对象赋值给接口的引用
        Comparator comparator = new Comparator(){
            {
                System.out.println("匿名内部类没有构造方法,如果需要初始化操作,可以使用代码块");
            }
            @Override
            public int compare(Object obj1, Object obj2) {
                Book book1 = (Book)obj1;
                Book book2 = (Book)obj2;
                return book1.getName().compareTo(book2.getName());
            }
        };
        comparator.compare(book1, book2);


		// 【匿名内部类】 ---  匿名对象直接调用方法
		new Comparator(){
            @Override
            public int compare(Object obj1, Object obj2) {
                Book book1 = (Book)obj1;
                Book book2 = (Book)obj2;
                return book1.getName().compareTo(book2.getName());
            }
        }.compare(book1, book2);

		
		// 【匿名内部类】 ---  匿名对象直接作为方法的参数
		runMethod(new Comparator<Book>() {
            @Override
            public int compare(Book o1, Book o2) {
                return book1.getName().compareTo(book2.getName());
            }
        }, book1, book2);
    }

	public static int runMethod(Comparator c, Student o1, Student o2) {
        return c.compare(o1, o2);
    }
}

6. 内部类的作用和使用场合

内部类作用

  • 内部类提供了更小的封装。只能让外部类直接访问,不允许同一个包中的其他类直接访问
  • 内部类可以直接访问外部类的私有属性,内部类被当成是其外部类的成员。但外部类不能访问内部类的内部属性。
  • 接口只是解决了多重继承的部分问题,内部类使得多重继承的解决方案变得更加完整。(外部类可以继承一个类,那么内部类就可以直接使用外部类和继承的类的成员,而且内部类还可以继承某一个类。还可以创建多个内部类)
  • 用匿名内部类实现回调功能。 就是编写一个接口,然后你来实现这个接口,把这个接口的一个对象(new 接口(){})作以参数的形式传到另一个程序方法(demoMethod(接口 对象){对象.接口的方法})中,然后通过接口调用你的方法,匿名内部类就可以很好的展现了这一种回调功能。

内部类的使用场合

  • 内部类提供了更好的封装性,且可以很方便的访问外部类的属性。

    ==》在只为外部类提供服务的情况下,可以优先考虑使用内部类

  • 使用内部类间接实现多继承:需要用到多个类的成员时

7. 虚拟机的内存结构

虚拟机(JVM,Java virtual Machine),通过软件模拟的具有完整硬件系统功能、运行在一个完全隔离环境中的完整计算机系统

Java虚拟机对字节码(class)进行解释,生成对应平台的机器码并执行。虚拟机是Java跨平台的重要原因。

Java虚拟机在执行Java程序的过程中,会把它所管理的内存,划分为若干个不同的数据区域。主要包括方法区堆区虚拟机栈本地方法栈程序计数器。其中方法区和堆区为进程所有子线程共享,其它的为线程独有的。

image-20210517200542140

程序计数器线程独有的。当前线程所执行的字节码的行号指示器。每个线程都有一个独立的程序计数器,各线程之间的计数器互不影响,独立存储。

Java虚拟机栈和本地方法栈线程独有的。虚拟机栈执行Java方法,本地方法栈则执行Native方法

Java堆所有线程共享。垃圾收集器管理的主要区域,也被称为GC堆。在虚拟机启动时创建。此区域的唯一目的是存放对象实例,几乎所有对象的实例都在这分配。

方法区所有线程共享。存储已经被虚拟机加载的类信息:常量,静态变量。即时编译器编译后的代码等。

7.1 JVM堆内存划分

JDK1.8之前

image-20210517203208888

JDK1.8之后:永久代变为了 元空间(MetaSpace)

堆内存分为三部分:

  • 年轻代(Young):分为eden区 + 两个大小相同的存活期 S0,S1

    • 所有使用 new关键字 新实例化的对象,一定会在eden区保存(除非大对象,eden区放不下)

    • 存活区分为两个相等大小的存活区,存活区保存的一定是在 eden 区保存好久,且经过好几次的小GC还保存下来的活跃空间,那么这个对象将晋升到存活区中。

    • 存活区一定有两块大小相等的空间。目的是一块存活区未来晋升,另外一块为了对象回收。这两块内存空间一定有一块是空的

    • 在年轻代中使用的是 MinorGC,采用的是复制算法

  • 老年代(Tenured):

    • 接收由年轻代发送过来的对象,一般情况下,经过数次 MinorGC 之后还会保存下来的对象才会进入老年代。
    • 每次进行 MinorGC后存活的对象,年龄都会 + 1,到了一定年龄(默认 15),进入老年代
    • 如果要保存的对象超过了 eden 区的大小,也会直接保存在老年代中
    • 当老年代内存不足时,将引发 “majorGC”,即 “Full GC
  • 永久代:

    • 不进行GC
    • 永久代使用的是 JVM 的堆内存空间,元空间(JDK1.8 之后)使用的是物理内存
    • JDK6及以前版本,字符串常量池是放在堆的永久代中(只有4m,太小),JDK7中,字符串常量值放到了堆内存中

8. 垃圾回收

Java引入了垃圾回收机制(Garbage Collection)。

分代垃圾回收

  1. new 出来的对象先放到年轻代的 eden 区中,一段时间以后,扫描一下 eden 区,发现有不用的(没有引用指向),直接就删除了,有用的,就放到存活区中(S0),此时 eden 区和 S1 区为空的
  2. 再有 new 的对象放到 eden 区中,然后再进行一次扫描,扫描 eden 区和 S0,还有用的都放到 S1 中,此时 eden 区和 S0 为空的
  3. 有 new 的继续放到 eden 区,再扫描不为空的那个,统一放到 空的存活区中
  4. 每一次移动,对象的年龄都会 + 1 ,到 15 之后还没被回收,就移到老年代
  5. 当老年代内存满了之后,进行一次回收,将不用的删除

垃圾回收相关技能点

  • 垃圾回收机制,主要回收 JVM 堆内存里的对象空间
  • 有多种垃圾回收实现算法,表现各异
  • 垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制的执行
  • 可以将对象的引用变量置为 null,暗示GC进行回收
  • 可以通过 System.gc() 或者 Runtime.getRuntime().gc()来通知系统进行垃圾回收,但系统是否进行GC依然不确定
  • GC回收任何对象之前, 总会调用它的 finalize() 方法(如果覆盖该方法,让一个新的引用变量重新引用该对象,则会重新激活该对象)
  • 永远不要主动调用某个对象的 finalize 方法,交由 GC 调用
public class Student {
    @Override
    protected void finalize() throws Throwable {
        System.out.println("对象被回收之前的遗言。。。。");
    }

    public static void main(String[] args) {
        Student stu = new Student();
        new Student();
        new Student();
        new Student();
        new Student();

        // 通知系统,进行垃圾回收,不一定全部回收
        System.gc();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值