Java语言特点

来源:互联网 发布:linux怎样切换到root 编辑:程序博客网 时间:2024/06/18 10:00

* 平台无关性

java语言可以做到“一次编译、到处运行”。体现了其平台无关性的特点,那么这个是怎么做到的呢?

其实,java是一门解释性的语言,当编译java文件时,编译器会将文件编译成class字节码文件,只要响应环境中有JVM就能运行这个代码了(class字节码文件由JVM解释、执行)。

从中,我们也可以看到java 运行时的层次性,java源码 -> java class -> JVM -> 操作系统。


* 丰富的类库

java语言本身就内置许多类库,在开源社区还有诸多优秀的java类库。极大的缩减了Java开发人员的工作量,降低了工作复杂度,加快了项目进度。

并且,jar包形式的交付件保证了模块间的独立和解耦。


* Java程序初始化的顺序

Java程序的初始化一般遵循三个原则:

        1、静态对象(变量)优先于非静态对象(变量)初始化

2、父类优先于之类进行初始化

3、按照成员变量的定义顺序进行初始化,即使变量散布于方法定义之中,它们依然在任何方法(包括构造函数)被调用之前先初始化。


实例化一个Java对象时,代码内部初始化顺序如下(优先级从高->低):

父类静态变量 -> 父类静态代码块 -> 之类静态变量 -> 之类静态代码块 -> 父类非静态变量 -> 父类非静态代码块 -> 父类构造函数 -> 之类非静态变量 -> 之类非静态代码块

-> 之类构造函数


* Java语言中为什么要保留基础数据类型

基本类型一直都是Java语言的一部分,主要是基于程序性能的考量,基本类型定义的变量是存放在栈中的,比如:int i = 5;而Integer j = newInteger(10);j只是一个

对象的应用,存放在栈中,而实际的数值10是存放在堆里的,堆的读写速度远不及栈。

再有就是基本类型定义的变量创建和销毁很快,而类定义的变量还要JVM去销毁。也是性能的考量。


  一般建议使用包装类,虽然会牺牲一些转换效率,但可以避免持久化数据时产生的一些异常。


* Java 为什么不支持多继承

类定义属性和方法,用于描述某一类事物的抽象

而接口定义的是行为,并不限于任何具体的意向。


按照逻辑上来说,单继承更明确一个之类就应该是其父类代表的事物中的某个更具体、细分的类别,而不应该是既是这个东西又是那个东西。

而从实用角度来说单继承更易读、易维护、语义清晰、逻辑清楚。

而接口则不同,接口只定义一些公共行为。


类对接口的implements称作 实现 ,不叫继承


在Java中非要使用类似多继承的特性,在Java 8后可以使用“extends + implements(+ default 函数)  + 组合” 来做。


* abstract class 和 interface 有什么不同

1、abstract class 里的抽象方法旨在设计的功能是跟当前类所代表的事物强相关的。

               interface里的方法更抽象,更通用。

        2、使用时,abstract class 的设计理念是 "is-a"的关系,而interface是“has-a”的关系。

3、abstract class里可以有功能性的函数体,interface没有(但java 8开始也有了)

4、abstract class里的成员变量跟普通class用法一致,而interface里的成员变量肯定是public static final的静态变量,而且必须是赋值了的。


不过,很多时候可以用interface替换abstract class。


* Object.hashCode

Object中的hashCode用于生成对象的哈希值。用于相关的数据结构和算法。

hashCode的常规协定:

1、在Java应用程序执行期间,在同一个对象上多次调用hashCode方法,必须一致的返回相同的整数,前提是equals比较中所用的信息没有被修改。

2、而一个应用程序重启后再运行,所返回的hashCode可以不一致。

3、如果equals方法返回的两个对象是相等的,那么hashCode值必须一致。

      而如果equals方法返回的是不相等,这两对象的hashCode可能不一样,可能一样。

4、当equals被Override时,通常都需要连带Override hashCode方法。以保持hashCode方法的常规协定。


* Java 中String,StringBuffer,StringBuilder的区别

 可变与不可变:

String 使用 private final char value[] 存储字符串的,即String存储的字符串是不可变的。

StringBuffer和StringBuilder存储的字符串是可变的。

是否多线程安全:

String 对象不可变,线程安全。

StringBuffer对方法加了同步锁,线程安全。

StringBuilder没有对方法加同步锁,线程不安全。


字符串拼接性能:

性能从高到低:StringBuilder -> StringBuffer -> String


* Java中有哪几种流

字节流和字符流


Java中所有的数据都是以流的形式保存的,流中保存的实际上全都是字节文件。即保存在文件里的都是字节信息。

Java中字节流输入\输出的抽象基类是:InputStream \ OutputStream。字符流的抽象基类:Reader \ Writer


字符流是由JVM在内存中将字节(字符集)转化为2个字节的Unicode字符为单位的字符而成的。


字符流(String)与字节流(byte[])互相转化时,涉及到对应的字符集,如果相互转化时未明确指定字符集类型,Java就会用操作系统的lang进行处理。


两者差异:

* 字节流在操作时候本身不会用到缓存区(内存),是与文件本身直接操作的。而字符流在操作的时候是使用到缓冲区的。

* 字节流在操作文件时,及时不关闭资源(close方法),文件也能输出。但字符流不使用close的话,不会输出任何内容(除非flush强制刷新)


* Java NIO

特点1:不阻塞。                              阻塞IO在read,accept等方法时,当未满足条件时线程是阻塞的。

特点2:单线程多数据流。               阻塞IO在使用时,若有多个数据流,就需要创建多个线程。并频繁在线程间切换CPU,经常是无意义的CPU切换。

特点3:NIO是缓冲的。                    标准java数据字节流,是不缓冲的。


* Java序列化

必须实现序列化的情况

1、需要通过网络发送对象,或对象的状态需要被持久化到数据库或文件中。

2、序列化能实现深复制,即可以复制引用的对象。


serialVersionUID

在序列化和反序列化过程中,serialVersionUID起着非常重要的作用。在反序列化过程中,通过serialVersionUID来判定类的兼容性,如果待序列化的对象与目标对象的serialVersionUID不一致,那么再反序列化时就会抛出InvalidClassException。

使用自定的serialVersionUID有3个优点:

1、提高程序的运行效率。如果在类中未显示的声明serialVersionUID,那么在序列化时会通过计算得到一个serialVersionUID值。通过显示声明serialVersionUID的方式省去了计算的过程。因此提高了运行效率。

2、提高程序不同平台上的兼容性。不显示声明serialVersionUID时,JVM会自动生成一个,这个值跟编译器有关。因为各个平台的编译器在计算serialVersionUID时可能会采用不同的计算方式。

3、增强程序各个版本的兼容性。通过显示声明的serialVersionUID在类发生变化时,由于serialVersionUID还是同一个,有机会做到兼容。而不显示声明serialVersionUID的类,由于编译器是根据类的内容计算serialVersionUID的,修改后的类的serialVersionUID完全有可能跟修改前的不一样,所以会不兼容。


序列化与外部序列化:

两者的主要区别是序列化时内置的API,只需要实现Serializable接口。而外部序列化时,Externalizable接口中的读写方法必须由开发人员自己实现。

Externalizable难度大,但因此把控制权交给了开发人员,开发人员可能根据场景实现更优的序列化读写方法。


* Java中堆和栈的区别

1、栈用于存放基础数据类型和对象的引用变量(局部变量)。堆主要用于存放new出来的对象。


* Lambda表达式中的Stream()和ParallelStream

Stream 表示集合处理任务串行执行

ParallelStream 表示集合处理任务并行执行(效率高很多)。

Stream和ParallelStream的选择:

1、是否需要并行?

2、任务之间是否是独立的?是否会引起任何竞态条件?

3、结果是否取决于任务的调用顺序?


* Java中的高阶函数

在Java 8以前,重用是建立在对象和类型系统之上。而Java 8中则将重用的概念更进一步,使用函数也能够实现代码的重用。所谓高阶函数,不要被其名字唬住了,实际上很简单:
* 将函数作为参数传入到另外一个函数中
* 函数的返回值可以是函数类型
* 在函数中创建另一个函数

如果一个方法接受一个函数式接口作为参数,那么我们可以传入以下类型作为参数:
* 匿名内部类(Anonymous Inner Class)
* Lambda表达式
* 方法或者构造器的引用(Method or Constructor Reference)

例:

interface Function{int execute(int x);}

private int[] array;public List(int[] array){ this.array = array;}public void map(Function func){for(int i = 0, len = this.array.length; i < len; i++){this.array[i] = func.execute(this.array[i]); }}
实际使用中,会让map接收一个实现了Function的类的实例。

public static void main(String[] args){List list = new List(new int[]{1, 2, 3, 4, 5}); list.print();list.map(new Function(){Public int execute(int x){return x * 2;}}),list.print();list.map(new Function(){      //匿名类方式Public int execute(int x){return x * 3;}}),list.print();    }

高阶函数代码重用性高


* java中的printf、print、println区别

printf      主要是继承了C语言的printf的一些特性,可以进行格式化输出
print       就是一般的标准输出,但是不换行
println和print基本没什么差别,就是最后会换行

* Java中内存泄露出现的场景








0 0