如何理解面向对象和面向过程
面向过程: 把问题分成一个一个步骤,每个步骤用函数实现,依次调用
(函数为了解决特定的子问题)
面向对象:将问题分解成一个一个步骤,对每个步骤进行相应的抽象,形成对象,通过不同对象之间的调用,组合解决问题
(将问题分解成对象,对象与对象之间通信来解决问题:如读者对象使用借书的方法,图书管理员对象处理请求并且更新书籍的状态)
面向对象的三大基本特征
封装,继承,多态
面向对象的五大原则
单一职责原则:一个类只干一件事
开放封闭原则:对拓展开放,对修改封闭
Liskov替换原则:子类必须能够替换基类
依赖倒置原则:程序依赖抽象接口,不依赖具体实现
接口隔离原则:使用多个小的专门的接口,而不要使用大的总接口
为什么Java不支持多继承
如果实现多继承就会存在菱形继承问题,同时支持多继承复杂度也会增加,一个类继承多个父类,可能会继承大量的属性和方法,在修改一个父类时,可能会影响到多个子类,增加代码的耦合度
菱形继承
如果D继承了BC,BC有共同的父类A,这样的话D就获得了两份A的父类,这样就产生了歧义
接口和抽象类的区别
区别 | 接口 | 抽象类 |
方法定义 | 只定义了方法,没有实现代码 | 抽象类有实现代码 |
修饰符 | 接口都是public | 抽象类有private、protected、public、<defaiult>这些修饰符 |
构造器 | 没有构造器 | 有构造器 |
继承和实现 | 可以被实现,可以实现多个 | 可以被继承,只能继承一个 |
职责 | 指定规范 | 为了复用 |
特殊情况:默认方法和静态方法的接口中可以写实现类
interface MyInterface {
void myMethod(); // 方法声明,没有具体实现
default void myDefaultMethod() {
System.out.println("This is a default method."); // 默认方法提供了具体实现
}
static void myStaticMethod() {
System.out.println("This is a static method."); // 静态方法提供了具体实现
}
}
为什么Java要有包装类
Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。为了让基本类型有对象的特征,所以出现了包装类。
基本类型和包装类型的区别
不同点 | 基本类型 | 包装类型 |
默认值不同 | 默认值为0,false,\u0000 | null |
初始化方式不同 | 不需要new | 需要new |
存储方式不同 | 保存在栈上 | 保存在堆上 |
接口返回中,使用基本类型还是包装类
回答:使用包装类
举例:如果某个字段表示费率的是Float rate,在接口中返回时,如果出现接口异常的情况可能会返回默认值,如果使用包装类将返回null,如果不是包装类会返回0.00,因为接口是异常的状态,返回null可以很清楚的反馈出报错,如果返回0.00就无法分辨到底是报错还是正常。
为什么不能使用浮点数表示金额
浮点数是一种用近似值表示小数的方式,浮点数只是近似值,不是精确值,不能用来表示金额,会有精度丢失情况
数据库存储金额用什么类型
使用BigDecimal
使用LongInt从分数位算起,如1元写作100
为什么BigDecimal要用compareTo比较大小而不用equals比较大小
因为equals比较大小会分别比较值(value)和标度(scale),对于0.1和0.10这两个数字,他们的值是一样的,但是他们的精度不一样,所以如果这两个值用equal比较就会返回false。所以要使用compareTo来比较,compareTo只会比较两个数字的值,不会比较精度。
BigDecimal(Double)和BigDecimal(String)有什么区别
因为double是不准确的,0.1对于double来说只是一个近似值,所以使用BigDecimal(Double)转换出来的值也依然是个近似值,如果使用BigDecimal(String),如果是字符串0.1,转换出的值就是0.1,不会出现近似值的问题。
附阿里巴巴Java开发手册
为什么Java中负数的绝对值不一定是正数
例如用Math。abs取绝对值
Math.abs(orderId.hashCode());
得到的结果可能是负数,因为int的取值范围是-2^31 - 2^31 如果取得-2147483648(-2^31 -1)的绝对值就是2147483648(2^31 +1) 会造成越界,超出int的取值范围
String StringBuilder和StringBuffer的区别
String是不可变的,StringBuider和StringBuffer是可变的。StringBuffer是线程安全的,StringBuilder是非线程安全的。
为什么String是不可变的
String的源码
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}
}
1.因为String被定义为了final,所以它不能被继承,方法也无法被覆盖
2.final修饰字符串内容的char[],所以数组一旦被初始化就不能再指向其他数组
3.String类没有提供修改字符串的公共方法,如果要修改字符串其实是新创建了一个字符串
4.所以一些方法,比如substring concat 其实是创建了一个新的字符串,而不是在原字符串的基础上做修改
示意图
String + 是如何实现的
因为String是不可变的,所以String + 其实是将String转为StringBuilder 然后使用append做拼接
示例代码:
String wechat = "Hollis";
String introduce = "Chuang";
String hollis = (new StringBuilder()).append(wechat).append(",").append(introduce).toString();