多态性
是面向对象中最重要的概念,在Java中的体现: 对象的多态性:父类的引用指向子类的对象
Person p = new Student(); //Person类型的变量p,指向Student类型的对象
Object o = new Person();//Object类型的变量o,指向Person类型的对象
o = new Student(); //Object类型的变量o,指向Student类型的对象
子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
一个引用类型变量如果声明为父类的类型,但实际引用的是子类 对象,那么该变量就不能再访问子类中添加的属性和方法
Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
虚拟方法调用(多态情况下)(Virtual Method Invocation)
子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父 类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。
instanceof 操作符
x instanceof A:检验x是否为类A的对象,返回值为boolean型。
要求x所属的类与类A必须是子类和父类的关系,否则编译错误。
如果x属于类A的子类B,x instanceof A值也为true。
Object类是所有Java类的根父类
如果在类的声明中未使用extends关键字指明其父类,则默认父类 为java.lang.Object类
public class Person { … } 等价于: public class Person extends Object { … }
例:method(Object obj){…} //可以接收任何类作为其参数
Person o=new Person();
method(o);
==操作符与equals方法:
==操作符
基本类型比较值:只要两个变量的值相等,即为true。
引用类型比较引用(是否指向同一个对象):只有指向同一个对象时,==才返回true。
用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本 数据类型除外),否则编译出错
equals():所有类都继承了Object,也就获得了equals()方法。还可以重写。
只能比较引用类型,其作用与“==”相同,比较是否指向同一个对象。
格式:obj1.equals(obj2)
特例:当用equals()方法进行比较时,对类File、String、Date及包装类 (Wrapper Class)来说,是比较类型及内容而不考虑引用的是否是同一个对 象;原因:在这些类中重写了Object类的equals()方法。
面试题:==和equals的区别
1 == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型 就是比较内存地址
2 equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也 是==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中 用的比较多,久而久之,形成了equals是比较值的错误观点。
3 具体要看自定义类里有没有重写Object的equals方法来判断。
4 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
toString() 方法
toString()方法在Object类中定义,其返回值是String类型,返回类名和它 的引用地址。
在进行String与其它类型数据的连接操作时,自动调用toString()方法
包装类(Wrapper)的使用
代码演示:
// 包装类的使用 int i = 5; Integer integer = new Integer(i);//把基本数据类型转化为包装类,调用new Integer() System.out.println(integer.getClass().getName());//java.lang.Integer int i1 = integer.intValue();//包装类转化为基本数据类型,调用包装类的方法:xxxValue() System.out.println(i1);//5 int j = 1; String s = String.valueOf(j);// 基本数据类型转化为String类,方式一:调用String.valueOf() System.out.println(s.getClass().getName());//java.lang.String String s1 = j + ""; // 基本数据类型转化为String类,方式二:+ "" System.out.println(s1.getClass().getName());//java.lang.String String s2 = "123"; int i2 = Integer.parseInt(s2); // String类转化为基本数据类型,调用Integer.parseInt() System.out.println(i2);//123 Integer integer1 = new Integer(2); String s3 = integer1.toString();// 包装类转化为String类,调用包装类对象的toString()方法 System.out.println(s3.getClass().getName());//java.lang.String Integer i3 = new Integer("456");// String类转化为包装类 System.out.println(i3.getClass().getName()); //java.lang.Integer
垃圾回收机制关键点
垃圾回收机制只回收JVM堆内存里的对象空间。
对其他物理连接,比如数据库连接、输入流输出流、Socket连接无能为力
现在的JVM有多种垃圾回收实现算法,表现各异。
垃圾回收发生具有不可预知性,程序无法精确控制垃圾回收机制执行。
可以将对象的引用变量设置为null,暗示垃圾回收机制可以回收该对象。
程序员可以通过System.gc()或者Runtime.getRuntime().gc()来通知系统进行垃圾回收,会有
一些效果,但是系统是否进行垃圾回收依然不确定。
垃圾回收机制回收任何对象之前,总会先调用它的finalize方法(如果覆盖该方法,让一
个新的引用变量重新引用该对象,则会重新激活对象)。
永远不要主动调用某个对象的finalize方法,应该交给垃圾回收机制调用。
如果想让一个类的所有实例共享数据,就用类变量!
因为不需要实例就可以访问static方法,因此static方法内部不能有this。(也 不能有super ? YES!)
static修饰的方法不能被重写
单例(Singleton)设计模式-饿汉式
class Singleton { // 1.私有化构造器 private Singleton() { } // 2.内部提供一个当前类的实例 // 4.此实例也必须静态化 private static Singleton single = new Singleton(); // 3.提供公共的静态的方法,返回当前类的对象 public static Singleton getInstance() { return single; } }
单例(Singleton)设计模式-懒汉式(线程不安全)
class Singleton { // 1.私有化构造器 private Singleton() { } // 2.内部提供一个当前类的实例 // 4.此实例也必须静态化 private static Singleton single; // 3.提供公共的静态的方法,返回当前类的对象 public static Singleton getInstance() { if(single == null) { single = new Singleton(); } return single; } }
工厂设计模式
interface Car { void run(); } class Audi implements Car { public void run() { System.out.println("奥迪在跑"); } } class BYD implements Car { public void run() { System.out.println("比亚迪在跑"); } } //工厂类 class CarFactory { //方式一 public static Car getCar(String type) { if ("奥迪".equals(type)) { return new Audi(); } else if ("比亚迪".equals(type)) { return new BYD(); } else { return null; } } //方式二 // public static Car getAudi() { // return new Audi(); // } // // public static Car getByd() { // return new BYD(); // } } public class Client02 { public static void main(String[] args) { Car a = CarFactory.getCar("奥迪"); a.run(); Car b = CarFactory.getCar("比亚迪"); b.run(); } }
对main方法的语法理解:
public static void main(String[] args) {}
由于Java虚拟机需要调用类的main()方法,所以该方法的访问权限必须是 public,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须 是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令 时传递给所运行的类的参数。
抽象方法:只有方法的声明,没有方法的实现。以分号结束:
比如:public abstract void talk();
接口(interface)是抽象方法和常量值定义的集合
接口中的所有成员变量都默认是由public static final修饰的。
接口中的所有抽象方法都默认是由public abstract修饰的。
Java 8中,你可以为接口添加静态方法和默认方法。
静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,
默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。
异常处理
详情见博客《Java2EE练习及面试题_chapter07异常处理》
多线程
JDK1.5之前创建新执行线程有两种方法:
继承Thread类的方式
定义子类继承Thread类。
子类中重写Thread类中的run方法。
创建Thread子类对象,即创建了线程对象。
调用线程对象start方法:启动线程,调用run方法。
代码演示:
package com.jerry.java; /** * @author jerry_jy * @create 2022-10-01 17:33 */ public class ThreadTest { public static void main(String[] args) { //3.创建Thread子类对象,即创建了线程对象。 MyThread myThread = new MyThread(); //4.调用线程对象start方法:启动线程,调用run方法。 myThread.start(); } } //1.定义子类继承Thread类。 class MyThread extends Thread { public MyThread() { super(); } // 2.子类中重写Thread类中的run方法。 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("子线程:" + i); } } }
注意点:
如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU 调度决定。
想要启动多线程,必须调用start方法。
一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上 的异常“IllegalThreadStateException”。
实现Runnable接口的方式
定义子类,实现Runnable接口。
子类中重写Runnable接口中的run方法。
通过Thread类含参构造器创建线程对象。
将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
代码演示:
package com.jerry.java; /** * @author jerry_jy * @create 2022-10-01 17:40 */ public class RunnableTest { public static void main(String[] args) { MyRunnable myRunnable = new MyRunnable(); //3.通过Thread类含参构造器创建线程对象。 //4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。 Thread thread = new Thread(myRunnable); //5.调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。 thread.start(); } } //1.定义子类,实现Runnable接口。 class MyRunnable implements Runnable{ //2.子类中重写Runnable接口中的run方法。 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("子线程:"+i); } } }
多线程的创建–>继承方式和实现方式的联系与区别:
区别
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable:线程代码存在接口的子类的run方法。
实现方式的好处
避免了单继承的局限性
多个线程可以共享同一个接口实现类的对象,非常适合多个相同线 程来处理同一份资源。
Thread类的有关方法
void start(): 启动线程,并执行对象的run()方法 run(): 线程在被调度时执行的操作 String getName(): 返回线程的名称 void setName(String name):设置该线程名称 static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类 static void yield():线程让步 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 若队列中没有同优先级的线程,忽略此方法 join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止 低优先级的线程也可以获得执行 static void sleep(long millis):(指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。 抛出InterruptedException异常 stop(): 强制线程生命期结束,不推荐使用 boolean isAlive():返回boolean,判断线程是否还活着
线程的优先级
线程的优先级等级 MAX_PRIORITY:10 MIN _PRIORITY:1 NORM_PRIORITY:5 涉及的方法 getPriority() :返回线程优先值 setPriority(int newPriority) :改变线程的优先级 说明 线程创建时继承父线程的优先级 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
补充:线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程
它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开
守护线程是用来服务用户线程的,通过在start()方法前调用 thread.setDaemon(true)可以把一个用户线程变成一个守护线程
Java垃圾回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出。
线程的生命周期
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五 种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已 具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中 止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
Synchronized的使用方法
Java对于多线程的安全问题提供了专业的解决方式:同步机制
1. 同步代码块: synchronized (对象){ // 需要被同步的代码; }
2. synchronized还可以放在方法声明中,表示整个方法为同步方法。 例如: public synchronized void show (String name){ …. }
同步机制中的锁
同步锁机制:
在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防 止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法 就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须 锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁 之时,另一个任务就可以锁定并使用它了。
synchronized的锁是什么?
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)
同步方法的锁:静态方法(类名.class)、非静态方法(this)
同步代码块:自己指定,很多时候也是指定为this或类名.class
单例设计模式之懒汉式(线程安全)
package com.jerry.java; /** * @author jerry_jy * @create 2022-10-01 19:27 */ public class SingletonTest { // 单例设计模式之懒汉式(线程安全) public static void main(String[] args) { Singleton singleton1 = Singleton.getInstance(); Singleton singleton2 = Singleton.getInstance(); System.out.println(singleton1 == singleton2);//true } } class Singleton { // 2.内部提供一个当前类Singleton的实例singleton // 4.此实例也必须静态化static private static Singleton singleton; // 1.私有化构造器 private Singleton() { } // 3.提供公共的静态的方法getInstance,返回当前类的对象return singleton public static Singleton getInstance() { if (singleton == null) { //synchronized同步监视器:每次只new 一个当前类的对象 synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; } }
Lock(锁)
class A{ private final ReentrantLock lock = new ReenTrantLock(); public void m(){ lock.lock(); try{ //保证线程安全的代码; } finally{ lock.unlock(); } } }
注意:如果同步代码有异常,要将unlock()写入finally语句块
synchronized 与 Lock 的对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有 更好的扩展性(提供更多的子类)
优先使用顺序:
Lock --> 同步代码块(已经进入了方法体,分配了相应资源)–> 同步方法 (在方法体之外)
线程通信
wait() 与 notify() 和 notifyAll()
wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报 java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声明。
wait() 方法
在当前线程中调用方法: 对象名.wait()
使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify (或notifyAll) 为止。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
调用此方法后,当前线程将释放对象监控权 ,然后进入等待
在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。
notify()/notifyAll()
在当前线程中调用方法: 对象名.notify()
功能:唤醒等待该对象监控权的一个/所有线程
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
示例题目:使用两个线程打印 1-100。线程1, 线程2 交替打印
package com.jerry.java; /** * @author jerry_jy * @create 2022-10-02 17:26 */ public class Communication implements Runnable { int i = 1; @Override public void run() { while (true) { synchronized (this) { notify(); if (i <= 100) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + i); i++; try { //使得调用如下wait()方法的线程进入阻塞状态 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } } } public static void main(String[] args) { Communication communication = new Communication(); Thread thread1 = new Thread(communication); Thread thread2 = new Thread(communication); thread1.setName("线程一"); thread2.setName("线程二"); thread1.start(); thread2.start(); } }
经典例题:生产者/消费者问题
package com.jerry.java; /** * @author jerry_jy * @create 2022-10-02 17:45 */ public class ProductTest { /* 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。 这里可能出现两个问题: 生产者比消费者快时,消费者会漏掉一些数据没有取到。 消费者比生产者快时,消费者会取相同的数据。 */ public static void main(String[] args) { Clerk clerk = new Clerk(); Productor productor = new Productor(clerk); Consumer consumer = new Consumer(clerk); Thread thread1 = new Thread(productor); Thread thread2 = new Thread(consumer); thread1.start(); thread2.start(); } } class Clerk { private int product = 0; public synchronized void addProduct() { if (product > 20) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { product++; System.out.println("生产者生产了第" + product + "个产品"); notifyAll(); } } public synchronized void getProduct() { if (product <= 0) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println("消费者取走了第" + product + "个产品"); product--; notifyAll(); } } } class Productor implements Runnable { Clerk clerk; public Productor(Clerk clerk) { this.clerk = clerk; } @Override public void run() { System.out.println("生产者开始生产产品"); while (true) { try { Thread.sleep((int) Math.random() * 1000); } catch (InterruptedException e) { e.printStackTrace(); } clerk.addProduct(); } } } class Consumer implements Runnable { Clerk clerk; public Consumer(Clerk clerk) { this.clerk = clerk; } @Override public void run() { while (true) { try { Thread.sleep((int) Math.random() * 1000); } catch (InterruptedException e) { e.printStackTrace(); } clerk.getProduct(); } } }
JDK5.0新增线程创建方式
新增方式一:实现Callable接口
与使用Runnable相比, Callable功能更强大些
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
package com.jerry.java; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @author jerry_jy * @create 2022-10-02 18:46 */ public class CallableTest { public static void main(String[] args) { //3.创建Callable接口实现类的对象 MyCallable myCallable = new MyCallable(); //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象 FutureTask futureTask = new FutureTask<>(myCallable); //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start() Thread thread = new Thread(futureTask); thread.start(); //6.获取Callable中call方法的返回值 //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。 try { Object o = futureTask.get(); System.out.println("总和为:" + o); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } //1.创建一个实现Callable的实现类 class MyCallable implements Callable { //2.实现call方法,将此线程需要执行的操作声明在call()中 @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { sum += i; } return sum; } }
新增方式二:使用线程池
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
package com.jerry.java; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; /** * @author jerry_jy * @create 2022-10-02 18:54 */ public class ThreadPool { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //设置线程池的属性 System.out.println(service.getClass());//class java.util.concurrent.ThreadPoolExecutor service1.setCorePoolSize(15); System.out.println(service1.getMaximumPoolSize());//10 //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象 service.execute(new NumberThread());//适合适用于Runnable service.execute(new NumberThread1());//适合适用于Runnable //3.关闭连接池 service.shutdown(); } } class NumberThread implements Runnable { @Override public void run() { for (int i = 0; i <= 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ": " + i); } } } } class NumberThread1 implements Runnable { @Override public void run() { for (int i = 0; i <= 100; i++) { if (i % 2 != 0) { System.out.println(Thread.currentThread().getName() + ": " + i); } } } }
–end–