**《Java高级程序设计》考试说明
考试时间:开学第一周;
考试形式:笔试;
考试在总成绩中的占比:60%。
请再次查看自己的头歌、学习通和实验系统中的任务是否全部完成,这些作业任务占总成绩的40%。
一、题型:
题型 选择 程序分析 程序填空 编程
题目数量 10 5 5 2
分值 10 20 30 40
二、考查范围:
1、第1-11章内容,主要考核程序设计能力;
2、题目主要来自:课后习题、课堂实例、教材案例和头歌实训;
三、参考题型
1、程序分析:
阅读下面的程序,分析代码是否能够编译通过,如果能编译通过,请列出运行的结果;否则请说明编译失败的原因。
主要考查语法基础,比如static、this、构造方法、变量继承、方法的重载和重写、上转型对象变量和方法的调用,集合元素的遍历和删除等。
(1)自动类型提升
byte->short->int->long->float->double (不可以反过来)
public class Example01 {
public static void main(String[] args) {
int num = 4;
byte b = num;
System.out.println(b);
}
}
编译上述代码,程序报错。
错误原因:数据类型不兼容,不能将int类型转换成byte类型。
(2)变量的作用域
public class Example04 {
public static void main(String[] args) {
int x = 12; // 定义了变量x
{
int y = 96; // 定义了变量y
System.out.println("x is " + x); // 访问变量x
System.out.println("y is " + y); // 访问变量y
}
y = x; // 访问变量x,为变量y赋值
System.out.println("x is " + x); // 访问变量x
}
}
编译上述代码,程序报错,原因:变量y在作用域范围之外被使用(y=x;)。
(3)面向对象的三大特性:
封装,继承 多态
(4)this关键字
class Student {
private String name;
private int age;
public Student () {
System.out.println("实例化了一个新的Student对象。");
}
public Student (String name,int age) {
this(); // 调用无参的构造方法
在使用this调用类的构造方法时,应注意以下几点:
(1)只能在构造方法中使用this调用其他的构造方法,不能在成员方法中通过this调用其他构造方法。
(2)在构造方法中,使用this调用构造方法的语句必须位于第一行,且只能出现一次。下面程序的写法是错误的:
public Student(String name) {
System.out.println("有参的构造方法被调用了。");
this(name); //不在第一行,编译错误!
}
(3)不能在一个类的两个构造方法中使用this互相调用,错误程序如下。
class Student {
public Student () {
this("张三"); // 调用有参构造方法
System.out.println("无参的构造方法被调用了。");
}
public Student (String name) {
this(); // 调用无参构造方法
System.out.println("有参的构造方法被调用了。");
}
}
(5)代码块
普通代码块就是直接在方法或是语句中定义的代码块,具体示例如下。
public class Example12 {
public static void main(String[] args) {
{
int age = 18;
System.out.println("这是普通代码块。age:"+age);
}
int age = 30;
System.out.println("age:"+age);
}
}
构造代码块是直接在类中定义的代码块。接下来通过一个案例演示构造代码块的作用。
构造块的执行顺序大于构造方法(这里和构造块写在前面还是后面没有关系)。
每当实例化一个Student类对象,都会在执行构造方法之前执行构造代码块。
1 class Student{
2 String name; //成员属性
3 {
4 System.out.println("我是构造代码块"); //与构造方法同级
5 }
6 //构造方法
7 public Student(){
8 System.out.println("我是Student类的构造方法");
9 }
10 }
(6)static关键字
(1)静态属性(也称全局属性),static声明的属性是对所有对象共享的。
静态属性可以使用类名直接访问
(2)静态方法
静态方法只能访问静态成员,因为非静态成员需要先创建对象才能访问,即随着对象的创建,非静态成员才会分配内存。而静态方法在被调用时可以不创建任何对象。
(3)静态代码块
在Java类中,用static关键字修饰的代码块称为静态代码块。当类被加载时,静态代码块会执行,由于类只加载一次,因此静态代码块只执行一次。在程序中,通常使用静态代码块对类的成员变量进行初始化。
class Student{
String name; //成员属性
{
System.out.println("我是构造代码块");
}
static {
System.out.println("我是静态代码块");
}
public Student(){ //构造方法
System.out.println("我是Student类的构造方法");
}
}
class Example16{
public static void main(String[] args) {
Student stu1 = new Student();
Student stu2 = new Student();
Student stu3 = new Student();
}
}
在3次实例化对象的过程中,静态代码块中的内容只输出了一次,这就说明静态代码块在类第一次使用时才会被加载,并且只会加载一次。
(7)继承
在Java中,类只支持单继承,不允许多重继承。也就是说一个类只能有一个直接父类,例如下面这种情况是不合法的。
class A{}
class B{}
class C extends A,B{} // C类不可以同时继承A类和B类
(8)方法的重写
在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。在子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表以及返回值类型,且在子类重写的方法不能拥有比父类方法更加严格的访问权限。父类中的方法是public权限,子类的方法就不能是private权限。
1 // 定义Animal类
2 class Animal {
3 //定义动物叫的方法
4 void shout() {
5 System.out.println("动物发出叫声");
6 }
7 }
8 // 定义Dog类继承动物类
9 class Dog extends Animal {
10 //重写父类Animal中的shout()方法
11 void shout() {
12 System.out.println("汪汪汪……");
13 }
14 }
(9)super关键字
当子类重写父类的方法后,子类对象将无法访问父类被重写的方法,为了解决这个问题,Java提供了super关键字,super关键字可以在子类中调用父类的普通属性、方法以及构造方法。
super.成员变量
super.成员方法(参数1,参数2…)
(10)final关键字
(1)使用final修饰的类不能有子类。即该类不能被继承 (2)使用final修饰的方法不能被子类重写。
(3)使用final修饰的变量(成员变量和局部变量)是常量,常量不可修改。
(10)抽象类
抽象类的定义规则如下:
(1)包含一个以上抽象方法的类必须是抽象类。
(2)抽象类和抽象方法都要使用abstract关键字声明。
(3)抽象方法只需声明而不需要实现。
(4)如果一个类继承了抽象类,那么该子类必须实现抽象类中的全部抽象方法。
使用abstract关键字修饰的抽象方法不能使用private修饰,因为抽象方法必须被子类实现,如果使用了private声明,则子类无法实现该方法。
publid abstract class/void
(11)接口
如果一个抽象类的所有方法都是抽象的,则可以将这个类定义接口。
接口是一种特殊的类,由全局常量和公共的抽象方法组成,不能包含普通方法。
接口中的变量默认使用“public static final”进行修饰,即全局常量。
接口中定义的方法默认使用“public abstract”进行修饰,即抽象方法。
接口使用interface关键字声明,语法格式如下:
public interface 接口名 extends 接口1,接口2... {
public static final 数据类型 常量名 = 常量值;
public default 返回值类型 抽象方法名(参数列表);
public abstract 返回值类型 方法名(参数列表){
//默认方法的方法体
}
public abstract 返回值类型方法名(参数列表){
//类方法的方法体
}
}
不管写不写访问权限,接口中的方法访问权限永远是public。与此类似,在接口中定义常量时,可以省略前面的“public static final”,此时,接口会默认为常量添加“public static final”。
在Java中,接口是不允许继承抽象类的,但是允许一个接口继承多个接口。接下来通过一个案例讲解接口的继承。
1 // 定义抽象类Animal
2 interface Animal {
3 public String NAME = "牧羊犬";
4 public void info(); // 定义抽象方法info()
5 }
6 interface Color {
7 public void black(); // 定义抽象方法shout()
8 }
9 interface Action extends Animal,Color{
10 public void shout(); // 定义抽象方法black()11 }
(11)多态
在Java中,多态是指不同对象在调用同一个方法时表现出的多种不同行为。
Java中多态主要有以下两种形式:
(1)方法的重载。 (2)对象的多态性(方法重写)。
对象类型转换主要分为以下两种情况:
(1)向上转型:子类对象→父类对象。
(2)向下转型:父类对象→子类对象。
对象类型的转换格式如下所示。
对象向上转型:父类类型 父类对象 = 子类实例;
对象向下转型: 父类类型 父类对象 = 子类实例; 子类类型
子类对象 = (子类)父类对象;
对于向上转型,程序会自动完成,而向下转型时,必须指明要转型的子类类型。
(12)instanceof关键字
Java中可以使用instanceof关键字判断一个对象是否是某个类(或接口)的实例,语法格式如下所示。
对象 instanceof类(或接口)
在上述格式中,如果对象是指定的类的实例对象,则返回true,否则返回false。
(13)Object类
它是所有类的父类,俗称“超类”,每个类都间接继承Object类。
(14)内部类
在Java中,允许在一个类的内部定义类,这样的类称作内部类,内部类所在的类称作外部类。在实际开发中,根据内部类的位置、修饰符和定义方式的不同,内部类可分为4种,分别是成员内部类、局部内部类、静态内部类、匿名内部类。
(1)成员内部类
在一个类中除了可以定义成员变量、成员方法,还可以定义类,这样的类被称作成员内部类。成员内部类可以访问外部类的所有成员。
1 class Outer {
2 int m = 0; // 定义类的成员变量
3 // 下面的代码定义了一个成员方法,方法中访问内部类
4 void test1() {
5 System.out.println("外部类成员方法");
6 }
7 // 下面的代码定义了一个成员内部类
8 class Inner {
9 int n = 1;
10 void show1() {
11 // 在成员内部类的方法中访问外部类的成员变量
12 System.out.println("外部成员变量m = " + m);
13 }
14 void show2() {
15 // 在成员内部类的方法中访问外部类的成员变量
16 System.out.println("内部成员方法");
17 }
18 }
19 void test2() {
20 Inner inner = new Inner();
21 System.out.println("内部成员变量n = " + inner.n);
22 inner.show2();
23 }
24 }
25 public class Example20 {
26 public static void main(String[] args) {
27 Outer outer = new Outer();
28 Outer.Inner inner = outer.new Inner();
29 inner.show1();
30 outer.test2();
31 }
32 }
如果想通过外部类访问内部类,则需要通过外部类创建内部类对象,创建内部类对象的具体语法格式如下:
外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
(2)局部内部类(方法内部类)
在局部内部类中,局部内部类可以访问外部类的所有成员变量和方法,而局部内部类中变量和方法却只能在所属方法中访问。
(3)静态内部类
所谓静态内部类,就是使用static关键字修饰的成员内部类。
静态内部类只能访问外部类的静态成员,通过外部类访问静态内部类成员时,可以跳过外部类直接访问静态内部类。
创建静态内部类对象的基本语法格式如下:
外部类名.静态内部类名 变量名 = new 外部类名().静态内部类名();
(4)匿名内部类
匿名内部类是没有名称的内部类。
创建匿名内部类的基本语法格式如下:
new 父接口(){
//匿名内部类实现部分
}
(15)异常
(16)集合类
为了在程序中可以保存数目不确定的对象,Java提供了一系列特殊的类,这些类可以存储任意类型的对象,并且长度可变,这些类被统称为集合。集合类都位于java.util包中,使用时必须导包。
单列集合Collection和双列集合Map
Collection:单列集合类的根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是List和Set。
List的特点是元素有序、元素可重复。
Set的特点是元素无序,而且不可重复。
List接口的主要实现类有ArrayList和LinkedList,Set接口的主要实现类有HashSet和TreeSet。
整个集合类的继承体系如下图。
(1)ArrayList集合
由于ArrayList集合的底层是使用一个数组来保存元素,在增加或删除指定位置的元素时,会导致创建新的数组,效率比较低,因此不适合做大量的增删操作。因为这种数组的结构允许程序通过索引的方式来访问元素,所以使用ArrayList集合查找元素很便捷。
(2)LinkedList集合
LinkedList集合对于元素的增删操作具有很高的效率
(3)Iterator接口
在程序开发中,经常需要遍历集合中的所有元素。针对这种需求,Java专门提供了一个接口Iterator。
Collection接口与Map接口主要用于存储元素,而Iterator主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。
9 Iterator it = list.iterator(); // 获取Iterator对象
10 while (it.hasNext()) { // 判断ArrayList集合中是否存在下一个元素
11 Object obj = it.next(); // 取出ArrayList集合中的元素
12 System.out.println(obj);
13 }
14 }
15 }
(4)Set接口
HashSet是根据对象的哈希值来确定元素在集合中的存储位置,具有良好的存取和查找性能。TreeSet则是以二叉树的方式来存储元素,它可以实现对集合中的元素进行排序。
1、HashSet集合之所以能确保不出现重复的元素,是因为它在存入元素时做了很多工作。当调用HashSet集合的add()方法存入元素时,首先调用当前存入对象的hashCode()方法获得对象的哈希值,然后根据对象的哈希值计算出一个存储位置。如果该位置上没有元素,则直接将元素存入,如果该位置上有元素存在,则会调用equals()方法让当前存入的元素依次和该位置上的元素进行比较,如果返回的结果为false就将该元素存入集合,返回的结果为true则说明有重复元素,就将该元素舍弃。
2、TreeSet集合之所以可以对添加的元素进行排序,是因为元素的类可以实现Comparable接口 (基本类型的包装类,String类都实现了该接口)。
Comparable接口强行对实现它的每个类的对象进行整体排序,这种排序被称为类的自然排序。Comparable接口的 compareTo() 方法被称为自然比较方法。
如果将自定义的Student对象存入TreeSet,TreeSet将不会对添加的元素进行排序,Student对象必须实现Comparable接口并重写compareTo()方法实现对象元素的顺序存取。
(5)Map接口
1、HashMap集合
HashMap集合是Map接口的一个实现类,用于存储键值映射关系,但HashMap集合没有重复的键并且键值无序。
遍历XXMap集合的方式
1.1首先调用Map对象的KeySet()方法,获得存储Map中所有键的Set集合,然后通过Iterator迭代Set集合的每一个元素,即每一个键,最后通过调用get(String key)方法,根据键获取对应的值。
HashMap map = new HashMap();
Set keySet = map.keySet();
Iterator it = keySet.iterator();
while(it.hasNext()){
Object key = it.next();
Object val = map.getKey(obj);
System.out.println(key+":"+val);
}
1.2 XXMap集合的另外一种遍历方式是先获取集合中的所有映射关系,然后从映射关系中取出键和值。
首先调用Map对象的entrySet()方法获得存储在Map中所有映射的Set集合,这个集合中存放了Map.Entry类型的元素(Entry是Map内部接口),每个Map.Entry对象代表Map中的一个键值对,然后迭代Set集合,获得每一个映射对象,并分别调用映射对象的getKey()和getValue()方法获取键和值。
HashMap map = new HashMap();
Set entrySet = map.entrySet();
Iterator it =entrySet.iterator();
while(it.hasNext()){
Map.Entry entry = (Map.Entry)(it.next());
Object key = entry.getKey();
Object val = entry.getValue();
System.out.println(key+":"+val);
}
(6)Properties集合
Properties集合来存取应用的配置项。
假设有一个文本编辑工具,要求默认背景色是红色,字体大小为14px,语言为中文,其配置项如下面的代码:
Backgroup-color = red
Font-size = 14px
Language = chinese
1 import java.util.*;
2 public class Example21 {
3 public static void main(String[] args) {
4 Properties p=new Properties(); // 创建Properties对象
5 p.setProperty("Backgroup-color", "red");
6 p.setProperty("Font-size", "14px");
7 p.setProperty("Language", "chinese");
8 Enumeration names = p.propertyNames();//获取Enumeration对象所有键枚举
9 while(names.hasMoreElements()){ // 循环遍历所有的键
10 String key=(String) names.nextElement();
11 String value=p.getProperty(key); // 获取对应键的值
12 System.out.println(key+" = "+value);
13 }
14 }
15 }
上述代码的Properties类中,针对字符串的存取提供了两个专用的方法setProperty()和getProperty()。
setProperty()方法用于将配置项的键和值添加到Properties集合当中。
在第8行代码中通过调用Properties的propertyNames()方法得到一个包含所有键的Enumeration对象,
然后在遍历所有的键时,通过调用getProperty()方法获得键所对应的值。
(17)增强for循环
foreach循环用于遍历数组或集合中的元素,具体语法格式如下:
for(容器中元素类型 临时变量 :容器变量) {
执行语句
}
foreach循环虽然书写起来很简洁,但在使用时也存在一定的局限性。当使用foreach循环遍历集合和数组时,只能访问集合中的元素,不能对其中的元素进行修改。
(18)Lambda表达式
Lambda可以取代大部分的匿名内部类,写出更优雅的Java代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。JDK也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
Lambda表达式由参数列表、箭头符号 和函数体组成。
函数体既可以是一个表达式,也可以是一个语句块。
Lambda表达式常用的语法格式如下表。
2、程序填空:
补充代码,完成以下功能。
主要考查知识的运用,比如使用集合或数组进行统计、排序,文件的创建和过滤、目录遍历和删除、线程创建和同步、TCP或UDP通信、数据库操作和GUI界面的设计、绘制等。
(1)集合的统计或排序
(2)File类
文件的创建和删除
1 import java.io.IOException;
2 import java.io.File;
3 class Examole01 {
4 public static void main(String[] args) throws IOException {
5 //磁盘下创建文件
6 File file=new File("d:\\hello\\demo.txt");
7 if(file.exists()){ //如果存在这个文件就删除,否则就创建
8 file.delete();
9 }else{
10 System.out.println(file.createNewFile());
11 }
目录的创建和删除
12 //在磁盘下创建一层目录,并且在目录下创建文件文件
13 File fileDemo=new File("d:\\hello1\\demo.txt");
14 if(!(fileDemo.getParentFile().exists())){ //判断d:\demo目录是否存在
15 fileDemo.getParentFile().mkdir();
16 }
17 if(fileDemo.exists()){ //如果存在这个文件就删除,否则就创建
18 fileDemo.delete();
19 }else{
20 System.out.println(fileDemo.createNewFile());
21 }
22 }
23 }
遍历目录下的文件
File类的list()方法用于遍历指定目录下的所有文件
File file = new File("D:/IdeaWorkspace/chapter07");
if (file.isDirectory()) { // 判断File对象对应的目录是否存在
String[] names = file.list (); // 获得目录下的所有文件的文件名
for (String name : names) {
System.out.println(name); // 输出文件名
}
}
上述程序实现了遍历一个目录下所有文件的功能,然而有时程序只是需要得到指定类型的文件,如获取指定目录下所有的“.java”文件。针对这种需求,File类中提供了一个重载的list(FilenameFilter filter)方法,该方法接收一个FilenameFilter类型的参数。FilenameFilter是一个接口,被称作文件过滤器,当中定义了一个抽象方法accept(File dir,String name)。在调用list()方法时,需要实现文件过滤器FilenameFilter,并在accept()方法中做出判断,从而获得指定类型的文件。
接下来通过一个案例来演示如何遍历指定目录下所有扩展名为“.java”的文件。
1 import java.io.File;
2 import java.io.FilenameFilter;
3 public class Example04 {
4 public static void main(String[] args) throws Exception {
5 // 创建File对象
6 File file = new File("D:/IdeaProjects/text/com/itcast");
7 // 创建过滤器对象
8 FilenameFilter filter = new FilenameFilter() {
9 // 实现accept()方法
10 public boolean accept(File dir, String name) {
11 File currFile = new File(dir, name);
12 // 如果文件名以.java结尾返回true,否则返回false
13 if (currFile.isFile() && name.endsWith(".java")) {
14 return true;
15 } else {
16 return false;
17 }
18 }
19 };
20 if (file.exists()) { // 判断File对象对应的目录是否存在
21 String[] lists = file.list(filter); // 获得过滤后的所有文件名数组
22 for (String name : lists) {
23 System.out.println(name);
24 }
25 }
26 }
27 }
listFiles()方法返回一个File对象数组,当对数组中的元素进行遍历时,如果元素中还有子目录需要遍历,则需要使用递归。
接下来通过一个案例来实现遍历指定目录下的文件。
File file = new File("D:\\eclipseWorkspace\\JavaBasicWorkspace\\chapter07");
fileDir(file);
public static void fileDir(File file){
File[] files = file.listFiles();
for(File f:files){
if(f.isDirectory())
fileDir(f);
System.out.println(file.getAbsolutePath());
}
}
删除文件及目录
File类的delete()方法只能删除一个指定的文件,不能删除目录,只能删除空的目录
1 import java.io.*;
2 public class Example07 {
3 public static void main(String[] args) {
4 File file = new File("D:\\hello\\test");
5 deleteDir(file); // 调用deleteDir删除方法
6 }
7 public static void deleteDir(File dir) {
8 if (dir.exists()) { // 判断传入的File对象是否存在
9 File[] files = dir.listFiles(); // 得到File数组
10 for (File file : files) { // 遍历所有的子目录和文件
11 if (file.isDirectory()) {
12 deleteDir(file); // 如果是目录,递归调用deleteDir()
13 } else {
14 // 如果是文件,直接删除
15 file.delete();
16 }
17 }
18 // 删除完一个目录里的所有文件后,就删除这个目录
19 dir.delete();
20 }
21 }
22 }
上述代码中,第45行代码定义了一个File对象并将File对象传入deleteDir()方法中,第721行代码定义了一个删除目录的静态方法deleteDir()来接收一个File对象,并将所有的子目录和文件对象放在一个File数组中,遍历这个File数组,如果是文件,则直接删除,如果是目录,则删除目录中的文件,当目录中的文件全部删除完之后,删除目录。
(3)多线程
1、线程的创建
Java中提供了两种多线程实现方式,一种是继承java.lang包下的Thread类,覆写Thread类的run()方法,在run()方法中实现运行在线程上的代码;另一种是实现java.lang.Runnable接口,同样是在run()方法中实现运行在线程上的代码。
1 public class Example02 {
2 public static void main(String[] args) {
3 MyThread myThread = new MyThread(); // 创建线程MyThread的线程对象
4 myThread.start(); // 开启线程
5 while (true) { // 通过死循环语句打印输出
6 System.out.println("main()方法在运行");
7 }
8 }
9 }
10 class MyThread extends Thread {
11 public void run() {
12 while (true) { // 通过死循环语句打印输出
13 System.out.println("MyThread类的run()方法在运行");
14 }
15 }
16 }
通过继承Thread类可以实现多线程,但是这种方式有一定的局限性。因为Java只支持单继承,一个类一旦继承了某个父类就无法再继承Thread类,比如学生类Student继承了Person类,就无法通过继承Thread类创建线程。
为了克服这种弊端,Thread类提供了另外一个构造方法Thread(Runnable target),其中Runnable是一个接口,它只有一个run()方法。当通过Thread(Runnable target)构造方法创建线程对象时,只需为该方法传递一个实现了Runnable接口的实例对象,这样创建的线程将调用实现了Runnable接口的类中的run()方法作为运行代码,而不需要调用Thread类中的run()方法。
小提示:
JDK8简化了多线程的创建方法,在创建线程时指定线程要调用的方法,格式如下。
Thread t = new Thread(() -> {
//main方法代码
}
});
线程休眠
Thread.sleep()
线程让步
thread.yield();
线程插队
thread.join()
线程安全问题其实就是由多个线程同时处理共享资源所导致的,要想解决线程安全问题,必须得保证在任何时刻只能有一个线程访问共享资源。具体示例如下:
while (tickets > 0) {
try {
Thread.sleep(10); // 经过此处的线程休眠10毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + “—卖出的票”+ tickets–);
}
为了实现这种限制,Java中提供了同步机制。当多个线程使用同一个共享资源时,可以将处理共享资源的代码放在一个使用synchronized关键字修饰的代码块中,这个代码块被称作同步代码块。使用synchronized关键字创建同步代码块的语法
格式如下:
synchronized(lock){
操作共享资源代码块
}
上面的格式中,lock是一个锁对象,它是同步代码块的关键。当某一个线程执行同步代码块时,其他线程将无法执行当前同步代码块,会发生阻塞,等当前线程执行完同步代码块后,所有的线程开始抢夺线程的执行权,抢到执行权的线程将进入同步代码块,执行其中的代码。循环往复,直到共享资源被处理完为止。这个过程就好比一个公用电话亭,只有前一个人打完电话出来后,后面的人才可以打。
1 //定义Ticket1类继承Runnable接口
2 class Ticket1 implements Runnable {
3 private int tickets = 10; // 定义变量tickets,并赋值10
4 Object lock = new Object(); // 定义任意一个对象,用作同步代码块的锁
5 public void run() {
6 while (true) {
7 synchronized (lock) { // 定义同步代码块
8 try {
9 Thread.sleep(10); // 经过的线程休眠10毫秒
10 } catch (InterruptedException e) {
11 e.printStackTrace();
12 }
13 if (tickets > 0) {
14 System.out.println(Thread.currentThread().getName()
15 + "---卖出的票" + tickets--);
16 } else { // 如果 tickets小于0,跳出循环
17 break;
18 }
19 }
20 }
21 }
22 }
23public class Example11 {
24 public static void main(String[] args) {
25 Ticket1 ticket = new Ticket1(); // 创建Ticket1对象
26 // 创建并开启四个线程
27 new Thread(ticket, "线程一").start();
28 new Thread(ticket, "线程二").start();
29 new Thread(ticket, "线程三").start();
30 new Thread(ticket, "线程四").start();
31 }
32 }
1 public class Example03 {
2 public static void main(String[] args) {
3 MyThread myThread = new MyThread(); // 创建MyThread的实例对象
4 Thread thread = new Thread(myThread); // 创建线程对象
5 thread.start(); // 开启线程,执行线程中的run()方法
6 while (true) {
7 System.out.println("main()方法在运行");
8 }
9 }
10 }
11 class MyThread implements Runnable {
12 public void run() { // 线程的代码段,当调用start()方法时,线程从此处开始执行
13 while (true) {
14 System.out.println("MyThread类的run()方法在运行");
15 }
16 }
17 }
售票两种创建线程的方式对比
首先通过继承Thread类的方式创建多线程。
1 public class Example04 {
2 public static void main(String[] args) {
3 new TicketWindow().start(); // 创建第一个线程对象TicketWindow并开启
4 new TicketWindow().start(); // 创建第二个线程对象TicketWindow并开启
5 new TicketWindow().start(); // 创建第三个线程对象TicketWindow并开启
6 new TicketWindow().start(); // 创建第四个线程对象TicketWindow并开启
7 }
8 }
9 class TicketWindow extends Thread {
10 private int tickets = 100;
11 public void run() {
12 while (true) { // 通过死循环语句打印语句
13 if (tickets > 0) {
14 Thread th = Thread.currentThread(); // 获取当前线程
15 String th_name = th.getName(); // 获取当前线程的名字
16 System.out.println(th_name + " 正在发售第 " + tickets-- + " 张票 ");
17 }
18 }
19 }
20 }
从运行结果可以看出,每张票都被打印了四次。出现这样现象的原因是**四个线程没有共享**100张票,而是各自出售了100张票。
在程序中创建了四个TicketWindow对象,就等于创建了四个售票程序,每个程序中都有100张票,每个线程在独立地处理各自的资源。
需要注意的是,上述程序中每个线程都有自己的名字,主线程默认的名字是“main”,用户创建的第一个线程的名字默认为“Thread-0”,第二个线程的名字默认为“Thread-1”,以此类推。
如果希望指定线程的名称,可以通过调用setName(String name)方法为线程设置名称。
由于现实中铁路系统的票资源是共享的,因此上面的运行结果显然不合理。为了保证资源共享,在程序中只能创建一个售票对象,然后开启多个线程去运行同一个售票对象的售票方法。简单来说就是四个线程运行同一个售票程序,这时就需要用到多线程的第二种实现方式。
接下来,通过实现Runnable接口的方式来实现多线程的创建。修改上述程序,并使用构造方法Thread(Runnable target, String name)在创建线程对象时指定线程的名称。
1 public class Example05 {
2 public static void main(String[] args) {
3 TicketWindow tw = new TicketWindow(); // 创建TicketWindow实例对象tw
4 new Thread(tw, "窗口1").start(); // 创建线程对象并命名为窗口1,开启线程
5 new Thread(tw, "窗口2").start(); // 创建线程对象并命名为窗口2,开启线程
6 new Thread(tw, "窗口3").start(); // 创建线程对象并命名为窗口3,开启线程
7 new Thread(tw, "窗口4").start(); // 创建线程对象并命名为窗口4,开启线程
8 }
9 }
10 class TicketWindow implements Runnable {
11 private int tickets = 100;
12 public void run() {
13 while (true) {
14 if (tickets > 0) {
15 Thread th = Thread.currentThread(); // 获取当前线程
16 String th_name = th.getName(); // 获取当前线程的名字
17 System.out.println(th_name + " 正在发售第 " + tickets-- + " 张票 ");
18 }
19 }
20 }
21 }
上述程序中,第10~21行代码创建了一个TicketWindow对象并实现了Runnable接口,然后在mian方法中创建了四个线程,在每个线程上都去调用这个TicketWindow对象中的run()方法,这样就可以确保四个线程访问的是同一个tickets变量,共享100张车票。
网络编程
1、InetAddress
在Java中,提供了一个与IP地址相关的InetAddress类,该类用于封装一个IP地址
接下来通过一个案例来演示InetAddress常用方法的使用。
1 import java.net.InetAddress;
2 public class Example01 {
3 public static void main(String[] args) throws Exception {
//获取表示本地主机的InetAdress对象
4 InetAddress localAddress = InetAddress.getLocalHost();
5 InetAddress remoteAddress = InetAddress. getByName("www.itcast.cn");
6 System.out.println("本机的IP地址:" + localAddress.getHostAddress());
7 System.out.println("itcast的IP地址:" + remoteAddress.getHostAddress());
8 System.out.println("3秒是否可达:" + remoteAddress.isReachable(3000));
9 System.out.println("itcast的主机名为:" + remoteAddress.getHostName());
10 }
11 }
UDP网络程序
DatagramPacket类用于封装UDP通信中发送或者接收的数据。想要创建一个DatagramPacket对象
(1)DatagramPacket(byte[] buf,int length)
使用该构造方法在创建DatagramPacket对象时,指定了封装数据的字节数组和数据的大小,没有指定IP地址和端口号。很明显,这样的对象只能用于接收端,不能用于发送端。因为发送端一定要明确指出数据的目的地(IP地址和端口号),而接收端不需要明确知道数据的来源,只需要接收到数据即可。
(2)DatagramPacket(byte[] buf,int length,InetAddress addr,int port)
使用该构造方法在创建DatagramPacket对象时,不仅指定了封装数据的字节数组和数据的大小,还指定了数据包的目标IP地址(addr)和端口号(port)。该对象通常用于发送端,因为在发送数据时必须指定接收端的IP地址和端口号,就好像发送货物的集装箱上面必须标明接收人的地址一样。
1 import java.net.*;
2 // 接收端程序
3 public class Receiver {
4 public static void main(String[] args) throws Exception {
5 byte[] buf = new byte[1024]; // 创建一个字节数组,用于接收数据
6 // 定义一个DatagramSocket对象,监听的端口号为8954
7 DatagramSocket ds = new DatagramSocket(8954);
8 // 定义一个DatagramPacket对象,用于接收数据,包装数据
9 DatagramPacket dp = new DatagramPacket(buf,buf.length);
10 System.out.println("等待接收数据");
11 ds.receive(dp); // 等待接收数据,如果没有数据则会阻塞
12 // 调用DatagramPacket的方法获得接收到的信息
13 //包括数据的内容、长度、发送的IP地址和端口号
14 String str = new String(dp.getData(), 0, dp.getLength()) +
15 "from "+ dp.getAddress().getHostAddress() + ":" + dp.getPort();
16 System.out.println(str); // 打印接收到的信息
17 ds.close();// 释放资源
18 }
19 }
实现了接收端程序之后,接下来还需要编写一个发送端的程序。
1 import java.net.*;
2 //发送端程序
3 public class Sender {
4 public static void main(String[] args) throws Exception {
5 // 创建一个DatagramSocket对象
6 DatagramSocket ds = new DatagramSocket(3000);
7 String str = "hello world"; // 要发送的数据
8 byte[] arr = str.getBytes(); //将定义的字符串转为字节数组
9 //创建一个要发送的数据包,数据包包括发送的数据,
10 //数据的长度,接收端的IP地址以及端口号
11 DatagramPacket dp = new DatagramPacket(arr, arr.length,
12 InetAddress.getByName("localhost"), 8954);
13 System.out.println("发送信息");
14 ds.send(dp); // 发送数据
15 ds.close(); // 释放资源
16 }
17 }
TCP通信
Java提供了两个用于实现TCP程序的类,一个是ServerSocket类,用于表示服务器端;一个是Socket类,用于表示客户端。通信时,首先要创建代表服务器端的ServerSocket对象,创建该对象相当于开启一个服务,此服务会等待客户端的连接;然后创建代表客户端的Socket对象,使用该对象向服务器端发出连接请求,服务器端响应请求后,两者才建立连接,开始通信。
(1)ServerSocket()
ServerSocket有一个不带参数的默认构造方法。通过该方法创建的ServerSocket对象不与任何端口绑定,这样的ServerSocket对象创建的服务器端没有监听任何端口,不能直接使用,还需要继续调用bind(SocketAddress endpoint)方法将其绑定到指定的端口号上,才可以正常使用。
ServerSocket对象负责监听某台计算机的某个端口号,在创建ServerSocket对象后,需要继续调用该对象的accept()方法,接收来自客户端的请求。
当执行了accept()方法之后,服务器端程序会发生阻塞,直到客户端发出连接请求时,accept()方法才会返回一个Socket对象用于和客户端实现通信,程序才能继续向下执行。
(1)Socket()
使用该构造方法在创建Socket对象时,并没有指定IP地址和端口号,也就意味着只创建了客户端对象,并没有去连接任何服务器。通过该构造方法创建对象后还需调用connect(SocketAddress endpoint)方法,才能完成与指定服务器端的连接,其中参数endpoint用于封装IP地址和端口号。
(2)Socket(String host, int port)
使用该构造方法在创建Socket对象时,会根据参数去连接在指定地址和端口上运行的服务器程序,其中参数host接收的是一个字符串类型的IP地址。
(3)Socket(InetAddress address, int port)
该构造方法在使用上与第二个构造方法类似,参数address用于接收一个InetAddress类型的对象,该对象用于封装一个IP地址。
接下来通过一个TCP通信的案例来进一步学习ServerSocket、Socket类的用法。要实现TCP通信需要创建一个服务器端程序和一个客户端程序,为了保证数据传输的安全性,首先需要实现服务器端程序。服务器端程序实现如下。
服务器端向客户端发送数据
服务器端
1 import java.io.*;
2 import java.net.*;
3 public class Server {
4 public static void main(String[] args) throws Exception {
5 new TCPServer().listen(); // 创建TCPServer对象,并调用listen()方法
6 }
7 }
8 // TCP服务器端
9 class TCPServer {
10 private static final int PORT = 7788; // 定义一个端口号
11 public void listen() throws Exception { // 定义一个listen()方法,抛出异常
12 ServerSocket serverSocket = new ServerSocket(PORT);
13 // 调用ServerSocket的accept()方法接收数据
14 Socket client = serverSocket.accept();
15 OutputStream os = client.getOutputStream();// 获取客户端的输出流
16 System.out.println("开始与客户端交互数据");
17 // 当客户端连接到服务器端时,向客户端输出数据
18 os.write(("传智播客欢迎你!").getBytes());
19 Thread.sleep(5000);// 模拟执行其他功能占用的时间
20 System.out.println("结束与客户端交互数据");
21 os.close();
22 client.close();
23 }
24 }
客户端
1 import java.io.*;
2 import java.net.*;
3 public class Client {
4 public static void main(String[] args) throws Exception {
5 new TCPClient().connect();// 创建TCPClient对象,并调用connect()方法
6 }
7 }
8 //TCP客户端
9 class TCPClient {
10 private static final int PORT = 7788; // 服务器端的端口号
11 public void connect() throws Exception {
12 //创建一个Socket并连接到给出地址和端口号的计算机
13 Socket client = new Socket(InetAddress.getLocalHost(), PORT);
14 InputStream is = client.getInputStream(); // 得到接收数据的流
15 byte[] buf = new byte[1024]; // 定义1024个字节数组的缓冲区
16 int len = is.read(buf); // 将数据读到缓冲区中
17 System.out.println(new String(buf, 0, len));// 将缓冲区中的数据输出
18 client.close(); // 关闭Socket对象,释放资源
19 }
20 }
多线程的TCP网络编程
3、编程:
主要考查使用Java技术进行问题分析和综合运用,比如面向对象的分析和实现,数据库操作,网络编程和多线程及GUI的综合应用,通常需要多个类协同完成。
4、选择:
主要考查基本概念和基本原理。
四、重点掌握的案例
1、教材案例:
【案例3-2】银行存取款程序设计;
【案例3-4】学生投票系统
【案例4-2】饲养员喂养动物
【案例4-6】经理与员工工资案例(利用多态实现)
【案例4-7】图形的面积与周长计算程序
【案例6-4】斗地主洗牌发牌
【案例6-5】模拟百度翻译
【案例9-1】模拟微信聊天
【案例9-2】字符串反转
【案例8-3】模拟银行存取钱
【案例8-4】模拟12306售票
【案例7-2】商城进货记录交易
2、其他案例:课后习题、头歌任务和课堂实例
1、编写一个小游戏:设计一窗体,窗体中上下有两个名称为“你来点我啊!!!”的按钮,当鼠标移动到上面按钮时,上面按钮消失,下面的显示;移动到下面时,下面消失,上面的显示。
2、设计一个窗体,窗体中有一个按钮,当单击按钮时,可以添加其它按钮,并按数字依次出现,当单击数字按钮时,被单击按钮消失,此窗体带关闭功能。
3、实现跟随鼠标拖动在窗口中绘制自由曲线;
4、实现动态图片效果:5幅图片循环播放。
5、编写一个程序,遍历出指定目录下所有的.java文件,并将其绝对路径存入一个list集合中输出。
6、删除包含子文件的目录;
7、列出目录下所有的”.java”文件;
8、已知在数据库jdbc中有一个名称为user的表,表中包含三个字段id,name,password。要求使用JDBCUtils工具类编写一个程序,使程序执行后,
(1)可以向user表中插入1条数据;
(2)可以向user表中插入5条数据。
9、数组的冒泡排序:将考试成绩排序并输出,返回成绩的个数;
10.各类集合的特点及应用:
整理给定的电话簿,具体要求如下:
1)接收给定的一行字符串(该字符串属于电话簿,包含多个电话号码,如:13545453432,13678909808);
2)使用 HashSet 去除电话簿中可能存在的重复电话号码;
3)按升序打印去重后的电话号码。
11、Set集合元素唯一性和有序性的实现:
(1)唯一性:重写元素对象所在类的hashcode()和equals();
(2)有序性:元素对象所在类实现Comparable接口,或者将Comparator接口的实现类对象传入集合。
12、要求在客户端向服务端发送一条消息,同时接收服务端返回的一条消息,从而完成一次完整的通信。其中:
(1)服务器IP地址为本地地址localhost,端口号为3022;
(2)relation()方法中填写接收服务端消息并输出的相关代码。
(3)start()方法中填写向服务器发送消息的相关代码,发送的消息为字符串“hello,Server”。
13、使用基于UDP的Java Socket编程,完成在线咨询功能:
(1)客户向咨询人员咨询。
(2)咨询人员给出回答。
(3)客户和咨询人员可以一直沟通,直到客户发送bye给咨询人员。**