文章目录
1. 访问控制符
- public(公共访问权限)
如果一个成员被public修饰,那么这个成员可以被所有类访问,不管这个类和被访问的类是否在同一个包下,是否具有父子继承关系。 - protected(子类访问权限)
如果一个成员被 protected 修饰,那么这个成员不仅可以被同一包下的其他类访问,也可以被不同包中的子类访问。 - default(包访问权限)
可以被相同包下的其他类访问。 - private:这个成员只能在该类内部被访问
2. String
String Pool
- 字符串常量池保存着所有字符串字面量,这些字面量在编译时期就确定。还可以使用String的 intern() 方法在运行过程中将字符串添加到 String Pool 中。
- 当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。
3. final
-
数据
对于基本类型,final 使数值不变
对于引用类型,final 使引用不变,也就是说引用变量 a 只能当前这个对象,而不能引用别的对象,但是被引用的对象本身是可以修改的。 -
方法
声明方法不能被子类重写。private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。 -
类
声明类不允许被继承
4. static
- 静态变量
- 静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份。
- 实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。
- 静态方法
- 静态方法在类加载的时候就存在了,它不依赖于任何实例。所以静态方法必须有实现,也就是说它不能是抽象方法。
- 只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。
- 静态语句块
静态语句块在类初始化时运行一次。 - 静态内部类
非静态内部类依赖于外部类的实例,而静态内部类不需要。
初始化顺序
- 静态变量和静态语句块优先于实例变量和普通语句块,静态变量和静态语句块的初始化顺序取决于它们在代码中的顺序。最后才是构造函数的初始化。
- 存在继承的情况下,初始化顺序为:
- 父类(静态变量、静态语句块)
- 子类(静态变量、静态语句块)
- 父类(实例变量、普通语句块)
- 父类(构造函数)
- 子类(实例变量、普通语句块)
- 子类(构造函数)
5. equals 和==区别
equals()
- equals方法不能作用于基本数据类型的变量,equals 继承 Object 类,比较的是是否是同一个对象
- 如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址
- 诸如String、Date等类对equals方法进行了重写的话,比较的是所指向的对象的内容
== 比较的是值是否相等
- 如果作用于基本数据类型的变量,则直接比较其存储的 “值”是否相等;
- 如果作用于引用类型的变量,则比较的是所指向的对象的地址
6. Object 类中的方法
- clone 方法
clone - getClass 方法
- toString 方法
- finalize 方法
- equals 方法
- hashCode 方法
- wait 方法
- notify 方法
clone()
- 为什么克隆
克隆的对象可能包含一些已经修改过的属性,保留着想要想要克隆的对象的值,而new出来的对象的属性全是一个新的对象,对应的属性没有值,所以我们还要重新给这个对象赋值。即当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。 - 如何克隆
- 深克隆和浅克隆
深克隆和浅克隆
拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。
toString()
finalize()
equals()
- 等价性
- 对称性
- 传递性
- 一致性
- 与null比较
- 对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
- 对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。
hashCode()
- hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价。
- 在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。
- 在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,保证等价的两个对象散列值也相等。下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hashCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象。
EqualExample e1 = new EqualExample(1, 1, 1);
EqualExample e2 = new EqualExample(1, 1, 1);
System.out.println(e1.equals(e2)); // true
HashSet<EqualExample> set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size()); // 2
wait()
notify()
7. Java8 的新特性
8. 方法重载和方法重写
方法的重写存在于继承体系当中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
为了满足里氏替换原则,方法重写必须满足下面三个条件:【子类需要能够当作父类来使用而且更加特殊】
- 子类方法的访问权限必须大于等于父类方法
- 子类方法的返回类型必须是父类方法返回类型或为其子类型
理解:我的理解是,这是为了向上转型;既然子类重写了父类的方法,有时候就需要用父类对象引用来调用子类重写的方法,在上面例子的情况下,也就是说要把A的子类对象引用赋给A的对象引用,如果此时返回值类型不是A类或A的子类,其他类的对象引用是不能赋给A的对象引用的,这样就会出错;所以说,子类重写的方法,如果返回值为类类型,其返回值类型必须与父类返回值类型相同或为父类返回值类型的子类。 - 子类方法抛出的异常类型必须是父类抛出异常类型或为其子类型。
9. 泛型
定义泛型的接口、类
- 允许在定义接口、类时指定类型形参,类型形参在整个接口、类体内可当成类型使用。例如,我们在使用List类型时,为 E 形参传入 String 类型实参,则产生一种新的类型:List。
- 虽然程序只定义了一个 List 接口,但实际使用时可以产生无数个 List 接口,只要为 E 传入不同的类型实参,系统就会多出一个新的 List 子接口。
- 我们可以为任何类添加泛型声明:
public class Apple<T> {
//使用T类型形参定义属性
private T info;
public Apple(T info) {
this.info = info;
}
public void setInfo(T info) {
this.info = info;
}
public static void main(String[] args) {
//因为传给T的形参是String类型,所以构造器的参数只能是String
Apple<String> a1 = new Apple<String>("苹果");
}
}
类型通配符
当我们使用一个泛型时,应该为这个类型传入一个类型实参,如果没有传入类型实参,就会引起泛型警告。比如,有如下方法:
public void test(List c){
for(int i = 0; i < c.size(); i++){
System.out.println(c.get(i));
}
}
问题是上面List是一个泛型声明接口,List没有传入实际类型,将会报错。将代码改为如下:
public void test(List<Object> c){
for(int i = 0; i < c.size(); i++){
System.out.println(c.get(i));
}
}
但是,如果:
//创建一个List<String>对象
List<String> list = new ArrayList<String>();
//调用test方法
test(list);
这里就会报错,意味着List 不能被当作 List 来使用,也就是说,List并不是List的子类。那么如何解决这个问题呢?
使用类型通配符
为了表示各种泛型List的父类,我们需要使用类型通配符,类型通配符是一个 ?,将一个问好作为类型实参传给 List,写作 List<?> 【意思是未知元素类型的List】,他的元素类型可以匹配任何类型。
我们将上面的方法改为如下:
public void test(List<?> c){
for(int i = 0; i < c.size(); i++){
System.out.println(c.get(i));
}
}
10. 泛型中 T 和 ?的区别 ?
用上面的例子来解释
11. 抽象类与接口
抽象类
抽象类和抽象方法都使用 abstract 关键字进行声明。如果一个类中包含抽象方法,那么这个类必须声明为抽象类。
抽象类和普通类最大的区别是,抽象类不能被实例化,需要继承抽象类才能实例化其子类。
接口
- 接口是抽象类的延伸,在 Java 8 之前,它可以看成是一个完全抽象的类,也就是说它不能有任何的方法实现。
- 从 Java 8 开始,接口也可以拥有默认的方法实现,这是因为不支持默认方法的接口的维护成本太高了。在 Java 8 之前,如果一个接口想要添加新的方法,那么要修改所有实现了该接口的类。
- 接口的成员(字段 + 方法)默认都是 public 的,并且不允许定义为 private 或者 protected。
- 接口的字段默认都是 static 和 final 的。
比较
- 从使用上来看,一个类可以实现多个接口,但是不能继承多个抽象类。
- 接口的字段只能是 static 和 final 类型的,而抽象类的字段没有这种限制。
- 接口的成员只能是 public 的,而抽象类的成员可以有多种访问权限。
使用选择
- 需要使用多继承
- 需要让不相关的类都实现一个方法
抽象类
- 需要在几个相关的类中共享代码。
- 需要能控制继承来的成员的访问权限,而不是都为 public。
- 需要继承非静态和非常量字段。
Super
- 访问父类的构造函数:可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作。
- 访问父类的成员:如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
反射
定义
- 每个类都有一个 Class 对象,包含了与类有关的信息。当编译一个新类时,会产生一个同名的 .class 文件,该文件内容保存着 Class 对象。
- 类加载相当于 Class 对象的加载,类在第一次使用时才动态加载到 JVM 中。也可以使用Class.forName(“com.mysql.jdbc.Driver”) 这种方式来控制类的加载,该方法会返回一个 Class 对象。
Java 中用到的设计模式
- 装饰者模式
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,
- InputStream 是抽象组件;
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如BufferedInputStream 为 FileInputStream 提供缓存的功能。
- 实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
- DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。
NIO
新的输入/输入(NIO)库是在 JDK1.4 中引入的,弥补了原来的 I/O 的不足, 提供了高速的、面向块的 I/O。
流与块
I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
面向流的 I/O 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创
建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O
通常相当慢。
面向块的 I/O 一次处理一个数据块,按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
选择器
- NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。
- NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。
- 通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。
- 因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。
对比
NIO 与普通 I/O 的区别主要有以下两点:
- NIO 是非阻塞的;
- NIO 面向块,I/O 面向流。
JDBC
JDBC 介绍
JDBC(Java DataBase Connectivity)是Java和数据库之间的一个桥梁,是一个规范而不是一个实现,能够执行SQL语句。它由一组用Java语言编写的类和接口组成。各种不同类型的数据库都有相应的实现,本文中的代码都是采用MySQL数据库实现的。
JDBC 编程步骤
创建Statement或者PreparedStatement接口,执行SQL语句
- 使用 Statement 接口
Statement接口创建之后,可以执行SQL语句,完成对数据库的增删改查。其中 ,增删改只需要改变SQL语句的内容就能完成,然而查询略显复杂。在Statement中使用字符串拼接的方式,该方式存在句法复杂,容易犯错等缺点,具体在下文中的对比中介绍。所以Statement在实际过程中使用的非常的少,所以具体的我们放道PreparedStatement那里给出详细代码。
Statement s = conn.createStatement();
// 准备sql语句
// 注意: 字符串要用单引号'
String sql = "insert into t_courses values(null,"+"'数学')";
//在statement中使用字符串拼接的方式,这种方式存在诸多问题
s.execute(sql);
System.out.println("执行插入语句成功");
- 使用 PreparedStatement 接口
与 Statement一样,PreparedStatement也是用来执行sql语句的,与创建Statement不同的是,需要根据sql语句创建PreparedStatement。除此之外,还能够通过设置参数,指定相应的值,而不是Statement那样使用字符串拼接。
/**
* 添加课程
* @param courseName 课程名称
*/
public void addCourse(String courseName){
String sql = "insert into t_course(course_name) values(?)";
//该语句为每个 IN 参数保留一个问号(“?”)作为占位符
Connection conn = null; //和数据库取得连接
PreparedStatement pstmt = null; //创建statement
try{
conn = DbUtil.getConnection();
pstmt = (PreparedStatement) conn.prepareStatement(sql);
pstmt.setString(1, courseName); //给占位符赋值
pstmt.executeUpdate(); //执行
}catch(SQLException e){
e.printStackTrace();
}
finally{
DbUtil.close(pstmt);
DbUtil.close(conn); //必须关闭
}
}
- 使用PreparedStatement时,他的SQL语句不再采用字符串拼接的方式,而是采用占位符的方式。“?”在这里就起到占位符的作用。这种方式除了避免了statement拼接字符串的繁琐之外,还能够提高性能。每次SQL语句都是一样的,数据库就不会再次编译,这样能够显著提高性能。
String、StringBuilder 和 StringBuffer 的实现
String
ublic final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
//数组定义为常量,不可修改
private final char value[];
public String() {
this.value = new char[0];
}
在 Java 8 中,String 类的实现使用 char 数组存储数据。final 修饰的 String 类,以及 final 修饰的 char[] value,表示 String 类不可被继承,且 value 只数组初始化之后就不能引用其它数组。String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。
StringBuilder
从源码可以看出 StringBuilder 继承了抽象类 AbstractStringBuilder,所以在StringBuilder 中并没有所要维护的数组 char[] value,因为 AbstractStringBuilder 类已经包含了继承即可。同时可以看出 StringBuilder 也被关键字 final 修饰,即不能被继承,而且底层数组并没,所以 StringBuilder 是一个可以被修改的动态数组。同时还可以看出,StringBuilder 是JDK1.5引入的。
StringBuffer
StringBuffer 与 StringBuilder 不同的是 StringBuffer 是线程安全的,大部分方法都是使用 synchronized 修饰的。