1. static关键字是什么意思?Java 中是否可以覆盖(override)一个 private 或者是 static 的方法?是否可以在 static 环境中访问非static 变量?
static关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。
Java中static方法不能被覆盖,因为方法覆盖(override)是基于运行时动态绑定的,而 static 方法是编译时静态绑定的。static 方法跟类的任何实例都不相关,所以概念上不适用。
不可以在static 环境中访问非static 变量,static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被 Java 虚拟机载入的时候,会对 static 变量进行初始化(与类同生死)。非static变量,只有类被创建时,才会分配空间(如果是方法内的局部变量,那么在方法被调用的时候才创建)。
2. Java中接口和抽象类的区别
接口比抽象类更加抽象,因为抽象类中可以定义构造器,可以有抽象方法和具体方法,而接口中不能定义构造器而且其中的方法全部都是抽象方法。
类可以实现很多个接口,但是只能继承一个抽象类。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现, 否则该类仍然需要被声明为抽象类。
抽象类可以在不提供接口方法实现的情况下实现接口。
Java 接口中声明的变量默认都是final的。抽象类可以包含非final的变量。
Java 接口中的成员函数默认是 public 的。抽象类的成员函数可以是 private,protected 或者是 public。
接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含 main方法的话是可以被调用的。(抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用)
接口可以继承接口。抽象类可以实现(implements)接口**,抽象类可继承具体类,但前提是具体类必须有明确的构造函数**。
有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法。 接口可以继承接口,而且支持多重继承。抽象类可以实现(implements)接口,抽象类可继承具体类也可以继承抽象类。
3. 进程和线程
进程是程序的一次动态执行过程,每个进程都有自己独立的内存空间。一个应用程序可以同时启动多个进程(比如浏览器可以开多个窗口,每个窗口就是一个进程),进程是执行着的应用程序,而线程是进程内部的一个执行序列。一个进程可以有多个线程。线程又叫做轻量级进程。
多进程操作系统能够运行多个进程,每个进程都能够循环利用所需要的CPU时间片,使的所有进程看上去像在同时运行一样。
线程是进程的一个执行流程,是CPU调度和分 派的基本单位。一个进程可以由多个线程组成,也就是一个进程可以同时运行多个不同的线程,每个线程完成不同的任务。
线程的并发运行:就是一个进程内若干个线程同时运行。(比如:word的拼写检查功能和首字母自动大写功能是word进程中的线程)
线程和进程的关系是一个局部和整体的关系,每个进程都由操作系统分配独立的内存地址空间,而同一进程的所有线程都在同一地址空间工作。(进程在执行时通常拥有独立的内存单元,而线程之间可以实现内存共享)
虽然使用多线程的编程通常能够带来更好的性能和用户体验,但是多线程占用更多的CPU资源,对于其他的进程并不友好(可能占用了更多的CPU 资源),也不是线程越多, 程序的性能就越好,因为线程之间的调度和切换也会浪费CPU 时间。
4. 线程的生命周期
一个线程的完整生命周期要经历5中状态:新建、就绪、运行、阻塞、死亡。
新建状态:使用new和某种线程的构造方法来创建线程对象,该线程就会进入新建状态,系统为该线程对象分配内存空间。处于新建状态的线程可以通过调用**start()**方法进入就绪状态。
就绪状态(Runnable):此时线程已经具备了运行的条件,进入了线程队列,等待系统分配CPU资源,一旦获得CPU资源,该线程就会进入运行状态。(线程准备运行,不一定立马就能开始执行)
运行状态(Running):进入运行在状态,线程会执行自己的**run()**方法中的代码。就是进程正在执行线程的代码。
阻塞状态(Blocked):一个正在执行的线程,如果执行了suspend、join或sleep方法,或等待io设备的使用权,那么该线程将会让出自己的CUP控制权并暂时中止自己的执行,进入阻塞状态。阻塞的线程,不能够进入就绪队列,只有当阻塞原因被消除的时候,线程才能进入就绪状态,重新进入线程队列中排队等待CPU资源,然后继续执行。
死亡状态(Dead):一个线程完成了全部工作或者被提前强制性的中止,该线程就处于死亡状态。
睡眠状态(Sleeping):线程通过Thread.sleep(线程睡眠)、Object.wait(线程等待)、LockSupport.park(线程暂停)三种方式 使线程进入休眠状态。 同步阻塞(Blocked on Synchronization):sleep( ) 使线程在一定的时间内进入阻塞状态(不会释放锁资源)、wait( ) 使线程进入阻塞状态,(释放自己占有的锁资源,搭配notify( )使用)、suspend( ) 使线程进入阻塞状态(必须其对应的resume( )被调用,才能使线程重新进入可执行状态)
5. 线程同步以及线程调度相关的方法
wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁; sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常; notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM 确定唤醒哪个线程,而且与优先级无关; notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
6. Thread类的sleep()方法和对象的wait()方法有什么区别?线程的sleep()方法和yield()方法有什么区别?
Thread类的sleep()方法和对象的wait()方法
sleep()**方法(休眠)是**线程类( Thread)**的**静态方法,调用此方法让当前线程暂停执行指定的时间, 将执行机会( CPU)让给其他线程, 但是**对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态)。 *wait()* 方法是Object 类的方法**,调用对象的wait()方法导致当前线程 放弃对象的锁(线程暂停执行), 进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等锁池(lock pool), 如果线程重新获得对象的锁就可以进入就绪状态。
线程的sleep()方法和yield()方法
1)sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会; 2)线程执行sleep()方法后转入阻塞( blocked)状态, 而执行yield()方法后转入就绪( ready)状态; 3)sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常; 4)sleep()方法比yield()方法(跟操作系统CPU调度相关)具有更好的可移植性。
7. 什么是线程池(thread pool)
在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java 中,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是** 池化资源技术**产生的原因。 线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建, 使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。 Java 5+中的Executor接口定义一个执行线程的工具。它的子类型即线程池接口是ExecutorService。工具类Executors面提供了一些静态工厂方法,生成一些常用的线程池。 工具类Executors生成线程池的常用方法:
1)newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。 2) newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程(返回到线程池中)。【推荐使用】 3)newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60 秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 4)newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。 5)newSingleThreadExecutor:创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。
如果希望在服务器上使用线程池,强烈建议使用newFixedThreadPool方法来创建线程池,这样能获得更好的性能。
8. Java中守护线程和本地线程区别
java 中的线程分为两种:守护线程( Daemon)和用户线程( User)。 j任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(boolon);true则把该线程设置为守护线程,false则设置为用户线程。Thread.setDaemon() 必须在Thread.start()之前调用,否则运行时会抛出异常。
守护线程和本地线程两者的区别
唯一的区别是判断虚拟机(JVM)何时离开,守护线程Daemon是为其他线程提供服务,如果全部的用户现场Thread 已经撤离, Daemon 没有可服务的线程,JVM 撤离。也可 以理解为守护线程是JVM 自动创建的线程( 但不一定),用户线程是程序创建的线程;比如JVM 的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是Java 虚拟机上仅剩的线 程时,Java 虚拟机会自动离开。
9. synchronized关键字的用法,当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?
1)synchronized 关键字可以将对象或者方法标记为同步,以实现对对象和方法的互斥访问,可以用synchronized(对象) { }定义同步代码块,或者在声明方法时将synchronized作为方法的修饰符。 2)不能进入。 其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象锁已经被取走,那么试图进入B 方法的线程就只能在等锁池( 注意不是等待池)中等待对象的锁。
10. 简述synchronized和java.util.concurrent.locks.Lock的异同
Lock是JDK1.5 以后引入的新的API,和关键字synchronized 的异同: 相同点:Lock 能完成synchronized 所实现的所有功能; 主要不同点:Lock有比synchronized 更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized会自动释放锁,而Lock一定要求程序员手工释放,并且最好在finally 块中释放(这是释放外部资源的最好的地方)。
11. 死锁、活锁、饥饿?产生死锁的必要条件是什么?如何确保 N 个线程可以访问N 个资源同时又不导致死锁?
死锁、活锁、饥饿 的概念,之间的区别
1)死锁:两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去 2)活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试, 失败。 3)饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。
活锁和死锁的区别: 处于活锁的实体是在不断的改变状态,所谓的“ 活”, 而 处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。
Java 中导致饥饿的原因: 1)高优先级线程吞噬所有的低优先级线程的CPU 时间。 2)线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前 持续地对该同步块进行访问。 3)线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait 方 法),因为其他线程总是被持续地获得唤醒。
产生死锁的必要条件:
1)互斥条件:所**谓互斥就是进程在某一时间内独占资源。 2)**请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。 3)不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。 4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
如何确保 N 个线程可以访问N 个资源同时又不导致死锁
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。
多线程的上下文
多线程会共同使用一组计算机上的CPU,而线程数大于给程序分配的CPU 数量时,为了让各个线程都有执行的机会,就需要轮转使用CPU。不同的线程切换使用CPU发生的切换数据等就是上下文切换。
12. 内存中的栈(stack)、堆(heap)和方法区(method area)的用法
堆:存放方法对象(通过new 关键字和构造器创建的对象)
栈:存放方法以及方法中的局部变量
方法区:(也叫共享区)存放代码片段、静态属性、常量池(比如常量池中存放字符串的值)
通常我们定义一个基本数据类型的变量,一个对象的引用,函数调用的现场保存都使用JVM 中的栈空间;而通过new 关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor 和To Survivor)、Tenured;方法区和堆都 是各个线程共享的内存区域,用于存储已经被JVM 加载的类信息、常量、静态变 量、JIT 编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、” hello” 和常量都是放在常量池中, 常量池是方法区的一部分。栈空间操作起来 最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM 的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量 池空间不足则会引发OutOfMemoryError。 String str = new String("HelloWorld");,这个代码中,变量str 存放在栈上,用new 创建出来的字符串对象放在堆上,”HelloWorld” 这个字面量(String)是放在方法区中。
13. switch 的参数类型
JDK1.5 以前,switch()的参数中,只能是byte、short、char、int。 从JDK1.5 开始, Java 中引入了枚举类型与byte short char int的包装类。 从JDK 1.7 开始,参数还可以是字符串( String)类型,但是长整型( long)在目前所有的版本中都是不可以的。 也就是现在switch支持byte、short、char、int、String、枚举
JDK1.5,对四个包装类的支持是因为java编译器在底层手动进行拆箱,而对枚举类的支持是因为枚举类有一个ordinal方法,该方法实际上是一个int类型的数值。 jdk1.7开始支持String类型,但实际上String类型有一个hashCode算法,结果也是int类型.而byte short char类型可以在不损失精度的情况下向上转型成int类型,所以总的来说,可以认为switch中只支持int.
14. 两个对象值相同(x.equals(y) == true),但却可有不同的hash code,这句话对不对?【***】
不对,如果两个对象x 和y 满足x.equals(y) == true,它们的哈希码(hash code) 应当相同。 Java 对于eqauls 方法和hashCode 方法是这样规定的:
1)如果两个对象相同(equals 方法返回true),那么它们的hashCode 值一定要相同; 2)如果两个对象的hashCode 相同,它们并不一定相同。当然,你未必要按照要求去做,但是(重写hashCode)如果你违背了上述原则就会发现在使用容器时,相同的对象可以出现在Set 集合中,同时增加新元素的效率会大大下降(对于使用哈希存储的系统,如果哈希码频繁的冲突将会造成存取性能急剧下降)。
关于equals方法,有以下内容必须满足
首先equals 方法必须满足 1)自反性( x.equals(x)必须返回true) 2)对称性( x.equals(y)返回true 时, y.equals(x)也必须返回true) 3)传递性(x.equals(y)和y.equals(z)都返回true 时, x.equals(z)也必须返回true) 4)一致性(当x和y引用的对象信息没有被修改时,多次调用x.equals(y)应该得到同样的返回值) ,而且对于任何非null值的引用x,x.equals(null)必须返回false。
实现高质量的equals 方法的诀窍包括
1)使用==操作符检查”参数是否为这个对象的引用”; 2)使用instanceof 操作符检查”参数是否为正确的类型”; 3) 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配; 4)重写equals方法后,问自己它是否满足自反性、对称性、传递性、一致性; 5) 重写equals 时总是要重写hashCode; 6) 不要将equals 方法参数中的Object 对象替换为其他的类型,在重写时不要忘掉@Override 注解。
15. String 和StringBuilder、StringBuffer 的区别【***】
Java平台提供了两种类型的字符串: String 和StringBuffer/StringBuilder,它们可以储存和操作字符串。 其中String 是只读字符串,也就意味着String 引用的字符串内容是不能被改变的。 而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder 是JDK 1.5 中引入的,它和StringBuffer 的方法完全相同, 区别在于StringBuffer因为被synchronized 修饰,是线程安全的而StringBuilder是线程不安全的(所有方面都没有被synchronized 修饰) 因为StringBuilder没有被synchronized同步修饰,所以StringBuilder
【面试题】什么情况下用+运算符进行字符串连接比调用StringBuffer/StringBuilder 对象的append 方法连接字符串性能更好?
首先要清楚以下两点: 1)String 对象的intern方法会得到字符串对象在常量池中对应的版本的引用(如果常量池中有一个字符串与String 对象的equals结果是true),如果常量池中没有对应的字符串,则该字符串将被添加到常量池中, 然后返回常量池中字符串的引用; 2)字符串的+操作其本质是创建了StringBuilder对象进行append操作,然后将拼接后的StringBuilder 对象用toString方法处理成String 对象。
16. 重载(Overload)和重写(Override)的区别。重载的方法能否根据返回类型进行区分?
方法的重载和重写都是实现多态的方式,区别在于重载实现的是编译时的多态性,而重写实现的是运行时的多态性。 所谓的重载是编译时多态性,就是根据实际的参数列表,在编译的时候就能够确定执行重载方法中的哪一个了。 所谓的重写是运行时多态性,就比如父类对象引用子类实例时,调用方法只有在运行时才知道到底执行了哪个方法。 重载发生在一个类中,同名的方法如果有不同的参数列表( 参数类型不同、参数个数不同或者二者都不同)则视为重载;(重载对返回类型没有特殊的要求,只考虑参数列表) 重写发生在子类与父类之间(需要继承),重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常( 里氏代换原则)。
17. JVM是什么?为什么 Java 被称作是“平台无关的编程语言”?JVM 加载class 文件的原理机制【***】
Java 虚拟机是一个可以执行 Java 字节码的虚拟机进程。Java 源文件被编译成能被 Java 虚拟机执行的字节码文件。
Java 被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java 虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。
Java代码在JVM中的执行流程JVM的类加载原理图JVM 中类的装载是由类加载器( ClassLoader)和它的子类来实现的, Java 中的类加载器是一个重要的Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。 由于Java 的跨平台性,经过编译的Java 源程序并不是一个可执行程序,而是一个或多个类class文件。当Java 程序需要使用某个类时,JVM 会确保这个类已经被加载、连接( 验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读入到内存中,通常是创建一个字节数组读入.class 文件,然后产生与所加载类对应的Class 对象。加载完成后,Class 对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备( 为静态变量分配内存并设置默认的初始值)和解析( 将符号引用替换为直接引用)三个步骤。最后JVM 对类进行初始化,包括: 1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类; 2)如果类中存在初始化语句, 就依次执行这些初始化语句。
类的加载是由类加载器完成的 类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器( System)和用户自定义类加载器(java.lang.ClassLoader 的子类) 。 从Java 2( JDK 1.2)开始, 类加载过程采取了父亲委托机制(PDM)。PDM 更好的保证了Java 平台的安全性,在该机制中, JVM 自带的Bootstrap 是根加载器, 其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向Java 程序提供对Bootstrap 的引用。
Bootstrap:一般用本地代码实现,负责加载JVM 基础核心类库(rt.jar);
Extension:从java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;
System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath 或者系统属性java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。
18. Java 中会存在内存泄漏吗?
理论上,Java因为有垃圾回收机制( GC)不会存在内存泄露问题( 这也是Java 被广泛使用于服务器端编程的一个重要原因); 然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被GC 回收,因此也会导致内存泄露的发生。 例如:Hibernate 的Session( 一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,如果不及时关闭(close)或清空( flush)一级缓存就可能导致内存泄露。
//实现了一个栈(先进后出(FILO))结构代码import java.util.Arrays;import java.util.EmptyStackException;public class MyStack { private T[] elements; private int size = 0; private static final int INIT_CAPACITY = 16; public MyStack() { elements = (T[]) new Object[INIT_CAPACITY]; } public void push(T elem) { ensureCapacity(); elements[size++] = elem; } public T pop() { if(size == 0) throw new EmptyStackException(); return elements[--size]; } private void ensureCapacity() { if(elements.length == size) { elements = Arrays.copyOf(elements, 2 * size + 1); } }}123456789101112131415161718192021222324252627282930
上述代码是实现了一个栈的先进后出(FILO)结构,但是其中的pop 方法却存在 内存泄露的问题 当我们用pop 方法弹出栈中的对象时,该对象不会被当作垃圾回收,即使使用栈的程序不再引用这些对象, 因为栈内部维护着对这些对象的过期引用(obsolete reference)。 在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无意识的对象保持。如果一个对象引用被无意识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其他对象,即使这样的对象只有少数几个,也可能会导致很多的对象被排除在垃圾回收之外,从而对性能造成重大影响,极端情况下会引发Disk Paging( 物理内存与硬盘的虚拟内存交换数据),甚至造成OutOfMemoryError。
19. 抽象方法(abstract)是否可以被静态的(static)、本地方法(native)和同步(synchronized)修饰。
答案是都不能。 1)抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。 2)本地方法是由本地代码(如C代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。 3)synchronized 和方法的实现细节有关,抽象方法不涉及实现细节, 因此也是相互矛盾的。
20. 静态变量和实例变量的区别
静态变量是被static修饰符修饰的变量,也称为类变量, 它属于类(与类同生死),不属于类的任何一个对象,一个类不管创建多少个对象, 静态变量在内存中有且仅有一个拷贝;静态变量可以实现让多个对象共享内存。 实例变量必须依存于某一实例,需要先创建对象然后通过对象才能访问到它。 (在Java 开发中, 上下文类和工具类中通常会有大量的静态成员。)
21. 实现对象克隆的2种方法【***】
对象是引用数据类型,对象的赋值仅仅是吧对象的引用赋值给另一个对象,他们的堆空间是相同的。 克隆就是需要有一个完全相同的对象,而且两者之间完全互不影响,克隆对象和原对象是两个完全对立的对象。(java.lang.Object类的clone()方法,对象克隆就是对象的复制,即完整的复制出一个对象。)
1)实现Cloneable接口并重写Object 类中的clone()方法;(浅克隆对象的克隆默认是浅度克隆,也就是包含在对象内部的对象是不会被克隆的),【通过clone()实现深度克隆:让克隆对象所包含的类也实现clone功能(实现cloneable接口,重载clone()方法)】 2)实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆
基于序列化和反序列化(Serializable)实现的克隆不仅仅是深度克隆, 更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object 类的clone 方法克隆对象。让问题在编译的时候暴露出来总是好过把问题留到运行时。
22. Java 中如何实现序列化Serializable,有什么意义?
Serializable序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。 序列化是为了解决对象流读写操作时可能引发的问题( 如果不进行序列化可能会存在数据乱序的问题) 。 要实现序列化,需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态); 如果需要反序列化则可以用一个输入流建立对象输入流,然后通过readObject 方法从流中读取对象。序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆(上面所说的)。
23. GC垃圾回收器,为什么需要它。【***】
java中由JVM的垃圾回收管理器(GC)来负责回收不需要再使用的堆内存和栈内存。GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的。 JVM的垃圾回收采用动态存储管理技术,它能够自己检测内存的使用情况,自动地释放不再被程序引用的对象,按照特定的垃圾收集算法来实现内存资源自动回收功能。 JVM垃圾回收管理器处理释放没用的对象外,还可以清除内存记录的碎片(因为创建对象和垃圾收集齐全不断释放对其对象所占用的空间,因此内存会出现碎片)。碎片整理将所占用的堆内存移动到堆的一端,JVM将整理出的内存分配给新的对象。 JVM垃圾回收器有一个潜在的缺点 : 增加了系统的开销,影响了程序的性能。因为JVM必须追踪运行程序中有用的对象,到最终释放没用的对象,这个过程需要花费处理器的时间。 一个对象如果不再被任何栈内存所引用,则该对象成为垃圾对象。垃圾收集器对垃圾对象的收集时间是不确定的,也可以直接使用System.gc()方法Runtime.getRuntime().gc()回收垃圾对象,但是这种强制执行垃圾回收程序对系统性能会产生负面影响。(JVM 可以屏蔽掉显示的垃圾回收调用,就是显示调用方法,JVM的GC会自动进行管理)虽然你可以调用System.gc() 或者Runtime.gc(),但是没有办法保证GC的执行。
垃圾回收可以有效的防止内存泄露, 有效的使用可以使用的内存。垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回 收器对某个对象或所有对象进行垃圾回收。在Java 诞生初期,垃圾回收是Java 最大的亮点之一,因为服务器端的编程需要有效的防止内存泄露问题,然而时过 境迁,如今Java 的垃圾回收机制已经成为被诟病的东西。移动智能终端用户通常 觉得iOS 的系统比Android 系统有更好的用户体验,其中一个深层次的原因就在 于Android 系统中垃圾回收的不可预知性。
补充:垃圾回收机制有很多种,包括:分代复制垃圾回收、标记垃圾回收、增量 垃圾回收等方式。标准的Java 进程既有栈又有堆。栈保存了原始型局部变量,堆 保存了要创建的对象。Java 平台对堆内存回收和再利用的基本算法被称为标记和 清除,但是Java 对其进行了改进,采用“ 分代式垃圾收集”。这种方法会跟Java 对象的生命周期将堆内存划分为不同的区域, 在垃圾收集过程中,可能会将对象 移动到不同区域:
24. String和基本数据类型之间的转换
1)将字符串转换为基本数据类型 调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)即可返回相应基本类型; 2)将基本数据类型转换为字符串 一种方法是将基本数据类型与空字符串 "" 通过 + 连接即可获得其所对应的字符串; 另一种方法是调用String 类中的valueOf()方法返回相应字符串。String.valueOf()
25. Java和JavaScript区别
Java和JavaScript介绍
JavaScript 与Java 是两个公司开发的不同的两个产品。Java 是原Sun Microsystems 公司推出的面向对象的程序设计语言,特别适合于互联网应用程序开发;而JavaScript 是Netscape 公司的产品,为了扩展Netscape浏览器的功能而开发的一种可以嵌入Web 页面中运行的基于对象和事件驱动的解释性语言。 JavaScript 的前身是LiveScript;而Java 的前身是Oak 语言。
Java和JavaScript的区别
1)Java是面向对象的而JavaScript是基于对象的。 Java 是一种真正的面向对象的语言,即使是开发简单的程序,必须设计对象;JavaScript 是种脚本语言,它可以用来制作与网络无关的,与用户交互作用的复杂软件。它是一种基于对象(Object-Based)和事件驱动(Event-Driven)的编程语言,因而它本身提供了非常丰富的内部对象供设计人员使用。 2)Java是编译运行,而JavaScript是解释执行的。 Java 的源代码在执行之前,必须经过编译。JavaScript 是一种解释性编程语言,其源代码不需经过编译,由浏览器解释执行。(目前的浏览器几乎都使用了JIT(即时编译)技术来提升JavaScript 的运行效率 3)Java变量必须先声明后使用,JavaScript是弱类型语言,直接使用。 Java采用强类型变量检查,即所有变量在编译之前必须作声明;JavaScript 中变量是弱类型的,甚至在使用变量前可以不作声明,JavaScript 的解释器在运行时检查推断其数据类型。 4)Java是静态语言,JavaScript是动态语言。
26. try{}里有一个return 语句,finally是否执行,执行位置。
try{}里有一个return语句返回,那么紧跟在这个try后的finally{}里的代码也是会被执行的,是在方法返回调用前(return前执行) 如果存在finally代码块,try中的return语句不会立马返回调用者,而是记录下返回值待finally代码块执行完毕之后再向调用者返回其值(执行return)。 在finally中改变返回值的做法是不好的,如果在finally 中修改了返回值,就会返回修改后的值。在finally 中返回或者修改返回值会对程序造成很大的困扰,C#中直接用编译错误的方式来阻止程序员干这种龌龊的事情,Java 中也可以通过提升编译器的语法检查级别来产生警告或错误(idea、eclipse也能自己设置)
27. 运行时异常和受检查异常
异常表示程序运行过程中可能出现的非正常状态。 运行时异常:表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误,只要程序设计得没有问题通常就不会发生。(在运行是抛出) 受检查异常跟程序运行的上下文环境有关,即使程序设计无误, 仍然可能因使用的问题而引发。Java 编译器要求方法必须声明抛出可能发生的受检查异常,但是并不要求必须声明抛出未被捕获的运行时异常。 常见的运行时异常:
ArithmeticException(算术异常) ClassCastException (类转换异常) IllegalArgumentException (非法参数异常) IndexOutOfBoundsException (下标越界异常) NullPointerException (空指针异常) SecurityException (安全异常)
28. final、finally、finalize 的区别
final关键字
一个基本数据类型声明为final,就只能进行一次赋值(初始化),编译器会自动检查代码,如果需要修改final的初始化,就会编译报错。final声明的引用数据类型,当前引用是不能改变的,但是可以改变引用指向的内存空间的值。final一般和static搭配使用作为常量。 final关键字的三种用法: 1)修饰类:表示该类不能被继承; 2)修饰方法:表示方法不能被重写; 3)修饰变量: 表示变量只能一次赋值以后值不能被修改(常量)(final修饰成员变量必须在声明时初始化或者再构造器中初始化,否则报编译错误,而且final修饰的变量通常都是常量,常量名全部大写) 如果一个类被声明为final,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract 是反义词。将变量声明为final,可以保证它们在使用中不被改变,被声明为final 的变量必须在声明时给定初值,而在以后的引用中只能读取不可修改。被声明为final 的方法也同样只能使用,不能在子类中被重写。
final优势: 1)final关键字可以提高性能,JVM和Java应用都会缓存final变量。 2)final变量可以在安全的多线程环境下进行资源共享,而不需要额外的同步开销。
finally
通常放在try…catch…的后面构造总是执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM 不关闭都能执行,可以将释放外部资源的代码写在finally 块中(关闭数据库连接、释放资源)。
finalize
Object 类中定义的方法,Java 中允许使用finalize()方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize()方法可以整理系统资源或者执行其他清理工作。
本文作者:strive_day