java复习

**《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给咨询人员。**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值