1 解释以下什么是面向对象
2 面向对象四大特性
- 抽象
- 抽象
- 封装
- 封装:封装是指把数据和操作数据的方法封装起来,对数据的访问只能通过已定义的接口
- 继承
- 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类,得到继承信息的类被称为子类
- 子类拥有父类对象所有的属性和方法,包括私有属性和私有方法,但是父类中的私有属性和私有方法对于子类来说只是拥有,但是无权访问。因为在一个子类被创建的时候,首先会在内存中创建一个父类对象,然后在父类对象外部放上子类独有的属性,两者结合起来形成一个子类的对象
- 子类可以拥有自己的属性和方法
- 子类可以用自己的方式实现父类的方法,也就是方法的重写
- 多态
- 多态:多态分为编译时多态和运行时多态。编译时多态指同名方法的重载,运行时多态指子类重写父类方法。运行时多态需要做两件事:一是子类继承父类并重写父类方法,二是父类型引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为
3 JDK JRE JVM
- JDK
- JDK:Java Development Kit,java开发工具包,真个java的核心,包括了java运行环境JRE,java工具和java基础类库
- JRE
- JRE:Java Runtime Environment,java运行环境,包含JVM标准实现及java核心类库
- JVM
- JVM:Java Virtual Machine,java虚拟机,整个java实现跨平台最核心的部分,能够运行以java语言写的软件程序。java程序首先会被编译成字节码,然后在JVM上解释执行
4 重写和重载的区别
- 重写
- 重载:编译时多态,同一个类中同名方法参数列表不同,不能根据返回类型进行区分,因为函数调用时不能指定类型信息,编译器不知道需要调用哪个函数
- 重载
- 重写:运行时多态,子类重写父类方法,方法签名相同,处理逻辑可能不同
5 java是否允许重写一个private方法或者static方法
- private方法
- java中不可以覆盖private方法,因为private修饰的变量和方法只能在当前类中使用,子类继承父类时,子类对于父类中的方法没有访问权限
- static方法
- java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法和任何实例都不相关,概念上不使用
- 静态方法可以被继承,但是不能被重写。如果父类和子类中存在方法签名相同的静态方法,那么子类的方法会把继承而来的父类方法隐藏,但是不是重写。简单讲就是父类的方法和子类的方法是两个没有关系的方法,具体调用哪一个方法是看当前是哪个对象的引用,这种父类方法也不存在多态的性质
6 构造器是否可以重写
- 父类的私有属性和构造方法,子类没有访问权限,所以,构造器不能被重写,但是可以重载,所以可以看到一个类中有多个构造器的情况
7 构造器特征
- 名字与类名相同
- 没有返回值,但是不能用void修饰
- 生成类的实例时自动执行,无需调用
8 java中声明一个无参的空的构造器有什么作用
- 如果类没有构造方法,java会默认生成一个什么都不做的构造方法,如果有构造方法,则不自动生成。在执行子类的构造方法之前,如果没有使用super来显示调用父类特定的构造方法,则会调用父类中没有参数的构造方法。因此,如果父类中定义了有参的构造器而没有无参的构造器,在子类的构造方法中又没有现实的通过super来调用父类中特定的 构造方法,那么,编译出错。因为java程序在父类中找不到没有参数的构造方法可以执行,解决策略就是在父类中添加一个空的无参的构造方法
9 java创建一个对象实例有几种策略
- 使用new关键字
- 使用Class类的newInstance方法,该方法调用无参的构造器创建对象
- 使用clone方法
- 反序列化
10 抽象类和接口有什么区别
- 抽象类中可以定义构造函数,接口中不能定义构造函数
- 抽象类中可以有抽象方法和具体方法,而接口中只能由抽象方法
- 抽象类的成员权限可以是public,default,protected,抽象类中的抽象就是为了重写,故不能被private修饰,而接口中成员只可以是public,方法默认public abstract成员变量默认public static abstract
- 抽象类可以包含静态方法,接口中不能包含静态方法
- 在java8中允许在接口中包含带有具体实现的方法,使用default修饰,这类方法就是默认方法
- 抽象类中可以包含静态方法,在java8之前,接口中不能包含静态方法,在java8及之后可以包含。之前不能包含是因为接口不可以实现方法,只可以定义方法,所以不能使用静态方法,因为静态方法必须实现。现在可以包含了,只能直接用接口调用静态方法。java8仍然不能包含静态代码块
11 静态变量和实例变量的区别
- 静态变量:由static修饰,也称为类变量,属于类,因此不管创建多少个对象,静态变量在内存中有且仅有一个拷贝,静态变量可以实现多个对象共享内存
- 实例变量:属于某一个具体的实例,首先创建对象,然后通过对象才能访问到它
12 short s1 = 1; s1 = s1 + 1; 有什么错? short s1 = 1; s1 += 1;有什么错
- short s1 = 1; s1 = s1 + 1;在做s1 + 1时会将类型隐式转换为int型,然后将int型值赋值给short会出现类型转换错误
- short s1 = 1; s1 += 1;+=是java规定的运算符,java编译器会对其进行特殊处理,因此可以正确编译
13 Integer和int的区别,包装类和基本类型的区别
- int是java八中基本数据烈性之一,而Integer是java为int类型提供的包装类
- int型变量的默认值为0,Integer变量的默认值为null,Integer可以区分null和0
- Integer变量必须实例化后才可以使用,int不需要
- 由于Integer变量实际上是对以恶搞Integer对象的引用,所以两个通过new生成的Integer变量永远是不相等的,因为其内存地址是不相同的
- Integer变量和int变量比较时,只要两个变量的值是相等的,则结果为true。因为包装类Integer和基本数据类型int进行比较时,java会自动拆箱为int,然后进行比较,实际上就是两个int型变量在进行比较
- 非new生成的Integer变量和new Integer生成的变量进行比较时,结果为false。因为非new生成的Integer变量指向的是java常量池中的对象,而new Integer生成的变量指向队中新建的对象,两者在内存中地址不相同
- 对于两个非new生成的Integer对象进行比较时,如果两个变量区间在-128~127,那么结果为true否则为false。包装类缓存。valueOf方法
- 包装类缓存:Boolean全部缓存,Byte全部缓存,Character缓存小于等于127的值,Short缓存-128~127,Long缓存-128~127,Integer缓存-128~127,Float和Double没有缓存
14 装箱与拆箱
- 自动拆箱是java编译器在基本数据类型和对应的包装类之间做一个转化。
15 switch能用在哪些数据类型上
- byte,short,char,int,String,枚举常量
16 字节和字符区别
- 字节是存储容量的基本单位
- 字符是数字,字母,汉字以及其他语言的各种符号
- 一个字符由一个或多个字节的二进制单位组成
17 面向对象四大特性
- 字符串常量池的需要,字符串常量池是java堆内存中一个特殊的存储区域,当创建一个String对象时,加入此字符串已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象
- 允许String对象缓存HashCode,java中String对象的哈希码被频繁的使用。字符串不变性保证了HashCode的唯一性,一次可以放心的进行缓存。这也是一种优化手段,意味着不必每次都去计算新的HashCode
- String被许多java类用作参数,比如文件路径,如果String可变,那么会引起各种安全隐患
18 String StringBuilder StringBuffer区别
- String用于字符串操作,属于不可变类,String不是基本数据类型,是引用类型,底层用char数组实现
- StringBuilder线程不安全
- StringBuffer线程安全
- 在StringBuffer中,并非所有的方法都是用了Synchronized修饰来实现同步,比如insert方法
- 执行效率:StringBuilder > StringBuffer > String
19 String字符串修改实现的原理
- 当使用String类型来堆字符串进行修改时,其实现方法是,首先创建一个StringBuffer,然后调用StringBuffer的append方法,最后调用StringBuffer的toString方法将结果返回
20 String类的常用方法有哪些
- indexOf:返回指定字符的索引
- charAt:返回指定索引处的字符
- replace:字符串替换
- trim:去除字符串两端空白
- split:分割字符串,返回一个分割后的字符串数组
- getBytes:返回字符串的byte类型数组
- length:返回字符串的长度
- toLowerCase:将字符串转成小写字母
- toUpperCase:将字符串转成大写字母
- subString:截取字符串
- equal:字符串比较
21 final修饰StringBuffer后还可以append吗
- 可以。final修饰的是一个引用变量,那么这个引用始终只能指向这个对象,但是这个对象的内部属性值是可以变化的
22 Object的常用方法有哪些
- clone方法:用于创建并返回当前对象的一份拷贝
- getClass:用于返回当前运行时对象的Class
- toString:返回对象的字符串表示形式
- finalize:实例被垃圾回收期回收时触发的方法
- equals:用于比较两个对象的内存地址是否相等,一般需要重写
- hashCode:用于返回对象的哈希值
- notify:唤醒一个在此对象监视器上等待的线程。如果有多个线程在等待,只会唤醒一个
- notifyAll:唤醒一个在此对象监视器上等待的线程。如果有多个线程在等待,唤醒全部
- wati:让当前线程等待
23 为什么wati/notify方法放在Object类中而不是Thread类中
- java提供的锁是对象级别而不是线程级别,每个对象都有锁,通过线程获得。如果线程需要等待某些锁,那么调用对象中的wati方法就没有意义了。如果wait方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,wait,notify,notifyAll都是锁级别操作,锁属于对象,所以将其定义在Object中
24 final finally finalize区别
- final:用于修饰属性,方法和类,表示属性不可变,方法不可覆盖,被其修饰的类不可被继承
- finally:异常处理语句结构的一部分,表示总是执行
- finallize:object类的一个方法,在垃圾回收时,在第一次标记结束后会调用finalize方法
25 finally块中的代码什么时候被执行
- 在java的异常处理体系中,finally块的作用就是为了保证无论出现什么情况,finally块里的代码一定会被执行。由于程序执行return就意味着结束对当前函数的调用并跳出这个函数体,因此任何语句都要执行在return之前执行,除非有exit,因此,finally中的代码也是在return之前执行的
- 如果try-finally或者catch-finally中都有return,那么finally中的return会覆盖别处的return,最终返回到调用者的是finally中的return值
26 finally是不是一定会被执行
- 不一定。比如以下两种情况
- 当程序进入try块之前就出现异常时,会直接结束,不会执行finally块中的代码
- 当程序在try块中强制退出时也不会去执行finally中的代码,比如在try中执行了exit方法
27 try-catch-finally中,如果catch中return了,finally还会执行吗
- 会。程序在执行到return时会首先将返回值存储在一个指定位置,其次去执行finally块,最后再返回。因此,对于基本数据类型,再finally块中改变return的值没有任何影响,直接覆盖掉;而对于引用类型时有影响的,返回的是再finally对前面return语句返回对象的修改值
28 try-cache-finally中哪个部分可以省略
- catch可以省略。try只适合处理运行时异常,try+catch适合处理运行时异常和普通异常。也就是说,如果只用try去处理普通异常却不加以catch处理,编译出错,因为编译器规定,普通异常如果选择捕获,必须使用catch显示声明以便进行下一步处理。而运行时异常没有如此规定,所以catch可以省略。
29 static关键字作用
- 静态变量:也称为类变量。这个变量属于类,类所有的实例都共享静态变量,可以直接通过类名访问。静态变量再内存中只存在一份
- 静态方法:静态方法在类加载的时候就已经存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说他不能是抽象方法。只能访问所属类的静态字段和静态方法,不能有super和this关键字
- 静态语句块:静态语句块在类初始化时运行一次
- 静态内部类:非静态内部类依赖于外部类的实例,而静态内部类不需要。静态内部类不能访问外部类的非静态的变量和方法
- 初始化顺序:静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于他们在代码中的位置
- 在有继承关系的情况下,初始化顺序为:父类(静态变量,静态语句块) 子类(静态变量,静态语句块) 父类(实例变量,普通语句块) 父类(构造函数) 子类(实例变量,普通语句块) 子类(构造函数)
30 super关键字作用
- 访问父类的构造函数,可以使用super()来访问父类的构造函数,从而委托父类完成一些初始化工作
- 访问父类成员变量,如果子类重写了父类的某个方法,可以通过使用super关键字来引用父类的方法实现
- this和super不能同时出现在一个构造函数里面,因为this必然会调用其他构造函数,其他构造函数也必然会有super的存在,所以在同一个构造函数里面有相同的语句,就失去了语句的意义,编译器也通不过
31 transient关键字作用
- 对不不想进行序列化的变量,使用transient关键字修饰
- transient关键字的作用是,阻止实例中哪些用此关键字修饰的变量序列化。当对象被反序列化时,被transient修饰的变量值不会被持久化和恢复。transient只能修饰变量,不能修饰类和方法
32 ==和equal的区别
- ==:如果比较的对象时基本数据类型,则比较的是数值是否相等,如果比较的是引用类型,比较的是对象的地址是否相等
- equals:用来比较两个对象的内容是否相等。equals不能用于比较基本数据类型的变量。如果没有对equals方法进行重写,则比较的是引用类型的 变量所指向的对象的地址
33 两个对象hashCode相同是否equals
- 两个对象hashCode相同不一定equals
- 两个对象equals一定hashCode相同
34 使用HashMap或者HashSet等集合时,重写equals方法时为什么一定要重写hashCode方法
- 对于对象集合的判重,如果一个集合含有n个对象实例的话,仅仅使用equals方法的话,对于一个ui想的判重就需要比较n-1次,实例数越多,比较次数越多,时间成本增加。但是同时使用哈希表的话,就能快速定位到对象的大概存储位置,并且定位到大概存储位置后,在后续比较中,如果两个对象hashCode不相同,那么也不需要调用equals方法了,大大减少了equals比较方法
- 如果两个对象相等,那么hashCode一定相同
- 如果两个对象相等,那么对于两个对象分别调用equals方法都返回true
- 如果两个对象hashCode相等,那么其不一定相当
- equals方法被覆盖过,则hashCode方法也必须被覆盖
- hashCode的默认行为是对堆上的对象产生独特值。如果没有重写hashCode,则该class的两个对象无论如何都不会相等,即使这两个对象指向相同的数据
35 &和&&的区别
- 在java中&和&&都是表示逻辑运算符,都表示逻辑运算符and,当两百年表达式都为真时整体计算结果才为真
- &&有短路功能,当第一个表达式值为false时,则不再进行第二个表达式的计算
- &不管第一个表达式结果是否为true,第二个表达式都会进行计算。除此之外,&还可以用作为运算符,当&两边不为布尔值时,&表示按位操作
36 java是值传递还是引用传递
- java的参数是以值传递的方式传入方法中,而不是引用传递
- 当传递方法参数类型为基本数据类型时,一个方法时不可能修改一个基本数据类型的参数
- 当传递方法参数类型为引用数据类型时,一个方法将修改要给引用数据类型的参数所指向对象的值。即使java函数在传递引用数据类型的时后,只是拷贝了引用值,所以方法也可以根据该引用值指向同一个对象并修改,但是这仍然是值传递而不是引用传递
37 Math.round()与Math.ceil()
- round向0靠拢
- ceil向无穷靠拢
37 两个二进制数的疑惑结果是什么
- 两个二进制数异或结果是这两个二进制数差的绝对值。表达式如下:a^b = |a-b|
- 两个二进制a与b异或,即a和b两个数按位进行运算。如果对应位相同,则该位结果位0,相当于对应的算数减,如果不同,则结果为1,相当于算数加。由于二进制数每一位要么是0要么是1,则按异或操作可表达为按位相减取值的绝对值,再按位累加
38 如何实现对象的克隆
- 实现Cloneable接口并重写Object类中的clone方法
- 实现Serializable接口,通过对象的序列化和反序列化来实现克隆,可以实现真正的深克隆
39 深克隆和浅克隆的区别
- 浅克隆:拷贝对象和原始对象的引用类型引用同一个对象。浅克隆只是复制了对象引用的地址,两个对象指向同一个内存地址,所以修改其中任意一个的值,另一个值就会随之改变,这是浅克隆
- 深克隆:拷贝对象和原始对象的引用类型引用不同对象。深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个不会发生改变,这是深克隆
- 深克隆的实现就是在引用类型所在的类实现Cloneable接口,并使用public访问修饰符重写clone方法
- java中定义的clone没有深浅之分,都是统一的调用Object的clone方法。为什么会有深克隆的概念?是由于问哦们在实现的过程中可以嵌套了clone方法的调用。也就是说深克隆就是在需要克隆的对象类型的类中重新实现克隆方法clone
40 什么是java的序列化,如何实现java的序列化
- 对象序列化是一个用于将对象状态转换为字节流的过程,可以将其保存到磁盘文件中或通过网络发送到任何其他程序。从字节流创建对象的相反过程称为反序列化。而创建的字节流是平台无关的,在一个平台上序列化的对象可以在不同的平台上反序列化。序列化是为了解决在对象流进行读写操作时所引发的问题
- 序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,只是用于标注该对象是可以被序列化的,然后使用一个输出流来构建一个ObjectOutputStream对象,接着使用ObjectOutputStream对象的writeObject方法可以将参数的对象写出,要恢复的话则使用输入流
41 什么情况下需要序列化
- 当你想把内存中的对象状态保存到一个文件中或者数据库中的时候
- 当你想用套接字在网络上传送对象的时候
- 当你想通过RMI传输对象的时候