java 高级特性
堆内存和栈的内存的区别
(1)栈内存用来存储局部变量和方法调用,堆内存用来存储Java中的对象。无论是成员变量、局部变量还是类变量,他们指向的对象都储存在堆内存中。
(2)栈内存可以理解为线程的私有内存,而堆内存中的对象对所有的线程可见。堆内存中的对象可以被所用线程访问。
(3)栈内存的空间远远小于堆内存的空间。
(4)如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError.如果堆内存中没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError.
String是基本数据类型吗?八种基本数据类型
整型:byte,int,short,long
浮点型:float,double
字符型:char
布尔型:boolean
String,StringBuffer,StringBuilder的区别
(1)string 的实例是不可变对象,一旦被创建就不能修改其值,对于已经存在的String对象的修改都是重新创建一个新的对象。
(2)StringBuffer和StringBuilder的对象是可变对象,修改时不会创建新的对象。
也因此,在拼接字符串时:
拼接速度方面:StringBuilder>StringBuffer>>String
线程安全方面:StringBuffer>StringBuilder>String
静态变量和实例变量的区别?
语法定义上的区别:静态变量要需要static关键字,实例变量不需要
程序运行是的区别:实例变量属于某个对象的属性,必须创建了实例对象;静态变量不属于某个实例对象,属于类,也叫做类变量。
抽象类和接口的区别
(1)抽象类中可以有构造方法、可以有非抽象方法、可以有静态方法,可以有普通成员变量,而接口中均不可以。
(2)抽象类中的抽象方法的访问类型可以是public或者protected,而接口中只能是public类型,且默认为public abstract
(3)抽象类和接口中都可以包含静态成员变量,但抽象类中的静态成员变量的访问类型可以任意,而接口中定义的变量只能是public static final 类型,并且默认为public static final类型。
(4)一个类可以实现多个接口,但只能继承一个抽象类。
面向对象的特征有哪些方面?
(1)封装。封装就是隐藏实现细节。通过private将属性私有化,提供共有方法(setter,getter方法)访问私有属性。优点:增加了数据访问限制,增强了程序的可维护性,使程序更加安全。
(2)抽象。抽象就是找出一些事物的相似和共性之处,然后将这些事物归为一个类。
(3)继承。继承是类之间的一种关系,子类自动共享父类的属性和方法。优点:提高了程序的可复用性和可扩展性,继承只能是单继承。
(4)多态。多态就是向上转型,即同一个引用类型,使用不同的实例可以执行不同的操作,即父类引用子类对象。优点:增强了程序的可扩展性、可维护性以及灵活性。
verload和ovrride的区别
共同点:方法名都是一致的。
区别:
1.重载必须在同类中,而重写在父子类之间。
2.重载参数的个数或类型不能相同,而重写参数的类型必须要一致。
3.重载对返回值没有要求,而重写要求子类的返回值必须要和父类一致,或者为父类返回值的子类
4.重载对修饰符没有要求,而重写要求子类的修饰必须大于等于父类的修饰符。
5.重载对方法的异常类型和数目没有要求,而重写的方法所抛出的异常必须和被覆盖方法的所抛出的异常一致,或者是其子类。
final 在 java 中有什么作用?
- final 修饰的类叫最终类,该类不能被继承。
- final 修饰的方法不能被重写。
- final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
list、set、map 三个接口,存取元素是各有什么特点。
1.首先,list和set集合接口都继承的是collection接口,只能添加单列元素;Map集合接口和collection接口是同一个级别,它以键值对的形式存储数据。 Collection的实现类通常用add()添加元素
2.List及其实现类是可变大小的列表,适用于按数值索引访问元素。List包括ArrayList和LinkedList类,LinkedList擅长添加删除元素,ArrayList擅长循环遍历。List有顺序,且允许重复。
3.Set集合中不允许有重复的数据,由于set是无序集合,在循环遍历的时候需要用增强for循环或者Iterator。Set包括HashSet和TreeSet类,HashSet是无序的,TreeSet是按字母顺序排序的。
4.Map集合包括HashMap类,Properties类,HashTable类。Map没有add()方法,向Map里添加数据只能用put()。Map不允许重复(key值不能相同,但是Value可以相同),只要key值相同,后面添加的数据就会把前面的覆盖掉。另外,key值是可以为空。
List的三个子类的特点(2017-2-23)
ArrayList 底层结构是数组,底层查询快,增删慢。
LinkedList 底层结构是链表型的,增删快,查询慢。
voctor 底层结构是数组 线程安全的,增删慢,查询慢。
16. “==”和“equals”方法的区别?
“==”比较的是两个引用对象是否相等,即是否指向同一个内存空间
equals()比较的是字符串内容是否相同。
继承的特性
-
子类拥有父类非 private 的属性、方法。
-
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
-
子类可以用自己的方式实现父类的方法。
-
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 A 类继承 B 类,B 类继承 C 类,所以按照关系就是 C 类是 B 类的父类,B 类是 A 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
-
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。
继承关键字
继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。
反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Java 反射主要提供以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
- 在运行时调用任意一个对象的方法
重点:是运行时而不是编译时
二、反射的主要用途
很多人都认为反射在实际的 Java 开发应用中并不广泛,其实不然。当我们在使用 IDE(如 Eclipse,IDEA)时,当我们输入一个对象或类并想调用它的属性或方法时,一按点号,编译器就会自动列出它的属性或方法,这里就会用到反射。
反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
- 新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
-
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
-
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
-
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
- 死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
-
Java 提供了三种创建线程的方法:
- 通过实现 Runnable 接口;
创建一个实现 Runnable 接口的类。
为了实现 Runnable,一个类只需要执行一个方法调用 run(),
你可以重写该方法,重要的是理解的 run() 可以调用其他方法,使用其他类,并声明变量,就像主线程一样。
在创建一个实现 Runnable 接口的类之后,你可以在类中实例化一个线程对象。
- 通过继承 Thread 类本身;
创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。
继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。
该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。
- 通过 Callable 和 Future 创建线程。
创建线程的三种方式的对比
-
1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
-
2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
HashMap 和HashTable有什么区别?(2017-2-23)
HashMap是线程不安全的,HashMap是一个接口,是Map的一个子接口,是将键映射到值得对象,不允许键值重复,
允许空键和空值;由于非线程安全,HashMap的效率要较HashTable的效率高一些.
HashTable 是线程安全的一个集合,不允许null值作为一个key值或者Value值;
HashTable 是 sychronize,多个线程访问时不需要自己为它的方法实现同步,而 HashMap 在被多个线程访问的时
候需要自己为它的方法实现同步;HashMap实现了Map接口,我们常用HashMap进行put和get操作读存键值对数据。
-
说一下 HashMap 的实现原理?
HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。
需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
-
线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了
造成系统拥挤效率不高。用线程池控制线程数量,其他线程 排队等候。一个任务执行完毕,再从队列的中取
最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如
果线程 池中有等待的工作线程,就可以开始运行了;否则进入等待队列。为什么要用线程池:
减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务
器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机) -
线程池的参数:
corePoolSize: 线程池中核心线程数量(经常干活的线程的数量),后续简称为coreSize
workQueue:当前线程数量等于coreSize的时候,新来的任务保存的地方,等着有空闲线程的时候再执行,后续简称为queue
maximumPoolSize:当前线程数量等于coreSize并且queue也满了,这时候就得额外创建线程了,创建之后线程池中的最大线程数就是由这个值决定的,后续简称为maxSize
keepAliveTime:空闲线程能存活的时间数值
unit:空闲线程能存活的时间单位
threadFactory:创建线程的工厂,可以指定线程的名字啊等等
handler:当前线程数等于maxSize,并且queue也满了,对于新来的任务的处理策略(是丢掉呢还是抛出异常呢)
-
newSingleThreadExecutor返回以个包含单线程的Executor,将多个任务交给此Exector时,这个线程处理完一个任务后接着处理下一个任务,若该线程出现异常,将会有一个新的线程来替代。
newFixedThreadPool返回一个包含指定数目线程的线程池,如果任务数量多于线程数目,那么没有没有执行的任务必须等待,直到有任务完成为止。
newCachedThreadPool根据用户的任务数创建相应的线程来处理,该线程池不会对线程数目加以限制,完全依赖于JVM能创建线程的数量,可能引起内存不足。 底层是基于ThreadPoolExecutor实现,借助reentrantlock保证并发。 coreSize核心线程数,maxsize最大线程数。
-
IO: InputStream,OutputStream, FileInputStream,FileOutputStream, BufferedInputStream,BufferedOutputStream Reader,Writer BufferedReader,BufferedWriter Java.io 包几乎包含了所有操作输入、输出需要的类。所有这些流类代表了输入源和输出目标。 Java.io 包中的流支持很多种格式,比如:基本类型、对象、本地化字符集等等。 一个流可以理解为一个数据的序列。输入流表示从一个源读取数据,输出流表示向一个目标写数据。
-
2.什么是流,按照传输的单位,分成哪两种流,并且他们的父类叫什么流是指数据的传输 字节流,字符流 字节流:InputStream(输入) OutputStream(输出) 字符流:Reader Writer 5.BufferedReader属于哪种流,它主要是用来做什么的,它里面有那些经典的方法 属于处理流中的缓冲流,可以将读取的内容存在内存里面,有readLine()方法 节点流 直接与数据源相连,用于输入或者输出 处理流:在节点流的基础上对之进行加工,进行一些功能的扩展 处理流的构造器必须要 传入节点流的子类 对文本文件操作用什么I/O流?FileReader FileWriter
-
9. 创建一个TCP客户程序的顺序是 D 建立socket A 获得I/O流 C 对I/O流进行读写操作 B 关闭I/O流 E 关闭socket 10. 创建一个TCP服务程序的顺序是 B 创建一个服务器socket C 从服务器socket接受客户连接请求 A 创建一个服务线程处理新的连接 D 在服务线程中,从socket中获得I/O流 E 对I/O流进行读写操作,完成与客户的交互 G 关闭I/O流 F 关闭socket java.io.File.mkdir():只能创建一级目录,且父目录必须存在,否则无法成功创建一个目录。 java.io.File.mkdirs():可以创建多级目录,父目录不一定存在。 io流怎样读取文件的?使用File对象获取文件路径,通过字符流Reader加入文件,使用字符缓存流BufferedReader处理Reader,再定义一个字符串,循环遍历出文件。 流一般需要不需要关闭,如果关闭的话在用什么方法,一般要在那个代码块里面关闭比较好,处理流是怎么关闭的,如果有多个流互相调用传入是怎么关闭的? 流一旦打开就必须关闭,使用close方法 放入finally语句块中(finally 语句一定会执行) 调用的处理流就关闭处理流 多个流互相调用只关闭最外层的流 OutputStream里面的write()是什么意思,write(byte b[], int off, int len)这个方法里面的三个参数分别是什么意思 write将指定字节传入数据源 Byte b[ ]是byte数组 b[off]是传入的第一个字符 b[off+len-1]是传入的最后的一个字符 len是实际长度 InputStream里的read()返回的是什么,read(byte[] data)是什么意思,返回的是什么值 返回的是所读取的字节的int型(范围0-255) read(byte [ ] data)将读取的字节储存在这个数组 返回的就是传入数组参数个数 什么叫对象序列化,什么是反序列化,实现对象序列化需要做哪些工作 对象序列化,将对象以二进制的形式保存在硬盘上 反序列化;将二进制的文件转化为对象读取 实现serializable接口 不想让字段放在硬盘上就加transient 什么是Java序列化,如何实现Java序列化? 序列化就是一种用来处理对象流的机制,将对象的内容进行流化。可以对流化后的对象进行读写操作,可以将流化后的对象传输于网络之间。序列化是为了解决在对象流读写操作时所引发的问题。 序列化的实现:将需要被序列化的类实现Serialize接口,没有需要实现的方法,此接口只是为了标注对象可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,再使用ObjectOutputStream对象的write(Object obj)方法就可以将参数obj的对象写出。 如何实现对象克隆(1)实现Cloneable接口重写Object类中的clone()方法(2)实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆
-
常见的异常类有哪些?
- NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
- SQLException:提供关于数据库访问错误或其他错误信息的异常。
- IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
- NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
- FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。
- IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
- ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
- ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。
- IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
- ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
- NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。
- NoSuchMethodException:无法找到某一特定方法时,抛出该异常。
- SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。
- UnsupportedOperationException:当不支持请求的操作时,抛出该异常。
- RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。
-
get 和 post 请求有哪些区别?
- GET在浏览器回退时是无害的,而POST会再次提交请求。
- GET产生的URL地址可以被Bookmark,而POST不可以。
- GET请求会被浏览器主动cache,而POST不会,除非手动设置。
- GET请求只能进行url编码,而POST支持多种编码方式。
- GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。
- GET请求在URL中传送的参数是有长度限制的,而POST么有。
- 对参数的数据类型,GET只接受ASCII字符,而POST没有限制。
- GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。
- GET参数通过URL传递,POST放在Request body中。
-
如何实现跨域?
方式一:图片ping或script标签跨域
图片ping常用于跟踪用户点击页面或动态广告曝光次数。
script标签可以得到从其他来源数据,这也是JSONP依赖的根据。方式二:JSONP跨域
JSONP(JSON with Padding)是数据格式JSON的一种“使用模式”,可以让网页从别的网域要数据。根据 XmlHttpRequest 对象受到同源策略的影响,而利用 <script>元素的这个开放策略,网页可以得到从其他来源动态产生的JSON数据,而这种使用模式就是所谓的 JSONP。用JSONP抓到的数据并不是JSON,而是任意的JavaScript,用 JavaScript解释器运行而不是用JSON解析器解析。所有,通过Chrome查看所有JSONP发送的Get请求都是js类型,而非XHR。 -
懒汉式写法(线程安全)
public class Singleton {
private static Singleton instance;
private Singleton (){}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}饿汉式写法
public class Singleton {
private static Singleton instance = new Singleton();
private Singleton (){}
public static Singleton getInstance() {
return instance;
}
}静态内部类
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒啊,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏。
双重校验锁
public class Singleton {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}总结:我个人比较喜欢静态内部类写法和饿汉式写法,其实这两种写法能够应付绝大多数情况了。其他写法也可以选择,主要还是看业务需求吧。
-
- 通过实现 Runnable 接口;