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 修饰后,实现类可以选择是否重写)
- 如果一个接口新增了一个功能,不必对那些实现类重新设计。
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程序的过程中,会把它所管理的内存,划分为若干个不同的数据区域。主要包括方法区、堆区、虚拟机栈、本地方法栈、程序计数器。其中方法区和堆区为进程所有子线程共享
,其它的为线程独有的。
程序计数器:线程独有的
。当前线程所执行的字节码的行号指示器。每个线程都有一个独立的程序计数器,各线程之间的计数器互不影响,独立存储。
Java虚拟机栈和本地方法栈:线程独有的
。虚拟机栈执行Java方法,本地方法栈则执行Native方法
Java堆:所有线程共享
。垃圾收集器管理的主要区域,也被称为GC堆。在虚拟机启动时创建。此区域的唯一目的是存放对象实例,几乎所有对象的实例都在这分配。
方法区:所有线程共享
。存储已经被虚拟机加载的类信息:常量,静态变量。即时编译器编译后的代码等。
7.1 JVM堆内存划分
JDK1.8之前:
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)。
分代垃圾回收:
- new 出来的对象先放到年轻代的 eden 区中,一段时间以后,扫描一下 eden 区,发现有不用的(没有引用指向),直接就删除了,有用的,就放到存活区中(S0),此时 eden 区和 S1 区为空的
- 再有 new 的对象放到 eden 区中,然后再进行一次扫描,扫描 eden 区和 S0,还有用的都放到 S1 中,此时 eden 区和 S0 为空的
- 有 new 的继续放到 eden 区,再扫描不为空的那个,统一放到 空的存活区中
- 每一次移动,对象的年龄都会 + 1 ,到 15 之后还没被回收,就移到老年代
- 当老年代内存满了之后,进行一次回收,将不用的删除
垃圾回收相关技能点:
- 垃圾回收机制,主要回收 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();
}
}