- 请说说你对反射的了解
- 请你说说泛型、泛型擦除
- 请介绍一下访问修饰符
- 请你说说重载和重写的区别,构造方法能不能重写
- 说说你对面向对象的理解
- 请你说说Java的异常处理机制
- 介绍一下包装类的自动拆装箱与自动装箱
- 请你讲一下Java 8的新特性
- 请你说说hashCode()和equals()的区别,为什么重写equals()就要重写hashcod()
- 请你说说==和equal的区别
- String、StringBuffer、StringBuilder有什么区别
- 请你说说String类
- 说说static的用法
- 请你说一说final关键字
- 请你说一下接口和抽象类的区别
- 请你说一下Java的基本类型和引用类型。
- 请你说一下Java的特点和优点,为什么要选择Java?
请说说你对反射的了解
反射就是在程序运行期间动态的获取对象的属性和方法的功能叫做反射。它能够在程序运行期间,对于任意一个类,都能知道它所有的方法和属性,对于任意一个对象,都能知道他的属性和方法。
获取Class对象的三种方式:
- getClass();
- xx.class;
- Class.forName(“xxx”);
反射的优缺点:
- 优点:运行期间能够动态的获取类,提高代码的灵活性。
- 缺点:性能比直接的Java代码要慢很多。
应用场景:
- 使用JDBC时,如果要创建数据库的连接,则需要先通过反射机制加载数据库的驱动程序;
- 多数框架都支持注解/XML配置,从配置中解析出来的类是字符串,需要利用反射机制实例化;
- Spring AOP的实现方案,是在程序运行时创建目标对象的代理类,这必须由反射机制来实现。
请你说说泛型、泛型擦除
Java在1.5版本中引入了泛型,在没有泛型之前,每次从集合中读取对象都必须进行类型转换,而这么做带来的结果就是:如果有人不小心插入了类型错误的对象,那么在运行时转换处理阶段就会出错。在提出泛型之后,我们可以告诉编译器集合中接受哪些对象类型。编译器会自动的为你的插入进行转化,并在编译时告知是否插入了类型错误的对象。这使程序变得更加安全更加清楚
Java的泛型是伪泛型,我们写好的代码在编译时期会被去掉,这就是泛型擦除请介绍一下访问修饰符
Java除了提供的三个访问修饰符分别代表三个访问级别之外还有一个不加修饰符的访问级别,它们访问级别控制从小到大为: private->default->protected->public
他们访问级别分别如下:
- private:类中被private修饰的成员只能在当前类的内部被访问。根据这点,我们可以使用它来修饰成员变量,从而将成员变量隐藏在这个类的内部。
- default:如果类中的成员或者一个外部类不使用任何访问修饰符来进行修饰,那么他就是default级别的,default访问控制的类成员或者外部类可以被相同包下的其他类访问。
- protected:如果一个类成员被protected访问修饰符修饰,那么这个成员不但可以被同一个包下的其他类访问,还可以被其他包下的子类访问。一般来讲,如果一个方法被protected修饰,那么通常是希望它的子类来重写它。
- public:这是Java中最宽松的访问级别,如果类成员被这个修饰符修饰,那么无论访问类和被访问类在不在一个包下,有没有父子关系,这个类成员都可以被访问到。
对于局部变量而言,它的作用域就是他所在的方法,不可能被其它类所访问,所以不能使用访问修饰符来修饰。
对于外部类而言,它只有两种控制级别:public和默认,外部类之所以不能用protected和private修饰,是因为外部类没有处于任何类的内部,所以就没有它所在类的内部,所在类的子类两个范围,protected和private没有意义。
使用public声明的外部类可以被所有类引用;不使用访问修饰符创建的外部类只有同一个包内的类能引用。
请你说说重载和重写的区别,构造方法能不能重写
- 重载要求发生在同一个类中,多个方法之间方法名相同且参数列表不同(注意重载与方法的返回值以及访问修饰符无关)。
- 重写发生在父类子类中,若子类方法想要和父类方法构成重写关系,则它的方法名、参数列表必须与父类方法相同。另外,返回值要小于等于父类方法,抛出的异常要小于等于父类方法,访问修饰符则要大于等于父类方法。若父类方法的访问修饰符为private,则子类不能对其重写。
- 同一个类中有多个构造器,多个构造器的形参列表不同就被称为构造器重载,构造器重载让Java类包含了多个初始化逻辑,从而允许使用不同的构造器来初始化对象。
- 构造方法不能重写。因为构造方法需要和类保持同名,而重写的要求是子类方法要和父类方法保持同名。如果允许重写构造方法的话,那么子类中将会存在与类名不同的构造方法,这与构造方法的要求是矛盾的。
父类方法和子类方法之间也有可能发生重载,因为子类会获得父类的方法,如果子类中定义了一个与父类方法名字相同但参数列表不同的方法,就会形成子类方法和父类方法的重载。
说说你对面向对象的理解
面向对象的三大基本特征是:封装、继承、多态。
其中,封装是指将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,让外部程序通过该类提供的方法来实现对内部信息的操作和访问,这种做法有助于规范使用者的行为,让使用者只能通过事先预定的方法访问数据,提高了代码的可维护性;
- 继承是面向对象实现代码复用的重要手段,Java通过extends作为关键字实现类的继承,实现继承的类被称为子类,被继承的类称为父类(有的也被称为基类和超类)
- 多态的实现离不开继承,在设计程序时,我们可以将参数的类型定义为父类型。在调用程序时,则可以根据实际情况,传入该父类型的某个子类型的实例,这样就实现了多态。
- 对于父类型,可以有三种形式,即普通的类、抽象类、接口。
- 对于子类型,则要根据它自身的特征,重写父类的某些方法,或实现抽象类/接口的某些抽象方法。
通过封装可以实现这些优点:
- 隐藏类的成员变量和实现细节,不允许外部直接访问
- 规范使用者的行为,让使用者只能通过事先预定的方法访问数据,通过在这个方法中加入逻辑控制,限制使用者对成员变量的不合理访问
- 可进行数据检查,从而有利于保证对象信息的完整性;
- 便于修改,提高代码的可维护性
使用继承的优点包括:
- 代码共享,减少创建类的工作量,每个子类都拥有父类的方法和属性,提高了代码复用
- 提高代码的可扩展性,很多开源框架的扩展接口都是通过继承父类来完成的
但同时继承也有很多缺点:
- 继承是侵入性的。只要继承,就必须拥有父类的所有属性和方法;
- 降低代码的灵活性,子类必须拥有父类的属性和方法。
- 增强了耦合性。当父类的常量、变量和方法被修改时,需要考虑子类的修改,而且在缺乏规范的环境下,这种修改可能会导致大段的代码需要重构。
那么多态也有这些优点:
- 提高了代码的维护性与扩展性
java中实现多态需要三个条件:
- 需要有继承关系的存在。
- 需要有方法的重写。
- 需要有父类的引用指向子类对象。
请你说说Java的异常处理机制
Java的异常机制可以分成异常处理、抛出异常和异常跟踪栈问题三个部分。
处理异常的语句由try、catch、finally三部分组成。try块用于包裹业务代码,catch块用于捕获并处理某个类型的异常,finally块则用于回收资源。
如果业务代码发生异常,系统就会创建一个异常对象,并将这个异常对象提交给JVM,然后由JVM寻找可以处理这个异常的catch块,并将异常对象交给这个catch块处理。
如果JVM没有找到可以处理异常的catch代码块,那么运行环境会终止,Java程序也会退出。若业务代码打开了某项资源,则可以在finally块中关闭这项资源,因为无论是否发生异常,finally块一定会执行(一般情况下)。
当程序出现错误时,系统会自动抛出异常。除此以外,Java也允许程序主动抛出异常。当业务代码中,判断某项错误的条件成立时,可以使用throw关键字向外抛出异常。在这种情况下,如果当前方法不知道该如何处理这个异常,可以在方法签名上通过throws关键字声明抛出异常,则该异常将交给JVM处理。
程序运行时,经常会发生一系列方法调用,从而形成方法调用栈。异常机制会导致异常在这些方法之间传播,而异常传播的顺序与方法的调用相反。异常从发生异常的方法向外传播,首先传给该方法的调用者,再传给上层调用者,以此类推。最终会传到main方法,若依然没有得到处理,则JVM会终止程序,并打印异常跟踪栈的信息。
throw、throws区别
throws:
- 需要有父类的引用指向子类对象。
只能在方法签名中使用
- 可以声明抛出多个异常,多个一场之间用逗号隔开
- 表示当前方法不知道如何处理这个异常,这个异常由该方法的调用者处理(如果main方法也不知该怎么处理异常,这个异常就会交给JVM处理,JVM处理异常的方式是,打印异常跟踪栈信息并终止程序运行,这也就是为什么程序遇到异常会自动结束的的原因)
- throws表示出现异常的一种可能性,并不一定会发生这些异常
throw:
- 表示方法内抛出某种异常对象,throw语句可以单独使用。
- throw语句抛出的是一个异常实例,不是一个异常类,而且每次只能抛出一个异常实例
- 执行throw一定抛出了某种异常
关于finally的问题 当Java程序执行try块、catch块时遇到了return或throw语句,这两个语句都会导致该方法立即结束,但是系统执行这两个语句并不会结束该方法,而是去寻找该异常处理流程中是否包含finally块
- 如果没有finally块,程序立即执行return或throw语句,方法终止;
- 如果有finally块,系统立即开始执行finally块。只有当finally块执行完成后,系统才会再次跳回来执行try块、catch块里的return或throw语句;
- 如果finally块里也使用了return或throw等语句,finally块会终止方法,系统将不会跳回去执行try块、catch块里的任何代码。这将会导致try块、catch块中的return、throw语句失效,所以,我们应该尽量避免在finally块中使用return或throw。
finally代码块不执行的几种情况:
- 如果当一个线程在执行 try 语句块或者catch语句块时被打断interrupted或者被终止killed,与其相对应的 finally 语句块可能不会执行。
如果在try块或catch块中使用
System.exit(1);来退出虚拟机,则finally块将失去执行的机会。介绍一下包装类的自动拆装箱与自动装箱
自动装箱、自动拆箱是JDK1.5提供的功能。
自动装箱是指把一个基本类型的数据直接赋值给对应的包装类型;自动拆箱是指把一个包装类型的对象直接赋值给对应的基本类型;
通过自动装箱、自动拆箱功能,可以大大简化基本类型变量和包装类对象之间的转换过程。比如,某个方法的参数类型为包装类型,调用时我们所持有的数据却是基本类型的值,则可以不做任何特殊的处理,直接将这个基本类型的值传入给方法。
Java是一门面向对象的编程语言,其设计理念是“一切皆对象”。但8种基本数据类型却不具备对象的特性。Java之所以提供8种基本数据类型,主要是为了照顾程序员的传统习惯。这8种基本数据类型的确带来了一定的方便性,但在某些时候也会受到一些制约。比如,所有的引用类型的变量都继承于Object类,都可以当做Object类型的变量使用,但基本数据类型却不可以。如果某个方法需要Object类型的参数,但实际传入的值却是数字的话,就需要做特殊的处理了。有了包装类,这种问题就可以得以简化。
不同包装类不能直接进行比较,这包括:不能用==进行直接比较,因为它们是不同的数据类型;
- 不能转为字符串进行比较,因为转为字符串后,浮点值带小数点,整数值不带,这样它们永远都不相等;
- 不能使用compareTo方法进行比较,虽然它们都有compareTo方法,但该方法只能对相同类型进行比较。
整数、浮点类型的包装类,都继承于Number类型,而Number类型分别定义了将数字转换为byte、short、int、long、float、double的方法。所以,可以将Integer、Double先转为转换为相同的基本数据类型(如double),然后使用==进行比较。
请你讲一下Java 8的新特性
- Lambda表达式:使用 Lambda 表达式,可以更简洁地表示单方法接口(称为功能接口)的实例。
- 方法引用:方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与Lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
- Java8对接口进行了改进:允许在接口中定义默认方法,默认方法必须使用default修饰。
- Stream API:新添加的Stream API(java.util.stream)支持对元素流进行函数式操作。Stream API 集成在 Collections API 中,可以对集合进行批量操作,例如顺序或并行的 map-reduce 转换。
-
请你说说hashCode()和equals()的区别,为什么重写equals()就要重写hashcod()
hashCode()方法的主要用途是获取哈希码,equals()主要用来比较两个对象是否相等。
如果两个对象相等,它们必须有相同的哈希码;
- 如果两个对象的哈希码相同,他们却不一定相等。
也就是说
- equals()比较两个对象相等时hashCode()一定相等
- hashCode()相等的两个对象equqls()不一定相等。
Object类提供的equals()方法默认是用==来进行比较的,也就是说只有两个对象是同一个对象时,才能返回相等的结果。如果想要判断两个对象是否相等,那么必须重写他们的equal和hashcode方法
请你说说==和equal的区别
==和EQUALS都是JAVA中判断两个变量是否相等的方式
如果判断的是两个基本类型的变量,并且两者都是数值类型(不一定要求数据类型完全相同),只要两个变量的值相等就会返回TRUE。
对于两个引用变量只有他们指向同一个引用时,==才会返回TRUE。==不能用于比较类型上没有父子关系的两个对象。
Object类提供的equals()方法默认是用==来进行比较的,也就是说只有两个对象是同一个对象时,才能返回相等的结果。如果想要判断两个对象是否相等,那么必须重写他们的equal和hashcode方法
String、StringBuffer、StringBuilder有什么区别
String是一个不可变类,也就是说,一个String对象创建之后,直到这个对象销毁为止,对象中的字符序列都不能被改变。
StringBuffer对象则代表一个字符序列可变的字符串,当一个StringBuffer对象被创建之后,我们可以通过StringBuffer提供的append()、insert()、reverse()、setCharAt()、setLength()、等方法来改变这个字符串对象的字符序列。当通过StringBuffer得到期待中字符序列的字符串时,就可以通过toString()方法将其转换为String对象。
StringBuilder类是JDK1.5中新增的类,和StringBuffer类相比,它们有共同的父类AbstractStringBuilder,二者无论是构造器还是方法都基本相同,不同的一点是,StringBuilder没有考虑线程安全问题,也正因如此,StringBuilder比StringBuffer性能略高。因此,如果是在单线程下操作大量数据,应优先使用StringBuilder类;如果是在多线程下操作大量数据,应优先使用StringBuffer类。
请你说说String类
String类是Java最常用的API,它包含了大量处理字符串的方法,比较常用的有:
- char charAt(int index):返回指定索引处的字符;
- String substring(int beginIndex, int endIndex):从此字符串中截取出一部分子字符串;
- String[] split(String regex):以指定的规则将此字符串分割成数组;
- String trim():删除字符串前导和后置的空格;
- int indexOf(String str):返回子串在此字符串首次出现的索引;
- int lastIndexOf(String str):返回子串在此字符串最后出现的索引;
- boolean startsWith(String prefix):判断此字符串是否以指定的前缀开头;
- boolean endsWith(String suffix):判断此字符串是否以指定的后缀结尾;
- String toUpperCase():将此字符串中所有的字符大写;
- String toLowerCase():将此字符串中所有的字符小写;
- String replaceFirst(String regex, String replacement):用指定字符串替换第一个匹配的子串;
- String replaceAll(String regex, String replacement):用指定字符串替换所有的匹配的子串。
String类是由final修饰的,所以他不能被继承。
创建字符串有两种方式:
- 一种是使用字符串直接量
- 另一种是使用new关键字
当使用字符串直接量的方式来创建字符串时,JVM会使用常量池来管理这个字符串,当使用new关键字来创建字符串时,JVM会先使用常量池来管理字符串直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象会被保存在堆内存中。对比来说,采用new的方式会多创建出一个对象来,占用了更多的内存 ,所以建议采用直接量的方式来创建字符串。
说说static的用法
Java类中包含了成员变量、方法、构造器、初始化块和内部类(包括接口、枚举)5种成员,static关键字可以修饰除了构造器外的其他4种成员。
static关键字修饰的成员被称为类成员。类成员属于整个类,不属于单个对象。
static修饰的部分会和类同时被加载。被static修饰的成员先于对象存在,因此,当一个类加载完毕,即使没有创建对象也可以去访问被static修饰的部分。
静态方法中没有this关键词,因为静态方法是和类同时被加载的,而this是随着对象的创建存在的。静态比对象优先存在。也就是说,静态可以访问静态,但静态不能访问非静态而非静态可以访问静态。
请你说一说final关键字
final关键字可以用来标志其修饰的类,方法和变量不可变。
- 当final修饰类时,该类不能被继承,例如java.lang.Math类就是一个final类,它不能被继承。
- final修饰的方法不能被重写,如果出于某些原因你不希望子类重写父类的某个方法,就可以用final关键字修饰这个方法。
- 当final用来修饰变量时,代表该变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值。
- final既可以修饰成员变量(包括类变量和实例变量),也可以修饰局部变量、形参。
- 对于final修饰的成员变量而言,一旦有了初始值就不能被重新赋值,如果既没有在定义成员变量时指定初始值,也没有在初始化块,构造器中为成员变量指定初始值,那么这个成员变量的值将一直是系统默认分配的0、’\u0000’、false或者是null,那么这个成员变量就失去了存在的意义
实例变量与静态变量的区别:https://blog.csdn.net/weixin_45972870/article/details/123012130
请你说一下接口和抽象类的区别
首先先说一下他们的相同点吧
- 接口和抽象类都可以有抽象方法,如果他们的子类是普通类,那么必须实现他们的抽象方法
- 接口和抽象类都不能被实例化,一般位于继承树的顶端
在用法上他们有一些区别
- 接口里只能包含抽象方法和默认方法,1.8之前不能有普通方法,1.8之后普通方法必须为default修饰。抽象类则可以包含普通方法。
- 接口里的变量默认是public static final的,是静态常量,不能有成员变量;抽象类里既可以定义普通成员变量,也可以定义静态常量
- 接口里不包含构造器;抽象类可以包含构造器,但抽象类的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作
- 一个类最多只能有一个父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足
在二者的设计目的上
- 接口作为系统与外界交互的窗口,体现了一种规范。对于接口的实现者来说,接口规定了实现者必须向外提供哪些服务;对于接口的调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务。
抽象类则不一样,抽象类作为系统中多个子类的共同父类,它体现的是一种模板式设计。抽象类作为多个子类的父类,它可以被当作系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能,但这个产品依然不能当作最终产品,必须要有更进一步的完善。
请你说一下Java的基本类型和引用类型。
Java的数据类型主要分为两大类:基本数据类型和引用数据类型。
- 基本数据类型有8个,分为四个小类。分别是:整数类型(int/byte/short/long)、浮点数类型(flout/double)、字符类型(char)以及布尔类型(boolean)。其中int是最常用的整数类型,double是最常用的浮点数类型,除了boolean类型外,其他7个数据类型都能够看作是数字类型,能够相互转换。
引用数据类型主要有数组、类、接口以及null等。所谓引用类型其实就是一个对象的引用。
请你说一下Java的特点和优点,为什么要选择Java?
Java是一门面向对象的编程语言,他在继承C++优点的同时,去除了C++当中比较难以理解的多继承和指针等问题,所以他相对于C++来说更加容易理解和使用。
- Java有跨平台性,可以做到“一次编译,到处运行”。我们只需要在不同操作系统中安装不同平台对应的Java虚拟机JVM,就可以将编译器编译生成的.class文件转换成不同操作系统所能执行的二进制文件。
- Java提供了丰富的类库,通过这些类库程序员可以很大程度上减轻了开发负担,节省整个项目完成时间。除此之外Java还有自己的垃圾回收器,开发人员不需要过多关注内存情况,进一步节省开发人员的时间。
- Java具有安全性和健壮性。它提供了用于防治恶意攻击的安全检查机制:数组边界检测和ByteCode校验等。它的健壮性体现在她的强类型机制、垃圾回收器、异常处理器等。
