Java基础

JAVA开发六大原则

抽象类和接口的对比

如何去设计类和接口(Effective Java)

1、使类和成员的可访问性最小化

2、复合优先于继承

3、接口优于抽象类

4、优先考虑静态成员类

三大特性

多态

重载

使用final的意义

四大引用

值传递和引用传递的区别?

值传递

引用传递

equals()

==和equals的区别?实现equals要注意哪些东西?

equals()与hashCode()之间的关系

一个字符(英文字母)占多少个字节,一个中文占多少字节?

java中double和float精度丢失问题及解决方法

BigDecimal

注解

元注解(4个)

自定义注解

Arrays.sort()原理分析

源码中的快速排序,主要做了以下几个方面的优化

foreach和while的区别(编译之后)

创建一个类的几种方法?

Redirect和forward

Object跟这些标记符代表的java类型有啥区别呢?

Java 异常

throw和throws区别

什么情况finally不会执行

finally方法一定会被执行么?

.class 文件是什么类型文件

java中序列化之子类继承父类序列化

标识符

Integer i=new Integer(127);和Integer i=127;的区别

手写单例模式

为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object类里?

Java中wait 和sleep 方法比较

hashCode和equals方法的关系

Object类中有哪些方法

String

String s=new String("xyz")究竟创建String Object分为两种情况

Java中由substring方法引发的内存泄漏

什么是值传递和引用传递

什么是泛型,为什么要使用以及类型擦除

什么是序列化?为什么要序列化?

反射

优点

缺点

Java1.0-1.12各个版本的新特性

JAVA开发六大原则

单一原则 : 一个类或一个方法只负责一件事情

里斯替换原则: 子类不应该重写父类已实现的方法,重载不应该比父类的参数更少

依赖倒置原则: 面向接口编程.(面向接口更能添加程序的可扩展性)

接口隔离原则: 接口中的方法应该细分,要合理的隔离开不同的功能到不同的接口中.

迪米特原则: 高内聚低耦合

开闭原则: 对修改关闭,对扩展开放

总结: 用抽象构建框架,用实现扩展细节

抽象类和接口的对比

参数抽象类接口

默认的方法实现它可以有默认的方法实现接口完全是抽象的。它根本不存在方法的实现

实现子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现

构造器抽象类可以有构造器接口不能有构造器

与正常Java类的区别除了你不能实例化抽象类之外,它和普通Java类没有任何区别接口是完全不同的类型

访问修饰符抽象方法可以有public、protected和default这些修饰符接口方法默认修饰符是public。你不可以使用其它修饰符。

main方法抽象方法可以有main方法并且我们可以运行它接口没有main方法,因此我们不能运行它。

多继承抽象方法可以继承一个类和实现多个接口接口只可以继承一个或多个其它接口

速度它比接口速度要快接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。

添加新方法如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。

如何去设计类和接口(Effective Java)

1、使类和成员的可访问性最小化

尽可能地使每个类或者成员不被外界访问,尽可能最小的访问级别。

2、复合优先于继承

与方法调用不同的是,继承打破了封装性。超类的实现有可能会随着发行版本的不同而有所变化,如果真的发生了变化,子类可能会遭到破坏,即使它的代码完全没有改变。

建议新的类中增加一个私有域,它引现有类的一个实例。这种设计被称做“复合(composition)

3、接口优于抽象类

如果你希望让两个类扩展同一个抽象类,就必须把抽象类放到类型层次结构的高处,以便这两个类的一个祖先成为它的子类。遗憾的是这样做会间接到伤害到类层次,迫使这个公共祖先到所有后代类都扩展这个新的抽象类,无论它对于这些后代类是否合适。

4、优先考虑静态成员类

非静态成员类的每个实例都隐含着与外围类的一个外围实例(enclosing instance)相关联。

三大特性

多态

好处

提高了代码的维护性(继承保证)

提高了代码的扩展性(由多态保证) 多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在复运行时,可以通过指向基类的指针,来调用实现派生类中的方法。

实现原理 多态是面向对象编程语言的重要特性,它允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。

Java 对于方法调用动态绑定的实现主要依赖于方法表,但通过类引用调用(invokevitual)和接口引用调用(invokeinterface)的实现则有所不同

重载

在编译器眼里,方法名称+参数类型+参数个数,组成一个唯一键,称为方法签名。返回值并不是方法签名的一部分,会导致编译出错。

一个引用变量到底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。

使用final的意义

为方法“上锁”,防止任何继承类改变它的本来含义和实现。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。

提高程序执行的效率,将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里(内嵌机制)

如果一个数据既是static又是final,那么它会拥有一块无法改变的存储空间

四大引用

引用类型回收时机使用场景

强引用不回收创建对象实例

软引用内存不足时图片缓存

弱引用垃圾回收WeakHashMap,维护一种非强制的映射关系

虚引用Unknow跟踪对象垃圾回收的活动

值传递和引用传递的区别?

值传递

在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。

public static void valueCrossTest(int age, float weight){

        System.out.println("传入的age:" + age);

        System.out.println("传入的weight:" + weight);

        age = 33;

        weight = 89.5f;

        System.out.println("方法内重新赋值后的age:" + age);

        System.out.println("方法内重新赋值后的weight:" + weight);

    }

    public static void main(String[] args) {

        int a = 25;

        float w = 77.5f;

        valueCrossTest(a, w);

        System.out.println("方法执行后的age:" + a);

        System.out.println("方法执行后的weight:"+w);

    }

传入的age:25

传入的weight:77.5

方法内重新赋值后的age:33

方法内重新赋值后的weight:89.5

方法执行后的age:25

方法执行后的weight:77.5

只是改变了当前栈帧(valueCrossTest方法所在栈帧)里的内容,当方法执行结束之后,这些局部变量都会被销毁,mian方法所在栈帧重新回到栈顶,成为当前栈帧,再次输出a和w时,依然是初始化时的内容。

值传递传递的是真实内容的一个副本,对副本的操作不影响原内容,也就是形参怎么变化,不会影响实参对应的内容。

引用传递

”引用”也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向通愉快内存地址,对形参的操作会影响的真实内容

public static void PersonCrossTest(Person person){

        System.out.println("传入的person的name:"+person.getName());

        person.setName("我是张小龙");

        System.out.println("方法内重新赋值后的name:"+person.getName());

    }

    public static void main(String[] args) {

        Person p = new Person();

        p.setName("我是马化腾");

        p.setAge(45);

        PersonCrossTest(p);

        System.out.println("方法执行后的name:"+p.getName());

    }

传入的person的name:我是马化腾

方法内重新赋值后的name:我是张小龙

方法执行后的name:我是张小龙

可以看出,person经过personCrossTest()方法的执行之后,内容发生了改变,这印证了上面所说的“引用传递”,对形参的操作,改变了实际对象的内容。

修改一下

  public static void PersonCrossTest(Person person){

        System.out.println("传入的person的name:"+person.getName());

        person=new Person();//加多此行代码

        person.setName("我是张小龙");

        System.out.println("方法内重新赋值后的name:"+person.getName());

    }

传入的person的name:我是马化腾

方法内重新赋值后的name:我是张小龙

方法执行后的name:我是马化腾

JVM需要在堆内另外开辟一块内存来存储new Person(),假如地址为“xo3333”,那此时形参person指向了这个地址,假如真的是引用传递,那么由上面讲到:引用传递中形参实参指向同一个对象,形参的操作会改变实参对象的改变。

equals()

Obejct的equals()源码

publicbooleanequals(Objectobj) {return(this==obj);    }

从代码可知,Object类的equals方法是比较的地址,所以最初的equals方法和==的作用是一致的

像String、Double、Integer、Date、Point这些不变类都重写了equals(),重写都是为判断的根据是值,而不地址

比如String的equals()源码

publicbooleanequals(ObjectanObject) {if(this==anObject) {returntrue;        }if(anObjectinstanceofString) {StringanotherString=(String)anObject;intn=value.length;if(n==anotherString.value.length) {charv1[]=value;charv2[]=anotherString.value;inti=0;while(n--!=0) {if(v1[i]!=v2[i])returnfalse;                    i++;                }returntrue;            }        }returnfalse;    }

比如Integer的equals()源码

publicbooleanequals(Objectobj) {if(objinstanceofInteger) {returnvalue==((Integer)obj).intValue();        }returnfalse;    }

==和equals的区别?实现equals要注意哪些东西?

==和equals的区别

==:判断两个字符串在内存中首地址是否相同,即判断两者是否是同一个字符串对象

equles():如果没有重写equals()方法比较的是对象的地址,因为对Object来说对象没有什么属性可以比较,只能比较最底层的地址。 而如果重写equals()方法时,该方法的对象因为是Object的子类,所以调用时会调用子类对象里面的方法.所以只有重写equals()方法后,两者比较的才是内容.或者说重写可以使自己定义比较的规则,不想按照地址去比较.

实现equals要注意哪些东西? 1、自反性:对于任何非空引用x,x.equals(x)应该返回true。 2、对称性:对于任何引用x和y,如果x.equals(y)返回true,那么y.equals(x)也应该返回true。 3、传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)也应该返回true。 4、一致性:如果x和y引用的对象没有发生变化,那么反复调用x.equals(y)应该返回同样的结果。 5、非空性:对于任意非空引用x,x.equals(null)应该返回false。 compareTo()

publicintcompareTo(IntegeranotherInteger) {returncompare(this.value, anotherInteger.value);    }publicstaticintcompare(intx,inty) {return(x<y)?-1:((x==y)?0:1);  }

equals()与hashCode()之间的关系

如果两个对象equals()方法相等则它们的hashCode返回值一定要相同,如果两个对象的hashCode返回值相同,但它们的equals()方法不一定相等。

hashCode()的作用是为了提高在散列结构存储中查找的效率

Java中重写equals()方法时尽量要重写hashCode()方法的原因:声明相等对象必须具有相等的哈希码,包括 HashMap、HashSet、Hashtable 等

一个字符(英文字母)占多少个字节,一个中文占多少字节?

一个字符占1个字节(GBK、ASCII、UTF-8)

一个中文占 2 个字节(GBK、ASCII)

一个中文占 3 个字节(UTF-8)

java中double和float精度丢失问题及解决方法

System.out.println(0.11+2001299.32);

控制台输出2001299.4300000002

在需要精确的表示两位小数时我们需要把他们转换为BigDecimal对象,然后再进行运算。

另外需要注意

使用BigDecimal(double val)构造函数时仍会存在精度丢失问题,建议使用BigDecimal(String val)

BigDecimal

publicBigDecimal(doubleval)

将 double 转换为 BigDecimal,后者是 double 的二进制浮点值准确的十进制表示形式。返回的 BigDecimal 的标度是使 (10scale × val) 为整数的最小值。 注:

此构造方法的结果有一定的不可预知性。有人可能认为在 Java 中写入 new BigDecimal(0.1) 所创建的 BigDecimal 正好等于 0.1(非标度值 1,其标度为 1),但是它实际上等于 0.1000000000000000055511151231257827021181583404541015625。这是因为 0.1 无法准确地表示为 double(或者说对于该情况,不能表示为任何有限长度的二进制小数)。这样,传入 到构造方法的值不会正好等于 0.1(虽然表面上等于该值)。

另一方面,String 构造方法是完全可预知的:写入 new BigDecimal("0.1") 将创建一个 BigDecimal,它正好 等于预期的 0.1。因此,比较而言,通常建议优先使用 String 构造方法。

当 double 必须用作 BigDecimal 的源时,请注意,此构造方法提供了一个准确转换;它不提供与以下操作相同的结果:先使用 Double.toString(double) 方法,然后使用 BigDecimal(String) 构造方法,将 double 转换为 String。要获取该结果,请使用 static valueOf(double) 方法。

注解

元注解(4个)

@Target – 作用域

ElementType.TYPE 用于描述类、接口或enum声明

ElementType.FIELD 用于描述实例变量

ElementType.METHOD 方法声明

ElementType.PARAMETER 参数

ElementType.CONSTRUCTOR 构造器

ElementType.LOCAL_VARIABLE 局部变量

ElementType.ANNOTATION_TYPE 另一个注释

ElementType.PACKAGE 包

@Retention 生命周期,定义了该Annotation被保留的时间长短

RetentionPolicy.SOURCE – 在源文件中有效(即源文件保留)

RetentionPolicy.CLASS – 在class文件中有效(即class保留)

RetentionPolicy.RUNTIME– 在运行时有效(即运行时保留)

@Documented 是否生成javadoc文档。

@Inherited 是否被子类继承

自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。 @interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

格式:public @interface 注解名 {定义体}

/** * 水果名称注解*/@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceFruitName{Stringvalue() default "";}

publicclassApple{@FruitName("Apple")privateStringappleName;    }

Arrays.sort()原理分析

首先说一下,Collections.sort方法底层也是调用的Arrays.sort方法。

Java Arrays中提供了对所有类型的排序。其中主要分为Primitive(8种基本类型)和Object两大类。

基本类型:采用调优的快速排序;

对象类型:采用改进的归并排序。既快速(nlog(n))又稳定,对象数组中保存的只是对象的引用,这样多次移位并不会造成额外的开销,但是,对象数组对比较次数一般比较敏感,有可能对象的比较比单纯数的比较开销大很多。归并排序在这方面比快速排序做得更好,这也是选择它作为对象排序的一个重要原因之一。

排序优化:实现中快排和归并都采用递归方式,而在递归的底层,也就是待排序的数组长度小于7时,直接使用冒泡排序,而不再递归下去.

分析: 长度为6的数组冒泡排序总比较次数最多也就1+2+3+4+5+6=21次,最好情况下只有6次比较。而快排或归并涉及到递归调用等的开销,其时间效率在n较小时劣势就凸显了,因此这里采用了冒泡排序,这也是对快速排序极重要的优化。

源码中的快速排序,主要做了以下几个方面的优化

当待排序的数组中的元素个数较少时,源码中的阀值为7,采用的是插入排序。尽管插入排序的时间复杂度为0(n^2),但是当数组元素较少时,插入排序优于快速排序,因为这时快速排序的递归操作影响性能。

较好的选择了划分元(基准元素)。能够将数组分成大致两个相等的部分,避免出现最坏的情况。例如当数组有序的的情况下,选择第一个元素作为划分元,将使得算法的时间复杂度达到O(n^2).

  源码中选择划分元的方法:

当数组大小为 size=7 时 ,取数组中间元素作为划分元。int n=m>>1;(此方法值得借鉴)

当数组大小 7<size<=40时,取首、中、末三个元素中间大小的元素作为划分元。

当数组大小 size>40 时 ,从待排数组中较均匀的选择9个元素,选出一个伪中数做为划分元。

3. 普通的快速排序算法,经过一次划分后,将划分元排到素组较中间的位置,左边的元素小于划分元,右边的元素大于划分元,而没有将与划分元相等的元素放在其附近,这一点,在Arrays.sort()中得到了较大的优化,将与划分元相等的元素移到数组中间来

jdk1.7后底层实现都是TimeSort实现的。TimSort是优化后的归并排序,TimSort算法就是找到已经排好序数据的子序列,然后对剩余部分排序,然后合并起来.

foreach和while的区别(编译之后)

在while循环里,会读入一行输入,把它存入某个变量并且执行循环主体。然后,它再回头去找其他的输入行。

在foreach循环中,整行输入操作符会在列表上下文中执行(因为foreach需要逐行处理列表的内容)。在循环开始执行之前,它必须先将输入全部读进来。

当输入大容量的文件时,使用foreach会占用大量的内存。两者的差异会十分明显。因此,最好的做法,通常是尽量使用while循环的简写,让它每次处理一行。

foreach 在编译的时候编译器会自动将对for这个关键字的使用转化为对目标的迭代器的使用,这就是foreach循环的原理

创建一个类的几种方法?

使用new关键字 → 调用了构造函数

使用Class类的newInstance方法 → 调用了构造函数

Employeeemp2=(Employee)Class.forName("org.programming.mitra.exercises.Employee").newInstance();

使用Constructor类的newInstance方法 → 调用了构造函数

Constructorconstructor=Employee.class.getConstructor();Employeeemp3=constructor.newInstance();

使用clone方法 → 没有调用构造函数

使用反序列化 → 没有调用构造函数

ObjectInputStreamin=newObjectInputStream(newFileInputStream("data.obj"));Employeeemp5=(Employee) in.readObject();

Redirect和forward

上图所示的间接转发请求的过程如下: 浏览器向Servlet1发出访问请求; Servlet1调用sendRedirect()方法,将浏览器重定向到Servlet2; 浏览器向servlet2发出请求; 最终由Servlet2做出响应。

上图所示的直接转发请求的过程如下: 浏览器向Servlet1发出访问请求; Servlet1调用forward()方法,在服务器端将请求转发给Servlet2; 最终由Servlet2做出响应。

Object跟这些标记符代表的java类型有啥区别呢?

Object是所有类的根类,任何类的对象都可以设置给该Object引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型T、E等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换。

Java 异常

Java把异常当作对象来处理,并定义一个基类java.lang.Throwable作为所有异常的超类。

在Java API中已经定义了许多异常类,这些异常类分为两大类,错误Error和异常Exception。

Java异常层次结构图如下图所示:

Error:Error类对象由 Java 虚拟机生成并抛出,Error表示编译时和系统错误,通常不能预期和恢复,比如硬件故障、JVM崩溃、内存不足等 。例如,Java虚拟机运行错误(Virtual MachineError),当JVM不再有继续执行操作所需的内存资源时,将出现 OutOfMemoryError。这些异常发生时,Java虚拟机(JVM)一般会选择线程终止;还有发生在虚拟机试图执行应用时,如类定义错误(NoClassDefFoundError)、链接错误(LinkageError)。这些错误是不可查的,因为它们在应用程序的控制和处理能力之 外,而且绝大多数是程序运行时不允许出现的状况。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况。在Java中,错误通常是使用Error的子类描述。

Exception:在Exception分支中有一个重要的子类RuntimeException(运行时异常),该类型的异常自动为你所编写的程序定义ArrayIndexOutOfBoundsException(数组下标越界)、NullPointerException(空指针异常)、ArithmeticException(算术异常)、MissingResourceException(丢失资源)、ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生;而RuntimeException之外的异常我们统称为非运行时异常,类型上属于Exception类及其子类,从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不自定义检查异常。

throw和throws区别

throw:(针对对象的做法) 抛出一个异常,可以是系统定义的,也可以是自己定义的

publicvoidyichang(){NumberFormatExceptione=newNumberFormatException();throwe;}

throws:(针对一个方法抛出的异常) 抛出一个异常,可以是系统定义的,也可以是自己定义的。

publicvoidyichang() throwsNumberFormatException{inta=Integer.parseInt("10L");}

throws出现在方法函数头;而throw出现在函数体。

throws表示出现异常的一种可能性,并不一定会发生这些异常;throw则是抛出了异常,执行throw则一定抛出了某种异常。

两者都是消极处理异常的方式(这里的消极并不是说这种方式不好),只是抛出或者可能抛出异常,但是不会由函数去处理异常,真正的处理异常由函数的上层调用处理。

什么情况finally不会执行

1、没有进入try代码块。 2、进入try代码块 , 但是代码运行中出现了死循环或死锁状态。 3、进入try代码块, 但是执行了 System.exit()操作。

注意, finally 是在 return 表达式运行后执行的 , 此时将要 return 的结果 已 经被暂 存起来 , 待 finally 代码块执行结束后再将之前暂存的结果返回

private static int test1() {

        int tmp = 10000;

        try {

            throw new Exception();

        } catch (Exception e) {

          return ++tmp;

        } finally {

          tmp = 99999;

        }

    }

此方法最终的返回值是 10001 ,而不是 99999。

相对在 finally 代码块中赋值,更加危险的做法是在 finally块中使用 return 操作,这样的代码会使返回值变得非常不可控。

private static int test1() {

int x = 1;

int y = 10;

int z = 100;

        try {

          return ++x;

        } catch (Exception e) {

          return ++y;

        } finally {

          return ++z;

        }

    }

( 1 )最后 return 的功件是由 finally 代码块巾的 return ++z 完成的,所以为法返 回的结果是 101。 ( 2 )语旬 return ++x 中的++x 被成功执行,所以运行结果是x=2。 ( 3 ) 如果有异常抛出 ,那么运行结果将会是 y =11,而 x=1;

finally代码块中使用 return语旬,使返回值的判断变得复杂,所以避免返回值不 可控,我们不要在 finally代码块中使用 return语句。

finally方法一定会被执行么?

java中,如果想要执行try中的代码之后,不允许再执行finally中的代码,有以下两种方式:

使用System.exit(1)来退出虚拟机

把当前执行trycatchfinally代码的线程设置为守护线程

.class 文件是什么类型文件

class文件是一种8位字节的二进制流文件

java中序列化之子类继承父类序列化

父类实现了Serializable,子类不需要实现Serializable

相关注意事项

序列化时,只对对象的状态进行保存,而不管对象的方法;

当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;

并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:

1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。 2. 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。 2,反过来父类未实现Serializable,子类实现了,序列化子类实例的时候,父类的属性是直接被跳过不保存,还是能保存但不能还原?(答案:值不保存)

解:父类实现接口后,所有派生类的属性都会被序列化。子类实现接口的话,父类的属性值丢失。

java中序列化之子类继承父类序列化

标识符

标识符可以包括这4种字符:字母、下划线、$、数字;开头不能是数字;不能是关键字

Integer i=new Integer(127);和Integer i=127;的区别

Integer i = 127的时候,使用Java常量池技术,是为了方便快捷地创建某些对象,当你需要一个对象时候,就去这个池子里面找,找不到就在池子里面创建一个。但是必须注意 如果对象是用new 创建的。那么不管是什么对像,它是不会放到池子里的,而是向堆申请新的空间存储。Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值在-128到127之间的数时才可使用对象池。超过了就要申请空间创建对象了

inti1=128;Integeri2=128;Integeri3=newInteger(128);//自动拆箱System.out.println(i1==i2);//trueSystem.out.println(i1==i3);//trueIntegeri5=127;Integeri6=127;System.out.println(i5==i6);//trueIntegeri5=127;Integerii5=newInteger(127);System.out.println(i5==ii5);//falseIntegeri7=newInteger(127);Integeri8=newInteger(127);System.out.println(i7==i8);//false

手写单例模式

最好的单例模式是静态内部类,不要写双重检验

privatestaticclassLazySomethingHolder{publicstaticSomethingsomething=newSomething();}publicstaticSomethinggetInstance() {returnLazySomethingHolder.something;}

为什么线程通信的方法wait(), notify()和notifyAll()被定义在Object类里?

Java的每个对象中都有一个锁(monitor,也可以成为监视器) 并且wait(),notify()等方法用于等待对象的锁或者通知其他线程对象的监视器可用。在Java的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是Object类的一部分,这样Java的每一个类都有用于线程间通信的基本方法

Java中wait 和sleep 方法比较

这两个方法来自不同的类分别是Thread和Object

最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。

wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)

sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

sleep方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在sleep的过程中过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。

注意sleep()方法是一个静态方法,也就是说他只对当前对象有效,通过t.sleep()让t对象进入sleep,这样的做法是错误的,它只会是使当前线程被sleep 而不是t线程

wait属于Object的成员方法,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法也同样会在wait的过程中有可能被其他对象调用interrupt()方法而产生

hashCode和equals方法的关系

在有些情况下,程序设计者在设计一个类的时候为需要重写equals方法,比如String类,但是千万要注意,在重写equals方法的同时,必须重写hashCode方法。 也就是说对于两个对象,如果调用equals方法得到的结果为true,则两个对象的hashcode值必定相等; 如果equals方法得到的结果为false,则两个对象的hashcode值不一定不同; 如果两个对象的hashcode值不等,则equals方法得到的结果必定为false; 如果两个对象的hashcode值相等,则equals方法得到的结果未知。

Object类中有哪些方法

Object是所有类的父类,它有很多类对象会用到的方法

Object方法:equals()、toString()、finalize()、hashCode()、getClass()、clone()、wait()、notify()、notifyAll()

packagejava.lang;publicclassObject{privatestaticnativevoidregisterNatives();static{        registerNatives();    }//返回一个对象的运行时类,获得类型的信息。publicfinalnativeClass<?>getClass();//该方法将对象的内存地址进行哈希运算,返回一个int类型的哈希值,是相等对象拥有相同的哈希码,尽量让不等的对象具有不同的哈希码。publicnativeinthashCode();//指示某个其他对象是否与此对象"相等"。publicbooleanequals(Objectobj) {return(this==obj);    }//创建并返回此对象的一个副本(复制对象)protectednativeObjectclone()throwsCloneNotSupportedException;//返回该对象的字符串表示。以便用户能够获得一些有关对象状态的基本信息。简单说就是利用字符串来表示对象。publicStringtoString() {returngetClass().getName()+"@"+Integer.toHexString(hashCode());    }//唤醒在此对象监视器上等待的单个线程。publicfinalnativevoidnotify();//唤醒在次对象监视器上等待的所有线程。publicfinalnativevoidnotifyAll();//导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法,或者超过指定的时间量。publicfinalnativevoidwait(longtimeout)throwsInterruptedException;//导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量。publicfinalvoidwait(longtimeout,intnanos)throwsInterruptedException{if(timeout<0) {thrownewIllegalArgumentException("timeout value is negative");        }if(nanos<0||nanos>999999) {thrownewIllegalArgumentException("nanosecond timeout value out of range");        }if(nanos>0) {            timeout++;        }        wait(timeout);    }//导致当前的线程等待,直到其他线程调用此对象的notify()方法或notifyAll()方法。publicfinalvoidwait()throwsInterruptedException{        wait(0);    }//当垃圾回收器确定不存在对该对象的更多引用时,对象的垃圾回收器调用该方法。protectedvoidfinalize()throwsThrowable{ }}

String

String s=new String("xyz")究竟创建String Object分为两种情况

如果String常理池中,已经创建"xyz",则不会继续创建,此时只创建了一个对象new String("xyz");

如果String常理池中,没有创建"xyz",则会创建两个对象,一个对象的值是"xyz",一个对象new String("xyz")。

Java中由substring方法引发的内存泄漏

内存溢出(out of memory ):通俗的说就是内存不够用了,比如在一个无限循环中不断创建一个大的对象,很快就会引发内存溢出。

内存泄漏(leak of memory):是指为一个对象分配内存之后,在对象已经不在使用时未及时的释放,导致一直占据内存单元,使实际可用内存减少,就好像内存泄漏了一样。

substring(int beginIndex, int endndex )是String类的一个方法,但是这个方法在JDK6和JDK7中的实现是完全不同的(虽然它们都达到了同样的效果)。在JDK1.6中不当使用substring会导致严重的内存泄漏问题。

String str = "abcdefghijklmnopqrst";

String sub = str.substring(1, 3);

str = null;

这段简单的程序有两个字符串变量str、sub。sub字符串是由父字符串str截取得到的,假如上述这段程序在JDK1.6中运行,我们知道数组的内存空间分配是在堆上进行的,那么sub和str的内部char数组value是公用了同一个,也就是上述有字符a~字符t组成的char数组,str和sub唯一的差别就是在数组中其实beginIndex和字符长度count的不同。在第三句,我们使str引用为空,本意是释放str占用的空间,但是这个时候,GC是无法回收这个大的char数组的,因为还在被sub字符串内部引用着,虽然sub只截取这个大数组的一小部分。当str是一个非常大字符串的时候,这种浪费是非常明显的,甚至会带来性能问题,解决这个问题可以是通过以下的方法:

String str = "abcdefghijklmnopqrst";

String sub = str.substring(1, 3) + "";

str = null;

利用的就是字符串的拼接技术,它会创建一个新的字符串,这个新的字符串会使用一个新的内部char数组存储自己实际需要的字符,这样父数组的char数组就不会被其他引用,令str=null,在下一次GC回收的时候会回收整个str占用的空间。但是这样书写很明显是不好看的,所以在JDK7中,substring 被重新实现了。

在JDK7中改进了substring的实现,它实际是为截取的子字符串在堆中创建了一个新的char数组用于保存子字符串的字符。这样子字符串和父字符串也就没有什么必然的联系了,当父字符串的引用失效的时候,GC就会适时的回收父字符串占用的内存空间。

什么是值传递和引用传递

值传递

publicclassTempTest{privatevoidtest1(inta) {    a=5;System.out.println("test1方法中的a="+a);  }publicstaticvoidmain(String[]args) {TempTestt=newTempTest();inta=3;    t.test1(11);System.out.println("main方法中a="+a);  }}

test1方法中的a=5 main方法中a=3 值传递:传递的是值的拷贝,传递后就互不相关了 引用传递:传递的是变量所对应的内存空间的地址

publicclassTempTest{privatevoidtest1(Aa) {    a.age=20;System.out.println("test1方法中a="+a.age);  }publicstaticvoidmain(String[]args) {TempTestt=newTempTest();Aa=newA();    a.age=10;    t.test1(a);System.out.println("main方法中a="+a.age);  }}classA{publicintage=0;}

test1方法中a=20 main方法中a=20 传递前和传递后都指向同一个引用(同一个内存空间) 如果不互相影响,方法是在test1方法里面新new一个实例就可以了

什么是泛型,为什么要使用以及类型擦除

泛型的本质就是“参数化类型”,也就是说所操作的数据类型被指定为一个参数。 创建集合时就指定集合元素的数据类型,该集合只能保存其指定类型的元素, 避免使用强制类型转换。

Java 编译器生成的字节码是不包含泛型信息的,泛型类型信息将在 编译处理 时 被擦除,这个过程即 类型擦除。类型擦除可以简单的理解为将泛型 java 代码转 换为普通 java 代码,只不过编译器更直接点,将泛型 java 代码直接转换成普通 java 字节码。

类型擦除的主要过程如下:

将所有的泛型参数用其最左边界(最顶级的父类型)类型替换。

移除所有的类型参数。

什么是序列化?为什么要序列化?

序列化,又称为“串化”,可以形象的把它理解为把Java对象内存中的数据采编成一串二进制的数据,然后把这些数据存放在可以可以持久化的数据设备上,如磁盘。当需要还原这些数据的时候,在通过反序列化的过程,把对象又重新还原到内存中。

为什么要将数据序列化?可以从两个方面来解释,一方面是为了方便数据存储,另一方面是为了方便数据的传递。

序列化好处:

方便数据传递,减少了数据丢失率,增强了程序安全性。

有利于数据存储,减少了不必要的内存浪费,节约了资源。

简化了数据库结构,增强了程序的可维护性。

反射

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

Class<?>clz=Class.forName("fs.Student");Studentstu=(Student) clz.newInstance();

优点

反射提高了程序的灵活性和扩展性,在底层框架中用的比较多,业务层面的开发过程中尽量少用。

缺点

性能问题。反射包括了一些动态类型,所以 JVM 无法对这些代码进行优化。因此,反射操作的效 率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程 序中使用反射。

安全限制。反射要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有 安全限制的环境中运行,如 Applet

内部暴露。由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方 法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。 反射代码破坏了抽象性。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,976评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,249评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,449评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,433评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,460评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,132评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,721评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,641评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,180评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,267评论 3 339
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,408评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,076评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,767评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,255评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,386评论 1 271
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,764评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,413评论 2 358