Java基础扫盲
文章目录
- Java基础扫盲
- 1 Java创建一个对象的方法有几种?[~](https://www.cnblogs.com/liululee/p/11570353.html)
- 2 Java中== 和equals的区别是什么?
- 3 序列化的作用是什么?常见的序列化方法是什么?Java自带的序列化是怎么实现的?
- 4 解释下重载和重写的区别?[~](https://blog.youkuaiyun.com/geekmubai/article/details/81975990)
- 5 [有了解过java的异常机制么?](https://blog.youkuaiyun.com/hguisu/article/details/6155636)请结合项目描述一下你是怎么处理异常的?是否会自定义异常?
- 6 Object类有去了解么?该类有哪些常用的方法,分别是怎么实现的?
- [7 java的hashcode和equals方法的作用?什么时候会用到?](https://www.cnblogs.com/whgk/p/6071617.html)
- 8 String是一个基本类型么?那java的基本类型有哪些?String和stringBuffer和stringBuilder的区别是什么?
- String与int相互转化的方法:
- 9 String str="i"与 String str=new String("i")一样吗?不同点主要在哪里呢?
- 10 抽象类和接口的区别?抽象类一定要有抽象函数么?接口定义的变量一定是常量么?接口中可以定义函数的实现么?
- 11 final、static关键字有了解,在java中的作用。抽象类可以使用final修饰么?
- 12 [final,finally,finalize分别是什么](https://www.cnblogs.com/ktao/p/8586966.html)?
- 13 [Java的IO流有了解过](https://blog.youkuaiyun.com/mu_wind/article/details/108674284),实现一个按行读取数据的方式。
- 14 Java的反射原理是什么?getClass和Class.forName()的区别是什么?
- 15 [如何实现一个list类型的深拷贝](https://blog.youkuaiyun.com/riemann_/article/details/87217229)?Java的Cloneable接口的作用是什么?
- [16 Java的泛型的作用是什么?](https://blog.youkuaiyun.com/briblue/article/details/76736356)
- 17 Java的注解有了解,其底层的实现原理是什么?怎么定义一个注解?
- 18 Java中两个类的关系有多少种?有了解过设计模式么?
- 19 Java的collection有几种?Collection和Collections的区别是什么?
- 20 ArrayList、LinkedList和vector的区别?它们是线程安全的么?如果想要线程安全应该要怎么实现?
1 Java创建一个对象的方法有几种?~
5种
1、使用new关键字 → 调用了构造函数
这种方式,我们可以调用任意的构造函数(无参的和带参数的)。
NewObject newObject1 = new NewObject();
2、使用Class类的newInstance方法 → 调用了构造函数
使用Class类的newInstance方法创建对象。这个newInstance方法调用无参的构造函数创建对象。
NewObject newObject2 = Class.forName("com.zmxqq.NewObject").newInstance();
3、使用Constructor类的newInstance方法 → 调用了构造函数
与 Class 类中的 newInstance() 方法相似,在此我们将使用 java.lang.reflect.Constructor 类中的 newInstance() 方法创建对象。通过使用这个 newInstance() 方法我们能够调用有参构造函数和私有构造函数。
NewObject newObject3 = NewObject.class.getConstructor().newInstance();
4、使用clone方法 → 没有调用构造函数
无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。
要使用clone方法,我们需要先实现Cloneable接口并重写clone方法。
NewObject newObject4 = (NewObject)newObject3.clone();
5、使用反序列化deserialization → 没有调用构造函数
当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。
对于序列化对象,我们需要在类中实现 Serializable 接口。//序列化Serialization ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.obj")); out.writeObject(newObject4); out.close(); //反序列化Deserialization ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj")); NewObject newObject5 = (NewObject) in.readObject(); in.close();
2 Java中== 和equals的区别是什么?
对于基础类型,==比较值是否相等
对于引用类型,Object类中默认的的equals方法与==比较的都是对象引用是否指向了同一块内存。子类一般会重写equals方法,改为比较对象值是否相等(StringBuffer没有重写)。
注意:用equals方法判断之前会调用hashcode方法判断对象在hash表中的位置是否相等,地址相等再用equals进行比较,减少equals比较次数。所以重写equals方法、改用对象值比较时,一定要重写hashcode方法,不然对象值相同的不同对象在hashcode判断这一步就判成了不等。
3 序列化的作用是什么?常见的序列化方法是什么?Java自带的序列化是怎么实现的?
序列化就是一种用来处理对象流的机制,对象流也就是将对象的内容进行流化。序列化可以永久保存对象的状态到本地文件或者数据库中,方便对流化后的对象进行网络传输和RMI远程调用对象以及进程间的对象传递。
将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的。
public class NewObject implements Serializable { //... }
序列化使用一个输出流(如文件输出流:FileOutputStream)来构造一个 ObjectOutputStream(对象输出流)对象。接着,使用ObjectOutputStream对象的writeObject(Object obj)方法将obj对象写到输出流中(即保存其状态)。
反序列化则使用输入流((如文件输入流:FileInputStream))构造一个ObjectInputStream(对象输入流)对象。接着使用ObjectInputStream对象的readObject()方法将对象流恢复为对象。
真正的读写操作是靠
FileOutputStream
和FileInputStream
字节流NewObject newObject4 = new NewObject("4"); //序列化Serialization System.out.println(newObject4.toString()); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.obj")); out.writeObject(newObject4); out.close(); System.out.println(newObject4.toString()); //反序列化Deserialization ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj")); NewObject newObject5 = (NewObject) in.readObject(); in.close(); System.out.println(newObject5.toString());
注意:
1、序列化时,只对对象的状态进行保存,而不管对象的方法;
2、当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;
3、当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;所以要添加序列号,避免重复序列化对象。这也直接实现了深拷贝。
4、声明为static和transient类型的成员数据不能被序列化。因为static是类级别的,transient代表对象的临时数据。
4 解释下重载和重写的区别?~
- (1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- (2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
重载 重写 参数列表 必须修改 不能修改 返回类型 任意 不能修改(派生类可以) 异常 任意 可以减少或删除,一定不能抛出新的或者更广的异常 修饰符 任意 一定不能做更严格的限制(可以降低限制) 范围 同一个类中 有继承关系的子类中 重写的好处在于子类可以根据需要,定义特定于自己的行为。 方法重载是让类以统一的方式处理不同类型数据的一种手段。调用方法时通过传递给它们的不同个数和类型的参数来决定具体使用哪个方法,也就是多态性。
5 有了解过java的异常机制么?请结合项目描述一下你是怎么处理异常的?是否会自定义异常?
①继承自Throwable类,分为Error错误和Exception异常两种,其中异常是指程序本身可以处理的问题。Exception中的RuntimeException及其子类属于运行时异常,编译器不会强制要求处理这类异常,例如NullPointerException(空指针异常),IndexOutOfBoundsException(下标越界异常),ArithmeticException(算数异常)等,只有程序运行时出现才会抛出这种异常。除RuntimeException及其子类外,Exception类其他的子类都属于非运行时异常,也就是编译时异常,例如ClassNotFoundException(类不存在异常)、IOException(IO异常)、SQLException(SQL异常)和一些自定义的异常,如果程序中可能出现这几种异常,编译器会提示用try-catch语句捕获处理异常或者在方法签名上用throws语句声明并抛出异常,否则编译报错。
//两种方法处理ClassNotFoundException异常 //抛出异常 public void test() throws ClassNotFoundException { newObject5 = (NewObject) in.readObject(); } //捕获异常: try { newObject5 = (NewObject) in.readObject(); } catch (ClassNotFoundException e) { e.printStackTrace(); }
②因为Error属于程序不可处理的错误,运行时异常也因为无法预料会是哪种异常,所以在处理异常时我只是处理编译时异常。在项目中一般在service实现类,涉及到调用dao层执行SQL语句,查询结果进行逻辑处理的时候,可能会出现NoSuchMethodException(方法未找到抛出的异常)、ClassCastException(类型转换异常类)、会用try-catch捕获一下;还有就是excel工具类和序列化时会出现的IOException(输入输出流异常类)也会捕获一下。在catch语句里用printStackTrace()方法,将对象的堆栈跟踪输出至错误输出流,作为System.err 的值。
如果一个方法可能出现多次异常,需要将底层异常类的catch字句放到前面,相对高层的异常类的catch语句放到后面,因为Java虚拟机会把实际抛出的异常对象依次和各个catch代码块声明的异常类型匹配,如果异常对象为某个异常类型或其子类的实例,就执行这个catch代码块,一个catch捕获成功后整个try-catch语句也就结束了。
最后写一个捕获Exception异常的catch语句,避免遗漏某些异常。
③首先创建自定义异常类,选择继承Exception或RuntimeException;在方法中通过try-catch捕获并处理自定义异常,或者通过throw关键字抛出异常对象,由调用者捕获并处理异常。
class MyException extends Exception { // 创建自定义异常类 String message; // 定义String类型变量 public MyException(String ErrorMessagr) { // 父类方法 message = ErrorMessagr; } @Override public String getMessage() { // 覆盖getMessage()方法 return message; } }
当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行:
1)在finally语句块中发生了异常。
2)在前面的代码中用了System.exit()退出程序。
3)程序所在的线程死亡。
4)关闭CPU。如果使用throws抛出异常类对象,该方法的调用者必须处理或者重新抛出该异常。对于重写的方法,可以减少或删除异常,一定不能抛出新的或者更广的异常
6 Object类有去了解么?该类有哪些常用的方法,分别是怎么实现的?
registerNatives方法
private static native void registerNatives(); /** * 对象初始化时自动调用此方法 */ static { registerNatives(); }
作用:类被加载时注册该类所包含的除了registerNatives()方法以外的所有本地方法,例如getClass()、hashCode()、clone()、notify()、notifyAll()、wait()等
getClass方法
/** * 返回此Object的运行时类型 */ public final native Class<?> getClass();
hashCode方法
public native int hashCode();
作用:将对象的物理地址转换成一个整数,hash函数的算法计算该整数就得到了hashcode,返回的是对象在哈希表中的位置,这个方法在一些具有哈希功能的集合Collection中用到。
equals方法
public boolean equals(Object obj) { return (this == obj); }
作用:比较的是对象的内存地址。子类一般都要重写这个方法,改为比较对象值是否相等(StringBuffer就没重写)
注意:用equals方法判断之前会调用hashcode方法判断对象地址是否相等,地址相等再用equals进行比较,减少equals比较次数。所以重写equals方法、改用对象值比较时,一定要重写hashcode方法,不然对象值相同的不同对象在hashcode判断这一步就判成了不等。
clone方法
protected native Object clone() throws CloneNotSupportedException;
作用:对象的浅复制,只有实现了Cloneable接口才可以调用该方法,否则抛出CloneNotSupportedException异常。
toString方法
/** * 返回该对象的字符串表示,非常重要的方法 * getClass().getName();获取字节码文件的对应全路径名例如java.lang.Object * Integer.toHexString(hashCode());将哈希值转成16进制数格式的字符串。 */ public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
notify、notifyAll方法
public final native void notify(); public final native void notifyAll();
作用:唤醒在该对象上等待的某个/所有线程。
wait方法
public final native void wait(long timeout) throws InterruptedException;
作用:使当前线程进入等待状态,当
1)超出timeout
2)其他线程通过调用notify方法或notifyAll方法通知当前等待的线程醒来
3)其他线程调用了interrupt中断该线程
则当前线程退出等待状态。
finalize方法
protected void finalize() throws Throwable { }
作用:回收对象时调用。子类若要在对象回收时添加逻辑处理,可重写finalize方法。
7 java的hashcode和equals方法的作用?什么时候会用到?
hashcode和equals方法都是Object类的方法。
public native int hashCode();
hashcode作用:将对象的物理地址转换成一个整数,hash函数的算法计算该整数就得到了hashcode,返回对象在哈希表中的位置,这个方法在一些具有哈希功能的集合Collection中用到。
public boolean equals(Object obj) { return (this == obj); }
equals比较的是对象的内存地址。子类一般都要重写这个方法,改为比较对象值是否相等(StringBuffer就没重写)
注意:用equals方法判断之前会调用hashcode方法判断对象是否存放在哈希表中同一位置,位置相等再用equals进行比较,减少equals比较次数。所以重写equals方法、改用对象值比较时,一定要重写hashcode方法让equals相等的对象hashcode也相等,不然对象值相同的不同对象在hashcode判断这一步就判成了不等。
8 String是一个基本类型么?那java的基本类型有哪些?String和stringBuffer和stringBuilder的区别是什么?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-axCP8M3l-1610003158921)(图片库/20190226164706730.png)]
String不是一个基本类型
String 是不可变的对象,每次试图改变String都会生成新的String对象,然后将指针指向新的对象,对于经常要改变的字符串就会浪费很多内存空间。
StringBuffer 和 StringBuilder 的对象可以多次修改,且不会产生新的对象。
StringBuffer 线程安全,StringBuilder 非线程安全,但StringBuilder 速度更快。
String与int相互转化的方法:
Integer转String
Integer a = 1 ;
String str = Integer.toString(a); // 注意a不能为null
String str = a.toString(); // a不能为null
String str = String.valueOf(a); // a为null会转为"null"
String转Integer/int
Integer.valueOf(str); // 返回值为Integer类型,确保str非null
Integer.parseInt(str); // 返回值为int类型
针对不同的返回要求选不同的方法,避免拆装箱影响性能。
9 String str="i"与 String str=new String(“i”)一样吗?不同点主要在哪里呢?
JVM为了提升性能和减少内存开销,避免字符串的重复创建,维护了一块特殊的内存空间——常量池。
String str="i"会直接在常量池中寻找是否有"i"这个字符串,有就指向它,没有就在常量池中创建一个;
String str=new String(“i”)会在堆内存中划分一块区域,存储一个String对象。如果常量池中有"i",对象指向常量池中的"i",如果常量池没有,则在常量池新建一个"i"。
10 抽象类和接口的区别?抽象类一定要有抽象函数么?接口定义的变量一定是常量么?接口中可以定义函数的实现么?
抽象类不可以用于创建对象。抽象类可以包含抽象方法,这些方法将在具体的子类中实现。一个父类设计得非常抽象,以至于他都没有任何具体的实例。这样的类称为抽象类。通过extends继承。
接口是一种与类相似的结构,只包含常量和公共抽象方法。接口的目的是指明相关或者不相关类的多个对象的共同行为。通过implements实现接口继承。
可以定义一个不包含抽象方法的抽象类,这种类是用来定义新子类的基类的。
接口中所有的数据域都是public static final,而且所有的方法都是public abstract。接口只是对一类事物的属性和行为更高层次的抽象,所以变量一定是常量,只能读不能改。
JDK1.8的新特性允许用default和static关键字修饰接口中的方法,default为接口中的抽象方法写了一个默认的方法体实现,可以在子接口继续复写这个方法;static修饰静态方法,可以直接使用接口调用。
11 final、static关键字有了解,在java中的作用。抽象类可以使用final修饰么?
static、final、static final的区别 final: final修饰类的时,表明该类不能被其他类所继承;修饰方法,使方法不能被继承类所修改;修饰变量表示常量,只能赋值一次,赋值后不可修改。final修饰的属性的初始化可以在编译期,也可以在运行期,初始化后不能被改变。 对于基本类型数据,final会将值变为一个常数(创建后不能被修改);但是对于引用,final会将引用变为一个常数(进行声明时,必须将引用指向到一个具体的对象。而且不能再将引用指向另一个对象。但是,对象的本身是可以修改的。这一限制也适用于数组,数组也属于对象,数组本身也是可以修改的。方法参数中的final形参,意味着在该方法内部,我们不能改变参数指向的实际东西,也就是说在方法内部不能给形参再另外赋值,以及不能更改参数引用所指向的对象)。 static: static可以修饰:属性,方法,代码段,内部类(静态内部类或嵌套内部类) static修饰的属性的初始化在编译期(类加载的时候),初始化后能改变。 static修饰的属性所有对象都只有一个值。 static修饰的属性强调它们只有一个。 static修饰的属性、方法、代码段跟该类的具体对象无关,不创建对象也能调用static修饰的属性、方法等 static和“this、super”势不两立,static跟具体对象无关,而this、super正好跟具体对象有关。 static不可以修饰局部变量。 static final和final static: static final和final static语法和用法上没有任何区别,一般习惯static写在前面。 static final: static修饰的属性强调它们只有一个,final修饰的属性表明是一个常数(创建后不能被修改)。static final修饰的属性表示一旦给值,就不可修改,并且可以通过类名访问。 static final也可以修饰方法,表示该方法不能重写,可以在不new对象的情况下调用。
抽象类和抽象方法用abstract修饰,需要由子类继承并实现抽象方法,final修饰的类或方法表示不能继承,所以不能用final修饰抽象类
12 final,finally,finalize分别是什么?
final修饰类的时,表明该类不能被其他类所继承;修饰方法,使方法不能被继承类所修改;修饰变量表示常量,只能赋值一次,赋值后不可修改。
finally作为异常处理的一部分,能用在try/catch语句中,附带一个finally语句块,表示这段语句最终一定会被执行(不管有没有抛出异常),经常被用在需要释放资源的情况下。几种情况下finally语句块不会执行:
1)在finally语句块中发生了异常。
2)在前面的代码中用了System.exit()退出程序。
3)程序所在的线程死亡。
4)关闭CPU。finalize()是在java.lang.Object里定义的,回收对象时调用。子类若要在对象回收时添加逻辑处理或者释放资源,可重写finalize方法。
13 Java的IO流有了解过,实现一个按行读取数据的方式。
IO,即
in
和out
,也就是输入和输出,指应用程序和外部设备之间的数据传递,常见的外部设备包括文件、管道、网络连接。流(
Stream
),是一个抽象的概念,是指一连串的数据(字符或字节),也是以先进先出的方式发送信息的通道。一般来说关于流的特性有下面几点:
- 先进先出:最先写入输出流的数据最先被输入流读取到。
- 顺序存取:可以一个接一个地往流中写入一串字节,读出时也将按写入顺序读取一串字节,不能随机访问中间的数据。(
RandomAccessFile
除外)- 只读或只写:每个流只能是输入流或输出流的一种,不能同时具备两个功能,输入流只能进行读操作,对输出流只能进行写操作。在一个数据传输通道中,如果既要写入数据,又要读取数据,则要分别提供两个流。
IO流主要的分类方式有以下3种:
- 按数据流的方向:输入流、输出流
- 按处理数据单位:字节流、字符流
- 按功能:节点流、处理流
输入与输出是相对于应用程序而言的,比如文件读写,读取文件是输入流,写文件是输出流;
字节流和字符流的用法几乎完成全一样,区别在于字节流和字符流所操作的数据单元不同,字节流操作的是数据单元是8位的字节,字符流操作的是数据单元为16位的字符;
节点流:直接操作数据读写的流类,比如
FileInputStream
处理流:对一个已存在的流的链接和封装,通过对数据进行处理为程序提供功能强大、灵活的读写功能,例如
BufferedInputStream
(缓冲字节流),处理流是对节点流的封装,最终的数据处理还是由节点流完成的。处理流中常用的的缓冲流
BufferedInputStream
会在内存中设置一个缓存区,缓冲区先存储足够的待操作数据后,再与内存或磁盘进行交互。这样,在总数据量不变的情况下,通过提高每次交互的数据量,减少了交互次数。
InputStream
:InputStream
是所有字节输入流的抽象基类,前面说过抽象类不能被实例化,实际上是作为模板而存在的,为所有实现类定义了处理输入流的方法。
FileInputStream
:文件输入流,一个非常重要的字节输入流,用于对文件进行读取操作。
PipedInputStream
:管道字节输入流,能实现多线程间的管道通信。
ByteArrayInputStream
:字节数组输入流,从字节数组(byte[])中进行以字节为单位的读取,也就是将资源文件都以字节的形式存入到该类中的字节数组中去。
FilterInputStream
:装饰者类,具体的装饰者继承该类,这些类都是处理类,作用是对节点类进行封装,实现一些特殊功能。
DataInputStream
:数据输入流,它是用来装饰其它输入流,作用是“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”。
BufferedInputStream
:缓冲流,对节点流进行装饰,内部会有一个缓存区,用来存放字节,每次都是将缓存区存满然后发送,而不是一个字节或两个字节这样发送,效率更高。
ObjectInputStream
:对象输入流,用来提供对基本数据或对象的持久存储。通俗点说,也就是能直接传输对象,通常应用在反序列化中。它也是一种处理流,构造器的入参是一个InputStream
的实例对象。
InputStreamReader
:从字节流到字符流的桥梁(InputStreamReader
构造器入参是FileInputStream
的实例对象),它读取字节并使用指定的字符集将其解码为字符。它使用的字符集可以通过名称指定,也可以显式给定,或者可以接受平台的默认字符集。OutputStreamWriter
:从字符到字节。
BufferedReader
:从字符输入流中读取文本,设置一个缓冲区来提高效率。BufferedReader
是对InputStreamReader
的封装,前者构造器的入参就是后者的一个实例对象。
FileReader
:用于读取字符文件的便利类,new FileReader(File file)
等同于new InputStreamReader(new FileInputStream(file, true),"UTF-8")
,但FileReader
不能指定字符编码和默认字节缓冲区大小。
PipedReader
:管道字符输入流。实现多线程间的管道通信。
CharArrayReader
:从Char
数组中读取数据的介质流。
StringReader
:从String
中读取数据的介质流。字节输入流
InputStream
主要方法:
read()
:从此输入流中读取一个数据字节。read(byte[] b)
:从此输入流中将最多 b.length 个字节的数据读入一个 byte 数组中。read(byte[] b, int off, int len)
:从此输入流中将最多 len 个字节的数据读入一个 byte 数组中。close()
:关闭此输入流并释放与该流关联的所有系统资源。字节输出流
OutputStream
主要方法:
write(byte[] b)
:将 b.length 个字节从指定 byte 数组写入此文件输出流中。write(byte[] b, int off, int len)
:将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此文件输出流。write(int b)
:将指定字节写入此文件输出流。close()
:关闭此输入流并释放与该流关联的所有系统资源。字符输入流
Reader
主要方法:
read()
:读取单个字符。read(char[] cbuf)
:将字符读入数组。read(char[] cbuf, int off, int len)
: 将字符读入数组的某一部分。read(CharBuffer target)
:试图将字符读入指定的字符缓冲区。flush()
:刷新该流的缓冲。close()
:关闭此流,但要先刷新它。字符输出流
Writer
主要方法:
write(char[] cbuf)
:写入字符数组。write(char[] cbuf, int off, int len)
:写入字符数组的某一部分。write(int c)
:写入单个字符。write(String str)
:写入字符串。write(String str, int off, int len)
:写入字符串的某一部分。flush()
:刷新该流的缓冲。close()
:关闭此流,但要先刷新它。另外,字符缓冲流还有两个独特的方法:
BufferedWriter
类newLine()
:写入一个行分隔符。这个方法会自动适配所在系统的行分隔符。BufferedReader
类readLine()
:读取一个文本行。package test.java.Test; import java.io.*; /** * 按行读取文件 * @author zmxqq * @date 2020/12/29 - 10:25 */ public class IOTest { public static void main(String[] args) { // 为了在finally字句中关闭此缓冲流,需要定义在try语句块外面 BufferedReader bufferedReader = null; try { // 创建文件类 File file = new File("C:/Desktop/1.txt"); if (!file.exists()) { file.createNewFile(); } /** * 如果是调用接口,可以使用url的connection.getInputStream()建立字节流 * URL url = new URL("https://..."); * URLConnection connection = url.openConnection(); * BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8") ); */ StringBuilder stringBuilder = new StringBuilder(); String line = new String(); // 缓冲流处理 ; InputStreamReader:字节转为字符,这里用到的装饰器模式 bufferedReader =new BufferedReader(new InputStreamReader(new FileInputStream(file),"UTF-8")); // 等同于bufferedReader =new BufferedReader(new FileReader(File file)); // BufferedReader特有的按行读取方法readLine() while ((line = bufferedReader.readLine()) != null) { stringBuilder.append(line); System.out.println(line); } System.out.println(stringBuilder); } catch (IOException e) { e.printStackTrace(); } catch (Exception e){ e.printStackTrace(); } finally { if (bufferedReader != null) { try { bufferedReader.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
1.txt文件内容 A B c 打印结果 A B C ABC
14 Java的反射原理是什么?getClass和Class.forName()的区别是什么?
每个类编译时都会产生一个对应的Class对象,通过这个对象能够在运行时发现和使用类的信息。
获取一个类对应的Class类的方法:
- Class clazz = 对象.getClass()
- Class clazz = 类名.class
- Class clazz = Class.forName(“类完整的路径”)
其中Class.forName()会初始化该类,执行静态代码块;Class.forName()需要完整的类路径名,getClass()需要对象;Class.forName()和getClass()只能用于引用类型,而类名.class还可以用于基本类型,例如int.class。Class.forName()还需要抛出或捕获ClassNotFoundException类不存在异常,因为无法确定字符串表示的全路径类名是否存在。
其他反射方法:
Class superClass = clazz.getSuperclass();获取对应类的父类
Class[] stuAllClasses=clazz.getDeclaredClasses();当前类所有的内部类
Class[] strClasses=clazz.getClasses();当前类和父类所有公共的内部类和接口
String modifier=Modifier.toString(clazz.getModifiers()
(int 类型));当前类的修饰符Constructor[] constructors = clazz.getConstructors();获取所有的公共构造方法
Constructor[] constructors1 = clazz.getDeclaredConstructors();获取所有的构造方法
Constructor constructors1 = clazz.getDeclaredConstructor(int.class);获取指定参数的构造方法
Field field = clazz.getField(“name”)/getDeclaredField(String name)/getFields()/getDeclaredFields();获取指定字段或公共字段或所有字段
Method method = clazz.getDeclaredMethod(String name, Class<?>… parameterTypes)、getMethod(String name, Class<?>… parameterTypes) \getMethods()\getDeclaredMethods(); 获取方法
A a = (A) clazz.newInstance();创建新实例
15 如何实现一个list类型的深拷贝?Java的Cloneable接口的作用是什么?
引用拷贝:创建一个指向对象的引用变量的拷贝。
对象拷贝:创建对象本身的一个副本。
浅拷贝:浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。
深拷贝:深拷贝把要复制的对象和其所引用的对象都复制了一遍。
Cloneable接口没有内容,用于标记某个类是可以克隆的。只有实现了Cloneable接口,在重写Object的clone方法时才可以调用clone()方法,否则抛出CloneNotSupportedException异常。
// 对象拷贝浅拷贝,重写clone方法,执行super.clone()进行浅拷贝。注意实现Cloneable接口 public Object clone() throws CloneNotSupportedException { return super.clone(); } // (一)对象拷贝深拷贝,重写clone方法,在调用super.clone()复制当前对象后,还要复制内部所有调用的对象。注意实现Cloneable接口 public Object clone() throws CloneNotSupportedException { A a = (A)super.clone(); A.setB((B)a.getB().clone()); retutn a; } // (二)序列化实现深拷贝。注意实现Serializable接口 public Object deepCopy(Object object) throws IOException, ClassNotFoundException { Object o = null; if (object != null) { ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("object")); objectOutputStream.writeObject(object); objectOutputStream.close(); ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("object")); o = objectInputStream.readObject(); objectInputStream.close(); } return o; }
16 Java的泛型的作用是什么?
泛型就是为了参数化类型,在创建对象或调用方法的时候才明确下具体的类型。
泛型作用:使用泛型,编译时会进行类型检查,加强类型安全;不再需要强制类型转换,减少了ClassCastException类型转换的异常;提高了代码的可读性,<>中的类型就是要操作的类型。
在操作集合时常用到泛型,比如
List<Map<String, Object>> list = new ArrayList<>();
在StringUtil工具类里也会用到泛型中,例如判断对象是否为空、两个对象是否相等等,都不需要确定具体的类型,可以使用通配符
?
。public static boolean isNotEmpty(Collection<?> coll){return !isEmpty(coll);}
另外在excel工具类中也会用到泛型,因为导出到excel的数据不需要考虑具体的类型,用
private List<T> list;
声明excel的数据,在使用cell.setCellValue添加到excel里时只需获取对象T vo = (T) list.get(i)
的属性,转为String类型存到excel中。泛型类:
public class Test<T> { T field1; }
泛型接口:
public interface Iterable<T> { }
泛型方法:
public <E> E testMethod1(E e){ return e; }
List<Integer> list = new ArrayList<>(); Integer integer = 11; String string = "13"; list.add(integer); list.add(1); list.add(string); //使用了泛型,编译时就会报错。如果不使用泛型,编译时正常,如果忘记用instanceof处理String类型,运行时就会报错 for (int i = 0; i < list.size(); i++) { int a = list.get(i); //使用了泛型,编译器已经知道list包含的是Integer类型的数据,所以不需要强制转换。又因为自动拆箱、装箱的关系,可以直接使用Integer的原始类型int System.out.println(a); }
类型擦除:泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉。在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如
<T>
则会被转译成普通的 Object 类型,如果指定了上限如<T extends String>
则类型参数就被替换成类型上限。
17 Java的注解有了解,其底层的实现原理是什么?怎么定义一个注解?
Java用
public @interface 注解名 {定义体}
定义一个注解注解的参数格式:访问修饰符(public、default不填)+数据类型(八种基本数据类型、String、Class、enum、Annotation、以上类型的数组)+参数名称()+default默认值,例如
String value() default "";
注解相当于代码中的特殊标记,根据元注解
@Retention
的SOURCE、CLASS 、RUNTIME 三个值,使注解能分别在编译、类加载、运行时被读取。又根据元注解@Target
限制注解可以标注的范围,比如(TYPE接口、类、枚举、注解;FIELD字段、枚举的常量;METHOD方法、PARAMETER方法参数)平时用到很多注解,像Spring框架的@Controller、@Service、@Mapper;多用于检查和标记的Java原生的@Override、@FunctionalInterface、@SuppressWarnings等。
用到的自定义注解:
在用AOP实现日志的时候会用到自定义注解,自定义一个运行时有效,作用在方法上的注解,包含方法所在模块、具体操作等日志参数。然后再AOP的切面类中通过反射获取标记了此注解的方法,得到此方法日志注解的参数,保存或进行其他处理。
在分权分域时也自定义了注解,比如@Org组织架构注解:在拦截器方法里通过反射获取方法上@Org注解的人员id参数,根据人员的权限决定是否对mybatis的绑定sql通过拼接sql语句添加限制。
编译器会对java源码进行解析并生成class文件,而注解也是在编译时由编译器进行处理,编译器会对注解符号处理并附加到class结构中,根据jvm规范和Target的参数,编译器会对应将注解信息存放到类、字段、方法自己的属性上。
18 Java中两个类的关系有多少种?有了解过设计模式么?
继承关系:指定了子类如何特化父类的所有特征和行为。
实现关系:是一种类与接口的关系,表示类对接口所有特征和行为的实现。
关联关系:一个类的成员变量有另一个类的属性和方法
聚合关系:是关联关系的一种,是整体与部分的关系,且部分可以离开整体而单独存在
组合关系:是关联关系的一种,是整体与部分的关系,但部分不能离开整体而单独存在
依赖关系:一个类的实现需要另一个类的协助。例如局部变量、方法的参数是其他类,或者要调用其他类的静态方法
构建器模式builder
装饰器模式
代理模式
单例模式
原型模式
工厂模式
19 Java的collection有几种?Collection和Collections的区别是什么?
在jdk的java.util包下,集合主要派生自Collection接口和Map接口,Collection接口属于对象的集合,Map接口属于键值对的集合。
实现自Collection接口的又有List接口(有序可重复)和Set接口(不可重复)。
List接口的实现类有LinkedList(基于链表,增删快,查询慢,线程不安全),ArrayList(基于数组,查询快,增删慢,线程不安全),Vector(基于数组,线程安全,读写都加锁,每个方法都加了synchronized),CopyOnWriteArrayList(线程安全,写加锁,读不加锁,写时拷贝一个新的数组,写结束后数组指针指向新数组),Stack(基于数组,继承自Vector,线程安全的栈,先进后出)。
Set接口的实现类有HashSet(基于HashMap实现),TreeSet(底层红黑树,基于TreeMap实现),LinkedHashSet(继承自HashSet,保留元素插入顺序)。
实现自Map接口的HashMap(基于哈希映射,线程不安全,key唯一,key和value都可以为null),Hashtable(key和value都不能为null,大部分方法加锁,线程安全,效率低),ConcurrentHashMap(key或value不能为空,根据key的hash值找到桶的位置,锁住头节点,写操作后释放锁;针对读操作采用volatile修饰Node<K, V>[]类型的集合元素),TreeMap(底层红黑树,元素可排序),LinkedHashMap(基于哈希表和双向链表,保留元素插入顺序)。
实际使用:
HashSet常用作去重,将需要去重的元素加到hashset中。
Set<String> hashSet = new HashSet<>(); hashSet.add(locationId); hashSet.contains(locationId);
Collection<E>是Java集合框架中的基本接口;
Collections是Java集合框架提供的一个工具类,其中包含了用于操作或返回集合的静态方法。
比如Collections.sort(list):对指定的list按升序进行排序,list中的元素要实现Comparable接口。
Collections.reverse(list):对指定的list进行反转。
Collections.copy(list,li):将li元素拷贝到list中。
Collections.synchronizedMap(Map):创建线程安全的Map集合。
20 ArrayList、LinkedList和vector的区别?它们是线程安全的么?如果想要线程安全应该要怎么实现?
ArrayList、LinkedList和vector都继承自List接口
ArrayList底层数据结构是数组,查询快、增删慢、线程不安全、效率高。
LisnkedList底层数据结构是链表,查询慢、增删快、线程不安全、效率高。
Vector底层数据结构是数组,查询快、增删慢、线程安全、效率低,每个方法都加了锁。
可以使用CopyOnWriteArrayList实现线程安全