不同点
1.接口在Java8之前不能写方法实现逻辑,Java8及以后的版本,可以用default关键字写方法的实现。
2.接口中方法都是public的,public可以省略,而抽象类没有这个限制。
3.接口用interface
关键字,抽象类用abstract class
来声明。
相同点:
接口和抽象类都不能直接new。
接口用于制定规范,而抽象类用于代码的复用,比如模板方法模式。
实际开发中,我们用接口来制定规范,直接参与上层业务(Controller)代码的编写,然后具体的实现放到业务层(ServiceImpl),如果实现类有很多相同的逻辑,就可以考虑封装为一个抽象类。
举一个例子,Excel导入,我们可以封装一个导入接口:
public interface ImportService {
/**
* Excel导入
* @param file 上传的excel文件
* @return
*/
Result importData(MultipartFile file);
}
importData 要求输入一个file文件,返回导入的结果。
具体使用是在controller层:
public Result imp(@RequestParam("file") MultipartFile file) {
return userImportService.importData(file);
}
userImportService 就是具体的实现类,是ImportService接口的业务实现。
又因为Excel导入通常都需要这么几个步骤:解析文件 -> 校验数据 -> 保存数据 -> 返回导入结果。
因此我们可以再单独封装一个抽象类。
public abstract class AbstractImportService implements ImportService {
@Override
@Transactional(rollbackFor = Throwable.class)
public Result importData(MultipartFile file) {
checkFileType(file.getOriginalFilename());
List dataList = parseData(file);
ImportResultDto resultDTO = checkData(dataList);
if (resultDTO == null || CollectionUtils.isEmpty(resultDTO.getErrorList())) {
this.saveData(dataList);
return Result.success();
}
return Result.fail(ResultEnum.SYSTEM_OP_ERROR.getCode(), "导入失败", resultDTO);
}
/**
* 解析文件中的数据
*
* @param file 文件
* @return
*/
protected abstract List parseData(MultipartFile file);
/**
* 校验数据
*
* @param dataList 数据
* @return
*/
protected abstract ImportResultDto checkData(List dataList);
/**
* 处理数据
*
* @param dataList 数据
*/
protected abstract void saveData(List dataList);
/**
* 校验文件类型
*/
private static void checkFileType(String fileName) {
if (StringUtils.isNotBlank(fileName) && fileName.contains(".")) {
String suffix = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
if (StringUtils.isNotBlank(suffix) && Arrays.asList(".xls", ".xlsx").contains(suffix)) {
return;
}
}
throw new BusinessInfoException(ResultEnum.SYSTEM_INNER_ERROR, "只支持excel文件导入,请检查!");
}
}
这就是一个典型的模板方法模式,因为这个抽象类已经实现了接口,所以真正的实现类就不需要重复去实现了,只需要继承这个抽象类即可。
先说渊源,Java毕竟是面向对象的语言,很多地方都需要用到象,比如集合类中,是无法塞进基本类型的。
某杠精:你胡扯,我塞入int类型的压根不报错!
public static void main(String[] args) {
List numbers = new ArrayList();
numbers.add(1);
}
这是因为从JDK5开始,就支持了自动拆装箱系统,所以并不是集合可以装进基本类型,而是系统帮我们自动装箱了。
自动装箱都是通过包装类的 valueof()
方法来实现的自动折箱都是通过包装类对象的xxxValue()
来实现的。
已int为例:
Integer a = 1; // 自动装箱
int b = a; // 自动拆箱
相当于
Integer a = Integer.valueOf(1);
int b = a.intValue();
1.注意空指针
有的时候,我们会直接用包装类去参与四则运算,这个时候要注意包装类对象不能是null,因为自动拆箱会调用xxxValue()
方法,就会有报空指针的风险。
public class Test {
public static void main(String[] args) {
Integer a = getCount();
int b = a + 10;
}
private static Integer getCount() {
// 因为某种原因,返回了null
return null;
}
}
恭喜你,喜提 NullPointerException一份。
2.Entity中一定要用包装类
java老鸟都知道,Entity中一定不能用基本类型,因为基本类型有默认初始值,比如int类型默认就是0,如果结合Mybatis,会不小心把这个默认值误更新到数据库!而用Integer则没问题,因为对象默认为null,mybatis默认的策略就是不更新Null值,是安全的。
8.接口的返回值一定要用包装类
我们写接口给外部调用,按理说就必须要是一个明确的值,如果你接口返回boolean,默认是false,这个false我们也不知道究竟是计算后得到的,还是基本类型默认的,这就有二义性。
要解答这个问题,我们得知道,计算机中用32bit来标识一个浮点数,即将一个数字转换为一个32位的二进制数。比如10,就是1010。表达整数没问题,但是并非所有小数都能用二进制表示,我们知道10进制转二进制用的是辗转相除法(除以2),那总有除不尽的时候吧?
所以,即便float有32bit,也是一个近似值。double也是一样的,只是精度更高了,有64bit。
对于金额的计算,Java提供了BigDecimal来表示和运算。
可变性: String是不可变的,StringBuilder和StringBuffer是可变的。
为什么String要设计为不可变的呢?
首先String使用太频繁了,JVM单独开辟一块区域用作字符串缓存池,节省内存的开支。又因为String不可变,所以hashCode也不变,方便命中缓存提高效率,我们用HashMap的时候,你敢说自己不用String作为Key吗?一样的道理。
安全性: StringBuffer是线程安全的,而StringBuilder是非线程安全的。
StringBuilder是非线程安全的,也就意味着效率更高,在方法体里面,本来也不会涉及到线程安全问题,拼接较多的话值得使用。
尽量不要使用 + 来拼接字符串,因为 + 虽然编译时也会转成StringBuilder,但每次都会new一个,反而影响性能。
泛型是JDK5引入的一种新特性,泛型包括泛型类和泛型方法,目的是保证数据类型的安全,常与集合类一起使用。
1.方便:可以提高代码的复用性。
以List接口为例,我们可以将String、Integer等类型放入List中,如不用泛型,存放String类型要写一个List接口,存放Integer要写另外一个List接口,泛型可以很好的解决这个问题。
ArrayList源码:
2.安全
在泛型出之前,通过Object实现的类型转换需要在运行时检查,如果类型转换出错,程序直接GG,可能会带来毁灭性打击。。而泛型的作用就是在编译时做类型检查这无疑增加程序的安全性。
3.什么是泛型擦除?
泛型擦除是指在编译过程中,泛型会被擦除,最终只会得到同一份字节码。因此,泛型主要的功能就是在编译时确保数据类型的正确性,防止把问题留到运行时。