面向对象
三大特性
- 封装
- 继承
- 多态
封装
数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
-
减少耦合
-
减轻维护的负担
-
有效地调节性能
-
提高软件的可重用性
继承
继承实现了 IS-A 关系
继承应该遵循里氏替换原则,子类对象必须能够替换掉所有父类对象
多态
多态分为编译时多态和运行时多态:
- 编译时多态主要指方法的重载
- 运行时多态指程序中定义的对象引用所指向的具体类型在运行期间才确定
运行时多态有三个条件:
- 继承
- 覆盖(重写)
- 向上转型
类图
- 泛化关系
用来描述继承关系,在 Java 中使用 extends 关键字。
- 实现关系
用来实现一个接口,在 Java 中使用 implement 关键字。
- 聚合关系
表示整体由部分组成,但是整体和部分不是强依赖的,整体不存在了部分还是会存在。
- 组合关系
和聚合不同,组合中整体和部分是强依赖的,整体不存在了部分也不存在了。
- 关联关系
表示不同类对象之间有关联,这是一种静态关系,与运行过程的状态无关,在最开始就可以确定
- 依赖关系
和关联关系不同的是,依赖关系是在运行过程中起作用的。A 类和 B 类是依赖关系主要有三种形式:
- A 类是 B 类中的(某中方法的)局部变量;
- A 类是 B 类方法当中的一个参数;
- A 类向 B 类发送消息,从而影响 B 类发生变化;
数据类型
八个基本类型:
- boolean/1 (1位)
- byte/8 (1字节)
- char/16 (2字节)
- short/16 (2字节)
- int/32 (4字节)
- float/32 (4字节)
- long/64 (8字节)
- double/64 (8字节)
缓存池
new Integer(1) 与 Integer.valueOf(1) 的区别在于:
- new Integer(1) 每次都会新建一个对象
- Integer.valueOf(1) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。
先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容.(Integer缓存池的大小默认为 -128~127)
String
String 被声明为 final,因此它不可被继承
内部使用 char 数组存储数据,该数组被声明为 final(JDK8),JDK9内存使用byte数组存储数据。
JDK9之前,JVM因为String使用char数组存储,每个char占2个字节,所以即使字符串只需要1字节/字符,它也要按照2字节/字符进行分配,浪费了一半的内存空间。
JDK9是怎么解决这个问题的呢?一个字符串出来的时候判断,它是不是只有Latin-1字符,如果是,就按照1字节/字符的规格进行分配内存,如果不是,就按照2字节/字符的规格进行分配。
不可变的好处
1.可以缓存hash值
2.String Pool的需要
如果一个 String 对象已经被创建过了,那么就会从 String Pool 中取得引用。只有 String 是不可变的,才可能使用 String Pool
3. 安全性
String 经常作为参数,String 不可变性可以保证参数不可变。(如网络连接配置)
4. 线程安全
String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
String, StringBuffer 和 StringBuilder
1.可变性
- String不可变
- StringBuffer和StringBuilder可变
2.线程安全 String不可变,是线程安全 StringBuffer是线程安全,内部使用synchronized进行同步 StringBuilder是线程不安全
String.intern()
使用 String.intern() 可以保证相同内容的字符串变量引用同一的内存对象。
1、最简单的比如:String s1 = “a” + “b”;创建了几个对象?
答:最多1个,多个字符串常量相加会被编译器优化为一个字符串常量即"ab",如果字符串常量池不存在,则创建该对象。
2、相对简单的比如:String s1 = new String(“ab”);创建了几个对象?
答:1个或2个,使用new实例化对象,必然会在堆区创建一个对象,另外一个就是如果在字符串常量池中不存在"ab"这个对象,则会创建这个"ab"常量对象。
3、稍微难一点的比如:String s2 = new String(“a”) + new String(“b”);创建了几个对象?
答:至少4个,最多6个
1个new StringBuilder()和2个new String()
另外"a"、"b"可能会在常量池新建对象
最后1个是,StringBuilder()的toString()方法底层实现是new String(value, 0, count)
有的同学可能会有疑问,那"ab"字符串不会在常量池中也创建吗?
答案是,不会,最后StringBuilder的toString() 的调用,并不会在字符串常量池中去创建"ab"对象。
6、最难的无非就是再调用intern()方法,比如:String s3= new String(“a”) + new String(“a”);s3.intern();创建了几个对象?
答:最少4个,最多7个
1个new StringBuilder()和两个new String
另外"a"、"b"可能会在常量池新建对象
最后1个是,StringBuilder()的toString()方法底层实现是new String(value, 0, count)
最后是调用intern()方法,会去找到字符串常量池,判断"ab"是否存在,不存在,则创建"ab"对象。
运算
参数传递
Java 的参数是以值传递的形式传入方法中,而不是引用传递。
float和double
1.1 字面量属于 double 类型,不能直接将 1.1 直接赋值给 float 变量,因为这是向下转型。Java 不能隐式执行向下转型,因为这会使得精度降低。
float f = 1.1; // error
float f1 = 1.1f;
switch
从 Java 7 开始,可以在 switch 条件判断语句中使用 String 对象。
继承
访问权限
Java 中有三个访问权限修饰符: private、protected 以及 public,如果不加访问修饰符,表示包级可见。
抽象类和接口
-
抽象类 抽象类和抽象方法都使用 abstract 关键字进行声明。抽象类一般会包含抽象方法,抽象方法一定位于抽象类中。
-
接口 接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。
从 Java 8 开始,接口也可以拥有默认的方法实现,接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。接口的字段默认都是 static 和 final 的。
super
- 访问父类的构造函数: 可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
- 访问父类的成员: 如果子类重写了父类的中某个方法的实现,可以通过使用 super 关键字来引用父类的方法实现。
重写与重载
1.重写(Override)
存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
为了满足里式替换原则,重写有有以下两个限制:
- 子类方法的访问权限必须大于等于父类方法;
- 子类方法的返回类型必须是父类方法返回类型或为其子类型。
2. 重载(Overload)
存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。
应该注意的是,返回值不同,其它都相同不算是重载。
Object通用方法
equals()
hashCode()
hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
clone()
clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。
应该注意的是,clone() 方法并不是 Cloneable 接口的方法,而是 Object 的一个 protected 方法。Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。
- 浅拷贝
拷贝对象和原始对象的引用类型引用同一个对象。
- 深拷贝
拷贝对象和原始对象的引用类型引用不同对象。
- clone() 的替代方案
使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。Effective Java 书上讲到,最好不要去使用 clone(),可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。
关键字
final
1.数据
- 对于基本类型,final 使数值不变;
- 对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。
final int x = 1;
fianl A a = new A();
2.方法
声明方法不能被子类重写。
private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。
3.类
声明类不允许被继承。
static
1.静态变量
类所有的实例都共享静态变量,可以直接通过类名来访问它;静态变量在内存中只存在一份。
2.静态方法
静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现
3.静态语句块
静态语句块在类初始化时运行一次
4.静态内部类
非静态内部类依赖于外部类的实例,而静态内部类不需要
5.初始化顺序
静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序
- 父类(静态变量、静态语句块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)
反射
每个类都有一个 Class 对象,包含了与类有关的信息
动态加载类信息,而且可以加载早期不在的.class
异常
Throwable 可以用来表示任何可以作为异常抛出的类,分为两种: Error 和 Exception
-
Error表示JVM无法处理的错误
-
Exception
-
受检异常
需要用 try...catch... 语句捕获并进行处理,并且可以从异常中恢复
-
非受检异常
是程序运行时错误,此时程序崩溃并且无法恢复
-
泛型
1. Java中的泛型是什么 ? 使用泛型的好处是什么?
它提供了编译期的类型安全,确保你只能把正确类型的对象放入集合中,避免了在运行时出现ClassCastException。
2. Java的泛型是如何工作的 ? 什么是类型擦除 ?
泛型是通过类型擦除来实现的,例如List在运行时仅用一个List来表示。这样做的目的,是确保能和Java 5之前的版本开发二进制类库进行兼容。你无法在运行时访问到类型参数,因为编译器已经把泛型类型转换成了原始类型。
3. 什么是泛型中的限定通配符和非限定通配符 ?
-
<? extends T>它通过确保类型必须是T的子类来设定类型的上界。 -
<? super T>它通过确保类型必须是T的父类来设定类型的下界。
4.Array中可以用泛型吗?
Array事实上并不支持泛型,因为List可以提供编译期的类型安全保证,而Array却不能
注解
Java 注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。