多线程(二) 一、线程间通信 1.定义线程间通信就是多个线程操作同一资源,但是操作的动作不同。 2.等待唤醒机制等待唤醒机制,是由wait(),notify()或notifyAll()等方法组成。对于有些资源的操作,需要一个线程完成一步,进入等待状态,将CPU执行权交由另一个线程,让它完成下一步的操作,如此交替进行。这个过程中,一个线程需要在完成一步操作后,先通知(notify())另一个线程运行,再等待(wait()),进入冻结状态,以此类推。等待中的线程,都储存在系统线程池中,等待这被notify()唤醒。 以下代码,通过等待唤醒机制,实现了生产一个披萨,消费一个披萨: 程序运行部分结果如下: 3.Object类中的wait等方法wait()等多线程同步等待唤醒机制中的方法,被定义在Object类中是因为:首先,在等待唤醒机制中,无论是等待操作,还是唤醒操作,都必须标识出等待的这个线程和被唤醒的这个线程锁持有的锁;表现为代码是:锁.wait();锁.notify();而这个锁,由synchronized关键字格式可知,可以是任意对象;那么,可以被任意对象调用的方法,一定是定义在了Object类当中。wait(),notify(),notifyAll()这些方法都被定义在了Object类中,因为这些方法是要使用在多线程同步的等待唤醒机制当中,必须具备能被任意对象调用的特性。所以,这些方法要被定义在Object类中。 4、生产者消费者模型在实际生产时,会有多个线程负责生产,多个线程负责消费;那么在上述代码中启动新线程,来模拟多线程生产消费的情况。 示例代码: 用这样的方式,运行会出现如下结果: 生产了两个披萨,但只消费了一个。现在0,1线程负责生产,2,3线程负责消费,原因推断:1)当0线程生产完一个披萨,进入冻结;2)1线程判断有披萨,进入冻结;3)2线程消费一个披萨,唤醒0线程,进入冻结;4)3线程判断没披萨,进入冻结;5)现在出于运行状态的只有0线程,0线程生产一个披萨,唤醒1线程(1线程是线程池中第一个线程),进入冻结;6)1线程又生产了一个披萨 这导致了生产两个,只消费一个的问题。这个问题的发生是因为,第5步0线程唤醒1线程的时候,由于1线程的等待代码在if语句中,1线程醒了之后,不需要再判断flag的值所导致。如果1线程被唤醒,还要继续判断flag的值,就不会产生这个情况。因此,要将if判断,改为while循环,让线程被唤醒之后,再次判断flag的值。 示例代码: 每次被唤醒,都要判断flag的值。代码运行结果如下: 程序出现了无响应,因为使用while循环,可能会出现所有线程全部进入冻结状态的情况。要解决这个问题,必须用到另一个方法notifyAll();唤醒所有线程。由于用了while循环,所有线程被唤醒之后第一件事是判断flag的值,所以不会再出现多生产或多消费问题。至此,程序运行正常。 示例代码: 程序运行部分结果: 二、jdk5新特性 1.概述jdk5开始,提供了多线程同步的升级解决方案。将synchronized关键字,替换成Lock接口;将Object对象,替换为Condition对象;将wai(),notify(),notifyAll()方法,替换为await(),signal(),signalAll()方法。一个锁,可以对应多个Condition对象。这个特性的出现,可以让多线程在唤醒其他线程时,不必唤醒本方的线程,只唤醒对方线程。例如在生产者消费者模型中,使用Lock和Condition类,可以实现只唤醒消费者线程,或只唤醒生产者线程。 2.Lock接口和Condition接口1)Lock接口已知实现类中,有ReentrantLock类。这个子类可以用来实例化,创建ReentrantLock对象 ReentrantLock lock = new ReentrantLock(); 2)Condition接口的实例可以通过newCondition()方法获得 Condition conditon = Lock.newCondition(); 3)一个Lock对象可以对应多个Condition对象 Condition condition1 = Lock.newCondition();Condition condition2 = Lock.newCondition(); 3.新特性应用将此新特性应用在消费者生产者模型中,实现只唤醒对方线程。 修改之后的Pizza类代码如下: 分别创建的conditionPro和conditionCon对象,用于实现只唤醒对方线程,代码更优。 三、停止线程 1.线程停止原理stop()方法已经过时,停止的唯一标准就是run()方法结束。开启多线程运行,运行代码通常都是循环结构,只要控制住循环,就可以让run()方法结束,就可以让线程结束。 注意:当线程处于冻结状态,无法读取控制循环的标记,线程就不会结束。 2.interrupt()方法将处于冻结状态的线程,强制恢复到运行状态。interrupt()方法是在清除线程的冻结状态。 示例代码: 如果不调用t1和t2线程的interrupt()方法,程序会无响应,因为两个线程都处于冻结状态,无法继续运行。 上述程序运行结果: 四、Thread类其他方法 1.setDaemon()方法将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。该方法必须在启动线程前调用。守护线程可以理解为后台线程。后台线程开启后,会和前台线程(一般线程)一起抢夺CPU资源;当所有前台线程结束运行后,后台线程自动结束。可以理解为,后台线程依赖前台线程的运行。 示例代码: 在启动两个线程前,将两个线程设置为守护线程,其他代码不变;那么这两个线程依赖主线程运行;虽然这两个线程都处于冻结状态,但是当主线程运行完毕,这两个守护进程随之结束。 2.join()方法调用join()方法的线程,在申请CPU执行权。之前拥有CPU执行权的线程,将转入冻结状态,等调用join()方法的线程执行完毕,再转回运行状态。 示例代码: 程序在启动t1线程之后,主线程先等待t1线程打印完100个数;主线程再继续和t2线程交替打印100个数。…
Author: 0pr
Java基础—多线程(一)
多线程(一) 一、进程和线程 1.区别和联系区别:进程是一个正在进行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或一个控制单元。进程是系统进行资源分配和调度的一个独立单位。每一个应用程序启动的时候,都会被分配一定的内存空间。进程是用于标识这片内存空间,用于封装内存空间里的控制单元。 线程是进程中一个独立的控制单元,控制着进程的执行。它是CPU分配和调度其资源的基本单位。线程没有办法脱离进程独立运行,必须包含在进程中运行。多线程之间共享进程拥有的资源。 联系:进程和线程都是系统创建的。一个进程中至少有一个线程。同一个进程中的多个线程可以并发执行,提高程序运行效率。 示例代码: 用dos命令行编译上述代码时,会启动javac进程;运行上述代码时,会启动java进程,该进程中至少有一个线程在负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称为主线程。 小扩展jvm启动的时候,不止一个线程,还有负责垃圾回收机制的进程。 二、多线程 1.多线程存在的意义多线程的出现能让程序产生同时运行的效果。 2.自定义线程Java提供了对线程这类事物的描述,封装为Thread类。创建线程有两种方式:1)继承Thread类,复写run()方法,调用线程的start()方法 示例代码: start()方法启动线程,调用run()方法;修改代码,分析打印结果。 示例代码: 程序运行部分结果: 运行结果每次都不同,因为多个线程都在获取CPU的执行权(资源);CPU执行哪个线程,就运行哪个线程里的代码;某一时刻,只有一个线程在运行(多核除外);CPU在做着快速切换,达到了看上去程序是同时运行的效果。这是多线程的随机性。如图: 程序开始运行,主线程启动,随后创建好的myThread线程启动,负责执行run()方法中的代码;主线程负责执行main()方法中的代码;CPU分配资源,交替执行两个线程,所以有如上运行结果。 小扩展1复写run()方法的原因Thread类用于描述线程,该类定义了一个功能用于存储线程要运行的代码,这个功能就是run()方法。 小扩展2直接调用子类中的run()方法,程序的运行结果示例代码: 程序运行结果为: 如果直接调用run()方法,该程序并没有启动新的线程,而整个程序是由主线程完成执行的。myThread线程虽然被创建,但是始终没有启动。如图: 主线程执行到run()方法,就将run()方法中的代码先执行完,再回到main方法中,执行main方法中的代码;主线程独享CPU资源;直接调用run()方法的效果,相当于对象调用方法,没有多线程执行的特点。 小练习1创建两个线程,和主线程交替运行。示例代码: 程序部分运行结果为: 小练习2用Thread类的getName()方法,打印线程名称示例代码: 程序部分运行结果为: Thread-0即为tt1线程名称。每个线程有自己默认的名称,即Thread-[编号]。标准通用的获取当前线程名称的方法是使用Thread类的currentThread()方法: Thread.currentThread().getName(); currentThread()方法为静态方法,获取当前线程对象,返回Thread类型。如果要自定义名称,使用Thread类的setName()方法或使用线程构造方法即可。 2)实现Runnable接口用多线程实现一个简单的多窗口卖票程序。 示例代码: 程序部分运行结果为: 上述程序声明了静态成员,由于静态成员声明周期过长,一般不建议使用静态属性。如果没有静态,则每个线程对象里有100张票,总共将卖出400张。要解决这个问题,就要使用线程的第二种创建方法:实现Runnable接口。 实现Runnable接口的步骤:1> 声明一个类,实现Runnable接口示例代码:class Ticket implements Runnable {} 2> 复写Runnable接口中的run()方法,将线程要运行的代码存放在run()方法中示例代码:class Ticket implements Runnable {public void run() {} } 3> 创建Thread线程对象,并将Runnable接口的子类对象作为实际参数传给Thread类的构造方法示例代码:Ticket ticket = new Ticket();Thread…
Java基础—面向对象(五)
面向对象(五) 一、finally关键字 1.定义finally代码块,定义了无论异常发生与否,一定会执行的代码。通常用于关闭资源。流操作,数据库操作等,最后都应该用finally关闭流和数据库连接,以确保资源释放。 2.异常处理的分层设计分层设计是模块式开发的体现,每个开发者负责相应的模块。如果异常出现,各开发者负责处理各自模块的异常,并封装好能够被其他模块开发者解决的异常对象(层内封装),抛出给相应开发者。这样的开发模式,让异常处理合理化,每个异常都能被有效解决,提高开发效率。 3.异常处理语句格式1)try catch 示例代码: 2)try catch finally 示例代码: 3)try finally 示例代码: 该格式用于,在程序其他地方处理异常,但在此try fianlly语句中一定要关闭资源的情况,将关闭资源的代码,放在finally中。 小扩展finally在其之前异常处理阶段出现了System.exit(0);这行代码的情况下,不会被执行 示例代码: 程序输出结果为: 二、异常在继承状态中的特点 1.子类在复写父类方法时,如果父类的方法抛出异常,那么子类复写的方法只能抛出父类异常或其异常的子类 示例代码: 异常结构图: Exception AException BException CException 子类继承父类,子类不能比父类出现的异常还多;其出现的异常,也只能是父类的异常,或父类异常的子类。 示例代码: 上述代码,在main方法中将SubClass的对象传给function()方法,而function()方法接受一个父类对象作为参数,即父类引用指向子类对象;那么p.method()调用的是子类SubClass的method()方法;由于父类抛出的是AException,在Test类中用try catch进行捕捉,并且catch语句只能处理AException及AException的子类;如果子类的method()方法抛出的是CException,CException和AException没有任何关系,catch语句无法处理,程序无法编译;如果子类的method()方法抛出的是AException或BException,AException是父类方法抛出的异常,BException是父类方法异常的子类,那么catch语句中捕捉AException就能处理子类方法抛出的异常;这是为什么子类复写父类的方法,只能抛出父类方法的异常,或父类方法异常的子类。 2.如果父类方法抛出多个异常,那么子类复写该方法时,只能抛出父类方法异常或其子集父类抛出A,B,C异常,子类只能抛出A,B,C异常或A,B,C异常的子集 3.如果父类或者接口的方法中没有抛出异常,子类复写的方法也不能抛出异常通用规则,如果子类方法可能会发生父类方法中没有的异常,只能在子类方法内部进行catch处理,不能抛出。 4.运行时异常练习写一个程序,获取长方形面积;如果初始化的时候传入的长或宽的值小于等于0,抛出异常。 示例代码: 创建Rectangle对象,传入0,4,程序运行结果为: 异常机制的出现,让问题处理代码和正常流程代码分离,代码阅读性更强。 三、异常机制总结 1.异常的定义异常是对问题的描述,并将问题封装成对象。 2.异常的优点1)将问题进行封装;2)将正常流程代码和问题处理代码分析 3.异常处理原则1)异常有两种处理方式:try catch或者throws;2)调用抛出异常的方法时,抛出了几个异常,就要处理几个异常;3)多个catch语句,处理异常父类的catch语句放在最下面;4)catch内,定义针对性的处理方式,不要简单打印,也不要什么都不定义;5)捕获的异常,如果处理不了,可以继续在catch中抛出;6)在抛出异常时,尽量结合分层设计思想,将异常转化为其他模块可以处理的异常,封装后再抛出 4.异常注意事项1)子类复写父类的方法时,子类复写的方法抛出的异常必须是父类方法抛出的异常或父类方法抛出异常的子类或者子集;2)如果父类或者接口的方法没有抛出异常,子类复写的方法不能抛出异常;如有异常,只能内部解决 5.异常体系 Throwable Error Exception RuntimeException 异常体系中的所有类,以及建立的对象,都具备可抛性,可以被throw和throws关键字操作。 6.throw和throws的区别1)throw定义在方法内,用于抛出异常对象方法内抛出的异常对象(非运行时异常),必须在方法上声明,或用try catch处理,否则编译失败;throw单独存在时,下面不要定义语句,因为执行不到2)throws定义在方法上,用于抛出异常类方法上声明了异常,调用这必须进行处理,可以抛出,也可以try catch 7.异常的分类1)编译时异常(编译时被检测)该异常在编译时,如果没有处理(没有抛出,也没有try catch),编译失败2)运行时异常(编译时不检测)该异常在编译时不需要声明或者处理,编译都通过;该异常的发生,建议不处理,让程序停止,由方法调用者对代码进行修正 8.异常处理代码try…
Java基础—面向对象(四)
面向对象(四) 一、内部类 1.内部类的定义将一个类定义在另一个类的内部,这个内部的类就被成为内部类(内置类,嵌套类)。 2.内部类的特点1)内部类可以直接访问外部类的成员,包括私有成员(成员前省略了外部类名.this.);2)外部类要访问内部类的成员,必须建立内部类的对象,通过内部类对象访问 示例代码: 程序输出结果为: 3)当内部来在外部类的成员位置上时,可以被private关键字修饰,进行封装 示例代码: 4)外部其他类直接访问内部类成员(内部类声明在外部来成员位置上,且非私有) 示例代码: 5)不涉及特有数据的时候,内部类可以被static关键字修饰,具备静态特性 内部类被静态修饰后,只能访问外部类的静态成员,有访问局限性。外部其他类直接访问静态内部类的非静态成员的方式为: 示例代码: 外部其他类直接访问静态内部类的静态成员的方式为: 6)内部类中若声明了静态成员,该内部类必须是静态的7)外部类的静态方法访问内部类时,内部类必须是静态的8)重名变量注意事项 示例代码: 程序输出结果为: 如果要输出4,输出语句应为System.out.println(“Inner: ” + this.x);如果要输出3,输出语句应为System.out.println(“Inner: ” + Outer.this.x); 9)内部类可以被定义在局部位置上内部类定义在局部位置上的示例及特点 示例代码: 特点:1> 不可以被成员修饰符修饰,如private2> 仍持有外部类的引用,仍可以直接访问外部类的成员3> 如果要访问其所在局部的局部变量,要用final修饰该变量 3.内部类的应用描述事物的时候,如果事物内部还包含有事物。由于该内部事物在使用外部事物的内容,所以该内部的事物用内部类来描述。如描述人体的时候,人体由器官组成,包括心脏;心脏有属性(成员变量),行为(成员方法),比较复杂(应声明成一个类),心脏又和人体其他器官有直接的联系(需要直接访问外部类的成员),那么应将心脏作为对象封装,形成人体的内部类,并私有,对外提供方法访问。 二、匿名内部类 1.定义没有名字的内部类叫做匿名内部类,即内部类的简写格式 2.匿名内部类使用的前提内部类必须继承一个类或者实现接口 示例代码: 3.匿名内部类是一个匿名子类对象,是一个带内容的对象,是将定义类和创建对象封装为一体的表现方式,为了简化书写,方便方法调用而存在 匿名内部类就是一个子类继承父类的类体,除了复写父类的方法,还能在匿名内部类中定义子类的特有方法(面试用) 示例代码: 匿名内部类中的方法不要超过3个 4.匿名内部类的应用 示例代码: 5.匿名内部类练习根据main方法中的代码,补全其他代码 示例代码: 小扩展(面试用):没有实现接口,也没有继承任何父类,仍然可以使用匿名内部类调用方法;只需使用所有类的父类Object类即可示例代码: 三、异常机制 1.定义异常是程序在运行(非编译)过程中出现的不正常情况。异常用来描述生活中出现的问题,也是一个对象。 2.问题的分类Java对于问题的描述分为两大类,有严重与非严重之分。对于严重的问题,Java用Error类来描述,称为错误;对于非严重的问题,Java用Exception类进行描述,称为异常。1)对于Error,一般不编写针对性的代码进行处理;2)对于Exception,可以针对性的编写代码进行处理 Error和Exception都是Throwable的子类 3.异常的处理Java提供了特定的语句对异常进行处理: 示例代码: 示例代码: 4.异常对象常见方法1)getMessage() 示例代码: 2)toString() 示例代码:…
Java基础—面向对象(三)
面向对象(三) 一、多态 1.多态定义多态就是事物存在的多种体现形态。多态允许同一父类不同的子类对象对同一方法产生不同的行为方式。例如,中国人和俄罗斯人都是人的子类,都具备吃饭这个功能;但是当中国人和俄罗斯人分别调用吃饭这个方法的时候,其执行变现出不同的行为方式,即俄罗斯人是用叉子吃,中国人是用筷子吃。 2.多态的体现多态是指在执行期间(而非编译期间)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。示例代码一步一步解释多态的形成过程。 定义一个Man类,代表人,定义俄罗斯人Russian和中国人Chinese的类,并继承Man,复写Man中的eat()方法,并创建对象调用该方法。 示例代码: 在主函数中,如果要创建很多个Russian对象,或者Chinese对象,就要写很多句对象.方法的语句,那么将该语句提取成方法。 示例代码: 但是这么一来,如果以后还要加入印度人Indian,他们用手吃饭。那么在主函数中,就要加入新的eat()方法: public static void eat(Indian i); 如果以后还要创建很多国家人的对象,这样的方式就不合理。由于无论俄罗斯人,中国人还是印度人,都是人的子类,那么语句: Man m = new Chinese(); 是没有问题的,因为中国人是人。所以,修改之后的代码,就是多态的体现。 示例代码: 主函数中func()方法在执行的时候,判断接收到的人对象的具体类型是中国人还是俄罗斯人(动态绑定),最后调用相应对象的eat()方法,体现多态。 小扩展:Man r = new Russian();这行代码,是向上转型,将子类Russian向上转型为Man类型,也称为类型提升。 3.多态的前提要实现多态,必须有:1)继承必须有类与类之间的继承关系,有继承,才有第二前提复写; 2)复写必须有子类对父类方法的复写,有复写,多态才有可以调用的方法,存在才有意义; 3)父类引用指向子类对象(向上转型)必须有向上转型,子类都继承于父类,多态在程序执行期间,通过判断调用方法的对象的实际类型,体现出具体方法内容,才能达到让代码具备可扩展性的目的 4.多态的利弊1)利:多态提高了程序的扩展性; 2)弊:只能使用父类的引用访问父类中的成员(详细解释见代码) 示例代码: 如果想要调用Chinese对象特有的speak()方法,应该强制将父类引用向下转型成子类类型。 示例代码: 注意:Man m = new Chinese();Chinese c = (Chinese)m;这是合法的;但是如果写成:Man m = new Man();Chinese c = (Chinese)m;这是非法的,m指向的是人类型,不能说人就是中国人。转型的原则可以理解成,自始至终是子类的对象在做向上或者向下转型。 小扩展:instanceof运算符m instanceof Chinese判断m是否属于Chinese类型,是,返回true,否,返回false 5.多态使用的注意事项1)多态状态下成员方法的特点:以代码Parent…
Java基础—面向对象(二)
面向对象(二) 一、继承 1.继承概述将多个类的共性抽取出来,形成一个父类。让所有具备这些共性属性的类,继承这个父类,成为其子类。这样做的好处在于:1)这样可以避免重复代码,提高代码复用性;2)继承是面向对象另一特点多态的基础之一,有了继承,才有多态;3)同时可以使子类的功能更加强大灵活,便于扩展 注意:1)不要仅为了获取其他类的功能,而使用继承;必须在类与类之间有所属关系的情况下,才使用继承; 2)Java中只支持单继承。因为多继承容易带来安全隐患。如果支持多继承,多个父类当中有相同名字的不同功能的方法,子类在调用同名方法的时候,虚拟机不能确定要运行哪个; 3)Java中保留了多继承的机制,用多实现的方式来体现(implements); 4)Java支持多层继承,C继承B,B继承A,以此类推,形成一个继承体系。继承体系相对复杂,想要良好运用该体系,先查阅体系中父类的描述,通过了解父类中的共性功能,就能了解该继承体系的基本功能。在具体调用时,应创建最小子类的对象。由于:1> 父类不能创建对象,如抽象类;2> 子类对象有更多的功能,包括父类和自由的功能 2.继承的表现形式类与类之间不止有A属于B这一种关系,还有聚集的关系。聚集关系分为:1)聚合关系:A由B组成,B是A中的一个,但是缺少一个B,不影响A的整体功能;例如:球队由球员组成,一个球员是球队的一员;2)组合关系:A由B组成,B是A的一部分,缺少B,A的功能将受到极大影响;例如:心脏是人的一部分 3.继承的应用1)super关键字1> super关键字和this关键字的用法基本一致;2> this代表本类对象的引用,super代表父类对象的引用 2)这一小节探讨继承关系出现后,类中成员的变化1> 变量的变化 示例代码: 继承状态下,成员变量内存加载图示: 继承状态下,成员变量的变化如下:如果子类和父类中出现非私有同名变量,子类要访问本类中的同名变量,用this关键字;子类要访问父类中的同名变量,用super关键字。 小扩展:继承的时候,子类继承父类的成员变量,同时继承该成员变量的值。 示例代码: 2> 方法的变化 示例代码: 继承状态下,成员方法的变化如下:当子类出现和父类一模一样的方法时,子类对象调用同名方法,会运行子类方法的内容。由于子类继承了父类的方法,但是子类在具体实现的时候需要有不同的功能,这个时候,只需要用到方法的另一个特性:复写。在子类中定义一个和父类完全相同的方法,在方法体中实现子类的特有功能即可。如果要在子类的复写方法中,调用父类的方法,只需要加上super.方法名即可。 小扩展:super关键字的使用 示例代码: 子类对象调用show()方法时,上述代码既输出”Parent show”,也输出”Sub show”。 复写注意事项:a. 复写成功必须保证子类方法权限大于等于父类方法权限;b. 静态方法只能复写静态方法;c. 复写和重载的区别在与,复写要求子类父类方法一模一样,重载要求同名函数的参数列表不一样 3> 构造方法的变化 示例代码: 子类对象进行初始化时,父类的构造方法也会执行。因为子类的构造方法第一行默认写有隐式语句super();super()会访问父类中空参数的构造方法。super();存在于所有子类的所有构造方法的第一行。如果访问父类中指定的构造方法,只需通过手动定义super语句。 子类一定要访问父类构造方法的原因:由于子类继承了父类的成员,子类对象在被创建时,应该先知道父类是如何对其成员进行初始化的,以避免代码功能的重复。 注意:构造函数中,this和super只能存在一个,因为两者都必须写在构造方法第一行。 二、final关键字 1.final的定义及特点final作为一个修饰符:1)可以修饰类,变量以及方法;2)被final修饰的类不能被继承;3)被final修饰的方法不能被复写;4)被final修饰的变量是常量,值不能被改变;既可以修饰成员变量,也可以修饰局部变量;5)内部类定义在类中的局部位置上时,只能访问该局部被final修饰的局部变量 示例代码: 三、抽象类 1.抽象的定义抽象就是将多个事物的共性的、本质的内容进行抽取和概括。例如:老虎,猫,狮子都是猫科动物,猫科就是一个抽象的概念。 2.抽象类抽象类是包含抽象方法的类。抽象方法只有方法的定义,没有方法体。抽象方法的具体实现,有子类复写完成。抽象方法只负责抽象出不同对象的共性,但是不涉及具体实现的细节。例如:猫和狗都会叫,但是叫的内容不同。抽象方法定义一个叫的功能,具体叫的内容则有子类对象来实现。 3.抽象类的特点1)抽象方法一定定义在抽象类中;2)抽象方法和抽象类都被abstract关键字修饰;3)抽象类不可以实例化;4)抽象类中的抽象方法要被使用,必须由子类复写其所有抽象方法,由子类对象进行调用;如果子类中只覆盖了部分抽象方法,那么该子类还是一个抽象类;5)抽象类中可以不定义抽象方法,仅为了让该类不被实例化 代码示例: 4.抽象类练习1)练习1:开发一个系统需要对员工进行建模,员工包含3个属性:姓名,工号以及工资;经理也是员工,除了含有员工的属性外,另外还有一个奖金属性。请使用继承的思想设计出员工类和经理类。 示例代码: 2)练习2:模版方法模式使用System类中的currentTimeMillis()方法获取一段程序运行的时间。 示例代码: 如果GetTime类要被其他程序使用,来获取程序运行时间,那么该类中所要运行的程序代码是不确定的。因此,首先将要运行的代码提取出来,封装在一个方法中。 示例代码: 现在想运行另外一段代码,直接修改源代码的方式操作性极差。那么可以运用一个类,继承GetTime类,然后以复写runCode()方法的方式更改要运行的代码,并在主函数中创建子类的对象,调用getTime()方法即可。 示例代码:…
Java基础—面向对象(一)
面向对象(一) 一、理解面向对象 面向对象作为一种编程思想,其本质是基于面向过程的。相对于强调功能行为的面向过程变成方式而言,面向对象强调的是功能的封装,形成具备一定功能的对象。面向对象的思维方式,符合人们的思考习惯,可以将复杂的事情简单化。从面向过程到面向对象,程序员完成了从执行者到指挥者的角色转变。 在使用面向对象概念的时候:1.先找具有所需要功能的对象是否存在,存在即可使用;2.如果不存在,那么创建一个具备所需功能的对象;3.创建具备不用功能对象的过程,就是简化开发,提高代码复用性的过程 二、面向对象的特征: 1.封装:Java的封装是一种信息隐藏技术,将属性和方法封装在一个类当中,只对调用者开放相应接口来访问该类的成员属性和方法。封装提高了程序的复用性和可维护性; 2.继承:Java的继承特性,能使一个派生类类拥有其父类的属性和方法,并扩展其特有的方法; 3.多态:指允许不同类的对象对同一方法做出不同响应。即同一方法可以根据调用对象的不同而表现出多种不同的行为方式。多态的发生必须满足1)继承;2)复写;3)父类引用指向子类对象;这三个条件 三、类与对象的关系 Java中通过定义类的形式,来描述生活中的具体事物。类是具体事物的抽象定义。对象则是该类在生活中的具体体现,是实实在在的个体。 四、类的定义 生活中描述事物,就是要描述事物的不同属性和行为。如:人有身高,体重,年龄等属性;有起床,刷牙等行为。在Java中,用类(class)来描述事物。 属性:就是类中声明的成员变量;行为:就是类中声明的成员方法; 定义一个类,其实就是在定义类中的成员变量和成员方法。 类的示例代码: 创建对象的代码示例: 该对象的内存结构图如下: 当new一个Car对象的时候:1.在堆内存中开辟空间,储存Car()对象及其成员变量color,num;2.将内存地址赋给栈内存中该对象的引用c1,c1即指向Car()对象;3.c1修改color的值,将新值赋给color 五、成员变量和局部变量的不同 1.作用域不同:1)成员变量作用域为整个类;2)局部变量作用域为方法或者语句 2.在内存中的位置不同:1)成员变量在堆内存中;对象实例被new出来之后,才开辟内存空间给成员变量;2)局部变量在栈内存中 六、匿名对象: 1.应用一: 如果使用多个匿名对象,第一行执行完之后,由于没有引用指向这个对象,该对象立刻成为垃圾,等待回收。 用匿名对象调用方法是通常做法,调用成员属性没有意义。所以,当对对象方法只调用一次时,可以用匿名对象完成。如果对对象进行多个成员调用,必须给这个对象起名字。 2.应用二:将匿名对象作为实际参数进行传递。 不使用匿名对象作为参数的示例代码: 上述代码内存运行过程如图: 运行过程:1.将创建的对象的内存地址赋给引用c,c即指向该对象;2.将引用c传给该对象的show()方法,即将该对象的地址赋给show()方法的参数c,show()方法中的参数c即指向该对象;3.show()方法访问该对象的成员变量 使用匿名对象作为参数的示例代码: 上述代码内存运行过程如图: 使用匿名对象作为参数,直接将对象地址赋给show()方法的参数c,c再访问该对象的成员变量。 七、封装 1.定义封装是指,隐藏对象的属性和实现细节,仅对外提供公共访问方式。 2.封装的好处1)类的内部变化被隔离,调用者无须知道这些变化,照样能实现功能;2)类内部细节的实现无须调用者了解,便于使用;3)封装的代码可以被不同调用者使用,提高复用性;4)不想对外开放的属性,通过封装能很好隐藏起来,提高安全性 3.private关键字private:用于修饰类中的成员变量和成员方法,被修饰的成员只能在本类被访问。private是封装的一种表现形式。当使用private修饰成员变量后,应设置相应的get和set方法,让外部访问私有变量。 示例代码: 八、构造方法 1.构造方法特点1)方法名与类名相同;2)不用定义返回值类型;3)不可以写return语句;4)在创建对象的时候,自动调用 2.构造方法的作用构造方法可以对对象进行初始化。对象在创建的时候,就应该具备其基本特性。构造方法就初始化了这些基本特性。 3.构造方法小细节1)当一个类中没有定义构造方法时,Java默认会为该类加入一个空参数的构造方法;2)当在类中声明了自定义的构造方法后,Java不再为该类添加构造方法;3)对象创建时就具备的特性和行为,可以定义在构造函方法中,以便创建时初始化;4)构造方法中初始化过了的私有变量,还是需要定义set和get方法,因为在对象的使用过程中,可能涉及改变其私有变量的值,此时就要用到set方法,获取该私有变量的值,需要用到get方法 示例代码: 九、构造代码块 1.构造代码块的定义构造代码块是定义在类中的一个独立代码区间。构造代码块的作用是为对象进行初始化。对象一旦创建就立刻运行构造代码块,且运行优先级高于该类的构造方法。 2.构造代码和构造方法的区别1)构造代码是给所有对象进行统一初始化;2)构造方法是给对应的对象初始化 3.构造代码块的应用将所有对象的共性属性或行为定义在构造代码块中,那么所有对象创建的时候同一执行构造代码块中的内容。 示例代码: 十、this关键字 1.this的概念this关键字代表调用this所在方法的对象的引用,即哪个对象调用了this所在的方法,this就指向那个对象。 2.this关键字的应用1)this关键字用于区分局部变量和成员变量重名的情况;并且,定义类中方法时,如果需要在方法内部使用本类对象的时候,用this指向这个对象 示例代码: 2)this关键字可以用于构造方法间互相调用 示例代码: 3)注意:this关键字调用其他构造方法的语句只能写在构造方法的第一行,因为初始化动作要先执行 十一、static关键字…
Java基础—数组
一、数组的定义 1.数组的概念数组是同一种类型数据的集合,是一个容器,用于存储多个数据。 2.数组的优点数组中的每个元素都有下表值,下标从0开始。下标值方便程序员对数组中的数据进行操作。 3.数组定义的格式1)元素类型[]数组名=new 元素类型[元素个数或数组长度]; 这种定义方法称之为动态定义法。 示例:int[] arr = new int[5]; 2) 元素类型[]数组名= new 元素类型[]{元素,元素,……}; 这种定义方法称之为静态定义法。 示例:int[] arr = new int[] {3,5,1,7}; int[] arr = {3,5,1,7}; 二、引用数据类型内存结构 1.Java内存结构由于Java对不同类型的数据有不同的处理方式,所以Java对内存进行了不同的区域划分。这提高了运算效率,优化了内存管理方式。 1)栈内存栈内存用于储存局部变量。当数据使用完毕之后,其所占空间会自动释放。 2)堆内存a. 堆内存用于储存对象和数组,所有通过new建立的实例都在堆内存中存放;b. 每一个对象实例都有一个内存地址;c. 实例中的变量都有默认的初始化值;d. 实例不再被使用时(没有任何引用指向该实例),该实例会在不确定的时间被垃圾回收器回收,释放内存 2.数组的内存结构示意图数组属于引用数据类型,其内存结构示意图如下: 当执行int[] x = new int[3]时,1)在栈内存开辟一个内存空间,用于存储变量x;2)在堆内存开辟内存空间,用于存储new出来的数组对象实例;3)将数组对象的内存首地址(0x0079)赋给x;4)至此,x就是数组对象的引用,指向数组对象 3.两个引用指向同一数组对象实例 该代码段中,将数组的内存地址引用赋给了x,也赋给了y。那么x,y就指向了同一数组对象。x,y的内存情况如下图: 因此,当x = null执行之后,内存中并没有产生垃圾,因为变量y也指向该数组,该数组仍处于被引用状态。同时,y[1] = 89执行之后,x[1]的值也将变为89,而不是0。 4.基本数据类型内存结构对比引用数据类型中,两个变量指向同一对象实例,其中一个变量对对象的值进行修改,会影响另一个变量,这是引用数据类型的规律。下面对比一下基本数据类型做同样的赋值操作之后的内存示意图。 运行结束后,a的值仍旧为5。内存图如下: 三、数组操作过程中常见的异常 1.ArrayIndexOutOfBoundsException数组下标越界异常 arr[3]超出下标范围,出现异常。 2.NullPointerException空指针异常 arr被赋值为空,已经无法指向数组对象,再使用arr操作数组,出现空指针异常。 四、排序算法 1.选择排序1)从下标为0的数开始,逐一和后面的数进行比较,遇到小的数,则交换位置…
Java基础—for循环的嵌套
利用for循环的嵌套打印六种类型的 * 形三角。示例一: 示例二: 示例三: 示例四: 示例五: 示例六: 写代码之前,最重要的是先有思路,然后写出步骤,分阶段完成各个目标,达到最终结果。