前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java基础

Java基础

原创
作者头像
橘子又加强了么
发布2023-09-25 09:33:12
2140
发布2023-09-25 09:33:12
举报

JAVA经典面试题

Java的基本数据类型有哪些

整数类型:

byte:8位有符号整数,取值范围为-128到127。

short:16位有符号整数,取值范围为-32,768到32,767。

int:32位有符号整数,取值范围为-2,147,483,648到2,147,483,647。

long:64位有符号整数,取值范围为-9,223,372,036,854,775,808到9,223,372,036,854,775,807。

浮点类型:

float:32位浮点数,取值范围为1.4E-45到3.4028235E+38,精度约为6-7位小数。

double:64位浮点数,取值范围为4.9E-324到1.7976931348623157E+308,精度约为15位小数。

字符类型:

char:16位Unicode字符,取值范围为'\u0000'到'\uffff'。

布尔类型:

boolean:表示逻辑值,只有两个取值:true和false。

String,StringBuffer,StringBuilder

String:

String是Java中最常用的字符串类,它是不可变的(Immutable)。

当创建一个String对象时,它的值就不能再被修改。如果对String对象进行操作(如拼接、替换等),实际上是创建了一个新的String对象。

String对象的不可变性使得它在多线程环境下是线程安全的,可以被多个线程共享而不会出现问题。

由于String的不可变性,频繁的字符串拼接操作会导致大量的临时对象创建和内存开销。

StringBuffer:

StringBuffer是可变的字符串类,用于处理频繁的字符串操作。

StringBuffer对象的值可以修改,而不会创建新的对象。它提供了一系列的方法来进行字符串的拼接、插入、删除、替换等操作。

StringBuffer是线程安全的,它的方法都使用了synchronized关键字进行同步,适用于多线程环境。

StringBuilder:

StringBuilder也是可变的字符串类,与StringBuffer功能类似,但不同的是StringBuilder是非线程安全的。

StringBuilder提供了与StringBuffer相同的方法,用于进行字符串的操作,但没有同步机制。因此,在单线程环境下,StringBuilder的性能更好。

选择使用哪个类取决于具体的需求

如果需要频繁进行字符串操作或在多线程环境下使用,建议使用StringBuffer(线程安全)或

如果单线程环境下,频繁操作字符串,建议使用StringBuilder(单线程环境下性能更好)。

如果字符串不需要被修改,或者只进行少量操作,可以使用String,由于其不可变性,可以带来更好的性能和安全性。

需要注意的是,在进行字符串拼接时,尽量避免直接使用"+"操作符,因为它会产生大量的临时对象,而是使用StringBuffer或StringBuilder的append方法来提高性能。

==和equals方法的区别

==操作符用于比较两个对象的引用是否相等,即判断两个对象是否指向同一个内存地址。具体区别如下:

使用 == 比较基本类型时,比较的是它们的值是否相等。

使用 == 比较引用类型时,比较的是对象的引用是否相等,即它们是否指向同一个对象。

equals()方法:equals()方法是Object类中定义的方法,用于比较两个对象的内容是否相等。equals()方法的默认行为与==操作符相同,即比较对象的引用是否相等。但是,很多类(如String、Integer等)会重写equals()方法,以便根据对象的内容进行比较。

代码语言:txt
复制
String str1 = "Hello";
String str2 = new String("Hello");
String str3 = str1;

System.out.println(str1 == str2);         // false,引用不同
System.out.println(str1 == str3);         // true,引用相同
System.out.println(str1.equals(str2));    // true,内容相等,String类重写了equals方法

总结:

==比较的是对象的引用是否相等。

equals()方法用于比较对象的内容是否相等,具体行为取决于对象的类是否重写了equals()方法。

为什么重写了equlas之后要重写hashcode方法

在Java中,当我们重写了一个类的 equals() 方法时,通常也需要重写 hashCode() 方法。这是因为在使用哈希表(如 HashMapHashSet 等)等基于哈希的数据结构时,hashCode() 方法的正确性对于保持数据结构的性能和一致性是至关重要的。

以下是为什么需要同时重写 equals()hashCode() 的原因:

  1. equals() 方法用于判断两个对象的内容是否相等。根据 Java 规定,如果两个对象通过 equals() 方法比较相等,那么它们的 hashCode() 值必须相等。这是为了保证在哈希表中查找对象时能够正确地找到对应的桶。
  2. hashCode() 方法用于计算对象的哈希码,它的作用是确定对象在哈希表中的存储位置。根据 Java 规定,如果两个对象通过 equals() 方法比较相等,它们的 hashCode() 值必须相等。这是为了保证在哈希表中存储对象时能够正确地分布到各个桶中,提高哈希表的性能。

如果我们只重写了 equals() 方法而没有重写 hashCode() 方法,那么在使用哈希表的时候可能会导致以下问题:

  • 当我们将对象添加到一个哈希表中时,由于没有正确重写 hashCode() 方法,对象将被存储在错误的位置,导致查找时无法正确获取对象。
  • 在使用哈希表的集合类(如 HashSet)时,由于没有正确重写 hashCode() 方法,无法正确判断集合中是否已经包含某个对象,可能导致重复元素的存在。

因此,为了保证 equals()hashCode() 的一致性,我们通常需要同时重写这两个方法,以确保在使用哈希表和集合类时能够正确地操作对象。

为啥有时会出现 4.0 - 3.6 = 0.40000001 这种现象

由于浮点数的精度问题导致的。在计算机中,浮点数的表示方式是有限的,无法精确地表示所有的实数。

Java中使用的浮点数类型(如 float 和 double)采用IEEE 754标准,以二进制形式表示浮点数。然而,由于浮点数采用二进制表示,而实数通常采用十进制表示,存在一些十进制数无法精确转换为二进制的情况。

在你提到的例子中, 4.0 - 3.6 的结果预期应该是 0.4,但由于浮点数的表示限制,计算机无法准确表示 0.4。因此,计算结果可能会存在一个微小的舍入误差,最终得到 0.40000001

对于一些应用场景,特别是涉及到货币计算等需要精确结果的情况,我们通常会使用 BigDecimal 类来进行精确计算,避免浮点数精度问题带来的影响。

集合相关问题

List,Set,Queue,Map四种集合的特点和区别

在 Java 中,List、Set、Queue 和 Map 是常用的集合接口,它们各自具有不同的特点和用途:

  1. List(列表):
    • 允许重复元素。
    • 元素按照插入顺序排序。
    • 可以通过索引访问元素。
    • 常见的实现类有 ArrayList、LinkedList 和 Vector。
  2. Set(集合):
    • 不允许重复元素,每个元素在集合中唯一。
    • 不保证元素的顺序。
    • 常见的实现类有 HashSet、TreeSet 和 LinkedHashSet。
  3. Queue(队列):
    • 通常用于实现先进先出(FIFO)的数据结构。
    • 元素按照插入顺序排序。
    • 常见的实现类有 LinkedList、ArrayDeque 和 PriorityQueue。
  4. Map(映射):
    • 使用键值对的方式存储数据。
    • 键(Key)不允许重复,每个键对应一个值(Value)。
    • 可以通过键来获取对应的值。
    • 常见的实现类有 HashMap、TreeMap 和 LinkedHashMap。

区别总结如下:

  • 重复元素:List 允许重复元素,Set 和 Map 不允许重复元素。
  • 元素顺序:List 和 Queue 都按照插入顺序排序,Set 和 Map 不保证元素的顺序。
  • 索引访问:List 可以通过索引访问元素,Set、Queue 和 Map 不支持索引访问。
  • 数据结构:List 是一个有序的集合,Set 是一个无序的集合,Queue 是一个先进先出的队列,Map 是一个键值对的映射表。

根据具体的需求,选择适合的集合类型可以更好地满足程序的功能和性能要求。

ArrayList和Vector的区别

ArrayList和Vector是Java中常用的两种动态数组实现类,它们有以下区别:

  1. 线程安全性:ArrayList是非线程安全的,而Vector是线程安全的。在多线程环境下,使用ArrayList可能需要进行额外的同步措施来确保线程安全,而Vector在实现上已经进行了同步处理。
  2. 性能:由于Vector是线程安全的,它在执行操作时需要进行同步,这会带来额外的开销,因此在性能上通常比ArrayList稍差。
  3. 增长策略: ArrayList: 无参构造时,初始elementData为空,第一次添加元素时扩容为10,以后按1.5倍扩容 有参构造时,初始为传入参数大小,以后按1.5倍扩容 Vector: 无参构造时,初始为10,按2倍扩容 有参构造时,初始为传入参数大小,按2倍扩容

综上所述,ArrayList更适合在单线程环境下使用,它的性能较高;而Vector适用于多线程环境,因为它提供了线程安全的操作。然而,从Java 1.2开始,引入了更强大的并发集合类(如ConcurrentHashMap和CopyOnWriteArrayList),它们在多线程环境下提供了更好的性能和线程安全性,因此在新的代码中推荐使用这些并发集合类。

线程安全的集合有哪些?

Vector

HashTable

Stack

Java中提供了多个线程安全的集合类,其中一些常用的包括:

  1. ConcurrentHashMap:线程安全的哈希表实现,支持高并发读写操作。
  2. ConcurrentSkipListMap:线程安全的有序映射表实现,基于跳表数据结构,支持高并发读写操作。
  3. ConcurrentSkipListSet:线程安全的有序集合实现,基于跳表数据结构,支持高并发读写操作。
  4. CopyOnWriteArrayList:线程安全的动态数组实现,适用于读操作频繁、写操作较少的场景。
  5. BlockingQueue:阻塞队列接口的实现类,例如ArrayBlockingQueueLinkedBlockingQueue等,提供了线程安全的生产者-消费者模式的队列操作。
  6. ConcurrentLinkedQueue:线程安全的无界队列实现,适用于高并发场景下的队列操作。
  7. ConcurrentLinkedDeque:线程安全的无界双端队列实现,可以在队列的两端进行插入和删除操作。
  8. ConcurrentLinkedHashMap:线程安全的哈希表和链表的结合实现,提供高并发读写操作和LRU(最近最少使用)缓存策略。

这些线程安全的集合类提供了在多线程环境下安全访问和修改集合的功能,避免了手动进行同步操作的复杂性。根据具体的需求和使用场景,选择适合的线程安全集合可以提高代码的性能和可靠性。

线程通信方式有哪些

在 Java 中,常见的线程通信方式包括:

  1. 使用共享变量(Shared Variables):多个线程可以通过共享变量进行通信。线程可以读取和修改共享变量来传递信息和共享数据。然而,需要确保对共享变量的访问是线程安全的,可以使用synchronized关键字或使用java.util.concurrent包中的同步工具类来实现线程安全的共享变量访问。
  2. 使用管程(Monitor):管程是一种高级的线程通信机制,用于解决共享资源的并发访问问题。通过在共享资源上定义操作,如进入、退出、等待、通知等,线程可以按照规定的方式进行协作和同步。Java中的wait()notify()notifyAll()方法以及synchronized关键字提供了管程的实现。
  3. 使用条件变量(Condition):条件变量用于在多线程间进行等待和通知的机制。线程可以通过条件变量等待某个条件的满足,并在条件满足时进行通知。Java中的Condition接口及其实现类提供了条件变量的实现,通常与ReentrantLock结合使用。
  4. 使用信号量(Semaphore):信号量是一种计数器,用于控制同时访问某个资源的线程数量。线程在访问资源之前要获取信号量的许可,许可数量有限。当一个线程完成访问后,释放许可,其他线程可以获取许可并访问资源。Java中的Semaphore类提供了信号量的实现。
  5. 使用阻塞队列(Blocking Queue):阻塞队列是一种特殊的队列,支持线程在队列为空或队列已满时进行阻塞等待和唤醒。线程可以通过阻塞队列进行数据的安全传递和同步。Java中的BlockingQueue接口及其实现类提供了阻塞队列的功能。

这些线程通信方式提供了不同的机制和语义,用于解决多线程编程中的同步、互斥和协作问题。根据具体的场景和需求,选择适当的线程通信方式可以确保线程之间的正确协作和数据共享。

Java8有哪些新特性

Java 8引入了许多新的特性和改进,以下是其中一些主要的特性:

  1. Lambda表达式:Lambda表达式是一种新的语法,简化了匿名内部类的使用,使得代码更加简洁和易读。
  2. 函数式接口:引入了函数式接口的概念,即只有一个抽象方法的接口。函数式接口可以与Lambda表达式一起使用,支持函数式编程风格。
  3. Stream API:引入了Stream API,提供了一种更简洁、更灵活的方式来处理集合数据。Stream API支持丰富的操作,如过滤、映射、排序、聚合等。
  4. 接口的默认方法和静态方法:接口中可以定义默认方法和静态方法,使得接口可以包含方法的具体实现,避免了为了新增方法而破坏已有实现的问题。
  5. 方法引用:方法引用提供了一种简洁的语法来直接引用已有方法,可以使代码更加简洁和可读。
  6. 新的日期/时间 API:引入了java.time包,提供了全新的日期和时间API,解决了旧的java.util.Datejava.util.Calendar类的问题,提供了更好的可读性和处理能力。
  7. Optional类:Optional类是一个容器对象,用于表示一个值存在或不存在。它可以避免空指针异常,并提供了对于可能为空的值的更好的处理方式。
  8. 并发改进:引入了新的并发类,如CompletableFutureStampedLock,以及对并发类库的改进,使得编写并发代码更加简单和高效。

这只是Java 8中的一些主要特性,还有其他一些改进和语言层面的变化,如重复注解、类型注解、新的编译器工具等。这些特性使得Java 8成为一个重要的版本,为Java语言带来了更多的灵活性和功能增强。

HashMap 在 jdk 1.7 和 1.8 的区别?

HashMap在JDK 1.7和JDK 1.8中有一些区别,以下是其中的主要区别:

  1. 数据结构:在JDK 1.7中,HashMap使用数组和链表的组合来实现,即采用数组存储元素,每个数组元素是一个链表。而在JDK 1.8中,当链表长度超过一定阈值(默认为8)时,链表会转换为红黑树,以提高查找和删除操作的效率。
  2. 扩容机制:在JDK 1.7中,当HashMap的容量达到阈值时,会进行扩容操作。扩容涉及到重新计算元素的位置,对于每个元素,都需要重新计算其hash值,并将其放入新的位置。而在JDK 1.8中,扩容时使用了一种更高效的方式,称为"红黑树分化",能够更快地将链表转换为红黑树,并重新计算元素的位置。
  3. 并发安全性:在JDK 1.7中,HashMap是非线程安全的,如果多个线程同时对HashMap进行修改,可能会导致数据结构破坏和数据丢失。而在JDK 1.8中,引入了新的并发安全的HashMap实现,称为ConcurrentHashMap,它提供了更好的并发性能和线程安全。
  4. 性能优化:在JDK 1.8中,对HashMap进行了性能优化。例如,引入了红黑树来替代链表,提高了查找和删除操作的效率;改进了计算哈希值的算法,减少了哈希碰撞的可能性;优化了扩容机制,减少了重新计算元素位置的次数等。

总的来说,JDK 1.8中的HashMap相对于JDK 1.7中的HashMap在性能和并发安全性方面都有所提升。因此,在使用HashMap时,如果条件允许,推荐使用JDK 1.8及以上版本,以获得更好的性能和安全性。

JAVA函数简化的过程

在Java中,函数的简化过程从匿名内部类开始,逐步演化到Lambda表达式。下面是一步一步的介绍:

  1. 匿名内部类:在Java中,可以使用匿名内部类来实现函数式接口的实例化,从而达到函数的目的。例如,假设我们有一个接口MyInterface,其中包含一个抽象方法void doSomething()。我们可以通过创建一个匿名内部类的实例来实现这个接口:
代码语言:java
复制

MyInterface myInterface = new MyInterface() {

代码语言:txt
复制
   @Override
代码语言:txt
复制
   public void doSomething() {
代码语言:txt
复制
       System.out.println("Doing something");
代码语言:txt
复制
   }

};

代码语言:txt
复制

在这个例子中,我们创建了一个实现了MyInterface接口的匿名内部类的实例,并重写了doSomething()方法。

  1. 函数式接口:在Java 8中引入了函数式接口的概念。函数式接口是一个只包含一个抽象方法的接口。通过使用@FunctionalInterface注解,可以明确地标识一个接口为函数式接口。例如,我们可以将上述的MyInterface接口定义为函数式接口:
代码语言:java
复制

@FunctionalInterface

interface MyInterface {

代码语言:txt
复制
   void doSomething();

}

代码语言:txt
复制

使用@FunctionalInterface注解可以确保该接口只包含一个抽象方法。

函数式接口的作用

当我们声明一个接口为函数式接口时,它可以用于以下情况:

  1. 作为参数传递:

假设我们有一个处理字符串的方法processString,它接受一个字符串和一个函数式接口作为参数,并将该函数应用于输入的字符串:

代码语言:java
复制
@FunctionalInterface
interface StringProcessor {
    String process(String str);
}

public class Main {
    public static void processString(String str, StringProcessor processor) {
        String result = processor.process(str);
        System.out.println(result);
    }

    public static void main(String[] args) {
        String input = "Hello, World!";
        processString(input, str -> str.toUpperCase()); // 使用Lambda表达式将字符串转换为大写
        processString(input, str -> str.toLowerCase()); // 使用Lambda表达式将字符串转换为小写
    }
}

在上述示例中,我们声明了一个函数式接口StringProcessor,其中包含一个抽象方法process,用于处理字符串。然后,我们定义了一个processString方法,它接受一个字符串和一个StringProcessor接口作为参数。在main方法中,我们通过Lambda表达式传递了两个不同的字符串处理逻辑。

  1. 作为返回值:

假设我们有一个方法createStringProcessor,它根据传入的条件返回不同的字符串处理器:

代码语言:java
复制
@FunctionalInterface
interface StringProcessor {
    String process(String str);
}

public class Main {
    public static StringProcessor createStringProcessor(boolean toUpper) {
        if (toUpper) {
            return str -> str.toUpperCase();
        } else {
            return str -> str.toLowerCase();
        }
    }

    public static void main(String[] args) {
        StringProcessor upperProcessor = createStringProcessor(true);
        StringProcessor lowerProcessor = createStringProcessor(false);

        String input = "Hello, World!";
        String result1 = upperProcessor.process(input);
        String result2 = lowerProcessor.process(input);

        System.out.println(result1); // 输出:HELLO, WORLD!
        System.out.println(result2); // 输出:hello, world!
    }
}

在上述示例中,我们定义了一个createStringProcessor方法,它根据传入的toUpper参数返回一个字符串处理器。如果toUppertrue,则返回一个将字符串转换为大写的处理器;如果toUpperfalse,则返回一个将字符串转换为小写的处理器。在main方法中,我们分别使用这两个返回的字符串处理器对输入字符串进行处理。

这些示例展示了函数式接口的使用场景,通过函数式接口,我们可以以更灵活的方式传递和使用函数,从而使代码更加简洁、可读和可组合。

  1. Lambda表达式:Lambda表达式是一种简洁的语法形式,用于实现函数式接口。Lambda表达式可以用来替代匿名内部类的实例化。上述的匿名内部类实例化可以使用Lambda表达式来简化:
代码语言:java
复制

MyInterface myInterface = () -> System.out.println("Doing something");

代码语言:txt
复制

在这个例子中,Lambda表达式() -> System.out.println("Doing something")实现了MyInterface接口的抽象方法doSomething()

Lambda表达式的基本语法结构为:(参数列表) -> { 方法体 }。在这个例子中,Lambda表达式没有参数,方法体只有一行代码。

  1. 方法引用:在某些情况下,Lambda表达式可以进一步简化为方法引用。方法引用是对已有方法的一个简洁的引用,可以直接使用方法的名称来替代Lambda表达式。例如,假设存在一个静态方法void printMessage(String message),可以使用方法引用来替代Lambda表达式:
代码语言:java
复制

MyInterface myInterface = MyClass::printMessage;

// MyClass类中的静态方法

static void printMessage(String message) {

代码语言:txt
复制
   System.out.println(message);

}

代码语言:txt
复制

在这个例子中,MyClass::printMessage是对静态方法printMessage的方法引用。

使用Lambda表达式和方法引用可以使代码变得更加简洁、易读和易维护。它们在处理函数式编程和处理集合等数据操作时非常有用。但是请注意,Lambda表达式和方法引用只能用于函数式接口,即只包含一个抽象方法的接口。

java 基础

Java 特性

  1. 跨平台性(Platform Independence):Java程序可以在不同的操作系统上运行,因为它是一种跨平台的语言。这是通过Java的"Write Once, Run Anywhere"(一次编写,到处运行)特性实现的,即一次编写的代码可以在任何支持Java的平台上运行,只要有对应的Java虚拟机(JVM)。
  2. 面向对象(Object-Oriented):Java是一种面向对象的编程语言,它支持面向对象编程原则,如封装、继承和多态。这有助于创建模块化和可维护的代码。
  3. 简单和易学(Simple and Easy to Learn):Java设计追求简单性,具有清晰的语法和丰富的标准库。这使得它相对容易学习和使用,尤其对于初学者来说。
  4. 安全性(Security):Java提供了多层次的安全性,包括字节码验证和安全管理器。这些机制有助于防止恶意代码对系统的破坏。
  5. 自动内存管理(Automatic Memory Management):Java具有垃圾回收机制,可以自动管理内存分配和释放,减少了内存泄漏和悬挂指针等常见错误的发生。
  6. 多线程支持(Multithreading Support):Java内置了多线程支持,使开发者能够轻松创建多线程应用程序,提高了并发性能。
  7. 强类型检查(Strongly Typed):Java是一种强类型语言,这意味着变量的类型必须在编译时明确定义,有助于减少类型错误。
  8. 动态性(Dynamic): Java具有一些动态编程特性,如反射,可以在运行时获取和操作类的信息。

JAVA static 关键字

在Java中,使用 static 关键字修饰的方法和不使用 static 关键字修饰的方法有以下区别:

静态方法(使用 static 修饰)

  • 属于类本身,不依赖于类的实例对象。
  • 可以直接通过类名来调用,无需创建类的实例。
  • 静态方法可以被类的所有实例共享,可以在任何地方直接调用,包括其他类中。
  • 静态方法不能访问非静态的成员变量和非静态的方法,只能访问静态成员变量和静态方法。
  • 静态方法不能被子类重写。

实例方法(不使用 static 修饰)

  • 属于类的实例对象,在创建类的实例后才能调用。
  • 实例方法可以访问和操作实例的成员变量和其他实例方法。
  • 每个类的实例都有自己的一份实例方法,彼此之间互不影响。
  • 实例方法可以被子类重写(覆盖)。

JAVA final关键字

在Java中,final关键字用于表示不可变性和最终性。它可以应用于变量、方法和类。

对于变量:当一个变量被声明为final时,它的值不能再被修改,即成为一个常量。一旦被赋值,其值就无法改变。final变量通常用全大写字母命名,多个单词之间使用下划线分隔。

对于方法:当一个方法被声明为final时,它不能被子类重写(覆盖)。这意味着该方法的实现在父类中是最终的,子类不能对其进行修改。final方法通常用于确保方法的行为在继承层次结构中保持一致。

对于类:当一个类被声明为final时,它不能被继承。这意味着其他类无法扩展该类,即不能创建该类的子类。final类通常用于表示不可变的类,或者是为了安全性或性能的考虑。

使用final关键字的好处包括:

安全性:通过将变量、方法或类声明为final,可以防止其被修改或继承,确保其行为的稳定性和安全性。

优化:编译器可以对final变量进行优化,例如进行内联优化,以提高程序的性能。

可读性和可维护性:使用final关键字可以明确代码的意图,使代码更易读和易于维护。

当使用final关键字修饰引用类型变量时,确实是表示对变量的引用是不可变的,但是并不意味着对象本身是不可变的。下面是一个示例代码:

代码语言:txt
复制
public class FinalExample {
    public static void main(String[] args) {
        final StringBuilder sb = new StringBuilder("Hello");
        System.out.println(sb); // 输出:Hello

        sb.append(" World");
        System.out.println(sb); // 输出:Hello World

        // 重新赋值给sb,这是不允许的,会编译错误
        // sb = new StringBuilder("Goodbye");
    }
}

如果要实现对象本身的不可变性,可以采取其他方式,例如将成员变量私有化,并提供只读方法,以确保对象在创建后不可修改。

JAVA抽象类

抽象类的含义

Java中使用abstract关键字修饰的类,即为抽象类,抽象类的含义是:一个类中没有包含一个足够的信息来描绘一个具体的对象。

抽象类不能实例化,但是类的其他功能与普通类一样,成员变量、成员方法和构造方法的访问方式和普通类一样

由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用

父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。

在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

抽象方法

如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。

Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。

抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

示例:

代码语言:txt
复制
public abstract class Employee
{
   private String name;
   private String address;
   private int number;
   
   public abstract double computePay();
   
   //其余代码
}

声明抽象方法会造成以下两个结果:

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。

抽象类总结规定:

  1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
  2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
  3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
  4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
  5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

Java接口

接口定义了一组方法的签名,但没有具体的实现,只是规定了类应该遵循的行为。类可以实现一个或多个接口,并提供接口中定义的方法的具体实现。

理解:想象你正在组装一辆汽车。汽车由多个组件组成,例如引擎、车轮、座椅等。在这个场景中,接口就像一个组件的规范,它定义了该组件应该具有的功能和特征,但并不关心具体的实现方式。例如,引擎接口可能规定了启动、加速和停止这些方法的签名,但不关心具体的引擎如何实现这些功能。这样,不同的引擎供应商可以根据接口规范实现自己的引擎,只要它们提供了规定的方法和行为即可。

在Java中,接口类似于这个组件规范。它定义了一组方法的签名,任何类可以实现这个接口,并提供方法的具体实现。实现接口的类必须实现接口中定义的所有方法,以满足接口的规范。

接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类

在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

基于代码解释:

代码语言:txt
复制
//接口类型可以用来声明一个变量,他们可以成为一个空指针
MyInterface myVar; //其中 MyInterface 是一个接口类型,当我们声明一个接口类型的变量时,如果没有为其赋值,它的值将为 null

// 如果我们有一个类 MyClass 实现了接口 MyInterface,可以进行下面的赋值
MyInterface myVar = new MyClass();  //这样,接口类型的变量 myVar 将绑定到 MyClass 类的实例对象,我们可以通过 myVar 调用接口中定义的方法

接口与类的区别

接口不能用于实例化对象。

接口没有构造方法。

接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。

接口不能包含成员变量,除了 static 和 final 变量。

接口不是被类继承了,而是要被类实现。

接口支持多继承。

接口特性

接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。

接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。

接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

抽象类和接口的区别

  1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
  2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
  3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
  4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

注:JDK 1.8 以后,接口里可以有静态方法和方法体了。

注:JDK 1.8 以后,接口允许包含具体实现的方法,该方法称为"默认方法",默认方法使用 default 关键字修饰。更多内容可参考 Java 8 默认方法。

注:JDK 1.9 以后,允许将方法定义为 private,使得某些复用的代码不会把方法暴露出去。更多内容可参考 Java 9 私有接口方法。

接口声明方式
代码语言:txt
复制
[可见度] interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}

接口有以下特性:

  • 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
  • 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
  • 接口中的方法都是公有的。

实例:

代码语言:txt
复制
/* 文件名 : Animal.java */
interface Animal {
   public void eat();
   public void travel();
}
接口的实现和继承

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。

类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。

实现一个接口的语法,可以使下面的方式:

代码语言:txt
复制
class MyClass implements Interface1, Interface2, Interface3 {
    // 类的实现代码
}

在实现接口的时候,也要注意一些规则:

  • 一个类可以同时实现多个接口。
  • 一个类只能继承一个类,但是能实现多个接口。
  • 一个接口能继承另一个接口,这和类之间的继承比较相似。

重写接口中声明的方法时,需要注意以下规则:

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

接口继承:

代码语言:txt
复制
interface Interface1 {
    // 接口1的内容
}

interface Interface2 {
    // 接口2的内容
}

interface Interface3 extends Interface1, Interface2 {
    // 接口3的内容
}

综合实例:

代码语言:txt
复制
interface Animal {
    void eat();
    void sleep();
}


interface Swimmer extends Animal {
    void swim();
}

class Dolphin implements Swimmer {
    public void eat() {
        System.out.println("Dolphin is eating.");
    }

    public void sleep() {
        System.out.println("Dolphin is sleeping.");
    }

    public void swim() {
        System.out.println("Dolphin is swimming.");
    }

    public static void staticFun(){
        System.out.println("这是接口的静态方法");
    }
}

public class Main {
    public static void main(String[] args) {
        Dolphin dolphin = new Dolphin();
        dolphin.eat();
        dolphin.sleep();
        dolphin.swim();
        dolphin.staticFun();
    }
}

输出结果:

代码语言:txt
复制
Dolphin is eating.
Dolphin is sleeping.
Dolphin is swimming.
这是接口的静态方法

Java泛型

泛型的含义

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数

适用场景:

写一个排序方法,能够对整型数组、字符串数组甚至其他任何类型的数组进行排序,该如何实现?

答案是:可以使用 Java 泛型。使用 Java 泛型的概念,我们可以写一个泛型方法来对一个对象数组排序。然后,调用该泛型方法来对整型数组、浮点数数组、字符串数组等进行排序。

泛型方法

如何构造泛型方法:

  • 所有泛型方法声明都有一个类型参数声明部分(由尖括号分隔),该类型参数声明部分在方法返回类型之前 (可以理解为声明使用了哪些泛型)
  • 每一个类型参数声明部分包含一个或多个类型参数,参数间用逗号隔开。(一个泛型方法中可以使用多个参数类型)
  • 类型参数能被用来声明返回值类型,并且能作为泛型方法得到的实际参数类型的占位符(既可以作为方法声明时返回类型的指定,也可以在方法接收参数中,作为实参的占位符(就是可以当形参))
  • 注意类型参数只能代表引用型类型,不能是原始类型(像 int、double、char 等)

java中的泛型标记符:

  • E - Element (在集合中使用,因为集合中存放的是元素)
  • T - Type(Java 类)
  • K - Key(键)
  • V - Value(值)
  • N - Number(数值类型)
  • ? - 表示不确定的 java 类型

下面是一个泛型排序方法示例:

代码语言:txt
复制
import java.util.Comparator;
public class GenericSort {
    public static <T> void sort(T[] arr, Comparator<? super T> comparator) {
        int n = arr.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - i - 1; j++) {
                if (comparator.compare(arr[j], arr[j + 1]) > 0) {
                    T temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
    }
    public static void main(String[] args) {
        String[] strings = {"yaochuanbiao", "lidongni", "zhangyuanqing"};
        Comparator<String> comparator = String::compareTo; // 使用默认的比较器
        sort(strings, comparator);
        for (String s : strings) {
            System.out.print(s + " ");
        }
    }
}

有界的类型参数:

可能有时候,你会想限制那些被允许传递到一个类型参数的类型种类范围。例如,一个操作数字的方法可能只希望接受Number或者Number子类的实例。这就是有界类型参数的目的。

要声明一个有界的类型参数,首先列出类型参数的名称,后跟extends关键字,最后紧跟它的上界。

示例如下:

代码语言:txt
复制
public static <T extends Comparable<T>> T maximum(T x, T y, T z)
   {                     
      T max = x; // 假设x是初始最大值
      if ( y.compareTo( max ) > 0 ){
         max = y; //y 更大
      }
      if ( z.compareTo( max ) > 0 ){
         max = z; // 现在 z 更大           
      }
      return max; // 返回最大对象
   }
泛型类

泛型类的声明和非泛型类的声明类似,除了在类名后面添加了类型参数声明部分。

泛型类的类型参数声明部分也包含一个或多个类型参数,参数间用逗号隔开。一个泛型参数,也被称为一个类型变量,是用于指定一个泛型类型名称的标识符

下面是一个泛型类声明的示例:

代码语言:txt
复制
public class Box<T> {
   
  private T t;
 
  public void add(T t) {
    this.t = t;
  }
 
  public T get() {
    return t;
  }
 
  public static void main(String[] args) {
    Box<Integer> integerBox = new Box<Integer>();
    Box<String> stringBox = new Box<String>();
 
    integerBox.add(new Integer(10));
    stringBox.add(new String("菜鸟教程"));
 
    System.out.printf("整型值为 :%d\n\n", integerBox.get());
    System.out.printf("字符串为 :%s\n", stringBox.get());
  }
}

运行结果如下:

代码语言:txt
复制
整型值为 :10

字符串为 :菜鸟教程
类型通配符

类型通配符一般是使用 ? 代替具体的类型参数。例如 List<?> 在逻辑上是 List<String>,List<Integer> 等所有 List<具体类型实参> 的父类

示例:

代码语言:txt
复制
import java.util.*;
 
public class GenericTest {
     
    public static void main(String[] args) {
        List<String> name = new ArrayList<String>();
        List<Integer> age = new ArrayList<Integer>();
        List<Number> number = new ArrayList<Number>();
        
        name.add("icon");
        age.add(18);
        number.add(314);
 
        getData(name);
        getData(age);
        getData(number);
       
   }
 
   public static void getData(List<?> data) {
      System.out.println("data :" + data.get(0));
   }
}

运行结果:

代码语言:txt
复制
data :icon
data :18
data :314

解析: 因为 getData() 方法的参数是 List<?> 类型的,所以 name,age,number 都可以作为这个方法的实参,这就是通配符的作用。

类型通配符上限通过形如List<? extends Number>来定义,如此定义就是通配符泛型值接受Number及其下层子类类型

类型通配符下限通过形如 List<? super Number> 来定义,表示类型只能接受 Number 及其上层父类类型,如 Object 类型的实例

Java异常处理

Exception类的层次

Throwable 类有两个子类,Exception类和Error类。

所有的异常类是从 java.lang.Exception 类继承的子类。

Exception 类有两个主要的子类:IOException 类和 RuntimeException

Error 用来指示运行时环境发生的错误,Java 程序通常不捕获错误。错误一般发生在严重故障时,它们在Java程序处理的范畴之外。例如:JVM 内存溢出。一般地,程序不会从错误中恢复。

Exception类的层次
Exception类的层次
捕获异常

使用 try 和 catch 关键字可以捕获异常。try/catch 代码块放在异常可能发生的地方。

try/catch代码块中的代码称为保护代码,使用 try/catch 的语法如下:

代码语言:txt
复制
try
{
   // 程序代码
}catch(ExceptionName e1)
{
   //Catch 块
}

示例:

代码语言:txt
复制
// 文件名 : ExcepTest.java
import java.io.*;
public class ExcepTest{
 
   public static void main(String args[]){
      try{
         int a[] = new int[2];
         System.out.println("Access element three :" + a[3]);
      }catch(ArrayIndexOutOfBoundsException e){
         System.out.println("Exception thrown  :" + e);
      }
      System.out.println("Out of the block");
   }
}

运行结果:

代码语言:txt
复制
Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
Out of the block

多重捕获块:

代码语言:txt
复制
try{
   // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}catch(异常类型3 异常的变量名3){
  // 程序代码
}

可以在 try 语句后面添加任意数量的 catch 块。

如果保护代码中发生异常,异常被抛给第一个 catch 块。

如果抛出异常的数据类型与 ExceptionType1 匹配,它在这里就会被捕获。

如果不匹配,它会被传递给第二个 catch 块。

如此,直到异常被捕获或者通过所有的 catch 块

throw和throws和finally

在Java中, throwthrows 关键字是用于处理异常的。

throw 关键字用于在代码中抛出异常,而 throws 关键字用于在方法声明中指定可能会抛出的异常类型。

throw关键字:

throw 关键字用于在当前方法中抛出一个异常。

通常情况下,当代码执行到某个条件下无法继续正常执行时,可以使用 throw 关键字抛出异常,以告知调用者当前代码的执行状态。

例如,下面的代码中,在方法中判断 num 是否小于 0,如果是,则抛出一个 IllegalArgumentException 异常

代码语言:txt
复制
public void checkNumber(int num) {
  if (num < 0) {
    throw new IllegalArgumentException("Number must be positive");
  }
}

throws关键字:

throws 关键字用于在方法声明中指定该方法可能抛出的异常。当方法内部抛出指定类型的异常时,该异常会被传递给调用该方法的代码,并在该代码中处理异常。

例如,下面的代码中,当 readFile 方法内部发生 IOException 异常时,会将该异常传递给调用该方法的代码。在调用该方法的代码中,必须捕获或声明处理 IOException 异常。

代码语言:txt
复制
public void readFile(String filePath) throws IOException {
  BufferedReader reader = new BufferedReader(new FileReader(filePath));
  String line = reader.readLine();
  while (line != null) {
    System.out.println(line);
    line = reader.readLine();
  }
  reader.close();
}

一个方法可以声明抛出多个异常,多个异常之间用逗号隔开。

代码语言:txt
复制
import java.io.*;
public class className
{
   public void withdraw(double amount) throws RemoteException,
                              InsufficientFundsException
   {
       // Method implementation
   }
   //Remainder of class definition
}

finally关键字

finally 关键字用来创建在 try 代码块后面执行的代码块。

无论是否发生异常,finally 代码块中的代码总会被执行。

在 finally 代码块中,可以运行清理类型等收尾善后性质的语句。

finally 代码块出现在 catch 代码块最后,语法如下:

代码语言:txt
复制
try{
  // 程序代码
}catch(异常类型1 异常的变量名1){
  // 程序代码
}catch(异常类型2 异常的变量名2){
  // 程序代码
}finally{
  // 程序代码
}

实例:

代码语言:txt
复制
public class ExcepTest{
  public static void main(String args[]){
    int a[] = new int[2];
    try{
       System.out.println("Access element three :" + a[3]);
    }catch(ArrayIndexOutOfBoundsException e){
       System.out.println("Exception thrown  :" + e);
    }
    finally{
       a[0] = 6;
       System.out.println("First element value: " +a[0]);
       System.out.println("The finally statement is executed");
    }
  }
}

运行结果:

代码语言:txt
复制
Exception thrown  :java.lang.ArrayIndexOutOfBoundsException: 3
First element value: 6
The finally statement is executed
  • catch 不能独立于 try 存在。
  • 在 try/catch 后面添加 finally 块并非强制性要求的。
  • try 代码后不能既没 catch 块也没 finally 块。
  • try, catch, finally 块之间不能添加任何代码。
try-with-resource语法糖
声明自定义异常

ArrayList和LinkList的区别

存储结构

ArrayList:基于索引的数据接口,它的底层是数组

LinkedList:基于双向链表,底层使用链表来保存集合中的元素

优势劣势:

Arraylist:

优势:以O(1)时间复杂度对元素进行随机访问

劣势:删除数据却是开销很大,因为这需要重排数组中的所有数据

LinkedList:

优势:插入,添加,删除操作速度更快

劣势:比ArrayList更占内存,因为LinkedList为每一个节点存储了两个引用

是否线程安全

ArrayList和LinkList:线程不安全的

HashMap相关问题

HashMap的键值对存储

HashMap中的数据是以键值对(Key-Value)的形式存储的,每个键对应唯一的值。键和值可以是任意非空对象,但键不能重复(根据equals()和hashCode()方法进行判断)

equals()和hashCode()方法

为了正确地使用HashMap,键对象必须正确地实现equals()和hashCode()方法。equals()方法用于比较键的相等性,hashCode()方法用于计算键的哈希码

哈希算法:

HashMap使用键的哈希码(通过hashCode()方法获取)来确定键值对在内部数组中的存储位置。哈希算法尽量使得键均匀分布,以提高性能

HashMap如何解决哈希冲突

由于哈希算法的限制,不同的键可能会映射到相同的哈希桶(数组索引)上,造成冲突。HashMap使用链表或红黑树来解决冲突,链表用于短小的链表,红黑树用于长度超过阈值的链表,以提高查找的效率

性能特点

HashMap提供了常数时间复杂度的插入、删除和查找操作(平均情况下)。但在极端情况下,如哈希冲突较多时,性能可能退化为O(n)

迭代顺序

HashMap的迭代顺序并不是按照插入顺序或者键的顺序来确定的,而是根据哈希桶的顺序进行的。如果需要有序遍历,可以使用LinkedHashMap

是否线程安全

非线程安全的,多线程环境下需要进行同步处理,如使用ConcurrentHashMap),或者使用线程安全的HashTable

扩容机制

当HashMap中的元素数量超过负载因子(默认为0.75)与数组容量的乘积时,会触发扩容操作,以保持较低的哈希冲突率和更好的性能

冲突解决过程

在HashMap中,当发生哈希冲突(即不同的键映射到了相同的哈希桶)时,会使用红黑树来解决冲突。具体的步骤如下:

哈希桶结构:

HashMap内部维护一个数组,称为哈希桶(Hash Bucket)。

每个哈希桶存储一个链表或红黑树的根节点。

冲突处理:

当发生哈希冲突时,新的键值对会被添加到冲突桶(冲突链表或红黑树)的末尾。

如果冲突链表长度小于阈值(默认为8),则继续使用链表进行存储。

如果冲突链表长度达到阈值或超过了阈值,则将链表转换为红黑树。

链表转红黑树:

当链表转换为红黑树时,首先会创建一个新的红黑树节点作为根节点,并将链表的元素逐个转移到红黑树中。

在转移过程中,会根据键的哈希值进行比较,按照二叉搜索树的规则将节点插入到红黑树的合适位置。

插入节点后,会进行红黑树的平衡操作,包括颜色变换和旋转,以保持红黑树的平衡性。

冲突解决:

当进行查找操作时,首先根据键的哈希值确定哈希桶的位置。

如果该位置是一个红黑树的根节点,则通过红黑树的查找操作进行查找。

如果该位置是一个链表的头节点,则通过链表的顺序查找进行查找。

通过使用红黑树来解决哈希冲突,HashMap能够保持较低的冲突率和更好的性能。红黑树的平衡性能保证了查找、插入和删除操作的时间复杂度为O(log n),相对于链表的线性查找效率更高。这使得HashMap在大量数据存储和查找的场景下表现出色。

Java中的封装

封装的含义:

封装(Encapsulation)是一种面向对象编程的原则和机制,用于将类的属性(数据)和方法(行为)封装在一个单元内部,并对外部提供访问和操作的接口

数据隐藏:通过将类的属性设置为私有(private),限制了对属性的直接访问。这样可以防止外部代码直接修改类的属性,确保数据的一致性和完整性。

公共接口:通过定义公共方法(getter和setter)来提供对私有属性的访问和修改。公共方法充当了类与外部交互的接口,控制对属性的访问和操作。公共方法可以对属性进行验证、计算或其他处理,隐藏了底层实现细节。

访问控制:通过使用访问修饰符(如public、private、protected)来限制对类成员的访问权限。私有成员只能在类的内部访问,公共成员可以在类的外部访问,受保护成员可以在同一包内或子类中访问。这样可以控制类的成员对外部的可见性,提供了更好的封装性。

java继承

Java继承的含义:

继承(Inheritance)是一种面向对象编程的概念,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。子类可以继承父类的非私有成员(字段和方法),并且可以添加自己的成员或覆盖父类的方法

代码重用:继承允许子类重用父类的代码。子类可以直接访问并使用父类的非私有成员,无需重新编写相同的代码。这样可以减少代码的冗余,提高代码的可维护性和可重用性。

层次关系:继承建立了类之间的层次关系。通过继承,可以创建一个类的层次结构,其中父类作为通用的基础类,子类可以继承并扩展父类的功能。这样可以使类之间的关系更加清晰和有组织。

方法覆盖(重写):子类可以覆盖父类的方法,即在子类中重新实现父类的方法。通过方法覆盖,子类可以根据自身的需求改变或扩展父类的行为。这提供了多态性的特性,允许通过父类的引用调用子类的方法。

继承关系的特性:子类继承了父类的属性和方法,包括公共(public)和受保护(protected)成员。私有(private)成员和构造函数不会被继承。子类可以访问继承的成员,可以新增自己的成员,也可以覆盖父类的方法。

is-a关系:继承反映了is-a关系。子类是父类的一种特殊类型,具备父类的特性,并且可以被当作父类的实例使用。例如,Cat类继承自Animal类,我们可以说"Cat is an Animal"

Java 为什么是单继承
  1. 减少复杂性:单继承可以减少编程语言的复杂性,使语言更容易学习和使用。多继承会引入复杂的继承图和潜在的歧义,因为一个类可以继承多个父类的属性和方法,这可能导致不确定性和难以排查的问题。
  2. 避免菱形继承问题:多继承可能导致菱形继承问题,也称为钻石问题。这种情况发生在一个类从两个不同的父类继承相同的方法或属性时,编译器难以确定应该使用哪个方法或属性。单继承可以消除这种歧义,因为每个子类只有一个直接的父类。
  3. 提高代码可读性:单继承使代码更易于理解。当一个类只有一个直接的父类时,你可以更容易地追踪类的行为和属性来源,而不需要考虑多个父类之间的交互。
  4. 促进接口的使用:为了弥补单继承的限制,Java引入了接口(interface)的概念,允许类实现多个接口。这样,类可以获得来自多个源头的行为,而不会引入多继承的复杂性问题。
  5. 维护灵活性:单继承使得类的继承关系更稳定,这有助于减少代码的脆弱性。如果一个类具有多个父类,当其中一个父类的实现发生变化时,可能会影响所有子类,这可能导致不可预测的结果。

Java多态

Java多态的含义:

多态(Polymorphism)是面向对象编程的一个重要概念,它允许使用父类的引用来引用子类的对象,并根据实际对象的类型来调用相应的方法。多态性使得可以在不同的对象上使用相同的方法名,但根据对象的实际类型,可以产生不同的行为

方法重写(Override):多态性基于方法的重写。子类可以根据自身的需求重写(覆盖)父类的方法。当调用一个被子类重写的方法时,根据实际对象的类型,会执行相应的子类方法,而不是父类的方法。

动态绑定(Dynamic Binding):在运行时,根据对象的实际类型来确定调用哪个方法。当使用父类的引用指向子类的对象时,编译器只能确定引用的静态类型,而具体调用的方法将在运行时确定。

向上转型(Upcasting):通过将子类的对象赋值给父类的引用,实现了向上转型。这样可以将子类对象视为父类对象,使用父类引用调用子类对象的方法。这提供了代码的灵活性和扩展性。

向下转型(Downcasting):当使用父类引用指向子类对象时,可以将父类引用转换为子类引用,以便调用子类特有的方法。这需要使用强制类型转换,并在转换之前进行类型检查,以避免类型转换异常。

多态数组和参数:可以创建存储不同子类对象的父类数组,通过父类引用调用相同的方法。方法参数也可以使用父类类型,接受不同子类对象作为参数,实现代码的通用性和复用性。

Java类加载

类加载(Class Loading)是Java虚拟机(JVM)执行Java程序时的一个重要过程,它负责将字节码文件加载到内存中,并转换成可执行的Java类

类加载器(Class Loader):Java类加载器负责加载Java类文件,将类的字节码加载到JVM中。Java中有三种内置的类加载器:根类加载器(Bootstrap Class Loader)、扩展类加载器(Extension Class Loader)和应用程序类加载器(Application Class Loader)。类加载器之间形成了层次结构,每个类加载器都有特定的加载范围和加载顺序。

类加载过程

加载(Loading):查找并加载类的字节码文件。

验证(Verification):验证字节码文件的正确性和安全性。

准备(Preparation):为类的静态变量分配内存,并设置默认初始值。

解析(Resolution):将符号引用转换为直接引用,即将类、方法和字段的引用解析为内存地址。

初始化(Initialization):执行类的初始化代码,包括静态变量赋值和静态代码块的执行。

类加载器的双亲委派模型:Java类加载器采用了双亲委派模型。当一个类加载器收到类加载请求时,它会先委派给父类加载器进行加载,只有在父类加载器找不到类的情况下,才由当前类加载器自己加载。这种模型可以保证类的一致性和安全性,并避免重复加载。

类加载时机:类的加载是在运行时动态进行的,根据需要进行加载。

当创建类的实例时。

当访问类的静态变量或静态方法时。

当使用反射机制操作类时。

当类的父类未被加载时,需要先加载父类。

Java的原始数据类型和对象

Java的原始数据类型:

整数类型:

byte:8位有符号整数,取值范围为-128到127。

short:16位有符号整数,取值范围为-32,768到32,767。

int:32位有符号整数,取值范围为-2,147,483,648到2,147,483,647。

long:64位有符号整数,取值范围为-9,223,372,036,854,775,808到9,223,372,036,854,775,807。

浮点类型:

float:32位浮点数,取值范围为1.4E-45到3.4028235E+38,精度约为6-7位小数。

double:64位浮点数,取值范围为4.9E-324到1.7976931348623157E+308,精度约为15位小数。

字符类型:

char:16位Unicode字符,取值范围为'\u0000'到'\uffff'。

布尔类型:

boolean:表示逻辑值,只有两个取值:true和false。

Java对象类型,它们都是基于类的引用类型,包括以下几种:

类类型(Class Types):Java中的类是对象类型,例如自定义的类、预定义的类(如String和Integer等)等。

接口类型(Interface Types):Java中的接口也是对象类型,接口定义了一组方法的规范,可以通过实现接口来创建对象。

数组类型(Array Types):数组也是对象类型,可以存储多个相同类型的元素。

原始数据类型和对象类型有一些区别:

存储方式:原始数据类型直接存储值,而对象类型存储的是引用,指向实际对象的内存地址。

默认值:原始数据类型有默认值(如0、0.0、false等),而对象类型的默认值是null。

方法调用:原始数据类型直接操作值,而对象类型需要通过方法调用来操作对象。

封装:原始数据类型不具备封装的能力,而对象类型可以通过封装类(如Integer、Double等)来提供更多的功能和操作。

JVM

JVM组成部分

JVM架构:JVM由三个主要的子系统组成:

类加载器子系统(Class Loader Subsystem):负责加载字节码文件并将其转换为可执行的类。

运行时数据区(Runtime Data Area):包括方法区、堆、栈、程序计数器和本地方法栈等,用于存储程序执行过程中的数据和信息。

执行引擎(Execution Engine):负责执行字节码指令,将字节码转换为机器码并执行。

Java GC

  1. 对象的创建和分配内存:当您在Java程序中创建对象时,Java虚拟机(JVM)会负责为对象分配内存空间。这个过程通常发生在堆内存中。
  2. 引用计数:Java不使用引用计数来跟踪对象的引用,而是使用基于可达性的垃圾回收算法。这意味着只有可以通过一系列有向引用到达的对象才被认为是可访问的。
  3. 垃圾对象识别:垃圾回收器会定期检查堆中的对象,查找不再可达的对象。不再可达的对象是不会被程序再次使用的对象。
  4. 回收垃圾对象:一旦垃圾回收器确定某个对象不再可达,它会将该对象标记为垃圾,并释放该对象所占用的内存。这个过程称为垃圾收集。
  5. 垃圾收集算法:Java有不同的垃圾收集算法,如标记-清除、复制、标记-整理等。这些算法决定了如何标记和清理垃圾对象。不同的算法适用于不同的场景和堆内存配置。
  6. 垃圾回收器:Java提供了不同类型的垃圾回收器,如Serial收集器、Parallel收集器、G1收集器和CMS收集器等,以满足不同应用程序的需求。开发人员可以根据应用程序性能需求选择不同的垃圾回收器。
  7. 内存管理:垃圾回收机制允许Java程序员专注于业务逻辑,而不必担心手动释放内存。但是,开发人员仍然需要注意避免内存泄漏,这是因为即使有垃圾回收,如果对象仍然被引用而无法被回收,内存也可能被耗尽。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JAVA经典面试题
    • Java的基本数据类型有哪些
      • String,StringBuffer,StringBuilder
        • ==和equals方法的区别
          • 为什么重写了equlas之后要重写hashcode方法
            • 为啥有时会出现 4.0 - 3.6 = 0.40000001 这种现象
              • 集合相关问题
                • List,Set,Queue,Map四种集合的特点和区别
                • ArrayList和Vector的区别
                • 线程安全的集合有哪些?
                • 线程通信方式有哪些
              • Java8有哪些新特性
                • HashMap 在 jdk 1.7 和 1.8 的区别?
                  • JAVA函数简化的过程
                  • java 基础
                    • Java 特性
                      • JAVA static 关键字
                        • JAVA final关键字
                          • JAVA抽象类
                            • 抽象类的含义
                            • 抽象方法
                            • 接口声明方式
                            • 接口的实现和继承
                        • Java接口
                          • Java泛型
                            • 泛型的含义
                            • 泛型方法
                            • 泛型类
                            • 类型通配符
                          • Java异常处理
                            • 捕获异常
                            • throw和throws和finally
                            • try-with-resource语法糖
                            • 声明自定义异常
                          • ArrayList和LinkList的区别
                            • HashMap相关问题
                              • Java中的封装
                                • 封装的含义:
                              • java继承
                                • Java继承的含义:
                                • Java 为什么是单继承
                              • Java多态
                                • Java多态的含义:
                              • Java类加载
                                • Java的原始数据类型和对象
                                  • Java的原始数据类型:
                                  • Java对象类型,它们都是基于类的引用类型,包括以下几种:
                                  • 原始数据类型和对象类型有一些区别:
                                • JVM
                                  • JVM组成部分
                                • Java GC
                                相关产品与服务
                                容器服务
                                腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档