常见面试题及解答|Java基础(一)

164 阅读13分钟

基础

Q:Integer和int的区别

int是基本数据类型,Integer是类,因为Java很多地方需要类,比如泛型里 Java有自动装箱和自动拆箱 对象类都是final的 -128到127之间,Integer.valueOf返回的是缓存,不新建对象。这个范围可以通过JVM参数设置

Q:Java和C++的区别

Java和C++都是面向对象的,都有封装继承和多态 C++有指针,需要程序员手动释放内存,Java没有指针,不需要手动释放内存 C++支持多继承,Java不支持多继承

Q:重写、重载

重载是一个类里,函数名相同,参数不同,返回值和访问权限不要求 重写是父类和子类之间,对象方法,static方法不行,是基于对象方法的。父类的方法在子类里必须可见,private不行。要求函数名相同,参数相同,返回值子类小于父类,编译异常子类小于父类,访问修饰符,子类大于等于父类。属性没有覆盖的概念,是一种隐藏。

Q:重载的方法,参数个数相同,都是引用类型,调用的时候如果每个参数都传入null,会有什么反应?编译报错?运行报错?

IDE报错,编译不通过

Q:static方法可以被子类重写吗

不可以,不能被重写,只能同名隐藏 父类引用指向子类对象,只会调用父类的静态方法,不具有多态性。可以继承

Q:初始化

局部变量(函数内的变量)不初始化会报错,成员变量有默认的初始值,数组对象是null

Q:instanceof

对象是不是这个类的对象,返回true或false 接口和父类都是true

Q:transient瞬时的

某成员变量有transient,不参与序列化

Q:代码块、静态代码块、构造代码块

代码块:在方法内部,目的是控制变量生命周期,及早释放,避免命名冲突 静态代码块,类里static{},作用是初始化类里的static变量。 在jvm加载类的时候执行,只执行一次,在主方法之前 构造代码块:初始化成员变量,构造方法共同的初始化可以提到构造代码块 每次创建对象时调用,在构造方法之前

Q:Object中的方法

1.clone 2.toString 3.hashcode 4.equals 5.getclass 6.wait 7.notify 8.notifyAll 9.finalize

Q:null

null可以强制转换成任何类,可以用来调用静态方法 ((People)null)testMethod(); 对象方法会报空指针

Q:实参个数可变

public void test(int....i){} 不确定个实参,可以是0个,可以不传,是一个数组 public void test(int[] i) 会报错,多重定义

Q:参数传递

Java是值传递,对象数组是拷贝地址

Q:实例化方法

1.new 2.clone,不调用构造方法 3.反序列化 4.反射newInstance,会调用无参的构造方法,没有回报错 5.工厂方法:String str = String.valueOf(23)

Q:String

Java虚拟机中有一个字符串池,是共享的,final的,不用担心改变 new String()会在堆里分配,而且不会再放到常量池中,要放进常量池需要intern()方法,这个方法放进常量池,返回常量池对象

Q:字符串内存分配

String s1 = new String("abc"); // 内存分配 String s2 = "abc"; if (s1 == s2) { // 能不能进来? n } String s3 = "abc"; if (s2 == s3) { // 能不能进来? y } s2 = new String("abc"); if (s1 == s2) { // 能不能进来? y } s2 = s1; s1 = "bcd"; // s2 = ? ab

Q:String、StringBuilder、StringBuffer

String不可变 StringBuilder可变,但是不线程安全,性能好 StringBuffer,线程安全

Q:final、finally、finalize

  • final修饰变量、类、方法,代表不可变,final static 代表常量 JVM会对方法,变量,类进行性能优化 final变量必须在声明的时候初始化或者在构造方法初始化 匿名类中所有变量都必须是final的
  • finally 异常中使用,代表一定会执行,用来断开连接,释放资源等,return会覆盖try,返回 除非try中exit或者退出虚拟机了
  • finalize finalize 方法是Object方法,在对象被回收前调用,作用是释放c++层的内存等 如果方法是纯Java写不需要写这个方法,finalize只执行一次,可以对象复活一次 垃圾回收的时候,判断这个对象是否覆盖了finalize方法,没有覆盖直接回收,有finalize方法,且没有执行过就放入F-Queue队列,由一个线程执行该队列中的finalize方法,执行完垃圾回收器再次判断,是否可达,不可达回收,否则复活 如果在执行finalize方法时,出现异常,垃圾回收器不会报异常,只会该对象的finalize执行退出

Q:抽象类和接口的区别

1.接口没有构造方法,抽象类有构造方法 2.抽象类可以有普通方法,接口所有方法都是抽象的 3.抽象类中有普通成员变量,接口没有普通成员变量 4.接口方法只能是public,抽象类可以是public、protected、默认类型 5.接口中可以有静态方法,但是必须实现 接口中可以包含静态成员变量,但是必须是public static final,抽象类都可

Q:多态

父类的引用指向不同的子类对象,可以不修改代码,让引用绑定到不同的类 父类引用可以调用父类中定义的所有属性和方法,只存在子类的方法和属性就不能访问了

内部类

Q:为什么需要内部类

1.通过内部类来隐藏信息 2.实现多重继承

Q:内部类为什么可以访问外部类变量

含有一个外部类的this指针 内部类分:成员内部类、局部内部类、静态内部类、匿名内部类 内部类可以无条件访问外部所有属性和方法,包括private和静态成员 和普通的成员一样 匿名内部类在new后面,只能生成一个对象 编译的时候由系统自动起名Outter$1.class 没有类名,不能写构造方法,编译器会生成一个不带参数的构造方法 要初始化可以用构造代码块 不能有静态方法(没有类名怎么调用啊)

Q:内部类为什么持有外部对象,static内部类为什么没有

内部类会生成一个私有构造函数,传入外部对象 class Outer$Inner{ private Outer&Inner(Outer paramOuter){} } 这是静态内部类编译后的字节码文件,编译器并没有为它添加额外的构造函数,所以它其实和我们的外部类没有任何关系,这是写在同一个.java源文件中而已.

Q:匿名内部类使用的参数为什么必须是final的

匿名内部类编译后也是单独一个class文件,仅仅有一个外部类的引用,外部传入参数,其实是参数的拷贝,内部的修改并不影响外部。这样从程序员角度看是同一个,内部改变了外部确没变,为了保持一致,所以规定用final避免形参可变。 内部类,实际会在匿名匿名内部类的构造方法中拷贝一份要使用的外部变量。这样,就牵扯两份变量的同步问题,比如内部类实例被回收、匿名内部类被实例化之前外部变量被更改等问题。解决该问题的方法,就是强制使用final,保证引用不可变

Q:内部类为什么可以访问外部private变量

Q:静态内部类和非静态的区别

静态内部类和静态成员变量类似 不能使用外部非静态成员变量或方法 普通内部类都可以方法 静态内部类可以声明普通成员变量和方法,普通内部类不能声明static成员变量和方法 静态内部类可以单独初始化,不依赖于外部 Inner i = new Outer.Inner() //普通内部类初始化 Outer o = new Outer(); Inner i = o.new Inner();

Q:枚举

枚举也是类,都继承了Enum,每一个变量是类的对象,还是static final修饰的 构造方法是private,没有办法创建枚举值。可以有成员变量,成员方法 public enum Color{ RED,BLUE,BLACK } 编译后 final enum Color{ public static final Color RED; public static final Color BLUE; //静态内部类,对对象初始化 static{ new Color[1] } private Color(){} } 继承Enum方法 (1)ordinal():返回枚举的顺序 Color.RED.ordinal(); 返回0 (2)compareTo():比较两个枚举的顺序 (3)values()返回全部枚举的数组 Color[] colors = Color.values(); (4)toString()返回常量的名 Color c = Color.RED; System.out.println(c); 返回RED (5)valueOf,返回这个名词的常量 Color.valueOf(“BLUE”); 返回Color.BLUE

Q:IO

字节流InputStream/OutputStream 字符流 Reader/Writer 字符流是字节流读取时查了指定的码表 字节流以字节(8bit)为单位,字符流以字符为单位 字节流能处理所有类型的数据,如图片、AVI,字符流只能处理字符数据 优先使用字节流

反射

Q:什么是反射

反射机制指的是程序在运行时能够获取自身的信息 在Java中,给定类的名字,那么就可以通过反射机制来获得类的所有信息。 Class.forName("com.mysql.jdbc.Driver.class").newInstance(); hibernate、struts都是用反射机制实现的。(android蓝牙)

Q:为什么要有反射,反射是怎么解决这个问题的,Android里如何用的项目里你是如何用反射的

反射可以在运行的时候动态的加载,而不是固定的,增加了灵活性 比如实例化一个对象new Person(),如果想实例化其他的对象,不想修改源码,就可以用反射class.forName(“person”).newInstance(),把加载哪个类写到配置文件中

Q:三种获取Class对象方法

1.Class的静态方法 Class.forName(“java.lang.String”); 2.使用类的.class语法 String.class; 3.对象的getClass()方法 String str = “aa”; Class<?> classType = str.getClass();

Q:反射创建对象,获取构造器

不带参的构造方法生成对象有两种方式 1.newInstance()直接生成即可,调用无参构造方法 Class clazz = String.class; Object obj = clazz.newInstance(); 2.用Constructor的构造方法调用newInstance(参数) Class clazz = Customer.class; //获得无参构造方法 Constructor cons = clazz.getConstructor(new Class[]{}); //通过构造方法来生成对象,构造方法调 con.newInstance() Object obj = cons.newInstance(new OBject[]{}); //传入参数 如果要通过带参的构造方法生成对象,只有一种方式 Class<?> clazz = Customer.class; Constructor cons2 = clazz.getConstructor(new Class[]{String.class,int.class}); Object obj2 = cons2.newInstance(new Object[]{“zhangsan”,20}); 私有构造方法加一个cons2.setAccessible(true); Teacher clazz =Teacher.class; Constrctor cons = clazz.getConstructor(String.class); Teacher t = (Teacher)cons.newInstance(“aa”);

Q:反射获取私有属性并修改

1.获得CLass 2.创建对象 3.getField或getDeclaredField获取Field 4.修改 Class<?> clazz = Class.forName("com.ang.Teacher"); 通过有参构造获取对象,有参构造必须为public类型 Constructor c = clazz.getConstructor(String.class,int.class);//字节码阶段,构造方法的参数只能是字节码对象; Teacher t = (Teacher) c.newInstance("王艳",100); //创建实例对象 Field field = clazz.getDeclaredField("name");//获取私有成员变量 field.setAccessible(true);//去除私有权限 field.set(t,”李代理”); //传入对象和这个属性的新值

Q:反射如何调用私有方法

Class<?> clazz = Class.forName("com.ang.Teacher"); Constructor c = clazz.getDeclaredConstructor(); //获取私有无参构造 c.setAccessible(true); //去除私有权限 Teacher t = (Teacher) c.newInstance();//通过私有构造获取实例 Method method = clazz.getDeclaredMethod("add", int.class,int.class);//字节码阶段反射有参方法是,需传入参数类型的字节码 method.setAccessible(true); //去除私有权限 int num = (Integer) method.invoke(t, 20,8); //invoke第一个是类的实例,第二个是参数 如果是静态方法,第一个参数,传入null method.invoke(null,20,8); getMethods()获得该类所有公有的方法,getDeclaredMethod(parameterTypes)获得该类某个方法,getDeclaredMethods()获得该类所有方法。带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法。其他的Annotation、Field、Constructor也是如此

Q:final成员变量修改

Java反射可以修改final成员变量吗? 分别两种情况 1.在定义的时候就初始化了值,private final String name = “huang”;因为编译器final类型的数据自动被优化了,所有用到的地方都替换成立常量,所以是return “huang”,而不是return this.name。所以不能修改,向阻止编译自动优化,可以改为final String name = (null!=null?”ddd”:”huang”) 2.如果是在构造函数初始化,可以修改 final Class<?> clz = p.getClass(); final Field nameField = clz.getDeclaredField("name"); nameField.setAccessible(true); nameField.set(p, String.valueOf("huang.damon"));

equals、hashcode

Q:equals、hashcode

为了提高效率,先判断hashcode,hashcode不相同,equals肯定不相同,就不用算了 如果hashcode相同,再判断equals。这样实际调用equals的次数就大大减少了 在HashSet中的使用,set不允许有相同的对象,就先hashcode,在equals,相同就不存 如果你的类不放在hashcode为基础的容器就不需要重新hashcode 但是如果要放到hashset或者hashmap,他们equals相同,hashcode必须相等 result = 29*getName().hashCode()+getBirthday.hashCode();

Q:怎么重写hashcode方法,String的hashcode方法

用equals中涉及到的属性来计算,equal相同的hashcode要相同 首先定义个初始值,一般来说取17 然后根据属性类型解析 1.boolean: hashcode = a?1:0 2.int,byte,shot,char : hashcode = (int)b 3.long: hashcode = c^c>>>32 4.float: hashcode = d.hashCode() 5.double: hashcode = e.hashCode() 6.引用:若为null则为0,否则递归调用引用的hashcode 7.数组,String:s[0]31 ^ (n-1) + s[1] * 31 ^ (n-2) + ..... + s[n-1] 第三步 result = result31 +hashcode

Q:自定义重写equals的同时还需要重写哪个方法

一般应该重写equals()还有hashCode()

Q:如果没有重写hashcode会发生什么

有些集合是不允许重复元素出现的,这两个方法用来保证集合中没有重复元素 如果只重写了equals方法,两个对象equals返回了true,但是没有重写hashcode方法 集合还是会插入元素,这样集合中就出现重复元素了 hashMap的put方法是先调用hashCode定位到数组的位置,如果该数组的位置上已经存在元素了,即table[i]!=null,那么遍历链表,调用equals方法判断key是否相等,如果equal不相同,说明没有找到这个key,表明这个key不存在,则会插入 如果没有重写hashcode方法,那么就无法定位到同一个数组位置,集合还是会插入元素,这样集合中就出现重复元素了,重写equals就没有意义了 如果重写了hashcode就能定位到数组相同的位置,就可以遍历这条单向链表,用equals判断这两个对象是否相同,如果相同就覆盖,不相同,就插入到链表的头节点处

Q:为什么要有拷贝?解决了什么问题,怎么解决的,什么场景使用

从一个已有的对象,创建一个相似或相同的对象,是模板设计模式,简化对象的创建 值直接拷贝,引用也直接拷贝,不行,怎么办?引用也实现拷贝,遍历值拷贝 或者先把对象序列化,再反序列化,是另一个对象了 浅拷贝:值直接拷贝,引用和对象,拷贝的地址,指向同一块内存,没有新开辟地址 深拷贝:引用和对象新开辟地址,也全部拷贝 clone方法是浅拷贝,implements Cloneable 要实现深拷贝 1.类里的每一个对象都重新clone方法,在顶层类,调用所有对象的clone方法 age是一个成员变量,age里实现clone方法,Student里有age,再调用age的clone方法 2.通过对象的序列化实现深拷贝 先把这个对象序列化,再反序列化给另一个对象 oos.writeObject(stu1); Student stu2 = (Student)ois.readObject(): clone()默认是深拷贝还是浅拷贝,如何让clone实现深拷贝