Java学习笔记02

面向对象基础

对象组成:属性和方法

  1. 属性

    对象名.属性名

  2. 行为

    对象名.方法名

对象与类的关系

  1. 类是抽象的,概念的,代表一类事物
  2. 对象是具体的,实际的,代表一个具体事务
  3. 类是对象的模板,对象是类的一个个体

类和对象的内存分配机制

Java 内存的结构分析

  1. 栈:一般存放基本数据类型(局部变量)
  2. 堆:存放对象(Cat cat , 数组等)
  3. 方法区:常量池(常量,比如字符串),类加载信息
Person p = new Person();
p.name = "jack";
p.age = 10;
  1. 先加载Person类信息(属性和方法信息,只会加载一次)
  2. 再堆中分配空间,进行默认初始化
  3. 把地址赋给p ,p就指向对象
  4. 进行指定初始化,比如 p.name=”jack” p.age = 10

作用

  1. 区分相同名字的类
  2. 方便管理类
  3. 控制访问范围

基本语法

package cn.itrf

package 关键词,表示打包

cn.itrf 表示包名

注意事项

  1. package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
  2. import指令 位置放在package的下面,在类定义前面,可以有多句且没有顺序要求

方法

方法的调用机制原理

  1. 当程序执行到方法时,就会开辟一个独立的空间(栈空间)
  2. 当方法执行完毕,或者执行到return语句时,就会返回
  3. 返回到调用方法的地方
  4. 返回后,继续执行方法后面的代码
  5. 当main方法(栈)执行完毕,这个程序退出

方法的访问修饰符

  1. public:公开的。无论是哪个类、哪个包、还是外部项目,都可以直接访问。
  2. protected:受保护的。同一个包内的类可以访问;不同包中的子类也可以访问。
  3. 默认(无修饰符):包级私有(package-private)。只有同一个包内的类可以访问,跨包了就不能访问。
  4. private:私有的。只有在定义它的类内部才能访问,体现了面向对象的封装性。通常属性会设为 private,然后通过 get/set 方法来间接访问。

方法的返回数据类型

1) 一个方法最多有一个返回值 [多个结果 返回数组 ]
2) 返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
3) 如果方法要求有返回数据类型,则方法体中最后的执行语句必须为 return 值; 而且要求返回值类型必须和 return 的值类型一致或兼容

4) 如果方法是 void,则方法体中可以没有 return 语句,或者 只写 return ;

注意事项
  1. 方法不能嵌套定义,方法体中不能再定义方法
  2. 基本数据类型,传递的是值(值拷贝),形参的任何变化不影响实参
  3. 引用数据类型,传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参

递归重要规则

  1. 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
  2. 方法的局部变量时独立的,不会相互影响
  3. 如果方法中使用的是引用类型变量(比如数组,对象),就会共享引用类型的数据
  4. 递归必须向退出递归条件逼近,否则就是无限递归
  5. 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕

方法重载

  1. 方法名:必须相同
  2. 参数列表:必须不同
  3. 返回类型:无要求

可变参数

public int sum(int... nums) {
int res = 0;
for(int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}
  1. int… 表示接受的是可变参数,类型是 int ,即可以接收多个 int(0-多)

  2. 使用可变参数时,可以当做数组来使用 即 nums 可以当做数组

  3. 遍历 nums 求和即可

注意事项
  1. 可变参数的实参可以为0个或者任意多个
  2. 可变参数的实参可以为数组
  3. 可变参数的本质就是数组
  4. 可变参数可以和普通类型的参数一起放在形参列表,但是必须保证可变参数在最后
  5. 一个形参列表中只能出现一个可变参数

作用域

  1. Java编程中,主要的变量就是属性(成员变量)和局部变量
  2. 局部变量一般是指在成员方法中定义的变量
  3. Java中作用域的分类
    1. 全局变量:也就是属性,作用域为整个类
    2. 局部变量:除了属性之外的其他变量,作用域是定义他的代码块
  4. 全局变量可以不赋值,直接使用,因为有默认值,局部变量必须赋值,才能使用,因为没有默认值

注意事项

  1. 属性和局部变量可以重名,访问时遵循就近原则
  2. 在同一个作用域中,两个变量不能重名
  3. 属性生命周期较长,便随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁
  4. 作用域范围不同
    1. 全局变量:可以被本类使用,或其他类使用
    2. 局部变量:只能在本类中对应的方法中使用
  5. 修饰符不同
    1. 全局变量可以加修饰符
    2. 局部变量不可用加修饰符

构造方法

[修饰符] 方法名(形参列表){
方法体;
}

构造方法又叫构造器(constructor),是类的一种特殊的方法,它的主要作用是完成对新对象的初始化。它有几个特点:

1) 方法名和类名相同
2) 没有返回值
3) 在创建对象时,系统会自动的调用该类的构造器完成对象的初始化。
4) 构造器没有返回值, 也不能写 void

注意事项

  1. 一个类可以定义多个不同的构造器,即构造器重载
  2. 构造器名要和类名相同
  3. 构造器没有返回值
  4. 构造器是完成对象的初始化,并不是创建对象
  5. 在创建对象时,系统自动的调用该类的构造方法
  6. 如果程序员没有定义构造器,系统会自动给类生成一个默认无参构造器(默认构造器)
  7. 一旦定义了自己的构造器,默认的构造器就被覆盖了,就不能再使用默认无参构成器了,除非显式的定义一下

this关键字

Java虚拟机会给每个对象分配this代表当前对象

注意事项

  1. this 关键字可以用来访问本类的属性、方法、构造器

2) this 用于区分当前类的属性和局部变量
3) 访问成员方法的语法:this.方法名(参数列表);

4) 访问构造器语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一条语句)

5) this 不能在类定义的外部使用,只能在类定义的方法中使用

==和equals()的区别

  1. ==(双等号)
    • 基本数据类型(如 int, double, boolean:比较的是它们的数值是否相等。
    • 引用数据类型(如对象、数组、字符串):比较的是它们的内存地址是否相同(即是否指向堆内存中的同一个对象)。
  2. equals() 方法
    • 对象专属:它是 Object 类的一个方法,所有类都继承了它。
    • 默认行为:如果没有重写(Override),它的底层逻辑和 == 一样,比较的还是内存地址。
    • 常用重写:像 StringInteger 等类都重写了 equals() 方法,用来比较它们的内容(值)是否相同。

面向对象的三大特征

封装、继承、多态

封装

封装就是把抽象出的数据和对数据的操作封装在一起,数据被保护在内部,程序的其他部分只能通过授权的操作,才能对数据进行操作

将属性设置为private 创建setXXX和getXXX的方法 public

继承

class 子类 extends 父类{

}

子类就是会自动拥有父类定义的属性和方法

父类又叫 超类,基类

子类又叫派生类

注意事项

1) 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问

2) 子类必须调用父类的构造器, 完成父类的初始化
3) 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过。

4) 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
5) super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
6) super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
7) Java 所有类都是 Object 类的子类, Object 是所有类的基类.
8) 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
9) 子类最多只能继承一个父类(指直接继承),即 Java 中是单继承机制。思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】

10) 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系

super关键字

super代表父类的引用,用于访问父类的属性、方法、构造器

不能访问父类的private属性和方法

当子类中有何父类中的成员(属性和方法)重名时,为了访问父类成员,必须通过super。如果没有重名,使用super、this、直接访问的结果一样

super.成员 的查找起点是直接父类,若直接父类没有对应成员,会继续沿着继承链向上查找(可找到爷爷类等上层父类);查找遵循继承链就近原则,同时受访问权限约束遇到private就断了报错;无法主动跳过直接父类,强制直接访问爷爷类中被父类重写 / 覆盖的同名成员

this和super的区别

对比项 this super
含义 当前类对象 父类存储空间入口
成员查找起点 当前类 直接父类(跳过当前类)
调用构造 this () 调用本类构造 super () 调用父类构造
使用位置 实例方法、构造器 实例方法、构造器
static 中 不允许 不允许
单独输出 this 可行 super 不能直接打印

方法的重写/覆盖

方法的覆盖就是子类的一个方法和父类的某个方法的名称、返回类型、参数一样,那么我们就说子类的这个方法覆盖了父类的方法

子类重写方法的返回值类型和父类返回值类型一致,或者是其子类

子类的方法不能缩小父类方法的访问范围

多态

方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。

重点

  1. 一个对象的编译类型和运行类型可以不一致
  2. 编译类型在定义对象时,就确定了,不能改变
  3. 运行类型是可以变化的
  4. 编译类型看定义时 = 号 的左边,运行类型看 = 号的 右边

多态前提(四个条件缺一不可)

  1. 继承 / 实现:子类继承父类(类继承)或者类实现接口
  2. 方法重写:子类重写父类方法
  3. 父类引用指向子类对象(向上转型)
  4. 调用重写方法

向上转型(自动转型,隐式转型)

格式:父类类型 引用 = new 子类();

Animal animal = new Dog(); // 向上转型

特点:

  1. 自动转换,不需要强制
  2. 可以调用:父类所有方法 + 子类重写的方法,只有方法哦!!!
  3. 不能调用子类独有的方法

向下转型(强制转型,显式转型)

作用:向上转型后,想要调用子类独有方法,必须向下转回子类类型

格式:子类类型 引用 = (子类)父类引用;

Animal a = new Dog(); //向上转型
Dog dog = (Dog) a; //向下转型
dog.watchDoor(); // 正常执行

巨大坑:类型转换异常 ClassCastException

只有原本就是该子类对象,才能强转;

Animal a = new Cat();
Dog d = (Dog)a; //运行报错!

解决方案:instanceof 判断

判断引用变量指向的实际对象是不是【某个类 / 它的子类 / 实现类】的实例

转型前先判断引用实际对象类型

if(a instanceof Dog){
Dog d = (Dog)a;
d.watchDoor();
}
类型 写法 是否强制 风险 能力
向上转型 父类 引用 = new 子类 () 自动 无风险 只能访问父类方法,执行子类重写方法;不能访问子类独有方法
向下转型 子类 引用 = (子类) 父类引用 需要强制 可能抛 ClassCastException,配合 instanceof 可以调用子类独有方法

易错总结

方法有多态,成员变量没有多态!

class A{ int num = 10; }
class B extends A{ int num = 20; }
A a = new B();
System.out.println(a.num); //输出10,看左边父类
  1. 静态方法重写不叫重写,叫隐藏,不存在多态;调用看左边
  2. 向上转型本质:多态的基础写法
  3. 向下转型不是多态,只是为了访问子类特有功能

Java的动态类型绑定

  1. 调用对象【方法】:存在动态绑定

    调用方法时,看对象真实运行类型(堆里 new 出来的类型),执行子类重写后的方法。

通俗:方法 → 运行看右边

  1. 调用对象【成员属性 (变量)】:没有动态绑定

访问变量时,看引用声明类型(等号左边是什么类型),哪里声明,就取哪里的属性。

通俗:属性 → 编译 / 运行都看左边

类变量

类变量也叫静态变量/静态属性,是该类的所有对象共享的变量,任何一个该类的对象去访问它时,取到的都是相同的值,同样任何一个该类的对象去修改它时,修改的也是同一个变量。

class Child { //类
private String name;
//定义一个变量 count ,是一个类变量(静态变量) static 静态
//该变量最大的特点就是会被 Child 类的所有的对象实例共享
public static int count = 0;
public Child(String name) {
this.name = name;
}
}

访问类变量

类名.类变量名 对象名.类变量名 推荐用第一种

生命周期

  1. 类变量是在类加载时就完成初始化了,也就是说,即使你没有创建对象,只要类加载了,就可以使用类变量
  2. 类变量的生命周期是随类的加载开始,随着类消亡而销毁

类方法

  1. 类方法和普通方法都是随着类的加载而加载,将结构信息存储在方法区:

    类方法中无 this 的参数

    普通方法中隐含着 this 的参数

  2. 类方法可以通过类名调用,也可以通过对象名调用。

  3. 普通方法和对象有关,需要通过对象名调用,比如对象名.方法名 (参数),不能通过类名调用。

  4. 类方法中不允许使用和对象有关的关键字,比如 this 和 super。普通方法 (成员方法) 可以。

  5. 类方法 (静态方法) 中 只能访问 静态变量 或静态方法 。

  6. 普通成员方法,既可以访问 非静态成员,也可以访问静态成员。

小结: 静态方法,只能访问静态的成员,非静态的方法,可以访问静态成员和非静态成员 (必须遵守访问权限)

mian方法

public static void main(String[] args)
  1. public:公共访问权限。JVM 需要跨类调用 main 方法,必须对外公开可访问。

  2. static:静态方法。JVM 启动时还没有创建类的实例对象,直接通过类名调用方法,不需要 new 对象。

  3. void:无返回值。JVM 不接收 main 函数的返回结果。

  4. main:固定方法名,JVM 识别的程序入口名称,不能修改。

  5. String[] args:命令行参数数组

    • 在控制台执行命令示例:

      java TestDemo 张三 20

      args[0]="张三" args[1]="20" args[2]="男"

    • args 名称可以修改(例如String[] params),但数组类型不能变。

三种代码块

态代码块

static {
//代码
}
  • 位置:类内部,方法外面,加 static

  • 触发时机:类加载的时候执行,只执行 1 次

  • 作用:初始化静态变量

  • 限制:

    只能访问静态成员

    不能使用 this、super

    不可以写 return

构造代码块(实例代码块 / 普通代码块【类内】)

{
//代码
}
  • 位置:类内部、方法外面,没有 static
  • 触发时机:每次创建对象 new 的时候执行
  • 执行时机:在构造方法之前执行
  • 作用:提取多个构造方法的公共重复代码
  • 可以访问静态、非静态成员,可以使用 this

局部代码块(方法内普通代码块)

public void test(){
{
//局部代码块
}
}
  • 位置:方法内部
  • 作用:限定局部变量生命周期,缩小作用域

区分「构造代码块」和「局部代码块」

执行顺序

情况 1:只创建单个对象

1. 静态代码块(类加载,最先执行,只运行一次)

2. 构造代码块(每次new对象都执行)

3. 构造方法

示例演示:

public class Test {
static {
System.out.println("静态代码块");
}
{
System.out.println("构造代码块");
}
public Test(){
System.out.println("构造方法");
}

public static void main(String[] args) {
new Test();
}
}
静态代码块
构造代码块
构造方法

情况 2:多次 new 对象

new Test();
new Test();
静态代码块    //类加载,仅第1次执行
构造代码块
构造方法
构造代码块 //第二次new,静态不再执行!
构造方法

关键结论:静态代码块随类加载,全局只执行一次;构造代码块每次 new 对象都会执行。

情况 3:存在父子继承关系

继承场景完整顺序口诀:

父静态 → 子静态 → 父构造代码块 → 父构造方法 → 子构造代码块 → 子构造方法

演示代码:

class Father{
static {System.out.println("父静态");}
{System.out.println("父构造代码块");}
public Father(){System.out.println("父构造器");}
}
class Son extends Father{
static {System.out.println("子静态");}
{System.out.println("子构造代码块");}
public Son(){System.out.println("子构造器");}
}

public class Main{
public static void main(String[] args) {
new Son();
}
}

运行结果:

父静态
子静态
父构造代码块
父构造器
子构造代码块
子构造器

易错提醒:

静态代码块只看类加载顺序,优先父类静态;静态代码块互不依赖实例,优先全部执行完所有静态,再执行实例相关代码块、构造器。

类型 格式 位置 执行时机 执行次数
静态代码块 static {} 类内,方法外 类加载阶段 整个程序只 1 次
构造代码块 {} 类内,方法外 每次 new 创建实例 每 new 一次执行一次
局部代码块 {} 方法内部 方法运行到该位置 运行到就执行

设计模式

  1. 静态方法和属性的经典使用
  2. 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。

单例模式

单例模式:保证一个类在整个程序中,只有唯一的一个实例对象,对外提供统一获取该对象的入口。

使用场景:工具类、线程池、数据库连接池、配置文件对象等,不需要多个实例,节约内存。

核心三大要点:

  1. 私有构造方法private 类名(){},禁止外部new创建对象
  2. 私有静态成员变量:保存唯一实例
  3. 公共静态方法:对外获取这个唯一实例

方式 1:饿汉式(静态常量写法,【线程安全】)

public class SingletonHungry {
// 1.类加载时直接创建实例
private static final SingletonHungry instance = new SingletonHungry();
// 2.私有构造,阻止外部new
private SingletonHungry(){}
// 3.对外获取实例的方法
public static SingletonHungry getInstance(){
return instance;
}
}
执行时机

类加载的时候直接创建对象

优点:

  • 写法简单,天然线程安全(类加载只执行一次,不存在多线程冲突)

缺点:

  • 类一旦加载,立刻创建对象。即使程序全程没有用到这个实例,对象依然创建,造成内存浪费。

饿汉:肚子一饿立刻吃饭 → 类加载直接实例化。


方式 2:懒汉式(基础版,多线程不安全!)

public class SingletonLazy {
// 先不创建实例
private static SingletonLazy instance;
private SingletonLazy(){}
// 调用方法时,才创建对象
public static SingletonLazy getInstance(){
// 第一次调用才new
if(instance == null){
instance = new SingletonLazy();
}
return instance;
}
}
执行时机

调用getInstance()方法的时候,才创建实例(延迟加载)

优点:懒加载,不用就不创建,节省内存

致命缺陷:多线程环境下会创建多个对象,线程不安全

线程安全优化:双重检查锁 DCL(面试标准懒汉写法)

public class SingletonLazyDCL {
// volatile 禁止指令重排,必须加上
private static volatile SingletonLazyDCL instance;
private SingletonLazyDCL(){}

public static SingletonLazyDCL getInstance(){
// 第一层判断:避免每次都竞争锁,提高效率
if(instance == null){
synchronized (SingletonLazyDCL.class){
// 第二层判断:防止多个线程同时等待锁,重复创建
if(instance == null){
instance = new SingletonLazyDCL();
}
}
}
return instance;
}
}

懒汉:不到饭点不吃饭 → 需要用到的时候才创建对象

对比 饿汉式 懒汉式 (DCL 双重检查锁)
创建时机 类加载阶段 第一次调用 getInstance ()
懒加载 ❌不支持 ✅支持,节省内存
线程安全 ✅天然安全 需要加锁 + volatile 保证安全
使用场景 确定一定会使用这个对象 不确定是否会使用,希望延迟创建

拓展知识点

  1. 私有构造方法依然可以被反射暴力破解,两种基础实现都不能防止反射创建多个实例;
  2. 还有第三种写法:静态内部类单例(推荐,兼顾懒加载与线程安全);
public class Singleton {
private Singleton(){}
private static class Inner{
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance(){
return Inner.INSTANCE;
}
}

final关键字

final 表示「最终的、不可修改」,可以修饰:变量、方法、类。

一、final 修饰【变量】

变量一旦赋值成功,不能重新赋值(常量)

  1. 局部变量
public void test(){
final int a;
a = 10;
// a = 20; // 编译报错,不能二次赋值
}

final 局部变量可以先声明、后面一次赋值,但是只能赋值一次

  1. 成员变量(实例变量)

两种赋值时机,二选一:

  1. 定义时直接初始化
  2. 在构造方法中赋值

不能不赋值!final 成员变量必须保证对象创建完成时有值。

class A{
final int num = 10;
// final int num; // 合法,可以在构造器赋值
public A(){
// num = 20;
}
}
  1. 静态成员变量(static final)

俗称常量,命名规范:全部大写,下划线分隔 MAX_VALUE

赋值时机:

  1. 声明处初始化
  2. 静态代码块初始化
public static final double PI = 3.1415926;

极易混淆重点:final 修饰引用类型变量

final Person p = new Person();
p.name = "张三"; // 合法!!
p = new Person(); // 报错!
  • final 限制的是引用地址不能变

  • 对象内部的属性依然可以修改!

    形象理解:遥控器不能换电视,但是可以换频道。


二、final 修饰【方法】

public final void show(){}

规则:

该方法不能被子类重写(override)

仍然可以被继承,子类可以调用,只是不能改写方法实现。

补充:

private 方法隐式带有 final 特性,子类无法重写私有方法。


三、final 修饰【类】

final class Animal{}

规则:

这个类不能被继承,没有子类

例子:StringInteger 都是 final 类,你无法写 class MyStr extends String


四、易错知识点汇总

  1. final 和 static 区别?
  • static:属于类,所有对象共享一份
  • final:值不可修改;两者经常组合 static final 定义常量
  1. 基本类型 final vs 引用类型 final
  • 基本类型:数值不能变
  • 引用类型:对象地址不能变,对象内部内容可变
  1. final 方法可以重载吗?

可以重载!只是禁止重写,重载不受影响。

class A{
public final void test(){}
public final void test(int a){} // 重载合法
}
  1. final 可以修饰构造方法吗?

不允许!构造方法本身不能被继承重写,语法不支持。

五、速记总结

  • final 变量:赋值一次,终身不改(引用变量地址不变,对象内容可变)
  • final 方法:禁止子类重写
  • final 类:断绝继承,不能有子类
修饰目标 作用
final 变量 只能赋值一次,不可重新赋值
final 方法 不能被子类重写
final 类 不能被继承

Java 抽象类(abstract class)

一、基础概念

abstract 关键字修饰的类,叫做抽象类

抽象类用来提取多个子类的公共模板,允许定义没有方法体的抽象方法,强制子类去实现。

语法格式:

abstract class 类名 {

}

二、抽象方法

语法:abstract 返回值 方法名();

  • 只有方法声明,没有方法体(没有 {})
  • 必须写在抽象类 / 接口当中
  • 作用:规范子类必须实现这个方法

示例:

abstract class Animal {
// 抽象方法:没有方法体
public abstract void shout();
}

三、核心硬性规则

  1. 抽象类不能 new 创建对象

    new Animal(); // 编译报错

    原因:类中有未实现的抽象方法,对象不完整。

  2. 如果一个类继承抽象类:

    • 方案①:普通子类 → 必须重写父类所有抽象方法
    • 方案②:子类也定义为 abstract 抽象类 → 可以不用实现抽象方法
// 普通子类,必须实现全部抽象方法
class Dog extends Animal{
@Override
public void shout() {
System.out.println("汪汪汪");
}
}
  1. 抽象类里可以包含什么?

普通成员变量、静态变量

普通实例方法、static 静态方法

构造方法(供子类创建对象时调用)

抽象方法

重点:抽象类有构造方法!只是不能自己实例化,子类 new 的时候通过 super() 调用。

  1. 几个语法禁忌

    abstractfinal不能一起修饰类

final 类不能被继承,abstract 类要求必须被继承实现,互相矛盾。

abstractprivate 不能一起修饰方法

private 方法子类看不见,无法重写实现,冲突。

abstractstatic 不能一起修饰方法

静态方法可以直接类名调用,不需要子类实现,冲突。

类型 能否实例化 能否拥有抽象方法
普通类 可以 new 不能有抽象方法
抽象类 不能直接 new 可以定义抽象方法

四、易错知识点

  1. 抽象类一定包含抽象方法?

    错误!抽象类可以没有任何抽象方法,只是很少这么写。

abstract class Test{
// 全部都是普通方法,合法抽象类
public void hello(){}
}
  1. 有抽象方法的类,一定是抽象类?

正确!普通类存在抽象方法直接编译报错。

Java 接口(interface)

接口是行为规范、标准契约,定义一组功能(方法声明),本身不关心怎么实现。

语法:

interface 接口名 {

}

实现接口,关键字:implements

class 类名 implements 接口1,接口2 {

}

不同 JDK 版本接口内允许的成员

1)JDK7 及以前(经典老式接口)

接口里面只能有:全局常量、抽象方法

  1. 常量:public static final 修饰,默认隐藏
interface Usb{
// 默认:public static final int MAX_SPEED = 480;
int MAX_SPEED = 480;
}

必须初始化,不能修改。

  1. 抽象方法:public abstract 修饰,默认隐藏
interface Usb{
// public abstract void work();
void work();
}

2)JDK 8 新增

默认方法(default):带方法体,实现类可以选择重写,不重写就用默认实现

default void show(){
System.out.println("默认方法");
}

静态方法(static):接口。静态方法 () 调用,实现类不能继承

3)JDK 9 新增

私有方法 private:供接口内部默认方法复用

面试常记:日常开发核心掌握【常量、抽象方法、default 默认方法、static 静态方法】


核心语法规则

  1. 接口不能 new 实例化,没有构造方法!
new Usb(); // 报错
  1. 类和接口:implements 多实现(一个类可以同时实现多个接口)
class Phone implements Usb, Bluetooth{}
  1. 接口与接口:extends 多继承
interface A{}
interface B{}
interface C extends A,B{}
  1. 实现类要求:
  • 普通类实现接口:必须重写接口中所有抽象方法
  • 抽象类实现接口:可以不实现抽象方法,交给它的子类完成
  1. 方法访问权限:

接口抽象方法强制 public,重写的时候不能缩小权限(不能写 protected/private)

对比项 抽象类 abstract class 接口 interface
关键字 class interface
构造方法 有构造方法(供子类 super 调用) 无构造方法,不能实例化
成员变量 可以普通变量、静态变量 只能public static final常量
方法 (JDK7) 普通方法 + 抽象方法 只能抽象方法、常量
继承关系 单继承 extends 多实现implements;接口之间多继承extends
设计角度 模板,描述事物是什么 行为标准,描述事物能做什么

设计思想区分:

Animal(抽象类):动物是什么

Run(接口):动物能跑

Dog extends Animal implements Run

易错知识点

  1. default 默认方法冲突问题

    一个类实现多个接口,如果多个接口存在同名同参数 default 方法,实现类必须手动重写该方法,否则编译报错。

  2. 接口静态方法不会被继承,普通类的可以被继承但不能重写

interface A{
static void test(){}
}
class B implements A{}
B.test(); // 错误,只能 A.test()
  1. 接口常量默认public static final,写不写都一样,不能修改。

Java 内部类

一、什么是内部类

把一个类定义在另一个类的内部,这个类就叫内部类;外面的类称为外部类(外围类)。

//外部类
class Outer{
//内部类
class Inner{

}
}

分类(四大类)

  1. 成员内部类(实例内部类)
  2. 静态内部类(静态嵌套类)
  3. 局部内部类(方法内定义)
  4. 匿名内部类(重点,经常和接口 / 抽象类搭配)

成员内部类(没有 static,写在外部类成员位置)

class Outer{
private String name = "外部类成员";
//成员内部类
class Inner{
public void show(){
//内部类可以直接访问外部类所有成员,包括private私有!
System.out.println(name);
}
}
}

核心规则

  1. 成员内部类依赖外部类对象,不能直接 new
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.show();

简写:Outer.Inner inner = new Outer().new Inner();

  1. 内外类出现同名变量怎么区分?
class Outer{
String a = "外部a";
class Inner{
String a = "内部a";
public void test(){
System.out.println(a); //内部a
System.out.println(this.a); //内部a
System.out.println(Outer.this.a); //外部类的a!
}
}
}
  1. 限制

    内部类里面不能定义静态成员(static 变量、静态方法)

因为成员内部类必须依托外部实例,静态属于类,冲突

  1. 访问权限

    成员内部类可以使用 private / protected / public修饰;

    如果内部类被 private 修饰,外部其他类无法访问这个内部类。


静态内部类(static 修饰,也叫嵌套类)

class Outer{
private static String msg = "静态外部变量";
//静态内部类
static class StaticInner{
public void show(){
//只能访问外部类【静态成员】
System.out.println(msg);
}
}
}

核心规则

  1. 不依赖外部类对象,直接通过外部类类名创建
Outer.StaticInner inner = new Outer.StaticInner();

不能写 outer.new StaticInner ()

  1. 访问范围

    只能直接访问外部类 static 静态属性、静态方法

    想要访问外部实例成员,必须传入外部类对象。

  2. 可以拥有自己的静态成员。

成员内部类 vs 静态内部类 关键区别

类型 创建方式 依赖外部对象 可访问外部成员
成员内部类 外部对象.new 内部类() 必须有外部实例 所有成员(静态 + 实例 + 私有)
静态内部类 new 外部类.内部类() 不需要外部实例 仅外部静态成员

局部内部类(定义在【方法里面】)

class Outer{
public void method(){
//局部内部类,作用域仅限于当前方法
class LocalInner{
public void show(){
System.out.println("局部内部类方法");
}
}
//只能在方法内创建使用
LocalInner li = new LocalInner();
li.show();
}
}

规则

  1. 作用域仅限当前方法,方法外面无法使用;
  2. 不能使用访问修饰符 public private protected
  3. JDK8 开始,如果访问方法里的局部变量,变量默认隐式 final(不能修改);
public void test(){
int num = 10;
class Inner{
void show(){
System.out.println(num);
}
}
// num = 20; //报错!局部内部类访问后,num等同于final
}

匿名内部类(重中之重)

没有类名的内部类,一般用来一次性实现接口 / 抽象类,简化代码。

语法格式:

new 接口/抽象类/父类(){
//重写方法
};

示例:基于接口

interface Usb{
void work();
}

public class Test{
public static void main(String[] args) {
//匿名内部类,相当于创建接口的实现类对象
Usb usb = new Usb(){
@Override
public void work() {
System.out.println("usb设备工作");
}
};
usb.work();
}
}

核心特点

  1. 匿名内部类只能使用一次,只能创建一个对象;下次创建对象的时候必须重新写一边,没有名字不能重复使用;
  2. 没有构造方法;
  3. 本质:隐式继承父类 / 实现接口;
  4. 属于局部内部类的一种;

常用场景:方法传参、线程 new Thread(new Runnable(){})

拓展:JDK8 Lambda 表达式可以简化只有一个抽象方法的接口,替代匿名内部类。


通用核心考点:内部类访问外部类

  1. 内部类天生持有外部类的隐式引用:外部类名.this
  2. 外部类想要访问内部类成员:必须先创建内部类对象,不能直接访问
class Outer{
class Inner{
int num = 100;
}
public void test(){
// System.out.println(num); //不行!
Inner in = new Inner();
System.out.println(in.num); //需要对象
}
}
类别 定义位置 是否 static 创建对象方式 核心特点
成员内部类 外部类中,方法外 new Outer().new Inner() 依赖外部对象,可访问所有外部成员,不能定义 static
静态内部类 外部类中,方法外 static new Outer.StaticInner() 无需外部对象,仅访问外部静态成员
局部内部类 方法体内 方法内 new 仅限方法内使用,不能加权限修饰符
匿名内部类 方法 / 参数中 new 父类/接口(){} 无类名,一次性使用,实现抽象方法