Java基础

1.面向对象三大特征:

(1)封装(Encapsulation)

封装就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

(2)继承(Inheritance)

继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

(3)多态(Polymorphism)

多态是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。

2.String、StringBuffer和StringBuilder的区别

(1)执行速度的不同(StringBuilder >

StringBuffer > String)

String:字符串常量

StringBuffer和StringBuilder:字符串变量

(2)是否线程安全

StringBuilder:线程非安全的

StringBuffer:线程安全的

String:线程安全(String是不可变类)

3.java.lang.Object类中的几个方法

(1)clone方法:创建并返回此对象的一个副本。

(2)equals方法:比较两个对象是否相等

(3)finalize方法:实例被垃圾回收器回收的时候触发的操作。

(4)getClass方法:返回当前运行时对象的Class对象

(5)hashCode方法:返回对象的哈希码值

(6)notify方法:唤醒在此对象监视器上等待的单个线程

(7)notifyAll方法:唤醒在此对象监视器上等待的所有线程

(8)toString方法:返回该对象的字符串表示

(9)wait方法:在其他线程调用此对象的notify方法或notifyAll方法前,导致当前线程等待

(10)wait(long timeout)方法:在其他线程调用此对象的notify方法或notifyAll方法,或者超过指定的时间量前,导致当前线程等待

(11)wait(long timeout,int nanos)方法:在其他线程调用此对象的notify方法或notifyAll方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待

4.String类常用方法

1)Object类的方法

2)charAt

3)compareTo

4)concat

5)contains

6)length

7)split

8)replace

9)substring

10)trim

11)toLowerCase

12)toUpperCase

5.equals和hashCode方法

覆盖equals时总要覆盖hashCode,相等的对象必须要有相等的散列码。一个哈希码可以映射到一个桶(bucket)中,hashcode的作用就是先确定对象是属于哪个桶的。如果多个对象有相同的哈希值,那么他们可以放在同一个桶中,如果有不同的哈希值,则需要放在不同的桶中。至于同一个桶中的各个对象之前如何区分就需要使用equals方法了。

如果相等的对象没有相等的散列码,那么就无法定位到相应的桶。

6.Override和Overload的区别

方法的重写(或覆盖)Override和重载Overload是Java多态性的不同表现。重载称为编译时多态,重写称为运行时多态。

方法覆盖:用在父子类中,方法名字相同,参数列表相同,声明形式都相同,但是子类方法的权限不允许小于父类,不允许抛出比父类更多的异常。

方法重载:在一个类中定义了同名的方法,它们或有不同的参数个数或有不同的参数类型。重载的方法可以改变返回值的类型。

7.==和equals的区别

1)“==”操作比较的是两个变量的值是否相等,对于引用型变量表示的是两个变量在堆中存储的地址是否相同,即栈中的内容是否相同

2)equals操作表示的两个变量是否是对同一个对象的引用,即堆中的内容是否相同

8.Comparable和Comparator区别比较

Comparable是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator是比较器,我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。

1)Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。

2)两种方法各有优劣,用Comparable简单,只要实现Comparable接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。用Comparator的好处是不需要修改源代码,而是另外实现一个比较器,当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了,并且在Comparator里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。

packagejava.lang;

publicinterfaceComparable<T>{

publicintcompareTo(T o);

}

packagejava.util;

publicinterfaceComparator<T>{

intcompare(T o1, T o2);

booleanequals(Object obj);

}

9.BIO、NIO和AIO(Java IO方式)

Java IO的方式通常分为同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。JDK1.4之前只支持BIO,1.4后开始支持NIO,1.7开始支持AIO。

同步时,应用程序会直接参与IO读写操作,并且我们的应用程序会直接阻塞到某一个方法上,直到数据准备就绪;或者采用轮询的策略实时检查数据的就绪状态例如NIO,如果就绪则获取数据。

异步时,则所在的IO读写操作交给操作系统处理,与我们的应用程序没有直接关系,我们应用程序不需要关心IO读写,当操作系统完成了IO读写操作时,会给我们应用程序发送通知,我们应用程序直接拿走数据即可。

同步阻塞IO(BIO)

同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以通过线程池机制改善。

同步非阻塞IO(NIO)

同步非阻塞,服务器实现模式为一个请求一个线程(使用单线程或者只使用少量的多线程,多个连接共用一个线程),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程需要时不时的询问IO操作是否就绪。NIO通常采用Reactor模式。

异步非阻塞IO(AIO)

服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。AIO通常采用Proactor模式。

适用场景

BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,程序直观简单易理解。

NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,并发局限于应用中,编程比较复杂。

AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂。

事件分发器的两种模式称为:Reactor和Proactor。Reactor模式是基于同步I/O的,而Proactor模式是和异步I/O相关的。在Reactor模式中,事件分发器等待某个事件或者可应用或个操作的状态发生(比如文件描述符可读写,或者是socket可读写),事件分发器就把这个事件传给事先注册的事件处理函数或者回调函数,由后者来做实际的读写操作。

而在Proactor模式中,事件处理者(或者代由事件分发器发起)直接发起一个异步读写操作(相当于请求),而实际的工作是由操作系统来完成的。发起时,需要提供的参数包括用于存放读到数据的缓存区、读的数据大小或用于存放外发数据的缓存区,以及这个请求完后的回调函数等信息。事件分发器得知了这个请求,它默默等待这个请求的完成,然后转发完成事件给相应的事件处理者或者回调。

举个例子,将有助于理解Reactor与Proactor二者的差异,以读操作为例(写操作类似)。

Reactor中实现读

1)注册读就绪事件和相应的事件处理器。

2)事件分发器等待事件。

3)事件到来,激活分发器,分发器调用事件对应的处理器。

4)事件处理器完成实际的读操作,处理读到的数据,注册新的事件,然后返还控制权。

Proactor中实现读:

1)处理器发起异步读操作(注意:操作系统必须支持异步IO)。在这种情况下,处理器无视IO就绪事件,它关注的是完成事件。

2)事件分发器等待操作完成事件。

3)在分发器等待过程中,操作系统利用并行的内核线程执行实际的读操作,并将结果数据存入用户自定义缓冲区,最后通知事件分发器读操作完成。

4)事件分发器呼唤处理器。

5)事件处理器处理用户自定义缓冲区中的数据,然后启动一个新的异步操作,并将控制权返回事件分发器。

可以看出,两个模式的相同点,都是对某个I/O事件的事件通知(即告诉某个模块,这个I/O操作可以进行或已经完成)。在结构上,两者也有相同点:事件分发器负责提交IO操作(异步)、查询设备是否可操作(同步),然后当条件满足时,就回调handler;不同点在于,异步情况下(Proactor),当回调handler时,表示I/O操作已经完成;同步情况下(Reactor),回调handler时,表示I/O设备可以进行某个操作(can read或can write)。

10.instanceof和getClass的区别

instanceof判断是否是某一类型的实例时,该类型可以是父类或者接口。而getclass用于判断准确的类型。同时,在这里必须说明的是,getclass判断的是该变量实际指向的对象的类型(即运行时类型),跟声明该变量的类型无关。

11.Java序列化

使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的“状态”,即它的成员变量,对象序列化不会关注类中的静态变量。

对象的序列化主要有两种用途:

1)把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;

2)在网络上传送对象的字节序列。

transient关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如int型的是0,对象型的是null。

单例模式模式的序列化

private ObjectreadResolve() {

return singleton;

}

如何对Java对象进行序列化与反序列化

在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。

根据父类对象序列化的规则,我们可以将不需要被序列化的字段抽取出来放到父类中,子类实现Serializable接口,父类不实现,根据父类序列化规则,父类的字段数据将不被序列化。

序列化及反序列化相关知识

1)在Java中,只要一个类实现了java.io.Serializable接口,那么它就可以被序列化。

2)通过ObjectOutputStream和ObjectInputStream对对象进行序列化及反序列化

3)虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化ID是否一致(就是private static final long serialVersionUID)

4)序列化并不保存静态变量。

5)要想将父类对象也序列化,就需要让父类也实现Serializable接口。

6)Transient关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient变量的值被设为初始值,如int型的是0,对象型的是null。

7)服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

在序列化过程中,如果被序列化的类中定义了writeObjectreadObject方法,虚拟机会试图调用对象类里的writeObject和readObject方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,则默认调用是ObjectOutputStream的defaultWriteObject方法以及ObjectInputStream的defaultReadObject方法。用户自定义的writeObject和readObject方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

序列化ID的问题

serialVersionUID适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException

12.异常机制

在Java应用程序中,异常处理机制为:抛出异常,捕捉异常


Java基础_第1张图片

Java的异常(包括Exception和Error)分为可查的异常(checked

exceptions)和不可查的异常(unchecked

exceptions)。

除了RuntimeException及其子类以外,其他的Exception类及其子类都属于可查异常。这种异常的特点是Java编译器会检查它,也就是说,当程序中可能出现这类异常,要么用try-catch语句捕获它,要么用throws子句声明抛出它,否则编译不会通过。

不可查异常(编译器不要求强制处置的异常):包括运行时异常(RuntimeException与其子类)和错误(Error

Exception这种异常分两大类运行时异常和非运行时异常(编译异常)。程序中应当尽可能去处理这些异常。

运行时异常:都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

非运行时异常(编译异常):是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOExceptionSQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

Java规定:对于可查异常必须捕捉、或者声明抛出,允许忽略不可查的RuntimeException和Error。

13.Java泛型的实现方法:类型擦除

Java的泛型是伪泛型。因为,在编译期间,所有的泛型信息都会被擦除掉。正确理解泛型概念的首要前提是理解类型擦出(type erasure)。

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

如在代码中定义的List和List等类型,在编译后都会编程List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。

14.深复制浅复制

浅复制(浅拷贝)

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象

深复制(深拷贝)

被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

15.反射

什么是反射(Reflection

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

为什么需要反射

通过反射,我们能够

Ø在运行时检测对象的类型;

Ø动态构造某个类的对象;

Ø检测类的属性和方法;

Ø任意调用对象的方法;

Ø修改构造函数、方法、属性的可见性。

反射是框架中常用的方法

在java.lang.reflect包中有三个重要的类:

ØField:描述类的域

ØMethod:描述类的方法

ØConstructor:描述类的构造器

对于public域(包括超类成员):

ØgetFields

ØgetMethods

ØgetConstructors

对于其它域(包括私有和受保护的成员,不包括超类成员):

ØgettDeclaredFields

ØgettDeclaredMethods

ØgettDeclaredConstructors

利用反射访问私有属性和方法

method.setAccessible(true);

缺点:性能是一个问题,反射相当于一系列解释操作,通知JVM要做的事情,性能比直接的Java代码要慢很多。

16.Java注解

注解

注解是Java 5的一个新特性。注解是插入你代码中的一种注释或者说是一种元数据(meta data)。这些注解信息可以在编译期使用预编译工具进行处理,也可以在运行期使用Java反射机制进行处理。

//声明Test注解

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface Test{

}

我们使用了@interface声明了Test注解,并使用@Target注解传入ElementType.METHOD参数来标明@Test只能用于方法上,@Retention(RetentionPolicy.RUNTIME)则用来表示该注解生存期是运行时,从代码上看注解的定义很像接口的定义,确实如此,毕竟在编译后也会生成Test.class文件。对于@Target和@Retention是由Java提供的元注解,所谓元注解就是标记其他注解的注解。

元注解

@Target用来约束注解可以应用的地方(如方法、类或字段),其中ElementType是枚举类型,其定义如下,也代表可能的取值范围。

public enum ElementType{

/**标明该注解可以用于类、接口(包括注解类型)或enum声明*/

TYPE,

/**标明该注解可以用于字段(域)声明,包括enum实例*/

FIELD,

/**标明该注解可以用于方法声明*/

METHOD,

/**标明该注解可以用于参数声明*/

PARAMETER,

/**标明注解可以用于构造函数声明*/

CONSTRUCTOR,

/**标明注解可以用于局部变量声明*/

LOCAL_VARIABLE,

/**标明注解可以用于注解声明(应用于另一个注解上)*/

ANNOTATION_TYPE,

/**标明注解可以用于包声明*/

PACKAGE,

/**

*标明注解可以用于类型参数声明(1.8新加入)

* @since 1.8

*/

TYPE_PARAMETER,

/**

*类型使用声明(1.8新加入)

* @since 1.8

*/

TYPE_USE

}

@Retention用来约束注解的生命周期,分别有三个值,源码级别(source),类文件级别(class)或者运行时级别(runtime)

注解的一些规则

1)注解可以使用的元素

Ø所有基本类型(int、float、boolean等)。

ØString

ØClass

Øenum

ØAnnotation

Ø以上类型的数组

2)默认值限制

编译器对元素的默认值有些过分挑剔,首先元素不能有不确定的值,即元素必须要么有默认值,要么在使用注解的时候提供元素的值。其次,对于非基本类型的元素,无论是在源码声明时,或者是在注解接口中定义默认值时,都不能用null作为其值。这个约束使得处理器很难表现一个元素的存在或者缺失的状态,因为在每个注解的声明中,所有的元素都存在并且具有相应的值,那么我们怎么样表现元素的存在或缺失状态呢?我们可以定义一些特殊值,例如空字符串或者负数,用以表示某个元素不存在。

3)注解不支持继承

不能使用关键字extends来继承某个@interface。

4)注解的方法不能有参数

在Java中实现注解一般是按照以下步骤:定义注解—>实现注解处理器—>使用注解。注解处理器的实现方式分为两种,一种是用反射的方式实现,另一种则是使用apt工具实现。

17.Java1.8新特性

lambda表达式

由于Java是以类为程序的基本单位,它没有类似于C语言的函数指针,在类似于函数回调这样的场景时,大部分都会使用到匿名类来代替,这样子造成了代码上的冗余,阅读起来也不方便。所以在Java 8里添加了Lambda表达式来弥补这个缺陷。Lambda的作用是让函数像变量一样可以作为参数传递。让我们先来看一个简单的例子:

publicstaticvoidmain(String[] args) {

List<String> names = Arrays.asList("shildon","drake","jackson");

Collections.sort(names,newComparator<String>() {

@Override

publicintcompare(Stringo1,Stringo2) {

returno1.compareTo(o2);

}

});

System.out.println(names);//打印结果:[drake, jackson, shildon]

}

从这个例子我们已经可以看出Java 8之前的不足之处。好了,现在我们有了Lambda表达式,来看看它是什么样的:

publicstaticvoidmain(String[] args) {

List<String> names = Arrays.asList("shildon","drake","jackson");

Collections.sort(names, (Stringa,Stringb) -> {

returna.compareTo(b);

});

System.out.println(names);//打印结果:[drake, jackson, shildon]

}

但是你还可以这样!

Collections.sort(names, (String a, String b) ->b.compareTo(a));

甚至可以这样!

Collections.sort(names, (a, b) -> b.compareTo(a));

你可能感兴趣的:(Java基础)