Java零散记录

Posted by 夏敏的博客 on October 8, 2016
  • String能被继承吗

    不能,因为String是这样定义的:public final class String extends Object,里边有final关键字,所以不能被继承。

  • 什么样的类不能被继承?

     一,在Java中,只要是被定义为final的类,也可以说是被final修饰的类,就是不能被继承的。
     二,final是java中的一个关键字,可以用来修饰变量、方法和类。用关键词final修饰的域成为最终域。用关键词final修饰的变量一旦赋值,就不能改变,也称为修饰的标识为常量。如果一个类的域被关键字final所修饰,它的取值在程序的整个执行过程中将不会改变。
     三,假如说整个类都是final,就表明自己不希望从这个类继承,或者不答应其他任何人采取这种操作。换言之,出于这样或那样的原因,我们的类肯定不需要进行任何改变;或者出于安全方面的理由,我们不希望进行子类化(子类处理)。

  • String/StringBuffer/StringBuilder区别

    Java 平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer和StringBulder类表示的字符串对象可以直接进行修改。StringBuilder是JDK1.5引入的,它和StringBuffer的方法完全相同,区别在于它是单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer略高。
    总结:
    String是不可变对象,每次改变都生成新对象
    StringBuffer 不生成新对象,字符串经常改变情况下,推荐使用
    StringBuilder 线程不安全,效率高

  • 静态内部类/匿名内部类/内部类

    静态内部类:使用static修饰的内部类
    匿名内部类:使用new生成的内部类
    内部类持有外部类的引用,因为内部类的产生依赖于外部类,持有的引用是类名.this。

  • switch是否能作用在byte上,是否能作用在long上,是否能作用在String上?

    switch支持使用byte类型,不支持long类型,String支持在java1.7引入

  • 接口的意义

    规范、扩展、回调

  • 覆盖equals时总要覆盖hashCode方法

    因为如果对象根据equals方法是比较相等的,那么调这两个对象中的任意一个对象的hashCode方法都必须产生相同的效果

  • 多态的好处

    可替换性、可扩充、接口性、灵活、简化

  • 线程阻塞

  • String转int

    int i = Integer.parseInt([String])

  • 格式化

        float a = 0.5534
        DecimalFormat fnum = new DecimalFormat("##0.0");
        String dd = fnum.format() + "%";

    结果为0.5

  • JVM参数

-Xmx128m:设置JVM最大可用内存为128M。

-Xms128m:设置JVM最小内存为128m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

-Xmn2g:设置年轻代大小为2G。整个堆大小=年轻代大小 + 年老代大小 + 持久代大小。持久代一般固定大小为64m,所以增大年轻代后,将会减小年老代大小。此值对系统性能影响较大,Sun官方推荐配置为整个堆的3/8。

-Xss128k:设置每个线程的堆栈大小。 JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。根据应用的线程所需内存大小进行调整。在相同物理内存下,减小这个值能生成更 多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。


  • Hashcode的作用。

http://c610367182.iteye.com/blog/1930676

以Java.lang.Object来理解,JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次做Object的比较或者取这个对象的时候,它会根据对象的hashcode再从Hash表中取这个对象。这样做的目的是提高取对象的效率。具体过程是这样:

  1. new Object(),JVM根据这个对象的Hashcode值,放入到对应的Hash表对应的Key上,如果不同的对象确产生了相同的hash值,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同hashcode的对象放到这个单链表上去,串在一起。

  2. 比较两个对象的时候,首先根据他们的hashcode去hash表中找他的对象,当两个对象的hashcode相同,那么就是说他们这两个对象放在Hash表中的同一个key上,那么他们一定在这个key上的链表上。那么此时就只能根据Object的equal方法来比较这个对象是否equal。当两个对象的hashcode不同的话,肯定他们不能equal.


  • try catch finally,try里有return,finally还执行么?

会执行,在方法 返回调用者前执行。Java允许在finally中改变返回值的做法是不好的,因为如果存在finally代码块,try中的return语句不会立马返回调用者,而是纪录下返回值待finally代码块执行完毕之后再向调用者返回其值,然后如果在finally中修改了返回值,这会对程序造成很大的困扰,C#中就从语法规定不能做这样的事。


  • Excption与Error区别

Error表示系统级的错误和程序不必处理的异常,是恢复不是不可能但很困难的情况下的一种严重问题;比如内存溢出,不可能指望程序能处理这样的状况;Exception表示需要捕捉或者需要程序进行处理的异常,是一种设计或实现问题;也就是说,它表示如果程序运行正常,从不会发生的情况。


  • final关键字的作用

相信对于final的用法,大多数人都可以随口说出三句话:

1、被final修饰的类不可以被继承

2、被final修饰的方法不可以被重写

3、被final修饰的变量不可以被改变

被final修饰的变量,不管变量是在是哪种变量,切记不可变的是变量的引用而非引用指向对象的内容。另外,本文中关于final的作用还有两点没有讲到:

1、被final修饰的方法,JVM会尝试为之寻求内联,这对于提升Java的效率是非常重要的。因此,假如能确定方法不会被继承,那么尽量将方法定义为final的。

2、被final修饰的常量,在编译阶段会存入调用类的常量池中。


  • 可变长度数组的原理

当元素超出数组长度,会产生一个新数组,将原数组的数据复制到新数组中,再将新的元素添加到新数组中。
ArrayList:是按照原数组的50%延长。构造一个初始容量为 10 的空列表。
Vector:是按照原数组的100%延长。


  • 哈希表的原理:

1,对对象元素中的关键字(对象中的特有数据),进行哈希算法的运算,并得出一个具体的算法值,这个值 称为哈希值。
2,哈希值就是这个元素的位置。
3,如果哈希值出现冲突,再次判断这个关键字对应的对象是否相同。如果对象相同,就不存储,因为元素重复。如果对象不同,就存储,在原来对象的哈希值基础 +1顺延。
4,存储哈希值的结构,我们称为哈希表。
5,既然哈希表是根据哈希值存储的,为了提高效率,最好保证对象的关键字是唯一的。 这样可以尽量少的判断关键字对应的对象是否相同,提高了哈希表的操作效率。

对于ArrayList集合,判断元素是否存在,或者删元素底层依据都是equals方法。
对于HashSet集合,判断元素是否存在,或者删除元素,底层依据的是hashCode方法和equals方法。


  • StringBuffer 和 StringBuilder

JDK1.5出现StringBuiler;构造一个其中不带字符的字符串生成器,初始容量为 16 个字符。该类被设计用作 StringBuffer的一个简易替换,用在字符串缓冲区被单个线程使用的时候(这种情况很普遍)。
方法和StringBuffer一样;

StringBuffer 和 StringBuilder 的区别:
StringBuffer线程安全。
StringBuilder线程不安全。

单线程操作,使用StringBuilder 效率高。
多线程操作,使用StringBuffer 安全。


  • 什么情况下finally的代码不执行

System.exit(0); //退出jvm,只有这种情况finally不执行。


  • 当同步函数被static修饰时,这时的同步用的是哪个锁呢?

    静态函数在加载时所属于类,这时有可能还没有该类产生的对象,但是该类的字节码文件加载进内存就已经被封装成了对象,这个对象就是该类的字节码文件对象。
    所以静态加载时,只有一个对象存在,那么静态同步函数就使用的这个对象。 这个对象就是 类名.class


  • Map集合存储和Collection有着很大不同:

    Collection一次存一个元素;Map一次存一对元素。
    Collection是单列集合;Map是双列集合。
    Map中的存储的一对元素:一个是键,一个是值,键与值之间有对应(映射)关系。
    特点:要保证map集合中键的唯一性。

  • 想要获取map中的所有元素:

    原理:map中是没有迭代器的,collection具备迭代器,只要将map集合转成Set集合,可以使用迭代器了。之所以转成set,是因为map集合具备着键的唯一性,其实set集合就来自于map,set集合底层其实用的就是map的方法。

    把map集合转成set的方法:
    Set keySet();
    Set entrySet();//取的是键和值的映射关系。


  • 静态方法可以被重写么?

能!静态方法是和类绑定的,虽然能复写,但是可能得到的不是你想要的效果。

如果从重写方法会有什么特点来看,我们是不能重写静态方法的。虽然就算你重写静态方法,编译器也不会报错。也就是说,如果你试图重写静态方法,Java不会阻止你这么做,但你却得不到预期的结果(重写仅对非静态方法有用)。重写指的是根据运行时对象的类型来决定调用哪个方法,而不是根据编译时的类型。让我们猜一猜为什么静态方法是比较特殊的?因为它们是类的方法,所以它们在编译阶段就使用编译出来的类型进行绑定了。使用对象引用来访问静态方法只是Java设计者给程序员的自由。我们应该直接使用类名来访问静态方法,而不要使用对象引用来访问。


  • Person p = new Person();创建一个对象都在内存中做了什么事情?

1:先将硬盘上指定位置的Person.class文件加载进内存。
2:执行main方法时,在栈内存中开辟了main方法的空间(压栈-进栈),然后在main方法的栈区分配了一个变量p。
3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new
4:在该实体空间中进行属性的空间分配,并进行了默认初始化。
5:对空间中的属性进行显示初始化。
6:进行实体的构造代码块初始化。
7:调用该实体对应的构造函数,进行构造函数初始化。()
8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)


  • 构造函数为什么要super第一行,

    用this调用构造函数,必须定义在构造函数的第一行。因为构造函数是用于初始化的,所以初始化动作一定要执行。否则编译失败。


  • java.lang.Object

Object:所有类的直接或者间接父类,Java认为所有的对象都具备一些基本的共性内容,这些内容可以不断的向上抽取,最终就抽取到了一个最顶层的类中的,该类中定义的就是所有对象都具备的功能。

具体方法:
1,boolean equals(Object obj):用于比较两个对象是否相等,其实内部比较的就是两个对象地址。如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果
2,String toString()
3,Class getClass():获取任意对象运行时的所属字节码文件对象。
4,int hashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表的性能。将该对象的内部地址转换成一个整数来实现的。

通常equals,toString,hashCode,在应用中都会被复写,建立具体对象的特有的内容。


  • 小小面试题:

          //1
          new Object(){
              void show(){
                  System.out.println("show run");				
              }
          }.show();									//写法和编译都没问题
          //2
          Object obj = new Object(){
              void show(){
                  System.out.println("show run");
              }
          };
          obj.show();								//写法正确,编译会报错
    

    1和2的写法正确吗?有区别吗?说出原因。
    写法是正确,1和2都是在通过匿名内部类建立一个Object类的子类对象。
    区别:
    第一个可是编译通过,并运行。
    第二个编译失败,因为匿名内部类是一个子类对象,当用Object的obj引用指向时,就被提升为了Object类型,而编译时检查Object类中是否有show方法,所以编译失败。


  • 在Serializable类中的serialVersionUID的作用

    private static final long serialVersionUID = -1672970955045193907L;

SUID不是一个对象的哈希值(翻译错了,公司一个牛逼同事提醒了!),是源类的哈希值。如果类更新,例如域的改变,SUID会变化,这里有4个步骤:
1.忽略SUID,相当于运行期间类的版本上的序列化和反序列上面没有差异。
2.写一个默认的SUID,这就好像线程头部。告诉JVM所有版本中有着同样SUID的都是同一个版本。
3.复制之前版本类的SUID。运行期间这个版本和之前版本是一样的版本。
4.使用类每个版本生成的SUID。如果SUID与新版本的类不同,那么运行期间两个版本是不同的,并且老版本类序列化后的实例并不可以反序列成新的类的实例。

实序列化的作用是能转化成Byte流,然后又能反序列化成原始的类。能在网络进行传输,也可以保存在磁盘中,有了SUID之后,那么如果序列化的类已经保存了在本地中,中途你更改了类后,SUID变了,那么反序列化的时候就不会变成原始的类了,还会抛异常,主要就是用于版本控制。

  • 序列化的原理

    Java序列化就是将一个对象转化为一个二进制表示的字节数组,通过保存或则转移这些二进制数组达到持久化的目的。

虽然Java的序列化能够保证对象状态的持久保存,但是遇到一些对象结构复杂的情况还是比较难处理的,下面是对一些复杂情况的总结:

当父类实现了Serializable接口的时候,所有的子类都能序列化 子类实现了Serializable接口,父类没有,父类中的属性不能被序列化(不报错,但是数据会丢失) 如果序列化的属性是对象,对象必须也能序列化,否则会报错 反序列化的时候,如果对象的属性有修改或则删减,修改的部分属性会丢失,但是不会报错 在反序列化的时候serialVersionUID被修改的话,会反序列化失败 在存Java环境下使用Java的序列化机制会支持的很好,但是在多语言环境下需要考虑别的序列化机制,比如xml,json,或则protobuf

假定一个User类,它的对象需要序列化,可以有如下三种方法:

(1)若User类仅仅实现了Serializable接口,则可以按照以下方式进行序列化和反序列化

ObjectOutputStream采用默认的序列化方式,对User对象的非transient的实例变量进行序列化。 ObjcetInputStream采用默认的反序列化方式,对对User对象的非transient的实例变量进行反序列化。

(2)若User类仅仅实现了Serializable接口,并且还定义了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),则采用以下方式进行序列化与反序列化。

ObjectOutputStream调用User对象的writeObject(ObjectOutputStream out)的方法进行序列化。 ObjectInputStream会调用User对象的readObject(ObjectInputStream in)的方法进行反序列化。

(3)若User类实现了Externalnalizable接口,且User类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化。

ObjectOutputStream调用User对象的writeExternal(ObjectOutput out))的方法进行序列化。 ObjectInputStream会调用User对象的readExternal(ObjectInput in)的方法进行反序列化。


  • 如何主动请求GC

1.System.gc()方法
调用System.gc()可以主动请求垃圾回收机制,但也仅仅是一个请求。JVM接受这个消息后,并不是立即做垃圾回收,而只是对几个垃圾回收算法做了加权,使垃圾回收操作容易发生,或提早发生,或回收较多而已。
良好的编程习惯是:GC调用不宜过多,保持代码健壮(记得将不用的变量置为null、资源使用完后释放),让虚拟机去管理内存。因为System.gc()方法会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。

2.finalize()方法
一旦垃圾回收器准备好释放对象占用的存储空间,首先会去调用finalize()方法进行一些必要的清理工作。只有到下一次再进行垃圾回收动作的时候,才会真正释放这个对象所占用的内存空间(所以调用finalize()并不一点会GC)。
另外finalize()函数是在垃圾回收器准备释放对象占用的存储空间的时候被调用的,绝对不能直接调用finalize(),所以应尽量避免用它


  • Java接口中定义常量时不需要写public static final

    接口中的属性的默认是public static final 、方法是public abstract


  • Java中也有多继承

    java的类无法进行多继承, 但是接口可以


  • Java枚举类(enum)中, 即使是内部类,也无法使用外部类的方法

    因为Java 枚举类默认为static类, 在我们写内部枚举类时若前面加上staic 会报冗余代码警告的.因此不能调用外部类的方法
    需要注意的是enum定义的类默认继承的是java.lang.Enum类而不是Object类。


  • Class下的newInstance()和new有什么区别?

    首先,newInstance( )是一个方法,而new是一个关键字,其次,Class下的newInstance()的使用有局限,因为它生成对象只能调用无参的构造函数,而使用new关键字生成对象没有这个限制。 好,到此为止,我们总结如下:
    Class.forName(““)返回的是类
    Class.forName(“”).newInstance()返回的是object


  • Integer的自动拆装箱的陷阱(整型数-128到127的值比较问题)

  Integer a1 = 127;
  Integer b1 = 127;
  if(a1==b1){
  	System.out.println("相等");
  }else{
  	System.out.println("不等");
  }

  Integer a = 128;
  Integer b = 128;
  if(a==b){
  	System.out.println("相等");
  }else{
  	System.out.println("不等");
  }

  运行结果是
相等
不等

JVM会自动维护八种基本类型的常量池,int常量池中初始化-128~127的范围,所以当为Integer i=127时,在自动装箱过程中是取自常量池中的数值,而当Integer i=128时,128不在常量池范围内,所以在自动装箱过程中需new 128,所以地址不一样。


  • Entity与Field

Entity是一个实体类, 可以通过

Field[] field = Class.getDeclaredFields();来获取到一个实体类的所有Field.

接下来可以通过Field的类的各个方法获取实体类里面的注解,成员变量名称等.

getType(): 获取属性声明时类型对象(返回class对象)
getGenericType() : 返回属性声的Type类型
getName() : 获取属性声明时名字
getAnnotations() : 获得这个属性上所有的注释
getModifiers() : 获取属性的修饰
isEnumConstant() : 判断这个属性是否是枚举类
isSynthetic() : 判断这个属性是否是 复合类
get(Object obj) : 取得obj对象这个Field上的值
set(Object obj, Object value) : 向obj对象的这个Field设置新值value

  • <font color=#C71585>Java IO流的问题</font>

    java.io使用了适配器模式装饰模式等设计模式来解决字符流的套接和输入输出问题。
    字节流只能一次处理一个字节,为了更方便的操作数据,便加入了套接流。

缓冲流为什么比普通的文件字节流效率高?
不带缓冲的操作,每读一个字节就要写入一个字节。
由于涉及磁盘的IO操作相比内存的操作要慢很多,所以不带缓冲的流效率很低。
带缓冲的流,可以一次读很多字节,但不向磁盘中写入,只是先放到内存里。
等凑够了缓冲区大小的时候一次性写入磁盘,这种方式可以减少磁盘操作次数,速度就会提高很多!

最底层 InputStream和OutputStream 基于字节流,没有缓存机制,一般用BufferInputStream和BufferOutputStream进行封装后使用。

BufferInputStream的read方法是阻塞线程的,BufferInputStream.read(buf) 会将输入流内的全部读入buf之后才返回。
BufferOutputStream.write(buf);会将buf中的内容输出到输出流,但是记得要flush;

PrintStream 和PrintWriter相似 可以自动刷新 只不过是对于字节流而言。
字节流一般用于传送二进制文件之类 至于字符流常常用reader进行包装后使用。
最常用的有BufferInputStreamReader和PrintWrinter ,BufferInputStreamReader的readline方法很实用 遇到\r\d会自动flush。

PrintWrinter 只要在构造函数中设置了刷新属性为true则其println方法可以自动刷新不用flush。
FilterInputStream和FilterOutputStream:过滤流,buffer流和data流均继承于此。
对于buffer流,只有缓冲区满时,才会将数据真正到输出流,但可以使用flush()方法人为的将尚未填满的缓冲区中的数据送出;不能确定文件的编码方式,在网络上难以应用。

实际中用的最多的还是:Data流可以让发送方和接收方按照同一的编码去处理。

DataInputStream和DataOutputStream:可以接受一行的数据,可以对其进行编码,也可以是套接流,可以套接文件字节流和网络字节流,读写的顺序要一致,否则读取会出现异常。
DataInputStream 是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。应用程序可以使用DataOutputStream(数据输出流)写入由DataInputStream(数据输入流)读取的数据。


  • java float类型比较细节 :

举个例子 27.2 == 272/10.0; 求输出结果 ;

一般人第一眼看到肯定觉得很简单 , 返回就是true嘛 ; 当告知你答案是false ,你依然不解,急着在编译器上实践,结果发现真的是false ,为什么呢?

答案是 : java中直接声明 27.2 默认是为double类型 ; 而272/10.0 返回时一个float类型 ; double == float 自然返回false ;
可换成这个写法 27.2f == 272/10.0 ; 直接声明一个float类型 ;


  • 使用 Arrays.copyOfRange(data, i, len); 可以拷贝数组里面的某一部分


  • DataInputStream的read()系列方法

    read() 读取一个字节,常规值0-255
    read(byte []) 方法, 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中,以整数形式返回实际读取的字节数。

读到结束,或者异常,返回值 -1

在有输入数据可用的时候,检测到文件末尾或者抛出异常前,此类方法一直是阻塞状态,但是当比如文件尾或者已经抛出异常的情况下,便不会阻塞,因此需要注意结束读取。


  • 3DES加密

    3DES是三重数据加密,且可以逆推的一种算法方案。但由于3DES的算法是公开的,所以算法本身没有密钥可言,主要依靠唯一密钥来确保数据加解密的安全。到目前为止,仍没有人能破解3DES

加密模式有:电子密码本模式ECB、加密块链模式CBC、加密反馈模式CFB、输出反馈模式OFB
填充方式有:NoPadding、ZerosPadding、PKCS5Padding

注意点:

  1. 密钥要大于等于24位
  2. 很多模式是先3DES编码再base64编码,所以解码要先base64解码,在3DES解码

  • Java对于自动装箱和自动拆箱的优化

    1. -127-127是享元模式, 常量池
    2. 编译前 Integer integer1 = 100; 编译后: Integer localInteger1 = Integer.valueOf(100);
    3. 拆包的时候会 localInteger1.intValue();

  • Java对于字符串拼装的优化

Java会对拼装的字符串进行转化成StringBuilder.

代码:

System.out.println(“result: “ + (integer3 == int2));

编译后:

System.out.println(new StringBuilder().append(“result: “).append(localInteger3.intValue() == j).toString());


  • Java中try catch finally语句中含有return语句的执行情况

    try语句在返回前,将其他所有的操作执行完,保留好要返回的值,而后转入执行finally中的语句,而后分为以下三种情况: 情况一:如果finally中有return语句,则会将try中的return语句”覆盖“掉,直接执行finally中的return语句,得到返回值,这样便无法得到try之前保留好的返回值。
    情况二:如果finally中没有return语句,也没有改变要返回值,则执行完finally中的语句后,会接着执行try中的return语句,返回之前保留的值。
    情况三:如果finally中没有return语句,但是改变了要返回的值,这里有点类似与引用传递和值传递的区别,分以下两种情况,:
    1)如果return的数据是基本数据类型或文本字符串,则在finally中对该基本数据的改变不起作用,try中的return语句依然会返回进入finally块之前保留的值。
    2)如果return的数据是引用数据类型,而在finally中对该引用数据类型的属性值的改变起作用,try中的return语句返回的就是在finally中改变后的该属性的值。


  • Java中的TreeSet,TreeMap


  • byte数组转long

      private static ByteBuffer buffer = ByteBuffer.allocate(8);
      public static long bytesToLong(byte[] bytes) {
          buffer.put(bytes, 0, bytes.length);
          buffer.flip();// need flip
          return buffer.getLong();
      }
    

  • String的Intern方法

    在 JAVA 语言中有8中基本类型和一种比较特殊的类型String。这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念。常量池就类似一个JAVA系统级别提供的缓存。8种基本类型的常量池都是系统协调的,String类型的常量池比较特殊。它的主要使用方法有两种:

直接使用双引号声明出来的String对象会直接存储在常量池中。 如果不是用双引号声明的String对象,可以使用String提供的intern方法。intern 方法会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中

作者:Anderson大码渣,欢迎关注我的简书: Anderson大码渣