基础问题
- 谈谈你对面向对象的理解 — 结合场景
为何要使用对象编程?
可重复利用,方便拓展
面向对象有三大特征:封装、继承和多态
- 封装:为什么要封装?可以使类的 成员(数据和行为)有选择性的暴露,这里面就有了四个不同的修饰符 — private protected default public
- 继承: 提高代码的可重用性,往往都是子 类继承父 类中的成员;
-
多态: 主要目的是实现 接口。通过 父类的引用指向子类的实例,来动态完成各个不同子类方法的调用。这里面就要涉及到重写和重载问题
-
重写 和 重载 的区别
作用范围不同:重载发生在同一个类中,重写发生在父子类中
对于一个函数来说:
重载
方法名必须相同,参数类型不同、个数不同、顺序不同;
返回值,可以不同;访问修饰符,可以不同
重写
方法名、参数列表必须相同 ;
返回值,范围要小于父类;访问修饰符,范围要大于父类,其中父类修饰符为private,子类就不能进行重写
- 泛型 generic
作用:通过在编译的时候能够检查类型安全(严格意义上是一种伪泛型)
什么时候用:
1、类、接口 — 刷题倒是常用
2、方法 — 来限定传入参数
static int countLegs (List animals ) {
int retVal = 0;
for ( Animal animal : animals )
{
retVal += animal.countLegs();
}
return retVal;
}
常用的通配符:
? 表示不确定的 java 类型
T (type) 表示具体的一个 java 类型
K V (key value) 分别代表 java 键值中的 Key Value
E (element) 代表 Element
- == 和 equals 的区别
这里就要提到Java中存在两种数据类型,基本数据类型 + 引用类型
== 在基本类型中,就是简单的进行数值比较,equals方法在基本类型中不存在
但是针对引用类型时,== 比较的 对象的地址值,Object 的 equals 方法就是比较对象的内存地址;因为所有的类都默认继承Object类,所以理论上这些类的equals方法都是进行地址值比较;
但一般常用的类,比如String,都将equals方法进行重写,主要比较的还是对象的内容
基本数据类型有几种? 6 + 2 数值型 + 字符 + 布尔(byte short int long float double character boolean)
- 为什么重写 equals 时必须重写 hashCode 方法?
hashCode()定义在 JDK 的 Object 类中,这就意味着 Java 中的任何类都包含有 hashCode() 函数。
那么hashCode() 的作用是啥?主要还是应用在HashMap这种需要涉及到散列表的类中,通过它获取对象的散列码,我们判断两个对象是否相等,需要:
通过hashCode() 获得两个对象的散列码,在两者相同的基础上,再进行equals判断内容是否相等。
- Java中存在一个空参构造器干嘛?
构造器的作用:初始化对象。
在Java框架中,往往通过反射来创建对象,之后动态的向对象赋值
Student s = (Student)Class.forName("Student类全限定名").newInstance();
另外,在父子类中,实例化子类对象往往是一个递归过程,也就是说会先实例化父类对象,一直向上,最终是先实例化Object类,这就要求父类要存在一个无参构造器,都为有参构造器的话,子类无法利用super()来实例化父类,另外父类也参与了子类变量的初始化工作。
- 抽象类和接口的区别
变量 抽象类可以是各种类型的,接口只能是 public static final
方法 抽象类可以实现,接口中只能存在 public abstract 方法
静态代码块和静态方法,抽象类可以有,但是接口不可以包含
继承:一个类只能继承一个抽象类,却能实现多个接口
String问题
- 内存问题
String st1 = new String("abc");
Q:这句话在内存中创建了几个对象
A:在内存中创建两个对象,一个在堆内存,一个在常量池,堆内存对象是常量池对象的一个拷贝副本。new这个关键字,就要想到new出来的对象都是存储在堆内存,”abc”属于字符串,字符串属于常量,所以应该在常量池中创建。
String st1 = "abc";
String st2 = "abc";
System.out.println(st1 == st2); //true
System.out.println(st1.equals(st2)); //true
String st1 = new String("abc");
String st2 = "abc";
System.out.println(st1 == st2); //false (一个在堆,一个在常量池)
System.out.println(st1.equals(st2)); //true
- String与 StringBuilder、StringBuffer区别
String因为被声明为 final class,是不可变字符串。因为它的不可变性,所以拼接字符串时候会产生很多无用的中间对象;
StringBuffer 就是为了解决这个问题而提供的一个类。它设计之初是一个线程安全的可修改的字符序列,但在很多情况下我们的字符串拼接操作不需要线程安全,所以就有了StringBuilder 。StringBuilder 是 JDK1.5 发布的,它和 StringBuffer 本质上没什么区别,就是去掉保证线程安全的那部分,减少开销,提升性能。
Object类
1、clone() -- 复制一个对象并返回,是一种浅拷贝(单纯复制对象的引用)
2、equals()、toString() -- 重点 几乎每次都要重写
3、finalize() -- 对象被回收之前调用,交给Garbage Collector处理,自己不要用
4、getClass() -- 对象获取自己的类
5、hashCode() -- 集合章节
6、notify()、notifyAll()、wait() --多线程章节
深浅拷贝 — Object拷贝方法是一种浅拷贝
- 值传递和引用传递有什么区别?
Java中参数传递是值传递
基本类型作为参数被传递时肯定是值传递;
引用类型作为参数被传递时也是值传递,只不过”值”为对应的引用。(对象本身发生变化,但是指向没有变)
- toString()
大部分的时候都需要进行重写
默认的形式为:
public String toString(){
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
//可以看出此处用到两个 Object的方法
static 与 final
static可以用来修饰类的方法、变量,修饰类,只能是内部类
被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。可以在没有创建对象的情况下来进行调用(方法/变量)
主要看 修饰方法 、 变量
1、修饰 方法,最典型的不就是main函数吗?
因为程序在执行main方法的时候没有创建任何对象,因此只有通过类名来访问。
public static void main(String[] args) {}
2、修饰变量
静态变量被所有的对象所共享,在内存中只有一个副本,它 当且仅当在类初次加载时会被初始化。
class MyClass {
public final double i = Math.random();
public static double j = Math.random();
}
//一旦类被加载后,j 的值就是一个确定的值,之后去访问它是不会变化其值的
//但是i 不同,创建不同实例,其值就随机变化一次
final关键字可以用来修饰类、方法和变量
当用final修饰 类时,表明这个类不能被继承,典型的就是String
当final修饰 方法时,被修饰的方法无法被所在类的子类重写(覆写),典型的就是Object类中的getClass()方法
对于一个final 变量,如果是 基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是 引用类型的变量,则在对其初始化之后便不能再让其指向另一个对象(但是对象里面的值是可以改变的,比如final修饰数组)
这里就有了与static的区别,static final 经常组合出现,作为 类常量使用
static 和 final的区别
- static修饰变量,表示在一个类中只保存一份副本( 指向唯一),没说它值不可变;
所有由一个类创建的实例,它们的static变量都指向相同,就像createCarNum,不同实例都可以修改它,但内存中只有一个,存在【方法区】
- final修饰变量,保证 在一个实例中变量值不可变,不同实例就不一定
不同实例,有不同的final变量,一旦确定就不能被修改。
可能会有疑惑,既然final变量要强制赋初值,那么不同实例不就是相同的值吗?反例请看:final double i = Math.random() 这个需要运行时才能确定
举例子:
Static修饰的createCarNum变量,一个类中只有一个指向,创建若干个CarFactory实例,能保证都只操作一个元素
public class CarConstants {
// 全局配置,一般全局配置会和final一起配合使用, 作为共享变量
public static final int MAX_CAR_NUM = 10000;
}
public class CarFactory {
// 计数器 -- 保证不管创建多少个实例都是这一个变量
private static int createCarNum = 0;
public static Car createCar() {
if (createCarNum > CarConstants.MAX_CAR_NUM) {
throw new RuntimeException("超出最大可生产数量");
}
Car c = new Car();
createCarNum++;
return c;
}
}
- 关键词比对:final、finally 和 finalize比较
final用于修饰属性,方法和类,表示属性不可变,方法不可以被覆盖,类不可以被继承。
finally是异常处理语句结构中,表示总是执行的部分。
finallize表示是object类一个方法,在垃圾回收机制中执行的时候会调用被回收对象的方法。允许回收此前未回收的内存垃圾。所有object都继承了 finalize()方法
- 反射
反射可以说是框架的灵魂,使得我们在运行时分析类和执行类的方法。
通过反射你可以获取任意一个类的所有属性和方法,你还可以调用这些方法和属性。
异常
- 异常分类
可以分为两个大类,Error 和 Exception,继承于 顶级父类 Throwable
Error,一般与虚拟机相关
分类:OOM(不断创建对象)、StackOverflow(递归)
Exception又分为:编译时异常、运行时异常
运行时异常
:不可预知,代码运行时,才可能知道
其中 编译时异常
,必须在编写代码时,加入try-catch、throws(将可能的异常延迟到运行时出现),否则无法通过编译
//自己进行处理
public static void method1() {
try {
method();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
//交给调用者来处理异常,调用者要不解决try-catch 或者再丢给上级
public static void method() throws FileNotFoundException{
File file = new File("hello.txt");
FileInputStream fis = new FileInputStream(file);
int data = fis.read();
while(data != -1) {
System.out.println((char)data);
data = fis.read();
}
fis.close();
}
- 你见过哪些异常?
编译时异常:IOException( — FileNotFoundException)、ClassNotFoundException
运行时异常:NullPointerException、
ArrayIndexOutOfBoundsException、
ArithmeticException — 算术异常(除0异常)、
NumberFormatException — 将字符串转为数字、
ClassCastException — 父子类强制转换。
- 异常处理机制
异常有两个过程
一、抛出异常,创建异常对象,交给运行时系统处理
二、处理异常:寻找合适的异常处理器处理异常,否则就终止运行
抛出异常:
① 系统自动生成的异常对象
② 手动生成一个异常对象,并抛出(throw)
处理异常:
① try-catch-finally — 针对可能出现的异常,自己进行处理
② throws — 丢给”上级”进行处理
“throws + 异常类型”写在方法的声明处。指明此方法执行时,可能会抛出的异常类型,由该方法的调用者来处理。
try-catch-finally:真正的将异常给处理掉了。throws的方式只是将异常抛给了方法的调用者,并没有真正将异常处理掉。
try – catch – finally
try { //执行的代码,其中可能有异常。一旦发现异常,则立即跳到catch执行。否则不会执行catch里面的内容 }
catch { //除非try里面执行代码发生了异常,否则这里的代码不会执行 }
finally
将 finally中的 return语句 注释掉
IO
- transient 关键字 — 和序列化有关
一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。
- 什么时候需要序列化? 怎么序列化?
当我们的对象需要跨平台存储和进行网络传输时,就考虑到序列化问题
Java序列化是指把Java 对象转换为字节序列的过程,而Java反序列化是指把字节序列恢复为Java对象的过程
/*
序列化步骤:
1、实现接口 Serializable
2、提供一个全局变量:serialVersionUID 序列版本号
注意: static和transient修饰的成员变量不能被序列化,其他默认可序列化
*/
- IO分类
流按照 传输单位分为 字节流和 字符流
字节流的抽象基类(父类)是: java.io.InputStream、 java.io.OutputStream
字符流的抽象基类(父类)是: java.io.Reader 、 java.io.Writer
访问文件,存在字符文件(word)和二进制文件(音视频)
那么就存在两种文件流:
1、FileInputStream、FileOutputStream — 字节
2、FileReader、FileWriter — 字符!!(别记混了)
File file = new File("hello.txt");
FileReader fr = new FileReader(file);
//字符流 -- 可以合为一步
FileReader fr = new FileReader("hello.txt");
//字节流
File srcFile = new File(srcPath);
fis = new FileInputStream(srcFile);
为了加快传输速度,有了缓冲流,它的操作对象是 字节、字符流,而不是文件
BufferedInputStream ← FileInputStream 字节缓冲流
BufferedReader ← FileReader 字符缓冲流
BufferedReader(new FileReader(new File("hello.txt")));
BufferedInputStream(new FileInputStream(srcFile));
- Java的IO 和 NIO区别
NIO(即异步IO)和 经典IO(也就是常说的阻塞式(B)IO)
NIO主要有Channel、Buffer和Selector 三大核心组件
太难了orz~ 弃之
排序
排序名称 时间复杂度 空间复杂度 稳定 其他性质 直接插入 O(n^2) O(1) 稳定 冒泡排序 O(n^2) O(1) 稳定 能全局定位 快速排序 O(nlogn) O(logn) 递归需要栈 不稳定 能全局定位 选择排序 O(n^2) O(1) 不稳定 能全局定位 堆排序 O(nlogn) O(1) 不稳定 能全局定位 归并排序 O(nlogn) O(n) 需要等长的辅助表 稳定
- Java排序接口
存在两种方式,一种是Comparable的自然排序,另一个为Comparator的定制排序
//Comparable 接口 -- 一劳永逸
重写int CompareTo(Object o)方法
规则:当前对象this大于形参对象obj,则返回正整数
public int compareTo(Object o) {
if(o instanceof Goods){
Goods goods = (Goods) o;
if(this.price > goods.price){
return 1;
} else if(this.price < goods.price){
return -1;
}else{
return 0;
}
}
}
//Comparator -- 创建临时需要比较的规则
重写int compare(Object o1, Object o2) 方法
规则:o1大于o2,返回正
Arrays.sort(arr1, new Comparator(){
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof String && o2 instanceof String){
String s1 = (String) o1;
String s2 = (String) o2;
return -s1.compareTo(s2); //这里实现字符串逆序排序
}
}
});
Original: https://www.cnblogs.com/spongie/p/14652946.html
Author: spongie
Title: Java基础问题
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/579120/
转载文章受原作者版权保护。转载请注明原作者出处!