Java基础常见面试题总结

Java基础常见面试题总结

基础概念与常识

Java语言有哪些特点?

1.简单易学;

2.面向对象(封装,继承,多态);

3.平台无关性(JVM实现了平台无关性);

4.支持多线程(C++11之前 没有内置多线程机制,因此必须调用操作系统的多线程来进行多线程设计,而Java提供了多线程支持);

5.可靠性(具有异常处理和自动内存管理机制);

6.安全性(Java本身的设计就提供了多重防护机制如控制访问符、限制程序直接访问操作系统资源);

7.高效性(通过JIT编译器等技术的优化,Java运行的效率还不错)

8.支持网络编程并且很方便;

9.编译与解释并存;

“Write Once, Run Anywhere(一次编写,随处运行)”这句宣传口号,真心经典,流传了好多年!以至于,直到今天,依然有很多人觉得跨平台是 Java 语言最大的优势。实际上,跨平台已经不是 Java 最大的卖点了,各种 JDK 新特性也不是。目前市面上虚拟化技术已经非常成熟,比如你通过 Docker 就很容易实现跨平台了。在我看来,Java 强大的生态才是!----JavaGuide

JVM & JDK & JRE

JVM

Java虚拟机(JVM)是运行Java字节码的虚拟机。JVM有针对不同系统的特定实现,目的是使用相同的字节码,都会给出相同的结果。字节码和不同系统上的JVM是实现Java语言“一次编译,随处运行”的关键

JVM并不只有一种,只要满足JVM规范,每个人都可以开发自己的专属JVM;

JDK和JRE

JDK(Java Development Kit),他是功能齐全的Java SDK,是提供给开发者使用,能够创建和编译Java程序的开发套件。包含了JRE、编译Java源码的Javac以及一些其他工具(Javadoc、jdb、jconsole、javap等等)

JRE(Java Runtime Environment)是Java运行时的环境。他是已经运行已编译Java程序所需的内容的集合,主要包括Java虚拟机(JVM)、Java基础类库(Class Library)

总的来说,JRE是Java运行时的环境,仅包含Java应用程序的运行时环境和必要类库。JDK包含了JRE,同时还包括了其他工具,用于Java应用程序的开发和调试。

JDK 包含 JRE

什么是字节码,采用字节码的好处?

在Java中JVM可以理解的代码叫做字节码(拓展名为.class的文件),他不面向任何特定的处理器,只面向虚拟机。

Java通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行起来还是相对高效的。而且由于字节码并不针对特定的机器,因此Java程序无需重新编译即可在多种不同的操作系统的计算机上运行。

Java 程序从源代码到运行的过程如下图所示

Java程序转变为机器代码的过程

.class->机器码这一步,JVM类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式执行速度相对比较慢,而且有些方法和代码块经常需要被调用(热点代码),所以后面引进了JIT(Just In Time Commpliation)编译器,JIT属于运行时编译。当JIT编译器完成第一次编译后,会将字节码对应的机器码保存起来,下次可以直接使用。这也解释了为什么说 Java 是编译与解释共存的语言

Java程序转变为机器代码的过程

JDK、JRE、JVM、JIT 这四者的关系如下图所示。

JDK、JRE、JVM、JIT 这四者的关系

为什么说Java语言“编译与解释并存”?

高级编程语言按照程序执行方式分为两种:

  • 编译型:编译型语言会将源代码一次性翻译成可以被该平台执行的机器码。一般情况下,该类型开发效率较低,执行速度较快。(C、C++、GO、Rust)
  • 解释型:解释型语言会通过解释器一句一句将代码解释为机器代码后再执行。该类型开发较快,执行速度较慢。(Python、JavaScript、PHP)
为什么说Java语言“编译与解释并存”?

因为Java语言既有编译型语言特征也有解释型语言特征。因为Java程序要经过先编译,后解释两个步骤,由Java编写的程序先经过编译步骤,生成字节码,这种字节码再由Java解释器来解释运行。

AOT有什么优点?为什么不全部使用AOT?

JDK9新引入了一种新的编译模式AOT(Ahead of Time Compilation)。这种模式会在程序被执行前就将其编译成机器码,属于静态编译(C++,Rust,GO就是静态编译)。AOT避免了JIT预热等各方面开销,可以提高Java程序的启动速度,避免预热时间长。并且,AOT还能减少内存占用和增强Java程序的安全性(AOT编译后的代码不容易被反编译和修改),特别适用于云原生场景。

AOT的优势在于启动时间,内存占用和打包体积。JIT的优势在于具备更高的极限处理能力,可以降低请求的最大延迟。

既然 AOT 这么多优点,那为什么不全部使用这种编译方式呢?

两者各有优点,AOT更适合云原生场景,对微服务架构支持也比较友好,AOT无法支持Java一些动态特性,如反射、动态代理、动态加载等。然而很多的框架和库(Spring)都用到了这些特性。如果只使用AOT编译就没办法使用这些框架和库了。

Java和C++的区别?

Java和C++都是面向对象语言,都支持封装、继承和多态

  • Java不提供指针来直接访问内存,程序内存更加安全
  • Java的类是单继承的,C++支持多重继承;但是Java接口可以多继承
  • Java有自动内存管理垃圾回收机制(GC),不需要手动释放无用内存。
  • C++同时支持方法重载和操作符重载,Java只支持方法重载(操作符重载增加了复杂性,与Java最初的设计思想不符)

基本语法

标识符和关键字区别?

在编写程序时,需要大量为程序,方法,类,变量等取名字,于是简单来说标识符就是一个名字

有些标识符,Java已经赋予了其特殊的含义,只能用于特定的地方,这些特殊的标识符就是关键字

Java关键字:

这里列举截止到Java17所有的关键字及其功能:

  • abstract 声明一个类为抽象类或一个方法为抽象方法。
  • assert 断言表达式使用,用来对某些东西进行判断,如果判断失败会抛出错误,默认无效,需要添加启动参数才能开启,一般在测试环境中使用。
  • boolean 基本数据类型,布尔类型,只有true和false两种结果。
  • break 用于for/while循环语句中对循环提前终止,以及在switch语句中结束当前case语句块,防止程序继续向下执行。
  • byte 基本数据类型,字节类型,可以表示1字节的数据,取值范围为-128 到 127。
  • case 用于在switch语句中判断条件,匹配后执行相应的代码块。
  • catch 用于捕获异常,处理try块中抛出的异常。
  • char 基本数据类型,字符类型,表示单个字符(16位Unicode字符)表示范围为’\u0000’ (0) 到 ‘\uffff’ (65535)
  • class:声明一个类型,此外,使用类名.class用于获取指定类型的Class对象。
  • const 保留关键字,无实际用途。
  • continue:用于for/while循环语句中跳过当前循环的剩余代码,直接开启下一轮循环。
  • default 在switch语句中表示默认情况,同时,在接口中也可以为抽象方法指定默认实现(仅Java8之后可用)
  • do 用于do-while循环中定义循环体,循环开始前会执行一次循环。
  • double 基本数据类型,双精度浮点数,表示范围为4.9e-324 到 1.8e+308,精度为15位小数。
  • else 与if语句一起使用,表示条件不成立时执行的代码块,也可以与if组合为else if判断。
  • enum 声明枚举类型。
  • extends 声明类继承另一个类,在接口中可以继承多个其他接口,在泛型类中List<? extends Number>表示为类型参数设定上界。
  • final 声明一个变量的值不可修改、一个类不能被继承、一个方法不能被子类重写。
  • finally 声明一个代码块,在异常处理时,无论是否发生异常都会执行。
  • float 基本数据类型,单精度浮点数,表示范围为1.4e-45 到 3.4e+38,精度为6-7位小数。
  • for 创建for循环语句。
  • if 创建条件判断语句。
  • implements 表示一个类实现的接口,可以跟一个或多个接口。
  • import 用于导入包或类。
  • instanceof 用于判断对象是否为指定类型。
  • int 基本数据类型,整数类型,取值范围为-2^31 到 2^31-1。
  • interface:定义接口。
  • long 基本数据类型,长整数类型,取值范围为-2^63 到 2^63-1。
  • native 用于声明一个本地方法,方法的实现使用非Java语言编写,一般为C或C++编写,详情请看JVM篇JNI相关介绍。
  • new 用于实例化对象。
  • null 表示空对象引用。
  • package 用于声明包。
  • private 权限修饰符,表示私有的,仅在类的内部可见。
  • protected 权限修饰符,表示受保护的,仅当前类和子类可见。
  • public 权限修饰符,表示公共的,对所有类可见。
  • record 用于声明记录类型,Java14新增类型。
  • return 用于方法返回值。
  • sealed 用于声明密封类型,Java17新增类型。
  • non-sealed 用于声明类型为非密封类型,Java17新增类型。
  • short 用于声明短整型变量。
  • static 将类、方法、属性声明为静态的。
  • strictfp 用于确保浮点运算结果一致。
  • super 表示父类对象,在泛型类中List<? super Integer>表示为类型参数设定下界,在编写子类构造方法时,默认需要优先执行super()调用父类构造方法(无参情况下可省略)在Java22之后super不再要求强制优先调用。
  • switch 用于多重条件判断,在Java13之后可以使用switch表达式。
  • synchronized 用于线程同步加锁。
  • this 表示当前对象,使用this()可以调用当前类型的构造方法。
  • throw 抛出异常对象。
  • throws 在方法声明中指定可能被抛出的异常。
  • transient 声明变量在对象序列化时不参与序列化。
  • try 异常捕获的起始块。
  • void 表示没有返回值。
  • volatile 用于可能会被多个线程同时修改的变量,使其保证线程之间的可见性,禁止指令重排,但无法保证原子性,详情请见JUC篇视频教程。
  • while 用于创建while循环,当条件满足时执行代码块。
移位运算符

<< :左移运算符,向左移若干位,高位丢弃,低位补零。x << 1,相当于 x 乘以 2(不溢出的情况下)。

>> :带符号右移,向右移若干位,高位补符号位,低位丢弃。正数高位补 0,负数高位补 1。x >> 1,相当于 x 除以 2。

>>> :无符号右移,忽略符号位,空位都以 0 补齐。

continue、break和return区别?

1.continue: 跳出当前这一次的循环,继续下一次循环

2.break: 跳出整个循环体,继续执行循环下面的语句。

3.return: 跳出所在的方法,结束该方法运行。

基本数据类型

Java中有8种基本数据类型:

  • 6种数字型:
    • 4中整数型:byte、short、int、long
    • 2种浮点型:float、double
  • 1种字符类型:char
  • 1种布尔类型:boolean

对应的包装类型:Byte、Short、Integer、Long、Float、Double、Character、Boolean

基本数据类型和包装类型的区别?

基本类型 vs 包装类型

用途:除了定义一些常量和局部变量之外,我们在其他地方比如方法参数、对象属性中很少会使用基本类型来定义变量。并且,包装类型可用于泛型,而基本类型不可以。

存储方式:基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,我们知道几乎所有对象实例都存在于堆中。

占用空间:相比于包装类型(对象类型), 基本数据类型占用的空间往往非常小。

默认值:成员变量包装类型不赋值就是 null ,而基本类型有默认值且不是 null

比较方式:对于基本数据类型来说,== 比较的是值。对于包装数据类型来说,== 比较的是对象的内存地址。所有整型包装类对象之间值的比较,全部使用 equals() 方法。

⚠️ 注意:基本数据类型存放在栈中是一个常见的误区! 基本数据类型的存储位置取决于它们的作用域和声明方式。如果它们是局部变量,那么它们会存放在栈中;如果它们是成员变量,那么它们会存放在堆中。

包装类型的缓存机制?

Java基本数据类型的包装类型大部分都用到了缓存机制来提升性能。

Byte、Short、Integer、Long这四种包装类默认创建了数值[-128, 127]的相应类型的缓存数据,Character创建了数值在[0, 127]范围的缓存数据,Boolean直接返回True或者False。

所有整型包装类对象之间值的比较,全部使用 equals 方法比较

自动装箱与拆箱?原理?

什么是自动拆装箱?

  • 装箱:将基本类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;
Integer i = 10;  //装箱
int n = i;   //拆箱

装箱其实就是调用了包装类的valueOf()方法,拆箱调用了xxxValue()方法。

  • Integer i = 10 等价于 Integer i = Integer.valueOf(10)
  • int n = i 等价于 int n = i.intValue();

如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。

变量

成员变量与局部变量的区别

成员变量 vs 局部变量

  • 语法形式:从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被 public,private,static 等修饰符所修饰,而局部变量不能被访问控制修饰符及 static 所修饰;但是,成员变量和局部变量都能被 final 所修饰。

    存储方式:从变量在内存中的存储方式来看,如果成员变量是使用 static 修饰的,那么这个成员变量是属于类的,如果没有使用 static 修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。

    生存时间:从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。

    默认值:从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外:被 final 修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。

为什么成员变量有默认值?

1.变量存储的是内存地址对应的任意随机值,程序读取该值时会出现意外。

2.默认值有两种设置方式:手动和自动

方法

静态方法为什么不能调用非静态成员?

1.静态方法是属于类的,类加载时就会分配内存,可以通过类名直接访问。而非静态成员属于实例对象,只有在对象实例化后才存在,需要通过类的实例对象去调用。

2.在类的非静态成员不存在时静态方法就已经存在了,此时内存调用内存中不存在的非静态成员属于非法操作。

重载与重写

重载

发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同,个数不同,顺序不同,返回值和访问修饰符可以不同。

重写

重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。

1.方法名】参数列表必须相同,子类方法返回值类型应比父类方法返回值类型相同或更小,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。

2.如果父类方法访问修饰符为 private/final/static 则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。

3.构造方法无法被重写

面向对象基础

面向对象和面向过程的区别?

两者区别在于解决问题的方式不同:

  • 面向过程把解决问题的过程拆分成一个个方法,通过一个个方法解决问题。
  • 面向对象会先抽象出对象,然后对象执行方法来解决问题。

面向对象开发的程序一般更易维护、易服用、易扩展。

创建一个对象用什么运算符?对象实体与对象引用有何不同?

new运算符,new创建对象实例(对象实例在堆内存中),对象引用指向对象实例(对象应用存放在栈内存中)。

  • 一个对象引用可以指向0或1个对象
  • 一个对象可以有n个引用指向它

对象相等和引用相等区别

  • 对象的相等一般比较的是内存中存放的内容是否相等。
  • 引用相等一般指的是指向的内存地址是否相等

==equals

面向对象的三大特征

封装

封装是指把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。

继承

继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间 ,提高我们的开发效率

关于继承如下 3 点请记住:

  1. 子类拥有父类对象所有的属性和方法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有
  2. 子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
  3. 子类可以用自己的方式实现父类的方法。
多态

多态,顾名思义,表示一个对象具有多种的状态,具体表现为父类的引用指向子类的实例。

多态的特点:

  • 对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
  • 引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
  • 多态不能调用“只在子类存在但在父类不存在”的方法;
  • 如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法。

接口和抽象类有什么共同点和区别?

共同点:
  • 都不能被实例化
  • 都可以包含抽象方法
  • 都可以有默认实现的方法(Java8可以用default关键字在接口中定义默认方法)
区别:
  • 接口主要用于对类的行为进行约束,实现某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
  • 一个类只能继承一个类,但是可以实现多个接口。
  • 接口中的成员变量只能是public static final类型的,不能被修改且必须有初始值,而抽象类的成员变量默认default,可以在子类中被重新定义,也可被重新赋值。

深拷贝和浅拷贝区别?什么是引用拷贝?

浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。

深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

引用拷贝:引用拷贝就是两个不同的引用指向同一个对象。

浅拷贝、深拷贝、引用拷贝示意图

Object

Object类常见方法?

/**
 * native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
 */
public final native Class<?> getClass()
/**
 * native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
 */
public native int hashCode()
/**
 * 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
 */
public boolean equals(Object obj)
/**
 * native 方法,用于创建并返回当前对象的一份拷贝。
 */
protected native Object clone() throws CloneNotSupportedException
/**
 * 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
 */
public String toString()
/**
 * native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
 */
public final native void notify()
/**
 * native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
 */
public final native void notifyAll()
/**
 * native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
 */
public final native void wait(long timeout) throws InterruptedException
/**
 * 多了 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。。
 */
public final void wait(long timeout, int nanos) throws InterruptedException
/**
 * 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
 */
public final void wait() throws InterruptedException
/**
 * 实例被垃圾回收器回收的时候触发的操作
 */
protected void finalize() throws Throwable { }

== 和 equals()的区别

==对于基本类型和引用类型作用效果是不同的:

  • 对基本类型来说,==比较的是值。
  • 对引用数据来说,==比较的是对象的内存地址。

equals()两种使用方法:

  • 类没有重写equals()方法:通过equals()比较该类的两个对象是,等价于通过“==”比较
  • 类重写了equals()方法:比较两个对象中的属性是否相等。

hashCode()有什么用?

hashCode() 的作用是获取哈希码(int 整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。

为什么要有hashCode?

当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashCode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode 值作比较,如果没有相符的 hashCodeHashSet 会假设对象没有重复出现。但是如果发现有相同 hashCode 值的对象,这时会调用 equals() 方法来检查 hashCode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。

那为什么JDK还要同时提供这两个方法呢?

因为在一些容器里(如HashMap、HashSet)中,有了hashCode()之后,判断元素是否相等效率更高。

我们在前面也提到了添加元素进HashSet的过程,如果 HashSet 在对比的时候,同样的 hashCode 有多个对象,它会继续使用 equals() 来判断是否真的相同。也就是说 hashCode 帮助我们大大缩小了查找成本。

那为什么不只提供hashCode()方法呢?

因为两个对象的hashcode值相等并不代表两个对象就相等。

为什么两个对象hashCode相等他们也不一定是相等的?

因为 hashCode() 所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode )。

总结下来就是:

  • 如果两个对象的hashCode 值相等,那这两个对象不一定相等(哈希碰撞)。
  • 如果两个对象的hashCode 值相等并且equals()方法也返回 true,我们才认为这两个对象相等。
  • 如果两个对象的hashCode 值不相等,我们就可以直接认为这两个对象不相等。

为什么重写equals()时必须重写hashCode()方法?

因为两个相等的对象hashCode值必须相等。如果equals方法判断两个对象是否相等的,那么这两个对象的hashCode值也要相等。

如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。

总结

  • equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
  • 两个对象有相同的 hashCode 值,他们也不一定是相等的(哈希碰撞)。

String

String、StringBuffer、StringBuilder的区别?

可变性

String是不可变的

线程安全性

String中的对象是不可变的,也就可以理解为常量,线程安全。StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 并没有对方法进行加同步锁,所以是非线程安全的。

性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

对于三者使用的总结:

  • 操作少量的数据: 适用 String
  • 单线程操作字符串缓冲区下操作大量数据: 适用 StringBuilder
  • 多线程操作字符串缓冲区下操作大量数据: 适用 StringBuffer

字符串常量池作用

字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

异常

Exception和Error区别?

Java中,所有异常都有一个共同祖先,java.lang包的Throwable类。

其中两个重要的子类:

  • Exception:程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (不受检查异常,可以不处理)。
  • Error:Error属于程序无法处理的错误。

finally中的代码一定会执行吗?

不一定!某些情况下不会。

比如finally之前虚拟机被终止运行,finally中的代码就不会被执行。

在以下 2 种特殊情况下,finally 块的代码也不会被执行:

  1. 程序所在的线程死亡。
  2. 关闭 CPU。

泛型

什么是泛型?作用是什么?

Java泛型是JDK5引入的新特性,使用泛型参数可以增强代码的可读性以及稳定性。

泛型的使用方式有哪几种?

泛型一般有三种使用方式:泛型类、泛型接口、泛型方法

1.泛型类:

//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{

    private T key;

    public Generic(T key) {
        this.key = key;
    }

    public T getKey(){
        return key;
    }
}
//实例化
Generic<Integer> genericInteger = new Generic<Integer>(123456);

2.泛型接口:

public interface Generator<T> {
    public T method();
}
//实现,不指定类型
class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}
//实现,指定类型
class GeneratorImpl<T> implements Generator<String>{
    @Override
    public String method() {
        return "hello";
    }
}

3.泛型方法:

   public static < E > void printArray( E[] inputArray )
   {
         for ( E element : inputArray ){
            System.out.printf( "%s ", element );
         }
         System.out.println();
    }
//使用
// 创建不同类型数组:Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray  );
printArray( stringArray  );

反射

反射是框架的灵魂,它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射可以获取任意一个类的所有属性和方法,还可以调用这些方法和属性。

反射的优缺点?

反射可以让我们的代码更加灵活、为各种框架提供开箱即用的功能提供的便利。但是,反射让我们在运行时有了分析操作类的能力的同时,也增加了安全问题,比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。

注解

{
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
//使用
// 创建不同类型数组:Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { “Hello”, “World” };
printArray( intArray );
printArray( stringArray );


## 反射

反射是框架的灵魂,它赋予了我们在运行时分析类以及执行类中方法的能力。通过反射可以获取任意一个类的所有属性和方法,还可以调用这些方法和属性。

### 反射的优缺点?

反射可以让我们的代码更加灵活、为各种框架提供开箱即用的功能提供的便利。但是,反射让我们在运行时有了分析操作类的能力的同时,也增加了安全问题,比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的。

## 注解

`Annotation` (注解) 是 Java5 开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类、方法或者变量,提供某些信息供程序在编译或者运行时使用。
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值