Java基础
Java基础
标签(空格分隔): 面试
1. 重载和重写
- 重载: 表示同一个类中可以有多个名称相同的方法, 但是需要参数列表不同(即参数个数和参数类型不同).
- 重写: 表示将子类中的方法与父类中的某个方法的名称和参数完全相同,这个时候子类重写了父类的该方法, 这个是多态性的一种表现.
子类覆盖父类的时候只能抛出比父类更少的异常, 因为子类用于解决父类存在的一些问题, 所以不能比父类有更多的问题. --- 越来越完美
子类方法的访问权限只能比父类的更大,不能更小, 如果父类的该方法使用私有修饰符修饰的话, 这样就不存在继承. --- 越来越开放.
2. 抽象类和接口的区别
参数 | 抽象类 | 接口 |
---|---|---|
默认的方法实现 | 可以有默认的方法实现 | 接口是抽象的,不存在方法的实现(JDK8 default) . |
实现 | 子类使用extend关键字来继承抽象类,如果子类不是抽象类的话,他需要提供抽象类中所有声明方法的实现. | 子类使用关键字来implates来实现接口, 他需要提供接口中所有声明的方法和实现. |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
与正常的Java类的区别 | 除了不能实例化抽象类之外,它和普通的Java类没有区别(实例化抽象类, 其实是匿名类,需要将其中所有的抽象函数实现之后可以实现匿名类.) | 接口和类不是同一个东西. |
访问修饰符 | 抽象方法可以有public,protected,default这些访问修饰符. | 接口方法默认的修饰符是public,你不可以使用其他修饰符 |
main 方法 |
抽象方法可以有main 方法,并且我们可以运行它 |
接口没有main 方法,因此我们不能运行它 |
多继承 | 抽象方法可以继承一个类和实现多个接口 | 接口只能继承一个或者多个其他接口 |
速度 | 比接口的速度更快 | 接口式比较慢的,因为它需要时间去寻找在类中实现的方法 |
添加新方法 | 如果向抽象类中添加新的方法,你可以给他提供默认的实现,因此你不需要改变你现在的代码 | 如果在接口中添加方法,那么你必须改变实现该接口的类. |
package pers.ycy.base;
public class FinalClass {
public static void main(String[] args) {
Animal ag = new Dog();
new Dog();
new Dog().run();
Animal animal = new Animal() {
@Override
protected void sing() {
System.out.println("sing");
}
};
animal.sing();
}
}
/**
* 抽象类, 动物.
*/
abstract class Animal {
int age;
String name;
public static void main(String[] args) {
Animal sing_sing = new Animal() {
@Override
protected void sing() {
System.out.println("sing sing");
}
};
sing_sing.run();
}
// 每次创建 新对象的时候会调用一次, 运行优先级 高于构造函数 低于静态代码块.
{
System.out.println("构造代码块");
}
// 在第一次创建对象的时候会执行一次. 运行优先于 构造代码块, 通常用于初始化只调用一次的数据, 例如: 初始化程序的运行配置信息
static {
System.out.println("静态代码块");
}
Animal() {
System.out.println("抽象类的构造函数");
}
abstract protected void sing();
protected void run() {
{
int x = 10;
System.out.println(x);
System.out.println("用于限定变量的作用域.");
}
System.out.println("动物是可以跑得");
}
}
class Dog extends Animal {
@Override
protected void sing() {
System.out.println("汪汪");
}
}
abstract class AGou extends Dog{
}
public class FinalClass {
public static void main(String[] args) {
Dog dog = new Dog();
dog.eat();
}
}
/**
* 接口和类 是不同的, 抽象类中的 构造代码块, 静态代码块, 构造函数. 接口中是没有的.
*/
interface Animal{
default void eat(){
System.out.println("接口的默认方法, 如果使用该方法, 则该方法需要对所有实现该接口的类都有用 .");
}
}
class Dog implements Animal{
}
3. 说说反射的用途和实现
反射的用途
- Java的反射机制是一个非常强大的功能, 在很多的项目当中都可以看到反射的身影Spring, Mybatis.通过反射机制我们可以在运行期间获取对象的类型信息. 利用这一点我们可以实现工厂模式和代理模式等设计模式, 同时也可以解决Java的泛型擦除问题.
反射的实现
- 获取一个对象对应的反射类在,Java方法中有下面几种方法可以获得反射类.
getClass()
Class.forName()
类.class
- 通过类加载器实现,
getClassLoader()
4. Session和Cookie的区别
- Cookie是存放在客户端上的,Session存放在服务器上.
- Cookie不是很安全, 别人可以分析存放在本地的Cookie并进行Cookie欺骗,考虑到安全应当使用Session.
- Session会在一定时间内保存在服务器上, 当访问增多的时候会比较占用服务器的性能, 考虑到减轻服务器的压力应当使用Cookie.
- 单个Cookie保存的数据不应当超过4k, 很多浏览器限定一个站点最多保存20个Cookie.
5. GET请求和POST方式的区别
- 根据Http规范,GET用于信息获取, 而且应该是安全的和幂等性[1]的
- 根据HTTP规范,POST请求可以改变服务器上的资源.
- 首先是"GET方式提交的数据最多只能是1024字节",因为GET是通过URL提交数据,那么GET可提交的数据量就和URK长度有直接的关系了.而实际上,URL参数不存在上限,HTTP协议规范没有对URL长度进行限制,这个限制是特定的浏览器以及服务器对它的限制,IE对URL长度的限制是2083(2k+35).对于其他浏览器. 一般没有长度限制
- POST没有大小限制,HTTP协议规范也没有进行大小限制.
6. Session分布式处理
6.1 Session复制
在支持Session复制的Web服务器上,通过修改Web服务器的配置,可以实现将Session同步到其它Web服务器上,达到每个Web服务器上都保存一致的Session.
- 优点: 代码上不需要做支持和修改.
- 缺点: 需要依赖支持Session复制的Web服务器, 一旦更换成不支持的Web服务器就不能使用了, 并且在用户量巨大的情况下可能会造成Session大爆炸.
- 适用场景: 只适用于Web服务器比较少并且Session数据量比较小的情况.
- 可用方案: 开源方案 tomcat-redis-session-manager, 暂不支持Tomcat8.
6.2 Session 粘滞
将用户的每次请求都通过某种方法强制分发到某一个Web服务器上,只要这个Web服务器上存储了相应的Session数据,就可以实现会话跟踪.
- 优点: 使用简单,没有额外开销.
- 缺点: 一旦某个Web服务器重启或者宕机,相对应的Session数据也会丢失,而且需要依赖负载均衡机制.
- 适用场景: 对稳定性要求不是很高的业务情景.
6.3 Session集中管理
在单独的服务器或者服务器集群上使用缓存技术,如Redis存储Session数据,集中管理所有的Session.所有的Web服务器都从这个存储介质中去的对应的Session,实现Session共享.
- 优点: 可靠性高,减少Web服务器的资源开销.
- 缺点: 实现上有些复杂,配置较多.
- 适用场景: Web服务器较多,要求高可用性的情况.
- 可用方案: 开源方案Spring Session,也可以自己实现,主要是重写HttpServletRequestWrapper中的getSession方法.
6.4 基于Cookie管理
这种方式每次发起请求的时候都需要将Session数据放到Cookie中传递给服务端.
- 优点: 不需要依赖额外外部存储,不需要额外配置.
- 缺点: 不安全,已被带去或篡改;Cookie的长度和数量有限制,需要消耗更多的网络带宽.
- 使用场景:数据不重要,不敏感且数据量较小的情况.
7. JDBC的流程
- 向
DriverManager
类注册驱动数据库驱动程序. - 调用
DriverManager.getConnection
方法,通过JDBC URL将用户名,密码,数据表等信息传入,以得到数据库链接. - 获取Connection之后,便可以通过createStatement创建Statement用以执行SQL语句.
- 有时候会得到查询结果,比如select,得到查询结果,查询的结果存放于(ResultSet)当中.
- 关闭数据库语句,关闭数据库链接.
8. MVC设计思想
MVC是三个单词的首字母缩写, 他们是Model, View, Controller. 这个模式认为, 程序不论简单与复杂, 从结构上来看,都可以分为三层:
- 最顶层(View): 直接面向用户的视图层. 它是提供给用户的操作界面, 是程序的外壳.
- 最底层(Model): 核心的用户层, 也就是程序需要操作的数据或信息.
- 中层(Controller): 根据用户从"视图层"输入的指令,选取"数据层"中的数据,然后对其进行相应的操作,产生最终的结果.
9. equals
和==
的区别
两个对象用
equals
比较返回true
,那么两个对象的hashcode
方法必须返回相同的结果.
- 问题: 如若相反的话,
hashtable
中, 两个自认为相同的对象(equals
为true
), 在取值和存值的时候就会出现, 下表地址不同的情况. 这样就麻烦很大了.
两个对象用
equals
比较返回false
,那么两个对象的hashcode
方法也不一定返回不同的值,但是最好返回不同的值,这样可以提高哈希表的性能.
- 两个自认为不同的对象, 在hashtable中存取值的时候下标居然相同. 这样问题就很大.
根据第二点,重写
equals
的时候,必须重写hashcode
,以确保equals
方法相等的时候两个对象的hashcode
返回相同的值.
- 由上述的两点 ,
equals
和hashcode
应该保持一致性. - set集合去重的时候, 判断是否相等用的是 哈希值和equals同时成立与否. 这样也可以看出应该让
equals
和hashcode
应该保持一致性.
equals
和==
的区别是:==
用于比较原生类型,而equals
方法用于检查对象的相等性.- 如果
==
和equals
用于比较对象,当两个引用地址相同,==
返回true
.而equals
可以返回true
或false
主要取决于重写的实现.最常见的一个例子,字符串的比较,不同情况==
和equals
返回不同的结果.
10. list
和map
的区别.
Map
是键值对的方式进行存储的, 根据值去获取键,List
是根据下标进行存储的, 所以必须要有顺序,有下标才能顺利的进行存取. 但是Map
就不需要下标,且没有顺序. 只需计算Hash值即可
Map
接口有三个实现类:HashMap
,HashTable
,LinkedHashMap
.
List
接口有三个实现类:LinkList
,ArrayList
,Vector
.
LinkedList
: 底层基于链表实现, 链表内存是散乱的, 每一个内节点都存储着 上一个节点的地址和下一个节点的地址以及本身的值.
Map
相当于和Collection
一个级别的,Map
集合存储键值对,且要求保持键的唯一性.
Map
是基于哈希存储的所以要求保持键的唯一性.
其中区别: List特点 元素放入有顺序,元素且可以重复. Map特点 元素按键值对存储,无放入顺序.
11. List
和Set
的区别
List
和Set
都是继承自Collection接口.
List
特点: 要么是链表要么是顺序数组,所以有顺序,元素可重复.
Set
特点: HashSet(底层是HashMap),LinkedHashSet.
12. ArrayList
和LinkList
的区别
其实就是顺序数组和链表的区别
13. ArrayList
和Vector
的区别
都是顺序数组, 但是
Vector
是线程安全的,ArrayList
是线程不安全的.
Vector
的数据增长是原来的一倍,ArrayList
是增加原来的50%.
- 堆栈,队列,这种经常存储和删除的应该使用
Vector
这样可以减少因为扩容产生的时间消耗. - 快速读取等操作应该使用
ArrayList
.
14. HashMap
和HashTable
HashMap几乎可以等价于HashTable,除了HashMap是非
synchronized的
,并且可以接受null
(HashMap
可以接受为null
的键值和值,而HashTable
则不可以).
HashMap
是非synchronize
,而HashTable
是synchronize
的.这意味着HashTable
是线程安全的,多个线程可以共享一个HashTable
,如果没有一个正确的同步的话,多个线程是不能共享HashMap
的, Java5提供了ConcurrentHashMap,它是HashMap的替代,比HashMap的扩展性更好.
15. 线程安全问题
- 最简单的方式,
Synchronization
关键字. - 使用
java.util.concurrent.atomic
包中的原子类, 例如AtomicInteger
. - 使用
java.util.concurrent.locks
包中的锁 - 使用线程安全的集合
ConcurrentHashMap
- 使用
volatile
关键字,保证变量可见性(直接从内存读,而不是从线程cache
读)
在JVM底层volatile 是采用“内存屏障”来实现的.
缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。其核心思想如下:当某个 CPU 在写数据时,如果发现操作的变量是共享变量,则会通知其他 CPU 告知该变量的缓存行是无效的,因此其他 CPU 在读取该变量时,发现其无效会重新从主存中加载数据
16. synchronized
与 lock
的区别
-
synchronized
与lock
的区别 -
synchronized(隐式锁)
: 在需要同步的对象中加入此控制,synchronized
方法上, 也可以加在特定代码块中, 括号中表示需要上锁的对象. -
lock(显示锁)
: 需要显示指定其实位置和终止位置. 一般使用ReentrantLock
类作为锁, 多个线程中必须要使用一个ReentrantLock
类做为对象才能保证锁的生效。且在加锁和解锁处需要通过lock()
和unlock()
显示指出。所以一般会在finally
块中写unlock()
以防死锁。 -
synchronized
和lock
性能区别 -
synchronized
是托管给JVM
执行的,而lock
是Java
写的控制锁的代码。在JDK 1.5
中,synchronize 是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java
提供的Lock
对象,性能更高一些。但是到了JDK 1.6
,发生了变化。synchronize
在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在JDK 1.6
上synchronize
的性能并不比Lock
差。
17. Java跳出 一重以上循环的方式。
public static void main(String[] args) {
ok:
for(int i=0;i<10;i++){
for(int j=0;j<10;j++){
if(j==5){
System.out.println("j==5");
break ok;
}
}
}
}
18. final
final 修饰一个关键字的时候,是栈内存不能变,和堆内存无关。
public static void main(String[] args) {
final StringBuilder a = new StringBuilder("1qwe");
a.append("asdasd");
}
一次请求和多个请求对于同一个资源来说应该具有同样的结果(网络超时,电脑爆炸之类的不算数.). ↩︎