写在前面: 本文为本人在期末复习时临时撰写的java复习文档,其中引用的他人内容均已添加链接.吐血整理数万字+数十张图片+示例代码,希望本文能对你有所帮助.
1.java概述
-
一次编程,到处运行 .java文件经过编译器变为.class字节码,在JVM上运行
public class HelloWorld{public static void main(String[] args){System.out.println("Hello World!");}
}
-
java环境搭建
-
编译与运行 javac HelloWorld.java — java HelloWorld
-
JDK>JRE>JVM,JVM为在真实计算机上通过软件仿真实现的虚拟机用来执行java字节码
-
包括了指令集,.class文件格式,寄存器组,类和接口的装载链接初始化,栈堆方法区
-
java特征:简单, 面向对象, 分布式, 解释型, 健壮,安全, 体系结构中立, 可移植, 高性能, 多线程和动态
2.面向对象程序设计概念
-
Object-Oriented Programming (OOP) is a programming paradigmusing “objects” – data structures consisting of data fieldsand methods together with their interactions – to designcomputer programs
-
Alan Kay的oop五大原则:一切皆对象;程序是一系列对象的组合,对象间通过消息传递进行联系;每个对象都有自身内存空间,可容纳其他对象;每个对象都有一种类型;同一类型的所有对象都能够接收相同的消息
-
对象包括成员变量和成员方法
-
类:In OOP, a class is an extensible program-code-template forcreating objects, providing initial values for state (membervariables) and implementations of behavior (member functionsor methods).
-
类与对象的关系
-
类是同种对象的集合与抽象
-
类描述对象的共同的数据特征和行为特征
-
类的实例化就是对象
-
-
抽象是一种设计技术,将复杂现象简化到可以分析的程度,识别重要细节,忽略无关细节.OOP过程需要确定将哪些属性和行为包括在给定的抽象中
-
接口:
-
类是通过在对象抽象的基础上对抽象结果的组织获得的,接口是组织的依据之一. An interface is a description of the actions that an object can do for the outside world.
-
一个类可以实现多个接口,一个接口可以被多个类实现
-
-
封装:把数据和方法包装进类中,并对具体实现进行隐藏
-
模块化: 不同对象间仅通过必要的消息进行交互
-
保证数据对象的一致性: 通过隐藏对象变量和方法实现, 防止绕过接口更改变量
-
易于维护: 对私有变量和私有方法的更改, 不会影响到调用对象接口的其他程序, 提高了程序的可移植性
-
-
继承:描述子类与父类之间的关系。子类是父类的特例, 继承、修改和细化父类的状态和行为
-
子类继承父类的变量和方法
-
子类可以增加新的变量和方法
-
子类可以重写(Override)继承来的方法
-
继承关系可以有多层,子类要继承它所有父类的方法与状态
-
-
多态:泛指能够通过同一名称或接口表示多种形式的方法和多种类型的对象的能力
-
编译时多态: 一个类中定义多个名称相同但参数不同的方法通过方法重载(overloading)实现
-
运行时多态: 通过相同的接口访问不同类型的对象的能力
- 这些对象所对应的每一种类型都提供了对该接口的独特实现
- 通过方法重写、向上转型和动态绑定, 达到“对外一个接口,内部多种实现”
-
3.java语言基础
-
标识符命名规则
-
一个由字母、“_”(下划线)、“$”和数字组成的不限长度的序列
-
以字母、“_”或“$”起始,不能以数字起始
-
不能是Java关键字,不能是true、false或null
-
大小写敏感不能包含空白
-
-
风格约定:(习惯)
-
不使用“_”、“$”作为标识符的第一个字符
-
类名、接口名: 所有单词首字母大写, 其余字母小写,如HelloWorld
-
变量名、方法名: 首单词小写, 其余单词的首字母大写,如someVariable
-
常量名: 完全大写, 单词间由“_”分隔, 如CONST_POOL_NUM
-
方法名使用动词, 类名和接口名使用名词, 变量名尽量有含义
-
定义类名时不要使用$, 否则该类的.class文件可能与编译器生成的内部类.class文件冲突
-
-
注释
-
关键字
-
数据类型
-
基本数据类型
-
boolean true or fasle,java中布尔值和整形不能互换
-
整型:byte,short int long char.long后加L或l,推荐用L,char采用国际unicode标准字符集
-
转义字符
-
浮点型:float double ,float后必须加F或f,默认为double,特殊浮点型NAN,正负无穷大POSITIVE_INFINITY,NEGATIVE_INFINITY,0.0与-0.0
-
-
引用数据类型
-
-
变量的声明,引用,赋值,引用变量声明时不分配数据空间,必须经过实例化
-
变量的作用域
-
变量的默认初始值
-
操作符
-
算术操作符 + – * / % ++ –
-
关系操作符 > >= < <= == !=
-
位操作符
-
逻辑操作符
-
赋值操作符
-
其他
-
-
强制类型转化
-
程序流控制
-
if ,if else,switch,while,do-while,for,增强for,break,continue,label,return,跳转语句
-
-
数组
-
数组声明:类型+名称+维度
-
数组初始化
-
数组元素访问:通过索引值访问,每个数组包含元素个数的成员变量length
-
4.java面向对象特性
-
对象与类
类定义:类声明+类体
类声明{ 成员变量声明:构造方法声明;成员方法声明;初始化程序块; } [public][abstract|final] class 类名 [extends 父类名] [implements 接口名列表]{ }
**abstract: 声明该类为抽象类(不能实例化出对象)** **final: 声明该类不能被继承** **extends:表示该类从父类继承得到。** **implements:该类实现了接口名列表中的所有接口** > 成员变量声明: [public | protceted | private] [static] [final] [transient] [volatile] 类型 变量名; > >public|protected|private: 成员变量的访问权限 static: 限制该成员变量为类变量(无static: 该成员变量为实例变量) final: 用于声明一个常量, 该常量的值在程序中不能修改 transient: 声明一个暂时性变量(不会被串行化) volatile: 禁止编译器通过保存多线程共享变量的多个拷贝来提高效率> 成员方法声明: [public | protected |private] [static] [final| abstract] [native] [synchronized] 返回类型 方法名([参数列表]) [throws 异常类型列表]{ [语句块] } >>public, protected, private, static:与成员变量类似 abstract:声明该方法是抽象方法,无方法体 final:声明该方法不能被重写 native:本地方法 synchronized:多线程访问共享数据throws:抛出异常的列表
方法调用中的参数传递
方法声明时的注意事项
可变长参数:类型 … 参数名
方法内部将可变长参数作为数组对待
可变长参数只能作为方法参数列表中最后一个参数
public class VariousArgs{ public double ratingAverage(double r, int ... points){ int sum=0; for(int p: points) sum+=p; return ((sum*r)/points.length); } public static void main(String[] args){ VariousArgs va=new VariousArgs(); System.out.println( va.ratingAverage(0.5, new int[]{95,90,85}) ); System.out.println( va.ratingAverage(0.5, 94,92,90,88,86)); } }
this关键字
1.若局部变量名与类的成员变量名相同, 则类的成员变量被隐藏, 需使用this将该成员变量显露出来
public class UnmaskField{ private int x = 1; private int y = 1; public void changeFields(int a, int b) { x = a; //x指成员变量 int y = b; //局部变量y使同名的类成员变量被隐藏 this.y = 8; //this.y指成员变量 System.out.println(“x=” + x + “; y=” + y); //局部变量y的值 } public void PrintFields() { System.out.println(“x=” + x + “; y=” + y); } public static void main(String args[]) { UnmaskField uf = new UnmaskField(); uf.PrintFields(); uf.changeFields(10,9); uf.PrintFields(); } }
2.this可作为返回值, 返回对当前对象的引用**(第一点其实就是第二点)**
Java学习笔记14—this作为返回值时返回的是什么_weixin_30896825的博客-CSDN博客
从结果可以看到,返回值与调用方法的对象引用是一致的,是指向同一个子类对象的;
综上,这些情况下也适用“谁调用返回谁”。
3.在一个构造方法中调用另外一个构造方法
public class Student{String behavior1;String behavior2;String clothes;public Studenr(){this("说普通话","做文明事");//通过参数去匹配构造方法,必须放在第一行}public Student(String behavior1,String behavior2){this("穿校服");//必须放在第一行this.behavior1=bahavior1;this.behavior2=behavior2;}public Student(String clothes){this.clothes=clothes;}public static void main(String[] args){Student xm =new Student();System.out.println(xm.behavior1+xm.behavior2);System.out.println(xm.clothes);} }
Java中在为什么要在一个构造方法中调用另外一个构造方法?以及如何调用?_wokkkok的博客-CSDN博客_java一个构造方法调用另一个构造方法
构造方法声明:
[public | protected| private] 类名([参数列表]){ [语句块] }
构造方法的名称必须与类名相同
构造方法没有返回值,这与返回值为void不同
构造方法在创建一个对象时调用,调用时必须使用关键字new
对于没有定义构造方法的类, Java编译器会自动加入一个特殊的构 造方法, 该构造方法没有参数且方法体为空, 称为默认构造方法 用默认构造方法初始化对象时,对象的成员变量初始化为默认值 若类中已定义了构造方法,则编译器不再向类中加入默认构造方法
-
访问权限控制
权限修饰符:类库开发者向类外部(客户程序员)指定哪些可用, 哪些不可用
public | protected | private | (default / package)
包(Package):相关类与接口的一个集合, 提供了类的命名空间的管理和访问保护
能够对类和接口按照其功能进行组织
每个包都提供独立的命名空间, 不同包中的同名类之间不会冲突
同一个包中的类之间有比较宽松的访问权限控制
定义:package com.java.demo 用.运算符分隔开
包定义语句在每个源程序中只能有一条,即一个类只能属于一个包
包定义语句必须在程序代码的第一行(除注释之外)
包成员:包中的类和接口
import pkg1[.pkg2[.pkg3 …]].(类名|*); “类名”指明所要引入的类 通配符*表示引入该包中的所有类 import语句须在源程序所有类声明之前,package语句之后
public
任意访问
default
包权限:可被包中的所有类访问
private
private权限的成员变量, 成员方法: 除包含该成员的类之外, 其他类均无法访问该成员
private权限的构造方法: 其他类不能生成该类的实例对象
同一个类的不同对象之间可互相访问私有成员
protected
可以被它所在的包中的所有类访问
可以被它所属类的子类访问 (不论子类是否在同一包中)
1.在子类中可以通过子类对象来访问父类的protected属性和方法
2.在子类中不能通过父类对象来访问父类的protected属性和方法
3.在子类中不能通过其他子类的对象来访问父类的protected属性和方法在子类中不能通过父类对象实例来访问父类的protected属性和方法_烟雨星空的博客-CSDN博客_子类不能访问父类非public和protected修饰的属性
-
对象的生命周期
对象的创建:声明+实例化
实例化四步骤:
为对象分配存储空间,并用默认值对成员变量初始化 在堆上new一块空间
执行显式初始化,即执行成员变量声明时的赋值
执行构造方法的主体,完成对象初始化
返回该对象的引用
class Window { Window(int m) { System.out.println(“window ” + m); } } class House { Window w1 = new Window(1);//先执行所有的显式初始化1 House() { System.out.println(“House”);//4 w3 = new Window(33);//5 } Window w2 = new Window(2);//2 void f() { System.out.println(“f()”); }//6 Window w3 = new Window(3);//3 } public class OrderOfInit { public static void main(String[] args) { House h = new House(); h.f(); } }
对象的使用
通过圆点运算符(.)
注意成员变量和方法的访问权限
对象的清除
自动垃圾回收机制(garbage collection)
一个对象没有引用指向它时被清除
Java运行环境中的垃圾收集器周期性地释放不用的对象占用的空间
垃圾收集器在对象被收集前调用对象的finalize()方法
某些情况下可通过调用System.gc()建议JVM执行垃圾收集
-
类的继承与多态
-
继承
类之间的“is a”关系,反映出一个类(子类)是另一个类(父类)的特例, 继承、修改和细化父类的状态和行为
继承是从已有的类创建新类的一种方法
class 子类名 extends 父类名{}
父类的public权限成员可以在子类中被访问
父类的protaeted权限成员可以在子类中通过子类对象访问,通过父类对象和其他对象均访问不到
创建一个类总会继承其他的类,如果没有显式地指明父类, 则从Java的标准根类Object进行继承
-
super关键字
1.当前对象的父类对象的引用
public class TestInheritance{ public static void main(String[] args){ Rectangle rect=new Rectangle(); rect.newDraw(); } } class Shape{ public void draw(){ System.out.println(“Draw shape”); } } class Rectangle extends Shape{ public void draw(){ System.out.println(“Draw Rectangle”); }//如果子类没有重写该方法,则调用两次父类的draw函数 public void newDraw(){ draw(); super.draw();//super表示当前类的父类的引用 } }
2.在子类构造方法中,通过“super([参数列表]);”调用 父类的构造方法。
该语句必须出现在子类构造方法的第一行
每当你new一个子类对象的时候,子类对象都隐式内置了一个父类对象。所以说,为了那个内置的父类对象,也需要进行初始化。
在子类的构造方法里面调用super,如果父类中含有无参构造方法,子类构造方法会隐式调用了一句super(),因此对于无参构造函数,super()可写,可不写
关于继承中的super()调用父类构造方法_weixin_34204722的博客-CSDN博客
-
子类对象创建与初始化一般步骤
1.子类构造方法调用父类构造方法
通过显式的super()方法调用或编译器隐含插入的super()方法调用
这一过程递归地进行, 直到根类Object的构造方法。在这一过程中, 子类对象所需的所有内存空间被分配, 所有成员变量均使用默认值 初始化
2.从根类Object的构造方法开始, 自顶向下地对每个类的对象依次 执行如下两步:
显式初始化及使用实例初始化程序块进行初始化
执行构造方法的主体(不包括使用super()调用父类构造方法的语 句)
3.返回该子类对象的引用
public class ConstructSubObj{ public static void main(String[] args){ Undergraduate ug=new Undergraduate(12345678); } } class Person{ Person() { System.out.println(“Person”); } } class Student extends Person{ Student(int id) { //隐含了super();System.out.println(“Student ”+id); } } class Undergraduate extends Student{ Undergraduate(int id) { super(id); //必须使用, 因为Student没有默认构造方法 System.out.println(“Undergraduate”); } }
-
多态
编译时多态:(静态多态,编译器工作)
重载(overloading)
**定义:**一个类中定义多个名称相同但参数不同的方法(与前面修饰符没关系,只与参数个数,顺序有关)
最最常见的一个重载:
System.out.println();
重载构造方法:
使用this()调用当前类的构造方法
当一个构造方法使用this()调用另一构造方法时, 对this的调用 必须位于此构造方法的起始处
对this()和super()的调用具有互斥性。当一个构造方法使用 this()调用另一构造方法时, 构造方法起始处不再有对super() 的调用
联系前面提到的this关键字使用
class ConstructorOverloading{ public static void main(String[] args){ Student stu=new Student(); System.out.println(stu.getName()+", "+stu.getID()); } } class Student{ private String name; private String id; public Student(String nm, String id){ this.name=nm; this.id=id; } public Student(String nm){ this(nm,"00000000"); } public Student(){ this("Unknown"); } public String getName(){ return name; } public String getID(){ return id; } }
运行时多态:
重写(overriding)
在子类中修改父类方法的实现
方法重写的前提:存在继承关系
不改变方法的名称、参数列表和返回值,改变方法的内部实现
子类中重写方法的访问权限不能缩小
子类中重写方法不能抛出新的异常
向上转型(upcasting)
将子类对象的引用转换成父类对象 的引用
向上转型使数组可以有不同类型对象:
Employee[] staff = new Employee[3]; staff[0] = new Manager(); staff[1] = new Secretary(); staff[2] = new Employee();
动态绑定(dynamic binding)
绑定:将方法调用和方法体联系在一起
动态绑定:绑定操作在程序运行时根据变量指向的对象实例的具体 类型找到正确的方法体(而不是根据变量本身的类型)
class PrivOverride { public void f() { System.out.println("fu f()"); } public static void main(String[] args) { PrivOverride po = new Derived();//根据po具体指向的对象实例,不根据变量本身类型 po.f(); } } class Derived extends PrivOverride { public int a; public void f() { System.out.println("zi f()"); } }
静态绑定与动态绑定:轻松理解java前期绑定(静态绑定)与后期绑定(动态绑定) 的区别。_待到春花烂漫时-CSDN博客
三者关系:
由于存在Upcasting, 父类变量可能指向父类的对象, 也可能指向 子类的对象
相应地, 通过父类变量发出的方法调用, 可能执行该方法在父类中 的实现, 也可能执行该方法在某子类中的实现, 具体执行哪个实现 由动态绑定决定
重写是运行时多态的基础, 重写关系下的两个方法的实现不同才 使得“多态”有意义
成员变量隐藏(具有运行时多态)
父类中成员变量被子类中同名的成员变量隐藏,并非被覆盖
隐藏不是覆盖, 两个变量存储空间仍独立, 隐藏仅限于访问的角度
成员变量隐藏和成员方法重写的前提
父类成员能够在子类中被访问到
父类中private成员,在子类中不能被隐藏(重写)
public class NotOverriding extends Base{//子类并没有重写父类的方法 private int i=2; public static void main(String[] args){ NotOverriding no=new NotOverriding(); no.increase();//执行父类的increase(),让父类的i自增1 System.out.println(no.i);//输出子类的i,为2 System.out.println(no.getI());//执行父类方法,返回父类的i,为101 } } class Base{ private int i=100; public void increase(){ this.i++; } public int getI(){ return this.i; } }
向下转型(downcasting)与运行时类型识别:
父类弱、子类强, 父类变量不能直接按子类引用, 必须强制转换才能作为子类的引用使用
向下转型(downcasting): 将父类类型的引用变量强制 (显式)地转换为子类类型
public class Downcasting{ public static void main(String[] args){ BaseClass[] b={new BaseClass(), new SubClass()};//b[1]向上转型为父类变量 b[0].f(); b[1].f(); //! b[1].g(); //b[i]为BaseClass类型,只有f()接口方法 ((SubClass)b[1]).g(); //! ((SubClass)b[0]).g(); //抛出运行时异常 for(int i=0;i<b.length;i++){ if(b[i] instanceof SubClass) ((SubClass)b[i]).g(); } } } class BaseClass{ public void f() { System.out.println("Base.f()"); } } class SubClass extends BaseClass{ public void f() { System.out.println("Sub.f()"); } public void g() { System.out.println("Sub.g()"); } }
-
-
5.java高级特征
-
静态变量、方法、初始化程序块
静态变量:(类变量)
在类的成员变量声明中带有static关键字的变量
该类的所有的对象实例之间共享使用方法区中的静态变量
class Employee{ private int id; public static int serialNum = 1; //静态成员变量 Employee(){ id=serialNum ++; }//核心 public static void main(String[] args){ Employee e1=new Employee(); Employee e2=new Employee(); Employee e3=new Employee(); } }
静态变量的创建:
静态变量的创建与实例对象无关 只在系统加载其所在类时分配空间并初始化, 且在创建该类的 实例对象时不再分配空间
静态方法:(类方法)
在类的成员方法声明中带有static关键字的方法
class GeneralFunction { public static int add(int x, int y) { return x + y; } } public class UseGeneral { public static void main(String[] args) { int c = GeneralFunction.add(9, 10); System.out.println(“9 + 10 = ” + c); } }
注意事项:
main方法是静态方法、程序入口点,JVM不创建实例对象就可以 执行该方法
构造方法本质上是静态方法
静态方法中没有this引用,因此静态方法不能直接调用实例方法, 也不能直接访问所属类的实例成员变量,必须通过对象访问
public class TestStaticMethod{ public static void main(String[] args){ StaticMethod obj=new StaticMethod(); StaticMethod.sPrintXAndY(obj); } } class StaticMethod{ int x=0; static int y=1; public void iPrintAndIncreaseY(){ sPrintY(); y++; } public static void sPrintY(){ //System.out.println(this.x); //不能访问实例成员变量 //iPrintAndIncreaseY(); //不能访问实例方法 System.out.println(StaticMethod.y); //可以访问静态变量 } public static void sPrintXAndY(StaticMethod o){ System.out.println(o.x); //可以通过o引用访问实例成员变量 o.iPrintAndIncreaseY(); //可以通过o引用调用实例方法 sPrintY(); //可以直接调用静态方法 } }
静态方法重写规则:
1.子类不能把父类的静态方法写为非静态
2.子类不能把父类的非静态写为静态
3.子类可以声明与父类静态方法相同的方法
4.静态方法的重写不会导致多态性
5.构造方法本质上是static方法,不具有多态性
Java多态和实现接口的类的对象赋值给接口引用的方法_Dream It Possible-CSDN博客
java静态方法不具有多态性详解
动态绑定失效:
1.基类方法是private或final修饰的,这个很好理解,因为private说明该方法对子类是不可见的,子类再写一个同名的方法并不是对父类方法进行复写(Override),而是重新生成一个新的方法,也就不存在多态的问题了。同理也可以解释final,因为方法同样是不可覆盖的。
2.方法是static修饰的
class Base {public static void staticMethod() {//static 方法System.out.println("Base staticMehtod");}public void dynamicMehtod() {System.out.println("Base dynamicMehtod");} }class Sub extends Base {public static void staticMethod() {System.out.println("Sub staticMehtod");}public void dynamicMehtod() {System.out.println("Sub dynamicMehtod");} }public class TJ4 {public static void main(String args[]) {Base c = new Sub();c.staticMethod();c.dynamicMehtod();} } 运行结果: Base staticMehtod // 静态方法并不与某个对象相关联,而是与类相关联 Sub dynamicMehtod //动态绑定
public class StaticPolymorphism{ public static void main(String[] args){ Sup s=new Sub(); s.mtd1(); s.mtd2(); //静态方法的重写不会导致多态性,不会动态绑定,规则4 } } class Sup { public void mtd1() { System.out.println(“Sup.instanceMethod”); } public static void mtd2() { System.out.println(“Sup.staticMethod”); } } class Sub extends Sup{ //public static void mtd1() {} //静态方法不能隐藏实例方法,规则2 //public void mtd2() {} //实例方法不能重写静态方法,规则1 public void mtd1(){ System.out.println(“Sub.instanceMethod”); } public static void mtd2() { System.out.println(“Sub.staticMethod”); } }
静态初始化程序块:
类定义中不属于任何方法体且以static关键字修饰的语句块 static {…}
在加载该类时执行且只执行一次
如果类中定义了多个静态语句块,则这些语句块按照在类中 出现的次序运行
static静态初始化块(详解与代码)_林高禄-CSDN博客
Java中静态初始化块、初始化块和构造方法_借你一秒-CSDN博客
public class TestStatic1 {private String no;private static String name;static{System.out.println("执行静态代码块");name = "林高禄";printName();}{System.out.println("执行非静态代码块");name = "麦迪";printName();}public static void printName(){System.out.println(name);}public static void main(String[] args) {TestStatic1 t = null;System.out.println("-----------------");TestStatic1 t2 = new TestStatic1();}} 结果为: 执行静态代码块 林高禄 ----------------- 执行非静态代码块 麦迪
-
FINAL关键字
在类声明中使用:表示类不能被继承
final class Employee { ... } class Manager extends Employee { ... } //错误
在成员方法声明及方法参数中使用:成员方法不能被重写, 参数变量值不能更改
成员方法声明:
1.被定义成final的方法不能被重写
2.将方法定义为final可使运行时的效率优化 对于final方法,编译器直接产生调用方法的代码,而阻止运行 时刻对方法调用的动态绑定
3.private的方法都隐含指定为是final的,对子类不可见就无所谓 被重写
方法参数中使用:
无法在方法中更改参数的值
在成员变量和局部变量声明中使用:表示变量的值不能更改
成员变量:
对于基本类型的成员变量: 数值不变; 用来定义常量:声明final变量并同时赋初值
对于引用类型的成员变量: 引用不变,但被引用对象可被修改
空白final:若final成员变量声明时未赋初值,则在所属类的 每个构造方法中都必须对该变量赋值
局部变量:
被定义成final的局部变量可以在所属方法的任何位置被赋值, 但只能赋一次
-
抽象类与接口
抽象方法:
只有方法声明而没有方法体的方法
抽象方法的声明中需加关键字abstract
抽象类:
由关键字abstract声明的类
包含抽象方法的类必须是抽象类
抽象类不能创建实例对象
若子类中实现了所有抽象方法, 则可创建该子类的实例对象
若子类中实现了部分抽象方法, 则该子类也是抽象类
抽象类的作用:为一类对象建立抽象的模型
接口:interface
接口中只声明方法(“做什么”, 抽象方法), 但不定义方法体 (“怎么做”)
将“做什么”与“怎么做”分离
不涉及任何实现细节, 实现了接口的类 具有该接口规定的行为
接口可看作使用类的“客户”代码与提供服务的类之间的契约或 协议
定义:
[public] interface 接口名 [extends 父接口列表]{接口体; }
public / default:任意类均可使用 / 与该接口在同一个包中的 类可使用
一个接口可以有多个父接口,子接口继承父接口的所有常量和方法
接口体: 常量定义+方法定义
常量默认具有:final static 属性
方法默认具有: public abstract 属性
常量不能为空白final的
在实现接口的类中,接口方法必须实现为public的
接口中成员不能使用的修饰符:transient, volatile, synchronized, private, protected
使用——用类实现接口,作为数据类型支持多态
类声明中的implements关键字
类可以使用接口定义的常量
类必须实现接口定义的所有方法(否则为抽象类)
实现该接口的类可看作该接口的“子类”,接口类型的变量 可指向该“子类”的实例
interface Human{void showNameInNativeLanguage(); } class Person{public static void main(String[] args){ Human e1 =new Chinese(); Human e2 = new American(); e1.showNameInNativeLanguage(); e2.showNameInNativeLanguage();} } class Chinese implements Human{public void showNameInNativeLanguage(){System.out.println("Chinese");} }class American implements Human{public void showNameInNativeLanguage(){System.out.println("English");} }
接口与多重继承
类继承:类继承只支持单继承(子类从单个直接父类继承), 不支持 多重继承(子类从多个直接父类继承)
接口继承支持多重继承
public class Sportsman extends Person implements Runner, Swimmer, Jumper{ public void run() { print (“Sportsman running”); }public void swim() { print(“Sportsman swimming”); } public void jump() { print(“Sportsman jumping”); }public static void toRun(Runner r) { r.run(); } public static void toSwim(Swimmer s) { s.swim(); }public static void toJump(Jumper j) { j.jump(); }public static void toEatAndDrink(Person p) { p.eat(); p.drink(); } public static void main(String[] args){ Sportsman s=new Sportsman(); toRun(s);toSwim(s);toJump(s); toEatAndDrink(s); } } class Person{ public void eat(){ print(“Person eating”); } void drink() { print(“Person drinking”); } } interface Runner{ void run(); void eat(); }interface Swimmer{ void swim(); }interface Jumper{ void jump(); }
接口和抽象类的区别:
接口中的所有方法都是抽象的,而抽象类可以定义非抽象方法
一个类可以实现多个接口,但只能继承一个抽象父类
接口与实现它的类不构成类的继承体系,抽象类属于类的 继承体系
通过继承扩展接口:
直接向接口中扩展方法可能带来问题:所有实现原来接口的类 将因为接口的改变而不能正常工作
不能向interface定义中随意增加方法,需要通过继承扩展接口
public interface StockWatcher {final String sunTicker = “SUNW”; final String oracleTicker = “ORCL”;void valueChanged(String tickerSymbol, double newValue); } public interface StockTracker extends StockWatcher { //通过子接口进行扩展 void currentValue(String tickerSymbol, double newValue); }
-
枚举类型
通过关键字enum将一组具名值的有限集合创建为一种类型 ,具名值又称为枚举常量
一个枚举类型实际定义了一个类, 该类可以包含方法和其他属性, 以支持对枚举值的操作, 还可以实现任意的接口
枚举类型变量属于引用变量, 变量取值范围为所有可能的 枚举常量
枚举类型定义:
[public ] enum 枚举类型名 [implements 接口名称]{枚举常量定义[枚举体定义] }
public/default:可被包外类访问 / 只能在同一包中访问
所有枚举类型都隐含继承java.lang.Enum类,故不能再继承 其他任何类
不想看了,懒得看了
-
6.容器类和常用预定义类
-
容器类
泛型:又称为参数化类型, 通过定义含有一个或多个类型参数的 类或接口, 对具有类似特征与行为的类进行抽象
类型参数:可以指代任何具体类型
JDK中java.util.ArrayList的定义, ArrayList定义了 一个泛型, E为类型参数, 它代表了能够放入ArrayList中的对象的 类型
使用一个预定义的泛型
对泛型中的类型参数进行具体化,即可得到具体的类
如果希望创建一个能够存放String对象的ArrayList, 应使用 **ArrayList**进行声明。
容器类:一个容器类的实例(容器、容器对象)表示了一组对象, 容器对象存放指向其他对象的引用(引用就是地址,引用对象就是指针所指的堆空间)
容器接口的基本特征:
Collection:集合接口树的根,定义通用的集合操作API
Set:集合。无序,不能包含重复元素
List:列表。有序,可包含重复元素,可通过索引序号访问元素
Queue:队列。必须按照排队规则来确定元素顺序,先进先出
Map:由一系列“键值对”组成的序列,允许通过键查找值
不能包含重复的键
每个键至多只映射到一个值
Set接口:
HashSet:采用Hash表实现Set接口,元素无固定顺序,对元素的访问效率高
TreeSet:实现了SortedSet接口,采用有序树结构存储集合元素,元素按照比较结果的升序排列
LinkedHashSet:采用Hash表和链表结合的方式实现Set接口,元素按照被添加的先后顺序保存
import java.util.*;class TestSet{ public static void main(String[] args){ Random rand=new Random(47); Set<Integer> s=new HashSet<Integer>();//HashSet无顺序,为何输出是升序? for(int i=0;i<5000;i++){ s.add(rand.nextInt(40)); } System.out.println(s); s=new TreeSet<Integer>();//升序排列 for(int i=0;i<5000;i++){ s.add(rand.nextInt(40)); } System.out.println(s); s=new LinkedHashSet<Integer>();//按添加先后顺序排列 for(int i=0;i<5000;i++){ s.add(rand.nextInt(40)); } System.out.println(s); } }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-26gMZFPg-1626355820365)({43596B4F-6807-41AD-894C-3B6D9830D5FE}.png.jpg)]
HashSet顺序输出原因:
Java-为什么遍历输出HashSet是有序的?_杨的博客-CSDN博客
说实话看了也没看懂。。。
List接口:
ArrayList:采用可变大小的数组实现List接口
无需声明上限,随着元素的增加,长度自动增加
对元素的随机访问速度快,插入/移除元素较慢
LinkedList:采用链表结构实现List接口
实际上实现了List接口、Queue接口和双端队列Deque接口, 因此可用来实现堆栈、队列或双端队列
插入/移除元素快,对元素的随机访问较慢
import java.util.*;public class UseArrayList { public static void main(String[] args) { List<String> scores = new ArrayList<String>(); scores.add("86"); // 添加元素 scores.add("98"); // 添加元素 scores.add(1, "99"); // 插入元素 for (int i = 0; i < scores.size(); i++) { System.out.print(scores.get(i) + " " ); // 输出结果 } scores.set(1, "77"); // 修改第二个元素 scores.remove(0); // 删除第一个元素 System.out.println("n修改并删除之后"); for (int i = 0; i < scores.size(); i++) { System.out.print(scores.get(i) + " "); } System.out.println(" n按字符串输出n" + scores.toString()); } }
86 99 98 修改并删除之后 77 98 按字符串输出 [77, 98]
Queue接口:
两种接口实现:LinkedList PriorityQueue
import java.util.*; public class TestQueue{ public static void printQueue(Queue q){ while(q.peek()!=null) System.out.print(q.remove()+" ");//队列为先进先出,Queue.remove()是返回第一个元素,并在队列中删除。 System.out.println(); } public static void main(String[] args){ Queue<Integer> q=new LinkedList<Integer>(); Random rand=new Random(37);//Random两种构造方法 for(int i=0;i<10;i++) q.offer(rand.nextInt(i+10)); TestQueue.printQueue(q); Queue<Character> qc=new LinkedList<Character>(); for(char c: "JavaLanguage".toCharArray()) qc.offer(c); TestQueue.printQueue(qc); } }
5 4 6 11 2 6 7 9 11 2 J a v a L a n g u a g e
关于Random的两种构造方法:
1.Random(),用于创建一个伪随机数生成器,无参构造,每次生成的随机数是不同的。
2.Random(long seed),使用一个long型的seed种子创建伪随机数生成器 ,有参构造,每次生成的随机数相同。
Map接口:
把键映射到某个值
一个键最多只能映射一个值
一个值可对应多个键
三种接口实现:
HashMap(无序): 使用Hash表实现Map接口
无序,非同步且允许空的键与值
相比Hashtable(legacy)效率高
TreeMap(有序): 与TreeSet类似,使用有序树实现SortedMap 接口
保证“键”始终处于排序状态
LinkedHashMap: 可以记住键/值项被添加的顺序的Map
import java.util.*; public class Freq { public static void main(String args[]) { String[] words = { "if", "it", "is", "to", "be", "it", "i", "up", "to" , "me", "to", "delegate" }; Integer freq; Map<String, Integer> m = new TreeMap<String, Integer>(); for (String a : words) { //以(单词,词频)为键值对, 构造频率表 freq = m.get(a); // 获取指定单词的词频。 if (freq == null) { // 词频递增 freq = new Integer(1); } else { freq = new Integer(freq + 1); // .intValue() } m.put(a, freq); // 在Map中更改词频 } System.out.println(m.size() + " distinct words detected:"); System.out.println(m); } }
9 distinct words detected: {be=1, delegate=1, i=1, if=1, is=1, it=2, me=1, to=3, up=1}
迭代器(Iterator)
Iterator是一个轻量级对象,用于遍历并选择序列中的对象
用法:
使用容器的iterator()方法返回容器的迭代器,该迭代器准备返回 容器的第一个元素
迭代器只能单向移动
next(): 获得序列的下一个元素
hasNext(): 检查序列中是否还有元素
remove(): 将迭代器新折返的元素(即由next()产生的最后一个元素)删除,因此在调用remove()之前必须先调用next()
public class TestIterator{public static void main(String[] args){String sentence ="I believe I can fiy, I believe I can touch the sky";String[] strs=sentence.spilt(" ");//将sentence以空格分隔开并传给string类型数组//System.out.println(Arrays.toString(strs));将strs打印得用Arrays.toString(strs)List<String> list=new ArrayList<String>(Array.asList(strs));//将数组当作List处理Iterator<String> it =list.iterator();while(it.hasNext()){System.out.print(it.next()+"_");}System.out.println();it=list.iterator();//Iterator单项移动,将it重置到第一位while(it.hasNext()){if(it.next().equels("I"))it.remove();}it=list.iterator();while(it.hasNext()){System.out.println(it.next()+"_");}System.out.println();} }
I_believe_I_can_fly_I_believe_I_can_touch_the_sky_ believe_ can_ fly_ believe_ can_ touch_ the_ sky_
Collection与增强版for循环
通过增强版for循环遍历Collection中的元素
public class CollectionWithForeach{public static void main(String[] args){String sentence ="I believe I can fly I believe I can touch the sky";String[] strs=sentence.split(" ");Collection<String> c =new ArrayList<String> (Arrays.asList(strs));for(String s:c){System.out.print(s+"_");}} }
I_believe_I_can_fly_I_believe_I_can_touch_the_sky_
-
String
java.lang.String
用于操作字符串
典型的构造方法:
public String(): 构造一个空字符串
public String(char[] value): 使用字符数组value中的字符以构造一个字符串
public String(String orig): 使用原字符串orig的拷贝以构造一个新字符串
特性:
String对象是不可变的:String类中每个修改String值的方法, 实际上都会创建一个新的String对象, 以包含修改后的字符串 的内容
String类中存在一个专门的常量池,具有特殊的作用
由于String对象一经创建就不能被修改, 因此String类型的 字面常量会自动被加入常量池中。每当程序中出现字面常量时, 搜索常量池中是否存在该字面常量字符串(用equals()方法 判断), 如果不存在, 就将该字面常量加入常量池中; 如果已 存在, 就直接返回常量池中的对象引用
public class StringImmediate{public static void main(String[] args){String s1=new String("hello");String s2=new String("hello");System.out.println(s1==s2);System.out.println("==========");String s3="hello";String s4="hello";System.out.println(s3==s4);} }
false ========== true
java.lang.String数据类型转换
各种基本数据类型与String类型之间可通过方法相互转换
基本类型值 ⇒ String类型字符串,使用String类的静态valueOf()方法
public static String valueOf(boolean b)
public static String valueOf(char c)
public static String valueOf(int i)
public static String valueOf(long l)
public static String valueOf(float f)
public static String valueOf(double d)
String类型字符串 ⇒ 基本类型值: 通过基本类型对应的Wrapper类的静态parse方法
String类型字符串 ⇒ Wrapper类对象: 通过基本类型对应的Wrapper类的静态valueOf方法
java.lang.StringBuilder
提供了一个字符串的可变序列,它对存储的字符序列可以任意 修改,使用起来比String类灵活
构造方法:
StringBuilder():构造一个空StringBuilder对象,初始容量 为16个字符
StringBuilder(String str):构造一个StringBuilder对象, 初始内容为字符串str的拷贝
两种特有的成员方法系列:
append(),根据参数的数据类型在StringBuilder对象的 末尾直接进行数据添加, 如
public StringBuilder append (boolean b)
insert(), 根据参数的数据类型在StringBuilder对象的 offset位置进行数据插入, 如
public StringBuilder insert (int offset, boolean b)
-
Wrapper类
Wrapper将基本类型表示成类,每个基本数据类型在java.lang包 中都有一个对应的Wrapper类
每个Wrapper类对象都封装了基本类型的一个值
Wrapper类实例的构造:将基本数据类型值传递给Wrapper类的 构造方法
Wrapper类的常用方法和变量:
数值型Wrapper类中的MIN_VALUE,MAX_VALUE
byteValue()/shortValue()/longValue()…:将当前Wrapper类型的值当作byte/short/long返回
valueOf():将字符串转换为Wrapper类型的实例
toString():将基本类型值转换为字符串
parseByte()/parseShort()/parseInt(), … : 将字符串转换为byte/short/int/…类型值
Autoboxing/Autounboxing
Autoboxing:在应该使用对象的地方使用基本类型的数据时, 编译器自动将该数据包装为对应的Wrapper类对象
Autounboxing:在应该使用基本类型数据的地方使用Wrapper 类的对象时,编译器自动从Wrapper类对象中取出所包含的基 本类型数据
public class Autoboxing{public static void main(String[] args){Integer a=25;Integer b=25;if(a.equals(b))System.out.println(a+" equals to "+b);System.out.println("The sum of "+a+" and "+b+" is "+(a+b));} }
25 equals to 25 The sum of 25 and 25 is 50
基本类型,wrapper类,String之间的转换
-
-
异常处理
-
异常的概念
在程序运行时,打断正常程序流程的不正常情况分两类
错误(Error):应用程序无法捕获的严重问题
异常(Exception):应用程序可捕获的一般问题
例如:
试图打开的文件不存在
网络连接中断
数组越界
要加载的类找不到
…
public class HelloWorld {public static void main(String args[]){int i=0;String greetings[]={ "Hello World!", "Hello!","HELLO WORLD!"};while (i<4){//数组越界抛出异常System.out.println(greetings[i]);i++;}System.out.println("end!");}}
Hello World! Hello! HELLO WORLD! Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3 at com.java.demo.HelloWorld.main(HelloWorld.java:9)
系统错误:虚拟机相关的问题,如虚拟机崩溃、动态链接失败、 低层资源错误等
总是不受编译器检查的(Unchecked)
可以被抛出,但无法恢复,不可能被捕获
异常分类:
1.Runtime异常(免检异常):由Runtime异常类及其子类表示的 异常,如数组越界、算术运算异常、空指针异常等
不受编译器检查(Unchecked),不需要显式地处理(捕获或抛出) 该异常就能编译通过
2.必检异常:除Runtime异常类及其子类之外的所有异常,如文件 不存在、无效URL等
编译器检查并强制程序对其进行异常处理(Checked)
Java.lang.Throwable:所有错误类和异常类的父类
检索异常的相关信息
输出显示异常发生位置的堆栈追踪轨迹(PrintStackTrace())
java.lang.ArithmeticException:算术运算异常
整数除0操作导致的异常,int i=10/0
java.lang.NullPointerException:空指针异常
调用一个null对象的实例方法
访问或修改一个null对象的成员变量
在数组变量引用到null对象时,访问数组的length或具体的数组元素
将null作为Throwable对象抛出
例: Date d=null; System.out.println(d.toString());
java.io.IOException: 输入输出时可能产生的各种异常
异常一般是由程序员的疏忽或者环境的变化所导致的
若不对异常进行处理,则会导致程序的不正常终止,为保证程序 正常运行,Java提供了异常处理机制
-
异常处理方法
捕获并处理异常
通过try-catch-finally语句来实现,基本格式:
try{//一条或多条可能抛出异常的Java语句 }catch(Exception e1){//捕获到ExceptionType1类型的异常时执行的代码 }catch(Exception e2){//捕获到ExceptionType2类型的异常时执行的代码 }... finally{//执行最终清理的语句 }
try:
把可能出现异常的语句都放在try语句块中
try语句块之后必须紧跟至少一个catch语句块
catch(ThrowableType objRef){…}
ThrowableType:当前catch语句块能够处理的异常类型,必须是Throwable类的子类
objRef: 异常处理程序中使用的指向被捕获异常对象的引用
finally
用于将除内存之外的资源恢复到初始状态。需清理的资源包括: 已打开的文件或网络连接,在屏幕上画的图形等
finally语句块可以省略
若finally语句块存在,则无论是否发生异常均执行
import java.io.*; import java.util.*; public class ListOfNumbers {private ArrayList<Integer> list;private static final int size = 10;public ListOfNumbers() {list = new ArrayList<Integer>(size);for (int i = 0; i < size; i++)list.add(new Integer(i)); }public void writeList(){PrintWriter out=new PrintWriter(new FileWriter(“OutFile.txt”));for (int i=0;i<size;i++)out.println(“Value at: ”+i+“ = ”+list.get(i));out.close(); }public static void main(String args[]) {ListOfNumbers list = new ListOfNumbers();list.writeList(); } }
调用了java.io.FileWriter的构造方法创建文件输出流,该方法声明如下: public FileWriter(String fileName) throws IOException; FileWriter()构造方法可以抛出必检异常IOException FileWriter()构造方法可以抛出必检异常IOException 此外,容器对象的get()方法还可能抛出免检异常IndexOutOfBoundsException
public void writeList() { PrintWriter out = null; try { out = new PrintWriter(new FileWriter(“OutFile.txt”)); for (int i = 0; i < size; i++) out.println(“Value at: ” + i + “ = ” + list.get(i)); } catch (IndexOutOfBoundsException e) { System.err.println(“Caught IndexOutOfBoundsException: ” + e.getMessage()); } catch (IOException e) { System.err.println(“Caught IOException: ” + e.getMessage()); } finally {/*执行程序的最后清理操作,关闭程序打开的文件流*/ if (out != null) { System.out.println(“Closing PrintWriter”); out.close(); } else { System.out.println(“PrintWriter not open”); } } }
多种异常同时处理:
可编写针对Exception的任何子类的catch块
子类的异常对象可与父类的异常处理程序匹配
若catch块针对叶节点,则是专用的异常处理,捕获一种特定的异常
若catch块针对中间节点,则是通用的异常处理,捕获该节点及其所 有子类表示的异常
public void writeList() { PrintWriter out = null; try { out = new PrintWriter(new FileWriter(“OutFile.txt”)); for (int i = 0; i < size; i++) out.println(“Value at: ” + i + “ = ” + list.get(i)); } catch (IOException e) { System.err.println(“Caught IOException: ” + e.getMessage()); } catch (Exception e){ /*所有非IOException*/ System.err.println(“Caught Exception: ” + e.getMessage()); } finally { if (out != null) { System.out.println(“Closing PrintWriter”); out.close(); } else { System.out.println(“PrintWriter not open”); } } }
将方法中产生的异常抛出
可能产生异常的方法不处理该异常,而是将该异常抛出到调用该 方法的程序
public void troubleSome() throws IOException{…}
声明抛出异常(方法声明的throws子句)
retType mtdName([paralist]) throws [exceptionList] {…}
抛出异常(throw语句)
throw someThrowableObj;
执行throw语句后,在当前方法中找是否有catch字句匹配抛出的 异常someThrowableObj的类型:
若找到, 则由该catch子句处理
若未找到, 则转向上一层调用者程序, 在调用者程序中查找是否 有catch子句匹配someThrowableObj
依次递归…
若一个异常在转向到main()后还未被处理,则程序将非正常终止
class ListOfNumbersDeclared{private ArrayList<Integer> list;private static final int size=10;public ListOfNumbersDeclared(){list=new ArrayList<String>(size);for(int i=0;i<size;i++)list.add(new Integer(i)); }public void writeList() throws IOException,IndexOutOfBoundsException{PrintWriter out=new PrintWrite(new FileWriter("OutFile1.txt"));for(int i=0;i<size;i++)out.println("value at: "+i+" = "+list.get(i));out.close();} } public class TestListOfNumbersDeclared{public static void main(String[] args){try{ListOfNumbersDeclared list =new ListOfNumbersDeclared();list.writeList();}catch(Exception e){}System.out.println("A list of numbers created and stored in OutFile1.txt");} }
接下来为自己写的代码测试:
package com.java.demo;//单个异常,数组越界public class Test {public static void main(String[] args) {String[] strs={"高等数学","大学物理","线性代数"};int i=0;try{System.out.println("========");while(i<4)//此时出现了数组越界{System.out.println(strs[i]);i++;}System.out.println("----------------");}catch(ArrayIndexOutOfBoundsException e1){//此处catch数组越界System.out.println("There is an arrayIndexOutOfBoundsException");}catch(Exception e2){System.out.println("There is an exception");}finally{System.out.println("*****************");}System.out.println("Program finished!");} }
======== 高等数学 大学物理 线性代数 There is an arrayIndexOutOfBoundsException ***************** Program finished!
package com.java.demo;//无异常,与上面代码类似public class Test {public static void main(String[] args) {String[] strs={"高等数学","大学物理","线性代数"};int i=0;try{System.out.println("========");while(i<3)//此处并未越界{System.out.println(strs[i]);i++;}System.out.println("----------------");}catch(ArrayIndexOutOfBoundsException e1){System.out.println("There is an arrayIndexOutOfBoundsException");}catch(Exception e2){System.out.println("There is an exception");}finally{System.out.println("*****************");}System.out.println("Program finished!");} }
======== 高等数学 大学物理 线性代数 ---------------- ***************** Program finished!
package com.java.demo;//两种异常,数组越界与空指针异常public class Test {public static void main(String[] args) {String[] strs={"高等数学","大学物理","线性代数"};int i=0;try{System.out.println("========");while(i<4)//数组越界{System.out.println(strs[i]);i++;}Person person=null;person.function();//空指针异常System.out.println("----------------");}catch(ArrayIndexOutOfBoundsException e1){System.out.println("There is an ArrayIndexOutOfBoundsException");}catch(NullPointerException e2){System.out.println("There is a NullPointerException");}catch(Exception e3){System.out.println("There is an Exception");}finally{System.out.println("*****************");}System.out.println("Program finished!");} } class Person{public void function(){System.out.println("方法执行");} }
======== 高等数学 大学物理 线性代数 There is an ArrayIndexOutOfBoundsException ***************** Program finished!
package com.java.demo;//与上面代码类似,无异常出现public class Test {public static void main(String[] args) {String[] strs={"高等数学","大学物理","线性代数"};int i=0;try{System.out.println("========");while(i<3){System.out.println(strs[i]);i++;}Person person=new Person();person.function();System.out.println("----------------");}catch(ArrayIndexOutOfBoundsException e1){System.out.println("There is an ArrayIndexOutOfBoundsException");}catch(NullPointerException e2){System.out.println("There is a NullPointerException");}catch(Exception e3){System.out.println("There is an Exception");}finally{System.out.println("*****************");}System.out.println("Program finished!");} } class Person{public void function(){System.out.println("方法执行");} }
======== 高等数学 大学物理 线性代数 方法执行 ---------------- ***************** Program finished!
-
自定义异常类
在实际程序设计中, 尽可能使用预定义的异常类型能够降低程 序的复杂度
但预定义异常类体系无法预见所有可能出现的程序错误, 因为 现实中存在无法用预定义异常类描述的问题, 因此需要用户自 己定义异常类
定义方法:
自定义异常类必须从已有的异常类继承, 因而必然是Exception 类的子类
最好选择意思相近的异常类作为新异常类的父类
尽可能避免从RuntimeException派生自定义异常类, 因为 RuntimeException免检, 而我们希望自定义异常必检, 这样编 译器就会强制程序捕获这些异常, 从而提高程序健壮性
自定义异常类可包含普通类的内容
定义:
public class ServerTimeOutException extends Exception{private String reason;private int port;public ServerTimeOutException(String reason,int port){this.reason=reason;this.port=port;}public String getReason(){return reason;}public int getPort(){return port;}... }
使用:
public void connectMe(String serverName) throws ServerTimeOutException{int success;int portToConnect=80;success =open(serverName,portToConnect);if(success==-1)throw new ServerTimeOutException("Could not connect",80); }
//处理可能抛出的自定义异常 public void findServer(){...try{connectMe(defaultServer);}catch(ServerTimeOutException e){System.out.println("Server timed out,try another");try{//try-catch语句块可以嵌套connectMe(alternateServer);}catch(ServerTimeOutException e1){System.out.println("No server available");}} }
//一段完整的自定义异常类代码 class CircleException extends Exception{ //自定义的异常类 double radius; CircleException(double r){ radius = r; } public String toString(){ return "半径r="+radius+"不是一个正数"; } } class Circle{ //定义Circle类 private double radius; //方法头确定抛出某种异常 public void setRadius(double r) throws CircleException{ if(r<0) //满足抛出异常的条件则抛出异常 throw new CircleException(r); else radius=r; } public void show(){ System.out.println("圆面积="+3.14*radius*radius); } } public class Demo{ public static void main(String[] args){ Circle cir=new Circle(); try{ cir.setRadius(-2.0); }catch(CircleException e){ System.out.println("自定义异常:"+e.toString()+""); } cir.show(); } }
自定义异常: 半径r=-2.0不是正数 圆面积: 0.0
-
-
线程
-
-
线程的概念
线程与进程:
进程:内核级的实体
包含代码、数据、堆,PCB(进程管理、内存管理、文件管理信 息)等
进程结构存在于内核空间,用户程序须通过系统调用进行访问 或改变
线程:用户级的实体
线程结构驻留在用户空间,能够被普通的用户级函数组成的线 程库直接访问
线程独有:寄存器(栈指针,程序计数器)、栈等
一个进程中的所有线程共享该进程的状态
Java语言的重要特征是在语言级支持多线程的程序设计
线程:
进程中的单个顺序执行流
“顺序”指逻辑上的“顺序”, 即同一时刻仅执行一条语句, 不是语法上的“顺序”, 与多种程序流控制(分支、循环等) 不矛盾
多线程:进程中包含多个顺序执行流
Java线程模型
线程模型:CPU+代码+数据的封装体
CPU: 线程在占用CPU时的CPU状态
代码: CPU所执行的代表线程行为的代码(可由多个线程共享)
数据: CPU执行代码过程中操作的数据, 包括线程独有数据(程 序计数器、栈等)和共享数据(如堆上的对象)
代码和数据相互独立
代码和部分数据可以多线程共享
代码+数据=线程体(决定线程的行为)
-
线程的创建
线程模型由java.lang.Thread类进行定义和描述
程序中的线程都是Thread类或者其子类
Thread类的构造方法:
public Thread(ThreadGroup group, Runnable target, String name);
public Thread();
public Thread(Runnable target);
public Thread(ThreadGroup group, Runnable target);
public Thread(String name);
public Thread(ThreadGroup group, String name);
public Thread(Runnable target, String name);
核心参数:
group:指明该线程所属的线程组(不建议使用)
name:线程名称
targer:提供线程体的对象(由Runnable接口变量引用的实现了Runnable接口的类的对象)
线程创建两种方法:
1.实现Runnable接口,提供run()实现,将该类的一个实例对象作为参数传递给Thread构造方法,该对象提供线程体
package cm.java.demo;public class CountDown implements Runnable{private static int idcnt=1;private final int threadid=idcnt++;int counter=3;public void run(){while(counter>=0){try{Thread.sleep(1000);}catch(Exception e){e.printStackTrace();}System.out.println("#"+threadid+(counter>0?"->"+counter: "->run!"));counter--;}}public static void main(String[] args) {Thread t1=new Thread(new CountDown());Thread t2=new Thread(new CountDown());t1.start();//多线程同时运行t2.start();System.out.println("waiting for run...");} }
waiting for run... #1->3 #2->3 #1->2 #2->2 #1->1 #2->1 #1->run! #2->run!
2.Thread类本身也实现了Runnable接口,因而通过重写Thread类子类中的run()方法定义线程行为,并通过创建Thread子类实例创建线程
package cm.java.demo;public class CountDown2 extends Thread{private static int idcnt=1;private final int threadid=idcnt++;int counter=4;public void run(){while(counter>=0){try{Thread.sleep(1000);}catch(Exception e){e.printStackTrace();}System.out.println("#"+threadid+(counter>0?"->"+counter: "->run!"));counter--;}}public static void main(String[] args) {CountDown2 t1=new CountDown2();Thread t2=new CountDown2();//动态绑定t1.start();t2.start();System.out.println("waiting for run...");} }
waiting for run... #1->4 #2->4 #1->3 #2->3 #1->2 #2->2 #1->1 #2->1 #1->run! #2->run!
两种方法比较:
实现Runnable接口优点:便于用extends继承其他类
继承Thread类的优点:程序代码更简单
线程运行:
新创建的线程不会自动运行,必须调用线程的start()方法
该方法的调用把线程的CPU状态置为可运行(Runnable)状态
Runnable状态意味着该线程可以参加调度, 被JVM调度运行, 但并不意味着线程一定会立即运行
-
线程调度控制:
-
线程优先级:
Thread类有三个有关线程优先级的静态常量:
MIN_PRIORITY:最低优先级(1)
MAX_PRIORITY:最高优先级(10)
NORM_PRIORITY:普通优先级(5)
MIN_PRIORITY≤线程优先级≤MAX_PRIORITY, 数值越大优先级越高
线程有缺省优先级,主线程缺省为NORM_PRIORITY
子线程继承其父线程(创建子线程的语句执行时所在的线程) 的优先级
获得/设定线程优先级 Thread类的getPriority() / **setPriority()**方法
注意:
用户尽可能不要改变线程的优先级
如需改变优先级,则一般在run()方法开始处进行设置
Java线程优先级与OS线程优先级难以映射,因此可移植的方式是仅 使用MIN_PRIORITY, MAX_PRIORITY和NORM_PRIORITY设置优先级
-
线程调度策略:
线程调度:在单个CPU上以某种顺序运行多个线程
Java的线程调度策略:
规范:基于优先级的抢先式调度
允许多个线程可运行,但只有一个线程在运行
根据线程优先级,选择高优先级的可运行线程
当前线程持续运行,直到以下两种情况之一
该线程自行进入非可运行状态(如执行Thread.sleep()调用,或等待 访问共享的资源)
出现更高优先级线程成为可运行(这时CPU被高优先级线程抢占运行)
同一优先级的多个可运行线程可分时轮流运行,也可逐个运行,由 JVM而定
实际:平台相关,JVM相关
-
线程的基本控制:
static Thread currentThread()//Thread类的静态方法,返回对当前正在执行的线程的引用boolean isAlive()//线程状态未知时,用以确认线程是否活着?//返回true:线程已启动,且尚未运行结束static void sleep()//使当前线程阻塞(睡眠)一段固定的时间。在线程睡眠时间内,将运行其他线程//sleep()结束后,线程从阻塞状态进入Runnable状态//sleep()可能抛出InterrputedExceptionjoin()//在当前线程中执行t.join()方法使当前线程等待,直到线程t结束为止,线程恢复到Runnable状态yield()//调用该方法告诉调度器当前线程愿意将CPU让给具有相同优先级的线程
public void doTask(){TimerThread t =new TimerThread(100);t.start();...//与线程t并发运行...try{t.join();//使当前线程等待线程t结束后再继续运行...}catch(InterruptedException e){e.printStackTrace();}}
结束线程:
线程完成运行并结束后,将不能再运行
线程除正常运行结束(run()方法执行完成)外,还可用其他方法 控制使其停止:
stop():强行终止线程,易造成线程不一致(废止,勿使用)
使用标志flag:通过设置flag 指明run()方法应该结束
public class TestStop { public static void main(String[] args){ Tick t=new Tick(); new Thread(t).start(); try{ Thread.sleep(3000); } catch (Exception e){} System.out.println(“quiting Task ...”); t.stopRunning(); } } class Tick implements Runnable { private boolean timeToQuit = false; public void stopRunning() { timeToQuit = true; } public void run( ){ while(!timeToQuit){ try { Thread.sleep(1000); System.out.println(“tick ...”); } catch (Exception e) {} } System.out.println(“Tick finished.”); } }
tick... Tick finished! quiting Task... tick... Tick finished!
-
线程同步
-
线程并发中的问题
多个线程相对执行的顺序不确定,导致执行结果不确定
多个线程操作共享数据时,执行结果不确定导致共享数据的 一致性被破坏
对共享数据操作的并发控制机制称为线程同步
public class MyStack{ private int idx = 0; private char[] data = new char[6]; public void push( char c ){ data[idx] = c; idx ++; // 栈顶指针指向下一个空单元 } public char pop( ){ idx--; return data[idx]; } }
-
对象锁与锁语句
对象锁: java中每个标记为synchronized的对象都含有一个排他锁
synchronized:对共享数据的排他操作的标记方法
synchronized标记在特定的代码片段(方法)上,这样的代码 片段又称临界区
共享数据包含在对象中,对象锁与对象一一对应,对象的所有 synchronized方法(代码片段)共享对象锁
排他:
线程获得对象锁后才能执行临界区代码片段(即拥有对该对象 的操作权)
这时,其他任何线程无法获得对象锁,也无法执行临界区代码
public class MyStack{ ... public void push(char c){ synchronized(this){ data[idx] = c; idx++; } } public char pop(){ synchronized(this){ idx--; return data[idx]; } } }
如果一个方法的整体都在synchronized块中,则可将synchronized 关键字置于方法声明中,即:
public synchronized void mtd() {/方法体/ }
等价于 public void mtd(){ synchronized(this){ /方法体/ }}
几点说明:
1.对象锁具有可重入性:允许已拥有某对象锁的线程再次请求并 获得该对象锁
2.对共享资源加锁,是通过限制代码对共享资源的访问方式来实 现的——不要留给用户绕过临界区代码访问共享资源的机会
将作为成员变量的共享数据设为private的
对共享数据的所有访问都必须使用synchronized
3.何时返还对象锁?
synchronized语句块执行完毕后
在synchronized语句块中出现exception时
持有锁的线程调用该对象的wait()方法,将该线程放入对象的wait pool中,等待某事件的发生
避免死锁:
死锁是指两个线程同时等待对方持有的锁
死锁的避免完全由程序控制
可以采用的方法——资源排序
在访问多个共享数据对象时,从全局考虑定义一个获得锁的顺序, 并在整个程序中都遵守此顺序;释放锁时,要按加锁的反序释放
-
线程间的交互
多个线程间不仅需要同步使用共享数据,还需要进行某种协作, 一种典型的协作方式是使用共享资源的wait()和notify()方法
wait()和notify()
线程在synchronized块中调用R.wait()释放共享对象R的对象锁, 将自身阻塞并进入R的wait pool 线程调用R.notify(),将共享对象R的wait pool中的一个线程唤 醒(移入lock pool),等待R的对象锁
notifyAll()将共享对象wait pool中的所有线程都移入lock pool
//Producer线程:每隔300ms产生一个字母压入theStack栈中,共200个class Producer implements Runnable {private SyncStack theStack;...public void run() {char c;for (int i = 0; i < 200; i++) {c = (char) (Math.random() * 26 + ‘A’);theStack.push(c);System.out.println(“Producer” + num + “:” + c);try {Thread.sleep(300);} catch (InterruptedException e) {}}}}
//Consumer线程:每隔300ms从theStack栈中取出一个字符,共200个class Consumer extends Thread {private SyncStack theStack;...public void run() {char c;for (int i = 0; i < 200; i++) {c = theStack.pop();System.out.println(“Consumer” + num + “:” + c);try {Thread.sleep(300);} catch (InterruptedException e) {}}}}
//堆栈类SyncStack:为保证共享数据一致性,push()与pop()定义为synchronizedpublic class SyncStack {private Vector<Character> buffer = new Vector<Character>(400, 200);public synchronized char pop() {char c;while (buffer.size() == 0) {try {this.wait(); //pop()中加入wait()} catch (InterruptedException e) {}}c = ((Character) buffer.remove(buffer.size() - 1)).charValue();return c;}public synchronized void push(char c) {this.notify(); //push()中加入notify()Character charObj = new Character(c);buffer.addElement(charObj);}}
-
线程状态与生命周期
New:线程被创建,尚未start()
Runnable: 线程可以被调度器选中执行
Running:线程正在占用cpu执行
Dead:run()执行结束,不会再被调度
Blocked: 线程此后还能运行,但某种条件阻止其运行
当前线程调用sleep()进入睡眠状态
当前线程调用t.join()等待线程t执行结束
当前线程试图执行临界区代码片段但未获得对象锁
当前线程调用共享对象的wait()方法
-
-
-
-
ps:如有错误敬请指正,欢迎评论区交流或者发私信
邮箱1654407501@qq.com,QQ号同邮箱