目录
Java基础
重载和重写
- 这道题考察关于方法覆写的规则 D
- 首先我们直到多态的前提就是发生在具有继承关系的类之间和方法重写
- 我们要明确重载和重写的区别
重写与重载
构造器(constructor)是否可被重写(override)
- 构造器不能被继承,因此不能被重写,但可以被重载。
重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
- 方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,而后者实现的是运行时的多态性。
- 重载:发生在同一个类中,方法名相同参数列表不同(参数类型不同、个数不同、顺序不同),与方法返回值和访问修饰符无关,即重载的方法不能根据返回类型进行区分
- 重写:发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
关于重写的访问修饰符的规则
- 其子类的访问修饰符的权限要大于等于父类的访问修饰符权限,private除外,public>protect>default(什么都不写)
为什么父类的方法修饰符是private,就不是重写呢?
- 因为我们知道重写在发生在继承关系中,也就是在子类中重写,如果父类方法是private,那么对于子类来说这个方法是不可见的,在子类中根本就不知道这个方法的存在,那么何来重写之说(重写是为了表达子类和父类行为的不同),那用private权限修饰,还重写就没有任何意义
为什么子类重写的返回值要小于于等于父类?
- 在一般情况下,重写的方法的返回值必须是一样的,不然就会报错,但是存在一种特殊情况,就是在继承关系中,在父类中可以至少是向上转型的类,就比如你的子类是学生,父类是人,这种逻辑是可以说通的,因为学生一定是人,但是你的子类是人,父类是学生,这就是说不通, 因为人不一定是学生啊
关于重写和重载对于static的规定
- 重载对于static没有任何的要求
- 重写不能发生在static方法,为什么呢?首先我们要明确为什么要重写,重写是为了运行多态(通过同一个引用,同一个方法,指向不同的对象的时候,表现出不同的表现),其多态的本质就是在于调用不同的对象,但是static是表示属于类的方法,所以重写不应该去用于static方法
面对对象编程
- 这道题很简单不过何为面对对象编程确很有玄机
- 面对对象编程的特性:抽象,封装,继承,多态
面向对象和面向过程的区别
面向过程:
- 优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。
- 缺点:没有面向对象易维护、易复用、易扩展
- 举例子:比如洗衣服,面向过程的步骤应该是:将洗衣服这件事拆分为一个个过程:我打开洗衣机+我放衣服+清洗+烘干
面向对象:
何为面对对象,类是对万物的一种高度抽象,不同是事务之间存在着不同的关系,应用在类上。如一个类与外界的封装关系,父类和子类之间的继承关系,一个类和多个类的多态关系,在Java开发中,万物皆对象,将世界的一切行为都概括为对象+行为+对象,而面对对象的三大特征就是封装+多态+继承
- 优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护
- 缺点:性能比面向过程低
- 面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。
- 面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们使用的就是面向对象了。
- 如果是面对的对象的洗衣服,会将过程拆分为不同对象的行为 :我:打开洗衣机,放入衣服 洗衣机:洗衣服,烘干,这样更易于复用,扩展,维护,比如不是我衣服,变成别人,换个对象就行,不需要换洗衣机
面对对象的三大特性
封装
封装是为隐藏了类的内部如何实现,实现了程序的保护性和易用性,比如对这个属性进行封装,那么类的外部想要使用这个属性,只能通过内部提供的方法进行使用(getter,setter),那么外部的使用就必须遵循其规定的规则,而易用性,我们只能提供特定的方法来进行使用,就不需要关心内部如何实现
继承
万物皆为类,有些类肯定会存在相同的属性和方法,比如狮子,大象,这些都是动物,其动物有的它们也有,这种关系是满足is a关系,那我们就可以这些子类去继承父类的属性和方法,子类就是对父类的延升,通过使用继承可以提高代码复用性
子类拥有父类非 private 的属性和方法,对于private的属性和方法,在子类中是不能直接使用的,必须通过父类提供的方法进行使用
子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法(重写)。
Java只支持单继承,但是可以进行多层继承
多态
首先多态的前提是两个类之间存在着继承关系,存在着方法的重写和向上转型,在引用的时候,用父类的引用去指向子类的对象,其意义是想通过顶层父类来代指所有的子类,通过同一个的引用,同一个方法名称,根据对象的不同表现出不同的行为,增加了代码的可移植性
- 所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。
- 方法的重载实现的是编译阶段的多态也称为前绑定,方法的重写是为了实现运行阶段的多态也称为后绑定
- 在Java中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)。
- 多态父类引用无法调用子类的特有功能,需要向下转型
向上转型
- 语法 父类名称 父类引用=new 子类对象();
- 前提 向上转型必须发生在有继承关系的类之间,可以不是直接子类,可以是子孙类,反正满足 is a的关系就行
- 意义通过最顶层的父类引用,指代所有的子类对象
String相关
- 而String类 的方法都是通过创建新的对象也就是new String()的方式返回的
String的特性
- 不变性,String 是只读字符串,对它进行任何操作,其实都是创建一个新的对象,再把引用指向该对象。不变模式的主要作用在于当一个对象需要被多线程共享并频繁访问时,可以保证数据的一致性。
- final,不可继承,使用 final 来定义 String 类,表示 String 类不能被继承,提高了系统的安全性
- 常量池优化,String 对象创建之后,会在字符串常量池中进行缓存,如果下次创建同样的对象时,会直接返回缓存的引用。
字符常量和字符串常量的区别
- 形式上:字符常量用''来引起的一个字符,字符串常量用""括起来的多个字符串
- 含义上: 字符常量相当于一个整形值(ASCII值),可以参加表达式运算 字符串常量代表一个地址值(该字符串在内存中存放位置)
- 内存上:一个字符常量占两个字节,字符串常量占多个字节(至少存在一个结束标志)
String是基本类型吗?
- 不是,基本类型8个,int,long,short,doubel,float,byte,boolean,剩下的都是引用类型,包括对象,数组,枚举
- String 底层就是一个 char 类型的数组,只是使用的时候开发者不需要直接操作底层数组,用更加简便的方式即可完成对字符串的使用。
String的四种产生方式
- 直接赋值法(语法糖),String str="abc";
- 实例化法,String str=new String("abc");
- 通过字符数组产生 char []date=new char[]{1,2,3,4}; String s=new String(date);
- 通过String的静态方法valueOf(任意数据类型)——>转换为字符串
String str="i"与 String str=new String(“i”)一样吗?
- 不一样,因为内存的分配方式不一样。String str="i"的方式,java 虚拟机会将其分配到常量池中;而 String str=new String(“i”) 则会被分到堆内存中。
String s = new String(“xyz”);创建了几个字符串对象
- 两个对象,一个是常量池区的"xyz"(前提是常量池中没有这个字符串常量),一个是用new创建在堆上的对象。
字符串的常量池
- 字符串的常量池在存储在堆区中的,专门用来存储字符串常量,可以提高内存的使用率,避免开辟多块空间存储相同的字符串,在创建字符串时 JVM 会首先检查字符串常量池,如果该字符串已经存在池中,则返回它的引用,如果不存在,则实例化一个字符串放到池中,并返回其引用。
- 什么时候会入池呢?在我们用直接赋值法的时候,会将字符串入池,并且返回其在池中的地址,如果池中已经存在了这个字符串常量,那么会直接去引用池中的字符串对象
- 当调用我们构造方法来产生一个String对象,会将出现的字符串常量放入池中,但是我们的对象不会指向其池中的地址,而是指向对应的字符串对象的地址,因为我们的程序是从右往左执行,出现了"hello"的常量字符串,所以入池了
- 手动入池的方法,intern()方法,在常量池中已经有了这个字符串常量对象那么就不会入池,但是会返回池中对应字符串常量的地址,如果不存在这个对象,就会将这个字符串对象入池,然后返回对应的池内地址
关于String的不可变性
/** The value is used for character storage. */ private final char value[];
- 因为字符串的底层数据结构是一个final形式的字符数组,所以是不可变的
- 我们对于字符的一些操作,都是改变的是字符串的引用,而不是改变字符串的内容
String不可变但不代表引用不可以变 String str = "Hello"; str = str + " World"; System.out.println("str=" + str); 结果: str=Hello World
解析:实际上,原来String的内容是不变的,只是str由原来指向"Hello"的内存地址转为指向"Hello World"的内存地址而已,也就是说多开辟了一块内存区域给"Hello World"字符串。
数组有没有 length()方法?String 有没有 length()方法
- 数组没有 length()方法 ,有 length 的属性。String 有 length()方法
在使用 HashMap 的时候,用 String 做 key 有什么好处?
- HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。
String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的
可变性
- String类中使用字符数组保存字符串,private final char value[],所以string对象是不可变的。StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,char[] value,这两种对象都是可变的。
线程安全性
- String中的对象是不可变的,也就可以理解为常量,线程安全,StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
性能
- 每次对String 类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String 对象。StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StirngBuilder 相比使用StringBuffer 仅能获得10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结
- 如果要操作少量的数据用 String
- 单线程操作字符串缓冲区 下操作大量数据 =StringBuilder
- 多线程操作字符串缓冲区 下操作大量数据 =StringBuffer
String常用的函数
- 比较的:equals(),compare()
- 截取的 substr(),charAt()
- 转换方面的 toCharArrary(),构造方法,getBytes()
- 查找方面的 contains(),index0f(),lastIndexOf(),startWith(),endWith()
- 替换操作:replaceAll(),replaceFirst()
- 拆分操作 split()
- 其他操作 获得长度 length(),入池intern(),toUpperCase()转大写,contact()连接并且不入池,isEmpty()是否为空,trim()去除两端的空格
数据类型
隐式(自动)类型转换和显示(强制)类型转换
隐式(自动)类型转换:从存储范围小的类型到存储范围大的类型。byte→short(char)→int→long→float→double
显示(强制)类型转换:从存储范围大的类型到存储范围小的类型。double→float→long→int→short(char)→byte。该类类型转换很可能存在精度的损失。
Math.round(11.5) 等于多少?Math.round(-11.5)等于多少
Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5 然后进行下取整。
short s1 = 1; s1 = s1 + 1;有错吗?
对于 short s1 = 1; s1 = s1 + 1;由于 1 是 int 类型,因此 s1+1 运算结果也是 int型,需要强制转换类型才能赋值给 short 型。而 short s1 = 1; s1 += 1;可以正确编译,因为 s1+= 1;相当于 s1 = (short(s1 + 1);其中有隐含的强制类型转换。
存储位置
方法区的存储什么数据
- 类中的所有方法都在这个方法区存储
- 常量和静态变量
访问修饰符
四大修饰符
private<default<protected<public
- private 表示私有的,被private修饰的属性和方法,只在当前类内部可见,出来类的范围,对外部就完全隐蔽了,外部不知道其的存在(对应封装思想)
- default 不写任何修饰权限的修饰符,表示的就是default,包访问权限,当前包的内部可见,不包括子包(同级目录可见)
- protected 继承,不同包的有继承关系的类之间可见,也包括同一个包下没有继承关系的类
- public 公开的,被public修饰的,当前整个程序(项目)都是可以使用的
关键字
final关键词
final用法的使用
- final修饰类的时候,表示这个类不能再被继承
- 被final修饰的变量是基本类型,变量的数值不能改变;被修饰的变量是引用类型,变量便不能在引用其他对象,但是变量所引用的对象本身是可以改变的。
- final修饰的方法,表示方法不能被重写
怎样声明一个类不会被继承,什么场景下会用
如果一个类被final修饰,此类不可以有子类,不能被其它类继承,如果一个类中的所有方法都没有重写的需要,当前类没有子类也罢,就可以使用final修饰类。
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final呢?它内部原理是什么呢?先看这段代码:public class Outer { void outMethod(){ final int a =10; class Inner { void innerMethod(){ System.out.println(a); } } } }
首先我们的Inner和Outer各自会生成对应的lclass对象,那么就是内部类不会随着方法执行完毕就会消耗,那么当外部方法执行完毕,其局部变量也会销毁。但是内部类的对象还可能存在(只有没有人引用才会死亡)。这就出现了一个矛盾,那么内部类对象就访问了一个不存在的变量,所以我们的解决方法就是将局部变量复制一份给内部类作为成员变量,这样当方法死亡的时候,内部类依然可以访问,当时这样我们必须要保证其两个变量是一样的,可能我们内部类去做修改变量的操作,为了防止这种情况的出现,所以在内部类使用的局部变量必须是final的
static关键字
- 这个代码运行起来,并不会发生错误,为什么呢,因为hello这个方法是静态的,所以是属于Test这个类的,test.hello,发挥作用的还是Test.hello(),所以并不会发生空指针异常
- 在向下转型的时候会用来判断是否能进行向下转型
static的意义
- 首先static的语义,用其修饰的方法或者属性都是属于类的,跟对象是没有关系的,以致于即使没有创建对象,也能使用属性和调用方法
- static关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候(在类加载的时候执行一次),会按照static块的顺序来执行每个static块,并且只会执行一次。
- 为什么说static块可以用来优化程序性能,是因为它的特性:只会在类加载的时候执行一次。因此,很多时候会将一些只需要进行一次的初始化操作都放在static代码块中进行。
static的特性
- static修饰的属性在JVM的方法区存储,是所有对象共享的,如果其中一个对象修改,那么所有该类的对象的属性都会修改
- 通过类名称来调用,也可以通过对象名来调用,但是这样是不规范的
- 在该类被第一次加载的时候,就会去加载被static修饰的部分,而且只在类第一次使用时加载并进行初始化
- static变量值在类加载的时候分配空间,以后创建类对象的时候不会重新分配。赋值的话,是可以任意赋值的
- 被static修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有创建对象,也可以去访问
关于static的注意项
- 能不能在方法里面定义static的属性?不能,因为方法内的属性是局部变量,存储在堆区,static修饰的是静态变量,存储在方法区,这就矛盾了
- 在静态方法中,只能调用static的方法和属性,不能调用成员方法和成员属性,在成员方法中,可以调用static方法和属性,也可以调用成员方法和成员属性(这里指的是直接调用)
- this()和super()都指的是对象,所以,均不可以在static环境中使用。
为什么主方法是static的
- 首先main方法是一个程序的入口,如果main方法是一个成员方法,那么必须就得到对象才能去执行,没进入不了程序,怎么获得对象?所以肯定是一个静态方法
super和this关键字
- 类方法不能访问成员方法,说的是不能直接访问,你要是在类方法里去创建一个对象,肯定是可以调用的
this的使用
- this可以引用当前类的成员属性,经典方法就是破除局部变量的就近原则
- this()可以用来直接调用本类的构造方法,必须放在第一行
- this可以调用本类的成员方法
- this可以表示当前对象的引用
- 关于this和super来调用父类的构造函数,super要放在第一行,this也必须放在第一行,所以这两个不能同时显式出现
super的使用
- super可以用来引用直接父类的成员属性。和this类似,但是this会先从当前类找,找不到,才从父类找(一层一层找,直到找到),super会直接从直接父类找,一层一层找
- super可以用来调用直接父类构造函数。(注意:super()一定要放在构造函数的第一行,否则编译不通过)
- super可以用来调用直接父类成员方法。如果是父类有,但是父类的访问修饰符是private,那么也会报异常
- super不可以表示当前对象的引用
- 子类的构造方法的第一行会默认会有super(),表示调用父类的无参构造方法,如果父类没有无参构造,那么就需要我们在子类的构造方法自己写super(有参构造),表示调用父类的有参构造,不然就会报错
- this()和super()都指的是对象,所以,均不可以在static环境中使用。包括:static变量,static方法,static语句块。
关键字switch支持什么类型
- switch的值只能是int | char | String | enum
- short,byte也可以,因为short,byte是小于4字节的,在内存中会当作int类型存储
流程控制语句
流程控制语句
- break ,continue ,return 的区别及作用
- break 跳出总上一层循环,不再执行循环(结束当前的循环体)
- continue 跳出本次循环,继续执行下次循环(结束正在执行的循环 进入下一个循环条件)
- return 程序返回,不再执行下面的代码(结束当前的方法 直接返回)
==和equals的区别
对于==
- 如果比较的是基本类型,那么比较的是两个变量的属性,如果比较的是引用数据类型,那么比较的是两个引入数据的地址
对于equals
- equals是Object的方法,可以在各自的子类重写
- 情况1:类没有覆盖 equals() 方法。则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
- 情况2:类覆盖了 equals() 方法。一般,我们都覆盖 equals() 方法来两个对象的内容相等;若它们的内容相等,则返回 true (即,认为这两个对象相等)。
hashCode 与 equals (重要)
HashSet如何检查重复两个对象的 hashCode() 相同,则 equals() 也一定为 true,对吗?
hashCode和equals方法的关系
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”
- hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode()函数。
为什么要有 hashCode
- 我们以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode:
- 当你把对象加入 HashSet 时,HashSet 会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet 就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度。
hashCode()与equals()的相关规定
- 如果两个对象相等,则hashcode一定也是相同的
- 两个对象相等,对两个对象分别调用equals方法都返回true
- 两个对象有相同的hashcode值,它们也不一定是相等的
equals 方法被覆盖过,则 hashCode 方法也必须被覆盖
- hashCode在具有哈希机制的集合中起着非常关键的作用,比如HashMap、HashSet等。以HashSet为例,HashSet的特点是存储元素时无序且唯一,在向HashSet中添加对象时,首相会计算对象的HashCode值来确定对象的存储位置,如果该位置没有其他对象,直接将该对象添加到该位置;如果该存储位置有存储其他对象(新添加的对象和该存储位置的对象的HashCode值相同),调用equals方法判断两个对象是否相同,如果相同,则添加对象失败,如果不相同,则会将该对象重新散列到其他位置。所以重写equals方法后,hashCode方法不重写的话,会导致所有对象的HashCode值都不相同,都能添加成功,那么HashSet中会出现很多重复元素,HashMap也是同理(因为HashSet的底层就是通过HashMap实现的),会出现大量相同的Key(HashMap中的key是唯一的,但不同的key可以对应相同的value)。所以重写equals方法后,hashCode方法也必须重写。同时因为两个对象的hashCode值不同,则它们一定不相等,所以先计算对象的hashCode值可以在一定程度上判断两个对象是否相等,提高了集合的效率。总结一下,一共两点:第一,在HashSet等集合中,不重写hashCode方法会导致其功能出现问题;第二,可以提高集合效率。
&&与&的区别
&和&&的区别
- &运算符有两种用法:(1)按位与;(2)逻辑与(在运算符两边都是布尔类型的时候是这个功能)
- &&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,&&是短路运算符,而&不是短路运算符
- 注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。
构造方法
构造方法是类的一种特殊的方法,在new一个对象的时候进行调用,就是为了对对象的属性进行赋值
使用关键字new产生一个对象时,大致分为两步
- 为对象在堆中分配空间
- 调用对象的构造方法为对象成员变量赋值
语法
- 方法名称和类同名
- 不用定义返回值类型 ,不可以写retrun语句
- 构造方法可以被重载
- 一个类至少存在一个构造方法,如果不写了构造方法,那么编译器会默认提供一个无参的构造方法,写了就不会有了
- 对象不能调用构造方法
在Java中定义一个不做事且没有参数的构造方法的作用
Java程序存在继承,在执行子类的构造方法时,如果没有用super()来调用父类特定的构造方法,则会调用父类中“没有参数的构造方法”。如果父类只定义了有参数的构造函数,而子类的构造函数没有用super调用父类那个特定的构造函数,就会出错。
接口和抽象类的对比
![]()
抽象类:
- 0 抽象类是用abstract关键词修饰
- 1.抽象类中可以定义构造器
- 2 抽象类就是普通类的超集,就是比普通类多了抽象方法,抽象方法可以一个都没有,也可以有N个,(有抽象方法的类必须是抽象类,但是抽象类不一定有抽象方法)
- 3 抽象类中可以定义成员变量
- 4.抽象类中可以包含静态方法
- 5.一个类只能继承一个抽象类 extends
接口:
- 0 接口用 interface关键字修饰
- 1.接口中不能定义构造器
- 2.方法全部都是抽象方法,默认都是 public,并且不允许定义为 private 或者 protected(Java8中接口中引入默认方法和静态方法,以此来减少抽象类和接口之间的差异。)
- 3 接口中定义的成员变量实际上都是公共静态常量 public static final
- 4.接口中不能有静态方法(Java8之前)
- 5.一个类可以实现多个接口,用implements
相同:
- 1.不能够实例化
- 2.可以将抽象类和接口类型作为引用类型,向上转型
- 3.一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要被声明为抽象类
关于抽象类和接口的概念分析
- 行为模型应该总是通过接口而不是抽象类定义,所以通常是优先选用接口,尽量少用抽象类。选择抽象类的时候通常是如下情况:需要定义子类的行为,又要为子类提供通用的功能。
- 抽象类更多是来描述一些我们脑子已经有的概念,但是又不能实例化的(类似一种模板设计,也就是说抽象类更偏向包含和实现子类的通用特性,子类去实现差异化的特性),比如人类,动物这种,是一种抽象的概况,比如我这个人去继承了人类这种抽象类,那我就拥有了人类共有的特征
- 接口更多的时候,是用来表示一种行为的抽象,和一种规范,比如我们的飞这种能力,我们很难去描述飞这种能力,所以我们用接口去实现飞这种能力,当我们实现了接口,就说明我们是有飞这种能力,第二种规范的话,比如usb接口,我们怎么鼠标想使用,必须去满足电脑usb接口的规范,我们才能正常使用
JDK8关于接口的改进
- 静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。
- 默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。
抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类
值传递
值传递
- 当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递,是值传递。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的,如果是基本类型的值传入,对基本类型的变量是没有改变的
Java程序设计语言对对象采用的不是引用调用,实际上,对象引用是按值传递的。
- 一个方法不能修改一个基本数据类型的参数(即数值型或布尔型》
- 一个方法可以改变一个对象参数的状态。
- 一个方法不能让对象参数引用一个新的对象。
值传递和引用传递有什么区别
- 值传递:指的是在方法调用时,传递的参数是按值的拷贝传递,传递的是值的拷贝,也就是说传递后就互不相关了。
- 引用传递:指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。
Java变量
成员变量与局部变量的区别有哪些
- 变量:在程序执行的过程中,在某个范围内其值可以发生改变的量。从本质上讲,变量其实是内存中的一小块区域
- 成员变量:方法外部,类内部定义的变量成员变量又称全局变量,可分为类变量和实例变量,有static修饰为类变量,没有static修饰为实例变量
- 局部变量:类的方法中的变量。
成员变量和局部变量的区别
作用域
- 实例变量:针对整个类有效。
- 局部变量:只在某个范围内有效。(一般指的就是方法,语句体内)
存储位置
- 实例变量:随着对象的创建而存在,随着对象的消失而消失,存储在堆内存中。
- 局部变量:在方法被调用,或者语句被执行的时候存在,存储在栈内存中。当方法调用完,或者语句结束后,就自动释放。
生命周期
- 实例变量:随着对象的创建而存在,随着对象的消失而消失
- 局部变量:当方法调用完,或者语句结束后,就自动释放。
初始值
- 实例变量:有默认初始值。
- 局部变量:没有默认初始值,使用前必须赋值。
使用原则
- 在使用变量时需要遵循的原则为:就近原则,首先在局部范围找,有就使用;接着在成员位置找。
Object的相关方法
- Object clone():创建与该对象的类相同的新对象,实现cloneable是一个标记接口,然后重写clone,会涉及到深浅拷贝,Object默认的是浅拷贝
- void finalize():当垃圾回收器确定不存在对该对象的更多引用时,对象垃圾回收器调用该方法
- Class getClass():返回一个对象运行时的实例类,三种获得反射对象的方法
- boolean equals(Object):比较两对象是否相等 ,equals方法在HashMap等跟hashcode方法配合使用,前面的euqals方法中有详细解释
- int hashCode():返回该对象的散列码值
- void wait():在其他线程调用此对象的 notify() 方法或 notifyAll()方法前,导致当前线程等待 在多线程方面细聊
- void notify():唤醒等待在该对象的监视器上的一个线程
- void notifyAll():唤醒等待在该对象的监视器上的全部线程
- String toString():返回该对象的字符串表示,在输出一个对象的名称的时候,会自动调用
Java包
JDK 中常用的包有哪些
- java.util 系统辅助包,比如集合类
- java.lang jdk1.1之后自动导入,JDK基础类,比如Object,String都在底下
- java.sql 数据库开发用到的包
- java.io,I/O开发包,文件的读取和写入
- java.net,网络编程包
import java和javax有什么区别
- 刚开始的时候 JavaAPI 所必需的包是 java 开头的包,javax 当时只是扩展 API 包来说使用。然而随着时间的推移,javax 逐渐的扩展成为 Java API 的组成部分。但是,将扩展从 javax 包移动到 java包将是太麻烦了,最终会破坏一堆现有的代码。因此,最终决定 javax 包将成为标准API的一部分
JVM
Java垃圾回收
简述Java垃圾回收机制
- 在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收
Java异常
![]()
- 这里的f a b表示 {a,b},将数据a b 传入 args数组
- 发生了数组越界异常
Java异常体系
- Error是程序员不能解决的错误, 比如OutOfMemory堆内存溢出错误 StackOverError是栈溢出错误
- 受检异常:Exception下除了RuntimeException的异常,都是受检异常
- 非受检异常:Error和RuntimeException都是非受检异常,不需要进行显式处理(throws/try-catch-finally) ,数组越界,除以0,空指针
Java异常关键字
- try – 用于监听。将要被监听的代码(可能抛出异常的代码)放在try语句块之内,当try语句块内发生异常时,异常就被抛出。
- catch – 用于捕获异常。catch用来捕获try语句块中发生的异常。
- finally语句块总是会被执行。它主要用于回收在try块里打开的物力资源(如数据库连接、网络连接和磁盘文件)。只有finally块,执行完成之后,才会回来执行try或者catch块中的return或者throw语句,如果finally中使用了return或者throw等终止方法的语句,则就不会跳回执行,直接停止。
- throw – 用于抛出异常。
- throws – 用在方法签名中,用于声明该方法可能抛出的异常。
Error 和 Exception 区别是什么?
- Error 类型的错误通常为虚拟机相关错误,如系统崩溃,内存不足,堆栈溢出等,编译器不会对这类错误进行检测,JAVA 应用程序也不应对这类错误进行捕获,一旦这类错误发生,通常应用程序会被终止,仅靠应用程序本身无法恢复;
- Exception 类的错误是可以在应用程序中进行捕获并处理的,通常遇到这种错误,应对其进行处理,使应用程序可以继续正常运行。
运行时异常和一般异常(受检异常)区别是什么?
- 运行时异常包括 RuntimeException 类及其子类,表示 JVM 在运行期间可能出现的异常。 Java 编译器不会检查运行时异常。
- 受检异常是Exception 中除 RuntimeException 及其子类之外的异常。 Java 编译器会检查受检异常。
- RuntimeException异常和受检异常之间的区别:是否强制要求调用者必须处理此异常,如果强制要求调用者必须进行处理,那么就使用受检异常,否则就选择非受检异常(RuntimeException)。一般来讲,如果没有特殊的要求,我们建议使用RuntimeException异常。
Java虽然提供了丰富的异常处理类,但是在项目中还会经常使用自定义异常,其主要原因是Java提供的异常类在某些情况下还是不能满足实际需球。例如以下情况:
- 1、系统中有些错误是符合Java语法,但不符合业务逻辑。
- 2、在分层的软件结构中,通常是在表现层统一对系统其他层次的异常进行捕获处理。
JVM 是如何处理异常的?
- 程序先执行 try 中的代码
- 如果 try 中的代码出现异常, 就会结束 try 中的代码, 看和 catch 中的异常类型是否匹配.
- 如果找到匹配的异常类型, 就会执行 catch 中的代码
- 如果没有找到匹配的异常类型, 就会将异常向上传递到上层调用者.
- 无论是否找到匹配的异常类型, finally 中的代码都会被执行到(在该方法结束之前执行).
- 如果上层调用者也没有处理的了异常, 就继续向上传递.
- 一直到 main 方法也没有合适的代码处理异常, 就会交给 JVM 来进行处理, 此时程序就会异常终止.
final、finally、finalize 有什么区别?
- final可以修饰类、变量、方法,修饰类表示该类不能被继承、修饰方法表示该方法不能被重写、修饰变量表示该变量是一个常量不能被重新赋值。
- finally一般作用在try-catch代码块中,在处理异常的时候,通常我们将一定要执行的代码方法finally代码块中,表示不管是否出现异常,该代码块都会执行,一般用来存放一些关闭资源的代码。
- finalize是一个方法,属于Object类的一个方法,而Object类是所有类的父类,Java 中允许使用 finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。
JavaI/O流
java 中 IO 流分为几种?
- 按照流的流向分,可以分为输入流和输出流;
- 按照操作单元划分,可以划分为字节流和字符流;
- 按照流的角色划分为节点流和处理流。
Java I0流的40多个类都是从如下4个抽象类基类中派生出来的。
- InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
- OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流
Files的常用方法都有哪些?
- Files. exists():检测文件路径是否存在。
- Files. createFile():创建文件。
- Files. createDirectory():创建文件夹。
- Files. delete():删除一个文件或目录。
- Files. copy():复制文件。
- Files. move():移动文件。
- Files. size():查看文件个数。
- Files. read():读取文件。
- Files. write():写入文件。
BIO,NIO,AIO 有什么区别
- BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
- NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
- AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制。
Java概述
何为编程
- 编程就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。为了使计算机能够理解人的意图,人类就必须要将需解决的问题的思路、方法、和手段通过计算机能够理解的形式告诉计算机,使得计算机能够根据人的指令一步一步去工作,完成某种特定的任务。这种人和计算机之间交流的过程就是编程。
什么是Java
- Java是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承、指针等概念,因此Java语言具有功能强大和简单易用两个特征。Java语言作为静态面向对象编程语言的代表,极好地实现了面向对象理论,允许程序员以优雅的思维方式进行复杂的编程 。
JDK和JRM和JVM的区别
- JDK :JDK还包括了一些JRE之外的东西 ,就是这些东西帮我们编译Java代码的, 还有就是监控JVM的一些工具 Java Development Kit是提供给Java开发人员使用的和Java基础类库。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)。查看JVM堆栈(jstack)等
- JRE :JRE是Java程序运行时环境,JavaRuntime Environment包括JVM和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,如果想要运行一个开发好的编译后的Java程序,计算机中只需要安装JRE即可,是Java运行的环境
- JVM:在倒数第二层 由他可以在(最后一层的)各种平台上运行 Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台,JVM是运行.class文件字节码
什么是跨平台性?原理是什么
- 所谓跨平台性,是指java语言编写的程序,一次编译后,可以在多个系统平台上运行(基于多个版本的JVM实现)。
- 实现原理:Java程序是通过JVM在系统平台上运行的,只要该系统可以安装相应的java虚拟机,该系统就可以运行java程序。
Java语言有哪些特点
- 简单易学(Java语言的语法与C语言和C++语言很接近)
- 面向对象(封装,继承,多态)
- 平台无关性(Java虚拟机实现平台无关性)
- 支持网络编程并且很方便(Java语言诞生本身就是为简化网络编程设计的,java.net包)
- 支持多线程(多线程机制使应用程序在同一时间并行执行多项任)
- 健壮性(Java语言的强类型机制、异常处理、垃圾的自动收集等)
- 安全性好
什么是字节码?采用字节码的最大好处是什么
- 字节码:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。
采用字节码的好处:
- Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不专对一种特定的机器,因此,Java程序无须重新编译便可在多种不同的计算机上运行,比如我们jar包就是.class文件。
Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
什么是Java程序的主类?应用程序和小程序的主类有何不同?
- 一个程序中可以有多个类,但只能有一个类是主类。在Java应用程序中,这个主类是指包含main()方法的类。而在Java小程序中,这个主类是一个继承自系统类JApplet或Applet的子类。应用程序的主类不一定要求是public类,但小程序的主类要求必须是public类。主类是Java程序执行的入口点。
Java应用程序与小程序之间有那些差别?
- 简单说应用程序是从主线程启动(也就是main()方法)。applet小程序没有main方法,主要是嵌在浏览器页面上运行(调用init()线程或者run()来启动),嵌入浏览器这点跟flash的小游戏类似。
Java反射
什么是反射机制
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
静态编译和动态编译
- 静态编译:在编译时确定类型,绑定对象
- 动态编译:运行时确定类型,绑定对象
- Person p =new Student();//p的编译时类型是Person,运行时类型是Student;
反射机制优缺点
- 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
- 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的java代码要慢很多。
反射机制的应用场景有哪些?
反射是框架设计的灵魂。
- 在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 SpringHibernate 等框架也大量使用到了反射机制。
- 举例:①我们在使用JDBC连接数据库时使用Class.forName()通过反射加载数据库的驱动程序;②Spring框架也用到很多反射机制,最经典的就是xml的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载入内存中; 2)Java类里面解析xml或properties里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的Class实例; 4)动态配置实例的属性
Java获取反射的三种方法
- 1.通过new对象实现反射机制 对象 对象.getClass
- 2.通过路径实现反射机制 Class.forName(包名.类名)
- 3.通过类名实现反射机制 类.class
反射创建对象
- 1. 使用 Class 对象的 newInstance()方法来创建该 Class 对象对应类的实例,但是这种方法要求该 Class 对象对应的类有默认的空构造器。
- 2. 先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例
包装类
- 因为Object接收不了八大基本数据类型,所以包装类应运而生,为了让Object能够接收
- 针对的是Byte,Int,Long,Short,Character
自动装箱与拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型(发生运算的时候);
int 和 Integer 有什么区别
- Java 是一个近乎纯洁的面向对象编程语言,但是为了编程的方便还是引入了基本数据类型,但是为了能够将这些基本数据类型当成对象操作,Java 为每一个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得二者可以相互转换。
Java 为每个原始类型提供了包装类型:
原始类型: boolean,char,byte,short,int,long,float,double
包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double
Integer a= 127 与 Integer b = 127相等吗
- 对于对象引用类型:==比较的是对象的内存地址。
- 对于基本数据类型:==比较的是值。
- 如果整型字面量的值在-128到127之间,那么自动装箱时或者调用包装类.valueOf不会new新的Integer对象(类似字符串的语法糖模式),而是直接引用常量池中的Integer对象,超过范围 a1==b1的结果是false
内部类
什么是内部类?
- 在Java中,可以将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。类似于我们人是一个类,心脏也可以是一个类,那么心脏这个类就是我们的内部类
引入内部类的原因
内部类的设计其实也是一种封装思想,其封装思想就是为了保护和易用
- 内部类和外部类能方便访问彼此的私有域(属性和方法)
- 内部类可以对外部类的外部完全隐藏(把内部类当作外部类的属性使用),可以使用private来修饰
- 内部类可以变相实现多继承(不推荐)
内部类的分类有哪些
内部类可以分为四种:成员内部类、局部内部类、匿名内部类和静态内部类。
匿名内部类的特点除了没有名字,匿名内部类还有以下特点:
- 匿名内部类必须继承一个抽象类或者实现一个接口。
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
关于内部成员成员类访问和创建问题
- 成员方法可以访问类的实例变量和静态变量,但是不能定义静态变量(类比成员方法也不能定义静态属性)
- 静态方法只能访问静态变量,不可以访问成员变量 类比内部成员类
- 内部成员类是可以访问其类中的静态域的(因为成员内部类是依赖于外部类对象的,都有了外部类对象,肯定能访问静态属性,成员属性也是可以访问的),但是不能在内部类定义静态域(假设可以,在成员内部类中拥有静态域,那么静态域是不需要对象就能访问的,都不需要成员内部类对象,那肯定不需要外部类对象,就违背了成员内部类要依赖外部类对象才能产生)
方法内部类
- 不能使用任何的权限修饰符(因为出了这个方法就不存在了,用修饰符没有意义)
- 对外部类的外部完全隐藏
- Test内部类要使用fun方法的形参或者是局部变量,该变量必须是隐式的final声明
- 在方法内部类也无法定义静态域,因为方法中就无法定义静态域,何况方法内部类被当作方法的局部变量
- 在方法内部类中使用了一个变量,那么这个变量编译之后就相当于final 修饰这个变量
匿名内部类(也是方法内部类的一种)
- 除了没有名字,匿名内部类还有以下特点:
- 匿名内部类必须继承一个抽象类或者实现一个接口。
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
内部类的优点
- 我们为什么要使用内部类呢?因为它有以下优点:
- 一个内部类对象可以访问创建它的外部类对象的内容,包括私有数据!
- 内部类不为同一包的其他类所见,具有很好的封装性;
- 内部类有效实现了“多重继承”,优化 java 单继承的缺陷。
- 匿名内部类可以很方便的定义回调。
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final呢?它内部原理是什么呢?先看这段代码:public class Outer { void outMethod(){ final int a =10; class Inner { void innerMethod(){ System.out.println(a); } } } }
以上例子,为什么要加final呢?是因为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。