2.方法执行顺序:
1.静态代码块,随着类加载而加载,且只执行一次
2.构造代码块,每创建一个对象就会执行一次,优先于构造方法执行
3.构造方法,每创建一个对象就会执行一次
3.java语言里面只支持单继承,即一个类只能有一个父类,子类可以继承父类中的非private修饰的成员方法和成员变量,构造方法不能被继承
4.Java不支持多继承,但是支持多层继承,即子类—>父类—>爷爷类….—>祖先类—>Object类,子类可以访问其先辈类里面的非private修饰的成员方法和成员变量
5.重写,也叫做覆盖,当父类中的方法无法满足子类需求时,子类可以将父类的方法进行重写编写来满足需求。比如孩子继承了父亲的房子,可以将房子重新装修。
方法重写的条件:
两个类必须是继承关系
必须具有相同的方法名,相同的返回值类型,相同的参数列表.
重写的方法不能比被重写的方法拥有更低的访问权限。
重写的方法不能比被重写的方法抛出更宽泛的异常。(关于异常后面的章节再讲。)
私有的方法不能被重写。
构造方法无法被重写,因为构造方法无法被继承。
静态的方法不存在重写。
重写指的是成员方法,和成员变量无关
6.super代表的是当前子类对象中的父类型特征。
什么时候使用super?
子类和父类中都有某个数据,例如,子类和父类中都有name这个属性。如果要再子类中访问父类中的name属性,需要使用super。例1
子类重写了父类的某个方法(假设这个方法名叫m1),如果在子类中需要调用父类中的m1方法时,需要使用super。例1
子类调用父类中的构造方法时,需要使用super。
注意:super不能用在静态方法中。
7.一个构造方法第一行如果没有this(…);也没有显示的去调用super(…);系统会默认调用super();如果已经有this了,那么就不会调用super了,super(…);的调用只能放在构造方法的第一行,只是调用了父类中的构造方法,但是并不会创建父类的对象。
super和this的对比
this和super分别代表什么
this:代表当前对象的引用
super:代表的是当前子类对象中的父类型特征
this和super的使用区别
调用成员变量
this.成员变量: 调用本类的成员变量
super.成员变量: 调用父类的成员变量
调用构造方法
this(…) :调用本类的构造方法
super(…):调用父类的构造方法
调用成员方法
this.成员方法:调用本类的成员方法
super.成员方法:调用父类的成员方法
8.继承中代码快的调用顺序。
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
分析:
1.系统将Fu.class和Zi.class分别加载到方法区的内存里面,静态代码会随着.class文件一块加载到方法区里面,所以先打印出了静态代码块中的内容。
2.构造代码块优先于构造方法执行,父类初始化之前,所以打印出父类中的构造代码块和构造方法中的内容。
9.Java里面,也是存在多态的,只要全部符合下面这三种情况,就是多态
有继承
有方法重写
有父类引用指向子类对象
10.上面代码中,a1是Animal类型的一个引用,指向的是其子类Cat的对象,这个就叫做父类引用指向子类对象。程序在编译的时候a1被看做Animal类型,所以a1.eat()绑定的是Animal类中的eat()方法,这叫做静态绑定,程序运行时,a1指向的是堆中的Cat对象,而在Cat中对eat()方法进行了重写,所以在运行阶段绑定的是Cat中的eat()方法,这叫做动态绑定
public class AnimalTest01{
publicstaticvoidmain(String[] args){
Cat c1 = new Cat();
c1.eat();
Animal a1 = new Cat();//父类引用指向子类对象 a1.eat();
System.out.println(a1.num);//因为成员变量不存在重写,所以结果是10 }
}
11.子类向父类型进行转换,是自动类型转换,也叫做向上转型。还有一种情况是父类向子类型转换,是强制类型转换
12.避免子类强制类型转换出错。开发中要是想避免这种错误需要使用instanceof来判断一下
public classAnimalTest01{
publicstaticvoidmain(String[] args){
Animal a1 = new Cat();//父类引用指向子类对象 //如果要是想执行Cat里面的move方法该怎么办? //只能强制类型转换,需要加强制类型转换符 Cat c1 = (Cat)a1;
c1.move();
Animal a2 = new Dog(); //向上转型. //进行强制类型转换时,需要先使用instanceof进行判断,避免ClassCastException
if(a2 instanceof Cat){
//强制类型转换 Cat c2 = (Cat)a2;
}
}
}
13.final的中文意思是最终,既然是最终就是已经结束了,无法再改变了。在Java里面final关键字同样也有着类似的功能。
final修饰的类无法被继承。例1
final修饰的方法无法被重写。例2
final修饰的局部变量,一旦赋值,不可再改变。例3
final修饰的成员变量必须初始化值。例4
14.在java里面可以使用关键字abstract修饰一个类,这样的类被称为抽象类,abstract修饰的方法叫做抽象方。
抽象类无法被实例化,无法创建抽象类的对象。
虽然抽象类没有办法实例化,但是抽象类也有构造方法,该构造方法是给子类创建对象用的。这也算是多态的一种。
抽象类中不一定有抽象方法,但抽象方法必须出现在抽象类中。
抽象类中的子类可以是抽象类,如果不是抽象类的话必须对抽象类中的抽象方法进行重写。
抽象类和抽象方法不能被final修饰
15.java语言里面使用interface来声明一个接口,接口其实是一个特殊的抽象类,在接口里面的方法全部都是抽象的。
关于接口,有几个需要注意的地方:
接口中只能出现常量和抽象方法
接口里面没有构造方法,无法创建接口的对象
接口和接口之间支持多继承,即一个接口可以有多个父接口
一个类可以实现多个接口,即一个类可以有多个父接口
一个类如果实现了接口,那么这个类需要重写接口中所有的抽象方法(建议),如果不重写则这个类需要声明为抽象类(不建议)
16.oracle官网里面找到API:http://docs.oracle.com/javase/8/docs/api/,
下载离线版的API,这里是CHM格式的:https://pan.baidu.com/s/1skR9wo1 密码:7jiw
17.Object类中设计toString方法的目的:返回java对象的字符串表示形式;
java对象中的equals方法的设计目的:判断两个对象是否一样;
finalize方法不需要程序员去调用,由系统自动调用。java对象如果没有更多的引用指向它,则该java对象成为垃圾数据,等 待垃圾回收器的回收,垃圾回收器在回收这个java对象之前会自动调用该对象的finalize方法。finalize方法是该对象马上就要 被回收了,例如:需要释放资源,则可以在该方法中释放
18.package语句只能出现在.java源文件的第一行
package语句在一个java文件中只能有一个
如果没有package,默认表示无包名
19.可以使用import关键字将不同包下的类导入,java.lang软件包下所有类不需要手动导入,系统自动导入,Object类,String类都在这个包里面
import com.monkey1024.oa.student.*//导入这个包下的所有类
import com.monkey1024.oa.student.AddStudent//导入这个包下的AddStudent类,建议使用这种方式
public class Test01{
publicstaticvoidmain(String[] args){
AddStudent as = new AddStudent();//这样就没问题了 as.add()
System.out.println(as);
}
}
20.java访问级别修饰符主要包括:private 、protected、public和default(默认),可以限定其他类对该类、属性和方法的使用权限
21.内部类,顾名思义就是在一个类的内部声明一个类。内部类可以直接访问外部类的成员,包括private修饰的变量和方法,内部类主要分为:
静态内部类
匿名内部类
成员内部类
局部内部类
22.异常主要分为:Error、一般性异常、RuntimeException
Error:如果程序出现了Error,那么将无法恢复,只能重新启动程序,最典型的Error的异常是:OutOfMemoryError
一般性异常:出现了这种异常必须在程序里面显示的处理,否则程序无法编译通过
RuntimeException:此种异常可以不用显示的处理,例如被0除异常,java没有要求我们一定要处理。
22.异常处理:
1.throws的作用是声明抛出异常,在方法声明的位置上使用throws关键字向上抛出异常。其实使用throws抛出异常并不是真正的去处理异常,而是抛给其调用者去处理,比如你在工作中遇到问题了,交给了你的领导去解决,领导如果也不想解决就交给他的领导去解决。在上面程序里面,我们抛出了异常,最后是交给了jvm解决,jvm的解决方式就是将错误信息打印至控制台,然后关闭程序。这里不是说使用throws是不好,使用throws主要意图是暴露问题,如何解决让调用者去决定
2.可以使用try…catch….finally处理异常,例如之前的程序可以使用try…catch…处理。可以捕捉多个异常,但是catch里面必须从小类型异常到大类型异常进行捕捉,先捕捉子后捕捉父,最多执行一个catch语句块里面的内容。jdk7新特性,可以将多个捕捉的异常放到一个catch里面。
3。被finally控制的语句体一定会执行,除非.执行finally语句体之前JVM退出(比如System.exit(0)),一般用于关闭资源;即使在方法里面执行了return,finally中的代码也会执行
4.throws
用在方法声明后面,跟的是异常类名
可以跟多个异常类名,用逗号隔开
表示抛出异常,由该方法的调用者来处理
throw
用在方法体内,跟的是异常对象名
只能抛出一个异常对象名
表示抛出异常,由方法体内的语句处理
23.什么是数组?
数组是存储同一种数据类型多个元素的集合。也可以看成是一个容器。
数组里面既可以存储基本数据类型,也可以存储引用数据类型,但是只能存储同一种数据类型。
数组是一种简单的数据结构,线性的结构
数组一旦创建其长度是不可变的
数组是引用数据类型
24.如何选择数组的初始化方式?
如果在创建数组的时候,知道数组中应该存储什么数据,这个时候当然采用静态初始化方式。
如果在创建数组的时候,无法预测到数组中存储什么数据,只是先开辟空间,则使用动态初始化方式。
两者最终的内存分布都是一样的
//静态
int[] arr = {1,2,3,4,5};
//动态
int[] arr1 =new int[4];
25.在java.util包里面有个Arrays类,里面提供了排序和二分法查找的方法,程序员直接调用就行,而且这些方法都是静态的,所以在使用时不用创建对象。
26.String类在java.lang包下面,是Object类的直接子类,通过API或者源码可以看到,String类是final修饰的,这说明String类不能被继承。字符串一旦创建好之后,里面的内容是不能被修改的,jvm会将双引号””中的内容存放在字符串常量池里面,常量池中的对象内容是不可修改的
1.可以使用下面两种方式创建String类型的对象:
String s3 = "hello";
String s4 = new String("monkey");
这两种方式的区别:
s3:系统会在常量池里面创建一个hello的字符串对象。
s4:系统会在常量池里面创建一个monkey字符串对象,然后在堆内存里面再创建一个monkey字符串对象。
2.在String类中有一个intern方法,当这个方法被调用的时候,如果在字符串常量池中存在与当前String相同的字符串时(equals),就会将该字符串返回,如果不存在时,就会将该字符串字面量放到字符串常量池中并返回该字符串的引用
3.在工作中尽量不要做字符串频繁的拼接操作。因为字符串一旦创建不可改变,如果频繁拼接,就会在字符串常量池中创建大量的字符串对象,给垃圾回收带来问题。如果需要做字符串频繁的拼接操作,最好使用StringBuffer或者StringBuilder,这两个类在后面会讲到
27.StringBuffer是一个字符串缓冲区,如果需要频繁的对字符串进行拼接时,建议使用StringBuffer。工作原理StringBuffer的底层是char数组,如果没有明确设定,则系统会默认创建一个长度为16的char类型数组,在使用时如果数组容量不够了,则会通过数组的拷贝对数组进行扩容,所以在使用StringBuffer时最好预测并手动初始化长度,这样能够减少数组的拷贝,从而提高效率。
1.String与StringBuffer的区别?
String是不可变字符序列,存储在字符串常量池中
StringBuffer的底层是char数组,系统会对该数组进行扩容
2.StringBuffer是jdk1.0版本中加入的,是线程安全的,效率低
StringBuilder是jdk5版本加入的,是线程不安全的,效率高
28.包装类
什么是包装类?
Java里面8个基本数据类型都有相应的类,这些类叫做包装类。
包装类有什么优点
可以在对象中定义更多的功能方法操作该数据,方便开发者操作数据,例如基本数据类型和字符串之间的转换。
1.基本数据类型和对应的包装类
基本数据类型 包装类
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
2.自动装箱:把基本类型转换为包装类类型;自动拆箱:把包装类类型转换为基本类型
3.如果数据是在(-128~127)之间,java中在方法区中引入了一个“整型常量池”
Integer i5 = 127;
Integer i6 = 127;
System.out.println(i5==i6); //true
29.BigInteger类可以让超过Integer范围的数据进行运算,通常在对数字计算比较大的行业中应用的多一些;
由于在运算的时候,float类型和double很容易丢失精度,在金融、银行等对数值精度要求非常高的领域里面,就不能使用float或double了,为了能精确的表示、计算浮点数,Java提供了BigDecimal。注意:如果对计算的数据要求高精度时,必须使用BigDecimal类
在一些金融或者银行的业务里面,会出现这样千分位格式的数字,¥123,456.00,表示人民币壹拾贰万叁仟肆佰伍拾陆元整,java.text包下提供了一个DecimalFormat的类可以满足这样的需求
30.在jdk5之后,引入了枚举(enum)的概念;
public enum Season{
Spring,Summer,Autumn,Winter
}
31.集合的由来
数组长度是固定,如果要改变数组的长度需要创建新的数组将旧数组里面的元素拷贝过去,使用起来不方便。
java给开发者提供了一些集合类,能够存储任意长度的对象,长度可以随着元素的增加而增加,随着元素的减少而减少,使用起来方便一些
1.集合类的一些特点
List:里面存放的数据是有顺序的,可以存放重复的数据。
Set:里面存放的数据是没有顺序的,不能存放重复的数据。
Queue:是一个队列,里面的数据是先进先出,可以存放重复的数据
2.数组和集合的区别
区别1:
数组既可以存储基本数据类型,又可以存储引用数据类型,基本数据类型存储的是值,引用数据类型存储的是地址值
集合只能存储引用数据类型(对象),如果存储基本数据类型时,会自动装箱变成相应的包装类
区别2:
数组长度是固定的,不能自动增长
集合的长度的是可变的,可以根据元素的增加而自动增长
3.list数组数据结构
特点:
查询和修改快
增加和删除慢
ArrayList和Vector的底层是使用数组的数据结构
在ArrayList中初始化的长度是10,如果长度不够用了,每次会增加之前长度的50%,然后将旧的集合中的数据拷贝到新的集合中。Vector每次扩容的长度是之前的一倍。
链表数据结构
特点:增加和删除快
查询和修改慢
LinkedList的底层使用的是链表数据结构,可以将链表看做是一条断开的车链子,两两相扣。
4.List的三个子类的特点
因为三个类都实现了List接口,所以里面的方法都差不多,那这三个类都有什么特点呢?
ArrayList:
底层数据结构是数组,查询快,增删慢。
线程不安全,效率高。
Vector:
底层数据结构是数组,查询快,增删慢。
线程安全,效率低。
Vector相对ArrayList查询慢(线程安全的)
Vector相对LinkedList增删慢(数组结构)
LinkedList:
底层数据结构是链表,查询慢,增删快。
线程不安全,效率高。
这个三个子类有什么区别和共同点?
Vector和ArrayList的区别
Vector是线程安全的,效率低
ArrayList是线程不安全的,效率高
共同点:都是数组实现的
ArrayList和LinkedList的区别
ArrayList底层是数组结果,查询和修改快
LinkedList底层是链表结构的,增和删比较快,查询和修改比较慢
共同点:都是线程不安全的
5.查询多用ArrayList
增删多用LinkedList
如果都多ArrayList
32.泛型的优点,可以统一集合中的数据类型,提高安全性;可以减少强制类型转换
1.通过API可以看到Collection,List,ArrayList,这几个类里面都有,这个就是泛型,里面的E可以是任何引用数据类型,使用泛型指明了数据类型之后,这个集合里面只能存储这种数据类型的对象
33.使用增强for循环可以简化数组和Collection集合的遍历。
for(元素数据类型 变量 : 数组或者Collection集合) {
使用变量即可,该变量就是元素
}
34.集合框架中的三种迭代方式删除数据:
普通for循环,可以删除,注意让索引做自减运算
迭代器,可以删除,但是必须使用迭代器自身的remove方法,否则会出现并发修改异常
增强for循环不能删除
package com.monkey1024.list;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;/**
* 集合框架中的三种迭代方式删除数据
*
*/public class ForTest02 {
publicstaticvoidmain(String[] args){
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("b");
list.add("c");
list.add("d");
//1,普通for循环删除,索引做自减运算 for(int i = 0; i < list.size(); i++) {
if("b".equals(list.get(i))) {
list.remove(i);
i--;
}
}
//2,迭代器删除 for(ListIterator<String> iter = list.listIterator(); iter.hasNext();){
if("b".equals(iter.next())) {
iter.remove();
}
}
//3,增强for循环不能删除,ConcurrentModificationException for (String string : list) {
if("b".equals(string)) {
list.remove("b");
}
}
System.out.println(list);
}
}
35.可变参数
在定义方法的时候不确定该定义多少个参数时,可以使用可变参数来定义,这样方法的参数个数会根据调用者来确定。
注意:如果一个方法有可变参数,并且有多个参数,那么,可变参数肯定是最后一个。
格式:修饰符 返回值类型 方法名(数据类型… 变量名){}
36.
1.基本数据类型的数组转换成集合,会将整个数组当作一个对象转换,下面程序将会打印出list的对象地址;
int[] arr = {1,2,3,4,5};
List<int[]> list = Arrays.asList(arr);
System.out.println(list);
2.将数组转换成集合,数组中的数据必须是引用数据类型;
Integer[] arr = {11,22,33,44,55}; List<Integer> list = Arrays.asList(arr);
System.out.println(list);
37.这里需要注意:在向HashSet中存放自定义类型对象时,一定要重写hashCode和equals方法
38.在HashSet中的元素是不能重复的,jvm可以通过equals方法来判断两个对象是否相同,假设自定义一个Person类里面有10个成员变量,每调用一次equals方法需要做10次if判断分别比较这10个成员变量是否相等,如果想HashSet中存放100个对象,那就会做1000次if判断,数据量大的话会严重影响性能。
要解决这个问题的话可以这样做,将一些特征相似或相近的对象归类放到一起给他们一个编号,在做equals判断时,先比较这些编号,编号相同的话再去比较equals,这样可以减少一些比较次数。这个编号可以通过HashCode方法获得。HashCode方法的作用就是将对象进行分类,然后获取到编号值。
举个例子,图书馆里面的书都是分好类的,想找《java编程思想》这本书,先找到计算机类的书架,然后再去找就行,倘若图书馆里面的书籍没有分类,那找起来就如大海捞针
39.通过LinkedHashSet的名字就可以看出,他的底层使用了链表的数据结构,因此LinkedHashSet的特点是读取元素的顺序跟存入元素的顺序是一致的,并且元素不能重复
40.TreeSet的特点是可以对存放进去的元素进行排序。这里还是存储之前定义的Person对象,需要实现Comparable接口并且重写compareTo方法,先根据name的字典顺序排序,然后再根据年龄进行排序。
41.除了Collection之外,常用的集合还有Map接口,里面常用的实现类图如下,map中的元素是以键-值的方式存在的,通过键可以获取到值,键是不可以重复的,跟地图比较像,通过一个坐标就可以找到具体的位置。
1.LinkedHashMap的特点:存取顺序一致
2.TreeMap的特点:可以对存储的元素进行排序
3.Hashtable是JDK1.0版本出现的,是线程安全的,效率低,不可以存储null键和null值
HashMap是JDK1.2版本出现的,可以存储null键和null值
42.就像数组中的Arrays工具类一样,在集合里面也有跟Arrays类似的工具类Collections
43.Collection总结
List(存取有序,有索引,可以重复)
ArrayList
底层是数组实现的,线程不安全,查找和修改快,增和删比较慢
LinkedList
底层是链表实现的,线程不安全,增和删比较快,查找和修改比较慢
Vector
底层是数组实现的,线程安全的,无论增删改查都慢
如果查找和修改多,用ArrayList
如果增和删多,用LinkedList
如果都多,用ArrayList
Set(存取无序,无索引,不可以重复)
HashSet
底层是哈希算法实现
LinkedHashSet
底层是链表实现,可以保证元素唯一,存取顺序一致
TreeSet
底层是二叉树算法实现,可以排序,存储自定义类型时需要注意实现Comparable接口并重写compareTo方法
一般在开发的时候不需要对存储的元素排序,所以在开发的时候大多用HashSet,HashSet的效率比较高.TreeSet在面试的时候比较多
Map
HashMap
底层是哈希算法
LinkedHashMap
底层是链表,存取顺序一致
TreeMap
底层是二叉树算法,可以排序
开发中用HashMap比较多
44。IO流的分类
按照流向分类:
输入流(InputStream和Reader):从硬盘或者别的地方读到内存里面
输出流(OutputStream和Writer):从内存里面向硬盘或者别的地方输出
按照操作类型分类:
字节流(InputStream和OutputStream) : 字节流可以操作任何数据,因为在计算机中的数据都是以字节的形式存储的。
字符流(Reader和Writer) : 字符流只能操作纯字符数据,防止乱码。
45.多线程的缺点
设计复杂
多线程中共享堆内存和方法区,因此里面的一些数据是可以共享的,在设计时要确保数据的准确性
资源消耗增多
栈内存是不共享的,如果启用多个线程的话会占用更多的内存
1.使用Thread创建线程的步骤:
1.自定义一个类,继承java.lang包下的Thread类
2.重写run方法
3.将要在线程中执行的代码编写在run方法中
4.创建上面自定义类的对象
5.调用start方法启动线程
2.使用Runnable创建线程步骤:
1.自定义一个类实现java.lang包下的Runnable接口
2.重写run方法
3.将要在线程中执行的代码编写在run方法中
4.创建上面自定义类的对象
5.创建Thread对象并将上面自定义类的对象作为参数传递给Thread的构造方法
6.调用start方法启动线程
// 1.自定义一个类实现java.lang包下的Runnable接口classMyRunnableimplementsRunnable{
// 2.重写run方法 @Override publicvoidrun(){
// 3.将要在线程中执行的代码编写在run方法中 for (int i = 0; i < 1000; i++) {
System.out.println("monkey");
}
}
}public classThreadTest02{
publicstaticvoidmain(String[] args){
// 4.创建上面自定义类的对象 MyRunnable mr = new MyRunnable();
// 5.创建Thread对象并将上面自定义类的对象作为参数传递给Thread的构造方法 Thread t = new Thread(mr);
//6.调用start方法启动线程 t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("1024");
}
}
}
3.使用Callable创建线程步骤:
1.自定义一个类实现java.util.concurrent包下的Callable接口
2.重写call方法
3.将要在线程中执行的代码编写在call方法中
4.创建ExecutorService线程池
5.将自定义类的对象放入线程池里面
6.获取线程的返回结果
7.关闭线程池,不再接收新的线程,未执行完的线程不会被关闭
4.多线程创建的三种方式对比
继承Thread
优点:可以直接使用Thread类中的方法,代码简单
缺点:继承Thread类之后就不能继承其他的类
实现Runnable接口
优点:即时自定义类已经有父类了也不受影响,因为可以实现多个接口
缺点: 在run方法内部需要获取到当前线程的Thread对象后才能使用Thread中的方法
实现Callable接口
优点:可以获取返回值,可以抛出异常
缺点:代码编写较为复杂
46.反射机制是在程序运行状态中,对于任意一个类,都能够获取这个类的所有属性和方法;
对于任意一个对象,都能够调用它的任意一个方法和属性;
这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制