- JVM(Java Virtual Machine)java虚拟机,实现 “一次编译,到处运行”
- JRE(Java Runtime Environment)java运行环境,包含JVM标准实现以及Java核心类库
- JDK(Java Development Kit)java开发工具包,是整个java的核心,包括java运行环境JRE、java工具和java基础类库
基本类型与包装类之间的转换:
温馨提示:在JDK5.0之后,基本类型和包装类之间的转换可以使用自动装箱和自动拆箱
1.3 JDK中主要的包:
2. 面向对象面向对象和面向过程的理解:
- 面向过程:强调的是功能行为,以函数为最小单位,考虑怎么做
- 面向对象:强调的是具备了功能的对象,以类或者对象为最小单位,考虑谁来做
几个概念的多种说法:
- 属性 = 成员变量 = field = 域,字段
- 方法 = 成员方法 = 函数 = method
- 创建类的对象 = 类的实例化 = 实例化类
所谓的匿名对象:就是在创建对象的时候没有显示的赋给一个变量名,特点是只能调用一次,通俗来讲就是所说的临时new一个对象(比如在函数的形参里)
类的属性有默认的初值
整型(byte、short、int、long:0)
浮点型(float、double:0.0)
字符型(char:0 (或'u0000'))
布尔型(boolean:false)
引用数据类型(类、数组、接口:null)
局部变量没有初值,所以局部变量在声明时要显示的赋一个初值,特别说明:形参不用赋初值,在调用时赋值即可。
Java规定的四种权限修饰符:
权限从小到大顺序为:private < 缺省 < protected < public
具体的修饰范围:
注意:4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类
修饰类的话,只能使用:缺省、public
子类继承父类以后,可以对父类中同名的同参数的方法进行覆盖操作,重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
重写的规则:
- 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同
- 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符
- 特殊情况:子类不能重写父类中声明为private权限的方法
- 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void
- 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类
- 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)
- 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型(具体放到异常处理时候讲)
重写和重载的区别
Java的多态:重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的参数表,对同名方法的名称做修饰。对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参数的方法。
所以:对于重载而言,在方法调用之前,编译器就已经确定了所要调用的方法,这称为“早绑定”或“静态绑定”
而对于多态,只等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为“晚绑定”或“动态绑定”。
多态的字面意思就是一个引用拥有多种形态,以继承为基础,根据引用的具体指向不同,使用该引用的操作也会有所不同。在编译时期,只能调用父类中声明的方法,但是在运行期,我们实际执行的是子类重写父类的方法。
总结:编译看(=)左边,运行看(=)右边
多态的三个必要条件:
- 要有继承
- 要有重写
- 向上转型:父类的引用指向子类的对象
多态代码示例:
public class Person {
String name;
int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println(name+"吃饭");
}
}
public class Student extends Person {
public Student (String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"吃面包");
}
}
public class Chinese extends Person {
public Chinese (String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(name+"吃水果");
}
}
//测试方法
public class Test {
public static void eat(Person person){
person.eat();
}
public static void main(String[] args){
Chinese p1 = new Chinese("张三",2);
Person p2 = new Person("李四",1);
eat(p1);
eat(p2);
}
}
向下转型:
有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。如何才能调用子类特的属性和方法?使用向下转型。
如何实现:使用强制类型转换符
注意点:
为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof判断转型对象a是否是类A的实例,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
super关键字super关键字可以理解为:父类的
super调用属性和方法
- 我们可以在子类的方法或构造器中。通过使用"super.属性"或"super.方法"的方式,显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略"super."
- 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用"super.属性"的方式,表明调用的是父类中声明的属性。
- 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。
super调用构造器
- 我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器
- 使用"super(形参列表)"时,必须声明在子类构造器的首行!
- 在类的构造器中,针对于"this(形参列表)"或"super(形参列表)"只能二一,不能同时出现
- 在构造器的首行,没显式的声明"this(形参列表)"或"super(形参列表)",则默认调用的是父类中空参的构造器:super()
- 在类的多个构造器中,至少一个类的构造器中使用了"super(形参列表)",调用父类中的构造器
static关键字可以用来修饰属性、方法、代码块、内部类
1. static修饰属性当类的属性被static修饰时,当创建了类的多个对象,多个对象共享同一个该静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,也是被修改过了的。
static修饰属性的其他说明:
2. static修饰方法
- 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
- 静态变量的加载要早于对象的创建。
- 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
随着类的加载而加载,可以通过"类.静态方法"的方式进行调用,静态方法中,只能调用静态的方法或属性非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
注意点:在静态的方法内,不能使用this关键字、super关键字
代码块作用:用来初始化类、对象的信息
静态代码块:
随着类的加载而执行,而且只执行一次,用来初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
静态代码块的执行要优先于非静态代码块的执行
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
非静态代码块:
final关键字随着对象的创建而执行,每创建一个对象,就执行一次非静态代码块,可以在创建对象时,对对象的属性等进行初始化
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
可以用来修饰:类、方法、变量
- final 用来修饰一个类:此类不能被其他类所继承。比如:String类、System类、StringBuffer类
- final 用来修饰方法:表明此方法不可以被重写
- final 用来修饰变量:此时的"变量"就称为是一个常量
用来修饰:类、方法
1. abstract修饰类:抽象类
- 此类不能实例化
- 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
- 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
2. abstract修饰方法:抽象方法
- 抽象方法只方法的声明,没有方法体
- 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
- 若子类重写了父类中的所的抽象方法后,此子类方可实例化
- 若子类没重写父类中的所的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
注意点:
interface接口abstract不能用来修饰:属性、构造器等结构
abstract不能用来修饰私方法、静态方法、final的方法、final的类
接口使用interface来定义,Java中,接口和类是并列的两个结构,接口中不能定义构造器的!意味着接口不可以实例化。接口的具体使用,体现多态性。
如果实现类覆盖了接口中的所抽象方法,则此实现类就可以实例化,如果实现类没覆盖接口中所的抽象方法,则此实现类仍为一个抽象类。
Java类可以实现多个接口,弥补了Java单继承性的局限性
接口与接口之间可以继承,而且可以多继承
内部类
Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类
如何创建成员内部类的对象?
创建静态的Dog内部类的实例(静态的成员内部类):
Person.Dog dog = new Person.Dog();
创建非静态的Bird内部类的实例(非静态的成员内部类):
Person.Bird bird = new Person.Bird();//错误的 //正确写法 Person p = new Person(); Person.Bird bird = p.new Bird();
如何在成员内部类中调用外部类的结构?
class Person{
String name = "小明";
public void eat(){
}
//非静态成员内部类
class Bird{
String name = "杜鹃";
public void display(String name){
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
//Person.this.eat();
}
}
}
3. 异常的体系结构
* java.lang.Throwable * |-----java.lang.Error:一般不编写针对性的代码进行处理。 * |-----java.lang.Exception:可以进行异常的处理 * |------编译时异常(checked) * |-----IOException * |-----FileNotFoundException * |-----ClassNotFoundException * |------运行时异常(unchecked,RuntimeException) * |-----NullPointerException * |-----ArrayIndexOutOfBoundsException * |-----ClassCastException * |-----NumberFormatException * |-----InputMismatchException * |-----ArithmeticException3.1 异常的处理:
处理方式一:try-catch-finally ,可能出现异常的代码放在try代码块里,抛出的异常类型在catch里,finally是一定能执行的代码
处理方式二:"throws + 异常类型"
写在方法的声明处。指明此方法执行时,可能会抛出的异常类型。一旦当方法体执行时,出现异常,仍会在异常代码处生成一个异常类的对象,此对象满足throws后异常类型时,就会被抛出。异常代码后续的代码,就不再执行!
两种方式的对比:
try-catch-finally:真正的将异常给处理掉了。
throws的方式只是将异常抛给了方法的调用者。并没真正将异常处理掉。
在开发中,如果父类中被重写的方法没有throws方式处理异常,则子类重写的方法也不能使用throws,如果子类重写的方法中有异常只能用try-catch-finally
4. 多线程 4.1 几个概念1. 程序(program):
是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
2. 进程(process):
程序的一次执行过程,或是正在运行的一个程序。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
3. 线程(thread):
进程可进一步细化为线程,是一个程序内部的一条执行路径。线程作为调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),线程切换的开销小。
一个java运行程序java.exe,至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。
4. 并行和并发的理解:
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀
4.2 创建多线程的方式 方式一:继承Thread类
步骤:
方式二:实现Runnable接口1. 创建一个继承于Thread类的子类
2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
3. 创建Thread类的子类的对象
4. 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()(自动调用)
1. 创建一个实现了Runnable接口的类
2. 实现类去实现Runnable中的抽象方法:run()
3. 创建实现类的对象
4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5. 通过Thread类的对象调用start()
两种方式的对比:
开发中优先选择:实现Runnable接口的方式,原因:
- 实现的方式没有类单继承的局限性
- 实现的方式更适合处理多个线程共享数据的情况
相同点:
- 两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
- 目前两种方式,要想启动线程,都是调用的Thread类中的start()。
线程的优先级:
方式三:实现Callable接口 (JDK5.0新增)* MAX_PRIORITY:10
* MIN _PRIORITY:1
* NORM_PRIORITY:5 默认优先级
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
- call()可以返回值的。
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- Callable是支持泛型的
4.3 Thread类中常用的方法
- start():启动当前线程;调用当前线程的run()
- run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
- currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- yield():释放当前cpu的执行权
- join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
- stop():已过时。当执行此方法时,强制结束当前线程。
- sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
- isAlive():判断当前线程是否存活
- getPriority():获取线程的优先级
- setPriority(int p):设置线程的优先级
4.4 线程的生命周期
4.5 线程的同步机制
通过线程的同步机制来解决线程的安全问题
方式一:同步代码块 synchronized(同步监视器){
//需要被同步的代码
}
说明:
需要被同步的代码:操作共享数据的代码
同步监视器:俗称 “锁” 任何一个类的对象,都可以充当锁,多个线程必须共用同一把锁
方式二:同步方法如果操作共享数据的代码完整的声明在一个方法中,并且方法中没有多余的代码,我们可以将此方法声明为同步方法
方式三:Lock锁
- 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
- 非静态的同步方法,同步监视器是:this
- 静态的同步方法,同步监视器是:当前类本身
从 JDK 5.0 开始 Java 提供了更强大的线程同步机制 通过显式定义同步锁对象来实现同步。同步锁使用 Lock 对象充当 。
class A{
private final ReentrantLock lock = new ReenTrantLock();
public void m(){
lock.lock();
try{
// 保证线程安全的代码
}
finally{
lock.unlock();
}
}
}
synchronized 与 Lock的异同?
4.6 线程的通信相同点:二者都可以解决线程安全问题
不同点:
- synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
- Lock是显示锁需要手动的启动同步(lock(),同时结束同步也需要手动的实现(unlock())
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,并且有较好的扩展性(提供更多的子类)
线程的通信涉及到三个方法:
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
- notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。
说明:
- 三个方法必须使用在synchronized同步代码块或者同步方法中
- 三个方法的调用者必须是同步代码块或者同步方法中的同步监视器, 否则会出现IllegalMonitorStateException异常
- 三个方法是定义在java.lang.Object类中。
sleep和wait()的异同?
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态。
不同点:
- 两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中
- 如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。
总结释放锁的操作:
总结不释放锁的操作:



