加载中...

Java基础学习


不同类型数组的初始化值

整数型:默认初始化值0
小数型:默认初始化值0.0
字符型:默认初始化值/u0000,即空格
布尔型:默认初始化值 false
引用型(除上面类型之外):默认初始化值null

基本数据类型和引用数据类型的区别

  1. 基本数据类型(Primitive Types):

    • Java的基本数据类型是预定义的,并且它们是直接存储数据值的。
    • 基本数据类型包括整型(int、short、long、byte)、浮点型(float、double)、字符型(char)和布尔型(boolean)。
    • 基本数据类型的变量存储的是实际的数据值,它们在内存中的存储是在栈(Stack)中。
    • 基本数据类型是值传递,传递的是实际的数据值。
  2. 引用类型(Reference Types):

    • 引用类型是由类(Class)、接口(Interface)、数组(Array)和枚举(Enum)等引用数据类型所构成的。
    • 引用类型的变量存储的是对象的引用(内存地址),而不是对象本身的实际数据。
    • 引用类型的对象在内存中的存储是在堆(Heap)中。
    • 引用类型的变量本质上是指向对象的引用,它们可以指向同一个对象,也可以指向不同的对象。
    • 引用类型是对象的引用传递,传递的是对象的引用。

区别总结如下:

  • 基本数据类型(Primitive Types):
    • 存储的是实际的数据值。
    • 在内存中存储在栈中。
    • 是值传递,传递的是实际的数据值。
  • 引用类型(Reference Types):
    • 存储的是对象的引用(内存地址)。
    • 对象存储在堆中。
    • 是引用传递,传递的是对象的引用。

idea的常用快捷键

  • Ctrl + P:查看形参
  • Ctrl + D:复制当前行到下一行
  • Ctrl + Y:删除代码
  • Ctrl + Shift + ,:一键生成标准的JavaBean代码[需要下载ptg插件]
  • Alt + Insert:生成标准的JavaBean代码
  • Ctrl + Alt + V:自动将该表达式或代码块提取为一个新的变量
  • Ctrl + Alt + L:格式化代码
  • Ctrl + Alt + M:提取方法
  • Alt + Enter:快速修复
  • Ctrl + Insert:自动生成代码
  • Tab:自动补全代码
  • Ctrl + B:跳转到声明处
  • Ctrl + Alt + T:选定结构(if语句,while语句)
  • Alt + 鼠标拖动:多重选择[对多个对象同时进行删除或修改]
  • Ctrl + Shift + ↑/↓ :代码上下移动
  • 选中 + Shift + F6:向下整体修改
  • Ctrl + N:查找并导航到指定的类
  • Ctrl + F12:调出当前编辑文件的结构视图或者是类的成员列表

方法区

方法区:用于描述存储已被虚拟机加载的类信息、字段信息、方法信息、常量、静态变量、即时编译器编译后的代码缓存等数据的内存区域
永久代(JDK8以前)/元空间(JDK8及以后):是方法区的具体实现

在JDK 8之前,方法区的实现通常是永久代。而在JDK 8及之后,方法区的实现主要是元空间。

将永久代替换为元空间的主要原因是解决了永久代存在的一些问题,使Java虚拟机更适应现代应用程序的需求。

  1. 内存管理方式

    • 永久代永久代的内存空间是在Java堆内的一部分,它受到了Java堆内存的大小限制。
    • 元空间元空间通常位于本地内存中,不再与Java堆绑定,因此不受Java堆内存的限制。这使得元空间可以根据需要动态扩展,避免了永久代固定大小带来的限制。
  2. 垃圾回收机制

    • 永久代永久代中的垃圾回收通常由Full GC(全局垃圾回收)来执行,效率较低,并且可能会导致长时间的停顿。
    • 元空间:元空间的内存管理不再依赖于Java虚拟机的垃圾回收机制,而是由本地内存的管理机制来负责。这降低了垃圾回收的成本,并提高了应用程序的性能和稳定性。
  3. 动态调整大小

    • 永久代永久代的大小是固定的,无法动态调整。这可能会导致在某些情况下出现永久代溢出的问题。
    • 元空间元空间可以根据应用程序的需要动态调整大小,不再受到固定大小的限制。这提高了虚拟机的灵活性,并减少了出现内存溢出的风险。
  4. 存储内容

    • 永久代:永久代主要用于存储类的元数据信息和静态变量。
    • 元空间:元空间用于存储类的元数据信息,例如类的结构、方法信息、注解等。与永久代相比,元空间更加灵活,并且可以存储更多类型的元数据信息

总的来说,替换永久代为元空间是为了提高Java虚拟机在处理大量类加载和卸载操作时的性能和可用性。元空间的动态调整大小、本地内存管理以及不依赖于Java虚拟机的垃圾回收机制等特性,使得它更适合应对现代应用程序的需求。

串池

基本概念

  • 串池(String Pool)是Java中的一种特殊的内存区域,用于存储字符串常量。它是字符串常量池的另一种称呼,因为在Java中,字符串常量池主要用于存储字符串常量。[串池中的字符串是不可变的]
  • 当我们创建一个字符串常量时,如果该字符串常量在字符串常量池中不存在,则会在字符串常量池中创建一个新的字符串对象,并将该字符串常量放入其中。如果字符串常量池中已经存在相同内容的字符串对象,则不会创建新的对象,而是直接返回已存在的对象的引用。[只有一份,全局共享]
  • 只有String类型的字符串才会被添加到字符串常量池中。StringBufferStringBuilder类型的字符串不会自动添加到字符串常量池中。这是因为 StringBuffer 和 StringBuilder 是可变的字符串,它们的内容在运行时可以被修改,而字符串常量池中的字符串是不可变的。

内存管理

  • 在JDK7版本开始从方法区中挪到了堆内存

字符串

1 基本概念

Java字符串就是Unicode字符序列。Java里没有内置的字符串类型,而是在标准的类库中提供了一个预定义类,String。每个用双引号""括起来的都是String类的一个实例
字符串在日常开发中最常用, Java字符串的一个重要特点就是字符串不可变

2 字符串的创建

  • 通过字符串字面量创建字符串常量。
  • 通过调用String类的构造函数。
  • 使用字符串连接运算符+创建字符串。[底层通常是使用StringBuilder(或 StringBuffer)进行字符串的拼接]
String str1 = "Hello";//通过字符串字面量创建字符串常量。
String str2 = new String("Hello");//通过调用String类的构造函数。
String str3 = "Hello" + " " + "world";//使用字符串连接运算符`+`创建字符串。

3 字符串存储的内存原理

  • 直接赋值或字符串连接操作中使用的是字符字面量(即单引号括起来的字符)的方式创建的字符串会复用串池中已存在的字符串常量
String s1 = "abc";// 字符串 "abc" 存储在字符串常量池中
String s2 = 'a' + 'b' + 'c';// 在编译时自动转换为字符串常量 "abc",在串池中已经存在"abc",直接复用
System.out.println(s1 == s2);//true
//"=="号在比较引用类型时比较的时地址,这里的true表示s1和s2指向的对象相同
  • new出来的不会复用,而是在堆中开辟一个新的空间
String s3 = new String("abc"); // 使用 new 关键字创建一个新的字符串对象,不会存储在字符串常量池中
System.out.println(s1 == s3); // 输出 false

4 字符串操作

  • length():用于获取字符串的长度,即字符串中包含的字符数。
  • substring(int beginIndex, int endIndex):方法用于截取字符串的子串,从 beginIndex 开始(包括),到 endIndex 结束(不包括)。
  • concat(String str):方法用于连接两个字符串,将参数字符串 str 连接到调用方法的字符串末尾。
  • indexOf(String str):方法用于查找指定子串 str 在字符串中第一次出现的位置,如果找到了,返回该子串的起始索引;如果没有找到,返回 -1。
String str = "Hello, world!";
int length = str.length();//13
String substring = str.substring(0, 5); //"Hello"
String newStr = str.concat(" Welcome"); //"Hello, world! Welcome"
int index = str.indexOf("world"); //7

5 String、StringBuffer、StringBuilder的区别

String

String 类是 Java 中最常用的字符串类,它表示一个不可变的字符序列。这意味着一旦创建了一个 String 对象,它的值就无法更改。因此,每次对字符串进行修改时都会创建一个新的字符串对象,旧的字符串对象则会被丢弃。这种不可变性使得字符串在多线程环境下是安全的,并且可以作为常量使用。
用途

  • 表示字符串常量。
  • 在字符串操作不频繁的场景下使用,例如配置文件、类文件的路径等。
StringBuffer

StringBuffer 类是 Java 中可变字符串的实现,它允许我们在字符串中进行添加、修改和删除操作。与 String 不同,StringBuffer 是可变的,可以动态地改变其内容,而不会创建新的对象。因此,StringBuffer 适用于需要频繁进行字符串操作的场景,例如在循环中构建字符串、拼接大量字符串等。
用途

  • 多线程环境下需要进行频繁的字符串拼接或修改操作时使用。
StringBuilder

StringBuilder 类与 StringBuffer 类功能类似,也是可变字符串的实现。与 StringBuffer 不同的是,StringBuilder 是非线程安全的,但是它的性能通常更好。因此,如果在单线程环境中进行字符串操作,通常优先选择 StringBuilder
用途

  • 单线程环境下需要频繁进行字符串操作时使用。

线程问题

  • StringBuffer
    StringBuffer 是线程安全的,因为它的方法都是同步的,即在方法内部使用了 synchronized 关键字来确保在多线程环境下操作字符串时的安全性。这意味着当多个线程同时访问同一个 StringBuffer 对象时,它们的操作会被正确地同步,不会发生数据不一致的情况。
  • StringBuilder
    StringBuilder 是非线程安全的,因为它的方法没有进行同步处理。这样的设计可以提升性能[它不会引入额外的同步开销,这使得它的性能可能会稍微优于 StringBuffer。],但也意味着在多线程环境下同时访问同一个 StringBuilder 对象时,可能会出现竞态条件(race condition),导致数据不一致或其他意外行为。

static 静态变量

1 static关键字主要有两种作用:

  • 为某特定数据类型或对象分配唯一的存储空间,而与创建对象的个数无关。
  • 实现某个方法或属性与类关联在一起而不是对象关联在一起,因此不需要实例化对象,只需要用类名就可以调用静态的属性或方法。

static的注意事项:

  • 静态方法只能访问静态变量和静态方法
  • 非静态方法可以访问静态变量或者静态方法,也可以访问非静态的成员变量和非静态的成员方法
  • 静态方法中是没有this关键字
    [总结]:
    静态方法中,只能访问静态。
    非静态方法可以访问所有。
    静态方法中没有this关键字

2.1 成员变量

Java类提供了两种类型的变量:用static关键字修饰的静态变量不用static关键字修饰的实例变量

  • 静态变量属于类,在内存中只有一个复制,只要静态变量所在的类被加载,这个静态变量就会被分配空间,因此就可以被使用了。对静态变量的引用有两种方式,分别是"类.静态变量"[推荐]和"对象.静态变量"。
  • 实例变量属于对象,只有对象被创建后,实例变量才会被分配内存空间,才能被使用,它在内存中存在多个复制,只有用"对象.实例变量"的方式来引用。
public class Student {
    public static int age;
    public int score;
}

public static void main(String[] args) {
        //非静态属性不可直接用类名直接调用,可以用利用对象调用
        //Student.score;  报错
        Student stu1 = new Student();
        int score = stu1.score;

        //静态属性可直接用类名调用,也可以用对象调用
        int age = Student.age;
        int age1 = stu1.age;
    }

静态变量特点:

  • 被该类所有对象共享
  • 不属于对象,属于类
  • 随着类的加载而加载,优先于对象存在
  • jdk8之前:放在方法区
    jdk8及以后:存放在堆中反射的class对象(即类加载后会在堆中生成一个对应的class对象)的尾部。

静态变量调用方式:

  • 类名调用[推荐]
  • 对象名调用

2.2 成员方法

Java中提供了static(静态)方法和非static(非静态)方法。

  • static方法是类的方法,不需要创建对象就可以被类名调用
  • 而非static方法是对象的方法,只有对象被创建出来后才可以被使用
  • static方法中不能使用this和super关键字,不能调用非static方法,只能访问所属类的静态成员变量和成员方法,因为当static方法被调用时,这个类的对象可能还没被创建,即使已经被创建了,也无法确定调用哪个对象的方法。同理,static方法也不能访问非static类型的变量
public class Student {
   private int age;
   private static String name;
   public static void say(){
        System.out.println("say");
    }

    public void run(){
        System.out.println("run");
    }
    public static void show(){
        System.out.println(name);
        //System.out.println(age); 报错
    }

}

public static void main(String[] args) {
    Student stu1 = new Student();
    
  //静态方法可以直接用类名调用,也可以用对象调用
        Student.say();
        stu1.say();

   //非静态方法只能用对象调用
        //Student.run(); 报错
        stu1.run();
}

单例设计模式:
static一个很重要的用途就是实现单例设计模式。单例模式的特点是该类只能有一个实例,为了实现这一功能,必须隐藏类的构造函数,即把构造函数声明为private,并提供一个创建对象的方法,由于构造对象被声明为private,外界无法直接创建这个类型的对象,只能通过该类提供的方法来获取类的对象,要达到这样的目的只能把创建对象的方法声明为static,程序实例如下:

class Singleton{
	private static Singleton instance=null;
	private Singleton(){}
	public static Singleton getInstance(){
		if(instance==null){
			instance=new Singleton();
		}
		return instance;
	}
}

静态方法特点:

  • 多用在测试类和工具类中
  • Javabean类中很少会用

静态方法调用方式:

  • 类名调用[推荐]
  • 对象名调用

2.3 static代码块

static代码块在类中是独立于成员变量和成员函数的代码块的。静态代码块先执行(优先级比构造方法还高),静态代码块中只能调用静态属性和方法,不能调用非静态的。
只执行一次,非静态代码块在在每次创建新对象的时候都会执行一次
可以应用于数据的初始化

public class Person {
    {
        System.out.println("匿名代码块");
    }
     //静态代码块先执行,且只执行一次
    static {
        System.out.println("静态代码块");
    }
     public Person(){
        System.out.println("构造器");
    }

    public static void main(String[] args) {
        new Person();
        System.out.println("============");
        new Person();
    }
}
/*
输出结果:
    静态代码块
    匿名代码块
    构造器
    ============
    匿名代码块
    构造器
*/

2.4 static内部类

  • static内部类可以不依赖于外部类实例对象而被实例化,而通常的内部类需要外部类实例化后才能实例化。
  • 静态内部类不能访问外部类的普通成员变量,只能访问外部类中的静态成员和静态成员方法,因为静态内部类是与类一起加载的。
  • 只有内部类才能被申明为static
public class Student {
    public static int age;
    public static Object inter;
    public int score;


    public static void say(){
        System.out.println("say");
    }


    public void run(){
        System.out.println("run");
    }

    public static class sinter{
        sinter(){
            System.out.println("这是静态内部类");
            //只能访问外部类中的静态成员和静态成员方法
            System.out.println(age);
            say();
        }

    }

    public class inter{
        inter(){
            System.out.println("这是非静态内部类");
            System.out.println(age+score);
            say();
            run();
        }
    }

}

//运行
public class text {
    public static void main(String[] args) {
        //静态内部类可直接实例化
        Student.sinter sinter = new Student.sinter();

        //非静态内部类需要创建一个外部类对象再实例化
        Student student = new Student();
        Student.inter inter = student.new inter();
    }

}

继承问题

1 继承的概念

继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义(重写),以及追加属性和方法(添加额外的父类没有的子类特有的方法)

2 实现格式

  • 继承通过extends关键字实现
  • 用法:class 子类 extends 父类 {}
    举例: class Dog extends Animal {}

3 子类的特点

  • 可以继承父类的成员变量[如果子类中有同名的成员变量,父类的变量会被隐藏]和虚方法表中的成员方法[非private、非static、非final]
  • 子类可以有自己的成员变量和方法
  • 子类可以重写从父类继承下来的成员方法[如果发生了重写,则会覆盖父类继承下来的成员方法]
  • 子类并不会继承父类的构造方法[因为父类的构造方法名与子类名存在冲突],因此我们创建子类时需手动添加子类的构造方法

注意:
子类中可以使用父类中的静态方法[static修饰],但这是因为静态方法属于类而不是对象,子类可以直接通过父类名来调用父类的静态方法,这种调用方式不涉及继承关系,只是通过类名来访问类的静态成员。它们并不会参与继承的机制。

4 继承中成员变量访问的特点

  • 遵循就近原则:局部变量->成员变量->父类
    • 先在子类的局部变量找,要是找到就用这个局部变量的值(就算有一个名字一模一样的成员变量,那也不会去访问哪个成员变量的)
    • 然后要是在局部变量里面找不到的话,就去成员变量去去找
    • 要是成员变量找不到的话,就去父类的成员变量去找
    • 要是父类没有就找父类的父类
    • ……
    • 要是都没有找到就报错

5 this & super

  • this:代表本类对象(且是调用this所在方法的那个对象)的引用
  • super:代表对象父类的引用,可以调用上一级的那个类的方法或属性

用法:可以解决无法访问子类和父类中相同名称的成员的问题

  • 成员变量:
    • this.成员变量 -> 访问本类成员变量
    • super.成员变量 -> 访问父类成员变量
  • 成员方法:
    • this.成员方法 -> 访问本类成员方法
    • super.成员方法 -> 访问父类成员方法
  • 构造方法:
    • this(…) -> 访问本类构造方法
    • super(…) -> 访问父类构造方法

注意:
不能在静态方法中使用this和super关键字。
因为静态方法是属于类而不是对象实例的,而thissuper关键字是关于对象实例的引用。

构造方法中的默认super
继承中构造方法的访问特点:
​子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化,原因在于,每一个子类构造方法的第一条语句默认都是:super()。但是你只要自己写了super(……)调用父类的带参构造方法,那个默认隐藏的super()就失效了。
注意:子类中所有的构造方法默认隐藏地有一个super(),都会访问父类中无参的构造方法[如果手动添加了一个super(……)调用父类的带参构造方法,那个默认隐藏的super()就会失效]

问题:如果父类中没有无参构造方法,只有带参构造方法,该怎么办呢?

  • 通过使用super关键字去显示的调用父类的带参构造方法
  • 在父类中自己提供一个无参构造方法

推荐方案:
​自己给出无参构造方法


补充说明
除了已经提到的内容外,还有一些关于 thissuper 的使用注意事项:

  1. this() 和 super() 不能同时出现在同一个构造方法中:在同一个构造方法中,this()super() 只能出现一个,并且必须是构造方法的第一条语句。
  2. this() 和 super() 只能用于构造方法this()super() 关键字只能用于构造方法中,不能用于普通方法。
  3. this 和 super 不能用于静态方法thissuper 关键字是关于对象实例的引用,而静态方法属于类而不是对象实例,因此无法在静态方法中使用这两个关键字。
  4. super 关键字可以在构造方法之外使用super 关键字除了可以在构造方法中调用父类构造方法之外,还可以在子类的其他方法中使用来调用父类的方法或属性。

6 方法的重写

  • 继承关系:方法重写是建立在类之间的继承关系上。子类可以继承父类的方法并对其进行重写。
  • 方法签名:子类中重写的方法必须与父类中被重写的方法具有相同的方法签名,包括方法名、参数列表和返回类型。[方法签名是指方法的名称以及参数的类型和顺序]。
  • 访问修饰符:子类中重写的方法的访问修饰符不能比父类中被重写方法的访问修饰符更严格。例如,如果父类中的方法是public,那么子类中重写的方法也必须是public,不能是protected或者private。
  • 异常:子类中重写的方法不能抛出比父类中被重写方法更宽泛的异常,但可以抛出更窄的异常,或者不抛出异常。
  • 返回类型:在Java 5之前,返回类型必须完全匹配。但在Java 5及以后版本,可以使用协变返回类型,即子类中重写的方法的返回类型可以是父类中被重写方法返回类型的子类型。
  • 重写的Override注解:用来检测当前的方法,是否是重写的方法,起到【校验】的作用。就是检查你写的方法有没有严格符合重写的格式。
    下面是一个简单的示例来说明方法重写:
class Animal {
    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog barks");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.makeSound(); // Output: Animal makes a sound
        
        Animal dog = new Dog();
        dog.makeSound(); // Output: Dog barks
    }
}

注意:
在Java中,不能重写静态方法。虽然子类可以定义与父类中静态方法具有相同的名称、参数格式和类型和返回值的方法,但这并不是方法重写,而是方法隐藏。
静态方法是与类相关联的方法,它们不是对象的一部分。当子类定义了一个与父类中静态方法同名的静态方法时,子类的方法将隐藏父类中的方法,而不是重写它。在调用静态方法时,编译器会根据引用类型来确定使用哪个方法。

class Parent {
    static void staticMethod() {
        System.out.println("Parent's static method");
    }
}

class Child extends Parent {
    static void staticMethod() {
        System.out.println("Child's static method");
    }
}

public class Main {
    public static void main(String[] args) {
        Parent.staticMethod(); // Output: Parent's static method
        Child.staticMethod();  // Output: Child's static method

        Parent parent = new Child();
        parent.staticMethod(); // Output: Parent's static method
    }
}

在上面的示例中,即使我们使用Parent类型的引用指向Child对象,调用的仍然是Parent类中的静态方法这是因为静态方法的调用是通过编译时类型(在编译时确定的类型)来决定的,而不是运行时类型(实际对象的类型)

7 继承的优劣

  • 好处:
    • 提高了代码的复用性(多个类相同的成员可以放到同一个父类中)
    • 提高了代码的维护性(如果方法的代码需要修改,修改父类的一处方法即可)
  • 弊端:
    • 继承让类与类之间产生了关系,类的耦合性增强了,当父类发生变化时子类实现也不得不跟着变化,削弱了子类的独立性。

8 java继承的注意事项

  • Java中类只支持单继承,不支持多继承(但是可以实现多个接口,单继承,多实现)
    • 错误范例:class A extends B, C { } [Java不允许存在多继承]
  • Java中所有类的根类都是Object
  • Java中类支持多层继承
  • 继承相关图解如下:

多态问题

1 多态的概念

  • 同类型的对象,表现出的不同形态
  • 表现形式:
    • 父类类型 对象名称 = 子类对象
    • Fu f = new Zi();
  • 多态的前提:
    • 有继承关系
    • 由父类引用指向子类对象
    • 有方法重写
  • 多态的好处:
    使用父类型作为参数,可以接收所有子类对象,体现多态的拓展性与便利

2 多态调用成员的特点

  • 变量调用:编译看左边,运行也看左边
  • 方法调用:编译看左边,运行看右边
class Animal {
    String name = "Animal";
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
    static void staticMethod() {
        System.out.println("Animal's static method");
    }
}

class Dog extends Animal {
    String name = "Dog";
    void makeSound() {
        System.out.println("Dog barks");
    }
    static void staticMethod() {
        System.out.println("Dog's static method");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog(); // 父类引用指向子类对象

        System.out.println(animal1.name); // 编译时看左边,运行时也看左边,输出: "Animal"
        animal1.makeSound(); // 编译时看左边,运行时看右边,输出: "Dog barks"
        animal1.staticMethod(); // 编译时看左边,运行时也看左边,输出: "Animal's static method"
    }
}

3 多态的弊端:

  • 不能调用子类的特有方法[因为在编译的时侯会先检查左边的父类中有没有这个方法,如果没有直接报错]
  • 解决方案:
    将变量转换为子类类型
    Dog d = (Dog) a; [只能强转为new出来的那个类型,强转类型与真实对象类型不一致会报错]

4 强转类型

  • instanceof:用于在运行时确定一个对象是否是某个类的实例或者其子类的实例。它的语法形式为:
  • 用法:object instanceof ClassName
    instanceof运算符返回一个布尔值,如果 object 是 ClassName 类型的实例或者其子类的实例,则返回 true,否则返回 false。[可搭配if语句使用]
class Animal {
    String name = "Animal";
    void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

class Dog extends Animal {
    String name = "Dog";
    void makeSound() {
        System.out.println("Dog barks");
    }

    void fetch() {
        System.out.println("Dog fetches a stick");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal1 = new Dog(); // 父类引用指向子类对象

        if (animal1 instanceof Dog) {
            Dog dog = (Dog) animal1;
            dog.fetch(); // 如果 animal1 是 Dog 类型的实例,则调用 fetch() 方法
        }
    }
}

在jdk14以后有以下新特性

if (animal1 instanceof Dog) {              if (animal1 instanceof Dog d) 
    Dog dog = (Dog) animal1;       <=>     //先判断a是否为Dog类型,如果是,则强转成Dog类型,转换之后变量名为d
                                           //如果不是,则不强转,结果直接是false
}                                          

包与导包

1 包

1.1 包的概念

包就是文件夹。用来管理各种不同功能的Java类,方便后期维护。

1.2 包的定义格式
  • 包名的规则: 公司域名反写+包的作用,需要全部英文小写,见名知意。
    • eg:ink.lusy.blog
1.3 带包编译&带包运行
  • 带包编译:javac -d . 全类名.java
    • eg:java -d . ink.lusy.blog.HelloWorld.java
  • 带包运行:java 全类名
    • eg:java ink.lusy.blog.HelloWorld
  • 全类名:包名+类名

2 导包

2.1 导包的意义
  • 使用不同包下的类时,使用的时候要写类的全路径,写起来太麻烦了
  • 为了简化带包的操作,Java就提供了导包的功能

注意:如果同时使用两个包中的同名类,需要用全类名。[导包将不再适用]

final

final:表示最终的、不可改变的

1 修饰类

作用:不能再被其他类继承

  • 表明该类为最终类,不能再被其他类继承
  • 该类中的所有方法都是最终方法(隐式地被final修饰)

应用场景

  • 工具类(Utility Classes): 如果某个类仅提供一组静态方法或者常量,并且不需要被继承修改,可以将其定义为final,例如Java中的java.lang.Math类就是一个典型的工具类。
  • 不可变类(Immutable Classes): 不可变类是指其实例一旦创建就不能被修改的类,一般来说,不可变类应该是final的,以确保它们的状态不会被修改。
  • 涉及安全敏感信息的类: 如果某个类涉及到处理安全敏感信息,例如密码、密钥等,为了确保安全性,应该将其定义为final,避免被子类继承修改。
  • 单例模式类(Singleton Classes): 单例模式中的类通常需要保证只有一个实例存在,并且提供全局访问点,为了确保单例的正确性,可以将单例类定义为final,防止被继承修改。
  • 为了提高性能的类: 在一些性能敏感的场景下,为了确保类的行为不被修改并且获得更好的性能,可以将其定义为final,使得编译器可以进行更多的优化。
  • 经常需要修改的类(迭代更新会经常改动的类):final类可以避免继承链的耦合性,减少了父类变更导致子类需要同步修改的风险,符合"低耦合,高内聚"的设计原则。

2 修饰方法

作用:子类不能重写(覆盖)该方法
应用场景:对继承没有太大意义的方法,或者父类自身特有的方法,这些方法不需要给子类使用,从而定义为最终方法,在类继承时,可以降低耦合度

3 修饰变量

  • final修饰的变量统称常量。
  • 常量的命名规范:
    • 单个单词:全部大写
    • 多个单词:全部大写,单词之间用下划线隔开

实际开发中,常量一般作为系统的配置信息,方便维护,提高可读性,

3.1 修饰局部变量

作用final修饰的变量只能被赋值一次,是不可改变的
优点:不需要重复的创建对象。

注意
这里说的final修饰的变量不可改变分为两种:

  • 对于基本数据类型:指的是数值不可改变
  • 对于引用数据类型:指的是变量存储的地址不可改变,对象内部的数据可以改变
    [变量存的值不可改变,对于基本数据类型而言,变量存的是数值,对于引用数据类型而言,变量存的是地址]
3.2 修饰全局变量(属性)

作用与上述局部变量一致,但需要注意以下3点

  • final修饰全局变量,必须手动赋初始值
  • 对于final修饰的全局变量,要么直接赋值,要么通过构造方法赋值
  • 对于未直接赋值的final类型的全局变量,所有的构造方法都必须对该变量进行赋值

权限修饰符

代码块

在Java中,代码块是一段用花括号 {} 括起来的代码,它可以在不同的位置出现,并且有不同的类型,包括局部代码块、构造代码块和静态代码块。

1 局部代码块(Local Blocks):

  • 局部代码块是定义在方法体内的代码块,通常用于限定变量的作用范围。
  • 局部代码块中声明的变量的作用范围仅限于该代码块内部。
  • 局部代码块在执行完毕后,其中声明的变量会被销毁,不再占用内存空间。
public void exampleMethod() {
    // 开始局部代码块
    {
        int x = 10;
        System.out.println("x inside local block: " + x);
    }
    // 结束局部代码块
    // 这里无法访问局部代码块中的变量 x
}

2 构造代码块(Initializer Blocks):

  • 构造代码块是定义在类中,没有任何修饰符(如static、public等)的代码块,它在每次创建对象时都会被执行。
  • 构造代码块主要用于初始化对象的共同属性,在每个构造函数之前执行。
  • 构造代码块不能被显式调用,它会在对象创建时自动执行。
public class ExampleClass {
    // 构造代码块
    {
        System.out.println("Constructor block executed");
    }

    public ExampleClass() {
        System.out.println("Constructor executed");
    }

    public static void main(String[] args) {
        ExampleClass obj = new ExampleClass();
    }
}

3 静态代码块(Static Blocks):

  • 静态代码块是定义在类中,使用 static 关键字修饰的代码块,它在类加载时执行,且只执行一次。
  • 静态代码块主要用于进行类的初始化操作,如加载驱动程序、初始化静态变量等。
  • 静态代码块在程序启动时自动执行,无需显式调用。
public class ExampleClass {
    // 静态代码块
    static {
        System.out.println("Static block executed");
    }

    public static void main(String[] args) {
        // 执行 main 方法时会触发类的加载,从而执行静态代码块
        System.out.println("Inside main method");
    }
}

抽象类和抽象方法

1 基本概念

  • 抽象方法:将共性的行为(方法)抽取到父类之后。由于每一个子类执行的内容是不一样,所以,在父类中不能确定具体的方法体,该方法就可以定义为抽象方法。

抽象方法就是以abstract修饰的方法,这种方法只声明返回的数据类型、方法名称和所需的参数,没有方法体,也就是说抽象方法只需要声明而不需要实现

  • 抽象类:如果一个类中存在抽象方法,那么该类就必须声明为抽象类

2 定义格式

  • 抽象方法:public abstract 返回值类型 方法名(参数列表);[分号;不能忘记]
  • 抽象类:public abstract class 类名 {}

3 注意事项

  • 抽象类不能实例化
  • 抽象类中不一定有抽象方法,有抽象方法的类一定是抽象类
    [未包含抽象方法的抽象类,目的就是不想让调用者创建该类对象,通常用于某些特殊的类结构设计。比较少用]
  • 抽象类可以有构造方法[给子类继承]
  • 抽象类的子类
    • 要么重写抽象类中的所有抽象方法
    • 要么是抽象类

4 意义

  • 抽取共性时,无法确定方法体,就可以把方法定义为抽象的。
  • 强制让子类按照某种格式进行重写。[提高代码可读性]

接口

1 基本概念

简单的说,接口就是一种被规范的标准,它定义了一组行为或功能,任何符合这个标准的类都可以被看作是这个接口的实现。接口的表现在于对行为的抽象
[类是对属性的抽象,接口是对行为的抽象]

2 定义和使用

  • 接口用关键字interface来定义:
    • pubilc interface 接口名 {}
    • interface 接口名 {}
    • 两种定义方式的区别:接口的访问权限不一样
  • 接口不能实例化
  • 接口和类之间是实现关系,通过implements关键字表示
    • public class 类名 implements 接口名1 接口名2 …… {}
  • 接口的子类(实现类)
    • 要么重写接口中的所有抽象方法
    • 要么是抽象类
// 定义一个接口
interface Shape {
    // 接口中的常量
    double PI = 3.14159;//前面有默认修饰符:public static final [在编译环节会隐式添加]

    // 接口中的抽象方法
    double calculateArea();//前面有默认修饰符:public abstract [在编译环节会隐式添加]
}

// 实现接口的类
class Circle implements Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    // 实现接口中的抽象方法
    @Override
    public double calculateArea() {
        return PI * radius * radius;
    }
}

// 主类
public class Main {
    public static void main(String[] args) {
        // 调用接口中的常量 -> 静态变量可以直接用类名调用[推荐]
        System.out.println("PI 常量的值: " + Shape.PI);

        // 创建 Circle 对象
        Circle circle = new Circle(5.0);
        
        // 调用实现接口的方法
        double area = circle.calculateArea();
        System.out.println("圆的面积: " + area);
    }
}

3 成员特点

  • 成员变量
    • 只能是常量
    • 默认修饰符:public static final
  • 构造方法
    • 没有
  • 成员方法
    • 只能是抽象方法[JDK7以前]
    • 默认修饰符:public abstract
    • JDK8的新特性: 接口中可以定义有方法体的方法(默认、静态)
    • JDK9的新特性: 接口中可以定义私有方法

4 接口和类之间的关系

  • 类和类的关系
    继承关系,只能单继承,不能多继承,但是可以多层继承
  • 类和接口的关系
    实现关系,可以单实现,也可以多实现,还可以在继承一个类的同时实现多个接口
  • 接口和接口的关系
    继承关系,可以单继承,也可以多继承
    public interface a extends b,c,d…{}

接口多继承的好处
定义了许多接口,如果一个设备要同时实现那么多接口将是不小的开销,对于同能同类的可以把他用一个接口继承起来,这样这个接口就要了那些接口的全部功能,我们使用类只要实现一个就可以了。

5 JDK8以后接口中新增的方法

5.1 默认方法
  • 允许在接口中定义默认方法,需要使用关键字default修饰
    • 作用:解决接口升级的问题[允许在接口中添加新的方法而不会破坏现有的实现类。]
  • 默认方法的定义格式
    • public default 返回值类型 方法名(参数列表) { }
    • eg:public default void show() { }
  • 调用方法
    • 实现类直接调用接口默认方法即可。[类名.默认方法名(参数列表)]
  • 默认方法的注意事项
    • 默认方法不是抽象方法,所以不强制被重写。但是如果被重写,重写的时候要去掉default关键字
    • public可以省略,default不能省略
    • 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
5.2 静态方法
  • 允许在接口中定义静态方法,需要使用关键字static修饰
    • 调用时无需实例化接口的实现类[通常用于实现工具类或提供通用功能的方法]
  • 静态方法的定义格式
    • public static 返回值类型 方法名(参数列表) { }
    • eg:public static void show() { }
  • 调用方法
    • 只能通过接口名调用
  • 静态方法的注意事项
    • 静态方法只能通过接口名调用,不能通过实现类名或者对象名调用
    • public可以省略,static不能省略

默认方法和静态方法功能上的区别

默认方法(Default Methods)

  • 默认方法是接口中带有默认实现的方法,它们允许在接口中添加新的方法而不会破坏现有的实现类。
  • 默认方法可以被实现类选择性地覆盖,以满足特定需求。
  • 默认方法主要用于接口的演进,允许向现有接口添加新的功能而不会影响已有的实现类。

静态方法(Static Methods)

  • 静态方法是在接口中带有 static 关键字修饰的方法,它们不是接口的实例方法,而是与接口直接关联的方法。
  • 静态方法可以直接通过接口名称调用,无需实例化接口的实现类。
  • 静态方法通常用于实现工具类或提供通用功能的方法。

6 JDK9以后接口中新增的方法

接口中可以定义私有方法

  • 权限范围:私有方法的访问权限仅限于接口内部
  • 作用:避免了代码的重复编写、隐藏接口的实现细节
  • 私有方法的注意事项:私有方法无法被实现类覆盖或继承,它们只能在接口内部使用。
6.1 普通的私有方法[对标JDK8中的默认方法]
  • 格式:private 返回值类型 方法名(参数列表) { }
  • eg:private void show() { }
6.2 静态的私有方法[对标JDK8中的静态方法]
  • 格式:private static 返回值类型 方法名(参数列表) { }
  • eg:private static void show() { }

内部类

类的五大成员
属性、方法、构造方法、代码块、内部类

内部类的基本概念
在一个类的里面,再定义一个类。[在A类的内部定义B类,B类就被称为内部类]

public class Outer{
    public class Inner {
        
    }
}//这里的Outer是外部类,Inner是内部类

内部类的作用
内部类表示的事物是外部类的一部分,且内部类单独存在又没有任何意义
比如:汽车的发动机,ArrayList的迭代器。人的心脏

内部类的访问特点

  • 内部类可以直接访问外部类的成员,包括私有
  • 外部类要访问内部类的成员,必须要创建对象[静态内部类中的静态成员不需要创建对象]

1 成员内部类

  • 写在成员位置的,属于外部类的成员。
  • 成员内部类可以被一些修饰符所修饰。[private、默认、protectd、public、static]
  • 在成员内部类里面,JDK16之前不能定义静态变量,JDK16开始才可以定义静态变量
pubilc class Car{
    String carName;
    int carAge;
    int carColor;

    //成员内部类
    class Engine{
        String engineName;
        int engageAge;
    }
}
1.1 获取成员内部类对象
  • 直接创建格式:外部类名.内部类名 对象名 = new 外部类名().new 内部类名();
  • 在外部类中编写方法,对外提供内部类的对象。[适用于内部类被private等修饰时使用]
1.2 外部类成员变量和内部类成员变量重名时,在内部类如何访问
  • 外部类名.this.变量名

2 静态内部类

  • 静态内部类只能访问外部类中的静态变量和静态方法,要访问外部类的非静态成员,则需要通过创建外部类的实例来进行访问。
  • 静态内部类是一种特殊的成员内部类。
pubilc class Car{
    String carName;
    int carAge;
    int carColor;

    //静态内部类
    static class Engine{
        String engineName;
        int engageAge;
    }
}
2.1 创建静态内部类对象的格式
  • 外部类名.内部类名 对象名 = new 外部类名.内部类名();
2.2 成员调用
  • 调用非静态成员的格式:先创建对象,用对象调用
  • 调用静态成员的格式:外部类名.内部类名.方法名()\变量名;

3 局部内部类

  • 将内部类定义在方法里面就叫做局部内部类,类似于方法里面的局部变量
  • 外界是无法直接使用,需要在方法内部创建对象并使用,
  • 该类可以直接访问外部类的成员,也可以访问方法内的局部变量

4 匿名内部类

  • 隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置[更常见]
//匿名内部类的格式
new 类名\接口名() {
    重写方法;
};
//最后的分号;,切记不能漏掉
4.1 格式细节
  • 包含了继承或实现,方法重写,创建对象。
  • 整体就是一个类的子类对象或者接口的实现类对象
4.2 应用场景

当方法的参数是接口或者类时,
以接口为例,可以传递这个接口的实现类对象
如果实现类只要用一次,就可以用匿名内部类简化代码。

public class Example {
    public static void main(String[] args) {
        // 创建一个匿名内部类实例
        EventListener listener = new EventListener() {
            @Override
            public void onEvent(String event) {
                System.out.println("Event received: " + event);
            }
        };

        // 使用该实例
        fireEvent(listener);
    }

    public static void fireEvent(EventListener listener) {
        // 模拟事件触发
        listener.onEvent("Button clicked");
    }

    // 定义事件监听接口
    interface EventListener {
        void onEvent(String event);
    }
}


文章作者: Lu
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Lu !
  目录
>