文章目录
- 1、Object常用方法哪些
- 扩展:对于`hashcode`理解
- 2、java是值传递还是引用传递
- 3、构造方法能不能重载,能不能重写
- 4、static作用
- 5、final 在 java 中的作用,有哪些用法?
- 6、String str=”aaa”,与 String str=new String(“aaa”)一样吗?
- 7、讲下 java 中的 math 类有那些常用方法?
- 8、什么是拆装箱
- 9、一个 java 类中包含那些内容?
- 10、那针对浮点型数据运算出现的误差的问题,你怎么解决?
- 11、一个”.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?
- 12、用最有效率的方法算出 2 乘以 8 等于几?
- 13、`assert`什么时候用
- 14、String是基本数据类型,有什么特性
- 15、java基础类型
- 16、java 中==和 equals 和 hashCode 的区别
- 17、int 与 integer 的区别
- 18、谈谈对 java 多态的理解
- 19、String、StringBuffer、StringBuilder 区别
- 20、抽象类和接口区别
- 21、抽象类和接口意义
- 22、抽象类和接口的应用场景
- 24、String 为什么要设计成不可变的?
- 25、在使用 HashMap 的时候,用 String 做 key 有什么好处?
- 26、BIO,NIO,AIO 有什么区别
- 27、io流分类
- 28、匿名内部类使用外部变量为什么必须是final修饰的
- 29、final修饰hashmap,到底可不可以修改
- 30、string关于split方法使用情况
- 31、深拷贝和浅拷贝
- java异常
- java反射
1、Object常用方法哪些
public int hashCode()
返回该对象的哈希码值。不同对象的,hashCode()一般来说不会相同。
但是,同一个对象的hashCode()值肯定相同。可以理解为逻辑地址值。
public final Class getClass()
返回此 Object 的运行时类,可以通过Class类中的一个方法,获取对象的真实类的全名称。
public String toString()
返回该对象的字符串表示。一般建议重写该方法。然后调用对象的toString()方法
object类的equals()
方法:指示其他某个对象是否与此对象“相等”。默认情况下比较的是对象的引用是否相同,重写后用于一般用于比较成员变量的值是否相等
使用clone()方法采用的是浅克隆的方式
扩展:对于hashcode
理解
hashcode
是什么
hash函数:hash是一个函数,该函数中的实现就是一种算法,就是通过一系列的算法来得到一个hash值。这个时候,我们就需要知道另一个东西,hash表,通过hash算法得到的hash值就在这张hash表中,也就是说,hash表就是所有的hash值组成的,有很多种hash函数,也就代表着有很多种算法得到hash。
常见的hash函数
方法 | 说明 |
---|---|
直接取余法 | f(x)=x mod maxM,maxM一般是不太接近2^t的一个质数 |
乘法取整法 | f(x)=trunc(x/maxM)*max |
平方取中法 |
对象的hashcode
如何来的
hashcode
就是通过hash函数得来的,就是通过某一种算法得到的,hashcode就是在hash表中有对应的位置。
首先一个对象肯定有物理地址,hashcode
代表对象的地址说的是对象在hash
表中的位置,物理地址说的对象存放在内存中的地址,那么对象如何得到hashcode
呢?通过对象的内部地址(也就是物理地址)转换成一个整数,然后该整数通过hash
函数的算法就得到了hashcode
。所以,hashcode
是什么呢?就是在hash表中对应的位置。,举个例子,hash
表中有 hashcode
为1、hashcode
为2、(…)3、4、5、6、7、8这样八个位置,有一个对象A,A的物理地址转换为一个整数17(这是假如),就通过直接取余算法,17%8=1,那么A的hashcode
就为1,且A就在hash表中1的位置。
hashcode
有什么作用
结论:hashcode
的存在主要是为了查找的快捷性,hashcode
是用来在散列存储结构中确定对象的存储地址的=>hashcode
来代表对象就是在hash表中的位置
equals
方法和hashcode
方法的联系
通过前面这个例子,大概可以知道,先通过hashcode来比较,如果hashcode相等,那么就用equals方法来比较两个对象是否相等。
用个例子说明:上面说的hash表中的8个位置,就好比8个桶,每个桶里能装很多的对象,对象A通过hash函数算法得到将它放到1号桶中,当然肯定有别的对象也会放到1号桶中,如果对象B也通过算法分到了1号桶,那么它如何识别桶中其他对象是否和它一样呢,这时候就需要equals方法来进行筛选了。
1、如果两个对象equals
相等,那么这两个对象的HashCode
一定也相同
2、如果两个对象的HashCode
相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置
String
重写hashcode
方法
/**
* Returns a hash code for this string. The hash code for a
* {@code String} object is computed as
* <blockquote><pre>
* s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
* </pre></blockquote>
* using {@code int} arithmetic, where {@code s[i]} is the
* <i>i</i>th character of the string, {@code n} is the length of
* the string, and {@code ^} indicates exponentiation.
* (The hash value of the empty string is zero.)
*
* @return a hash code value for this object.
*/
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
String
重写equals
方法
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
2、java是值传递还是引用传递
答案:值传递
引用传递(pass by reference)是指在调用方法时将实际参数的地址直接传递到方法中,那么在方法中对参数所进行的修改,将影响到实际参数。
值传递(pass by value)是指在调用方法时将实际参数拷贝一份传递到方法中,这样在方法中如果对参数进行修改,将不会影响到实际参数。
关于实际参数和形式参数说明
形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。
实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。
public static void main(String[] args) {
ParamTest pt = new ParamTest();
pt.sout("Hollis");//实际参数为 Hollis
}
public void sout(String name) { //形式参数为 name
System.out.println(name);
}
三种错误测试方案
package com.geekmice.onetomany.transfer;
public class TransferTest {
public static void main(String[] args) {
Student actualStudent = new Student();
actualStudent.setName("实际-胡汉三");
actualStudent.setAge(new Integer(18));
change(actualStudent);
System.out.println("print in main , actualStudent " + actualStudent);
String str = "实际-胡汉三";
change(str);
System.out.println("print in main , str is " + str);
int value = 10;
change(value);
System.out.println("print in main , value is " + value);
}
private static void change(String str) {
str = "临时-胡汉三";
System.out.println("print in change , str is " + str);
}
private static void change(int tempValue) {
tempValue = 11;
System.out.println("print in change , tempValue is " + tempValue);
}
private static void change(Student tempStudent) {
tempStudent.setName("临时-胡汉三");
System.out.println("print in change , tempStudent is " + tempStudent);
}
}
上面,我们举了三个例子,表现的结果却不一样;
其实,我想告诉大家的是,上面的概念没有错,只是代码的例子有问题。来,我再来给大家画一下概念中的重点,然后再举几个真正恰当的例子。
值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
那么,我来给大家总结一下,值传递和引用传递之前的区别的重点是什么。
还拿上面的一个例子来举例,我们真正的改变参数,看看会发生什么?
package com.geekmice.onetomany.transfer;
public class Student {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.geekmice.onetomany.transfer;
public class TransferTest {
public static void main(String[] args) {
Student actualStudent = new Student();
actualStudent.setName("实际-胡汉三");
actualStudent.setAge(new Integer(18));
change(actualStudent);
System.out.println("print in main , actualStudent " + actualStudent);
}
private static void change(Student tempStudent) {
tempStudent = new Student();
tempStudent.setName("临时-胡汉三");
tempStudent.setAge(new Integer(19));
System.out.println("print in change , tempStudent " + tempStudent);
}
输出结果如下
print in change , tempStudent Student{name=‘临时-胡汉三’, age=19}
print in main , actualStudent Student{name=‘实际-胡汉三’, age=18}
我们来画一张图,看一下整个过程中发生了什么,然后我再告诉你,为啥Java中只有值传递。
稍微解释下这张图,当我们在main中创建一个Student对象的时候,在堆中开辟一块内存,其中保存了name和age等数据。然后actualStudent 持有该内存的地址0x123456。当尝试调用change方法,并且actualStudent 作为实际参数传递给形式参数tempStudent 的时候,会把这个地址0x123456交给tempStudent ,这时,tempStudent 也指向了这个地址。然后在change方法内对参数进行修改的时候,即tempStudent = new Student();,会重新开辟一块0X456789的内存,赋值给user。后面对tempStudent 的任何修改都不会改变内存0X123456的内容。
上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在user=new User()的时候,实际参数的引用也应该改为指向0X456789,但是实际上并没有。
通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以,上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数
我们再来看看上面错误的三个例子
同样的,在参数传递的过程中,实际参数的地址0X1213456被拷贝给了形参,只是,在这个方法中,并没有对形参本身进行修改,而是修改的形参持有的地址中存储的内容。
所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。
那么,既然这样,为啥上面同样是传递对象,传递的String对象和Student对象的表现结果不一样呢?我们在change方法中使用 str = “临时-胡汉三”;试着去更改str的值,阴差阳错的直接改变了str的引用的地址。因为这段代码,会new一个String,在把引用交给str,即等价于name = new String(“临时-胡汉三”);。而原来的那个”临时-胡汉三”字符串还是由实参持有着的,所以,并没有修改到实际参数的值。
所以说,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。
结论
Java中的传递,是值传递,而这个值,实际上是对象的引用。
原因
1、值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化
3、构造方法能不能重载,能不能重写
答案:不能重写但是可以重载
案例:
import java.util.Date;
public class A implements Cloneable {
private String userName;
private Integer age;
private Date bornDate;
public A() {
}
public A(String userName, Integer age, Date bornDate) {
this.userName = userName;
this.age = age;
this.bornDate = bornDate;
}
}
// 子类
import java.util.Date;
public class B extends A {
public B() {
}
}
原因:因为构造方法不能被继承,构造方法不可以被重写,因为重写发生在父类和子类之间,要求方法名称相同,而构造方法的名称是和类名相同的,而子类类名不会和父类类名相同,所以不可以被重写。
扩展点:重载和重写区别
规则
重载 | 重写 |
1、参数列表不同(顺序,个数,类型)2、返回类型,访问修饰符可以改变 | 1、参数列表必须与被重写的方法一致 2、返回类型必须一致3、访问权限修饰符大于等于父类 |
重载表示一个类的多态性体现,编译器多态 | 重写表示子父类之前一种多态性体现,运行时多态 |
/**
* 编译器多态
*/
@Test
public void compilePeriod() {
int result = this.calc(1, 2);
logger.info("result结果 : [{}]", result);
double ending = this.calc(1.2f, 2.3f);
logger.info("ending结果 : [{}]", ending);
}
private int calc(int a, int b) {
return a + b;
}
private double calc(double a, double b) {
return a + b;
}
运行期:实现方式有继承父类以及实现某一个接口,即向上转型(父类引用子类实例对象)。如继承父类后方法的重写,编译器只会编译父类,从而确定父类。但是没有确定调用哪个具体的子类,如只会编译父类的静态成员变量,非静态成员变量,方法等,运行时看子类,去调用子类覆盖重写的方法,若子类没有重写,则调用父类的方法(即面向抽象、接口编程)
public abstract class Animal {
public abstract void eat();
}
public class Cat extends Animal {
private static final Logger logger = LoggerFactory.getLogger(Cat.class);
@Override
public void eat() {
logger.info("猫");
}
}
public class Pig extends Animal {
private static final Logger logger = LoggerFactory.getLogger(Pig.class);
@Override
public void eat() {
logger.info("猪");
}
}
@Test
public void runtime() {
Cat cat = new Cat();
Pig pig = new Pig();
showAnimalEat(cat);
showAnimalEat(pig);
}
/**
* 使用Cat和Pig父类作为方法参数,只写一个方法就可以实现调用两个类不同eat方法,实现同一个功能
*/
public static void showAnimalEat(Animal animal) {
animal.eat();
}
4、static作用
在Java中,static是静态修饰关键字。用于修饰类的成员方法、类的成员变量,另外可以编写static代码块来优化程序性能;被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。
(1)static成员变量
java中可以通过static关键字修饰变量达到全局变量的效果。static修饰的变量(静态变量)属于类,在类第一次通过类加载器到jvm时被分配内存空间。
(2)static成员方法
static修饰的方法属于类方法,不需要创建对象就可以调用。static方法中不能使用this和super等关键字,不能调用非static方法,只能访问所属类的静态成员变量和静态方法。
(3)static 代码块
JVM在加载类时会执行static代码块,static代码块常用于初始化静态变量,static代码只会在类被加载时执行且执行一次。
用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
需求:验证给出日期是否在1946-1964年之间
package com.geekmice.staging.staging.dao;
import lombok.Data;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Date;
@Data
public class Person {
private static final Logger log = LoggerFactory.getLogger(Person.class);
public static final String START_DATE = "1946";
public static final String END_DATE = "1964";
private Date birthDate;
public Person(Date birthDate) {
this.birthDate = birthDate;
}
public boolean isBornBoomer(Date date) {
Date startDate = null;
Date endDate = null;
boolean flag = false;
try {
startDate = DateUtils.parseDate(START_DATE, "yyyy");
endDate = DateUtils.parseDate(END_DATE, "yyyy");
flag = date.compareTo(startDate) >= BigDecimal.ROUND_UP && date.compareTo(endDate) < BigDecimal.ROUND_UP;
} catch (ParseException e) {
log.error("入参:[{}],[{}],错误原因:[{}],堆栈信息:[{}]", startDate, endDate, e.getMessage(), e);
}
return flag;
}
public static void main(String[] args) {
Person person = null;
try {
Date tempDate = DateUtils.parseDate("1998", "yyyy");
person = new Person(tempDate);
boolean bornBoomer = person.isBornBoomer(person.getBirthDate());
log.info("tempDate:[{}] 是否在1946到1964年", bornBoomer);
} catch (ParseException e) {
log.info("入参:[{}],错误原因:[{}],堆栈信息:[{}]", person.toString(), e.getMessage(), e);
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UEgIvZ0x-1670247575523)(C:/Users/PMB/AppData/Roaming/Typora/typora-user-images/image-20221123223738261.png)]
静态代码块优化
package com.geekmice.staging.staging.dao;
import lombok.Data;
import org.apache.commons.lang3.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Date;
@Data
public class Person {
private static final Logger log = LoggerFactory.getLogger(Person.class);
public static Date startDate = null;
public static Date endDate = null;
private Date birthDate;
static {
try {
startDate = DateUtils.parseDate("1946", "yyyy");
endDate = DateUtils.parseDate("1964", "yyyy");
} catch (ParseException e) {
log.error("入参:[{}],[{}],错误原因:[{}],堆栈信息:[{}]", startDate, endDate, e.getMessage(), e);
}
}
public Person(Date birthDate) {
this.birthDate = birthDate;
}
public boolean isBornBoomer(Date date) {
boolean flag =
date.compareTo(startDate) >= BigDecimal.ROUND_UP && date.compareTo(endDate) < BigDecimal.ROUND_UP;
return flag;
}
public static void main(String[] args) {
Person person = null;
try {
Date tempDate = DateUtils.parseDate("1998", "yyyy");
person = new Person(tempDate);
boolean bornBoomer = person.isBornBoomer(person.getBirthDate());
log.info("tempDate:[{}] 是否在1946到1964年", bornBoomer);
} catch (ParseException e) {
log.info("入参:[{}],错误原因:[{}],堆栈信息:[{}]", person.toString(), e.getMessage(), e);
}
}
}
(4)static内部类
static内部类可以不依赖外部类实例对象而被实例化,而内部类需要在外部类实例化后才能被实例化
问题1:静态代码块、静态方法、静态变量、普通代码块、构造方法的执行顺序。
package com.geekmice.staging.staging.dao;
public class StaticTest {
// 静态变量 age
static int age = 10;
static {
System.out.println("StaticTest静态代码块");
}
{
System.out.println("StaticTest普通代码块");
}
public static void exam() {
System.out.println("StaticTest静态方法");
}
public StaticTest() {
System.out.println("StaticTest无惨构造方法");
}
}
package com.geekmice.staging.staging.dao;
public class SonStaticTest extends StaticTest {
static {
System.out.println("SonStaticTest静态代码块");
}
{
System.out.println("SonStaticTest普通代码块");
}
public SonStaticTest() {
System.out.println("SonStaticTest无参构造方法");
}
public static void main(String[] args) {
StaticTest.exam();
new SonStaticTest();
System.out.println(StaticTest.age);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g58f5eip-1670247575524)(C:/Users/PMB/AppData/Roaming/Typora/typora-user-images/image-20221123221353280.png)]
分析
当加载SonStaticTest类时,JVM会去调用该类父类的静态代码块,父类的静态代码块运行完后去执行子类的静态代码块。当子类的静态代码块执行完毕后JVM又会去找父类中的普通代码块,当父类的普通代码块执行完毕后又会调用父类的构造方法。父类的构造方法执行完毕后,JVM又会去看子类中有没有普通代码块,如果有则执行。执行完毕后,会去调用子类的构造方法。这就是他们之间的顺序。
注意:java中所有类都是Object类的子类。当父类中含有有参构造而没有显示的写出无参构造,则子类必须用super关键字显示的调用父类的有参构造,否则编译将不通过
5、final 在 java 中的作用,有哪些用法?
final修饰的变量是不能够被改变的。但是这里的“不能够被改变”对于不同的数据类型是有不同的含义
修饰变量
当final修饰的是一个基本数据类型数据时, 这个数据的值在初始化后将不能被改变
当final修饰的是一个引用类型数据时, 也就是修饰一个对象时, 引用在初始化后将永远指向一个内存地址, 不可修改。但是该内存地址中保存的对象信息, 是可以进行修改的
修饰方法
首要作用是锁定方法, 不让任何继承类对其进行修改,不能重写
修饰类
表示类不可被继承。修饰的类所有成员方法都将被隐式修饰为final方法。
6、String str=”aaa”,与 String str=new String(“aaa”)一样吗?
答案:不一样
案例:
public class StringTest {
public static void main(String[] args){
String s1="Hello";
String s2="Hello";
String s3=new String("Hello");
System.out.println("s1和s2 引用地址是否相同:"+(s1 == s2));
System.out.println("s1和s2 值是否相同:"+s1.equals(s2));
System.out.println("s1和s3 引用地址是否相同:"+(s1 == s3));
System.out.println("s1和s3 值是否相同:"+s1.equals(s3));
}
}
// s1和s2 引用地址是否相同:true
// s1和s2 值是否相同:true
// s1和s3 引用地址是否相同:false
// s1和s3 值是否相同:true
结论:首先,这个代码里面有一个 new 关键字,这个关键字是在程序运行时,根据已
经加载的系统类 String,在堆内存里面实例化的一个字符串对象。然后,在这个 String 的构造方法里面,传递了一个“abc”字符串,因为 String 里面的字符串成员变量是 final 修饰的,所以它是一个字符串常量。
接下来,JVM 会拿字面量“abc”去字符串常量池里面试图去获取它对应的 String对象引用,如果拿不到,就会在堆内存里面创建一个”abc”的 String 对象并且把引用保存到字符串常量池里面。
后续如果再有字面量“abc”的定义,因为字符串常量池里面已经存在了字面量“abc”的引用,所以只需要从常量池获取对应的引用就可以了,不需要再创建。
所以,对于这个问题,我认为的答案是如果 abc 这个字符串常量不存在,则创建两个对象,分别是 abc 这个字符串常量,以及 new String 这个实例对象。如果 abc 这字符串常量存在,则只会创建一个对象
7、讲下 java 中的 math 类有那些常用方法?
Math.abs(a)
:取a的绝对值
Math.sqrt(a)
:取a的平方根
Math.cbrt(a)
:取a的立方根
Math.max(a,b)
:取a、b之间的最大值
Math.min(a,b)
:取a、b之间的最小值
Math.pow(a,b)
:取a的b平方
@Test
void t2(){
int intValue = Math.abs(-19);
double sqrt = Math.sqrt(4);
double cbrt = Math.cbrt(9);
int max = Math.max(1, 2);
int min = Math.min(1, 2);
double pow = Math.pow(2, 4);
System.out.println("-19绝对值:"+intValue);
System.out.println("4的平方根:"+sqrt);
System.out.println("9的立方根:"+cbrt);
System.out.println("max最大值:"+max);
System.out.println("min最小值:"+min);
System.out.println("2的4次幂:"+pow);
}
8、什么是拆装箱
在Java
中,数据类型可以分为两大种,Primitive Type
(基本类型)和Reference Type
(引用类型)。基本类型的数值不是对象,不能调用对象的toString()
、hashCode()
、getClass()
、equals()
等方法
装箱就是把byte
,int
,short
, long
,double
,float
,boolean
,char
这些Java
的基本数据类型在定义数据类型时不声明为相对应的引用类型,在编译器的处理下自动转化为引用类型的动作就叫做自动装箱
Integer a=3;//这是自动装箱
// 其实编译器调用的是static Integer valueOf(int i)这个方法,valueOf(int i)返回一个表示指定int值的Integer对象,那么就变成这样:
Integer a=3;
// 等价于 <=> Integer a = Integer.valueOf(3);
拆箱就是把Long,Integer,Double,Float 等将基本数据类型的首字母大写的相应的引用类型转化为基本数据类型的动作就叫自动拆箱
int i = new Integer(2);
// 这是拆箱
// 编译器内部会调用int intValue()返回该Integer对象的int值
尽量避免使用拆箱装箱操作
举例说明
Integer integer1 = 100;
Integer integer2 = 100;
System.out.println("integer1==integer2: " + (integer1 == integer2));// true 自动装箱的两个缓存中的 Integer对象的引用比较
System.out.println("integer1.equals(integer2): " + (integer1.equals(integer2)));// true
System.out.println("integer1.compare(integer2): " + integer1.compareTo(integer2));// 0
Integer integer3 = 200;
Integer integer4 = 200;
System.out.println("integer3==integer4: " + (integer3 == integer4));// false 自动装箱的两个new Integer的引用比较
System.out.println("integer3>integer4: " + (integer3 > integer4)); // false 将两个对象拆箱,再比较大小
System.out.println("integer3.equals(integer4): " + (integer3.equals(integer4)));// true
System.out.println("integer3.compare(integer4): " + integer3.compareTo(integer4));// 0
Integer integer5 = new Integer(100);
Integer integer6 = new Integer(100);
System.out.println("integer5==integer6: " + (integer5 == integer6)); // false 两个不同的Integer对象引用的比较
System.out.println("integer5.equals(integer6): " + (integer5.equals(integer6)));// true
System.out.println("integer5.compare(integer6): " + integer5.compareTo(integer6));// 0
int int1 = 100;
System.out.println("integer1==int1: " + (integer1 == int1));// true Integer缓存对象拆箱后与int比较
System.out.println("integer1.equals(int1): " + (integer1.equals(int1)));// true
System.out.println("integer1.compare(int1): " + integer1.compareTo(int1));// 0
int int2 = 200;
System.out.println("integer3==int2: " + (integer3 == int2));// true Integer对象拆箱后与int比较
System.out.println("integer3.equals(int2): " + (integer3.equals(int2)));// true
System.out.println("integer3.compare(int2): " + integer3.compareTo(int2));// 0
反编译之后
Integer localInteger1 = Integer.valueOf(100);
Integer localInteger2 = Integer.valueOf(100);
System.out.println(new StringBuilder()
.append("integer1==integer2: ")
.append(localInteger1 == localInteger2)
.toString());
System.out.println(new StringBuilder()
.append("integer1.equals(integer2): ")
.append(localInteger1.equals(localInteger2))
.toString());
System.out.println(new StringBuilder()
.append("integer1.compare(integer2): ")
.append(localInteger1.compareTo(localInteger2))
.toString());
Integer localInteger3 = Integer.valueOf(200);
Integer localInteger4 = Integer.valueOf(200);
System.out.println(new StringBuilder()
.append("integer3==integer4: ")
.append(localInteger3 == localInteger4)
.toString());
System.out.println(new StringBuilder().append("integer3>integer4: ")
.append(localInteger3.intValue() > localInteger4.intValue())
.toString());
System.out.println(new StringBuilder()
.append("integer3.equals(integer4): ")
.append(localInteger3.equals(localInteger4))
.toString());
System.out.println(new StringBuilder()
.append("integer3.compare(integer4): ")
.append(localInteger3.compareTo(localInteger4))
.toString());
Integer localInteger5 = new Integer(100);
Integer localInteger6 = new Integer(100);
System.out.println(new StringBuilder()
.append("integer5==integer6: ")
.append(localInteger5 == localInteger6)
.toString());
System.out.println(new StringBuilder()
.append("integer5.equals(integer6): ")
.append(localInteger5.equals(localInteger6))
.toString());
System.out.println(new StringBuilder()
.append("integer5.compare(integer6): ")
.append(localInteger5.compareTo(localInteger6))
.toString());
int i = 100;
System.out.println(new StringBuilder()
.append("integer1==int1: ")
.append(localInteger1.intValue() == i)
.toString());
System.out.println(new StringBuilder()
.append("integer1.equals(int1): ")
.append(localInteger1.equals(Integer.valueOf(i)))
.toString());
System.out.println(new StringBuilder()
.append("integer1.compare(int1): ")
.append(localInteger1.compareTo(Integer.valueOf(i)))
.toString());
int j = 200;
System.out.println(new StringBuilder().append("integer3==int2: ")
.append(localInteger3.intValue() == j)
.toString());
System.out.println(new StringBuilder()
.append("integer3.equals(int2): ")
.append(localInteger3.equals(Integer.valueOf(j)))
.toString());
System.out.println(new StringBuilder()
.append("integer3.compare(int2): ")
.append(localInteger3.compareTo(Integer.valueOf(j)))
.toString());
byte,short,int对应缓存 -128到127
Character对应 0-127
float和double没有自动装箱
9、一个 java 类中包含那些内容?
Java中一个类通常由物种元素组成,即属性、方法、构造方法、代码块以及内部类
10、那针对浮点型数据运算出现的误差的问题,你怎么解决?
原因
浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数,
因为计算机使用的是二进制,而二进制并不能完整表示所有小数,对于无法完整表示的小数,只能尽量用接近值表示,所以浮点数会存在精确度问题,而且 double 类型比 float 类型更精确
问题复现
@Test
void t3() {
double d = 0.1d;
double f = 0.2d;
System.out.println(d + f); // 0.30000000000000004
BigDecimal b1 = new BigDecimal(Double.toString(1.2));
BigDecimal b2 = BigDecimal.valueOf(0.6);
BigDecimal b3 = BigDecimal.valueOf(-0.6);
BigDecimal bigDecimal = new BigDecimal(1000);
BigDecimal bigDecimal2 = new BigDecimal("1000");
System.out.println("加:" + b1.add(b2));
System.out.println("减:" + b1.subtract(b2));
System.out.println("乘:" + b1.multiply(b2));
System.out.println("除:" + b1.divide(b2));
System.out.println("绝对值:" + b3.abs());
System.out.println(bigDecimal.multiply(b1));
System.out.println(bigDecimal2.multiply(b1));
}
答案:使用bigdecimal
问题:两个浮点数相加一定为另一个浮点数吗
答:不一定。如果等号两边都是可以完整表示的小数,那么等式成立。因为等号不成立的根本原因是浮点数无法完整表达部分小数
扩展:为什么阿里巴巴禁止使用BigDecimal的equals方法做等值比较?
equals方法比较的是值和精度,compareTo方法比较是值;
注意这里说的是bigdecimal构造方法,字符串
11、一个”.java”源文件中是否可以包含多个类(不是内部类)?有什么限制?
一个Java源文件中可以定义多个类,但是最多只有一个类被public修饰,并且这个类的类名与文件名必须相同。若这个文件中没有public的类,则文件名可随便命名(前提是符合规范)。要注意的是,当用javac指令编译有多个类的Java源文件时,它会给该源文件中的每一个类生成一个对应的.class 文件
12、用最有效率的方法算出 2 乘以 8 等于几?
2<<3
这道题的解法:2<<3
; 表示 2向左移 3位 <=> 2 * 2^3
(2的3次方) = 2 * 8
= 16;
因为一个数左移 n位,就相当于 2的 n次方,那么一个数乘以 8只要将其左移 3位即可,而位运算是CPU是直接支持的,效率最高
所以 2乘以 8等于几的最有效方法是 2<<3
。
// 验证移位
int leftMove = 2 << 3;
int rightMove = 2 >> 3;
logger.info("2<<3=[{}],2>>3=[{}]", leftMove, rightMove); // 16,0
19:58:14.205 [main] INFO com.geekmice.staging.staging.dao.JacksonTest - 2<<3=[16],2>>3=[0]
13、assert
什么时候用
简单的说,如果希望在不满足某些条件时阻止代码的执行,就可以考虑1断言来阻止它。
断言:终止程序,方法入参不合理结束,使用断言,我们可以用一个assert
语句来代替if
和throw
语句。
import org.springframework.util.Assert;
Assert.notNull(param,msg);
param:可以是字符串,对象,list,map
Assert.isTrue(param,msg);
param:布尔类型
14、String是基本数据类型,有什么特性
不是
不可变:private final byte[] value
; String类是final类,也即意味着String类不能被继承,并且它的成员方法都默认为final方法,String对象一旦被创建就是固定不变的了,对String对象的任何改变都不影响到原对象,相关的任何change操作都会生成新的对象
实例化:直接赋值和构造方法实例化,在直接赋值进行实例化的时候,会在堆中开辟一块空间,并且自动保存在对象池中等下次重复使用;而在构造方法实例化中会在堆中开辟两块空间,其中一块为垃圾空间等待回收.
使用字符串常量池:每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串
15、java基础类型
数值型:byte(位)、short(短整数)、int(整数)、long(长整数)、
浮点型:float(单精度)、double(双精度)、
字符型:char(字符)
布尔型:boolean(布尔值)
16、java 中==和 equals 和 hashCode 的区别
1、==若是基本数据类型比较,是比较值,若是引用类型,则比较的是他们在内存中的存 放地址。
2、equals 是 Object 类中的方法,Object 类的 equals 方法用于判断对象的内存地址引用 是不是同一个地址(是不是同一个对象)。若是类中覆盖了 equals 方法,就要根据具体代 码来确定,一般覆盖后都是通过对象的内容是否相等来判断对象是否相等。
3、hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
hashCode()计算出对象实例的哈希码,在对象进行散列时作为 key 存入。之所以有 hashCode 方法,因为在批量的对象比较中,hashCode 比较要比 equals 快;核心思想就是将集合分成若干个存储区域(可以看成一个个桶),每个对象可以计算出一个哈希码,可以根据哈希码分组,每组分别对应某个存储区域,这样一个对象根据它的哈希码就可以分到不同的存储区域(不同的区域)
所有对于需要大量并且快速的对比的话如果都用equals()去做显然效率太低,所以解决方式是,每当需要对比的时候,首先用hashCode()去对比,如果hashCode()不一样,则表示这两个对象肯定不相等(也就是不必再用equals()去再对比了),如果hashCode()相同,此时再对比他们的equals(),如果equals()也相同,则表示这两个对象是真的相同了,这样既能大大提高了效率也保证了对比的绝对正确性!
因为在散列表中,hashCode()相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得出键值对相等。
17、int 与 integer 的区别
-
Ingeter是int的包装类,int的初值为0,Ingeter的初值为null。
-
Integer变量必须实例化后才能使用;int变量不需要;
-
Integer实际是对象的引用,指向此new的Integer对象;int是直接存储数据值 ;
-
Integer是int的包装类;int是基本数据类型;
-
关于int,Integer之间进行比较说明
-
无论如何,Integer与new Integer不会相等。不会经历拆箱过程,new出来的对象存放在堆,而非newInteger常量则在常量池(在方法区),他们的内存地址不一样,所以为false。
-
两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false。因为java在编Integer i2 = 128的时候,被翻译成:Integer i2 = Integer.valueOf(128);而valueOf()函数会对-12到127之间的数进行缓存。
-
两个都是new出来的,都为false。还是内存地址不一样。
-
int和Integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比。
-
-
装箱 就是自动将基本数据类型转换为包装器类型;拆箱 就是自动将包装器类型转换为基本数据类型,从反编译的结果可以看到,在装箱的时候,调用了 Integer 的 valueOf(int i) 方法;而在拆箱的时候,则调用了 Integer 的 intValue() 方法
-
案例说明
public class IntAndIntegerTest{
public static void main(String[] args){
/* new出来的对象存放在堆,而非new的Integer常量则在常量池(在方法区),
他们的内存地址不一样,所以为false
*/
Integer i = 100 ;
Integer i1 = new Integer(100) ;
System.out.println("i == i1 ?" + (i==i1)) ; // false
System.out.println("*********************") ;
/*
两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false。
因为java在编译Integer i2 = 128的时候,被翻译成:Integer i2 = Integer.valueOf(128);而valueOf()函数会对-128到127之间的数进行缓存。
*/
Integer i3 = 127 ;
Integer i2 = 127 ;
Integer i4 = 129 ;
Integer i5 = 129 ;
System.out.println("i3 == i2 ? "+(i3==i2)) ;// true
System.out.println("i5 == i4 ? "+(i5==i4)) ;// false
System.out.println("*********************") ;
Integer i6 = new Integer(129) ;
Integer i7 = new Integer(129) ;
System.out.println("i7 == i6 ? "+(i7==i6)) ;
System.out.println("*********************") ;
/*
int和Integer(无论new否)比,都为true,因为会把Integer自动拆箱为int再去比。
*/
int i8 = 100 ;
Integer i9 = 100 ;
Integer i10 = new Integer(100) ;
System.out.println("i8 == i9 ? "+(i8==i9)) ;
System.out.println("i8 == i10 ? "+(i8==i10)) ;
}
}
8.关于Integer.valueOf()方法说明
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
IntegerCache 的源码如下
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
通过源码可以看到,在 Interger 的 valueOf 方法中,如果数值在 [-128, 127] 范围的时候,就会去 IntegerCache.cache 这个数组寻找有没有存在的 Integer 对象的引用,如果有,则直接返回对象的引用,如果没有(超过了范围),就新建一个 Integer 对象。这个是 cache 数组中已经存在的对象,只是一部分,数组下标范围为 [0,255],对应数值范围是 [-128,127]
9.经典面试题
面试题1
Integer a = 100;
Integer b = 100;
Integer a1 = 200;
Integer b1 = 200;
Integer a2 = new Integer(100);
a == b;
a1 == b1;
a == a2;
分析:
a == b 原因
a 和 b 先各自调用 Integer 的 valueOf() 方法进行装箱,因为数值 a 和 b 的数值是100的,在 [-128,127] 这个范围之间,所以执行 Integer a = 100 这行代码的时候,在装箱的时候,会先初始化一个长为 255 的 的数组,里面的值的范围是 -128~127,变量 a 的值直接从 cache 对应的位置拿,之后执行到 Integer b = 100 的时候,便去这个已经实例化好了的数组中拿对应位置的值,该位置和变量 a 在数组中的位置相同,最终,这两个变量拿到的是同一个数组的同一个位置的变量,此时肯定是相同的
a1 == b1 原因
a 和 b 的数值为200时,在调用 valueOf() 方法进行装箱的时候,因为值不在 [-128,127] 间,因此没有实例化 cache 数组和从数组中拿值的过程,因此只能各自实例化一个 Integer 的对象,此时这两个对象的地址是不同,因此不同
a == a2 原因
a 和 a2 比较的时候,由于 a 调用 valueOf() 进行自动装箱,而 a2 已经是 Integer 对象,相当于两个不同对象之间的比较,因为地址不同,所以肯定不等
面试题2
Double i1 = 100.0;
Double i2 = 100.0;
Double i3 = 200.0;
Double i4 = 200.0;
i1 == i2
i3 == i4
分析:
结果:false、false。
为什么呢?由于 Double 在进行装箱的时候也会调用 valueOf() 这个方法,因此看一下源代码
public static Double valueOf(double d) {
return new Double(d);
}
public Double(double value) {
this.value = value;
}
可以看到,Double 并没有 Integer 的缓存机制,而是直接返回了一个新的 Double 对象,因此以上的四个引用都是指向不同的对象的,所以不同
PS
为什么 Double 类的 valueOf() 方法会采用与 Integer 类的 valueOf() 方法不同的实现呢?
很简单:在某个范围内的整型数值的个数是有限的,而浮点数却不是。
我又查看了其他几种数据类型的 valueOf 源码
其中,Double 和 Float 没有缓存机制,都是直接返回新的对象;Integer、Short、Byte、Character 有缓存机制
面试题3
Boolean b1 = false;
Boolean b2 = false;
Boolean b3 = true;
Boolean b4 = true;
b1 == b2
b3 == b4
b1 == b3
分析:
结果:true、true、false
为什么会这样呢?看一下 Boolean 的 valueOf() 的源代码
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
其中的 TRUE 和 FALSE,代表两个静态成员属性
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
因此可以知道了,每次传入的 true 或 false,都是指向同一个 Boolean 对象,因此他们的引用肯定相同了
面试题4
Integer a = 10;
Integer b = 20;
Integer c = 30;
Integer d = 30;
Integer e = 300;
Integer f = 300;
Integer g = a + b;
c == d;
e == f;
c == g;
c == (a + b);
c.equals(a + b);
分析:
结果:true、false、true、true、true
先注意这句话:当 "=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)
前两个没什么好说的
第三个:由于运用了算术运算符(+),因此右边的算式在进行计算之前会先调用 intVal() 方法进行拆箱,在进行相加,然后得到的结果30之后,由于前面是 Integer g,因此还会再调用 valueOf() 方法进行装箱。由于此时 cache() 数组中已经有了 30 了,因此直接指向缓存池中的 30 这个 Integer 对象。此时 c == g 比较的还是对象的地址是否相同
第四个:由于右边是 a+b,包含算术运算,因此会调用 intVal() 方法,将左边的 c 进行拆箱,之后又分别对 a 和 b 进行拆箱,即一共调用了三次拆箱过程,最后比较的是数值大小,而不是地址
第五个:通过 equals 的源码看到,传入的参数需要是一个对象,这就意味着,和 equals 比较的时候,一定是装完箱之后的结果
a + b 的时候,a 和 b 各自执行 intVal() 方法进行拆箱,完成相加的计算之后,再对计算出的结果执行 valueOf() 方法进行装箱,此时再传入 equals 方法进行比较。equals 比较两个对象的时候,直接比较两个值拆箱之后的结果,即比较的是数值,本例中两个数值相同,所以输出为 true
面试题5
Integer a = 10;
Integer b = 20;
Long g = 30L;
Long h = 20L;
g == (a + b);
g.equals(a + b);
g.equals(a + h);
结果:true、false、true
和上面一样,同样是使用拆箱,第一题比较的拆完箱之后的值,但是需要注意的是,Integer 类型调用 Integer.valueOf 进行拆箱,而 Long 类型调用 Long.valueOf 进行拆箱
最后一个结果是 true,因为 a + h 会使小的精度转为大的精度,最终的 30 是 Long 类型的,因此结果是 true
18、谈谈对 java 多态的理解
首先明确java面向对象的三大特性是封装,继承以及多态。只有有了封装,继承,才会有多态
多态指的是对象的同一行为的不同形态,通俗点讲就是,调用同一个方法,但是呈现的功能却是不一样的
编译器多态和运行期多态
编译期就确定了调用类的哪一个方法,如方法的重载,相同的方法名,但是根据参数列表的不同,在编译期就确定了调用哪个方法
多态的体现:某个对象调用同一个方法,表现出不同的功能(由传入的参数决定)
运行期:实现方式有继承父类以及实现某一个接口,即向上转型(父类引用子类实例对象)。如继承父类后方法的重写,编译器只会编译父类,从而确定父类。但是没有确定调用哪个具体的子类,如只会编译父类的静态成员变量,非静态成员变量,方法等,运行时看子类,去调用子类覆盖重写的方法,若子类没有重写,则调用父类的方法(即面向抽象、接口编程)
多态的体现:当接口或父类调用同一个方法时,会呈现出不同的功能(由实际子类来决定)
如何实现?
继承、重定、向上转型,在多态中需要将子类的引用赋值给父类对象,只有这样该引用才能 够具备调用父类方法和子类的方法。
19、String、StringBuffer、StringBuilder 区别
都是操作字符串
String 类中使用字符数组保存字符串,因有 final 修饰符,String 对象是不 可变的,每次对 String 操作都会生成新的 String 对象,这样效率低,且浪费内存空间。但 线程安全。
StringBuilder 和 StringBuffer 也是使用字符数组保存字符,但这两种对象都是可变的,即 对字符串进行 append 操作,不会产生新的对象。它们的区别是:StringBuffer 对方法加 了同步锁,是线程安全的,StringBuilder 非线程安全。
关联知识点
1、StringBuffer为什么是线程安全的?
StringBuffer底层使用synchronized同步修饰方法,因此是线程安全的
2、为什么StringBuffer使用synchronized修饰方法就能保证线程安全?
synchronized是一个同步锁,在Java中每个类对象都可以作为锁,synchronized同步锁使用的关键在于对谁加锁~
synchronized修饰普通方法synchronized methodA(){//操作},是对当前对象加锁~
synchronized修饰静态方法static synchronized void methodB(){//操作},是对当前类的class对象(所有此类的对象)加锁~
synchronized修饰代码块methodC(obj){synchronized(obj) {//操作}},是对括号中的对象加锁 ~
因此,使用synchronized修饰方法时,会对方法中的相关对象进行加锁,如果某个线程抢先调用了该方法,那么将独占相关对象的锁,其他线程如果此时调用到该方法的相关对象时,会被阻塞~
3、循环字符串拼接
原始代码
public class Main{
public static void main(String[] args){
String s1 = "Hello"+"world"+"!";
String s2 = "xzy";
String s3 = s1+s2;
}
}
反编译之后
public class Main{
public static void main(String args[]){
String var1 = "Hello world!";
String var2 = "xzy";
(new StringBuilder()).append(var2).append(var1).toString();
}
}
反编译后的代码,在for循环中,每次都是new了一个StringBuilder,然后再把String转成StringBuilder,再进行append。而频繁的新建对象当然要耗费很多时间了,不仅仅会耗费时间,频繁的创建对象,还会造成内存资源的浪费
结论
如果要操作少量的数据用 = String
单线程操作字符串缓冲区 下操作大量数据 = StringBuilder
多线程操作字符串缓冲区 下操作大量数据 = StringBuffer
20、抽象类和接口区别
1)抽象类可以提供成员方法实现的细节,而接口只能存在抽象方法;
2)抽象类的成员变量可以是各种类型,而接口中成员变量只能是 public static final 类型;
3)接口中不能含有静态方法及静态代码块,而抽象类可以有静态方法和静态代码块;
4)一个类只能继承一个抽象类,用 extends 来继承,却可以实现多个接口,用 implements 来实现接口。
21、抽象类和接口意义
接口
1)有利于代码的规范,对于大型项目,对一些接口进行定义,可以给开发人员一个清晰的 指示,防止开发人员随意命名和代码混乱,影响开发效率。
2)有利于代码维护和扩展,当前类不能满足要求时,不需要重新设计类,只需要重新写了 个类实现对应的方法。
3)解耦作用,全局变量的定义,当发生需求变化时,只需改变接口中的值即可。
抽象类
抽象类是用来提供子类的通用性,用来创建继承层级里子类的模板,减少代码编写,有利于 代码规范化。
22、抽象类和接口的应用场景
抽象类的应用场景:1)规范了一组公共的方法,与状态无关,可以共享的,无需子类分别 实现;而另一些方法却需要各个子类根据自己特定状态来实现特定功能;
接口的应用场景: 2)定义一组接口,但不强迫每个实现类都必须实现所有的方法,可用抽象类定义一组方法 体可以是空方法体,由子类选择自己感兴趣的方法来覆盖;
限定参数类型的上界,参数类型必须是 T 或 T 的子类型,但对于 List,不能通过 add()来加入元素,因为不知道是 T 的哪一种子类; 限定参数类型的下界,参数类型必须是 T 或 T 的父类型,不能能过 get()获取 元素,因为不知道哪个超类;
泛型上限:?extends 类型
泛型下限:?super 类型
泛型擦除:
JVM在实现泛型的时候是使用擦除功能来实现的,也就是JVM本身是不支持泛型的,比如:
List integer = new ArrayList();
这样的一段代码,针对调用者而言语法结构发生了改变,但是实际上针对JVM而言,它会去解析泛型语法,让后使它变成了原始版本:
List integer = new ArrayList();
这种情况就称为泛型的“擦除”,因为JVM本身不支持泛型,所以在实现的时候如同楼上的说的,这确实是Java泛型的悲哀,它没有真正使用泛型的标准原理实现JVM对泛型的编译,只是玩了个小小的花招
主要原因是JVM在推出泛型的时候实际上并没有对JDK的编译器进行改写,所以使用了擦除功能,也就是说,针对JVM本身而言:
上边两段代码都是一模一样的,没有区别,因为使用的“擦除”功能,在“擦除”过程里面,它会针对Java泛型的新语法进行类型检测操作,JVM本身不支持泛型,在编译器进行泛型代码的编译的时候,其实是使用了“擦除”功能,就是JVM在编译带泛型的代码的时候,实际上对带泛型的代码进行了类型检查,然后“擦除”泛型代码中的类型支持,转换为普通类型进行编译。这里有一个新概念成为“外露”类型——单独出现而不是位于某个类型中的类型参数如(List中的T)针对T类型而言,T的上界就是Object。这一项技术的功能极其强大,我们可以使几乎所有泛型类型的精度增强,但是与JVM兼容。
24、String 为什么要设计成不可变的?
1)字符串常量池需要 String 不可变。因为 String 设计成不可变,当创建一个 String 对象时, 若此字符串值已经存在于常量池中,则不会创建一个新的对象,而是引用已经存在的对象。 如果字符串变量允许必变,会导致各种逻辑错误,如改变一个对象会影响到另一个独立对象。
2)String 对象可以缓存 hashCode。字符串的不可变性保证了 hash 码的唯一性,因此可以缓 存 String 的 hashCode,这样不用每次去重新计算哈希码。在进行字符串比较时,可以直接 比较 hashCode,提高了比较性能;
3)安全性。String 被许多 java 类用来当作参数,如 url 地址,文件 path 路径,反射机制所 需的 Strign 参数等,若 String 可变,将会引起各种安全隐患。
25、在使用 HashMap 的时候,用 String 做 key 有什么好处?
HashMap 内部实现是通过 key 的 hashcode 来确定 value 的存储位置,因为字符串是不可变的,所以当创建字符串时,它的 hashcode 被缓存下来,不需要再次计算,所以相比于其他对象更快。
26、BIO,NIO,AIO 有什么区别
同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用方便,并发处理能力低。
NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO 的操作基于事件和回调机制
BIO:同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的
NIO (New I/O): NIO是一种同步非阻塞的I/O模型,在Java 1.4 中引入了NIO框架,对应 java.nio 包,提供了 Channel , Selector,Buffer等抽象。NIO中的N可以理解为Non-blocking,不单纯是New。它支持面向缓冲的,基于通道的I/O操作方法。 NIO提供了与传统BIO模型中的 Socket 和 ServerSocket 相对应的 SocketChannel 和 ServerSocketChannel 两种不同的套接字通道实现,两种通道都支持阻塞和非阻塞两种模式。阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发
AIO (Asynchronous I/O): AIO 也就是 NIO 2。异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。AIO 是异步IO的缩写,虽然 NIO 在网络操作中,提供了非阻塞的方法,但是 NIO 的 IO 行为还是同步的。对于 NIO 来说,我们的业务线程是在 IO 操作准备好时,得到通知,接着就由这个线程自行进行 IO 操作,IO操作本身是同步的。查阅网上相关资料,我发现就目前来说 AIO 的应用还不是很广泛,Netty 之前也尝试使用过 AIO,不过又放弃了
27、io流分类
从大的方面来分可以分为字节流和字符流。
字符流提供了提供了reader和writer;字节流提供了outputstream 和inputstream.
28、匿名内部类使用外部变量为什么必须是final修饰的
原因:
1.生命周期不同: 为什么必须局部变量加final关键字呢?因为局部变量直接存储在栈中,当方法执行结束,非final的局部变量就被销毁,而局部内部类对局部变量的引用依然存在,当局部内部类要调用局部变量时,就会出错,出现非法引用 。简单来说,就是非final的局部变量的生命周期比局部内部类的生命周期短,是不是直接可以拷贝变量到局部内部类?这样内部类中就可以使用而且不担心生命周期问题呢?也是不可以的,因为直接拷贝又会出现第二个问题,就是数据不同步;
这个解释有一定局限性,局部变量确实会被回收,但是根据反编译结果可以看出,局部变量已经被传入内部类中,在传入的时候进行拷贝,既然我已经有了这样一份拷贝,那我就不需要原来的局部变量,那这个局部变量被回收,跟我有什么关系呢;
2.数据不同步:内部类并不是直接使用传递进来的参数,而是将传递进来的参数通过自己的构造器备份到自己内部,表面看是同一个变量,实际调用的是自己的属性而不是外部类方法的参数,如果在内部类中,修改了这些参数,并不会对外部变量产生影响,仅仅改变局部内部类中备份的参数。但是在外部调用时发现值并没有被修改,这种问题就会很尴尬,造成数据不同步。所以使用final避免数据不同步的问题
那为什么添加final修饰的局部变量,就可以被局部内部类引用呢?若定义为final,则java编译器则会在内部类NewAge内生成一个外部变量的拷贝,而且可以既可以保证内部类可以引用外部属性,又能保证值的唯一性。也就是拷贝了一个变量的副本,提供给局部内部类,这个副本的生命周期和局部内部类一样长,并且这个副本不可以修改,保证了数据的同步
public class Hello{
public static void main(String[] args){
String str = "haha";
new Thread(){
@Override
public void run(){
System.out.println(str);
}
}.start();
}
}
以上代码我们通过匿名内部类方式开启子线程,在其中访问外部变量
反编译之后
public class Hello$1 extends Thread{
private String val$str;
Hello$1(String paramString){
this.val$str=paramString;
}
public void run(){
System.out.println(this.val$str);
}
}
可以看出匿名内部类的构造方法传入一个参数,这个参数就是底层传入str,也就是说匿名内部类之所以可以访问局部变量,是因为底层将局部变量传入匿名内部类,并且在匿名内部类以成员变量形式存在,这个值的传递过程就是通过匿名内部类构造方法完成的。
29、final修饰hashmap,到底可不可以修改
答案:final修饰的变量,其实是它的引用不能被修改,里面的内容,比如说map、list等,是可以修改其内容的。
30、string关于split方法使用情况
public void splitMethod(){
// 字符串分隔
System.out.println(ErrorCodeConstants.DZ0000);
// "\\|" 匹配任何空白字符,包括了空格,制表符,换行 \t\r\n\v\f
String[] strArray="响应成功|0000".split("\\|");
for(int i=0;i<strArray.length;i++){
System.out.println(strArray[i]+",");
}
}
31、深拷贝和浅拷贝
浅拷贝
浅拷贝就是获得拷贝对象的引用,而不是正真意义上的拷贝一个对象,如下
Student student = new Student("胡斐", 18, LocalDate.now());
Student student1 = student ;
此时引用变量student 和student 1同时指向了同一个堆中的内存空间,变量student1 只是复制了实例Student 的引用地址,并不是重新在堆中开辟了一个新的空间位置,来完整的复制实例Student
深拷贝
深拷贝则是拷贝了源对象的所有值,所以即使源对象的值发生变化时,拷贝对象的值也不会改变。深拷贝则是真正意义上的拷贝
浅拷贝代码
School.java
package com.geekmice.onetomany.copy;
public class School implements Cloneable{
private String schoolName;
private String schoolLocation;
private Student student;
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
public String getSchoolLocation() {
return schoolLocation;
}
public void setSchoolLocation(String schoolLocation) {
this.schoolLocation = schoolLocation;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public School(String schoolName, String schoolLocation, Student student) {
this.schoolName = schoolName;
this.schoolLocation = schoolLocation;
this.student = student;
}
@Override
public String toString() {
return "School{" +
"schoolName='" + schoolName + '\'' +
", schoolLocation='" + schoolLocation + '\'' +
", student=" + student +
'}';
}
@Override
protected School clone() throws CloneNotSupportedException {
return (School)super.clone();
}
}
Student.java
package com.geekmice.onetomany.copy;
import java.time.LocalDate;
public class Student {
private String name;
private Integer age;
private LocalDate createDate;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public LocalDate getCreateDate() {
return createDate;
}
public void setCreateDate(LocalDate createDate) {
this.createDate = createDate;
}
public Student(String name, Integer age, LocalDate createDate) {
this.name = name;
this.age = age;
this.createDate = createDate;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", createDate=" + createDate +
'}';
}
}
ShallowTest.java
package com.geekmice.onetomany.copy;
import java.time.LocalDate;
public class ShallowTest {
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println("浅拷贝验证开始");
Student student = new Student("胡斐", 18, LocalDate.now());
School school = new School("耶鲁大学", "美国", student);
School cloneSchool = school.clone();
cloneSchool.setSchoolName("北京大学");
Student student1 = cloneSchool.getStudent();
cloneSchool.setStudent(student1);
System.out.println("拷贝之前对象school引用数据student的hashcode:"+school.getStudent().hashCode());
System.out.println("拷贝之后对象school引用数据student的hashcode:"+cloneSchool.getStudent().hashCode());
System.out.println("浅拷贝验证结束");
}
}
深拷贝实现
School.java
package com.geekmice.onetomany.copy;
import java.io.*;
public class School implements Serializable {
private String schoolName;
private String schoolLocation;
private Student student;
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
public String getSchoolLocation() {
return schoolLocation;
}
public void setSchoolLocation(String schoolLocation) {
this.schoolLocation = schoolLocation;
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public School(String schoolName, String schoolLocation, Student student) {
this.schoolName = schoolName;
this.schoolLocation = schoolLocation;
this.student = student;
}
@Override
public String toString() {
return "School{" +
"schoolName='" + schoolName + '\'' +
", schoolLocation='" + schoolLocation + '\'' +
", student=" + student +
'}';
}
public Object deepClone() throws Exception{
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return ois.readObject();
}
}
Student.java
package com.geekmice.onetomany.copy;
import java.io.Serializable;
import java.time.LocalDate;
public class Student implements Serializable {
private String name;
private Integer age;
private LocalDate createDate;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public LocalDate getCreateDate() {
return createDate;
}
public void setCreateDate(LocalDate createDate) {
this.createDate = createDate;
}
public Student(String name, Integer age, LocalDate createDate) {
this.name = name;
this.age = age;
this.createDate = createDate;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", createDate=" + createDate +
'}';
}
}
DeepTest.java
package com.geekmice.onetomany.copy;
import java.time.LocalDate;
public class DeepTest {
public static void main(String[] args) throws Exception {
System.out.println("深拷贝开始");
Student student = new Student("苗若兰", 16, LocalDate.now());
School school = new School("清华大学", "北京", student);
School cloneSchool = (School)school.deepClone();
System.out.println("school中引用数据student的hashcode:"+cloneSchool.getStudent().hashCode());
System.out.println("cloneSchool中引用数据student的hashcode:"+school.getStudent().hashCode());
System.out.println("深拷贝结束");
}
}
深拷贝开始
school中引用数据student的hashcode:1463801669
cloneSchool中引用数据student的hashcode:589873731
深拷贝结束
java异常
32、异常结构
Java 通过面向对象的方法进行异常处理,一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常,把各种不同的异常进行分类,并提供了良好的接口。在 Java 中,每个异常都是一个对象,它是 Throwable 类或其子类的实例。当一个方法出现异常后便抛出一个异常对象,该对象中包含有异常信息,调用这个对象的方法可以捕获到这个异常并可以对其进行处理。Java 的异常处理是通过 5 个关键词来实现的:try、catch、throw、throws 和 finally。
33、最常见到的 5 个 runtime exception
java.lang.NullPointerException
空指针异常;出现原因:调用了未经初始的对象或者是不存在的对象。
java.lang.ClassNotFoundException 指定的类找不到;出现原因:类的名称和路径加载错误;通常都是程序 试图通过字符串来加载某个类时可能引发异常。
java.lang.NumberFormatException字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。
java.lang.IndexOutOfBoundsException数组角标越界异常,常见于操作数组对象时发生。
java.lang.IllegalArgumentException方法传递参数错。
java.lang.ClassCastException数据类型转换异常。
java.lang.NoClassDefFoundException未找到类定义错误
java.lang.InstantiationException实例化异常。
java.lang.NoSuchMethodException` 方法不存在异常。
34、异常的处理机制有几种
异常捕捉:try…catch…finally,异常抛出:throws
35、try catch fifinally,try 里有 return,finally 还执行么?
执行,并且 finally 的执行早于 try 里面的 return
1、不管有木有出现异常,finally 块中代码都会执行
2、当 try 和 catch 中有 return 时,finally 仍然会执行
3、finally 是在 return 后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的值保存起来,管 finally 中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数返回值是在 finally 执行前确定的
4、finally 中最好不要包含 return,否则程序会提前退出,返回值不是 try 或 catch 中保存的返回值。
36、Excption 与 Error 包结构
37、Thow 与 thorws 区别
抛出异常有三种形式,一是throw,一个throws,还有一种系统自动抛异常。下面它们之间的异同
一、系统自动抛异常
当程序语句出现一些逻辑错误、主义错误或类型转换错误时,系统会自动抛出异常
二、throw
1、throw是语句抛出一个异常,一般是在代码块的内部,当程序现某种逻辑错误时由程序员主动抛出某种特定类型的异常
2、定义在方法体内
3、创建的是一个异常对象
4、确定了发生哪种异常才可以使用
三、throws
1、在方法参数列表后,throws后可以跟着多个异常名,表示抛出的异常用逗号隔开
2、表示向调用该类的位置抛出异常,不在该类解决
3、可能发生哪种异常
38、Exception和error区别
Error 和 Exception 都是Throwable的子类, 在java中只有Throwable类型的实例才可以被抛出或者捕获,它是异常处理机制的基本类型.
1,Exception 和 Error体现了java平台设计者对不同异常情况的分类, Exception是程序正常运行中,可以预料的意外情况,可以被捕获,进行相应的处理.
2.Error 是指正常情况下,不大可能出现的情况,绝大部分的Error 都会导致程序处于非正常的,不可恢复的状态, 不需要捕获, 常见的OutOfMemoryError 是Error的子类.
3.Exception 分为可检查异常(checked) 和 不可检查异常(unchecked).可检查异常在源代码里必须显式的进行捕获处理,这是编译期检查的一部分,不可检查异常是指运行时异常, 比如NullPointerException, ArrayIndexOutOfBoundsException之类, 通常是可以编码避免的逻辑错误,具体根据需要来判断是否需要捕获,并不会在编译期强制要求.
39、final,finally,finalize 的区别
final: 修饰类,此类不能继承;修饰方法,此方法不能重写;修饰变量,此变量不可修改即常量。
finally: 异常处理关键字try…catch…finally 一直会执行的代码
finalize: Object类中的一个方法,垃圾回收的方法
java反射
40、反射
对于任意一个类,都能够知道这个类的所有属性和方法
对任意一个对象,都能 调用它的任意一个方法和属性
本质:得到class对象之后,反向获取对象的各种属性;使用的前提条件=》必须先得到代表的字节码class,class类表示.class字节码问题
应用场景
1 jdbc
链接数据库时候使用反射 Class.forName()
;
2 Spring
框架也用到很多反射机制,最经典的就是xml
的配置模式。Spring
通过 XML
配置模式装载 Bean
的过程:
- 将程序内所有
XML
或Properties
配置文件加载入内存中; Java
类里面解析xml
或properties
里面的内容,得到对应实体类的字节码字符串以及相关的属性信息;- 使用反射机制,根据这个字符串获得某个类的
Class
实例; - 动态配置实例的属性
3 代理模式和aop应用
41、Java
反射创建对象效率高还是通过 new
创建对象的效率高
原因:通过new创建对象的效率比较高。通过反射时,先找查找类资源,使用类加载器创建,过程编译器处理,校验,比较繁琐,所以效率较低
/**
* @description new创建对象
*/
@Test
public void t11() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
for (int i = 0; i < 100000; i++) {
Object o = new Object();
}
stopWatch.stop();
logger.info("耗费时间:[{}]", stopWatch.shortSummary()); // 10万数据 6173100ns
}
/**
* @description 反射创建对象
*/
@Test
public void t12() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try {
for (int i = 0; i < 100000; i++) {
Class<?> aClass = Class.forName("java.lang.Object");
}
} catch (ClassNotFoundException e) {
logger.warn("错误原因:[{}],堆栈信息:[{}]", e.getMessage(), e);
}
stopWatch.stop();
logger.info("耗费时间:[{}]", stopWatch.shortSummary()); // 10万数据 173170800ns
}
42、Java 反射 API
Class类
Class cls = String.class;
String s = “hello”; Class cls = s.getClass();
Class.forName(“java.lang.String”) // 全类名
Field
Field getField(name) // 根据字段名获取某个 public 的field(包括父类);
Field getDeclaredField(name) // 根据字段名获取当前类的某个 Field(不包括父类,可以获取private属性)
Field[] getFields() // 获取所有 public 的 field(含父类);
Field[] getDeclaredFields() // 获取当前类的所有 filed (不包括父类);
// Field对象包含的信息:
Object get(Object obj) // Returns the value of the field represented by this Field, on the specified object.
getName() // 返回字段名称
getType() // 返回字段类型
getAnnotation(A.class) // 获取A注解
field.set(k,v) // 设置字段值,指定的实例,待修改的值
Method
Method getMethod(name,Class…) // 获取某个 public 的 Method(包括父类);
Method getDeclaredMethod(name,Class…) // 获取当前类的某个 Method(不包括父类);
Method[] getMethods() // 获取所有 public 的Method(包括父类);
Method[] getDeclaredMethods() // 获取当前类的所有Method(不包括父类)
参考代码
package com.geekmice.staging.staging.util;
import org.apache.commons.lang3.StringUtils;
import java.lang.reflect.Field;
import java.util.Objects;
public class ValidateUtils {
public static String validateClass(Object object) {
// 校验对象是否为空
String errorMsg = "";
if (Objects.isNull(object)) {
errorMsg = "请求参数不能为空";
return errorMsg;
}
Class<?> clz = object.getClass();
// 获取对象所有字段
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
// 字段允许访问
field.setAccessible(true);
// 获取字段类型
String fieldType = field.getType().toString();
// 一旦发现除了String类型的字段使用该注解,校验失效
if (!StringUtils.equals("class java.lang.String", fieldType) && Objects.nonNull(field.getAnnotation(ValidateColumn.class))) {
return "仅仅支持String类型的校验,所有校验失效";
}
// 开始校验
try {
ValidateColumn column = field.getAnnotation(ValidateColumn.class);
if (Objects.nonNull(column)) {
boolean notNull = column.Null();
if (notNull) {
String fieldValue = null == field.get(object) ? "" : field.get(object).toString();
// 校验非空
if (StringUtils.isEmpty(fieldValue)) {
errorMsg = column.NullMsg();
}
}
boolean len = column.Len();
if (len) {
String fieldValue = null == field.get(object) ? "" : field.get(object).toString();
if (StringUtils.length(fieldValue) > column.maxLen()) {
errorMsg = column.errorLenMsg();
}
}
}
} catch (Exception e) {
errorMsg = "注解校验失败";
}
}
return errorMsg;
}
}
43、除了使用 new 创建对象之外,还可以用什么方法创建对象?
Class对象的newInstance()方法
Student stu = Student.class.newInstance();
构造函数对象的newInstance()方法
Constructor<Student> constructor = Student.class.getInstance();
Student stu = constructor.newInstance();
对象反序列化
ObjectInputStream in = new ObjectInputStream (new FileInputStream("data.obj"));
Student stu3 = (Student)in.readObject();
Object对象的clone()方法
public class Dog implements Cloneable{
String name;
int age;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
Dog d1=new Dog();
Dog d2=(Dog) d1.clone();
System.out.println(d1==d2); // false
// 实验结果为false,因为clone是真实在内存中重新划分一块区域来存储新的对象,d1和d2是两个不同的对象所以返回结果值为false
44、什么叫对象序列化,什么是反序列化,实现对象序列化需要做哪些工作?
把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
对象的序列化主要有两种用途:
1)把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
2)在网络上传送对象的字节序列。
@Test
void t4() throws IOException, ClassNotFoundException {
// 序列化
A a = new A();
a.setUserName("熊爱红");
a.setAge(new Integer(18));
a.setBornDate(new Date());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("E:\\Documents\\onetomany\\a.txt")));
objectOutputStream.writeObject(a);
objectOutputStream.close();
System.out.println("序列化已经完成,生成a.txt文件");
System.out.println("+++++++++++++++++++++++++");
// 反序列化
ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("a.txt")));
A a1 = (A) inputStream.readObject();
System.out.println("反序列结果:");
System.out.println(a1);
}
45、谈一下对序列化和反序列化理解
是什么:对象与字节相互转换
为什么:其实我们的对象不只是存储在内存中,它还需要在传输网络中进行传输,并且保存起来之后下次再加载出来
多个项目进行RPC调用时,需要在网络上传输JavaBean对象,而网络上只允许二进制形式的数据进行传输,这时则需要用到序列化技术。
如何用:
输,这时则需要用到序列化技术。Java的序列化技术就是把对象转换成一串由二进制字节组成的数组,然后将这二进制数据保存在磁盘或传输网络。而后需要用到这对象时,磁盘或者网络接收者可以通过反序列化得到此对象,达到对象持久化的目的
46、反射使用步骤
@Test
void t5() throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//指定初始容量15来创建一个HashMap
HashMap m = new HashMap();
//获取HashMap整个类
Class<?> mapType = m.getClass();
//获取指定属性,也可以调用getDeclaredFields()方法获取属性数组
Field threshold = mapType.getDeclaredField("threshold");
//将目标属性设置为可以访问
threshold.setAccessible(true);
//获取指定方法,因为HashMap没有容量这个属性,但是capacity方法会返回容量值
Method capacity = mapType.getDeclaredMethod("capacity");
//设置目标方法为可访问
capacity.setAccessible(true);
//打印刚初始化的HashMap的容量、阈值和元素数量
System.out.println("容量:" + capacity.invoke(m) + " 阈值:" + threshold.get(m) + " 元素数量:" + m.size());
for (int i = 0; i < 17; i++) {
m.put(i, i);
//动态监测HashMap的容量、阈值和元素数量
System.out.println("容量:" + capacity.invoke(m) + " 阈值:" + threshold.get(m) + " 元素数量:" + m.size());
}
}
1、创建线程四种方案
2、bio,nio,aio
3、SpringBoot多线程定时任务
4、java到底是值传递还是引用传递
5、静态代码块、静态方法、静态变量、普通代码块、构造方法的执行顺序。
6、异常处理