26.数据隐藏
在定义一个对象的特性的时候,有必要决定这些特性的可见性,即哪些特性对外部是可见的,哪些特性用于表示内部状态。
通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。
例如:
//对象不仅能再类中方法,还能在类的外部"直接"访问
public class Student{
public String name;
public void println(){
System.out.println(this.name);
}
}
public class Test{
public static void main(String[] args){
Student s = new Student();
s.name = "tom";
}
}
在类中一般不会把数据直接暴露在外部的,而使用private(私有)关键字把数据隐藏起来
例如:
public class Student{
private String name;
}
public class Test{
public static void main(String[] args){
Student s = new Student();
//编译报错,在类的外部不能直接访问类中的私有成员
s.name = "tom";
}
}
如果在类的外部需要访问这些私有属性,那么可以在类中提供对于的get和set方法,以便让用户在类的外部可以间接的访问到私有属性
例如:
//set负责给属性赋值
//get负责返回属性的值
public class Student{
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
public class Test{
public static void main(String[] args){
Student s = new Student();
s.setName("tom");
System.out.println(s.getName());
}
}
思考:这样处理私有属性的作用
27.封装
1)隐藏代码的实现细节
2)统一用户的调用接口
3)提高系统的可维护性
28.方法重载
类中有多个方法,有着相同的方法名,但是方法的参数各不相同,这种情况被称为方法的重载。
方法的重载可以提供方法调用的灵活性。
例如:System.out.println()中的println方法,为什么可以把不同类型的参数传给这个方法?
例如:
public class Test{
public void test(String str){
}
public void test(int a){
}
}
方法重载必须满足一下条件:
1)方法名相同
2)参数列表不同(参数的类型、个数、顺序的不同)
public void test(Strig str){}
public void test(int a){}
public void test(Strig str,double d){}
public void test(Strig str){}
public void test(Strig str,double d){}
public void test(double d,Strig str){}
3)方法的返回值可以不同
注:在java中,判断一个类中的俩个方法是否相同,主要参考俩个方面:方法名字和参数列表
思考:方法重载有什么好处?
29)创建和初始化对象
使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用
那么对main方法中的以下代码:
Student s = new Student();
1)为对象分配内存空间,将对象的实例变量自动初始化默认值为0/false/null。(实例变量的隐式赋值)
2)如果代码中实例变量有显式赋值,那么就将之前的默认值覆盖掉。(之后可以通过例子看到这个现象)
例如:显式赋值
private String name = "tom";
3)调用构造器
4)把对象内存地址值赋值给变量。(=号赋值操作)
30.构造器
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下俩个特点:
1)必须和类的名字相同
2)必须没有返回类型,也不能写void
构造器的作用:
1)使用new创建对象的时候必须使用类的构造器
2)构造器中的代码执行后,可以给对象中的属性初始化赋值
例如:
public class Student{
private String name;
public Student(){
name = "tom";
}
}
构造器重载:
除了无参构造器之外,很多时候我们还会使用有参构造器,在创建对象时候可以给属性赋值.
例如:
public class Student{
private String name;
public Student(String name){
this.name = name;
}
}
构造器之间的调用:
使用this关键字,在一个构造器中可以调用另一个构造器的代码。
注意:this的这种用法不会产生新的对象,只是调用了构造器中的代码而已.一般情况下只有使用new关键字才会创建新对象。
例如:
public class Student{
private String name;
public Student(){
this();
}
public Student(String name){
this.name = name;
}
}
默认构造器:
在java中,即使我们在编写类的时候没有写构造器,那么在编译之后也会自动的添加一个无参构造器,这个无参构造器也被称为默认的构造器。
例如:
public class Student{
}
main:
//编译通过,因为有无参构造器
Student s = new Student();
但是,如果我们手动的编写了一个构造器,那么编译后就不会添加任何构造器了
例如:
public class Student{
private String name;
public Student(String name){
this.name = name;
}
}
main:
//编译报错,因为没有无参构造器
Student s = new Student();
31.继承
1)继承是类和类之间的一种关系
除此之外,类和类之间的关系还有依赖、组合、聚合等。
2)继承关系的俩个类,一个为子类(派生类),一个为父类(基类)。
子类继承父类,使用关键字extends来表示
例如:
public class student extends Person{
}
3)子类和父类之间,从意义上讲应该具有"is a"的关系.
例如:
student is a person
dog is a animal
4)类和类之间的继承是单继承
一个子类只能"直接"继承一个父类,就像是一个人只能有一个亲生父亲
一个父类可以被多子类继承,就像一个父亲可以有多个孩子
注:java中接口和接口之间,有可以继承,并且是多继承。
5)父类中的属性和方法可以被子类继承
子类中继承了父类中的属性和方法后,在子类中能不能直接使用这些属性和方法,是和这些属性和方法原有的修饰符(public protected defaul private)相关的。
例如:
父类中的属性和方法使用public修饰,在子类中继承后"可以直接"使用
父类中的属性和方法使用private修饰,在子类中继承后"不可以直接"使用
注:具体细则在修饰符部分详细说明
父类中的构造器是不能被子类继承的,但是子类的构造器中,会隐式的调用父类中的无参构造器(默认使用super关键字)。
注:具体细节在super关键字部分详细说明
6)Object类
java中的每一个类都是"直接" 或者 "间接"的继承了Object类.所以每一个对象都和Object类有"is a"的关系。从API文档中,可以看到任何一个类最上层的父类都是Object。(Object类本身除外)
AnyClass is a Object
例如:
System.out.println(任何对象 instanceof Object);
//输出结果:true
注:任何对象也包含数组对象
例如:
//编译后,Person类会默认继承Object
public class Person{}
//Student是间接的继承了Object
public class Student extends Person{}
在Object类中,提供了一些方法被子类继承,那么就意味着,在java中,任何一个对象都可以调用这些被继承过来的方法。(因为Object是所以类的父类)
例如:toString方法、equals方法、getClass方法等
注:Object类中的每一个方法之后都会使用到.
思考:继承有什么好处?
32.super关键字
子类继承父类之后,在子类中可以使用this来表示访问或调用子类中的属性或方法,使用super就表示访问或调用父类中的属性和方法
1)访问父类中的属性
例如:
public class Person{
protected String name = "zs";
}
public class Student extends Person{
private String name = "lisi";
public void test{
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
}
2)调用父类中的方法
例如:
public class Person{
public void print(){
System.out.println("Person");
}
}
public class Student extends Person{
public void print(){
System.out.println("Student");
}
public void test(){
print();
this.print();
super.print();
}
}
3)调用父类中的构造器
例如:
public class Person{
}
public class Student extends Person{
//编译通过,子类构造器中会隐式的调用父类的无参构造器
//super();
public Student(){
}
}
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
public class Student extends Person{
//编译报错,子类构造器中会隐式的调用父类的无参构造器,但是父类中没有无参构造器
//super();
public Student(){
}
}
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
public class Student extends Person{
//编译通过,子类构造器中显式的调用父类的有参构造器
public Student(){
super("tom");
}
}
注:不管是显式还是隐式的父类的构造器,super语句一定要出现在子类构造器中第一行代码。所以this和super不可能同时使用其调用构造器的功能,因为它们都要出现在第一行代码位置。
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
public class Student extends Person{
//编译报错,super调用构造器的语句不是第一行代码
public Student(){
System.out.println("Student");
super("tom");
}
}
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
//编译通过
public class Student extends Person{
private int age;
public Student(){
this(20);
}
public Student(int age){
super("tom");
this.age = age;
}
}
思考:这样做的目的是什么?
33.方法重写(方法覆盖)
1)方法重写只存在于子类和父类(包括直接父类和间接父类)之间。在同一个类中方法只能被重载,不能被重写.
2)静态方法不能覆盖
a. 父类的静态方法不能被子类重写为非静态方法 //编译出错
b. 父类的非静态方法不能被子类重写为静态方法;//编译出错
c. 子类可以定义与父类的静态方法同名的静态方法(但是这个不是覆盖)
例如:
A类继承B类 A和B中都一个相同的静态方法test
B a = new A();
a.test();//调用到的时候B类中的静态方法test
A a = new A();
a.test();//调用到的时候A类中的静态方法test
可以看出静态方法的调用只和变量声明的类型相关
这个和非静态方法的重写之后的效果完全不同
3)私有方法不能被子类重写
子类继承父类后,是不能直接访问父类中的私有方法的,那么就更谈不上重写了。
例如:
public class Person{
private void run(){}
}
//编译通过,但这不是重写,只是俩个类中分别有自己的私有方法
public class Student extends Person{
private void run(){}
}
4)重写的语法
1)方法名字必须一样
2)参数列表必须一样
3)访问修饰符可以扩大,但不能缩小
4)抛出异常范围可以缩小,但不能扩大
5)返回类型可以相同,也可以不同,如果不同,那么重写后的方法返回类型必须是原来方法返回类型的子类型.
例如:
public class Person{
public void run(){}
protected Object test()throws Exception{
return null;
}
}
//编译通过,子类继承父类,重写了run和test方法.
public class Student extends Person{
public void run(){}
public String test(){
return "";
}
}
5)为什么要重写
子类继承父类,继承了父类中的方法,但是父类中的方法并不一定能满足子类中的功能需要,所以子类中需要把方法进行重写。
34.多态
允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。
1)一个对象的实际类型是确定的
例如: new Student(); new Person();等
2)可以指向对象的引用的类型有很多
例如: Student继承了Person类
Student s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();
因为Person和Object都是Student的父类型
注:一个对象的实际类型是确定,但是可以指向这个对象的引用的类型,却是可以是这对象实际类型的任意父类型。
3)一个父类引用可以指向它的任何一个子类对象
例如:
Object o = new AnyClass();
Person p = null;
p = new Student();
p = new Teacher();
p = new Person();
4)多态中的方法调用
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
}
//调用到的run方法,是Student从Person继承过来的run方法
main:
Person p = new Student();
p.run();
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
public void run(){
//重写run方法
}
}
//调用到的run方法,是Student中重写的run方法
main:
Person p = new Student();
p.run();
注:子类继承父类,调用a方法,如果a方法在子类中没有重写,那么就是调用的是子类继承父类的a方法,如果重写了,那么调用的就是重写之后的方法。
5)子类中独有方法的调用
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
public void test(){
}
}
main:
Person p = new Student();
//调用到继承的run方法
p.run();
//编译报错,因为编译器检查变量p的类型是Person,但是在Person类中并没有发现test方法,所以编译报错.
p.test();
注:一个变量x,调用一个方法test,编译器是否能让其编译通过,主要是看声明变量x的类型中有没有定义test方法,如果有则编译通过,如果没有则编译报错.而不是看x所指向的对象中有没有test方法.
思考:为什么编译器会是这样检查?
6)子类引用和父类引用指向对象的区别
Student s = new Student();
Person p = new Student();
变量s能调用的方法是Student中有的方法(包括继承过来的),变量p能调用的方法是Person中有的方法(包括继承过来的)。
但是变量p是父类型的,p不仅可以指向Student对象,还可以指向Teacher类型对象等,但是变量s只能指向Studnet类型对象,及Student子类型对象。变量p能指向对象的范围是比变量s大的。
Object类型的变量o,能指向所有对象,它的范围最大,但是使用变量o能调用到的方法也是最少的,只能调用到Object中的声明的方法,因为变量o声明的类型就是Object.
7)多态的使用
如果使用多态解决以下问题:
public class Game{
public void start(BasketBall ball){
ball.run();
}
}
public class BasketBall{
public void run(){
//执行篮球游戏程序
}
}
在这种情况下,用户不断的变动需求:
想要增加足球游戏的运行
想要增加乒乓球游戏的运行
想要增加橄榄球游戏的运行
想要增加羽毛球球游戏的运行
想要增加铅球游戏的运行
....
如何处理?
35.instanceof和类型转换
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
}
public class Teacher extends Person{
}
例如:
main:
Object o = new Student();
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
System.out.println(o instanceof String);//false
---------------------------
Person o = new Student();
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
//编译报错
System.out.println(o instanceof String);
---------------------------
Student o = new Student();
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
//编译报错
System.out.println(o instanceof Teacher);
//编译报错
System.out.println(o instanceof String);
注1:
System.out.println(x instanceof Y);
该代码能否编译通过,主要是看声明变量x的类型和Y是否存在子父类的关系.有子父类关系就编译通过,没有子父类关系就是编译报错
注2:
System.out.println(x instanceof Y);
输出结果是true还是false,主要是看变量x所指向的对象实际类型是不是Y类型的子类型.
例如:
main:
Object o = new Person();
System.out.println(o instanceof Student);//false
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
System.out.println(o instanceof String);//false
类型转换
public class Person{
public void run(){}
}
public class Student extends Person{
public void go(){}
}
public class Teacher extends Person{
}
1)为什么要类型转换
//编译报错,因为p声明的类型Person中没有go方法
Person p = new Student();
p.go();
//需要把变量p的类型进行转换
Person p = new Student();
Student s = (Student)p;
s.go();
或者
//注意这种形式前面必须要俩个小括号
((Student)p).go();
2)类型转换中的问题
//编译通过
Object o = new Student();
Person p = (Person)o;
//编译通过
Object o = new Student();
Student s = (Student)o;
//编译通过,运行报错
Object o = new Teacher();
Student s = (Student)o;
即:
X x = (X)o;
运行是否报错,主要是变量o所指向的对象实现类型,是不是X类型的子类型,如果不是则运行就会报错。
36.static修饰符
1)static变量
在类中,使用static修饰的成员变量,就是静态变量,反之为非静态变量。
静态变量和非静态变量的区别
静态变量数属于类的,"可以"使用类名来访问,非静态变量是属于对象的,"必须"使用对象来访问.
例如:
public class Student{
private static int age;
private double score;
public static void main(String[] args) {
Student s = new Student();
//推荐使用类名访问静态成员
System.out.println(Student.age);
System.out.println(s.age);
System.out.println(s.score);
}
}
静态变量对于类而言在内存中只有一个,能被类的所有实例所共享。实例变量对于类的每个实例都有一份,它们之间互不影响.
例如:
public class Student{
private static int count;
private int num;
public Student() {
count++;
num++;
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
Student s4 = new Student();
//因为还是在类中,所以可以直接访问私有属性
System.out.println(s1.num);
System.out.println(s2.num);
System.out.println(s3.num);
System.out.println(s4.num);
System.out.println(Student.count);
System.out.println(s1.count);
System.out.println(s2.count);
System.out.println(s3.count);
System.out.println(s4.count);
}
}
在加载类的过程中为静态变量分配内存,实例变量在创建对象时分配内存
所以静态变量可以使用类名来直接访问,而不需要使用对象来访问.
2)static方法
在类中,使用static修饰的成员方法,就是静态方法,反之为非静态方法。
静态方法和非静态方法的区别
静态方法数属于类的,"可以"使用类名来调用,非静态方法是属于对象的,"必须"使用对象来调用.
静态方法"不可以"直接访问类中的非静态变量和非静态方法,但是"可以"直接访问类中的静态变量和静态方法
注意:this和super在类中属于非静态的变量.(静态方法中不能使用)
例如:
public class Student{
private static int count;
private int num;
public void run(){}
public static void go(){}
public static void test(){
//编译通过
System.out.println(count);
go();
//编译报错
System.out.println(num);
run();
}
}
非静态方法"可以"直接访问类中的非静态变量和非静态方法,也"可以"直接访问类中的静态变量和静态方法
例如:
public class Student{
private static int count;
private int num;
public void run(){}
public static void go(){}
public void test(){
//编译通过
System.out.println(count);
go();
//编译通过
System.out.println(num);
run();
}
}
思考:为什么静态方法和非静态方法不能直接相互访问?
父类的静态方法可以被子类继承,但是不能被子类重写
例如:
public class Person {
public static void method() {}
}
//编译报错
public class Student extends Person {
public void method(){}
}
例如:
public class Person {
public static void test() {
System.out.println("Person");
}
}
//编译通过,但不是重写
public class Student extends Person {
public static void test(){
System.out.println("Student");
}
}
main:
Perosn p = new Student();
p.test();//输出Person
p = new Person();
p.test();//输出Perosn
和非静态方法重写后的效果不一样
父类的非静态方法不能被子类重写为静态方法
例如:
public class Person {
public void test() {
System.out.println("Person");
}
}
//编译报错
public class Student extends Person {
public static void test(){
System.out.println("Student");
}
}
3)代码块和静态代码块
类中可以编写代码块和静态代码块
例如:
public class Person {
{
//代码块(匿名代码块)
}
static{
//静态代码块
}
}
匿名代码块和静态代码块的执行
因为没有名字,在程序并不能主动调用这些代码块。
匿名代码块是在创建对象的时候自动执行的,并且在构造器执行之前。同时匿名代码块在每次创建对象的时候都会自动执行.
静态代码块是在类加载完成之后就自动执行,并且只执行一次.
注:每个类在第一次被使用的时候就会被加载,并且一般只会加载一次.
例如:
public class Person {
{
System.out.println("匿名代码块");
}
static{
System.out.println("静态代码块");
}
public Person(){
System.out.println("构造器");
}
}
main:
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
//输出结果:
静态代码块
匿名代码块
构造器
匿名代码块
构造器
匿名代码块
构造器
匿名代码块和静态代码块的作用
匿名代码块的作用是给对象的成员变量初始化赋值,但是因为构造器也能完成这项工作,所以匿名代码块使用的并不多。
静态代码块的作用是给类中的静态成员变量初始化赋值。
例如:
public class Person {
public static String name;
static{
name = "tom";
}
public Person(){
name = "zs";
}
}
main:
System.out.println(Person.name);//tom
注:在构造器中给静态变量赋值,并不能保证能赋值成功,因为构造器是在创建对象的时候才指向,但是静态变量可以不创建对象而直接使用类名来访问.
创建和初始化对象的过程
Student s = new Student();
1)Student类之前没有进行类加载
1类加载,同时初始化类中静态的属性
2执行静态代码块
3分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
4调用Student的父类构造器
5对Student中的属性进行显示赋值(如果有的话)
6执行匿名代码块
7执行构造器
8返回内存地址
注:子类中非静态属性的显示赋值是在父类构造器执行完之后和子类中的匿名代码块执行之前的时候
例如:
public class Person{
private String name = "zs";
public Person() {
System.out.println("Person构造器");
print();
}
public void print(){
System.out.println("Person print方法: name = "+name);
}
}
public class Student extends Person{
private String name = "tom";
{
System.out.println("Student匿名代码块");
}
static{
System.out.println("Student静态代码块");
}
public Student(){
System.out.println("Student构造器");
}
public void print(){
System.out.println("student print方法: name = "+name);
}
public static void main(String[] args) {
new Student();
}
}
//输出结果:
Student静态代码块
Person构造器
student print方法: name = null
Student匿名代码块
Student构造器
Student s = new Student();
2)Student类之前已经进行了类加载
1.分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
2.调用Student的父类构造器
3.对Student中的属性进行显示赋值(如果有的话)
4.执行匿名代码块
5.执行构造器
6.返回内存地址
4)静态导入
静态导入是JDK5.0引入的新特性。
例如:
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
//之前是需要Math.random()调用的
System.out.println(random());
System.out.println(PI);
}
}
37.final修饰符
1)修饰类
用final修饰的类不能被继承,没有子类。
例如:我们是无法写一个类去继承String类,然后对String类型扩展的,因为API中已经被String类定义为final的了.
我们也可以定义final修饰的类:
public final class Action{
}
//编译报错
public class Go extends Action{
}
2)修饰方法
用final修饰的方法能被继承,但是不能被子类的重写。
例如:每个类都是Object类的子类,继承了Object中的众多方法,在子类中可以重写toString方法、equals方法等,但是不能重写getClass方法 wait方法等,因为这些方法都是使用fianl修饰的。
我们也可以定义final修饰的方法:
public class Person{
public final void print(){}
}
//编译报错
public class Student extends Person{
public void print(){
}
}
3)修饰变量
用final修饰的变量表示常量,只能被赋一次值.其实使用final修饰的变量也就成了常量了,因为值不会再变了。
修饰局部变量:
例如:
public class Person{
public void print(final int a){
//编译报错,不能再次赋值,传参的时候已经赋过了
a = 1;
}
}
public class Person{
public void print(){
final int a;
a = 1;
//编译报错,不能再次赋值
a = 2;
}
}
修饰成员变量:
非静态成员变量:
public class Person{
private final int a;
}
只有一次机会,可以给此变量a赋值的位置:
声明的同时赋值
匿名代码块中赋值
构造器中赋值(类中出现的所有构造器都要写)
静态成员变量:
public class Person{
private static final int a;
}
只有一次机会,可以给此变量a赋值的位置:
声明的同时赋值
静态代码块中赋值
4)修饰引用变量
main:
final Student s = new Student();
//编译通过
s.setName("tom");
s.setName("zs");
//编译报错,不能修改引用s指向的内存地址
s = new Student();
38.abstract修饰符
abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。
1)抽象类和抽象方法的关系
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
2)语法
public abstract class Action{
public abstract void doSomething();
}
public void doSomething(){...}
对于这个普通方法来讲:
"public void doSomething()"这部分是方法的声明
"{...}"这部分是方法的实现,如果大括号中什么都没写,就叫方法的空实现
声明类的同时,加上abstract修饰符就是抽象类
声明方法的时候,加上abstract修饰符,并且去掉方法的大口号,同时结尾加上分号,该方法就是抽象方法。
3)特点及作用
抽象类,不能使用new关键在来创建对象,它是用来让子类继承的。
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
注:子类继承抽象类后,需要实现抽象类中没有实现的抽象方法,否则这个子类也要声明为抽象类。
例如:
public abstract class Action{
public abstract void doSomething();
}
main:
//编译报错,抽象类不能new对象
Action a = new Action();
//子类继承抽象类
public class Eat extends Action{
//实现父类中没有实现的抽象方法
public void doSomething(){
//code
}
}
main:
Action a = new Eat();
a.doSomething();
注:子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。
思考1:抽象类不能new对象,那么抽象类中有没有构造器?
思考2:抽象类和抽象方法意义(为什么要编写抽象类、抽象方法)
有抽象方法的抽象类
无抽象方法的抽象类
39.接口
1)接口和抽象类区别
抽象类也是类,除了可以写抽象方法以及不能直接new对象之外,其他的和普通类没有什么不一样的。
接口已经另一种类型了,和类是有本质的区别的,所有不能用类的标准去衡量接口。
声明类的关键字是class,声明接口的关键字是interface。
抽象类是用来被继承的,java中的类是单继承。
类A继承了抽象类B,那么类A的对象就属于B类型了,可以使用多态
一个父类的引用,可以指向这个父类的任意子类对象
注:继承的关键字是extends
接口是用来被类实现的,java中的接口可以被多实现。
类A实现接口B、C、D、E..,那么类A的对象就属于B、C、D、E等类型了,可以使用多态
一个接口的引用,可以指向这个接口的任意实现类对象
注:实现的关键字是implements
2)接口中的方法都是抽象方法
接口中可以不写任何方法,但如果写方法了,该方法必须是抽象方法
例如:
public interface Action{
public abstract void run();
//默认就是public abstract修饰的
void test();
public void go();
}
3)接口中的变量都是静态常量(public static final修饰)
接口中可以不写任何属性,但如果写属性了,该属性必须是public static final修饰的静态常量。
注:可以直接使用接口名访问其属性。因为是public static修饰的
例如:
public interface Action{
public static final String NAME = "tom";
//默认就是public static final修饰的
int AGE = 20;
}
main:
System.out.println(Action.NAME);
System.out.println(Action.AGE);
4)一个类可以实现多个接口
例如:
public class Student implements A,B,C,D{
//Student需要实现接口A B C D中所有的抽象方法
//否则Student类就要声明为抽象类,因为有抽象方法没实现
}
main:
A s1 = new Student();
B s2 = new Student();
C s3 = new Student();
D s4 = new Student();
注:
s1只能调用接口A中声明的方法以及Object中的方法
s2只能调用接口B中声明的方法以及Object中的方法
s3只能调用接口C中声明的方法以及Object中的方法
s4只能调用接口D中声明的方法以及Object中的方法
注:必要时可以类型强制转换
例如:
接口A 中有test()
接口B 中有run()
A s1 = new Student();
s1.test();
B s2 = new Student();
s2.run();
if(s1 instanceof B){
((B)s1).run();
}
6)一个接口可以继承多个父接口
public interface A{
public void testA();
}
public interface B{
public void testB();
}
//接口C把接口A B中的方法都继承过来了
public interface C extends A,B{
public void testC();
}
//Student相当于实现了A B C三个接口,需要实现所有的抽象方法
//Student的对象也就同时属于A类型 B类型 C类型
public class Student implements C{
public viod testA(){}
public viod testB(){}
public viod testC(){}
}
main:
C o = new Student();
System.out.println(o instanceof A);//true
System.out.println(o instanceof B);//true
System.out.println(o instanceof C);//true
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
//编译报错
System.out.println(o instanceof String);
注:
System.out.println(o instanceof X);
如果o是一个接口类型声明的变量,那么只要X不是一个final修饰的类,该代码就能通过编译,至于其结果是不是true,就要看变量o指向的对象的实际类型,是不是X的子类或者实现类了。
注:一个引用所指向的对象,是有可能实现任何一个接口的。(java中的多实现)
40.访问控制
public protected default private是java中的访问控制修饰符.
注:这里的default的意思是什么都不写
例如:
public String name;
protected String name;
//default就表示这种情况
String name;
private String name;
1)修饰类
1.普通类
只能使用public和default来修饰源文件中编写的java类
public表示其他任何地方都有权限访问这个类
default表示只有和当前类在同一个包的类才有权限访问
例如: Test.java中有俩个类
----------------------
public class Test{
private class A{}
class B{}
protected class C{}
public class D{}
}
class Action{}
//编译报错
protected class Action2{}
private class Action3{}
----------------------
2.内部类
四个修饰符可以修饰特定的内部类
例如:
//四个内部类
public class Test{
private class A{}
class B{}
protected class C{}
public class D{}
}
2)修饰属性和方法
四个修饰都可以修饰类中的属性和方法,那么就以修饰属性为例来说明.(效果和修饰方法是一样的)
public class Person{
public String pubStr = "pubStr";
protected String proStr = "proStr";
String defStr = "defStr";
private String priStr = "priStr";
}
从四个地方测试这些属性的可见性:
类中 同包类中 子类中 不同包类中
public Y Y Y Y
protected Y Y Y N
default Y Y N N
private Y N N N
注:这里的子类中默认指的是不同包下面的子类
41.内部类
内部类不是在一个java源文件中编写俩个平行的俩个类,而是在一个类的内部再定义另外一个类。
我们可以把外边的类称为外部类,在其内部编写的类称为内部类。
内部类分为四种,成员内部类、静态内部类、局部内部类、匿名内部类
1)成员内部类(实例内部类、非静态内部类)
注:成员内部类中不能写静态属性和方法
public class InstanceOutterClass{
private String name;
private static int age;
public void say(){}
public static void go(){}
public class InstanceInnerClass{}
}
2)静态内部类
注:静态内部类中可以写静态属性和方法
public class StaticOutterClass{
private String name;
private static int num;
public void say(){}
public static void go(){}
public static class StaticInnerClass{}
}
3)局部内部类
//局部内部类是在一个方法内部声明的一个类
//局部内部类中可以访问外部类的成员变量及方法
//局部内部类中如果要访问该内部类所在方法中的局部变量,那么这个局部变量就必须是final修饰的
public class LocalOutterClass{
private String name;
private static int age;
public void say(){}
public static void go(){}
public void test(final int j){
final int i = 10;
class LocalInnerClass{
private String name;
private int age;
public void say(){
System.out.println(name);
System.out.println(this.name);
System.out.println(LocalInnerClass.this.name);
System.out.println(LocalOutterClass.this.name);
System.out.println(LocalOutterClass.age);
LocalOutterClass.this.say();
LocalOutterClass.go();
System.out.println(i);
System.out.println(j);
}
}
LocalInnerClass lic = new LocalInnerClass();
lic.say();
}
}
4)匿名内部类
//匿名内部类是最常用的一种内部类
1)匿名内部类需要依托于其他类或者接口来创建
如果依托的是类,那么创建出来的匿名内部类就默认是这个类的子类
如果依托的是接口,那么创建出来的匿名内部类就默认是这个接口的实现类。
2)匿名内部类的声明必须是在使用new关键字的时候
匿名内部类的声明及创建对象必须一气呵成,并且之后能反复使用,因为没有名字。
例如:
A是一个类(普通类、抽象类都可以)
依托于A类创建一个匿名内部类对象
main:
A a = new A(){
//实现A中的抽象方法
//或者重写A中的普通方法
};
注:这个大括号里面其实就是这个内部类的代码,只不过是声明该内部类的同时就是要new创建了其对象,并且不能反复使用,因为没有名字。
例如:
B是一个接口
依托于B接口创建一个匿名内部类对象
B b = new B(){
//实现B中的抽象方法
};
3)匿名内部类除了依托的类或接口之外,不能指定继承或者实现其他类或接口,同时也不能被其他类所继承,因为没有名字。
4)匿名内部中,我们不能写出其构造器,因为没有名字。
5)匿名内部中,除了重写上面的方法外,一般不会再写其他独有的方法,因为从外部不能直接调用到。(间接是调用到的)
public interface Work{
void doWork();
}
public class AnonymousOutterClass{
private String name;
private static int age;
public void say(){}
public static void go(){}
public void test(){
final int i = 90;
Work w = new Work(){
public void doWork(){
System.out.println(AnonymousOutterClass.this.name);
System.out.println(AnonymousOutterClass.age);
AnonymousOutterClass.this.say();
AnonymousOutterClass.go();
System.out.println(i);
}
};
w.doWork();
}
}
42.包装类(Wrapper)
在java中,有八种基本的数据类,这八种类型所表示的数据只是一些简单的数值(8/16/32/64位的数字),它们都不是对象,所以在API又针对这八种类型提供了对于的类类型,也就是包装类型,它们分别是:
Primitive-Type Wrapper-Class
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character
1)这些对应的包装类型都是java.lang下的类
在代码中可以直接使用而不需要导入.
2)每种包装类中都定义属性和方法供其对象使用
这是从基本类型变为包装类型最明显的区别,现在指向的是对象了,可以访问对象中的属性和调用对象中的方法了,之前只是一个简单的数值,没有任何属性和方法。
例如:
//使用变量i不能访问任何属性和访问
//因为变量i没有指向对象,也不能指向对象
int i = 1;
//查看API可知Integer类中的构造器如果使用
//使用变量i可以访问Integer类中定义的属性和方法
//因为变量i指向的是对象,这是一个引用类型的变量
Integer i = new Integer(1);
或者
Integer i = new Integer("1");
注:包装类中的属性和方法大都是静态的,可以使用类名直接访问。(也有非静态的方法,就需要使用对象调用了)
例如:
main:
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);
System.out.println(Integer.toBinaryString(100));
3)JDK1.5增加了自动拆箱装箱的功能(低版本JDK中编译报错)
注:针对八种基本类型及其包装类型,这里以int和Integer为例
//自动装箱,把字面值1包装成Integer类型对象
Integer i1 = 1;
//自动拆箱,把Integer对象转变为一个简单的int类型值
int i2 = new Integer(1);
注:
//编译通过
Integer a = 1;
//编译报错
//1可以自动装箱为Integer类型对象
//但是Integer和Long没有任何关系
Long b = 1;
//因为int是32位 long是64位
int --可以自动转换为--> long
//因为Integer和Long没有子父类关系
Integer --不能转换为--> Long
43.==和equals方法的区别
1)基本类型变量的使用
基本类型变量指向的不是对象,不能调用方法,所以只能使用==进行比较,并且比较的是基本类型变量的简单数值是否相等。
2)引用类型变量的使用
引用类型变量指向的是对象,所以既可以使用==进行比较,也可以使用equals进行比较
区别在于:
equals是Object中的方法,每个对象都可以调用和其他对象比较是否相等,默认比较的是俩个引用所指向的对象内存地址值(和==号的作用一样),但是也可以在类中对equals方法进行重写,按照自己的逻辑来比较俩个对象。
==是java中的基本的操作符,是不能重写的,俩个引用使用==进行比较,那么比较的是引用所指向的内存地址值是否相同。
44.toString方法hashCode方法
toString和hashCode都是Object类中的方法,所以每个对象都可以直接调用。
hashCode方法,返回该对象的哈希码值,Object中的实现一般是通过将该对象的内部地址转换成一个整数。
toString方法,返回该对象的字符串表示。
其形式为:
类的全限定名@hashCode方法返回值的十六进制形式
即:
getClass().getName() + "@" + Integer.toHexString(hashCode())
例如:
public class Student{
}
main:
Student s = new Student();
String str1 = s.toString();
String str2 = s.getClass().getName()+"@"+Integer.toHexString(s.hashCode());
System.out.println(str1);
System.out.println(str2);
输出结果:
com.briup.ch06.Student@6084fa6a
com.briup.ch06.Student@6084fa6a
注:我们可以把最后的那个六十进制数字认为是这个对象的内存地址,但是其实并不是真的地址值,而是这个对象的哈希码值,这个哈希码值默认又是通过对象地址值转换过来的一个数字。(如果我们重写了hashCode方法,那这个返回的哈希码值就真的和对象内存地址没有一点关系了)
例如:
Student s = new Student();
//打印引用时,默认调用其所指向对象的toString方法
System.out.println(s);
System.out.println(s.toString());
注:有些时候还是会有一点区别
Student s = null;
//打印null
System.out.println(s);
//运行报错,空指针异常
System.out.println(s.toString());
45.集合
数组的长度是固定的,在很多情况下,一组数据的数目是不固定的.
为了使程序能方便地存储和操纵数目不固定的一组数据,JDK的API中提供了集合,所有集合类都位于java.util包中
与数组不同,集合中不能存放基本类型数据,而只能存放对象的引用。(通常会直接描述为集合中存放的对象)
集合的特点:
1.可存放不同类型的对象(必须是对象)
数组只能存放同一类型数据,但是可以存放基本类型数据
2.集合的长度可以自动增加
数组的长度一旦确定,就不能再改变
3.集合对象中有众多方法可以直接调用进行元素(数据)操作
数组对象中没有方法可以对数据进行操作
4.java.util包中的辅助工具类Colletions,也能对集合中的元素进行操作
java.util包中的辅助工具类Arrays,是用来对数组中的元素进行操作的。
1)Iterable接口
实现这个接口的对象,可以使用"foreach"语句对其进行循环遍历.
并且该接口中提供了一个方法可以返回一个迭代器对象,迭代器对象在这里的作用就是循环遍历集合中的每一个元素。
public Iterator iterator();
2)Collection接口
Collection接口继承了Iterable接口。
Collection类型集合体系结构中的根接口,JDK中不提供此接口的任何直接实现,而是提供更具体的子接口(List和Set)。
Collection接口中提供了很多集合中通用的方法。
例如:
add 方法
remove 方法
clear 方法
size 方法
....
注:JDK5以上的自动装箱拆箱
3)Collection类型集合的遍历
1.通用方式: 使用集合中提供的迭代器
Collection c = new ..;(List或Set类型都可以)
//调用add方法存数据
Iterator iterator = c.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
2.List集合的特有方式:get方法通过下标访问元素
(注:Set集合不能这样使用)
List list = new ArrayList();
//调用add方法存数据
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
3.foreach循环(增强for循环)
注:JDK5.0版本及以上可用
Collection c = new ..;(List或Set类型都可以)
//调用add方法存数据
//自动遍历集合c中的元素,并且每次使用变量o来接收
for(Object o:c){
System.out.println(o);
}
注:foreach循环也可以遍历数组
4)List接口和Set接口
List和Set都是Collection接口的子接口,但它们各自的特点不同。
List类型集合特点:集合中的元素有序且可重复
Set类型集合特点 :集合中的元素不可重复,有没有序要看Set接口具体的实现类是谁。
注:有序指的是元素放到集合中的顺序和循环遍历出来的顺序一致
List接口常见的实现类有:ArrayList、LinkedList、Vector等
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构
2.对于数据的随机访问,ArrayList效率优于LinkedList,因为LinkedList要移动指针
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据
4.Vector是线程安全的集合,但是速度慢
5.查看API可知,List接口中是这些集合中共有的方法,而且每个集合类中又有一些自己独有的方法。
Set接口常见的实现类有:HashSet、LinkedHashSet
HashSet集合中元素的特点 :无序不可重复
LinkedHashSet集合中元素的特点:有序不可重复
思考:set集合中元素的不可重复是怎么实现的?
5)SortedSet接口和TreeSet类
SortedSet接口是Set接口的子接口,除了拥有Set集合的一些基本特点之外,还提供了排序的功能。
TreeSet类就是SortedSet接口的实现类
6)TreeSet类的排序功能
注:TreeSet排序永远都是从小到大排,但是谁大谁小是我们的方法说了算的
1.自然排序
核心:让元素自身具备比较性,需要实现Comparable接口,重写其compareTo方法,比较出大小后即可进行排序
java.lang.Comparable接口
例如:
public class Student implements Comparable{
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public int compareTo(Object o) {
Student s = (Student)o;
if(this.id<s.id){
return -1;
}
else if(this.id>s.id){
return 1;
}
return 0;
}
}
注:
s1.compareTo(s2);
返回正数说明s1大
返回负数说明s1小
返回0说明s1和s2相等
2.比较器排序(定制排序、客户化排序)
核心:使用比较器进行元素之间的比较,比较出大小后即可进行排序。
java.util.Comparator接口
例如:
main:
Set set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
return (int)(s1.getId()-s2.getId());
}
});
注:比较器排序的优先级比自然排序的高
思考:在TreeSet中整数默认是从小到大排序,字符是默认是按a-z的顺序,那么怎么能让它们的顺序是倒过来的?
7)Collection类型集合与数组之间的转换
Collection接口中定义了俩个方法,可以将当前集合转换为数组:
Object[] toArray() 返回包含集合中所有元素的数组
例如:
Collection c = new ...;(List或者Set的实现类都可以)
Object[] array = c.toArray();
System.out.println(Arrays.toString(array));
<T> T[] toArray(T[] a) 可指定返回数组的类型
例如:
Collection c = new ...;(List或者Set的实现类都可以)
String[] str = new String[set.size()];
//元素自动存放到数组str中了
set.toArray(str);
System.out.println(Arrays.toString(str));
java.util.Arrays中定义了一个方法,可以将数组转换为List类型集合
public static <T> List<T> asList(T... a)
例如:
Integer[] a = new Integer[4];
a[0] = 12;
a[1] = 13;
a[2] = 14;
a[3] = 15;
List list = Arrays.asList(a);
for(Object o:list){
System.out.println(o);
}
输出结果:
12
13
14
15
例如:
int[] a = new int[4];
a[0] = 12;
a[1] = 13;
a[2] = 14;
a[3] = 15;
List list = Arrays.asList(a);
for(Object o:list){
System.out.println(o);
}
输出结果:
[I@3cf5b814
注意基本类型数组和引用类型数组在这里的区别
8)Collection类型集合的工具类:Collections
注意Collection和Collections的区别:
一个是接口一个是类
java.util.Collections类是一个工具类,类中提供了很多的静态方法对Collection类型的集合进行操作
fill方法,使用指定元素替换指定列表中的所有元素。
例如:
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
Collections.fill(list, 20);
for(Object o:list){
System.out.println(o);
}
max方法,根据元素的自然顺序,返回给定集合的最大元素
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
System.out.println(Collections.max(list));
min方法,根据元素的自然顺序,返回给定集合的最小元素
reverse方法,反转集合中的元素
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
Collections.reverse(list);
for(Object o:list){
System.out.println(o);
}
sort方法,根据元素的自然顺序,对指定列表按升序进行排序
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
Collections.sort(list);
for(Object o:list){
System.out.println(o);
}
shuffle方法,使用默认随机源对指定列表进行置换
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
Collections.shuffle(list);
for(Object o:list){
System.out.println(o);
}
还有一系列的方法,可以把集合转为相应的线程安全的集合对象
synchronizedList
synchronizedSet
synchronizedMap
9)Map接口
Map类型的集合与Collection类型的集合不同,Map类型的集合存储数据的时候,要使用Key-Value的形式(键值对),且Key值不能重复,否则会覆盖原来的键值对
1)Map接口中的一些方法
put 方法
get 方法
clear 方法
containsKey 方法
containsValue 方法
isEmpty 方法
remove 方法
size 方法
10)Map接口的常用实现类
HashMap类和Hashtable类
1.HashMap是线程不安全的,Hashtable是线程安全的
2.HashMap允许key值或者value值为null,但是Hashtable中的key值或者value值都不允许为null,否则报错.
11)Map类型集合的遍历
1.使用keySet方法,可以返回该Map集合中的所有key值的set类型集合
例如:
Map map = new HashMap();
//调用put方法存值
for(Object key:map.keySet()){
System.out.println(key+" : "+map.get(key));
}
2.使用values方法,可以返回该Map集合中所有value值的Collection类型集合
例如:
Map map = new HashMap();
//调用put方法存值
for(Object value:map.values()){
System.out.println(value);
}
3.使用entrySet方法,可以返回该Map集合中,包含所有Entry类型对象的Set集合
Set<Map.Entry<K,V>> entrySet();
注:Entry是声明Map接口中的内部接口(看API或源码可知),一个Entry类型对象就表示Map中的一组键值对(K-V)
例如:
Map map = new HashMap();
//调用put方法存值
Set entrySet = map.entrySet();
for(Object obj:entrySet){
Entry entry = (Entry)obj;
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
注意这里导入Entry接口的形式。
12)SortedMap接口和TreeMap类
SortedMap接口是Map的子接口,其进一步提供对于键的排序功能。
TreeMap类就是SortedMap接口的实现类。
TreeMap可以对key值进行自然排序或者比较器排序,其用法和TreeSet是一致的。
46.泛型(Generics)
1)泛型的定义
泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type),也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型。
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
泛型的类型将来传入是只能是引用类型的,不能是基本类型的。
例如:
//编译通过
List<String>
List<Action>
List<Integer>
List<Integer[]>
List<int[]>
//编译报错
List<int>
2)java中的泛型只是在编辑期间起作用的,在运行时会把泛型信息擦除的。
只是在编译期间启动类型安全检查的作用,运行时不起作用。
例如:List<String> list = new ArrayList<String>();
虽然指定了泛型为String,但是在运行时候依然是可以向该list中存放其他类型数据的。(比如使用反射的方法)
//类中写了如下俩个方法编译会报错,因为编译后这俩个是相同的俩个方法
public void test(List<String> list){
}
public void test(List<Long> list){
}
3)泛型类
一个泛型类就是具有一个或多个类型变量(把类型参数化)的类。
定义一个泛型类十分简单,只需要在类名后面加上<>,再在里面加上类型参数.
注:类型变量使用大写形式,且比较短,这是很常见的。在JDK中,使用变量E表示集合的元素类型,K和V分别表示关键字与值的类型。(需要时还可以用其他的字母,也可以是一个或多个字母)
例如:
//这里的T是根据将来用户使用Point类的时候所传的类型来定
//Point<Double> p = new Point<Double>();
public class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
例如:
//这里的T和S是根据将来用户使用Point类的时候所传的类型来定
//Point<String,Integer> p = new Point<String,Integer>();
public class Point<T,S> {
private T x;
private S y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public S getY() {
return y;
}
public void setY(S y) {
this.y = y;
}
}
4)泛型接口
一个泛型接口就是具有一个或多个类型变量的接口。
例如:
public interface Action<T,U>{
void doSomeThing(T t,U u);
}
public class ActionTest implements Action<String,Date>{
public void doSomeThing(String str,Date date) {
System.out.println(str);
System.out.println(date);
}
}
5)泛型方法
在方法上直接声明泛型,该方法就是泛型方法
例如:
public class Test{
public <T> void run1(T t){
}
public <T> T run2(T t){
return t;
}
public <T,S> void run3(T t,S s){
}
}
6)通配符
泛型增加了java中类型的复杂性,例如List<String>、List<Integer>、List<Object>等这些都是不同的类型。
//编译报错
//虽然 List list = new ArrayList(); 是正确的
//虽然 Object是String的父类型
//但是下面代码编译时报错的,因为使用了泛型
List<Object> list = new ArrayList<String>();
泛型中?是通配符,它可以表示所有泛型的父类型:
List<?> list = new ArrayList<任意>();
例如:
//这时list可以指向任何泛型的List类型集合对象
public void test(List<?> list){
//编译报错,因为我们并不知道?到底代表什么类型
list.add(1);
//编译通过
for(Object o:list){
System.out.println(o);
}
}
注:通配符?只能用在泛型变量声明的时候。
7)泛型中的entends和super关键字
在泛型中可以使用entends和super关键字来表示将来用户所传的泛型参数的上限和下限。
entends关键字的使用:
例如: 在声明泛型类和泛型接口时使用extends
public class Point<T extends Number> {
private T x;
private T y;
}
public class Point<T extends Number,S> {
private T x;
private S y;
}
public interface Action<T extends Person> {
public void doSomeThing(T t);
}
例如:在声明泛型方法时使用extends
public <T extends Action> void run(T t){
}
例如:在声明泛型类型变量时使用extends
List<? extends Number> list = new ArrayList<Integer>();
List<? extends Number> list = new ArrayList<Long>();
List<? extends Number> list = new ArrayList<Double>();
//编译报错
List<? extends Number> list = new ArrayList<String>();
例如:
public void test(List<? extends Number> list){
}
super关键字的使用:
例如:
//编译报错
//声明泛型类或泛型接口时不能使用super
public class Point<T super Number> {
}
public interface Action<T super Number> {
}
//编译报错
//声明泛型方法时不能使用super
public <T super Action> void run2(T t){
}
例如:在声明泛型类型变量时使用super
//编译通过
List<? super Number> list1 = new ArrayList<Object>();
List<? super Student> list2 = new ArrayList<Object>();
List<? super Student> list3 = new ArrayList<Person>();
//编译通过
public void test(List<? super Number> list){
}
//编译通过
public void test(List<? super Student> list){
}
8)泛型中的&
使用&可以给泛型加多个限定
例如:
public class A{
}
public inerface B{
}
//不管该限定是类还是接口,统一都使用关键字extends
//可以使用&符号给出多个限定
//如果限定既有接口也有类,那么类必须只有一个,并且放在首位置
public class Point<T extends A&B> {
}
class Sub extends A implements B{}
main:
//编译报错
Point<A> p = new Point<A>();
Point<B> p = new Point<B>();
//编译通过
Point<Sub> p = new Point<Sub>();
9)观察API中Map接口及其方法的声明
public interface Map<K,V>{
public V get(Object key);
public Set<Map.Entry<K,V>> entrySet();
public Set<K> keySet();
public void putAll(Map<? extends K,? extends V> m);
..
}
47.枚举类型(enum)
JDK1.5增加了枚举类型,可以使用enum来定义
例如:
public enum Gender{
MALE,FEMALE;
}
其中每一个枚举元素都是该枚举类型的一个实例,并且默认是用public static final修饰的
1)枚举类型和类的关系
把Gender.class反编译后显示如下:
public final class com.briup.test.Gender extends java.lang.Enum<com.briup.test.Gender> {
public static final com.briup.test.Gender MALE;
public static final com.briup.test.Gender FEMALE;
private static final com.briup.test.Gender[] ENUM$VALUES;
static {};
private com.briup.test.Gender(java.lang.String, int);
public static com.briup.test.Gender[] values();
public static com.briup.test.Gender valueOf(java.lang.String);
}
说明枚举类型本质还是一个类,而且默认就是fianl修饰以及默认继承父类java.lang.Enum。
同时构造器是自动生成的且是私有的,表示不可再创建对象。(使用反射也不行)
private Gender(String name,int ordinal)
name : 枚举对象的名字
ordinal: 枚举元素的编号,默认从0开始
我们在枚举中所写的MALE和FEMALE其实就是Gender类型的俩个固定的对象。(public static final)
注:enum是java中的一个关键字,Enum是java中的一个类
2)获取枚举对象
1.获得枚举对象,三种方式
枚举对象在我们使用之前就已经全部创建好了,并且不会增加或减少,和声明的时候保持一致。
例如:
Gender g1 = Gender.FEMALE;
Gender g2 = Gender.MALE;
Gender g3 = Gender.valueOf("FEMALE");
Gender g4 = Gender.valueOf("MALE");
System.out.println(g1 == g3);//true
System.out.println(g2 == g4);//true
Gender g5 = Enum.valueOf(Gender.class, "FEMALE");
Gender g6 = Enum.valueOf(Gender.class, "MALE");
System.out.println(g1 == g5);//true
System.out.println(g2 == g6);//true
2.获得一个枚举类型的所有对象
Gender[] values = Gender.values();
for(Gender g:values){
System.out.println(g);
}
3)枚举类型的方法
枚举对象默认只能调用到父类Enum中的方法以及Object中的方法
如果想调用自己的方法,也可以在枚举类型中定义出属于自己的方法.
注:枚举也是类,只是一种特殊的类而且,所以在枚举类型中可以定义很多东西,像之前在类中写代码是一样的
例如:
public enum Gender {
MALE,FEMALE;
public void say(String name){
System.out.println("hello "+name+","+this);
}
public static void main(String[] args) {
Gender g1 = Gender.FEMALE;
Gender g2 = Gender.MALE;
g1.say("tom");
g2.say("tom");
}
}
也可以定义静态方法
例如:
public enum Gender {
MALE,FEMALE;
public void say(String name){
System.out.println("hello "+name+","+this);
}
public static void run(){
System.out.println("running..");
}
public static void main(String[] args) {
Gender.run();
}
}
4)枚举类型的属性
枚举类型中可以定义属于自己的属性
例如:
public enum Gender {
MALE,FEMALE;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//重写toString方法
public String toString() {
return this.getName();
}
public static void main(String[] args) {
Gender g = Gender.FEMALE;
g.setName("女");
System.out.println(g);
}
}
5)枚举类型的构造器
在枚举类型中定义其构造器,但是必须是私有的,默认也是私有的
例如:
public enum Gender {
//默认调用无参构造器
MALE,FEMALE;
private String name;
//有参构造器
private Gender(String name){
this.name = name;
}
//无参构造器
Gender(){
}
//重写toString方法
public String toString() {
return this.name;
}
}
构造器的调用:
在声明枚举对象的时候,其实就是在调用构造器,默认是隐式调用其无参构造器,也可以显式调用有参构造器
例如:
public enum Gender {
MALE("男"),FEMALE("女");
private String name;
private Gender(String name){
this.name = name;
}
Gender(){
this("");
}
public String toString() {
return this.name;
}
public static void main(String[] args) {
Gender g = Gender.FEMALE;
System.out.println(g);
}
}
6)枚举类型的抽象方法
枚举类型中可以编写抽象方法,但是这时候其每个对象都必须去实现这个抽象方法,否则编译报错。
形式上很像匿名内部类对象
例如:
public enum Gender {
MALE(){
public void run() {
System.out.println("MALE run");
}
},FEMALE(){
public void run() {
System.out.println("FEMALE run");
}
};
public abstract void run();
}
7)枚举类型可以实现接口
枚举类型不能指定继承其他父类,但是可以实现其他接口
例如:枚举类型中把该接口中的所有方法实现了
public interface Action{
void doSomeThing();
}
public enum Gender implements Action{
MALE,FEMALE;
public void doSomeThing() {
System.out.println("Gender go..");
}
}
例如:或者在每个对象中分别实现接口中所有方法也可以
public interface Action{
void doSomeThing();
}
public enum Gender implements Action{
MALE(){
public void doSomeThing() {
System.out.println("MALE go...");
}
},FEMALE(){
public void doSomeThing() {
System.out.println("FEMALE go...");
}
};
}
48.反射(Reflection)
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
对于任意一个对象,都能够调用它的任意一个方法和属性
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
即:在"运行时",通过反射机制可以动态获得到和该类型相关的各种信息。
1)Class类型 java.lang.Class类
Class是对java中所有类型的抽象。即一个Class类型对象可以表示出java中任意一种类型。
每种类型在加载到内存后,内存中都会生产一个与之对应的Class类型对象(有且只有一个),用来表示该类型。
每个类型都有且只有一个Class类型对象与之对应,通过这个Class类型对象就可以获得到该类型中的各种信息。
Class类是Java反射的入口.
1.表示基本类型
Class c = int.class;
System.out.println(c.isPrimitive());//true
System.out.println(c.getName());//int
注:其他基本类型的情况类似
2.表示类类型
注:s.getClass()方法返回的是变量s所指向对象的实现类型的Class对象。
Student s = new Student();
Class c1 = s.getClass();
Class c2 = Student.class;
System.out.println(c1 == c2);//true
//p指向的对象实际类型是Student
Person p = new Student();
Class c1 = p.getClass();//c1表示Student类型
Class c2 = Person.class;//c2表示Person类型
System.out.println(c1 == c2);//false
3.表示接口类型
Action a = new Student();
Class c1 = a.getClass();//c1表示Student类型
Class c2 = Action.class;//c2表示Action类型
System.out.println(c1 == c2);//false
System.out.println(c2.isInterface());//true
4.表示数组类型
int[] a = new int[4];
Class c1 = a.getClass();
Class c2 = int[].class;
System.out.println(c1 == c2);//true
System.out.println(c1.isArray());//true
Class c3 = c1.getComponentType();//c3表示该数组是使用什么类型声明出来的
System.out.println(c3.getName());//int
Student[] a = new Student[4];
Class c1 = a.getClass();
Class c2 = Student[].class;
System.out.println(c1 == c2);//true
System.out.println(c1.isArray());//true
Class c3 = c1.getComponentType();//c3表示该数组是使用什么类型声明出来的
System.out.println(c3.getName());//com.briup.test.Student
2)获得一个类类型的Class对象的三种方式
以上几种情况中,所以最多的还是类类型的Class对象
1.使用对象调用getClass方法获得
getClass是Object中的final修饰的方法,每个对象都可以调用而且不能重写
Student s = new Student();
Class c = s.getClass();
2.使用类名获得
Class c = Student.class;
3.使用Class类中的forName方法获得
//这种方法很灵活,只需一个String类型参数即可
//而String类型的数据改变起来很容易
//注意该方法是会抛出异常的
Class c = Class.forName("com.briup.test.Student");
注:以上三种方法获得的同一个对象(==比较),因为每个类型内存都有且只有一个Class类型对象
4)反射机制中的常见类的含义
java.lang包下:
Class 类 对java中所有类型抽象而得来的
package类 对java中所有包抽象而得来的
java.lang.reflect包下:
Modifier 类 对java中所有修饰符抽象而得来的
Field 类 对java中所有属性抽象而得来的
Method 类 对java中所有方法抽象而得来的
Constructor 类 对java中所有构造器抽象而得来的
Array 类 提供了对数组对象的动态访问
ParameterizedType接口 在反射中表示参数化类型
例如:List<String> Point<Long,Long>等这种带泛型的类型
5)使用Class类型对象获得类中的信息
1.获得该类所处的包的信息
Student s = new Student();
Class c = s.getClass();
System.out.println(c.getPackage().getName());
2.获得该类的修饰符信息
//每个修饰符对应一个int值
//如果有多个修饰符则int值相加
Student s = new Student();
Class c = s.getClass();
System.out.println(c.getModifiers());
System.out.println(Modifier.PUBLIC);
System.out.println(Modifier.FINAL);
3.获得该类的名字
Student s = new Student();
Class c = s.getClass();
System.out.println(c.getName());
4.获得该类的父类的Class对象
Student s = new Student();
Class c = s.getClass();
//superclass表示其父类型的Class对象
Class superclass = c.getSuperclass();
System.out.println(superclass.getName());
例如:
Class c = Object.class;
Class superclass = c.getSuperclass();
System.out.println(superclass.getName());
//运行报错,因为Object没有父类
例如:
//判断c1是不是c2的子类型
//判断c3是不是c2的子类型
Class c1 = Student.class;
Class c2 = Person.class;
Class c3 = String.class
System.out.println(c2.isAssignableFrom(c1));//true
System.out.println(c2.isAssignableFrom(c3));//false
5.获得该类所实现的接口类型的Class对象
Student s = new Student();
Class c = s.getClass();
Class[] interfaces = c.getInterfaces();
for(Class clazz:interfaces){
System.out.println(clazz.getName());
}
例如:
//判断c1是不是c2的实现类
//判断c3是不是c2的实现类
Class c1 = Student.class;
Class c2 = Action.class;
Class c3 = String.class
System.out.println(c2.isAssignableFrom(c1));//true
System.out.println(c2.isAssignableFrom(c3));//false
6.获得该类中所有的属性
Student s = new Student();
Class c = s.getClass();
Field[] declaredFields = c.getDeclaredFields();
for(Field f:declaredFields){
System.out.println(f.getModifiers());
System.out.println(f.getType().getName());
System.out.println(f.getName());
}
注:
getDeclaredFields()方法返回类中声明的属性,包括私有的
getFields()方法只返回类中public修饰的属性,包括继承的
例如:
//获得某个指定的属性(也包括私有属性)
Student s = new Student();
Class c = s.getClass();
Field f = c.getDeclaredField("score");
System.out.println(f.getModifiers());
System.out.println(f.getType().getName());
System.out.println(f.getName());
7.获得该类中所有的方法
Student s = new Student();
Class c = s.getClass();
Method[] declaredMethods = c.getDeclaredMethods();
for(Method m:declaredMethods){
System.out.println(m.getModifiers());
System.out.println(m.getReturnType().getName());
System.out.println(m.getName());
System.out.println(Arrays.toString(m.getParameterTypes()));
System.out.println(Arrays.toString(m.getExceptionTypes()));
}
注:
getDeclaredMethods()方法返回类中声明的方法,包括私有的
getMethods()方法只返回类中public修饰的方法,包括继承的
例如:
//获得某个指定的方法(也包括私有方法)
Student s = new Student();
Class c = s.getClass();
Method m = c.getDeclaredMethod("print");
System.out.println(m.getModifiers());
System.out.println(m.getReturnType().getName());
System.out.println(m.getName());
System.out.println(Arrays.toString(m.getParameterTypes()));
System.out.println(Arrays.toString(m.getExceptionTypes()));
8.获得该类中所有的构造器
Student s = new Student();
Class c = s.getClass();
Constructor[] declaredConstructors = c.getDeclaredConstructors();
for(Constructor con:declaredConstructors){
System.out.println(con.getModifiers());
System.out.println(con.getName());
System.out.println(Arrays.toString(con.getParameterTypes()));
System.out.println(Arrays.toString(con.getExceptionTypes()));
}
注:
getDeclaredConstructors()方法返回类中所有构造器
getConstructors()方法只返回类中public修饰的构造器
例如:
//获得某个指定的构造器(也包括私有构造器)
Student s = new Student();
Class c = s.getClass();
Constructor con = c.getDeclaredConstructor(double.class);
System.out.println(con.getModifiers());
System.out.println(con.getName());
System.out.println(Arrays.toString(con.getParameterTypes()));
System.out.println(Arrays.toString(con.getExceptionTypes()));
9.获得父类型中的泛型类型
因为泛型类的泛型参数在编译期会被擦除,所以我们不能再运行期间直接拿到该泛型的实际类型,但是可以通过子类的Class对象来获取父类的泛型类型。
例如: 不通过子类不能获得泛型实际类型
public class GenericTest<T,S>{
public T name;
public S say(T t,S s){
return s;
}
}
main:
GenericTest<String,Integer> t = new GenericTest<String,Integer>();
Class c = t.getClass();
Field field = c.getDeclaredField("name");
System.out.println(field.getType());
System.out.println(field.getGenericType());
//输出结果:
class java.lang.Object
T
System.out.println("-------------------------");
Method method = c.getMethod("say", Object.class,Object.class);
System.out.println(method.getReturnType());
System.out.println(method.getGenericReturnType());
//输出结果:
class java.lang.Object
S
System.out.println("-------------------------");
System.out.println(Arrays.toString(method.getParameterTypes()));
System.out.println(Arrays.toString(method.getGenericParameterTypes()));
//输出结果:
[class java.lang.Object, class java.lang.Object]
[T, S]
例如: 通过子类可以获得父类中泛型的实际类型
public class GenericTest<T,S>{
public T name;
public S say(T t,S s){
return s;
}
}
public class Sub entends GenericTest<String,Integer>{}
main:
Class c = Sub.class;
//获得父类类型,包含泛型参数信息
Type superType = c.getGenericSuperclass();
//判断父类类型是不是属于ParameterizedType类型
//ParameterizedType表示带泛型的类型
if(superType instanceof ParameterizedType){
//强转,并调用方法获得泛型参数的实例类型
ParameterizedType pt = (ParameterizedType)superType;
Type[] actualTypeArguments = pt.getActualTypeArguments();
//循环遍历,并强转为Class类型,因为Type接口中没有任何方法
for(Type t:actualTypeArguments){
Class clazz = (Class)t;
System.out.println(clazz.getName());
}
}
例如: 通过子类可以获得实现接口中泛型的实际类型
Class c = Sub.class;
Type[] types = c.getGenericInterfaces();
for(Type t:types){
if(t instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType)t;
Type[] actualTypeArguments = pt.getActualTypeArguments();
for(Type type:actualTypeArguments){
Class clazz = (Class)type;
System.out.println(clazz.getName());
}
}
}
10.获得类中的注解
使用反射也可以获得类中的注解.(在之后的内容在来了解)
6)反射中的常用操作
public class Student{
private long id;
private String name;
private static int age;
get/set
public static void say(){
System.out.println("say..");
}
}
1.使用反射的方式调用构造器创建类的对象
默认方式:必须调用无参构造器
Class c = Student.class;
Student s = (Student)c.newInstance();
通用方式:获得构造器对象,并调用该构造器
注:getConstructor方法和newInstance方法的参数都是可变参数
例如:获得无参构造器并调用创建对象
Class c = Student.class;
Constructor constructor = c.getConstructor();
Student o = (Student)constructor.newInstance();
System.out.println(o);
例如:获得有参构造器并调用创建对象
Class c = Student.class;
Constructor constructor = c.getConstructor(long.class,String.class);
Student o = (Student)constructor.newInstance(1L,"tom");
System.out.println(o);
2.使用反射的方式访问对象中的属性
例如:
Student s = new Student();
Class c = s.getClass();
Field[] declaredFields = c.getDeclaredFields();
for(Field f:declaredFields){
//设置私有属性可以被访问
f.setAccessible(true);
//判断属性是否为static,静态属性的访问不需要对象
if(Modifier.isStatic(f.getModifiers())){
System.out.println(f.getName() +" = "+f.get(null));
}else{
System.out.println(f.getName() +" = "+f.get(s));
}
}
注:Field类中的get方法可以获得属性值,set方法可以给属性设置值。
3.使用反射的方式调用对象中的方法
例如:
Student s = new Student();
Class c = s.getClass();
Method m1 = c.getMethod("setName",String.class);
//m1表示Student中的setName方法
//调用对象s中的m1方法,并且传参"tom"
//s.setName("tom");
m1.invoke(s, "tom");
Method m2 = c.getMethod("getName");
String name = (String)m2.invoke(s);
System.out.println("name = "+name);
//调用静态方法 不需要对象
Method m3 = c.getMethod("say");
m3.invoke(null);
4.使用反射的方式动态操作数组
注:
Object[] o1 = new int[1];//编译报错
long[] o2 = new int[1];//编译报错
int[] o3 = new int[1];//编译通过
Object[] o1 = new 任意引用类型[1];//编译通过
例如:
//要求:传任意类型"数组",把数组长度扩大1倍并返回
//注意这里不能收Object[],
//因为Object[] o = new Integer[4];编译通过
//但是Object[] o = new int[4]; 编译报错
//那么使用Object类中就可以接收任意类型的数组了
public static Object arrayCopy(Object obj){
//代码
}
实现:
public static Object arrayCopy(Object obj){
Class c = obj.getClass();
Object newArray = null;
if(c.isArray()){
int len = Array.getLength(obj);
Class<?> type = c.getComponentType();
newArray = Array.newInstance(type, len*2);
for(int i=0;i<len;i++){
Object value = Array.get(obj, i);
Array.set(newArray, i, value);
}
}
return newArray;
}
49.异常概述
在我们日常生活中,有时会出现各种各样的异常,例如:职工小王开车去上班,在正常情况下,小王会准时到达单位。但是天有不测风云,在小王去上班时,可能会遇到一些异常情况,比如小王的车子出了故障,小王只能改为步行.
异常指程序运行中出现的不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。
异常发生在程序运行期间,它影响了正常的程序执行流程
Java通过API中Throwable类的众多子类描述了各种不同的异常。Java中的异常都是对象,都是是Throwable子类的实例。
每种异常类型都代表了一个错误的情况。
例如:
java.lang.ArrayIndexoutofBoundsException类,表示数组的下标在使用中超过了边界
java.lang.ClassCastException类,表示类型转换出现了错误
50.Error和Exception
在Java中,所有的异常都有一个共同的父类Throwable,该类有两个重要的子类:Exception和Error,二者都是Java异常处理的重要子类,各自都包含大量子类。
它们都是java.lang下的类
java.lang.Throwable
java.lang.Error
java.lang.Exception
Error,这个是程序中发生的错误,是程序无法处理的,表示运行应用程序中较严重问题。而且很多错误与代码编写者执行的操作无关,而是表示代码运行时JVM出现了问题。例如,Java虚拟机运行错误(VirtualMachineError),当JVM中内存不足时,将出现 OutOfMemoryError。这些异常发生时,JVM一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应时
如Java虚拟机运行错误(VirtualMachineError)、类定义错误(NoClassDefFoundError)等。这些错误一般是不可查询的,因为它们在应用程序的控制和处理能力之外。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况,因为这是超出程序处理能力的。
Exception,这个是程序中发生的异常,是程序本身可以处理的。
我们进行抛出或捕获的异常就是这里类型及其子类型。
51.异常的抛出和捕获
异常的抛出:
在类中编写方法的时候,这个方法中将来被执行的代码如果有可能出现异常情况,那么就"可以"在方法的参数列表后声明该方法中可能会抛出的异常类型.
例如:
public class Test{
public void run()throws IOException,SQLException{
//..
}
}
1)如果有多个异常类型要抛出,那么需要使用逗号隔开.
2)所声明抛出的异常是该方法执行后"可能"会出现异常类型
3)异常抛了个方法的调用者,谁调用的方法谁就负责处理这些异常
异常的捕获
当我们调用了一个方法,该方法在声明的时候抛出了异常,那么我们作为方法的调用者就必须去处理这些被抛出的异常。
例如:
Class类中的forName方法的声明
public static Class<?> forName(String className)throws ClassNotFoundException
说明该方法在执行的时候有可能抛出ClassNotFoundException类型的异常,表示要加载的类找不到。
我们调用这个方法的时候,就需要对这个抛出的异常进行处理。
第一种方式是继续把这些异常抛出去
例如:
public static void main(String[] args)throws ClassNotFoundException{
Class.forName("test....");
}
在main方法中调用forName方法时候,我们并没有直接处理这个抛出的异常,而是继续把该异常往上抛出,抛给main方法的调用者。
第二种方式是使用try-catch语句块把抛出的异常进行捕获
例如:
public static void main(String[] args) {
try {
Class.forName("test...");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
1)try-catch是尝试着去捕获这些代码抛的异常,如果try语句块中的代码没有抛出异常,那么try-catch是没有任何作用的
2)如果try语句块中的代码抛出了异常,并且抛出的异常还是catch语句要处理的异常或其子类型异常,那么这时就会执行catch语句块中的代码,进行异常出现后的处理。
3)异常对象e常用的方法
e.printStackTrace()
引用出堆栈中出现的异常跟踪信息
e.getMessage()
返回异常的详细字符串信息(如果有的话)
4)不管方法声明中抛出了什么类型的异常,我们一般都是可以再catch中使用Exception类型进行捕获到的,因为Exception是所有异常的父类型。
例如:
try {
Class.forName("test...");
....
..
} catch (Exception e) {
e.printStackTrace();
}
5)如果代码中抛出了多种异常,也可以使用多个catch来分别捕获.当然也可以只使用一个最大的异常Exception
例如:
try {
Class c = Class.forName("test..");
c.getMethod("go");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
52.抛出和捕获对程序的影响
1)如果程序中的某行代码的执行抛出了异常,并且这个异常一种都没有被try-catch处理,那么这个异常最终会抛给JVM,JVM输出异常信息后就自动停止了
例如:
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("hello");
Class.forName("test..");
System.out.println("world");
//....其他代码
}
最终的结果是代码在调用forName方法抛出异常后,JVM处理后就停止了.并没有往下继续执行代码
2)如果使用try-catch语句去处理代码中抛出的异常,那么catch语句块处理完之后,代码还会在catch语句块下面继续执行
例如:
public static void main(String[] args){
System.out.println("hello");
try {
Class.forName("test..");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("world");
//....其他代码
}
最终的结果是catch语句执行完后,代码继续往下正常执行。
3)try-catch语句块虽然能处理完异常后继续让代码往下执行,但是在某些时候也会改变代码的执行流程(默认是从上往下顺序执行)
例如:
public static void main(String[] args){
System.out.println("hello");
try {
Class.forName("test..");
System.out.println("world");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//....其他代码
}
最终的结果是catch语句执行完后,代码执行下面的其他代码,但是上面打印world的语句就跳过去了。
53.finally语句块
由于异常处理有时候会改变程序的正常流程,这会使得某些不管在任何情况下都必须执行的步骤被忽略,从而影响程序的健壮性。
小王开了一家店,在店里上班的正常流程为:打开店门、工作8个小时、关门。异常流程为:小王在工作时突然犯病,因而提前下班
例如:
public void work() {
try {
开门();
工作8个小时();
关门();
} catch(Exception e) {
去医院()
}
}
小王在工作时突然犯病,那么流程会跳转到catch代码块,这意味着关门的操作不会被执行,这样的流程显然是不安全的,必须确保关门的操作在任何情况下都会被执行.
finally代码块能保证特定的操作总是会被执行,它的形式如下:
public void work() {
try {
开门();
工作8个小时();
} catch(Exception e) {
去医院()
} finally {
关门();
}
}
注:即使方法中执行了return语句,finally最后也是会被执行的。
54.编译异常和运行时异常
1)Exception有一个特殊的子类:RuntimeException
2)RuntimeException类型及其子类型都是属于运行时异常
3)其他类型的异常只要不是继承了RuntimeException类的,都属于编译异常
4)编译异常又称checked异常,运行时异常又称unchecked异常
因为编译器在编译期间如果遇到了checked异常,那么是一定会提示我们,让我们去处理的。但是如果遇到了unchecked异常,编译器是不做任何事情的。
常见的运行时异常:unchecked
java.lang.ArithmeticException
算术异常
java.lang.NullPointerException
空指针引用
java.lang.ArrayIndexoutofBoundsException
数组越界
java.lang.ClassCastException
强制类型转换异常
java.lang.NumberFormatException
数据格式异常
java.lang.NegativeArraySizeException
数组长度为负数异常
常见的编译异常:checked
编译器提示你需要处理的都为编译异常
java.lang.ClassNotFoundException
java.lang.DataFormatException
java.lang.NoSuchMethodException
java.io.IOException
java.sql.SQLException
55.自定义异常
在需要的情况下,可以通过扩展Exception类或RuntimeException类来创建自定义的异常(一般是扩展Exception类)。异常类包含了和异常相关的信息,这有助于负责捕获异常的catch代码块,正确地分析并处理异常
例如:我们任务在系统中用户要登录的账号和密码不匹配就是一种异常情况,但是JDK中并没有定义这种异常,所以我们可以进行自定义。
例如: 只需继承Exception即可.一般还会加入和父类中匹配的构造器
public class UserPasswordException extends Exception{
public UserPasswordException() {
super();
}
public UserPasswordException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public UserPasswordException(String message, Throwable cause) {
super(message, cause);
}
public UserPasswordException(String message) {
super(message);
}
public UserPasswordException(Throwable cause) {
super(cause);
}
}
例如:
注:异常是可以在代码中主动抛出的
public void login(String password)throws UserPasswordException{
if("123".equals(password)){
throw new UserPasswordException("密码错误");
}
}
将来login方法的调用者负责处理这个异常
56.断言
1)断言使用
在JDK1.4中,Java语言引入一个新的关键字: assert。 该关键字有两种形式:
assert 条件
以及
assert 条件:值或表达式
这两种形式都会对条件进行评估,如果结果为false则抛出AssertionError。
在第二种形式中,值或表达式的值会传入AssertionError的
构造器并转成一个消息字符串,成为要显示的错误信息
例如:
要断言x不是负数,只需要使用如下简单的语句:
assert x >= 0;
或者你可以将x的值传递给AssertionError对象,从而可以在报错时候显示:
assert x >= 0 : x;
或者
assert x >= 0 : "x的值不符合条件:x="+x;
2. 断言内容代码编译
因为assert在JDK1.4中是一个新的关键字,因此在使用时需要告诉编译器你编译所使用jdk的版本号。
javac -source 1.4 MyClass.java
在jdk的后续版本中,对断言的支持成为默认特性(JDK5.0以上使用时就不需要这个编译了,因为默认就支持的)。
3. 断言内容代码执行
默认情况下,断言是关闭的。要通过-enableassertions或者-ea选项来运行程序以打开断言:
java -enableassertions com.briup.ch07.Xxxx
java -ea com.briup.ch07.Xxxx
打开或关闭断言功能,决定了代码在执行时是否会跳过那些和断言相关的代码.因此断言并不会降低程序运行速度。
注意:使用eclipse运行代码的时候也是可以传参数的(包括俩种参数)
例如:
java -xx com.briup.ch07.Test yy
xx是给JVM传的参数 yy是给Test类的main方法传的参数
57.GUI概述-AWT和Swing
图形用户界面(Graphics User Interface, GUI) 是用户与程序交互的窗口,它比基于命令行的界面更直观并且更友好。
GUI的基本类库位于java.awt包中, 这个包也被称为抽象窗口工具集(Abstract Window Toolkit, AWT)。AWT按照面向对象的思想来创建GUI,它提供了容器类、组件类和布局管理器类。
随着Java的发展,AWT中的大部分组件已经渐渐被淘汰,它已经不能适应发展的需要,不能满足开发功能强大的用户界面的需要。这时Swing出现了,它是建立在AWT之上的组件集(对AWT组件的扩展),在不同的平台上都能保持组件的界面样式,因此得到了非常广泛的应用。
注:AWT和Swing之间的最大的区别在于Swing组件的实现与本地实现无关,所以能保证Swing组件在不同的平台上都能显示出原有的样式,并且Swing组件比AWT组件具有更多的功能。
AWT组件都在java.awt包下面,Swing组件都在javax.swing包下面
例如AWT和Swing中都有按钮,
java.awt.Button
javax.swing.JButton
AWT和Swing中都有复选框,
java.awt.Checkbox
javax.swing.JCheckBox
AWT和Swing中都有对话框,
java.awt.Dialog
javax.swing.JDialog
AWT和Swing中都有菜单组件,
java.awt.Menu
javax.swing.JMenu
...
注1:它们之间的名字的特点
注2:javax.swing中绝大大多数的组件都是继承了java.awt中Component
58.组件、容器、布局管理器的介绍
这三种东西是GUI界面中最重要的组成部分。
1)组件
java.awt.Component类是所以组件的父类(这是个抽象类)。同时在该类中也定义了所有组件都应该具备的公共方法。
Component类型对象是一个具有图形表示能力的对象,可在屏幕上显示,并可与用户进行交互。例如图形用户界面中的按钮、复选框和滚动条等都是它的实例。
//实现的接口没有写出来
public abstract class Component{}
下面常用的类都是Component的"直接"子类:
java.awt.Button
Button类用于创建GUI中的按钮
java.awt.Canvas
Canvas类表示屏幕上一个空白矩形区域,应用程序可以在该区域内绘图,或者可以从该区域捕获用户的输入事件。
java.awt.Checkbox
Checkbox类用于创建GUI中的复选框
java.awt.Choice
Choice类表示一个弹出式选择菜单,当前的选择显示为菜单的标题。
java.awt.Container
Container类是所有容器类的父类.(容器本身也是一种组件)
java.awt.Label
Label类型对象是一个可在容器中放置文本的组件。一个标签只显示一行只读文本。文本可由应用程序更改,但是用户不能直接对其进行编辑
java.awt.List
List组件为用户提供了一个可滚动的文本项列表。可设置此 list,使其允许用户进行单项或多项选择
java.awt.Scrollbar
Scrollbar类可用于创建一个滚动条
java.awt.TextComponent
TextComponent类是所有允许编辑文本的组件的父类。
2)容器
java.awt.Container类是所有容器的父类。
容器也是一种特殊的组件,它是一个可包含其他组件的组件,所以Container类继承了Component类
public class Container extends Component{}
下面常用的类都是Container的"直接"子类:
javax.swing.JComponent
JComponent类是除顶层容器外所有Swing组件的基类
java.awt.Panel
Panel是最简单的容器类,应用程序可以将其他组件放在面板提供的空间内,这些组件包括其他面板
java.awt.Window
Window对象是一个没有边界和菜单栏的顶层窗口
java.awt.ScrollPane
用于实现单个组件的水平或垂直滚动的容器类
3)布局管理器
Java中的图形界面在布局管理上采用容器和布局管理相分离的方案,也就是说容器只是把组件放进来,但它不管怎样放置。至于如何放置需要用到布局管理器。
java.awt.LayoutManager接口是所有布局管理需要实现的接口
Java中常用布局管理器有:
BorderLayout 边界布局管理器
FlowLayout 流式布局管理器
GridLayout 网格布局管理器
CardLayout 卡片布局管理器
注:很多容器都是有默认的布局管理器的,同时也允许用户修改
注:也可以不使用任何布局管理器,自己使用坐标控制组件在容器中的位置
59.组件、容器、布局管理器的使用
1)常用的容器类,Frame和Panel
java.awt.Frame类:
public class Frame extends Window{}
java.awt.Frame是带有标题和边框的顶层窗口,它是java.awt.Window类的直接子类
例如:
Frame frame = new Frame();
frame.setTitle("我的容器");
frame.setSize(500,500);
frame.setLocation(700, 300);
frame.setBackground(Color.lightGray);
frame.setResizable(true);
frame.setVisible(true);
注:frame要显示出来,最后一定要设置它的的可见性为true
思考:怎么能让窗口每次位于屏幕的正中间
Toolkit toolkit = Toolkit.getDefaultToolkit();
int width = (int) toolkit.getScreenSize().getWidth();
int height = (int) toolkit.getScreenSize().getHeight();
java.awt.Panel类:
public class Panel extends Container{}
Panel是最简单的容器类,也成为面板,应用程序可以将其他组件放在面板提供的空间内,这些组件包括其他面板(嵌套)。
注:Panel面板不能单独显示,必须把它放到Frame中
例如:运行后显示不了,设置Panel的大小位置以及可见性也没用
Panel panel = new Panel();
panel.setSize(400, 400);
panel.setLocation(700, 300);
panel.setBackground(Color.blue);
panel.setVisible(true);
例如:运行后可以显示
Frame frame = new Frame();
Panel panel = new Panel();
panel.setBackground(Color.blue);
frame.setSize(400, 400);
frame.setLocation(700, 300);
frame.add(panel);
frame.setVisible(true);
2)常用的容器类,JFrame JPanel
javax.swing.JFrame类:
public class JFrame extends Frame{}
例如:
JFrame jFrame = new JFrame();
jFrame.setTitle("我的容器");
jFrame.setResizable(true);
jFrame.setSize(500,500);
jFrame.setLocation(700, 300);
jFrame.setBackground(Color.lightGray);
jFrame.setVisible(true);
javax.swing.JPanel类:
public class JPanel extends JComponent
JPanel是一般轻量级容器,作用及用法和java.awt.Panel类似,它也是不能单独显示的。
3)常用的组件类(主要以Swing的为主,除非Swing中没有该组件)
javax.swing.JButton 按钮
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JButton btn = new JButton("登录");
panel.add(btn);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JCheckBox 复选框
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JCheckBox jck1 = new JCheckBox("篮球");
JCheckBox jck2 = new JCheckBox("足球");
panel.add(jck1);
panel.add(jck2);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.ButtonGroup 按钮组
javax.swing.JRadioButton 单选框
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
//需要把俩个单选按钮放到ButtonGroup中,表示为一组
ButtonGroup bg = new ButtonGroup();
JRadioButton rbtn1 = new JRadioButton("男");
JRadioButton rbtn2 = new JRadioButton("女",true);
bg.add(rbtn1);
bg.add(rbtn2);
panel.add(rbtn1);
panel.add(rbtn2);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.Choice 下拉菜单
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
Choice ColorChooser = new Choice();
ColorChooser.add("Green");
ColorChooser.add("Red");
ColorChooser.add("Blue");
panel.add(ColorChooser);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JComboBox 下拉菜单
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JComboBox<String> box = new JComboBox<>();
box.addItem("test1");
box.addItem("test2");
box.addItem("test3");
panel.add(box);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.ImageIcon 图片/图标
javax.swing.JLabel 标签(可放置文本和图片)
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JLabel label1 = new JLabel("大家好");
JLabel label2 = new JLabel();
label2.setIcon(new ImageIcon("src/com/briup/test/1.png"));
panel.add(label1);
panel.add(label2);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
注:按钮上也能类似的设置图片
javax.swing.JList 显示数据列表并且允许用户选择一个或多个项的组件(按住ctrl键多选)
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
String[] data = {"one", "two", "three", "four"};
JList<String> myList = new JList<String>(data);
panel.add(myList);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JScrollBar 滚动条
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
//垂直 VERTICAL, 水平 HORIZONTAL
JScrollBar bar = new JScrollBar(JScrollBar.HORIZONTAL);
bar.setPreferredSize(new Dimension(200, 20));
bar.setMinimum(0);
bar.setMaximum(100);
bar.setValue(50);
panel.add(bar);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JTextArea 文本域
javax.swing.JScrollPane 滚动面板(一般结合其他组件使用)
例如:结合文本域
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
//需要给panel换一个布局管理器
panel.setLayout(new BorderLayout());
JTextArea area = new JTextArea();
area.setSize(100, 100);
JScrollPane scrollPane = new JScrollPane(area);
panel.add(scrollPane);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JTextField 文本框
javax.swing.JPasswordField 密码框
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JTextField field = new JTextField(10);
JPasswordField passwordField = new JPasswordField(10);
panel.add(field);
panel.add(passwordField);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JMenuBar 菜单栏
javax.swing.JMenu 菜单
javax.swing.JMenuItem 菜单项
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JMenuBar mBar = new JMenuBar();
JMenu sMenu, gMenu;
sMenu = new JMenu("系统");
gMenu = new JMenu("工具");
JMenuItem sItem, cItem, tItem;
sItem = new JMenuItem("开始导航");
cItem = new JMenuItem("刷新系统");
tItem = new JMenuItem("退出系统");
sMenu.add(sItem);
sMenu.addSeparator();
sMenu.add(cItem);
sMenu.addSeparator();
sMenu.add(tItem);
JMenuItem xyItem, ycItem;
xyItem = new JMenuItem("显示菜单");
ycItem = new JMenuItem("隐藏菜单");
gMenu.add(xyItem);
gMenu.addSeparator();
gMenu.add(ycItem);
mBar.add(sMenu);
mBar.add(gMenu);
panel.add(mBar);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JDialog 对话框
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JButton okBut = new JButton("确定");
//参数1 对话框属于哪个窗口
//参数2 对话框标题
//参数3 弹框对话框后,是否阻止用户进行其他组件的操作
JDialog dialog = new JDialog(jFrame, "提示信息", true);
dialog.setBounds(730, 325, 350, 150);
dialog.setLayout(new FlowLayout());
dialog.add(okBut);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
dialog.setVisible(true);
注:之后可以通过鼠标点击控制对话框的出现和消失
javax.swing.JProgressBar 进度条
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JProgressBar bar = new JProgressBar();
//最小值为0
bar.setMinimum(0);
//最大值为100
bar.setMaximum(100);
//设置进度条中的值初始为0
bar.setValue(0);
//显示当前进度条的状态
bar.setStringPainted(true);
panel.add(bar);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
bar.setValue(40);
java.awt.Canvas 画布
注:必须为Canvas创建子类并重写paint方法
例如:
JFrame jFrame = new JFrame();
final JPanel panel = new JPanel();
Canvas canvas = new Canvas(){
@Override
public void paint(Graphics g) {
g.drawLine(0, 0, panel.getWidth(), panel.getHeight());
}
};
panel.setLayout(new BorderLayout());
panel.add(canvas);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
注:尝试在画布对象创建完后,添加如下代码观察效果
canvas.addMouseMotionListener(new MouseAdapter() {
public void mouseDragged(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Graphics g = canvas.getGraphics();
g.setColor(Color.RED);
g.fillOval(x, y, 10, 10);
}
});
注:addMouseMotionListener和addMouseListener所支持的事件是有区别的
4)常用的布局管理器的使用
注: JFrame的布局管理器默认是BorderLayout
JPanel的布局管理器默认是FlowLayout
BorderLayout 边界布局管理器
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JButton north,south,west,east,center;
north = new JButton("north");
south = new JButton("south");
west = new JButton("west");
east = new JButton("east");
center = new JButton("center");
//把按钮添加到panel中,并指明添加到的位置
panel.add(north,BorderLayout.NORTH);
panel.add(south,BorderLayout.SOUTH);
panel.add(west,BorderLayout.WEST);
panel.add(east,BorderLayout.EAST);
//panel.add(center)这样写和下面的效果是一样的
//如果你不指定放到BorderLayout那个位置的时候
//他会默认的把组件放到中间那个位置
panel.add(center,BorderLayout.CENTER);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
FlowLayout 流式布局管理器
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
JButton[] buts = new JButton[50];
for(int i=1;i<=buts.length;i++){
if(i<10){
buts[i-1] = new JButton("0"+i);
}else if (i<=50) {
buts[i-1] = new JButton(""+i);
}
panel.add(buts[i-1]);
}
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
GridLayout 网格布局管理器
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(3,3));
JButton[] buts = new JButton[9];
//设置container的布局管理器为GridLayout
//GridLayout(3,3)表示布局为3行3列
//循环创建按钮并放到panel中
for(int i=0;i<buts.length;i++){
buts[i] = new JButton((i+1)+"");
panel.add(buts[i]);
}
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
CardLayout 卡片布局管理器
例如:
JFrame jFrame = new JFrame();
final JPanel panel = new JPanel();
panel.setLayout(new CardLayout());
JPanel[] jPanels = new JPanel[4];
//创建四个JPanel放到数组中并设置背景颜色
jPanels[0] = new JPanel();
jPanels[0].setBackground(Color.red);
jPanels[1] = new JPanel();
jPanels[1].setBackground(Color.blue);
jPanels[2] = new JPanel();
jPanels[2].setBackground(Color.cyan);
jPanels[3] = new JPanel();
jPanels[3].setBackground(Color.green);
//向CardLayout布局管理器中放置组件的时候一定要给组件起一个名字
panel.add("a",jPanels[0]);
panel.add("b",jPanels[1]);
panel.add("c",jPanels[2]);
panel.add("d",jPanels[3]);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
注:现在所看到的结果是四个颜色不同的JPanel叠在一起的效果,看到是最上面一个JPanel
可以在上面例子中加入以下代码,看效果如何.
panel.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
//获得panel容器上面的布局管理器
//并转为CardLayout类型的对象
CardLayout cl = (CardLayout) panel.getLayout();
//显示CardLayout管理器中的下一个组件
//这个参数表示:在哪一个容器中设置的这个CardLayout管理器
cl.next(panel);
//cl.show(panel, "d");
}
});
注:JPanel和JPanel之间可以嵌套,每个JPanel又可以设置为不同的布局管理器,这样就可以得到大都数想要的组件布局情况了
自定义组件位置
有些时候也可以不使用任何布局管理器,用户通过坐标自己设置组件位置。
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
//把布局管理器设置为null
//表示不使用任何管理器
panel.setLayout(null);
JButton btn = new JButton("登录");
//设置组件的大小和位置
btn.setSize(100,30);
btn.setLocation(80,60);
panel.add(btn);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
60.编写一个GUI程序的基本方式
1)第一种情况:
public class JFrameTest{
//声明容器和要用的组件
private JFrame frame;
private JButton button;
public JFrameTest(){
//初始化主容器
frame = new JFrame();
frame.setTitle("我的组件");
frame.setSize(500, 500);
frame.setLocation(700, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponent();
frame.setVisible(true);
}
//初始化组件
private void initComponent(){
button = new JButton("登录");
frame.add(button);
}
public static void main(String[] args){
new JFrameTest();
}
}
2)第二种情况:
public class JFrameTest extends JFrame{
//声明容器和要用的组件
private JButton button;
public JFrameTest(){
//初始化主容器
this.setTitle("我的组件");
this.setSize(500, 500);
this.setLocation(700, 300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponent();
this.setVisible(true);
}
//初始化组件
private void initComponent(){
button = new JButton("登录");
this.add(button);
}
public static void main(String[] args){
new JFrameTest();
}
}
61.一个GUI的例子
public class MyFirstGUI extends JFrame{
private static final long serialVersionUID = 1L;
private JButton button;
private JPanel jPanel;
private JLabel addLabel,eqsLabel;
private JTextField f1,f2,f3;
public MyFirstGUI() {
setTitle("MY-GUI");
//设置位置和宽高
setBounds(40, 40, 400, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponent();
setVisible(true);
}
private void initComponent(){
//初始化组件
jPanel = new JPanel();
button = new JButton("add");
addLabel = new JLabel("+");
eqsLabel = new JLabel("=");
//创建文本输入框对象,并设置长度为5
f1 = new JTextField(5);
f2 = new JTextField(5);
f3 = new JTextField(5);
//设置容器的布局管理器并添加组件
jPanel.setLayout(new FlowLayout());
jPanel.add(f1);
jPanel.add(addLabel);
jPanel.add(f2);
jPanel.add(eqsLabel);
jPanel.add(f3);
jPanel.add(button);
this.add(jPanel);
//给指定的组件添加事件监听
button.addActionListener(new ActionListener(){
//当鼠标点击这个指定按钮的时候程序就会调用这个方法
@Override
public void actionPerformed(ActionEvent e) {
//拿到第一个输入框中的数组(String转换为Double)
double a =
Double.parseDouble(f1.getText());
//拿到第二个输入框中的数组(String转换为Double)
double b =
Double.parseDouble(f2.getText());
//相加得到结果
double c = a+b;
//把结果放到第三个输入框中
f3.setText(c+"");
}
});
}
public static void main(String[] args) {
new MyFirstGUI();
}
}
62.了解JFC
JFC(Java Foundation Classes)是一个图形框架,主要是由AWT、Swing以及Java2D三者所构成,若将这些一同搭配运用,
则用Java程式语言撰写开发成的使用者界面,无论移转、转移到Microsoft WindowsWindows、Mac OS X或Linux等各种不同的作业平台上,都能保有一致性的图像呈现。
63.AWT事件模型概述
使用AWT或者Swing中的容器、组件和布局管理器就可以构建出图形界面,但是这时候该界面还并不能和用户进行交换,因为图形界面中的组件还没有添加事件监听器,所以还不能对用户在界面中的操作进行处理。
在Java事件模型中,必须存在事件对象、事件源、事件监听器三部分。事件对象是表示发生了什么事件,事件源表示是谁产生的这个事件对象,事件处理器接收到事件对象后,可以对这个事件进行处理。
事件模型中的三要素:事件对象、事件源、事件监听器
注:不光是GUI中,在java的其他地方也会使用到事件模型。
在Java中一个事件监听器就是指事件发生时被通知的对象。它有两个要求:首先,为了可以接收到特殊类型事件的通知,它必须在事件源中已经注册;其次,它必须实现接收和处理事件的方法。
例如:
//btn就是事件源
JButton btn = new JButton("测试");
//给事件源btn注册事件监听器
//这里使用了匿名内部类对象作为监听器
btn.addActionListener(new ActionListener() {
//监听器中实现接收和处理事件的方法
//ActionEvent类型的引用e指向的就是按钮上所产生的事件对象
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("hello");
}
});
XxxxListener 监听器接口
事件源.addXxxxListener(new XxxxListener(){
//实现接口中的抽象方法
});
在事件源上注册好监听器之后,只要是在该事件源上产生了特定的事件对象,事件监听器就会自动被触发,并执行相应的方法处理。
例如:在上面的例子基础上
当我们使用鼠标点击btn这个按钮之后,产生了一个鼠标点击的事件对象(引用e会指向这个事件对象),然后注册的事件监听器会自动触发(这里的匿名内部类对象就是注册的监听器),并调用指定方法actionPerformed,对产生的事件进行处理。
64.事件源、事件对象、事件监听器
1)事件源
AWT和Swing中的几乎所有的组件都可以作为事件源,注意容器也是一种组件。
例如:窗口、面板、按钮、输入框、下拉类别、单选复选框、标签、滚动条、进度条等等
例如:AWT和Swing中的组件都是java.awt.Component类的子类型,Component类中定义了很多所有组件都可以调用的方法,这些方法中有很多是这种形式的:addXxxxListener
这些方法就是给组件中注册事件监听器的方法,只是不同类型的事件需要使用不同类型的监听器来监听,所以不同的addXxxxListener方法就表示给组件添加相应的事件监听器。(Xxxx代表事件的类型)
2)事件对象及其对应的处理接口(也就是事件监听器)
注:事件处理器都被定义为了接口,思考为什么都定义为接口
java.util.EventObject类
public class EventObject extends Object{}
该类是java中所有事件对象的父类型。
该类中有一个非常重要的方法:getSource
public Object getSource(){...}
该方法返还的对象是产生当前事件的事件源
例如:当前使用鼠标点击按钮btn的时候,会产生一个事件对象e,这个对象e就表示鼠标点击的事件,同时e也是EventObject类型的对象,调用getSource方法可以得到产生事件的事件源,也就是我们点击的那个按钮btn.
java.awt.Event类
public class Event extends Object{..}
在Java1.1和以后的版本中该类已被废弃,由AWTEvent类及其子类所取代.
java.awt.AWTEvent类
public abstract class AWTEvent extends EventObject{...}
该类是所有AWT事件的父类型,此类及其子类取代了原来的 java.awt.Event类
java.awt.event.ActionEvent类
public class ActionEvent extends AWTEvent{...}
动作事件类,单击按钮、选择菜单项或在文本框中按回车时可产生此事件对象。
可以处理该类型事件的监听器接口:ActionListener
注:ActionListener是一个很通用的接口,可以处理很多种组件上面产生的事件.
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JButton btn = new JButton("测试");
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("hello");
}
});
panel.add(btn);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.AdjustmentEvent类
public class AdjustmentEvent extends AWTEvent{..}
调整事件类,当改变滚动条滑块位置时可产生此事件对象。
该类代表由Adjustable类型对象所发出的调整事件。主要针对的是滚动条,Scrollbar和JScrollbar都是Adjustable接口的实现类。
可以处理该类型事件的监听器接口:AdjustmentListener
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JScrollBar bar = new JScrollBar(JScrollBar.HORIZONTAL, 0, 0, 0, 100);
bar.setPreferredSize(new Dimension(100, 20));
bar.addAdjustmentListener(new AdjustmentListener() {
@Override
public void adjustmentValueChanged(AdjustmentEvent e) {
System.out.println(e.getValue());
}
});
panel.add(bar);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.ComponentEvent类
public class ComponentEvent extends AWTEvent{...}
组件事件类,表示组件被移动、大小被更改或可见性被更改的事件,同时它也是其他组件级事件的父类:
java.awt.event.ContainerEvent
java.awt.event.FocusEvent
java.awt.event.WindowEvent
..
这些都是它的的子类
可以处理该类型事件的监听器接口:ComponentListener
该接口中有四个抽象方法:
componentMoved组件移动时被调用
componentHidden组件隐藏时被调用
componentResized组件缩放时被调用
componentShown组件显示时被调用
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
jFrame.addComponentListener(new ComponentListener() {
public void componentShown(ComponentEvent e) {
System.out.println("shown");
}
public void componentHidden(ComponentEvent e) {
System.out.println("Hidden");
}
public void componentResized(ComponentEvent e) {
System.out.println("Resized");
}
public void componentMoved(ComponentEvent e) {
System.out.println("Moved");
}
});
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.ContainerEvent类
public class ContainerEvent extends ComponentEvent{..}
容器事件类,容器中因为添加或移除组件而更改的事件。
可以处理该类型事件的监听器接口:ContainerListener
接口中有俩个方法:
componentAdded添加组件时被调用
componentRemoved移除组件时被调用
例如:
JFrame jFrame = new JFrame();
final JPanel panel = new JPanel();
JButton btn = new JButton("点击");
final JButton test = new JButton("测试");
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
panel.add(test);
//在运行中动态添加组件
//需要调用容器的validate和repaint
//或者是调用容器的revalidate方法
//否则动态添加的组件不显示
//panel.validate();
//panel.repaint();
panel.revalidate();
}
});
panel.addContainerListener(new ContainerListener() {
@Override
public void componentRemoved(ContainerEvent e) {
System.out.println("removed");
}
@Override
public void componentAdded(ContainerEvent e) {
System.out.println("Added");
}
});
panel.add(btn);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.WindowEvent类
public class WindowEvent extends ComponentEvent{..}
窗口事件类,窗口打开、关闭等操作是会创建该事件对象。
可以处理该类型事件的监听器接口:WindowListener
该接口中有七个方法:
windowOpened窗口打开后被调用
windowClosed窗口关闭后被调用
windowClosing窗口关闭时被调用
windowActivated窗口激活时被调用
windowDeactivated窗口失去焦点时被调用
windowIconified窗口最小化时被调用
windowDeiconified最小化窗口还原时被调用
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
jFrame.addWindowListener(new WindowListener() {
public void windowOpened(WindowEvent e) {}
public void windowIconified(WindowEvent e) {
System.out.println("最小化窗口");
}
public void windowDeiconified(WindowEvent e) {
System.out.println("最小化窗口被还原");
}
public void windowDeactivated(WindowEvent e) {}
public void windowClosing(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
});
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.FocusEvent类
public class FocusEvent extends ComponentEvent{...}
焦点事件类,当组件获得或者失去焦点的时候回产生该类型的事件对象。
可以处理该类型事件的监听器接口:FocusListener
该接口中有俩个方法:
focusGained组件获得焦点时被调用
focusLost组件失去焦点时被调用
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JTextField field = new JTextField(10);
JButton btn = new JButton("test");
field.addFocusListener(new FocusListener() {
public void focusLost(FocusEvent e) {
System.out.println("失去焦点");
}
public void focusGained(FocusEvent e) {
System.out.println("获得焦点");
}
});
panel.add(field);
panel.add(btn);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.ItemEvent类
public class ItemEvent extends AWTEvent{..}
选择事件类,选择复选框、选项框、单击列表框等时会产该事件对象。
可以处理该类型事件的监听器接口:ItemListener
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JCheckBox jck = new JCheckBox("自动登录");
jck.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
System.out.println(e.getStateChange());
}
});
panel.add(jck);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.TextEvent类
public class TextEvent extends AWTEvent{..}
文本内容类,组件中的文本已改变时会产生该事件对象。
可以处理该类型事件的监听器接口:TextListener
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
TextField field = new TextField(10);
field.setText("hello");
field.addTextListener(new TextListener() {
@Override
public void textValueChanged(TextEvent e) {
System.out.println("改变了");
}
});
panel.add(field);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
注:如果是JTextField,那么需要这样监听内容的改变:
JTextField field = new JTextField(10);
field.getDocument().addDocumentListener(new DocumentListener() {
public void removeUpdate(DocumentEvent e) {
}
public void insertUpdate(DocumentEvent e) {
}
public void changedUpdate(DocumentEvent e) {
}
});
java.awt.event.KeyEvent类
注:InputEvent是ComponentEvent的子类,ComponentEvent类在上面已经介绍过了。
public class KeyEvent extends InputEvent{..}
键盘事件类,键盘输入的时候回产生此事件对象。
可以处理该类型事件的监听器接口:KeyListener
该接口中有三个方法:
keyPressed键按下时被调用
keyReleased键释放时被调用
keyTyped键入某个键时被调用(F1等功能按键时不会触发)
注:KeyEvent类中定义了很多静态常量,几乎把键盘上所以的按键都表示出来了。
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JTextField field = new JTextField(10);
field.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
System.out.println("keyTyped "+e.getKeyCode()+" "+e.getKeyChar());
}
@Override
public void keyReleased(KeyEvent e) {
System.out.println("keyReleased "+e.getKeyCode());
}
@Override
public void keyPressed(KeyEvent e) {
System.out.println("keyPressed "+e.getKeyCode());
}
});
panel.add(field);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.MouseEvent类
public class MouseEvent extends InputEvent{..}
鼠标事件类,当鼠标在组件中发生鼠标动作的时候会产生此事件对象。
有三个接口可以处理该类型事件:
MouseListener接口
MouseMotionListener接口
MouseWheelListener接口
MouseListener接口中有五个方法:
mouseClicked 鼠标单击时被调用
mouseEntered 鼠标进入时被调用
mouseExited 鼠标离开时被调用
mousePressed 鼠标键按下时被调用
mouseReleased鼠标键释放时被调用
MouseMotionListener接口中有俩个方法:
mouseMoved 鼠标移动时被调用
mouseDragged鼠标拖拽时被调用
MouseWheelListener接口中有一个方法:
mouseWheelMoved 鼠标滚轮滚动时被调用
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
panel.addMouseListener(new MouseListener() {
public void mouseReleased(MouseEvent e) {
System.out.println("mouseReleased");
}
public void mousePressed(MouseEvent e) {
System.out.println("mousePressed");
}
public void mouseExited(MouseEvent e) {
System.out.println("mouseExited");
}
public void mouseEntered(MouseEvent e) {
System.out.println("mouseEntered");
}
public void mouseClicked(MouseEvent e) {
System.out.println("mouseClicked");
}
});
panel.addMouseMotionListener(new MouseMotionListener() {
public void mouseMoved(MouseEvent e) {
System.out.println("mouseMoved");
}
public void mouseDragged(MouseEvent e) {
System.out.println("mouseDragged");
}
});
panel.addMouseWheelListener(new MouseWheelListener() {
public void mouseWheelMoved(MouseWheelEvent e) {
System.out.println("mouseWheelMoved");
}
});
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
65.适配器
很多监听器接口中都定义了很多个方法,每个方法负责处理一种产生事件的情况,我们编写实现类的时候就需要实现监听器接口中的所有方法,但是很多时候我们其实只需要调用接口中的一个方法,但是由于语法要求我们还是必须把接口中的所有抽象全都实现了。
例如: 点击按钮输出hello world
JButton btn = new JButton("test");
btn.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent e) {
System.out.println("hello world");
}
public void mouseReleased(MouseEvent e) {}
public void mousePressed(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
});
使用了MouseListener接口的匿名内部类对象,并且五个方法全都实现了,但是其实我们只需要调用mouseClicked方法.
为了处理这个代码中出现的情况,又引入了接口的适配器类:XxxxAdapter
MouseAdapter实现了MouseListener, MouseWheelListener, MouseMotionListener三个接口,并且把接口中抽象方法全都进行了空实现,将来我们只需要创建MouseAdapter类的匿名内部类然后重写我们想调用的方法即可
例如: 点击按钮输出hello world
JButton btn = new JButton("test");
btn.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
System.out.println("hello world");
}
});
除了MouseAdapter实现MouseListener, MouseWheelListener, MouseMotionListener三个接口,是一个适配器类之外,还有其他的一些适配器类:
WindowAdapter适配器类实现了WindowListener, WindowStateListener, WindowFocusListener三个接口
ComponentAdapter适配器类实现了ComponentListener接口
ContainerAdapter适配器类实现了ContainerListener接口
FocusAdapter适配器类实现了FocusListener接口
KeyAdapter适配器类实现了KeyListener接口
MouseMotionAdapter适配器类实现了MouseMotionListener接口
HierarchyBoundsAdapter适配器类实现了HierarchyBoundsListener接口
66.定时器Timer
javax.swing.Timer类,可以定时触发事件,调用监听器的指定方法
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JPanel north = new JPanel();
JButton startBtn = new JButton("开始");
JButton endBtn = new JButton("停止");
final Canvas canvas = new Canvas();
final Timer timer = new Timer(500,new ActionListener() {
private int count;
@Override
public void actionPerformed(ActionEvent e) {
if(count*10>=canvas.getWidth()){
return ;
}
Graphics g = canvas.getGraphics();
g.drawLine(count*10, 0, count*10, canvas.getWidth());
count++;
}
});
startBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
timer.start();
}
});
endBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
timer.stop();
}
});
north.add(startBtn);
north.add(endBtn);
panel.setLayout(new BorderLayout());
panel.add(canvas);
jFrame.add(north,BorderLayout.NORTH);
jFrame.add(panel);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setVisible(true);
67.让当前类实现监听器接口
当前对象this就成为了监听器对象
例如:
public class Test extends JFrame implements ActionListener{
private static final long serialVersionUID = 1L;
private JPanel jPanel;
private JButton btn;
public Test() {
setBounds(700, 500, 500, 500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
initComponet();
setVisible(true);
}
private void initComponet(){
//初始化组件
jPanel = new JPanel();
btn = new JButton("测试");
//设置布局管理器并添加组件
jPanel.add(btn);
add(jPanel);
//给组件添加事件监听器
btn.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
System.out.println("hello world");
}
public static void main(String[] args) {
new Test();
}
}
68.进程和线程的概述
1)进程和线程定义
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
2)进程和线程关系
一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行.
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,并且线程拥有自己的栈空间.
一个程序至少有一个进程,一个进程至少有一个线程
3)进程和线程区别
进程和线程的主要区别在于它们是操作系统不同的资源管理方式。进程有独立的地址空间,一个进程崩溃后,一般是不会对其它进程产生影响;而线程只是一个进程中的不同执行路径,线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉.
4)操作系统中的进程和线程
在操作系统中,以多进程形式,允许多个任务同时运行(其实是进程之间切换运行的);以多线程形式,允许单个任务分成不同的部分运行(每个部分的代码由一个线程来负责执行)。
注:可以看出来一个应用程序的代码,主要是由线程负责在内存中执行,同时这些代码可以分为不同的部分交给多个线程分别执行,在线程执行代码过程中,如果需要用到计算的机资源,那么就可以从线程所属的进程中获取,而进程则是操作系统进行资源分配和调度的独立单位。
思考:为什么运行我们编写的java程序的时候要先启动JVM虚拟机?
69.java中的线程
1)Thread类
java.lang.Thread类
public class Thread extends Object implements Runnable{..}
Thread是java中的线程类,是对java中线程的抽象,Thread类型的对象就可以表示java中的一个线程
注:一个线程对象的作用就是可以单独运行我们所交给它的任务
注:Thread类及其子类的对象都可以表示一个线程对象
2)线程的分类
在Java中有两类线程:
用户线程 (User Thread)
也可以称为前台线程、执行线程
守护线程 (Daemon Thread)。
也可以称为后台线程、精灵线程(Daemon有精灵的意思)
守护线程,是指程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的退出: 如果用户线程已经全部处于死亡状态,虚拟机也就退出了,这是也不用管守护线程是否还存在了
注:java中创建出来的线程默认是用户线程,但是在启动线程前可以通过特定方法(setDaemon)把该线程转为守护线程
3)名字叫"main"的线程
当我们运行一个java程序的时候,其实就是让JVM创建一个名字叫"main"的线程,然后让这个线程去执行我们所编写的类中main方法的代码。我们可以把这个线程称之为main线程或主线程,因为这个线程是第一个执行我们编写代码的线程,但是这时候并不是只有这一个线程在JVM中,可以通过jconsole观察到当前的所有线程
注:jconsole是JDK自带的监测java程序运行的工具
4)多线程程序
我们之前所编写的代码绝大多数都是main线程执行的(单线程),但也有一些是多线程的程序,例如在GUI中的定时器Timer的使用,其实就是启动了一个新的线程,要不然怎么可能做到一边打地鼠加分,一边还可以进行倒计时显示.
由于java中允许在一个线程中创建并启动另一个线程,所以我们可以很容易的编程出一个多线程程序来。
思考:为什么要编写多线程程序,单线程程序不好么?
5)多线程程序的执行
为了提高程序执行效率,很多应用中都会采用多线程模式,这样可以将任务分解以及并行执行,从而提高程序的运行效率。但这都是代码级别的表现,而硬件上需要使用CPU的时间片模式来提供支持。程序的任何指令的执行都要竞争CPU这个最宝贵的资源,不论程序分成了多少个线程去执行各自的任务,这些线程都必须通过一定的方式来获取时间片,从而得到CPU的使用权进行代码的执行。
注:时间片就是CPU分配程序的使用时间,每个线程获得一个时间片后,在此段时间内是可以使用CPU进行运算的,但时间用完后就要交出CPU的使用权.
注:不同操作系统中,或者同类操作系统的不同算法中,时间片的大小是不一样的,但是不论哪种情况,对象我们来讲,这个时间片都是一个极短的一段时间.
让线程获得时间片的算法有多种,但是现在一般都是"抢占式",就是默认情况下,多个线程具有同等几率抢占到CPU的下一个时间片,最终谁能抢到那么这个时间片就算是谁的,使用完之后再退出来重新再争夺一下CPU的时间片
70.Thread类和Runnable接口
1)Thread类中的run方法
线程对象中的run方法,就是线程独立运行之后,必须要执行的方法,如果我们有什么代码要交给一个线程独立运行,那么就需要把这些代码放到run中.
2)Thread类中的start方法
在代码中,我们并不能直接调用一个线程对象的run方法,而且需要调用线程对象的start方法来启动这个线程,然后这个线程会自动的调用run方法的,如果直接调用了run方法,那就不是多线程代码了
3)Thread类和Runnable接口的关系
Runnable接口中只有一个方法:
public interface Runnable{
public void run();
}
Thread类是Runnable接口的实现类,大致代码如下:
public class Thread implements Runnable{
private Runnable target;
public Thread(){}
public Thread(Runnable target) {
this.target = target;
}
public void run(){
if (target != null) {
target.run();
}
}
}
4)创建和启动线程
第一种方式:创建Thread的子类对象,子类中重写run方法
例如:
//如果需要,可以考虑使用匿名内部类
Thread t = new Thread(){
public void run(){
//代码...
}
};
//启动线程
t.start();
第二种方式:创建Thread类对象,在构造器中传Runnable接口的实现类,实现类中重写run方法
例如:
//如果需要,可以考虑使用匿名内部类
Thread t = new Thread(new Runnable(){
public void run(){
//代码...
}
});
//启动线程
t.start();
注:观察直接调用线程对象的run方法和start方法后有什么不同?
71.线程对象的状态
在java中使用枚举类型Thread.State可以表示出一个线程对象当前的状态,调用线程对象的getState()方法可以获得线程的当前状态
java.lang.Thread.State枚举类型
public class Thread implements Runnable{
public enum State {
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}
}
1)Thread.State枚举类型每个对象表示的状态含义
NEW(新建尚未运行/启动)
A thread that has not yet started is in this state.
一般是还没调用start方法,或者刚刚调用了start方法,start方法不一定"立即"改变线程状态,中间可能需要一些步骤才完成一个线程的启动。
RUNNABLE(可运行状态: 包括正在运行或准备运行)
A thread executing in the Java virtual machine is in this state.
start方法调用结束,线程由NEW变成RUNNABLE.
线程存活着,并尝试抢占CPU资源,或者已经抢占到CPU资源正在运行的状态都显示为RUNNABLE
BLOCKED(等待获取锁时进入的状态)
A thread that is blocked waiting for a monitor lock is in this state.
线程A和线程B都要执行方法test,而且方法test被加了锁,线程A先拿到了锁被运行执行test方法,线程B这时候需要等待线程A把锁释放。这时候线程B就是处理BLOCKED
WAITING(通过wait方法进入"无限期"的等待)
A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
线程A和线程B都要执行方法test,而且方法test被加了锁,线程A先拿到了锁被运行执行test方法,线程B这时候需要等待线程A把锁释放(线程B处于BLOCKED状态),如果这时候线程A调用了wait方法,那么线程A就会马上交出CPU的使用权以及刚才拿到的锁,从而进入到WAITING状态,而线程B发现锁已经被释放了,线程B就从BLOCKED状态进入到了RUNNABLE,如果线程B拿到了锁之后在运行期间,调用了notify或者notifyAll方法,这时候线程A就会从WAITING状态进入到BLOCKED状态,从而等待锁的是释放.
TIMED_WAITING(通过sleep或wait等方法进入的"有限期"等待的状态)
A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
线程对象的sleep或wait等方法都可以传一个时间参数,表示就算没有其他线程调用特定方法来改变自己状态的时候,也可以通过这个时间参数让自己自动改变状态(因为时间到了)。
TERMINATED(线程终止状态)
A thread that has exited is in this state.
线程结束了,就处于这种状态,也就是run方法运行结束了。
2)通常对线程对象状态的描述
为了便于理解和记忆,通过会对Thread.State中定义的状态进行整理归类,最终可得到书中所描述的线程状态图。
图中将线程状态分为:
初始化状态
就绪状态
运行状态
死亡状态
阻塞状态(阻塞在等待池、阻塞在锁池、其他情况阻塞)
这样可以让我们对线程状态有一个更好的理解和掌握。
注:先整体了解下线程有哪些状态,等全面学完后再来验证每个状态的情况。
72.ThreadGroup线程组
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,对线程组的控管理,即同时控制线程组里面的这一批线程
用户创建的所有线程都属于指定线程组,如果没有显示指定属于哪个线程组,那么该线程就属于默认线程组(即名字叫"main"的线程组)
默认情况下,子线程和父线程处于同一个线程组
只有在创建线程时才能指定其所在的线程组,线程运行中途不能改变它所属的线程组,也就是说线程一旦指定所在的线程组,就直到该线程结束
1)创建线程组
java.lang.ThreadGroup类
创建线程组的时候需要指定一个线程组的名字,或者创建线程组的时候指定名字和它的父线程组。
创建线程组的时候需要指定线程组名字和它的父线程组,如果不指定其父线程组,那么默认是父线程组是当前线程组。(类中提供俩种构造器)
public ThreadGroup(String name);
public ThreadGroup(ThreadGroup parent, String name);
例如:
//获得当前线程的所属的线程组
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
//默认其父线程组是currentGroup
ThreadGroup tg1 = new ThreadGroup("线程组1");
//指定其父线程组tg1
ThreadGroup tg2 = new ThreadGroup(tg1,"线程组1");
2)线程和线程组
例如:
//不指定则属于默认线程组
Thread t1 = new Thread("t1线程");
//也可以指定线程组
ThreadGroup tg = new ThreadGroup("我的线程组");
Thread t1 = new Thread(tg,"t1线程");
73.Thread类中常用方法
注:Thread类中有一些方法已经被标注为过时,不推荐使用.
官网中还给出了弃用哪些方法的原因:
http://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
第一类:静态方法
public static int activeCount()
返回当前线程的线程组中活动线程的数目
public static Thread currentThread()
返回对当前正在执行的线程对象的引用
public static int enumerate(Thread[] tarray)
将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中
public static void sleep(long millis)
让当前线程在指定的毫秒数内休眠
public static void yield()
暂停当前运行的线程,让给其他线程使用CPU执行
public static boolean holdsLock(Object obj)
判断当前线程先是否拿着指定的锁
第二类:非静态方法
public void run()
线程要执行的代码在此方法中
public void start()
线程启动时必须调用的方法
public long getId()
返回该线程的标识符,线程ID是一个正的long数,在创建该线程时生成。线程ID是唯一的,线程终止时,该线程ID可以被重新使用
public void setName(String name)
设置该线程的名称
public String getName()
返回该线程的名称
public int setPriority()
设置线程的优先级
public int getPriority()
返回线程的优先级
public Thread.State getState()
返回该线程的状态
public ThreadGroup getThreadGroup()
返回该线程所属的线程组
public boolean isAlive()
测试线程是否处于活动状态
public void setDaemon(boolean on)
将该线程标记为守护线程或用户线程,默认false表示用户线程
public boolean isDaemon()
测试该线程是否为守护线程
public void join()
当前线程等待某个线程执行结束
public void join(long millis)
给定一个等待的限定时间
第三类:容易混淆的方法
public void interrupt()
中断线程
public boolean isInterrupted()
测试线程是否已经中断
public static boolean interrupted()
测试当前线程是否已经中断
如果线程a对象是处于阻塞状态的话,在线程b中调用a.interrupt()是会打断线程a的阻塞状态的(后抛出打断异常)
但是如果线程a对象是处于就绪等状态,在线程b中调用a.interrupt()只是会改变对象a内部的一个boolean类型标识,用来表示线程b想打断线程a
isInterrupted和interrupted的返回值就是这个boolean类型的值
区别在于静态方法interrupted在返回boolean值后,会把这个打断的标示符给清理掉,而且非静态方法isInterrupted不会清理
//观察Thread类中的部分源码
public class Thread implements Runnable{
public void interrupt() {
if (this != Thread.currentThread())checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
//判断当前线程是否是阻塞状态
if (b != null) {
// Just to set the interrupt flag
interrupt0();
b.interrupt(this);
return;
}
}
//如果不是阻塞状态就只set一下打断的flag
interrupt0();
}
private native void interrupt0();
public boolean isInterrupted() {
return isInterrupted(false);
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
private native boolean isInterrupted(boolean ClearInterrupted);
}
74.数据共享
俩个或多个线程可以共享对象中的数据
例如:
public class ThreadTest extends Thread{
private Student student;
public ThreadTest3(String name) {
super(name);
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public void run() {
String name = Thread.currentThread().getName();
for(int i=0;i<10;i++){
//先把当前线程的名字设置为student的名字
student.setName(name);
//再马上拿出名字打印出来
System.out.println(name+": "+student.getName());
}
}
public static void main(String[] args) {
Student s = new Student();
ThreadTest t1 = new ThreadTest("线程1");
t1.setStudent(s);
ThreadTest t2 = new ThreadTest("线程2");
t2.setStudent(s);
t1.start();
t2.start();
}
}
例如:
Runnable run = new Runnable() {
private int x;
public void run() {
String name = Thread.currentThread().getName();
for(int i=1;i<=500;i++){
x++;
}
System.out.println(name+" x = "+x);
}
};
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t1.start();
t2.start();
思考:对象中的那些变量会被多个线程所共享,共享数据在多线程环境中会有什么问题?
75.非线程安全和线程安全
在上面的例子中可以看到,在多个线程并发访问的环境中对共享数据进行操作的代码或方法中会出现其结果不确定的情况,那么这段代码或者方法就是非线程安全的。产生这种情况的原因大多是因为一个线程在执行这段代码或方法的时间内,另一个线程也是有可能执行这段代码或方法的(抢夺时间片),从而造成了这段代码或者方法中对共享数据操作的结果
如果一段代码在多个线程并发访问的时候是 非线程安全的,那么就可以采用把这段代码进行线程同步的方式进行处理,最终把这段代码变为线程安全的
线程同步其实就是本来多个线程并发访问这段代码的,同步后就变成了一个线程一个线程的按顺序线程访问这段代码,这样以来一个线程在执行这段代码期间,就不用担心其他线程会来打扰自己在这段代码中对共享数据的操作了。
注:线程同步是牺牲了效率换来了安全
76.线程同步的实现
在java中,使用synchronized关键字来实现线程同步的效果。
synchronized关键字可以用来修饰方法,也可以直接作用到某段代码上
例如:
public class Test{
private int x;
public synchronized void test(){
String name = Thread.currentThread().getName();
for(int i=0;i<100;i++){
x++;
}
System.out.println(name+": x="+x);
}
}
例如:
public class Test{
private int x;
public void test(){
String name = Thread.currentThread().getName();
synchronized(this){
for(int i=0;i<100;i++){
x++;
}
}
System.out.println(name+": x="+x);
}
}
synchronized关键字是加锁的意思,用它来修饰方法就表示给该方法加了锁,从而达到线程同步的效果;用它来修饰代码块就表示给该代码块加了锁,从而达到线程同步的效果。
例如:一个方法test使用synchronized关键字修饰后加了锁,如果这个时候有俩个线程对象t1和t2要并发访问test方法,假设t1先抢到了CPU的执行权,从而率先拿到了test方法上的锁,然后就进到test方法中执行代码,一个时间片用完之后,就退回到就绪状态,但是t1线程却依然拿着锁,那么下次CPU的抢占即使是t2抢到了也没有办法执行test方法,因为t2拿不到锁就没有办法进到test方法中执行代码,这时候t2线程就会进入到锁池里面了。
注:java中任何对象都可以当做锁,能否拿到锁就决定了一个线程是否能进入到被锁的代码块中去执行代码
例如:
public class Test{
//默认使用this来充当这把锁,锁的是test1方法
public synchronized void test1(){
//代码
}
//默认使用this.getClass()来充当这把锁,锁的是test2方法
public static synchronized void test2(){
//代码
}
public void test3(){
//这种形式可以使用任意对象充当锁,锁的是这个代码块
synchronized(任意对象){
//代码
}
}
}
77.线程通信-wait()与notify()/notifyAll()方法
在Object中,三个wait方法(重载),notify()以及notifyAll()方法都是和线程通信有关的方法
注:这几个方法只能是在synchronized关键字使用时,被用来充当锁的对象才能调用,并且只能再加锁的范围内调用,否则其他情况调用会抛出异常。
1)wait方法
当一个线程拿到锁,进入到被锁的代码中执行代码时候,突然调用了锁的wait方法,那么这个线程这时候就交出CPU使用权,并且把锁方法原出,然后由运行状态进入到等待池中,进行"无限期"的等待,直到有其他线程对象调用了特定的打断/唤醒方法后,这个线程才能从等待池中出来。
注:如果使用有参数的wait方法,那就是"有限期"的等待
例如:
//默认使用this充当这把锁
public synchronized void test(){
int a = 1;
if(a>0){
try {
//只能使用锁调用wait方法
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2)notify()方法
当一个线程在被锁的点中使用锁调用了notify()方法,那么可以随机唤醒在等待池等待这把锁的一个线程(如果有多个随机都等这同一把锁的话),这个被唤醒的线程就会从等待池进入到锁池中,如果之后某个时刻这个线程发现等待的池已经被其他线程释放了,那么它就会从锁池进入到就绪状态,准备争夺CPU的使用权并且争取这把锁。
例如:
//默认使用this充当这把锁
public synchronized void test(){
int a = 1;
if(a>0){
try {
//只能使用锁调用notify方法
this.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3)notifyAll()方法
与notify类似,不同之处在于,notifyAll()方法会叫醒等待池中等待同一把锁的所有线程对象。
例如:
//默认使用this充当这把锁
public synchronized void test(){
int a = 1;
if(a>0){
try {
//只能使用锁调用notifyAll方法
this.notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4)根据以下代码,补全pos和wit方法完成其功能
注:银行账号的余额不能是负数
public class Account {
//账号余额
private int balance;
public Account(int balnace) {
this.balance = balnace;
}
//存钱
public void pos(int money){
}
//消费
public void wit(int money){
}
}
//男孩,负责挣钱
public class Boy extends Thread{
private Account account;
public Boy(Account account, String name) {
this.account = account;
setName(name);
}
public void run() {
while(true){
int money = (int)(Math.random()*10000+1);
account.pos(money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//女孩,负责花钱
public class Girl extends Thread{
private Account account;
public Girl(Account account, String name) {
this.account = account;
setName(name);
}
public void run() {
while(true){
int money = (int)(Math.random()*10000+1);
account.wit(money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class AccountTest {
public static void main(String[] args) {
Account account = new Account(5000);
Boy boy = new Boy(account, "tom");
Girl lily1 = new Girl(account, "lily1");
Girl lily2 = new Girl(account, "lily2");
Girl lily3 = new Girl(account, "lily3");
boy.start();
lily1.start();
lily2.start();
lily3.start();
}
}
78.死锁
在程序中是不允许出现死锁情况,一旦发生那么只能手动停止JVM的运行,然后查找并修改产生死锁的问题代码。
简单的描述死锁就是:俩个线程t1和t2,t1拿着t2需要等待的锁不释放,而t2又拿着t1需要等待的锁不释放。
注:可以通过jconsole查看到线程死锁的情况
例如:
public class ThreadDeadLock extends Thread{
private Object obj1;
private Object obj2;
public ThreadDeadLock(Object obj1,Object obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
public void run() {
String name = Thread.currentThread().getName();
if("Thread-0".equals(name)){
while(true){
synchronized (obj1) {
synchronized (obj2) {
System.out.println(name+" 运行了..");
}
}
}
}
else{
while(true){
synchronized (obj2) {
synchronized (obj1) {
System.out.println(name+" 运行了..");
}
}
}
}
}
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = new Object();
Thread t1 = new ThreadDeadLock(obj1,obj2);
Thread t2 = new ThreadDeadLock(obj1,obj2);
t1.start();
t2.start();
}
}
79.volatile关键字
1)停止线程的运行
Thread类中的stop方法可以停止线程的运行,但是该方法已经被弃用了,一般会采用改变对象中的一个标识的方法来停止线程的运行或者使用interrupt方法来中断线程
例如:
public class VolatileTest extends Thread{
private boolean stop;
public void isStop(boolean stop){
this.stop = stop;
}
public void run() {
while(!stop){
System.out.println("hello");
}
}
public static void main(String[] args) {
VolatileTest t = new VolatileTest();
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.isStop(true);
System.out.println("main线程结束");
}
}
2)JVM对代码的执行优化
JVM启动的时候有俩种模式:-server和-client,在第一章的课程里已经介绍过了,其中-server模式下JVM会对热点代码使用JIT编译器以及进行代码优化,所以会出现下面的情况:
例如:
public class VolatileTest extends Thread{
private boolean stop;
public void isStop(boolean stop){
this.stop = stop;
}
public void run() {
int a = 0;
while(!stop){
a++;
}
System.out.println("a = "+a);
}
public static void main(String[] args) {
VolatileTest t = new VolatileTest();
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.isStop(true);
System.out.println("main线程结束");
}
}
注:观察执行结果发现,启动的新线程并不能被停止(注意主线程睡眠时间)
3)产生以上问题的原因
程序入口被执行之后,变量stop及其值是在堆区之中(主内存),新的线程启动运行之后,把堆区中的stop读取并加载到了线程的工作内存之中,虽然t.isStop(true)代码被调用了,但是这只是把主内存中的stop的值给设置为true了,线程工作内存中的stop的值却依然是false,这其实就是主内存和线工作内存中的值不一致造成的。这也是在-server模式下,JVM对代码进行优化提高了代码的执行效率而带来的一些问题。
4)以上问题的解决方案
1.使用synchronized给循环中的代码加锁
例如:
public void run() {
int a = 0;
while(!stop){
synchronized (this) {
a++;
}
}
System.out.println("a = "+a);
}
注:如果synchronized加锁是加在了while外面,则不能达到想要的效果
思考:为什么以上例子中的run方法中如下编写即可停止线程
public void run() {
while(!stop){
System.out.println("hello");
}
}
2.使用volatile关键字
例如:
public class VolatileTest extends Thread{
private volatile boolean stop;
public void isStop(boolean stop){
this.stop = stop;
}
public void run() {
long a = 0;
while(!stop){
a++;
}
System.out.println("a = "+a);
}
public static void main(String[] args) {
VolatileTest t = new VolatileTest();
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.isStop(true);
System.out.println("main线程结束");
}
}
注:从运行结果打印出的数字,可以看到volatile比使用synchronized关键字的效果要高很多。
5)volatile关键字
关键字volatile的作用是强制线程从公共堆栈(主内存)中取得变量的值,而不是从线程私有数据栈中取得变量的值
下面将关键字volatile和synchronized进行比较:
1.关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法和代码块。随着JDK版本的提高,synchronized关键字在执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的
2.多线程程序中访问到volatile不会发生阻塞,而synchronized会出现阻塞
3.volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它也会将线程工作内存和主内存中的数据做同步,但效率比volatile低
注:下面会有例子说明这个原子性的问题
4.关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的多个线程之间访问资源的同步性。
80.java.util.concurrent.locks.Lock接口
JDK5.0后新增了Lock接口,可以是用来实现synchronized关键字的作用,同时功能更加灵活和强大,ReentrantLock类是JDK中提供的Lock接口的默认实现
1)Lock接口的lock()方法和unlock()方法
例如:
public class LockTest extends Thread{
public static void main(String[] args) {
class Run implements Runnable{
private int i;
private Lock lock = new ReentrantLock();
public void run() {
for(int j=0;j<100000;j++){
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
}
}
public int getValue(){
return i;
}
};
Run run = new Run();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
Thread t4 = new Thread(run);
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i = "+run.getValue());
}
}
2)Lock接口的tryLock()方法
尝试着去拿锁,如果拿到了就返回true否则返回false,注意这点和synchronized是不同的,如果是synchronized的话,如果尝试拿不到锁的话,那么当前线程就会变了阻塞状态(阻塞在锁池)
此方法的典型使用语句如下:
Lock lock = new ReentrantLock();
if (lock.tryLock()) {
try {
//安全的同步操作
} finally {
lock.unlock();
}
} else {
//拿不到锁就去做其他事情,但这里就不是线程同步了
}
例如:
main:
class Run implements Runnable{
private Lock lock = new ReentrantLock();
private int i;
public void run() {
boolean flag = true;
String name = Thread.currentThread().getName();
while(flag){
if (lock.tryLock()) {
try {
for(int j=0;j<1000;j++){
i++;
}
} finally {
System.out.println(name+": i = "+i);
flag = false;
lock.unlock();
}
} else {
System.out.println(name+": 郁闷了,没拿到锁,那么就睡会把");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Runnable run = new Run();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t1.start();
t2.start();
3)Lock接口的newCondition方法和Condition接口
Condition接口表示一个条件,Lock接口的newCondition方法可以返回一个Condition接口的实现类对象,表示当前线程执行代码的条件。
Condition接口中的await()方法,相当于Object中的wait方法
Condition接口中的signal()方法,相当于Object中的notify()方法
Condition接口中的signalAll()方法,相当于Object中的notifyAll()方法
例如:这个是使用synchronized关键字实现的
public static void main(String[] args) {
Runnable run = new Runnable() {
private int x;
public void run() {
String name = Thread.currentThread().getName();
synchronized (this) {
for (int i = 0; i < 1000; i++) {
x++;
}
try {
this.notify();
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+" x = "+x);
this.notifyAll();
}
}
};
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t1.start();
t2.start();
}
例如:把上面的例子改为Lock和Condition实现
public static void main(String[] args) {
Runnable run = new Runnable() {
private int x;
private Lock lock = new ReentrantLock();
private Condition con = lock.newCondition();
public void run() {
String name = Thread.currentThread().getName();
lock.lock();
try {
for (int i = 0; i < 1000; i++) {
x++;
}
try {
con.signalAll();
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+" x = "+x);
con.signalAll();
} finally {
lock.unlock();
}
}
};
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t1.start();
t2.start();
}
注:俩种实现方法的效果是一样的,synchronized是java中的关键字,而Lock和Condition和JKD5.0新增的俩个接口,其结合着使用可以代替synchronized的功能,并且功能更加灵活和强大
81.原子操作
1.定义
原子操作(atomic operation)是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程切换。
2.非原子操作i++
在Java里面,++i(i++)或者--i(i--)不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值. 通常情况下,只能使用加锁才能保证读-改-写这三个操作是"原子性"的。也就是说我们平时所用的i++等操作是非原子操作,一般的需要加锁才能保证其"原子性"
例如:
public static void main(String[] args) {
class Run implements Runnable{
private int i;
public void run() {
for(int j=0;j<100000;j++){
i++;
}
}
public int getValue(){
return this.i;
}
};
Run run = new Run();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
Thread t4 = new Thread(run);
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i = "+run.getValue());
}
注:4个线程分别运行for循环10万次,每次i++,i是共享变量,那么最终i的值应该是40万,但是因为i++并非原子操作,所以最终结果也不一定是40万。
注:这里即使使用了volatile修饰共享变量i,也是不行的,因为volatile只是强制线程去主内存中取值和存值,而不能保证i++操作的原子性
3.加锁可以保证i++的原子性
例如:
public static void main(String[] args) {
class Run implements Runnable{
private int i;
public void run() {
for(int j=0;j<100000;j++){
i++;
}
}
public int getValue(){
return this.i;
}
};
Run run = new Run();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
Thread t4 = new Thread(run);
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i = "+run.getValue());
}
4.原子操作类
JDK5.0提供了一组atomic-class来帮助我们简化同步处理。基本工作原理是使用了同步synchronized的方法实现了对一个long, int等类型值的增、减、赋值(更新)操作.
java.util.concurrent.atomic.AtomicXxxxx类
这些类都是类似的使用方法,这里以AtomicInteger为例:
i.get()就是获得该对象对应的int值
i.incrementAndGet() 类似于 ++i
i.getAndIncrement() 类似于 i++
i.getAndDecrement() 类似于 i--
i.decrementAndGet() 类似于 --i
例如:上面的例子可以修改如下
public static void main(String[] args) {
class Run implements Runnable{
private AtomicInteger i = new AtomicInteger(0);
public void run() {
for(int j=0;j<100000;j++){
i.incrementAndGet();
}
}
public int getValue(){
return i.get();
}
};
Run run = new Run();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
Thread t4 = new Thread(run);
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i = "+run.getValue());
}
注:保证操作的原子性,每次运行后的结果都是40万
82.线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么如果一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务的话,那么就可以大大提高其运行的效率。
在Java中可以通过线程池来达到这样的效果
1)了解java.util.concurrent包中的一些类和接口
ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,该方法就是用来执行传进去的任务的
ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法
ThreadPoolExecutor继承了类AbstractExecutorService,并进行了扩展。ThreadPoolExecutor就是JKD中提供的一个线程池类。
ThreadPoolExecutor类中的一个构造器:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
参数的含义:
corePoolSize:线程池维护线程的最少数量
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime:线程池维护线程所允许的空闲时间
unit:线程池维护线程所允许的空闲时间的单位
workQueue:线程池所使用的缓冲队列
例如:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 2000, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyRun myTask = new MyRun();
executor.execute(myTask);
System.out.println("线程池中线程数目: "+executor.getPoolSize()+
",队列中等待执行的任务数目: "+executor.getQueue().size()+
",已执行完的任务数目: "+executor.getCompletedTaskCount());
}
executor.shutdown();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(executor.getCompletedTaskCount());
}
}
class MyRun implements Runnable{
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("任务结束");
}
}
注:从执行结果可以看出,当线程池中线程的数目大于5时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。
2)java.util.concurrent.Executors类
这个是一个工厂类,可以便捷的帮我们生产出线程池对象,同时该类中也提供了一些实用的工具方法.
工厂方法1:创建一个定长的线程池,可控制线程最大并发数,超出的线程会在队列中等待
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
例如:
main:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for(int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
fixedThreadPool.shutdown();
工厂方法2:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,否则则新建线程
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
例如:
main:
ThreadPoolExecutor cachedThreadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(index);
}
});
System.out.println("线程池中当前线程的数量: "+cachedThreadPool.getPoolSize());
}
cachedThreadPool.shutdown();
工厂方法3:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
例如:
main:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
singleThreadExecutor.shutdown();
工厂方法4:创建一个定长线程池,支持定时及周期性任务执行
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
例如:
main:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("延迟10秒后执行,之后再按照每3秒执行一次");
}
}, 10, 3, TimeUnit.SECONDS);
83.Callable接口和Future接口
Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且声明无法抛出异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值
java.util.concurrent.FutureTask类实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值.
例如:
假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到.
main:
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
//返回0-99的随机数,100取不到
return new Random().nextInt(100);
}
};
FutureTask<Integer> future = new FutureTask<Integer>(callable);
new Thread(future).start();
try {
//这里可以做其他的操作,等需要的时候可以调用future的get方法来拿到这个计算的结果
Thread.sleep(2000);
if(future.isDone()){
System.out.println(future.get());
}
}catch (Exception e) {
e.printStackTrace();
}
84.CompletionService接口和实现类ExecutorCompletionService
CompletionService主要用于将新添加的异步任务与已完成任务的结果分离开来
主要方法:
public Future<V> submit(Callable<V> task)
提交要执行任务,并返回表示任务结果的Future
public Future<V> take()
获取并移除表示下一个已完成任务的Future,如果目前不存在这样的任务,则等待。
public Future<V> poll()
获取并移除表示下一个已完成任务的Future,如果不存在这样的任务,则返回null。
例如:
main:
ExecutorService threadPool = Executors.newCachedThreadPool();
CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(threadPool);
for (int i = 0; i < 5; i++) {
final int taskID = i;
cs.submit(new Callable<Integer>() {
public Integer call() throws Exception {
return taskID;
}
});
}
for (int i = 0; i < 5; i++) {
try {
System.out.println(cs.take().get());
} catch (Exception e) {
e.printStackTrace();
}
}
85.流的概念
流是个抽象的概念,是对输入输出设备的抽象,Java程序中,对于数据的输入/输出操作都是以"流"的方式进行。设备可以是文件,网络,内存等
流具有方向性,至于是输入流还是输出流则是一个相对的概念,一般以程序为参考,如果数据的流向是程序至设备,我们成为输出流,如果数据的流向是设备至程序称为输入流。
数据以二进制的形式在程序与设备之间流动传输,就想水在管道里流动一样,所以就把这种数据传输的方式称之为输入流输出流
思考:代码程序中要读取文件中的内容,需要使用什么流?代码程序中要向文件中写内容,又需要使用什么流?
86.流的分类
1)按照流的方向分为输入流和输出流
2)按照处理数据的单位不同分为字节流和字符流
字节流读取的最小单位是一个字节(1byte=8bit),而字符流一次可以读取一个字符(1char = 2byte = 16bit)
3)按照功能的不同分为节点流和处理流
节点流是可以"直接"从一个数据源中读写数据的流。
处理流也可以称为功能流或者包装流,它是可以对节点流进行封装的一种流,封装后可以增加节点流的功能。
例如:FileInputStream是一个节点流,可以直接从文件读取数据,而BufferedInputStream可以包装 FileInputStream,使得其有缓冲数据的功能。
4)除了以上三种分类外,还有其他的一些类型的:对象流、缓冲流、压缩流、文件流等等,其实这些都是节点流或者处理流的子分类。当然还可以分出来其他的流类型,如果有这样需要的话。
5)不管流的分类是多么的丰富和复杂,其根源来自于四个基本的父类
字节输入流:InputStream
字节输出流:OutputStream
字符输入流:Reader
字符输出流:Writer
注:这四个父类都是抽象类
87.字节流中的常用节点流
注:java中常用的io流都在java.io包中
1)InputStream
//从输入流中读取数据的下一个字节
//如果到达流的末尾则返回 -1
public abstract int read();
//把读到的字节存到字节数组b中,并返回本次读到了多少个字节
public int read(byte[] b){..}
//把读到的字节存到字节数组b中,同时指定开始存的位置以及最大字节数,并返回本次读到了多少个字节
public int read(byte[] b,int off,int len){..}
//返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数
public int available(){..}
//跳过此输入流中数据的 n 个字节
public long skip(long n){..}
//关闭此输入流并释放与该流关联的所有系统资源
public void close(){..}
//测试此输入流是否支持 mark 和 reset 方法
public boolean markSupported(){..}
//在此输入流中标记当前的位置
public void mark(int readlimit){..}
//将此流重新定位到最后一次对此输入流调用mark方法时的位置
public void reset(){..}
2)OutputStream
//将指定的字节写入此输出流
public abstract void write(int b);
//将字节数组b中的所有字节写入此输出流
public void write(byte[] b){..}
//将字节数组b中的字节写入此输出流,指定开始位置及最大字节数
public void write(byte[] b,int off,int len){..}
//刷新此输出流并强制写出所有缓冲的输出字节
public void flush(){..}
//关闭此输出流并释放与此流有关的所有系统资源
public void close(){..}
3)InputStream的子类和OutputStream的子类几乎都是成对出现的,一个负责读数据的工作,一个负责写数据的工作
4)System.out和System.in
System类的部分源码:
public final class System{
//标准输入流
public final static InputStream in = null;
//标准输出流。
public final static PrintStream out = null;
//标准错误输出流
public final static PrintStream err = null;
public static void setIn(InputStream in){..}
public static void setOut(PrintStream out){..}
public static void setErr(PrintStream err){..}
}
标准输入流会默认从控制台读取数据
标准输出流会默认把数据输出到控制台
System.out.println(System.in.getClass());
System.out.println(System.out.getClass());
输出结果为:
class java.io.BufferedInputStream
class java.io.PrintStream
5)ByteArrayInputStream和ByteArrayOutputStream
ByteArrayInputStream可以从数组中读取字节
ByteArrayOutputStream可以把字节写到对象中的缓冲区里面,其实就是一个字节数组
6)FileInputStream和FileOutputStream
FileInputStream可以读取文件中的字节
FileOutputStream可以向文件中写进去字节
7)PipedInputStream和PipedOutputStream
PipedInputStream管道字节输入流
PipedOutputStream管道字节输出流
注:使用时需要把俩个管道进行对接
8)ObjectInputStream和ObjectOutputStream
在序列化中要使用的对象输入流和对象输出流,之后再来使用测试
9)java.io.File类
File类型对象可以表示一个文件也可以表示一个目录.
88.字节流中的处理流
也可以称为功能流或者包装流,因为它是对节点流进行包装的一种流,包装后可以增加节点流的功能。但是处理流本身并不能直接读写数据
1)BufferedInputStream和BufferedOutputStream
可以给字节流中的节点流提供代码缓冲区的输入/输出流
2)DataInputStream和DataOutputStream
可以给字节流中的节点流提供输入/输出java中不同类型的数据
3)PrintStream
PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式
89.字符流
1)Reader
public int read(){..}
public int read(char[] cbuf){..}
public abstract int read(char[] cbuf, int off,int len){..}
//指定缓冲区
//@since 1.5
public int read(CharBuffer target){..}
abstract public void close();
public long skip(long n){..}
public boolean markSupported(){..}
public void mark(int readAheadLimit){..}
public void reset(){..}
//Tells whether this stream is ready to be read
public boolean ready(){..}
2)Writer
public void write(int c){..}
public void write(char cbuf[]){..}
abstract public void write(char cbuf[], int off, int len);
public void write(String str){..}
public void write(String str, int off, int len){..}
abstract public void flush();
abstract public void close();
//@since 1.5
//和out.write(c)的效果一样
public Writer append(char c){..}
public Writer append(CharSequence csq){..}
public Writer append(CharSequence csq, int start, int end){..}
3)CharArrayReader和CharArrayWriter
CharArrayReader可以读取字符数组中的内容
CharArrayWriter可以向字符数组中写内容
4)FileReader和FileWriter
FileReader读取文件内容的便捷类,InputStreamReader的子类
FileWriter写入文件内容的便捷类,OutputStreamWriter的子类
5)PipedReader和PipedReader
PipedReader管道字符输入流
PipedReader管道字符输出流
6)BufferedReader和BufferedWriter
这个俩个流属于处理流,它们本身并不能读取数据,它们的作用是包装在其他节点流上面,为其提供额外的功能
7)PrintWriter
一般会把BufferedReader和PrintWriter配合在一起使用,因为BufferedReader可以一次读一行字符串,而PrintWriter可以一次写一行字符串(自动换行)。
90.转换流
InputStreamReader和OutputStreamWriter是一对名字中既有Stream,又有Reader或Writer的流,因为它们是转换流,负责把一个字节流转换为字符流。所以它们是字节流和字符串之间的桥梁.
注:在把字节流转换为字符流的过程中,还可以指定字符编码,避免乱码的出现。
91.对象流
1)序列化和反序列化
Java中的序列化是指把Java对象转换为字节序列的过程
对象---序列化--->01010101
Java中的反序列化是指把字节序列恢复为Java对象的过程
01010101---反序列化--->对象
思考:为什么需要序列化和反序列化?
2)如何实现序列化和反序列化
使用对象流即可实现对象的序列化和反序列化
ObjectOutputStream类中的方法可以完成对象的序列化:
public final void writeObject(Object obj){..}
ObjectInputStream类中的方法可以完成对象的反序列化:
public final Object readObject(){..}
注:这俩个对象流都属于字节流
3)序列化的要求
只有实现了java.io.Serializable接口的类的对象才可以被序列化,否则序列化时会报错
思考:测试序列化版本号的作用是什么?
4)transient关键字
在对象序列化的时候,被transient修饰的属性的值,在序列化过程中是会被忽略掉的。
92.随机访问流
java.io.RandomAccessFile类
public class RandomAccessFile extends Object{..}
这是个特殊的流,它不属于之前那些流的体系。
这个流的既可以用来读文件,也可以用来给文件中写内容,并且该类中的方法可以用来定位文件中的位置:
public native void seek(long pos);
构造器中需要设置该流的操作模式:
//对文件只读
RandomAccessFile r = new RandomAccessFile(filePath,"r");
//对文件即可读又可写
//但是一旦确定了读或者写,那么就不能在变
RandomAccessFile rw = new RandomAccessFile(filePath,"rw");
93.网络编程概述
计算机网络,是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在操作系统、网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。这个网络中包含的设备有:计算机、路由器、交换机等等
网络编程的实质就是已经建立好的计算机网络的基础之上,通过使用程序控制的特定方式,让两个(或多个)设备(例如计算机)之间进行数据传输
java中的网络编程并不要求我们之前对计算机网络的知识有那么深入的研究,只需对网络的概念有基本的认识,并了解IP地址、端口号、URL等内容,这样就能够学习并使用JDK中提供的网络编程的API
java中和网络编写相关的API都在java.net包下面
94.OSI七层模型和TCP/IP四层模型
开放系统互连参考模型 (Open System Interconnect 简称OSI)是国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)联合制定的开放系统互连参考模型,为开放式互连信息系统提供了一种功能结构的框架。它从低到高共七层分别是:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
ISO制定的OSI参考模型的过于庞大和复杂。为此,由技术人员自己开发的TCP/IP协议栈获得了更为广泛的应用。
TCP/IP协议栈是美国国防部高级研究计划局计算机网(Advanced Research Projects Agency Network,ARPANET)和其后继因特网使用的参考模型。ARPANET是由美国国防部(U.S.Department of Defense,DoD)赞助的研究网络。最初,它只连接了美国境内的四所大学。随后的几年中,它通过租用的电话线连接了数百所大学和政府部门。最终ARPANET发展成为全球规模最大的互连网络(也就是现在的因特网)。最初的ARPANET于1990年永久性地关闭。
TCP/IP参考模型分为四层:应用层、传输层、网络层和网络接口层.
注:TCP/IP参考模型也可以分为五层,就是再把网络接口层分为数据链路层和物理层。
95.TCP和UDP协议
1)TCP(transmission control protocol),传输控制协议
TCP协议是一个面向连接的、可靠的协议。它将一台主机发出的字节流无差错地发往互联网上的其他主机。在发送端,它负责把上层传送下来的字节流分成报文段并传递给下层。在接收端,它负责把收到的报文进行重组后递交给上层。TCP协议还要处理端到端的流量控制,以避免缓慢接收的接收方没有足够的缓冲区接收发送方发送的大量数据。
特点:传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据.例如:文件传输
2)UDP(user datagram protocol),用户数据报协议
UDP协议是一个不可靠的、无连接协议,主要适用于不需要对报文进行排序和流量控制的场合。
特点:与TCP特性恰恰相反,用于传输可靠性要求不高的数据.例如:QQ聊天发消息或者进行视频
96.HTTP和TPC/IP以及DNS
TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。所以,HTTP协议是建立在TCP/IP之上的一个协议。
DNS(Domain names System)是处于应用层的"服务",提供域名到IP地址之间的解析服务。可以让我们访问网络中的一台主机的时候不需要使用IP地址而是使用便于我们记忆的域名。
互联网之间是通过IP地址通信的,但是IP地址并不符合认得记忆习惯,人喜欢记忆有意义的字词。所以DNS服务就为了解决这个问题的,可以让我们使用域名来访问网络中的一台主机。
97.软件架构:C/S和B/S
C/S和B/S,是两种软件架构方式,都可以进行同样的业务处理,甚至也可以用相同的方式实现共同的逻辑。
C/S架构的全称是Client/Server,即客户端服务器端架构,客户端包含一个或多个在用户的电脑上运行的程序,而服务器端可以使用Socket来实现,服务器端的程序通过Socket与客户端的程序通信。
C/S 架构也可以看做是胖客户端架构,因为客户端需要实现很多的业务逻辑和界面展示功能。这种架构中,作为客户端的部分需要承受很大的压力,因为显示逻辑和事务处理都包含在其中。
优点:
C/S架构的界面和操作可以很丰富。
安全性能可以很容易保证。
响应速度较快。
缺点:
适用面窄,通常用于局域网中。
用户群固定,由于程序需要安装才可使用,因此不适合面向一些不可知的用户。
维护成本高,软件进行一次升级,则所有客户端的程序都需要改变。
B/S架构的全称为Browser/Server,即浏览器/服务器结构。Browser指的是Web浏览器,极少数业务逻辑在前端实现,主要业务功能都在服务器端实现。B/S架构的系统无须特别安装,只有Web浏览器即可。(通常是要基于HTTP协议进行信息交互)
B/S架构中,显示逻辑交给了Web浏览器,业务逻辑处理在放在了服务器端,这样就避免了庞大的胖客户端,减少了客户端的压力。因为客户端包含的逻辑很少,因此也被成为瘦客户端。
优点:
客户端无需安装,有Web浏览器即可。
B/S架构可以直接放在广域网上,通过一定的权限控制实现多客户访问的目的,交互性较强。
B/S架构无需升级多个客户端,升级服务器即可。
缺点:
在跨浏览器上,B/S架构不尽如人意。
显示效果要达到C/S程序的程度需要花费不少精力。
在速度和安全性上需要花费巨大的设计成本。
客户端服务器端的交互一般是请求-响应模式,通常需要刷新页面
98.java中基于TCP协议的网络编程
java.net.Socket类
socket也称为套接字,可以用来表示两台机器间通信的端点,可以用socket对客户端进行实现
java.net.ServerSocket类
ServerSocket可以实现服务器端套接字。
注:java的网络编程也可以称为套接字编程或者socket编程
99.java中基于UDP协议的网络编程
java.net.DatagramSocket类
UDP协议编程中的套接字,可以用来实现客户端也可以实现服务器端
java.net.DatagramPacket类
此类表示UDP协议通信中所传输的数据报包
注:无论客户端还是服务器都会使用这个俩个类
例如:
public class UDPServerTest {
private DatagramSocket socket;
private DatagramPacket packet;
private byte[] buf;
public UDPServerTest(int port) {
try {
socket = new DatagramSocket(port);
buf = new byte[512];
packet = new DatagramPacket(buf, buf.length);
} catch (SocketException e) {
e.printStackTrace();
}
}
public void start(){
try {
//使用packet接收数据
socket.receive(packet);
//也可以使用字节流从buf中读出数据
System.out.println("服务器接收的数据为:"+new String(buf,0,packet.getLength()));
} catch (IOException e) {
e.printStackTrace();
}finally {
if(socket!=null)socket.close();
}
}
public static void main(String[] args) {
UDPServerTest t = new UDPServerTest(9999);
t.start();
}
}
public class UDPClientTest {
private DatagramSocket socket;
private DatagramPacket packet;
public UDPClientTest() {
try {
socket = new DatagramSocket();
} catch (SocketException e) {
e.printStackTrace();
}
}
public void send(String ip,int port){
try {
byte[] buf = "hello world 中国".getBytes();
//打好数据报包,并指定要发生到的ip和端口
packet = new DatagramPacket(buf, buf.length,InetAddress.getByName(ip), port);
//发送数据
socket.send(packet);
System.out.println("客户端发送数据完毕");
} catch (IOException e) {
e.printStackTrace();
}finally {
if(socket!=null)socket.close();
}
}
public static void main(String[] args) {
UDPClientTest t = new UDPClientTest();
String ip = "127.0.0.1";
int port = 9999;
t.send(ip, port);
}
}
100.URI和URL
URI是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。
URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
URI是以一种抽象的,高层次概念定义统一资源标识,而URL则是具体的一种资源标识的方式。
在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的,schema(访问协议)必须被指定。
//是URI也是URL
ftp://ftp.is.co.za/rfc/rfc1808.txt
http://www.ietf.org/rfc/rfc2396.txt
mailto:John.Doe@example.com
telnet://192.0.2.16:80/
//只是URI
tel:+1-816-555-1212
urn:oasis:names:specification:docbook:dtd:xml:4.1.2
例如:
public static void main(String[] args) {
try {
URL url = new URL("https://www.baidu.com/?name=tom#N1");
//获取此URL的授权部分
System.out.println ("Authority = "+ url.getAuthority ());
//获取与此 URL关联协议的默认端口号
System.out.println ("Default port = " +url.getDefaultPort ());
//获取此 URL的主机名
System.out.println ("Host = " +url.getHost ());
//获取此 URL 的路径部分
System.out.println ("Path = " +url.getPath ());
//获取此 URL 的端口号,如果未设置端口号,则返回 -1
System.out.println ("Port = " +url.getPort ());
//获取此 URL 的协议名称
System.out.println ("Protocol = " +url.getProtocol ());
//获取此 URL 的查询部分
System.out.println ("Query = " +url.getQuery ());
//获取此 URL的锚点
System.out.println ("Ref = " +url.getRef ());
//获取此 URL的 userInfo部分,如果没有用户信息,则返回 null
System.out.println ("User Info = " +url.getUserInfo ());
//-------模拟浏览器向此URL发生请求----------------
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("GET");
httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0");
InputStream is = httpConn.getInputStream();
Reader in = new InputStreamReader(is);
int data = -1;
while ((data = in.read ()) != -1) {
System.out.print((char)data);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//close
}
}
输出结果:
Authority = www.baidu.com
Default port = 443
Host = www.baidu.com
Path = /
Port = -1
Protocol = https
Query = name=tom
Ref = N1
User Info = null
模拟浏览器向这个URL发送请求之后,接收到响应的内容态多,其结果保存到了文件里(url-response.txt)
在定义一个对象的特性的时候,有必要决定这些特性的可见性,即哪些特性对外部是可见的,哪些特性用于表示内部状态。
通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。
例如:
//对象不仅能再类中方法,还能在类的外部"直接"访问
public class Student{
public String name;
public void println(){
System.out.println(this.name);
}
}
public class Test{
public static void main(String[] args){
Student s = new Student();
s.name = "tom";
}
}
在类中一般不会把数据直接暴露在外部的,而使用private(私有)关键字把数据隐藏起来
例如:
public class Student{
private String name;
}
public class Test{
public static void main(String[] args){
Student s = new Student();
//编译报错,在类的外部不能直接访问类中的私有成员
s.name = "tom";
}
}
如果在类的外部需要访问这些私有属性,那么可以在类中提供对于的get和set方法,以便让用户在类的外部可以间接的访问到私有属性
例如:
//set负责给属性赋值
//get负责返回属性的值
public class Student{
private String name;
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
}
public class Test{
public static void main(String[] args){
Student s = new Student();
s.setName("tom");
System.out.println(s.getName());
}
}
思考:这样处理私有属性的作用
27.封装
1)隐藏代码的实现细节
2)统一用户的调用接口
3)提高系统的可维护性
28.方法重载
类中有多个方法,有着相同的方法名,但是方法的参数各不相同,这种情况被称为方法的重载。
方法的重载可以提供方法调用的灵活性。
例如:System.out.println()中的println方法,为什么可以把不同类型的参数传给这个方法?
例如:
public class Test{
public void test(String str){
}
public void test(int a){
}
}
方法重载必须满足一下条件:
1)方法名相同
2)参数列表不同(参数的类型、个数、顺序的不同)
public void test(Strig str){}
public void test(int a){}
public void test(Strig str,double d){}
public void test(Strig str){}
public void test(Strig str,double d){}
public void test(double d,Strig str){}
3)方法的返回值可以不同
注:在java中,判断一个类中的俩个方法是否相同,主要参考俩个方面:方法名字和参数列表
思考:方法重载有什么好处?
29)创建和初始化对象
使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用
那么对main方法中的以下代码:
Student s = new Student();
1)为对象分配内存空间,将对象的实例变量自动初始化默认值为0/false/null。(实例变量的隐式赋值)
2)如果代码中实例变量有显式赋值,那么就将之前的默认值覆盖掉。(之后可以通过例子看到这个现象)
例如:显式赋值
private String name = "tom";
3)调用构造器
4)把对象内存地址值赋值给变量。(=号赋值操作)
30.构造器
类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下俩个特点:
1)必须和类的名字相同
2)必须没有返回类型,也不能写void
构造器的作用:
1)使用new创建对象的时候必须使用类的构造器
2)构造器中的代码执行后,可以给对象中的属性初始化赋值
例如:
public class Student{
private String name;
public Student(){
name = "tom";
}
}
构造器重载:
除了无参构造器之外,很多时候我们还会使用有参构造器,在创建对象时候可以给属性赋值.
例如:
public class Student{
private String name;
public Student(String name){
this.name = name;
}
}
构造器之间的调用:
使用this关键字,在一个构造器中可以调用另一个构造器的代码。
注意:this的这种用法不会产生新的对象,只是调用了构造器中的代码而已.一般情况下只有使用new关键字才会创建新对象。
例如:
public class Student{
private String name;
public Student(){
this();
}
public Student(String name){
this.name = name;
}
}
默认构造器:
在java中,即使我们在编写类的时候没有写构造器,那么在编译之后也会自动的添加一个无参构造器,这个无参构造器也被称为默认的构造器。
例如:
public class Student{
}
main:
//编译通过,因为有无参构造器
Student s = new Student();
但是,如果我们手动的编写了一个构造器,那么编译后就不会添加任何构造器了
例如:
public class Student{
private String name;
public Student(String name){
this.name = name;
}
}
main:
//编译报错,因为没有无参构造器
Student s = new Student();
31.继承
1)继承是类和类之间的一种关系
除此之外,类和类之间的关系还有依赖、组合、聚合等。
2)继承关系的俩个类,一个为子类(派生类),一个为父类(基类)。
子类继承父类,使用关键字extends来表示
例如:
public class student extends Person{
}
3)子类和父类之间,从意义上讲应该具有"is a"的关系.
例如:
student is a person
dog is a animal
4)类和类之间的继承是单继承
一个子类只能"直接"继承一个父类,就像是一个人只能有一个亲生父亲
一个父类可以被多子类继承,就像一个父亲可以有多个孩子
注:java中接口和接口之间,有可以继承,并且是多继承。
5)父类中的属性和方法可以被子类继承
子类中继承了父类中的属性和方法后,在子类中能不能直接使用这些属性和方法,是和这些属性和方法原有的修饰符(public protected defaul private)相关的。
例如:
父类中的属性和方法使用public修饰,在子类中继承后"可以直接"使用
父类中的属性和方法使用private修饰,在子类中继承后"不可以直接"使用
注:具体细则在修饰符部分详细说明
父类中的构造器是不能被子类继承的,但是子类的构造器中,会隐式的调用父类中的无参构造器(默认使用super关键字)。
注:具体细节在super关键字部分详细说明
6)Object类
java中的每一个类都是"直接" 或者 "间接"的继承了Object类.所以每一个对象都和Object类有"is a"的关系。从API文档中,可以看到任何一个类最上层的父类都是Object。(Object类本身除外)
AnyClass is a Object
例如:
System.out.println(任何对象 instanceof Object);
//输出结果:true
注:任何对象也包含数组对象
例如:
//编译后,Person类会默认继承Object
public class Person{}
//Student是间接的继承了Object
public class Student extends Person{}
在Object类中,提供了一些方法被子类继承,那么就意味着,在java中,任何一个对象都可以调用这些被继承过来的方法。(因为Object是所以类的父类)
例如:toString方法、equals方法、getClass方法等
注:Object类中的每一个方法之后都会使用到.
思考:继承有什么好处?
32.super关键字
子类继承父类之后,在子类中可以使用this来表示访问或调用子类中的属性或方法,使用super就表示访问或调用父类中的属性和方法
1)访问父类中的属性
例如:
public class Person{
protected String name = "zs";
}
public class Student extends Person{
private String name = "lisi";
public void test{
System.out.println(name);
System.out.println(this.name);
System.out.println(super.name);
}
}
2)调用父类中的方法
例如:
public class Person{
public void print(){
System.out.println("Person");
}
}
public class Student extends Person{
public void print(){
System.out.println("Student");
}
public void test(){
print();
this.print();
super.print();
}
}
3)调用父类中的构造器
例如:
public class Person{
}
public class Student extends Person{
//编译通过,子类构造器中会隐式的调用父类的无参构造器
//super();
public Student(){
}
}
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
public class Student extends Person{
//编译报错,子类构造器中会隐式的调用父类的无参构造器,但是父类中没有无参构造器
//super();
public Student(){
}
}
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
public class Student extends Person{
//编译通过,子类构造器中显式的调用父类的有参构造器
public Student(){
super("tom");
}
}
注:不管是显式还是隐式的父类的构造器,super语句一定要出现在子类构造器中第一行代码。所以this和super不可能同时使用其调用构造器的功能,因为它们都要出现在第一行代码位置。
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
public class Student extends Person{
//编译报错,super调用构造器的语句不是第一行代码
public Student(){
System.out.println("Student");
super("tom");
}
}
例如:
public class Person{
protected String name;
public Person(String name){
this.name = name;
}
}
//编译通过
public class Student extends Person{
private int age;
public Student(){
this(20);
}
public Student(int age){
super("tom");
this.age = age;
}
}
思考:这样做的目的是什么?
33.方法重写(方法覆盖)
1)方法重写只存在于子类和父类(包括直接父类和间接父类)之间。在同一个类中方法只能被重载,不能被重写.
2)静态方法不能覆盖
a. 父类的静态方法不能被子类重写为非静态方法 //编译出错
b. 父类的非静态方法不能被子类重写为静态方法;//编译出错
c. 子类可以定义与父类的静态方法同名的静态方法(但是这个不是覆盖)
例如:
A类继承B类 A和B中都一个相同的静态方法test
B a = new A();
a.test();//调用到的时候B类中的静态方法test
A a = new A();
a.test();//调用到的时候A类中的静态方法test
可以看出静态方法的调用只和变量声明的类型相关
这个和非静态方法的重写之后的效果完全不同
3)私有方法不能被子类重写
子类继承父类后,是不能直接访问父类中的私有方法的,那么就更谈不上重写了。
例如:
public class Person{
private void run(){}
}
//编译通过,但这不是重写,只是俩个类中分别有自己的私有方法
public class Student extends Person{
private void run(){}
}
4)重写的语法
1)方法名字必须一样
2)参数列表必须一样
3)访问修饰符可以扩大,但不能缩小
4)抛出异常范围可以缩小,但不能扩大
5)返回类型可以相同,也可以不同,如果不同,那么重写后的方法返回类型必须是原来方法返回类型的子类型.
例如:
public class Person{
public void run(){}
protected Object test()throws Exception{
return null;
}
}
//编译通过,子类继承父类,重写了run和test方法.
public class Student extends Person{
public void run(){}
public String test(){
return "";
}
}
5)为什么要重写
子类继承父类,继承了父类中的方法,但是父类中的方法并不一定能满足子类中的功能需要,所以子类中需要把方法进行重写。
34.多态
允许不同类的对象对同一消息做出响应。即同一消息可以根据发送对象的不同而采用多种不同的行为方式。
1)一个对象的实际类型是确定的
例如: new Student(); new Person();等
2)可以指向对象的引用的类型有很多
例如: Student继承了Person类
Student s1 = new Student();
Person s2 = new Student();
Object s3 = new Student();
因为Person和Object都是Student的父类型
注:一个对象的实际类型是确定,但是可以指向这个对象的引用的类型,却是可以是这对象实际类型的任意父类型。
3)一个父类引用可以指向它的任何一个子类对象
例如:
Object o = new AnyClass();
Person p = null;
p = new Student();
p = new Teacher();
p = new Person();
4)多态中的方法调用
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
}
//调用到的run方法,是Student从Person继承过来的run方法
main:
Person p = new Student();
p.run();
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
public void run(){
//重写run方法
}
}
//调用到的run方法,是Student中重写的run方法
main:
Person p = new Student();
p.run();
注:子类继承父类,调用a方法,如果a方法在子类中没有重写,那么就是调用的是子类继承父类的a方法,如果重写了,那么调用的就是重写之后的方法。
5)子类中独有方法的调用
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
public void test(){
}
}
main:
Person p = new Student();
//调用到继承的run方法
p.run();
//编译报错,因为编译器检查变量p的类型是Person,但是在Person类中并没有发现test方法,所以编译报错.
p.test();
注:一个变量x,调用一个方法test,编译器是否能让其编译通过,主要是看声明变量x的类型中有没有定义test方法,如果有则编译通过,如果没有则编译报错.而不是看x所指向的对象中有没有test方法.
思考:为什么编译器会是这样检查?
6)子类引用和父类引用指向对象的区别
Student s = new Student();
Person p = new Student();
变量s能调用的方法是Student中有的方法(包括继承过来的),变量p能调用的方法是Person中有的方法(包括继承过来的)。
但是变量p是父类型的,p不仅可以指向Student对象,还可以指向Teacher类型对象等,但是变量s只能指向Studnet类型对象,及Student子类型对象。变量p能指向对象的范围是比变量s大的。
Object类型的变量o,能指向所有对象,它的范围最大,但是使用变量o能调用到的方法也是最少的,只能调用到Object中的声明的方法,因为变量o声明的类型就是Object.
7)多态的使用
如果使用多态解决以下问题:
public class Game{
public void start(BasketBall ball){
ball.run();
}
}
public class BasketBall{
public void run(){
//执行篮球游戏程序
}
}
在这种情况下,用户不断的变动需求:
想要增加足球游戏的运行
想要增加乒乓球游戏的运行
想要增加橄榄球游戏的运行
想要增加羽毛球球游戏的运行
想要增加铅球游戏的运行
....
如何处理?
35.instanceof和类型转换
例如:
public class Person{
public void run(){}
}
public class Student extends Person{
}
public class Teacher extends Person{
}
例如:
main:
Object o = new Student();
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
System.out.println(o instanceof String);//false
---------------------------
Person o = new Student();
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
//编译报错
System.out.println(o instanceof String);
---------------------------
Student o = new Student();
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
//编译报错
System.out.println(o instanceof Teacher);
//编译报错
System.out.println(o instanceof String);
注1:
System.out.println(x instanceof Y);
该代码能否编译通过,主要是看声明变量x的类型和Y是否存在子父类的关系.有子父类关系就编译通过,没有子父类关系就是编译报错
注2:
System.out.println(x instanceof Y);
输出结果是true还是false,主要是看变量x所指向的对象实际类型是不是Y类型的子类型.
例如:
main:
Object o = new Person();
System.out.println(o instanceof Student);//false
System.out.println(o instanceof Person);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
System.out.println(o instanceof String);//false
类型转换
public class Person{
public void run(){}
}
public class Student extends Person{
public void go(){}
}
public class Teacher extends Person{
}
1)为什么要类型转换
//编译报错,因为p声明的类型Person中没有go方法
Person p = new Student();
p.go();
//需要把变量p的类型进行转换
Person p = new Student();
Student s = (Student)p;
s.go();
或者
//注意这种形式前面必须要俩个小括号
((Student)p).go();
2)类型转换中的问题
//编译通过
Object o = new Student();
Person p = (Person)o;
//编译通过
Object o = new Student();
Student s = (Student)o;
//编译通过,运行报错
Object o = new Teacher();
Student s = (Student)o;
即:
X x = (X)o;
运行是否报错,主要是变量o所指向的对象实现类型,是不是X类型的子类型,如果不是则运行就会报错。
36.static修饰符
1)static变量
在类中,使用static修饰的成员变量,就是静态变量,反之为非静态变量。
静态变量和非静态变量的区别
静态变量数属于类的,"可以"使用类名来访问,非静态变量是属于对象的,"必须"使用对象来访问.
例如:
public class Student{
private static int age;
private double score;
public static void main(String[] args) {
Student s = new Student();
//推荐使用类名访问静态成员
System.out.println(Student.age);
System.out.println(s.age);
System.out.println(s.score);
}
}
静态变量对于类而言在内存中只有一个,能被类的所有实例所共享。实例变量对于类的每个实例都有一份,它们之间互不影响.
例如:
public class Student{
private static int count;
private int num;
public Student() {
count++;
num++;
}
public static void main(String[] args) {
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
Student s4 = new Student();
//因为还是在类中,所以可以直接访问私有属性
System.out.println(s1.num);
System.out.println(s2.num);
System.out.println(s3.num);
System.out.println(s4.num);
System.out.println(Student.count);
System.out.println(s1.count);
System.out.println(s2.count);
System.out.println(s3.count);
System.out.println(s4.count);
}
}
在加载类的过程中为静态变量分配内存,实例变量在创建对象时分配内存
所以静态变量可以使用类名来直接访问,而不需要使用对象来访问.
2)static方法
在类中,使用static修饰的成员方法,就是静态方法,反之为非静态方法。
静态方法和非静态方法的区别
静态方法数属于类的,"可以"使用类名来调用,非静态方法是属于对象的,"必须"使用对象来调用.
静态方法"不可以"直接访问类中的非静态变量和非静态方法,但是"可以"直接访问类中的静态变量和静态方法
注意:this和super在类中属于非静态的变量.(静态方法中不能使用)
例如:
public class Student{
private static int count;
private int num;
public void run(){}
public static void go(){}
public static void test(){
//编译通过
System.out.println(count);
go();
//编译报错
System.out.println(num);
run();
}
}
非静态方法"可以"直接访问类中的非静态变量和非静态方法,也"可以"直接访问类中的静态变量和静态方法
例如:
public class Student{
private static int count;
private int num;
public void run(){}
public static void go(){}
public void test(){
//编译通过
System.out.println(count);
go();
//编译通过
System.out.println(num);
run();
}
}
思考:为什么静态方法和非静态方法不能直接相互访问?
父类的静态方法可以被子类继承,但是不能被子类重写
例如:
public class Person {
public static void method() {}
}
//编译报错
public class Student extends Person {
public void method(){}
}
例如:
public class Person {
public static void test() {
System.out.println("Person");
}
}
//编译通过,但不是重写
public class Student extends Person {
public static void test(){
System.out.println("Student");
}
}
main:
Perosn p = new Student();
p.test();//输出Person
p = new Person();
p.test();//输出Perosn
和非静态方法重写后的效果不一样
父类的非静态方法不能被子类重写为静态方法
例如:
public class Person {
public void test() {
System.out.println("Person");
}
}
//编译报错
public class Student extends Person {
public static void test(){
System.out.println("Student");
}
}
3)代码块和静态代码块
类中可以编写代码块和静态代码块
例如:
public class Person {
{
//代码块(匿名代码块)
}
static{
//静态代码块
}
}
匿名代码块和静态代码块的执行
因为没有名字,在程序并不能主动调用这些代码块。
匿名代码块是在创建对象的时候自动执行的,并且在构造器执行之前。同时匿名代码块在每次创建对象的时候都会自动执行.
静态代码块是在类加载完成之后就自动执行,并且只执行一次.
注:每个类在第一次被使用的时候就会被加载,并且一般只会加载一次.
例如:
public class Person {
{
System.out.println("匿名代码块");
}
static{
System.out.println("静态代码块");
}
public Person(){
System.out.println("构造器");
}
}
main:
Student s1 = new Student();
Student s2 = new Student();
Student s3 = new Student();
//输出结果:
静态代码块
匿名代码块
构造器
匿名代码块
构造器
匿名代码块
构造器
匿名代码块和静态代码块的作用
匿名代码块的作用是给对象的成员变量初始化赋值,但是因为构造器也能完成这项工作,所以匿名代码块使用的并不多。
静态代码块的作用是给类中的静态成员变量初始化赋值。
例如:
public class Person {
public static String name;
static{
name = "tom";
}
public Person(){
name = "zs";
}
}
main:
System.out.println(Person.name);//tom
注:在构造器中给静态变量赋值,并不能保证能赋值成功,因为构造器是在创建对象的时候才指向,但是静态变量可以不创建对象而直接使用类名来访问.
创建和初始化对象的过程
Student s = new Student();
1)Student类之前没有进行类加载
1类加载,同时初始化类中静态的属性
2执行静态代码块
3分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
4调用Student的父类构造器
5对Student中的属性进行显示赋值(如果有的话)
6执行匿名代码块
7执行构造器
8返回内存地址
注:子类中非静态属性的显示赋值是在父类构造器执行完之后和子类中的匿名代码块执行之前的时候
例如:
public class Person{
private String name = "zs";
public Person() {
System.out.println("Person构造器");
print();
}
public void print(){
System.out.println("Person print方法: name = "+name);
}
}
public class Student extends Person{
private String name = "tom";
{
System.out.println("Student匿名代码块");
}
static{
System.out.println("Student静态代码块");
}
public Student(){
System.out.println("Student构造器");
}
public void print(){
System.out.println("student print方法: name = "+name);
}
public static void main(String[] args) {
new Student();
}
}
//输出结果:
Student静态代码块
Person构造器
student print方法: name = null
Student匿名代码块
Student构造器
Student s = new Student();
2)Student类之前已经进行了类加载
1.分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
2.调用Student的父类构造器
3.对Student中的属性进行显示赋值(如果有的话)
4.执行匿名代码块
5.执行构造器
6.返回内存地址
4)静态导入
静态导入是JDK5.0引入的新特性。
例如:
import static java.lang.Math.random;
import static java.lang.Math.PI;
public class Test {
public static void main(String[] args) {
//之前是需要Math.random()调用的
System.out.println(random());
System.out.println(PI);
}
}
37.final修饰符
1)修饰类
用final修饰的类不能被继承,没有子类。
例如:我们是无法写一个类去继承String类,然后对String类型扩展的,因为API中已经被String类定义为final的了.
我们也可以定义final修饰的类:
public final class Action{
}
//编译报错
public class Go extends Action{
}
2)修饰方法
用final修饰的方法能被继承,但是不能被子类的重写。
例如:每个类都是Object类的子类,继承了Object中的众多方法,在子类中可以重写toString方法、equals方法等,但是不能重写getClass方法 wait方法等,因为这些方法都是使用fianl修饰的。
我们也可以定义final修饰的方法:
public class Person{
public final void print(){}
}
//编译报错
public class Student extends Person{
public void print(){
}
}
3)修饰变量
用final修饰的变量表示常量,只能被赋一次值.其实使用final修饰的变量也就成了常量了,因为值不会再变了。
修饰局部变量:
例如:
public class Person{
public void print(final int a){
//编译报错,不能再次赋值,传参的时候已经赋过了
a = 1;
}
}
public class Person{
public void print(){
final int a;
a = 1;
//编译报错,不能再次赋值
a = 2;
}
}
修饰成员变量:
非静态成员变量:
public class Person{
private final int a;
}
只有一次机会,可以给此变量a赋值的位置:
声明的同时赋值
匿名代码块中赋值
构造器中赋值(类中出现的所有构造器都要写)
静态成员变量:
public class Person{
private static final int a;
}
只有一次机会,可以给此变量a赋值的位置:
声明的同时赋值
静态代码块中赋值
4)修饰引用变量
main:
final Student s = new Student();
//编译通过
s.setName("tom");
s.setName("zs");
//编译报错,不能修改引用s指向的内存地址
s = new Student();
38.abstract修饰符
abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。
1)抽象类和抽象方法的关系
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
2)语法
public abstract class Action{
public abstract void doSomething();
}
public void doSomething(){...}
对于这个普通方法来讲:
"public void doSomething()"这部分是方法的声明
"{...}"这部分是方法的实现,如果大括号中什么都没写,就叫方法的空实现
声明类的同时,加上abstract修饰符就是抽象类
声明方法的时候,加上abstract修饰符,并且去掉方法的大口号,同时结尾加上分号,该方法就是抽象方法。
3)特点及作用
抽象类,不能使用new关键在来创建对象,它是用来让子类继承的。
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
注:子类继承抽象类后,需要实现抽象类中没有实现的抽象方法,否则这个子类也要声明为抽象类。
例如:
public abstract class Action{
public abstract void doSomething();
}
main:
//编译报错,抽象类不能new对象
Action a = new Action();
//子类继承抽象类
public class Eat extends Action{
//实现父类中没有实现的抽象方法
public void doSomething(){
//code
}
}
main:
Action a = new Eat();
a.doSomething();
注:子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。
思考1:抽象类不能new对象,那么抽象类中有没有构造器?
思考2:抽象类和抽象方法意义(为什么要编写抽象类、抽象方法)
有抽象方法的抽象类
无抽象方法的抽象类
39.接口
1)接口和抽象类区别
抽象类也是类,除了可以写抽象方法以及不能直接new对象之外,其他的和普通类没有什么不一样的。
接口已经另一种类型了,和类是有本质的区别的,所有不能用类的标准去衡量接口。
声明类的关键字是class,声明接口的关键字是interface。
抽象类是用来被继承的,java中的类是单继承。
类A继承了抽象类B,那么类A的对象就属于B类型了,可以使用多态
一个父类的引用,可以指向这个父类的任意子类对象
注:继承的关键字是extends
接口是用来被类实现的,java中的接口可以被多实现。
类A实现接口B、C、D、E..,那么类A的对象就属于B、C、D、E等类型了,可以使用多态
一个接口的引用,可以指向这个接口的任意实现类对象
注:实现的关键字是implements
2)接口中的方法都是抽象方法
接口中可以不写任何方法,但如果写方法了,该方法必须是抽象方法
例如:
public interface Action{
public abstract void run();
//默认就是public abstract修饰的
void test();
public void go();
}
3)接口中的变量都是静态常量(public static final修饰)
接口中可以不写任何属性,但如果写属性了,该属性必须是public static final修饰的静态常量。
注:可以直接使用接口名访问其属性。因为是public static修饰的
例如:
public interface Action{
public static final String NAME = "tom";
//默认就是public static final修饰的
int AGE = 20;
}
main:
System.out.println(Action.NAME);
System.out.println(Action.AGE);
4)一个类可以实现多个接口
例如:
public class Student implements A,B,C,D{
//Student需要实现接口A B C D中所有的抽象方法
//否则Student类就要声明为抽象类,因为有抽象方法没实现
}
main:
A s1 = new Student();
B s2 = new Student();
C s3 = new Student();
D s4 = new Student();
注:
s1只能调用接口A中声明的方法以及Object中的方法
s2只能调用接口B中声明的方法以及Object中的方法
s3只能调用接口C中声明的方法以及Object中的方法
s4只能调用接口D中声明的方法以及Object中的方法
注:必要时可以类型强制转换
例如:
接口A 中有test()
接口B 中有run()
A s1 = new Student();
s1.test();
B s2 = new Student();
s2.run();
if(s1 instanceof B){
((B)s1).run();
}
6)一个接口可以继承多个父接口
public interface A{
public void testA();
}
public interface B{
public void testB();
}
//接口C把接口A B中的方法都继承过来了
public interface C extends A,B{
public void testC();
}
//Student相当于实现了A B C三个接口,需要实现所有的抽象方法
//Student的对象也就同时属于A类型 B类型 C类型
public class Student implements C{
public viod testA(){}
public viod testB(){}
public viod testC(){}
}
main:
C o = new Student();
System.out.println(o instanceof A);//true
System.out.println(o instanceof B);//true
System.out.println(o instanceof C);//true
System.out.println(o instanceof Student);//true
System.out.println(o instanceof Object);//true
System.out.println(o instanceof Teacher);//false
//编译报错
System.out.println(o instanceof String);
注:
System.out.println(o instanceof X);
如果o是一个接口类型声明的变量,那么只要X不是一个final修饰的类,该代码就能通过编译,至于其结果是不是true,就要看变量o指向的对象的实际类型,是不是X的子类或者实现类了。
注:一个引用所指向的对象,是有可能实现任何一个接口的。(java中的多实现)
40.访问控制
public protected default private是java中的访问控制修饰符.
注:这里的default的意思是什么都不写
例如:
public String name;
protected String name;
//default就表示这种情况
String name;
private String name;
1)修饰类
1.普通类
只能使用public和default来修饰源文件中编写的java类
public表示其他任何地方都有权限访问这个类
default表示只有和当前类在同一个包的类才有权限访问
例如: Test.java中有俩个类
----------------------
public class Test{
private class A{}
class B{}
protected class C{}
public class D{}
}
class Action{}
//编译报错
protected class Action2{}
private class Action3{}
----------------------
2.内部类
四个修饰符可以修饰特定的内部类
例如:
//四个内部类
public class Test{
private class A{}
class B{}
protected class C{}
public class D{}
}
2)修饰属性和方法
四个修饰都可以修饰类中的属性和方法,那么就以修饰属性为例来说明.(效果和修饰方法是一样的)
public class Person{
public String pubStr = "pubStr";
protected String proStr = "proStr";
String defStr = "defStr";
private String priStr = "priStr";
}
从四个地方测试这些属性的可见性:
类中 同包类中 子类中 不同包类中
public Y Y Y Y
protected Y Y Y N
default Y Y N N
private Y N N N
注:这里的子类中默认指的是不同包下面的子类
41.内部类
内部类不是在一个java源文件中编写俩个平行的俩个类,而是在一个类的内部再定义另外一个类。
我们可以把外边的类称为外部类,在其内部编写的类称为内部类。
内部类分为四种,成员内部类、静态内部类、局部内部类、匿名内部类
1)成员内部类(实例内部类、非静态内部类)
注:成员内部类中不能写静态属性和方法
public class InstanceOutterClass{
private String name;
private static int age;
public void say(){}
public static void go(){}
public class InstanceInnerClass{}
}
2)静态内部类
注:静态内部类中可以写静态属性和方法
public class StaticOutterClass{
private String name;
private static int num;
public void say(){}
public static void go(){}
public static class StaticInnerClass{}
}
3)局部内部类
//局部内部类是在一个方法内部声明的一个类
//局部内部类中可以访问外部类的成员变量及方法
//局部内部类中如果要访问该内部类所在方法中的局部变量,那么这个局部变量就必须是final修饰的
public class LocalOutterClass{
private String name;
private static int age;
public void say(){}
public static void go(){}
public void test(final int j){
final int i = 10;
class LocalInnerClass{
private String name;
private int age;
public void say(){
System.out.println(name);
System.out.println(this.name);
System.out.println(LocalInnerClass.this.name);
System.out.println(LocalOutterClass.this.name);
System.out.println(LocalOutterClass.age);
LocalOutterClass.this.say();
LocalOutterClass.go();
System.out.println(i);
System.out.println(j);
}
}
LocalInnerClass lic = new LocalInnerClass();
lic.say();
}
}
4)匿名内部类
//匿名内部类是最常用的一种内部类
1)匿名内部类需要依托于其他类或者接口来创建
如果依托的是类,那么创建出来的匿名内部类就默认是这个类的子类
如果依托的是接口,那么创建出来的匿名内部类就默认是这个接口的实现类。
2)匿名内部类的声明必须是在使用new关键字的时候
匿名内部类的声明及创建对象必须一气呵成,并且之后能反复使用,因为没有名字。
例如:
A是一个类(普通类、抽象类都可以)
依托于A类创建一个匿名内部类对象
main:
A a = new A(){
//实现A中的抽象方法
//或者重写A中的普通方法
};
注:这个大括号里面其实就是这个内部类的代码,只不过是声明该内部类的同时就是要new创建了其对象,并且不能反复使用,因为没有名字。
例如:
B是一个接口
依托于B接口创建一个匿名内部类对象
B b = new B(){
//实现B中的抽象方法
};
3)匿名内部类除了依托的类或接口之外,不能指定继承或者实现其他类或接口,同时也不能被其他类所继承,因为没有名字。
4)匿名内部中,我们不能写出其构造器,因为没有名字。
5)匿名内部中,除了重写上面的方法外,一般不会再写其他独有的方法,因为从外部不能直接调用到。(间接是调用到的)
public interface Work{
void doWork();
}
public class AnonymousOutterClass{
private String name;
private static int age;
public void say(){}
public static void go(){}
public void test(){
final int i = 90;
Work w = new Work(){
public void doWork(){
System.out.println(AnonymousOutterClass.this.name);
System.out.println(AnonymousOutterClass.age);
AnonymousOutterClass.this.say();
AnonymousOutterClass.go();
System.out.println(i);
}
};
w.doWork();
}
}
42.包装类(Wrapper)
在java中,有八种基本的数据类,这八种类型所表示的数据只是一些简单的数值(8/16/32/64位的数字),它们都不是对象,所以在API又针对这八种类型提供了对于的类类型,也就是包装类型,它们分别是:
Primitive-Type Wrapper-Class
byte Byte
short Short
int Integer
long Long
float Float
double Double
boolean Boolean
char Character
1)这些对应的包装类型都是java.lang下的类
在代码中可以直接使用而不需要导入.
2)每种包装类中都定义属性和方法供其对象使用
这是从基本类型变为包装类型最明显的区别,现在指向的是对象了,可以访问对象中的属性和调用对象中的方法了,之前只是一个简单的数值,没有任何属性和方法。
例如:
//使用变量i不能访问任何属性和访问
//因为变量i没有指向对象,也不能指向对象
int i = 1;
//查看API可知Integer类中的构造器如果使用
//使用变量i可以访问Integer类中定义的属性和方法
//因为变量i指向的是对象,这是一个引用类型的变量
Integer i = new Integer(1);
或者
Integer i = new Integer("1");
注:包装类中的属性和方法大都是静态的,可以使用类名直接访问。(也有非静态的方法,就需要使用对象调用了)
例如:
main:
System.out.println(Integer.MAX_VALUE);
System.out.println(Integer.MIN_VALUE);
System.out.println(Integer.toBinaryString(100));
3)JDK1.5增加了自动拆箱装箱的功能(低版本JDK中编译报错)
注:针对八种基本类型及其包装类型,这里以int和Integer为例
//自动装箱,把字面值1包装成Integer类型对象
Integer i1 = 1;
//自动拆箱,把Integer对象转变为一个简单的int类型值
int i2 = new Integer(1);
注:
//编译通过
Integer a = 1;
//编译报错
//1可以自动装箱为Integer类型对象
//但是Integer和Long没有任何关系
Long b = 1;
//因为int是32位 long是64位
int --可以自动转换为--> long
//因为Integer和Long没有子父类关系
Integer --不能转换为--> Long
43.==和equals方法的区别
1)基本类型变量的使用
基本类型变量指向的不是对象,不能调用方法,所以只能使用==进行比较,并且比较的是基本类型变量的简单数值是否相等。
2)引用类型变量的使用
引用类型变量指向的是对象,所以既可以使用==进行比较,也可以使用equals进行比较
区别在于:
equals是Object中的方法,每个对象都可以调用和其他对象比较是否相等,默认比较的是俩个引用所指向的对象内存地址值(和==号的作用一样),但是也可以在类中对equals方法进行重写,按照自己的逻辑来比较俩个对象。
==是java中的基本的操作符,是不能重写的,俩个引用使用==进行比较,那么比较的是引用所指向的内存地址值是否相同。
44.toString方法hashCode方法
toString和hashCode都是Object类中的方法,所以每个对象都可以直接调用。
hashCode方法,返回该对象的哈希码值,Object中的实现一般是通过将该对象的内部地址转换成一个整数。
toString方法,返回该对象的字符串表示。
其形式为:
类的全限定名@hashCode方法返回值的十六进制形式
即:
getClass().getName() + "@" + Integer.toHexString(hashCode())
例如:
public class Student{
}
main:
Student s = new Student();
String str1 = s.toString();
String str2 = s.getClass().getName()+"@"+Integer.toHexString(s.hashCode());
System.out.println(str1);
System.out.println(str2);
输出结果:
com.briup.ch06.Student@6084fa6a
com.briup.ch06.Student@6084fa6a
注:我们可以把最后的那个六十进制数字认为是这个对象的内存地址,但是其实并不是真的地址值,而是这个对象的哈希码值,这个哈希码值默认又是通过对象地址值转换过来的一个数字。(如果我们重写了hashCode方法,那这个返回的哈希码值就真的和对象内存地址没有一点关系了)
例如:
Student s = new Student();
//打印引用时,默认调用其所指向对象的toString方法
System.out.println(s);
System.out.println(s.toString());
注:有些时候还是会有一点区别
Student s = null;
//打印null
System.out.println(s);
//运行报错,空指针异常
System.out.println(s.toString());
45.集合
数组的长度是固定的,在很多情况下,一组数据的数目是不固定的.
为了使程序能方便地存储和操纵数目不固定的一组数据,JDK的API中提供了集合,所有集合类都位于java.util包中
与数组不同,集合中不能存放基本类型数据,而只能存放对象的引用。(通常会直接描述为集合中存放的对象)
集合的特点:
1.可存放不同类型的对象(必须是对象)
数组只能存放同一类型数据,但是可以存放基本类型数据
2.集合的长度可以自动增加
数组的长度一旦确定,就不能再改变
3.集合对象中有众多方法可以直接调用进行元素(数据)操作
数组对象中没有方法可以对数据进行操作
4.java.util包中的辅助工具类Colletions,也能对集合中的元素进行操作
java.util包中的辅助工具类Arrays,是用来对数组中的元素进行操作的。
1)Iterable接口
实现这个接口的对象,可以使用"foreach"语句对其进行循环遍历.
并且该接口中提供了一个方法可以返回一个迭代器对象,迭代器对象在这里的作用就是循环遍历集合中的每一个元素。
public Iterator iterator();
2)Collection接口
Collection接口继承了Iterable接口。
Collection类型集合体系结构中的根接口,JDK中不提供此接口的任何直接实现,而是提供更具体的子接口(List和Set)。
Collection接口中提供了很多集合中通用的方法。
例如:
add 方法
remove 方法
clear 方法
size 方法
....
注:JDK5以上的自动装箱拆箱
3)Collection类型集合的遍历
1.通用方式: 使用集合中提供的迭代器
Collection c = new ..;(List或Set类型都可以)
//调用add方法存数据
Iterator iterator = c.iterator();
while(iterator.hasNext()){
Object obj = iterator.next();
System.out.println(obj);
}
2.List集合的特有方式:get方法通过下标访问元素
(注:Set集合不能这样使用)
List list = new ArrayList();
//调用add方法存数据
for(int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
3.foreach循环(增强for循环)
注:JDK5.0版本及以上可用
Collection c = new ..;(List或Set类型都可以)
//调用add方法存数据
//自动遍历集合c中的元素,并且每次使用变量o来接收
for(Object o:c){
System.out.println(o);
}
注:foreach循环也可以遍历数组
4)List接口和Set接口
List和Set都是Collection接口的子接口,但它们各自的特点不同。
List类型集合特点:集合中的元素有序且可重复
Set类型集合特点 :集合中的元素不可重复,有没有序要看Set接口具体的实现类是谁。
注:有序指的是元素放到集合中的顺序和循环遍历出来的顺序一致
List接口常见的实现类有:ArrayList、LinkedList、Vector等
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构
2.对于数据的随机访问,ArrayList效率优于LinkedList,因为LinkedList要移动指针
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据
4.Vector是线程安全的集合,但是速度慢
5.查看API可知,List接口中是这些集合中共有的方法,而且每个集合类中又有一些自己独有的方法。
Set接口常见的实现类有:HashSet、LinkedHashSet
HashSet集合中元素的特点 :无序不可重复
LinkedHashSet集合中元素的特点:有序不可重复
思考:set集合中元素的不可重复是怎么实现的?
5)SortedSet接口和TreeSet类
SortedSet接口是Set接口的子接口,除了拥有Set集合的一些基本特点之外,还提供了排序的功能。
TreeSet类就是SortedSet接口的实现类
6)TreeSet类的排序功能
注:TreeSet排序永远都是从小到大排,但是谁大谁小是我们的方法说了算的
1.自然排序
核心:让元素自身具备比较性,需要实现Comparable接口,重写其compareTo方法,比较出大小后即可进行排序
java.lang.Comparable接口
例如:
public class Student implements Comparable{
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public int compareTo(Object o) {
Student s = (Student)o;
if(this.id<s.id){
return -1;
}
else if(this.id>s.id){
return 1;
}
return 0;
}
}
注:
s1.compareTo(s2);
返回正数说明s1大
返回负数说明s1小
返回0说明s1和s2相等
2.比较器排序(定制排序、客户化排序)
核心:使用比较器进行元素之间的比较,比较出大小后即可进行排序。
java.util.Comparator接口
例如:
main:
Set set = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
Student s1 = (Student) o1;
Student s2 = (Student) o2;
return (int)(s1.getId()-s2.getId());
}
});
注:比较器排序的优先级比自然排序的高
思考:在TreeSet中整数默认是从小到大排序,字符是默认是按a-z的顺序,那么怎么能让它们的顺序是倒过来的?
7)Collection类型集合与数组之间的转换
Collection接口中定义了俩个方法,可以将当前集合转换为数组:
Object[] toArray() 返回包含集合中所有元素的数组
例如:
Collection c = new ...;(List或者Set的实现类都可以)
Object[] array = c.toArray();
System.out.println(Arrays.toString(array));
<T> T[] toArray(T[] a) 可指定返回数组的类型
例如:
Collection c = new ...;(List或者Set的实现类都可以)
String[] str = new String[set.size()];
//元素自动存放到数组str中了
set.toArray(str);
System.out.println(Arrays.toString(str));
java.util.Arrays中定义了一个方法,可以将数组转换为List类型集合
public static <T> List<T> asList(T... a)
例如:
Integer[] a = new Integer[4];
a[0] = 12;
a[1] = 13;
a[2] = 14;
a[3] = 15;
List list = Arrays.asList(a);
for(Object o:list){
System.out.println(o);
}
输出结果:
12
13
14
15
例如:
int[] a = new int[4];
a[0] = 12;
a[1] = 13;
a[2] = 14;
a[3] = 15;
List list = Arrays.asList(a);
for(Object o:list){
System.out.println(o);
}
输出结果:
[I@3cf5b814
注意基本类型数组和引用类型数组在这里的区别
8)Collection类型集合的工具类:Collections
注意Collection和Collections的区别:
一个是接口一个是类
java.util.Collections类是一个工具类,类中提供了很多的静态方法对Collection类型的集合进行操作
fill方法,使用指定元素替换指定列表中的所有元素。
例如:
List list = new ArrayList();
list.add(1);
list.add(2);
list.add(3);
Collections.fill(list, 20);
for(Object o:list){
System.out.println(o);
}
max方法,根据元素的自然顺序,返回给定集合的最大元素
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
System.out.println(Collections.max(list));
min方法,根据元素的自然顺序,返回给定集合的最小元素
reverse方法,反转集合中的元素
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
Collections.reverse(list);
for(Object o:list){
System.out.println(o);
}
sort方法,根据元素的自然顺序,对指定列表按升序进行排序
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
Collections.sort(list);
for(Object o:list){
System.out.println(o);
}
shuffle方法,使用默认随机源对指定列表进行置换
例如:
List list = new ArrayList();
list.add(1);
list.add(9);
list.add(3);
Collections.shuffle(list);
for(Object o:list){
System.out.println(o);
}
还有一系列的方法,可以把集合转为相应的线程安全的集合对象
synchronizedList
synchronizedSet
synchronizedMap
9)Map接口
Map类型的集合与Collection类型的集合不同,Map类型的集合存储数据的时候,要使用Key-Value的形式(键值对),且Key值不能重复,否则会覆盖原来的键值对
1)Map接口中的一些方法
put 方法
get 方法
clear 方法
containsKey 方法
containsValue 方法
isEmpty 方法
remove 方法
size 方法
10)Map接口的常用实现类
HashMap类和Hashtable类
1.HashMap是线程不安全的,Hashtable是线程安全的
2.HashMap允许key值或者value值为null,但是Hashtable中的key值或者value值都不允许为null,否则报错.
11)Map类型集合的遍历
1.使用keySet方法,可以返回该Map集合中的所有key值的set类型集合
例如:
Map map = new HashMap();
//调用put方法存值
for(Object key:map.keySet()){
System.out.println(key+" : "+map.get(key));
}
2.使用values方法,可以返回该Map集合中所有value值的Collection类型集合
例如:
Map map = new HashMap();
//调用put方法存值
for(Object value:map.values()){
System.out.println(value);
}
3.使用entrySet方法,可以返回该Map集合中,包含所有Entry类型对象的Set集合
Set<Map.Entry<K,V>> entrySet();
注:Entry是声明Map接口中的内部接口(看API或源码可知),一个Entry类型对象就表示Map中的一组键值对(K-V)
例如:
Map map = new HashMap();
//调用put方法存值
Set entrySet = map.entrySet();
for(Object obj:entrySet){
Entry entry = (Entry)obj;
System.out.println(entry.getKey());
System.out.println(entry.getValue());
}
注意这里导入Entry接口的形式。
12)SortedMap接口和TreeMap类
SortedMap接口是Map的子接口,其进一步提供对于键的排序功能。
TreeMap类就是SortedMap接口的实现类。
TreeMap可以对key值进行自然排序或者比较器排序,其用法和TreeSet是一致的。
46.泛型(Generics)
1)泛型的定义
泛型是JDK 1.5的一项新特性,它的本质是参数化类型(Parameterized Type),也就是说所操作的数据类型被指定为一个参数,在用到的时候在指定具体的类型。
这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口和泛型方法。
泛型的类型将来传入是只能是引用类型的,不能是基本类型的。
例如:
//编译通过
List<String>
List<Action>
List<Integer>
List<Integer[]>
List<int[]>
//编译报错
List<int>
2)java中的泛型只是在编辑期间起作用的,在运行时会把泛型信息擦除的。
只是在编译期间启动类型安全检查的作用,运行时不起作用。
例如:List<String> list = new ArrayList<String>();
虽然指定了泛型为String,但是在运行时候依然是可以向该list中存放其他类型数据的。(比如使用反射的方法)
//类中写了如下俩个方法编译会报错,因为编译后这俩个是相同的俩个方法
public void test(List<String> list){
}
public void test(List<Long> list){
}
3)泛型类
一个泛型类就是具有一个或多个类型变量(把类型参数化)的类。
定义一个泛型类十分简单,只需要在类名后面加上<>,再在里面加上类型参数.
注:类型变量使用大写形式,且比较短,这是很常见的。在JDK中,使用变量E表示集合的元素类型,K和V分别表示关键字与值的类型。(需要时还可以用其他的字母,也可以是一个或多个字母)
例如:
//这里的T是根据将来用户使用Point类的时候所传的类型来定
//Point<Double> p = new Point<Double>();
public class Point<T> {
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
}
例如:
//这里的T和S是根据将来用户使用Point类的时候所传的类型来定
//Point<String,Integer> p = new Point<String,Integer>();
public class Point<T,S> {
private T x;
private S y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public S getY() {
return y;
}
public void setY(S y) {
this.y = y;
}
}
4)泛型接口
一个泛型接口就是具有一个或多个类型变量的接口。
例如:
public interface Action<T,U>{
void doSomeThing(T t,U u);
}
public class ActionTest implements Action<String,Date>{
public void doSomeThing(String str,Date date) {
System.out.println(str);
System.out.println(date);
}
}
5)泛型方法
在方法上直接声明泛型,该方法就是泛型方法
例如:
public class Test{
public <T> void run1(T t){
}
public <T> T run2(T t){
return t;
}
public <T,S> void run3(T t,S s){
}
}
6)通配符
泛型增加了java中类型的复杂性,例如List<String>、List<Integer>、List<Object>等这些都是不同的类型。
//编译报错
//虽然 List list = new ArrayList(); 是正确的
//虽然 Object是String的父类型
//但是下面代码编译时报错的,因为使用了泛型
List<Object> list = new ArrayList<String>();
泛型中?是通配符,它可以表示所有泛型的父类型:
List<?> list = new ArrayList<任意>();
例如:
//这时list可以指向任何泛型的List类型集合对象
public void test(List<?> list){
//编译报错,因为我们并不知道?到底代表什么类型
list.add(1);
//编译通过
for(Object o:list){
System.out.println(o);
}
}
注:通配符?只能用在泛型变量声明的时候。
7)泛型中的entends和super关键字
在泛型中可以使用entends和super关键字来表示将来用户所传的泛型参数的上限和下限。
entends关键字的使用:
例如: 在声明泛型类和泛型接口时使用extends
public class Point<T extends Number> {
private T x;
private T y;
}
public class Point<T extends Number,S> {
private T x;
private S y;
}
public interface Action<T extends Person> {
public void doSomeThing(T t);
}
例如:在声明泛型方法时使用extends
public <T extends Action> void run(T t){
}
例如:在声明泛型类型变量时使用extends
List<? extends Number> list = new ArrayList<Integer>();
List<? extends Number> list = new ArrayList<Long>();
List<? extends Number> list = new ArrayList<Double>();
//编译报错
List<? extends Number> list = new ArrayList<String>();
例如:
public void test(List<? extends Number> list){
}
super关键字的使用:
例如:
//编译报错
//声明泛型类或泛型接口时不能使用super
public class Point<T super Number> {
}
public interface Action<T super Number> {
}
//编译报错
//声明泛型方法时不能使用super
public <T super Action> void run2(T t){
}
例如:在声明泛型类型变量时使用super
//编译通过
List<? super Number> list1 = new ArrayList<Object>();
List<? super Student> list2 = new ArrayList<Object>();
List<? super Student> list3 = new ArrayList<Person>();
//编译通过
public void test(List<? super Number> list){
}
//编译通过
public void test(List<? super Student> list){
}
8)泛型中的&
使用&可以给泛型加多个限定
例如:
public class A{
}
public inerface B{
}
//不管该限定是类还是接口,统一都使用关键字extends
//可以使用&符号给出多个限定
//如果限定既有接口也有类,那么类必须只有一个,并且放在首位置
public class Point<T extends A&B> {
}
class Sub extends A implements B{}
main:
//编译报错
Point<A> p = new Point<A>();
Point<B> p = new Point<B>();
//编译通过
Point<Sub> p = new Point<Sub>();
9)观察API中Map接口及其方法的声明
public interface Map<K,V>{
public V get(Object key);
public Set<Map.Entry<K,V>> entrySet();
public Set<K> keySet();
public void putAll(Map<? extends K,? extends V> m);
..
}
47.枚举类型(enum)
JDK1.5增加了枚举类型,可以使用enum来定义
例如:
public enum Gender{
MALE,FEMALE;
}
其中每一个枚举元素都是该枚举类型的一个实例,并且默认是用public static final修饰的
1)枚举类型和类的关系
把Gender.class反编译后显示如下:
public final class com.briup.test.Gender extends java.lang.Enum<com.briup.test.Gender> {
public static final com.briup.test.Gender MALE;
public static final com.briup.test.Gender FEMALE;
private static final com.briup.test.Gender[] ENUM$VALUES;
static {};
private com.briup.test.Gender(java.lang.String, int);
public static com.briup.test.Gender[] values();
public static com.briup.test.Gender valueOf(java.lang.String);
}
说明枚举类型本质还是一个类,而且默认就是fianl修饰以及默认继承父类java.lang.Enum。
同时构造器是自动生成的且是私有的,表示不可再创建对象。(使用反射也不行)
private Gender(String name,int ordinal)
name : 枚举对象的名字
ordinal: 枚举元素的编号,默认从0开始
我们在枚举中所写的MALE和FEMALE其实就是Gender类型的俩个固定的对象。(public static final)
注:enum是java中的一个关键字,Enum是java中的一个类
2)获取枚举对象
1.获得枚举对象,三种方式
枚举对象在我们使用之前就已经全部创建好了,并且不会增加或减少,和声明的时候保持一致。
例如:
Gender g1 = Gender.FEMALE;
Gender g2 = Gender.MALE;
Gender g3 = Gender.valueOf("FEMALE");
Gender g4 = Gender.valueOf("MALE");
System.out.println(g1 == g3);//true
System.out.println(g2 == g4);//true
Gender g5 = Enum.valueOf(Gender.class, "FEMALE");
Gender g6 = Enum.valueOf(Gender.class, "MALE");
System.out.println(g1 == g5);//true
System.out.println(g2 == g6);//true
2.获得一个枚举类型的所有对象
Gender[] values = Gender.values();
for(Gender g:values){
System.out.println(g);
}
3)枚举类型的方法
枚举对象默认只能调用到父类Enum中的方法以及Object中的方法
如果想调用自己的方法,也可以在枚举类型中定义出属于自己的方法.
注:枚举也是类,只是一种特殊的类而且,所以在枚举类型中可以定义很多东西,像之前在类中写代码是一样的
例如:
public enum Gender {
MALE,FEMALE;
public void say(String name){
System.out.println("hello "+name+","+this);
}
public static void main(String[] args) {
Gender g1 = Gender.FEMALE;
Gender g2 = Gender.MALE;
g1.say("tom");
g2.say("tom");
}
}
也可以定义静态方法
例如:
public enum Gender {
MALE,FEMALE;
public void say(String name){
System.out.println("hello "+name+","+this);
}
public static void run(){
System.out.println("running..");
}
public static void main(String[] args) {
Gender.run();
}
}
4)枚举类型的属性
枚举类型中可以定义属于自己的属性
例如:
public enum Gender {
MALE,FEMALE;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//重写toString方法
public String toString() {
return this.getName();
}
public static void main(String[] args) {
Gender g = Gender.FEMALE;
g.setName("女");
System.out.println(g);
}
}
5)枚举类型的构造器
在枚举类型中定义其构造器,但是必须是私有的,默认也是私有的
例如:
public enum Gender {
//默认调用无参构造器
MALE,FEMALE;
private String name;
//有参构造器
private Gender(String name){
this.name = name;
}
//无参构造器
Gender(){
}
//重写toString方法
public String toString() {
return this.name;
}
}
构造器的调用:
在声明枚举对象的时候,其实就是在调用构造器,默认是隐式调用其无参构造器,也可以显式调用有参构造器
例如:
public enum Gender {
MALE("男"),FEMALE("女");
private String name;
private Gender(String name){
this.name = name;
}
Gender(){
this("");
}
public String toString() {
return this.name;
}
public static void main(String[] args) {
Gender g = Gender.FEMALE;
System.out.println(g);
}
}
6)枚举类型的抽象方法
枚举类型中可以编写抽象方法,但是这时候其每个对象都必须去实现这个抽象方法,否则编译报错。
形式上很像匿名内部类对象
例如:
public enum Gender {
MALE(){
public void run() {
System.out.println("MALE run");
}
},FEMALE(){
public void run() {
System.out.println("FEMALE run");
}
};
public abstract void run();
}
7)枚举类型可以实现接口
枚举类型不能指定继承其他父类,但是可以实现其他接口
例如:枚举类型中把该接口中的所有方法实现了
public interface Action{
void doSomeThing();
}
public enum Gender implements Action{
MALE,FEMALE;
public void doSomeThing() {
System.out.println("Gender go..");
}
}
例如:或者在每个对象中分别实现接口中所有方法也可以
public interface Action{
void doSomeThing();
}
public enum Gender implements Action{
MALE(){
public void doSomeThing() {
System.out.println("MALE go...");
}
},FEMALE(){
public void doSomeThing() {
System.out.println("FEMALE go...");
}
};
}
48.反射(Reflection)
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法
对于任意一个对象,都能够调用它的任意一个方法和属性
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
即:在"运行时",通过反射机制可以动态获得到和该类型相关的各种信息。
1)Class类型 java.lang.Class类
Class是对java中所有类型的抽象。即一个Class类型对象可以表示出java中任意一种类型。
每种类型在加载到内存后,内存中都会生产一个与之对应的Class类型对象(有且只有一个),用来表示该类型。
每个类型都有且只有一个Class类型对象与之对应,通过这个Class类型对象就可以获得到该类型中的各种信息。
Class类是Java反射的入口.
1.表示基本类型
Class c = int.class;
System.out.println(c.isPrimitive());//true
System.out.println(c.getName());//int
注:其他基本类型的情况类似
2.表示类类型
注:s.getClass()方法返回的是变量s所指向对象的实现类型的Class对象。
Student s = new Student();
Class c1 = s.getClass();
Class c2 = Student.class;
System.out.println(c1 == c2);//true
//p指向的对象实际类型是Student
Person p = new Student();
Class c1 = p.getClass();//c1表示Student类型
Class c2 = Person.class;//c2表示Person类型
System.out.println(c1 == c2);//false
3.表示接口类型
Action a = new Student();
Class c1 = a.getClass();//c1表示Student类型
Class c2 = Action.class;//c2表示Action类型
System.out.println(c1 == c2);//false
System.out.println(c2.isInterface());//true
4.表示数组类型
int[] a = new int[4];
Class c1 = a.getClass();
Class c2 = int[].class;
System.out.println(c1 == c2);//true
System.out.println(c1.isArray());//true
Class c3 = c1.getComponentType();//c3表示该数组是使用什么类型声明出来的
System.out.println(c3.getName());//int
Student[] a = new Student[4];
Class c1 = a.getClass();
Class c2 = Student[].class;
System.out.println(c1 == c2);//true
System.out.println(c1.isArray());//true
Class c3 = c1.getComponentType();//c3表示该数组是使用什么类型声明出来的
System.out.println(c3.getName());//com.briup.test.Student
2)获得一个类类型的Class对象的三种方式
以上几种情况中,所以最多的还是类类型的Class对象
1.使用对象调用getClass方法获得
getClass是Object中的final修饰的方法,每个对象都可以调用而且不能重写
Student s = new Student();
Class c = s.getClass();
2.使用类名获得
Class c = Student.class;
3.使用Class类中的forName方法获得
//这种方法很灵活,只需一个String类型参数即可
//而String类型的数据改变起来很容易
//注意该方法是会抛出异常的
Class c = Class.forName("com.briup.test.Student");
注:以上三种方法获得的同一个对象(==比较),因为每个类型内存都有且只有一个Class类型对象
4)反射机制中的常见类的含义
java.lang包下:
Class 类 对java中所有类型抽象而得来的
package类 对java中所有包抽象而得来的
java.lang.reflect包下:
Modifier 类 对java中所有修饰符抽象而得来的
Field 类 对java中所有属性抽象而得来的
Method 类 对java中所有方法抽象而得来的
Constructor 类 对java中所有构造器抽象而得来的
Array 类 提供了对数组对象的动态访问
ParameterizedType接口 在反射中表示参数化类型
例如:List<String> Point<Long,Long>等这种带泛型的类型
5)使用Class类型对象获得类中的信息
1.获得该类所处的包的信息
Student s = new Student();
Class c = s.getClass();
System.out.println(c.getPackage().getName());
2.获得该类的修饰符信息
//每个修饰符对应一个int值
//如果有多个修饰符则int值相加
Student s = new Student();
Class c = s.getClass();
System.out.println(c.getModifiers());
System.out.println(Modifier.PUBLIC);
System.out.println(Modifier.FINAL);
3.获得该类的名字
Student s = new Student();
Class c = s.getClass();
System.out.println(c.getName());
4.获得该类的父类的Class对象
Student s = new Student();
Class c = s.getClass();
//superclass表示其父类型的Class对象
Class superclass = c.getSuperclass();
System.out.println(superclass.getName());
例如:
Class c = Object.class;
Class superclass = c.getSuperclass();
System.out.println(superclass.getName());
//运行报错,因为Object没有父类
例如:
//判断c1是不是c2的子类型
//判断c3是不是c2的子类型
Class c1 = Student.class;
Class c2 = Person.class;
Class c3 = String.class
System.out.println(c2.isAssignableFrom(c1));//true
System.out.println(c2.isAssignableFrom(c3));//false
5.获得该类所实现的接口类型的Class对象
Student s = new Student();
Class c = s.getClass();
Class[] interfaces = c.getInterfaces();
for(Class clazz:interfaces){
System.out.println(clazz.getName());
}
例如:
//判断c1是不是c2的实现类
//判断c3是不是c2的实现类
Class c1 = Student.class;
Class c2 = Action.class;
Class c3 = String.class
System.out.println(c2.isAssignableFrom(c1));//true
System.out.println(c2.isAssignableFrom(c3));//false
6.获得该类中所有的属性
Student s = new Student();
Class c = s.getClass();
Field[] declaredFields = c.getDeclaredFields();
for(Field f:declaredFields){
System.out.println(f.getModifiers());
System.out.println(f.getType().getName());
System.out.println(f.getName());
}
注:
getDeclaredFields()方法返回类中声明的属性,包括私有的
getFields()方法只返回类中public修饰的属性,包括继承的
例如:
//获得某个指定的属性(也包括私有属性)
Student s = new Student();
Class c = s.getClass();
Field f = c.getDeclaredField("score");
System.out.println(f.getModifiers());
System.out.println(f.getType().getName());
System.out.println(f.getName());
7.获得该类中所有的方法
Student s = new Student();
Class c = s.getClass();
Method[] declaredMethods = c.getDeclaredMethods();
for(Method m:declaredMethods){
System.out.println(m.getModifiers());
System.out.println(m.getReturnType().getName());
System.out.println(m.getName());
System.out.println(Arrays.toString(m.getParameterTypes()));
System.out.println(Arrays.toString(m.getExceptionTypes()));
}
注:
getDeclaredMethods()方法返回类中声明的方法,包括私有的
getMethods()方法只返回类中public修饰的方法,包括继承的
例如:
//获得某个指定的方法(也包括私有方法)
Student s = new Student();
Class c = s.getClass();
Method m = c.getDeclaredMethod("print");
System.out.println(m.getModifiers());
System.out.println(m.getReturnType().getName());
System.out.println(m.getName());
System.out.println(Arrays.toString(m.getParameterTypes()));
System.out.println(Arrays.toString(m.getExceptionTypes()));
8.获得该类中所有的构造器
Student s = new Student();
Class c = s.getClass();
Constructor[] declaredConstructors = c.getDeclaredConstructors();
for(Constructor con:declaredConstructors){
System.out.println(con.getModifiers());
System.out.println(con.getName());
System.out.println(Arrays.toString(con.getParameterTypes()));
System.out.println(Arrays.toString(con.getExceptionTypes()));
}
注:
getDeclaredConstructors()方法返回类中所有构造器
getConstructors()方法只返回类中public修饰的构造器
例如:
//获得某个指定的构造器(也包括私有构造器)
Student s = new Student();
Class c = s.getClass();
Constructor con = c.getDeclaredConstructor(double.class);
System.out.println(con.getModifiers());
System.out.println(con.getName());
System.out.println(Arrays.toString(con.getParameterTypes()));
System.out.println(Arrays.toString(con.getExceptionTypes()));
9.获得父类型中的泛型类型
因为泛型类的泛型参数在编译期会被擦除,所以我们不能再运行期间直接拿到该泛型的实际类型,但是可以通过子类的Class对象来获取父类的泛型类型。
例如: 不通过子类不能获得泛型实际类型
public class GenericTest<T,S>{
public T name;
public S say(T t,S s){
return s;
}
}
main:
GenericTest<String,Integer> t = new GenericTest<String,Integer>();
Class c = t.getClass();
Field field = c.getDeclaredField("name");
System.out.println(field.getType());
System.out.println(field.getGenericType());
//输出结果:
class java.lang.Object
T
System.out.println("-------------------------");
Method method = c.getMethod("say", Object.class,Object.class);
System.out.println(method.getReturnType());
System.out.println(method.getGenericReturnType());
//输出结果:
class java.lang.Object
S
System.out.println("-------------------------");
System.out.println(Arrays.toString(method.getParameterTypes()));
System.out.println(Arrays.toString(method.getGenericParameterTypes()));
//输出结果:
[class java.lang.Object, class java.lang.Object]
[T, S]
例如: 通过子类可以获得父类中泛型的实际类型
public class GenericTest<T,S>{
public T name;
public S say(T t,S s){
return s;
}
}
public class Sub entends GenericTest<String,Integer>{}
main:
Class c = Sub.class;
//获得父类类型,包含泛型参数信息
Type superType = c.getGenericSuperclass();
//判断父类类型是不是属于ParameterizedType类型
//ParameterizedType表示带泛型的类型
if(superType instanceof ParameterizedType){
//强转,并调用方法获得泛型参数的实例类型
ParameterizedType pt = (ParameterizedType)superType;
Type[] actualTypeArguments = pt.getActualTypeArguments();
//循环遍历,并强转为Class类型,因为Type接口中没有任何方法
for(Type t:actualTypeArguments){
Class clazz = (Class)t;
System.out.println(clazz.getName());
}
}
例如: 通过子类可以获得实现接口中泛型的实际类型
Class c = Sub.class;
Type[] types = c.getGenericInterfaces();
for(Type t:types){
if(t instanceof ParameterizedType){
ParameterizedType pt = (ParameterizedType)t;
Type[] actualTypeArguments = pt.getActualTypeArguments();
for(Type type:actualTypeArguments){
Class clazz = (Class)type;
System.out.println(clazz.getName());
}
}
}
10.获得类中的注解
使用反射也可以获得类中的注解.(在之后的内容在来了解)
6)反射中的常用操作
public class Student{
private long id;
private String name;
private static int age;
get/set
public static void say(){
System.out.println("say..");
}
}
1.使用反射的方式调用构造器创建类的对象
默认方式:必须调用无参构造器
Class c = Student.class;
Student s = (Student)c.newInstance();
通用方式:获得构造器对象,并调用该构造器
注:getConstructor方法和newInstance方法的参数都是可变参数
例如:获得无参构造器并调用创建对象
Class c = Student.class;
Constructor constructor = c.getConstructor();
Student o = (Student)constructor.newInstance();
System.out.println(o);
例如:获得有参构造器并调用创建对象
Class c = Student.class;
Constructor constructor = c.getConstructor(long.class,String.class);
Student o = (Student)constructor.newInstance(1L,"tom");
System.out.println(o);
2.使用反射的方式访问对象中的属性
例如:
Student s = new Student();
Class c = s.getClass();
Field[] declaredFields = c.getDeclaredFields();
for(Field f:declaredFields){
//设置私有属性可以被访问
f.setAccessible(true);
//判断属性是否为static,静态属性的访问不需要对象
if(Modifier.isStatic(f.getModifiers())){
System.out.println(f.getName() +" = "+f.get(null));
}else{
System.out.println(f.getName() +" = "+f.get(s));
}
}
注:Field类中的get方法可以获得属性值,set方法可以给属性设置值。
3.使用反射的方式调用对象中的方法
例如:
Student s = new Student();
Class c = s.getClass();
Method m1 = c.getMethod("setName",String.class);
//m1表示Student中的setName方法
//调用对象s中的m1方法,并且传参"tom"
//s.setName("tom");
m1.invoke(s, "tom");
Method m2 = c.getMethod("getName");
String name = (String)m2.invoke(s);
System.out.println("name = "+name);
//调用静态方法 不需要对象
Method m3 = c.getMethod("say");
m3.invoke(null);
4.使用反射的方式动态操作数组
注:
Object[] o1 = new int[1];//编译报错
long[] o2 = new int[1];//编译报错
int[] o3 = new int[1];//编译通过
Object[] o1 = new 任意引用类型[1];//编译通过
例如:
//要求:传任意类型"数组",把数组长度扩大1倍并返回
//注意这里不能收Object[],
//因为Object[] o = new Integer[4];编译通过
//但是Object[] o = new int[4]; 编译报错
//那么使用Object类中就可以接收任意类型的数组了
public static Object arrayCopy(Object obj){
//代码
}
实现:
public static Object arrayCopy(Object obj){
Class c = obj.getClass();
Object newArray = null;
if(c.isArray()){
int len = Array.getLength(obj);
Class<?> type = c.getComponentType();
newArray = Array.newInstance(type, len*2);
for(int i=0;i<len;i++){
Object value = Array.get(obj, i);
Array.set(newArray, i, value);
}
}
return newArray;
}
49.异常概述
在我们日常生活中,有时会出现各种各样的异常,例如:职工小王开车去上班,在正常情况下,小王会准时到达单位。但是天有不测风云,在小王去上班时,可能会遇到一些异常情况,比如小王的车子出了故障,小王只能改为步行.
异常指程序运行中出现的不期而至的各种状况,如:文件找不到、网络连接失败、非法参数等。
异常发生在程序运行期间,它影响了正常的程序执行流程
Java通过API中Throwable类的众多子类描述了各种不同的异常。Java中的异常都是对象,都是是Throwable子类的实例。
每种异常类型都代表了一个错误的情况。
例如:
java.lang.ArrayIndexoutofBoundsException类,表示数组的下标在使用中超过了边界
java.lang.ClassCastException类,表示类型转换出现了错误
50.Error和Exception
在Java中,所有的异常都有一个共同的父类Throwable,该类有两个重要的子类:Exception和Error,二者都是Java异常处理的重要子类,各自都包含大量子类。
它们都是java.lang下的类
java.lang.Throwable
java.lang.Error
java.lang.Exception
Error,这个是程序中发生的错误,是程序无法处理的,表示运行应用程序中较严重问题。而且很多错误与代码编写者执行的操作无关,而是表示代码运行时JVM出现了问题。例如,Java虚拟机运行错误(VirtualMachineError),当JVM中内存不足时,将出现 OutOfMemoryError。这些异常发生时,JVM一般会选择线程终止。
这些错误表示故障发生于虚拟机自身、或者发生在虚拟机试图执行应时
如Java虚拟机运行错误(VirtualMachineError)、类定义错误(NoClassDefFoundError)等。这些错误一般是不可查询的,因为它们在应用程序的控制和处理能力之外。对于设计合理的应用程序来说,即使确实发生了错误,本质上也不应该试图去处理它所引起的异常状况,因为这是超出程序处理能力的。
Exception,这个是程序中发生的异常,是程序本身可以处理的。
我们进行抛出或捕获的异常就是这里类型及其子类型。
51.异常的抛出和捕获
异常的抛出:
在类中编写方法的时候,这个方法中将来被执行的代码如果有可能出现异常情况,那么就"可以"在方法的参数列表后声明该方法中可能会抛出的异常类型.
例如:
public class Test{
public void run()throws IOException,SQLException{
//..
}
}
1)如果有多个异常类型要抛出,那么需要使用逗号隔开.
2)所声明抛出的异常是该方法执行后"可能"会出现异常类型
3)异常抛了个方法的调用者,谁调用的方法谁就负责处理这些异常
异常的捕获
当我们调用了一个方法,该方法在声明的时候抛出了异常,那么我们作为方法的调用者就必须去处理这些被抛出的异常。
例如:
Class类中的forName方法的声明
public static Class<?> forName(String className)throws ClassNotFoundException
说明该方法在执行的时候有可能抛出ClassNotFoundException类型的异常,表示要加载的类找不到。
我们调用这个方法的时候,就需要对这个抛出的异常进行处理。
第一种方式是继续把这些异常抛出去
例如:
public static void main(String[] args)throws ClassNotFoundException{
Class.forName("test....");
}
在main方法中调用forName方法时候,我们并没有直接处理这个抛出的异常,而是继续把该异常往上抛出,抛给main方法的调用者。
第二种方式是使用try-catch语句块把抛出的异常进行捕获
例如:
public static void main(String[] args) {
try {
Class.forName("test...");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
1)try-catch是尝试着去捕获这些代码抛的异常,如果try语句块中的代码没有抛出异常,那么try-catch是没有任何作用的
2)如果try语句块中的代码抛出了异常,并且抛出的异常还是catch语句要处理的异常或其子类型异常,那么这时就会执行catch语句块中的代码,进行异常出现后的处理。
3)异常对象e常用的方法
e.printStackTrace()
引用出堆栈中出现的异常跟踪信息
e.getMessage()
返回异常的详细字符串信息(如果有的话)
4)不管方法声明中抛出了什么类型的异常,我们一般都是可以再catch中使用Exception类型进行捕获到的,因为Exception是所有异常的父类型。
例如:
try {
Class.forName("test...");
....
..
} catch (Exception e) {
e.printStackTrace();
}
5)如果代码中抛出了多种异常,也可以使用多个catch来分别捕获.当然也可以只使用一个最大的异常Exception
例如:
try {
Class c = Class.forName("test..");
c.getMethod("go");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
52.抛出和捕获对程序的影响
1)如果程序中的某行代码的执行抛出了异常,并且这个异常一种都没有被try-catch处理,那么这个异常最终会抛给JVM,JVM输出异常信息后就自动停止了
例如:
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("hello");
Class.forName("test..");
System.out.println("world");
//....其他代码
}
最终的结果是代码在调用forName方法抛出异常后,JVM处理后就停止了.并没有往下继续执行代码
2)如果使用try-catch语句去处理代码中抛出的异常,那么catch语句块处理完之后,代码还会在catch语句块下面继续执行
例如:
public static void main(String[] args){
System.out.println("hello");
try {
Class.forName("test..");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("world");
//....其他代码
}
最终的结果是catch语句执行完后,代码继续往下正常执行。
3)try-catch语句块虽然能处理完异常后继续让代码往下执行,但是在某些时候也会改变代码的执行流程(默认是从上往下顺序执行)
例如:
public static void main(String[] args){
System.out.println("hello");
try {
Class.forName("test..");
System.out.println("world");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//....其他代码
}
最终的结果是catch语句执行完后,代码执行下面的其他代码,但是上面打印world的语句就跳过去了。
53.finally语句块
由于异常处理有时候会改变程序的正常流程,这会使得某些不管在任何情况下都必须执行的步骤被忽略,从而影响程序的健壮性。
小王开了一家店,在店里上班的正常流程为:打开店门、工作8个小时、关门。异常流程为:小王在工作时突然犯病,因而提前下班
例如:
public void work() {
try {
开门();
工作8个小时();
关门();
} catch(Exception e) {
去医院()
}
}
小王在工作时突然犯病,那么流程会跳转到catch代码块,这意味着关门的操作不会被执行,这样的流程显然是不安全的,必须确保关门的操作在任何情况下都会被执行.
finally代码块能保证特定的操作总是会被执行,它的形式如下:
public void work() {
try {
开门();
工作8个小时();
} catch(Exception e) {
去医院()
} finally {
关门();
}
}
注:即使方法中执行了return语句,finally最后也是会被执行的。
54.编译异常和运行时异常
1)Exception有一个特殊的子类:RuntimeException
2)RuntimeException类型及其子类型都是属于运行时异常
3)其他类型的异常只要不是继承了RuntimeException类的,都属于编译异常
4)编译异常又称checked异常,运行时异常又称unchecked异常
因为编译器在编译期间如果遇到了checked异常,那么是一定会提示我们,让我们去处理的。但是如果遇到了unchecked异常,编译器是不做任何事情的。
常见的运行时异常:unchecked
java.lang.ArithmeticException
算术异常
java.lang.NullPointerException
空指针引用
java.lang.ArrayIndexoutofBoundsException
数组越界
java.lang.ClassCastException
强制类型转换异常
java.lang.NumberFormatException
数据格式异常
java.lang.NegativeArraySizeException
数组长度为负数异常
常见的编译异常:checked
编译器提示你需要处理的都为编译异常
java.lang.ClassNotFoundException
java.lang.DataFormatException
java.lang.NoSuchMethodException
java.io.IOException
java.sql.SQLException
55.自定义异常
在需要的情况下,可以通过扩展Exception类或RuntimeException类来创建自定义的异常(一般是扩展Exception类)。异常类包含了和异常相关的信息,这有助于负责捕获异常的catch代码块,正确地分析并处理异常
例如:我们任务在系统中用户要登录的账号和密码不匹配就是一种异常情况,但是JDK中并没有定义这种异常,所以我们可以进行自定义。
例如: 只需继承Exception即可.一般还会加入和父类中匹配的构造器
public class UserPasswordException extends Exception{
public UserPasswordException() {
super();
}
public UserPasswordException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public UserPasswordException(String message, Throwable cause) {
super(message, cause);
}
public UserPasswordException(String message) {
super(message);
}
public UserPasswordException(Throwable cause) {
super(cause);
}
}
例如:
注:异常是可以在代码中主动抛出的
public void login(String password)throws UserPasswordException{
if("123".equals(password)){
throw new UserPasswordException("密码错误");
}
}
将来login方法的调用者负责处理这个异常
56.断言
1)断言使用
在JDK1.4中,Java语言引入一个新的关键字: assert。 该关键字有两种形式:
assert 条件
以及
assert 条件:值或表达式
这两种形式都会对条件进行评估,如果结果为false则抛出AssertionError。
在第二种形式中,值或表达式的值会传入AssertionError的
构造器并转成一个消息字符串,成为要显示的错误信息
例如:
要断言x不是负数,只需要使用如下简单的语句:
assert x >= 0;
或者你可以将x的值传递给AssertionError对象,从而可以在报错时候显示:
assert x >= 0 : x;
或者
assert x >= 0 : "x的值不符合条件:x="+x;
2. 断言内容代码编译
因为assert在JDK1.4中是一个新的关键字,因此在使用时需要告诉编译器你编译所使用jdk的版本号。
javac -source 1.4 MyClass.java
在jdk的后续版本中,对断言的支持成为默认特性(JDK5.0以上使用时就不需要这个编译了,因为默认就支持的)。
3. 断言内容代码执行
默认情况下,断言是关闭的。要通过-enableassertions或者-ea选项来运行程序以打开断言:
java -enableassertions com.briup.ch07.Xxxx
java -ea com.briup.ch07.Xxxx
打开或关闭断言功能,决定了代码在执行时是否会跳过那些和断言相关的代码.因此断言并不会降低程序运行速度。
注意:使用eclipse运行代码的时候也是可以传参数的(包括俩种参数)
例如:
java -xx com.briup.ch07.Test yy
xx是给JVM传的参数 yy是给Test类的main方法传的参数
57.GUI概述-AWT和Swing
图形用户界面(Graphics User Interface, GUI) 是用户与程序交互的窗口,它比基于命令行的界面更直观并且更友好。
GUI的基本类库位于java.awt包中, 这个包也被称为抽象窗口工具集(Abstract Window Toolkit, AWT)。AWT按照面向对象的思想来创建GUI,它提供了容器类、组件类和布局管理器类。
随着Java的发展,AWT中的大部分组件已经渐渐被淘汰,它已经不能适应发展的需要,不能满足开发功能强大的用户界面的需要。这时Swing出现了,它是建立在AWT之上的组件集(对AWT组件的扩展),在不同的平台上都能保持组件的界面样式,因此得到了非常广泛的应用。
注:AWT和Swing之间的最大的区别在于Swing组件的实现与本地实现无关,所以能保证Swing组件在不同的平台上都能显示出原有的样式,并且Swing组件比AWT组件具有更多的功能。
AWT组件都在java.awt包下面,Swing组件都在javax.swing包下面
例如AWT和Swing中都有按钮,
java.awt.Button
javax.swing.JButton
AWT和Swing中都有复选框,
java.awt.Checkbox
javax.swing.JCheckBox
AWT和Swing中都有对话框,
java.awt.Dialog
javax.swing.JDialog
AWT和Swing中都有菜单组件,
java.awt.Menu
javax.swing.JMenu
...
注1:它们之间的名字的特点
注2:javax.swing中绝大大多数的组件都是继承了java.awt中Component
58.组件、容器、布局管理器的介绍
这三种东西是GUI界面中最重要的组成部分。
1)组件
java.awt.Component类是所以组件的父类(这是个抽象类)。同时在该类中也定义了所有组件都应该具备的公共方法。
Component类型对象是一个具有图形表示能力的对象,可在屏幕上显示,并可与用户进行交互。例如图形用户界面中的按钮、复选框和滚动条等都是它的实例。
//实现的接口没有写出来
public abstract class Component{}
下面常用的类都是Component的"直接"子类:
java.awt.Button
Button类用于创建GUI中的按钮
java.awt.Canvas
Canvas类表示屏幕上一个空白矩形区域,应用程序可以在该区域内绘图,或者可以从该区域捕获用户的输入事件。
java.awt.Checkbox
Checkbox类用于创建GUI中的复选框
java.awt.Choice
Choice类表示一个弹出式选择菜单,当前的选择显示为菜单的标题。
java.awt.Container
Container类是所有容器类的父类.(容器本身也是一种组件)
java.awt.Label
Label类型对象是一个可在容器中放置文本的组件。一个标签只显示一行只读文本。文本可由应用程序更改,但是用户不能直接对其进行编辑
java.awt.List
List组件为用户提供了一个可滚动的文本项列表。可设置此 list,使其允许用户进行单项或多项选择
java.awt.Scrollbar
Scrollbar类可用于创建一个滚动条
java.awt.TextComponent
TextComponent类是所有允许编辑文本的组件的父类。
2)容器
java.awt.Container类是所有容器的父类。
容器也是一种特殊的组件,它是一个可包含其他组件的组件,所以Container类继承了Component类
public class Container extends Component{}
下面常用的类都是Container的"直接"子类:
javax.swing.JComponent
JComponent类是除顶层容器外所有Swing组件的基类
java.awt.Panel
Panel是最简单的容器类,应用程序可以将其他组件放在面板提供的空间内,这些组件包括其他面板
java.awt.Window
Window对象是一个没有边界和菜单栏的顶层窗口
java.awt.ScrollPane
用于实现单个组件的水平或垂直滚动的容器类
3)布局管理器
Java中的图形界面在布局管理上采用容器和布局管理相分离的方案,也就是说容器只是把组件放进来,但它不管怎样放置。至于如何放置需要用到布局管理器。
java.awt.LayoutManager接口是所有布局管理需要实现的接口
Java中常用布局管理器有:
BorderLayout 边界布局管理器
FlowLayout 流式布局管理器
GridLayout 网格布局管理器
CardLayout 卡片布局管理器
注:很多容器都是有默认的布局管理器的,同时也允许用户修改
注:也可以不使用任何布局管理器,自己使用坐标控制组件在容器中的位置
59.组件、容器、布局管理器的使用
1)常用的容器类,Frame和Panel
java.awt.Frame类:
public class Frame extends Window{}
java.awt.Frame是带有标题和边框的顶层窗口,它是java.awt.Window类的直接子类
例如:
Frame frame = new Frame();
frame.setTitle("我的容器");
frame.setSize(500,500);
frame.setLocation(700, 300);
frame.setBackground(Color.lightGray);
frame.setResizable(true);
frame.setVisible(true);
注:frame要显示出来,最后一定要设置它的的可见性为true
思考:怎么能让窗口每次位于屏幕的正中间
Toolkit toolkit = Toolkit.getDefaultToolkit();
int width = (int) toolkit.getScreenSize().getWidth();
int height = (int) toolkit.getScreenSize().getHeight();
java.awt.Panel类:
public class Panel extends Container{}
Panel是最简单的容器类,也成为面板,应用程序可以将其他组件放在面板提供的空间内,这些组件包括其他面板(嵌套)。
注:Panel面板不能单独显示,必须把它放到Frame中
例如:运行后显示不了,设置Panel的大小位置以及可见性也没用
Panel panel = new Panel();
panel.setSize(400, 400);
panel.setLocation(700, 300);
panel.setBackground(Color.blue);
panel.setVisible(true);
例如:运行后可以显示
Frame frame = new Frame();
Panel panel = new Panel();
panel.setBackground(Color.blue);
frame.setSize(400, 400);
frame.setLocation(700, 300);
frame.add(panel);
frame.setVisible(true);
2)常用的容器类,JFrame JPanel
javax.swing.JFrame类:
public class JFrame extends Frame{}
例如:
JFrame jFrame = new JFrame();
jFrame.setTitle("我的容器");
jFrame.setResizable(true);
jFrame.setSize(500,500);
jFrame.setLocation(700, 300);
jFrame.setBackground(Color.lightGray);
jFrame.setVisible(true);
javax.swing.JPanel类:
public class JPanel extends JComponent
JPanel是一般轻量级容器,作用及用法和java.awt.Panel类似,它也是不能单独显示的。
3)常用的组件类(主要以Swing的为主,除非Swing中没有该组件)
javax.swing.JButton 按钮
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JButton btn = new JButton("登录");
panel.add(btn);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JCheckBox 复选框
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JCheckBox jck1 = new JCheckBox("篮球");
JCheckBox jck2 = new JCheckBox("足球");
panel.add(jck1);
panel.add(jck2);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.ButtonGroup 按钮组
javax.swing.JRadioButton 单选框
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
//需要把俩个单选按钮放到ButtonGroup中,表示为一组
ButtonGroup bg = new ButtonGroup();
JRadioButton rbtn1 = new JRadioButton("男");
JRadioButton rbtn2 = new JRadioButton("女",true);
bg.add(rbtn1);
bg.add(rbtn2);
panel.add(rbtn1);
panel.add(rbtn2);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.Choice 下拉菜单
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
Choice ColorChooser = new Choice();
ColorChooser.add("Green");
ColorChooser.add("Red");
ColorChooser.add("Blue");
panel.add(ColorChooser);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JComboBox 下拉菜单
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JComboBox<String> box = new JComboBox<>();
box.addItem("test1");
box.addItem("test2");
box.addItem("test3");
panel.add(box);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.ImageIcon 图片/图标
javax.swing.JLabel 标签(可放置文本和图片)
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JLabel label1 = new JLabel("大家好");
JLabel label2 = new JLabel();
label2.setIcon(new ImageIcon("src/com/briup/test/1.png"));
panel.add(label1);
panel.add(label2);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
注:按钮上也能类似的设置图片
javax.swing.JList 显示数据列表并且允许用户选择一个或多个项的组件(按住ctrl键多选)
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
String[] data = {"one", "two", "three", "four"};
JList<String> myList = new JList<String>(data);
panel.add(myList);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JScrollBar 滚动条
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
//垂直 VERTICAL, 水平 HORIZONTAL
JScrollBar bar = new JScrollBar(JScrollBar.HORIZONTAL);
bar.setPreferredSize(new Dimension(200, 20));
bar.setMinimum(0);
bar.setMaximum(100);
bar.setValue(50);
panel.add(bar);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JTextArea 文本域
javax.swing.JScrollPane 滚动面板(一般结合其他组件使用)
例如:结合文本域
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
//需要给panel换一个布局管理器
panel.setLayout(new BorderLayout());
JTextArea area = new JTextArea();
area.setSize(100, 100);
JScrollPane scrollPane = new JScrollPane(area);
panel.add(scrollPane);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JTextField 文本框
javax.swing.JPasswordField 密码框
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JTextField field = new JTextField(10);
JPasswordField passwordField = new JPasswordField(10);
panel.add(field);
panel.add(passwordField);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JMenuBar 菜单栏
javax.swing.JMenu 菜单
javax.swing.JMenuItem 菜单项
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JMenuBar mBar = new JMenuBar();
JMenu sMenu, gMenu;
sMenu = new JMenu("系统");
gMenu = new JMenu("工具");
JMenuItem sItem, cItem, tItem;
sItem = new JMenuItem("开始导航");
cItem = new JMenuItem("刷新系统");
tItem = new JMenuItem("退出系统");
sMenu.add(sItem);
sMenu.addSeparator();
sMenu.add(cItem);
sMenu.addSeparator();
sMenu.add(tItem);
JMenuItem xyItem, ycItem;
xyItem = new JMenuItem("显示菜单");
ycItem = new JMenuItem("隐藏菜单");
gMenu.add(xyItem);
gMenu.addSeparator();
gMenu.add(ycItem);
mBar.add(sMenu);
mBar.add(gMenu);
panel.add(mBar);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
javax.swing.JDialog 对话框
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JButton okBut = new JButton("确定");
//参数1 对话框属于哪个窗口
//参数2 对话框标题
//参数3 弹框对话框后,是否阻止用户进行其他组件的操作
JDialog dialog = new JDialog(jFrame, "提示信息", true);
dialog.setBounds(730, 325, 350, 150);
dialog.setLayout(new FlowLayout());
dialog.add(okBut);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
dialog.setVisible(true);
注:之后可以通过鼠标点击控制对话框的出现和消失
javax.swing.JProgressBar 进度条
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JProgressBar bar = new JProgressBar();
//最小值为0
bar.setMinimum(0);
//最大值为100
bar.setMaximum(100);
//设置进度条中的值初始为0
bar.setValue(0);
//显示当前进度条的状态
bar.setStringPainted(true);
panel.add(bar);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
bar.setValue(40);
java.awt.Canvas 画布
注:必须为Canvas创建子类并重写paint方法
例如:
JFrame jFrame = new JFrame();
final JPanel panel = new JPanel();
Canvas canvas = new Canvas(){
@Override
public void paint(Graphics g) {
g.drawLine(0, 0, panel.getWidth(), panel.getHeight());
}
};
panel.setLayout(new BorderLayout());
panel.add(canvas);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
注:尝试在画布对象创建完后,添加如下代码观察效果
canvas.addMouseMotionListener(new MouseAdapter() {
public void mouseDragged(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Graphics g = canvas.getGraphics();
g.setColor(Color.RED);
g.fillOval(x, y, 10, 10);
}
});
注:addMouseMotionListener和addMouseListener所支持的事件是有区别的
4)常用的布局管理器的使用
注: JFrame的布局管理器默认是BorderLayout
JPanel的布局管理器默认是FlowLayout
BorderLayout 边界布局管理器
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JButton north,south,west,east,center;
north = new JButton("north");
south = new JButton("south");
west = new JButton("west");
east = new JButton("east");
center = new JButton("center");
//把按钮添加到panel中,并指明添加到的位置
panel.add(north,BorderLayout.NORTH);
panel.add(south,BorderLayout.SOUTH);
panel.add(west,BorderLayout.WEST);
panel.add(east,BorderLayout.EAST);
//panel.add(center)这样写和下面的效果是一样的
//如果你不指定放到BorderLayout那个位置的时候
//他会默认的把组件放到中间那个位置
panel.add(center,BorderLayout.CENTER);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
FlowLayout 流式布局管理器
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
panel.setLayout(new FlowLayout());
JButton[] buts = new JButton[50];
for(int i=1;i<=buts.length;i++){
if(i<10){
buts[i-1] = new JButton("0"+i);
}else if (i<=50) {
buts[i-1] = new JButton(""+i);
}
panel.add(buts[i-1]);
}
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
GridLayout 网格布局管理器
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(3,3));
JButton[] buts = new JButton[9];
//设置container的布局管理器为GridLayout
//GridLayout(3,3)表示布局为3行3列
//循环创建按钮并放到panel中
for(int i=0;i<buts.length;i++){
buts[i] = new JButton((i+1)+"");
panel.add(buts[i]);
}
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
CardLayout 卡片布局管理器
例如:
JFrame jFrame = new JFrame();
final JPanel panel = new JPanel();
panel.setLayout(new CardLayout());
JPanel[] jPanels = new JPanel[4];
//创建四个JPanel放到数组中并设置背景颜色
jPanels[0] = new JPanel();
jPanels[0].setBackground(Color.red);
jPanels[1] = new JPanel();
jPanels[1].setBackground(Color.blue);
jPanels[2] = new JPanel();
jPanels[2].setBackground(Color.cyan);
jPanels[3] = new JPanel();
jPanels[3].setBackground(Color.green);
//向CardLayout布局管理器中放置组件的时候一定要给组件起一个名字
panel.add("a",jPanels[0]);
panel.add("b",jPanels[1]);
panel.add("c",jPanels[2]);
panel.add("d",jPanels[3]);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
注:现在所看到的结果是四个颜色不同的JPanel叠在一起的效果,看到是最上面一个JPanel
可以在上面例子中加入以下代码,看效果如何.
panel.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
//获得panel容器上面的布局管理器
//并转为CardLayout类型的对象
CardLayout cl = (CardLayout) panel.getLayout();
//显示CardLayout管理器中的下一个组件
//这个参数表示:在哪一个容器中设置的这个CardLayout管理器
cl.next(panel);
//cl.show(panel, "d");
}
});
注:JPanel和JPanel之间可以嵌套,每个JPanel又可以设置为不同的布局管理器,这样就可以得到大都数想要的组件布局情况了
自定义组件位置
有些时候也可以不使用任何布局管理器,用户通过坐标自己设置组件位置。
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
//把布局管理器设置为null
//表示不使用任何管理器
panel.setLayout(null);
JButton btn = new JButton("登录");
//设置组件的大小和位置
btn.setSize(100,30);
btn.setLocation(80,60);
panel.add(btn);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
60.编写一个GUI程序的基本方式
1)第一种情况:
public class JFrameTest{
//声明容器和要用的组件
private JFrame frame;
private JButton button;
public JFrameTest(){
//初始化主容器
frame = new JFrame();
frame.setTitle("我的组件");
frame.setSize(500, 500);
frame.setLocation(700, 300);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponent();
frame.setVisible(true);
}
//初始化组件
private void initComponent(){
button = new JButton("登录");
frame.add(button);
}
public static void main(String[] args){
new JFrameTest();
}
}
2)第二种情况:
public class JFrameTest extends JFrame{
//声明容器和要用的组件
private JButton button;
public JFrameTest(){
//初始化主容器
this.setTitle("我的组件");
this.setSize(500, 500);
this.setLocation(700, 300);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponent();
this.setVisible(true);
}
//初始化组件
private void initComponent(){
button = new JButton("登录");
this.add(button);
}
public static void main(String[] args){
new JFrameTest();
}
}
61.一个GUI的例子
public class MyFirstGUI extends JFrame{
private static final long serialVersionUID = 1L;
private JButton button;
private JPanel jPanel;
private JLabel addLabel,eqsLabel;
private JTextField f1,f2,f3;
public MyFirstGUI() {
setTitle("MY-GUI");
//设置位置和宽高
setBounds(40, 40, 400, 400);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initComponent();
setVisible(true);
}
private void initComponent(){
//初始化组件
jPanel = new JPanel();
button = new JButton("add");
addLabel = new JLabel("+");
eqsLabel = new JLabel("=");
//创建文本输入框对象,并设置长度为5
f1 = new JTextField(5);
f2 = new JTextField(5);
f3 = new JTextField(5);
//设置容器的布局管理器并添加组件
jPanel.setLayout(new FlowLayout());
jPanel.add(f1);
jPanel.add(addLabel);
jPanel.add(f2);
jPanel.add(eqsLabel);
jPanel.add(f3);
jPanel.add(button);
this.add(jPanel);
//给指定的组件添加事件监听
button.addActionListener(new ActionListener(){
//当鼠标点击这个指定按钮的时候程序就会调用这个方法
@Override
public void actionPerformed(ActionEvent e) {
//拿到第一个输入框中的数组(String转换为Double)
double a =
Double.parseDouble(f1.getText());
//拿到第二个输入框中的数组(String转换为Double)
double b =
Double.parseDouble(f2.getText());
//相加得到结果
double c = a+b;
//把结果放到第三个输入框中
f3.setText(c+"");
}
});
}
public static void main(String[] args) {
new MyFirstGUI();
}
}
62.了解JFC
JFC(Java Foundation Classes)是一个图形框架,主要是由AWT、Swing以及Java2D三者所构成,若将这些一同搭配运用,
则用Java程式语言撰写开发成的使用者界面,无论移转、转移到Microsoft WindowsWindows、Mac OS X或Linux等各种不同的作业平台上,都能保有一致性的图像呈现。
63.AWT事件模型概述
使用AWT或者Swing中的容器、组件和布局管理器就可以构建出图形界面,但是这时候该界面还并不能和用户进行交换,因为图形界面中的组件还没有添加事件监听器,所以还不能对用户在界面中的操作进行处理。
在Java事件模型中,必须存在事件对象、事件源、事件监听器三部分。事件对象是表示发生了什么事件,事件源表示是谁产生的这个事件对象,事件处理器接收到事件对象后,可以对这个事件进行处理。
事件模型中的三要素:事件对象、事件源、事件监听器
注:不光是GUI中,在java的其他地方也会使用到事件模型。
在Java中一个事件监听器就是指事件发生时被通知的对象。它有两个要求:首先,为了可以接收到特殊类型事件的通知,它必须在事件源中已经注册;其次,它必须实现接收和处理事件的方法。
例如:
//btn就是事件源
JButton btn = new JButton("测试");
//给事件源btn注册事件监听器
//这里使用了匿名内部类对象作为监听器
btn.addActionListener(new ActionListener() {
//监听器中实现接收和处理事件的方法
//ActionEvent类型的引用e指向的就是按钮上所产生的事件对象
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("hello");
}
});
XxxxListener 监听器接口
事件源.addXxxxListener(new XxxxListener(){
//实现接口中的抽象方法
});
在事件源上注册好监听器之后,只要是在该事件源上产生了特定的事件对象,事件监听器就会自动被触发,并执行相应的方法处理。
例如:在上面的例子基础上
当我们使用鼠标点击btn这个按钮之后,产生了一个鼠标点击的事件对象(引用e会指向这个事件对象),然后注册的事件监听器会自动触发(这里的匿名内部类对象就是注册的监听器),并调用指定方法actionPerformed,对产生的事件进行处理。
64.事件源、事件对象、事件监听器
1)事件源
AWT和Swing中的几乎所有的组件都可以作为事件源,注意容器也是一种组件。
例如:窗口、面板、按钮、输入框、下拉类别、单选复选框、标签、滚动条、进度条等等
例如:AWT和Swing中的组件都是java.awt.Component类的子类型,Component类中定义了很多所有组件都可以调用的方法,这些方法中有很多是这种形式的:addXxxxListener
这些方法就是给组件中注册事件监听器的方法,只是不同类型的事件需要使用不同类型的监听器来监听,所以不同的addXxxxListener方法就表示给组件添加相应的事件监听器。(Xxxx代表事件的类型)
2)事件对象及其对应的处理接口(也就是事件监听器)
注:事件处理器都被定义为了接口,思考为什么都定义为接口
java.util.EventObject类
public class EventObject extends Object{}
该类是java中所有事件对象的父类型。
该类中有一个非常重要的方法:getSource
public Object getSource(){...}
该方法返还的对象是产生当前事件的事件源
例如:当前使用鼠标点击按钮btn的时候,会产生一个事件对象e,这个对象e就表示鼠标点击的事件,同时e也是EventObject类型的对象,调用getSource方法可以得到产生事件的事件源,也就是我们点击的那个按钮btn.
java.awt.Event类
public class Event extends Object{..}
在Java1.1和以后的版本中该类已被废弃,由AWTEvent类及其子类所取代.
java.awt.AWTEvent类
public abstract class AWTEvent extends EventObject{...}
该类是所有AWT事件的父类型,此类及其子类取代了原来的 java.awt.Event类
java.awt.event.ActionEvent类
public class ActionEvent extends AWTEvent{...}
动作事件类,单击按钮、选择菜单项或在文本框中按回车时可产生此事件对象。
可以处理该类型事件的监听器接口:ActionListener
注:ActionListener是一个很通用的接口,可以处理很多种组件上面产生的事件.
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JButton btn = new JButton("测试");
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("hello");
}
});
panel.add(btn);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.AdjustmentEvent类
public class AdjustmentEvent extends AWTEvent{..}
调整事件类,当改变滚动条滑块位置时可产生此事件对象。
该类代表由Adjustable类型对象所发出的调整事件。主要针对的是滚动条,Scrollbar和JScrollbar都是Adjustable接口的实现类。
可以处理该类型事件的监听器接口:AdjustmentListener
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JScrollBar bar = new JScrollBar(JScrollBar.HORIZONTAL, 0, 0, 0, 100);
bar.setPreferredSize(new Dimension(100, 20));
bar.addAdjustmentListener(new AdjustmentListener() {
@Override
public void adjustmentValueChanged(AdjustmentEvent e) {
System.out.println(e.getValue());
}
});
panel.add(bar);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.ComponentEvent类
public class ComponentEvent extends AWTEvent{...}
组件事件类,表示组件被移动、大小被更改或可见性被更改的事件,同时它也是其他组件级事件的父类:
java.awt.event.ContainerEvent
java.awt.event.FocusEvent
java.awt.event.WindowEvent
..
这些都是它的的子类
可以处理该类型事件的监听器接口:ComponentListener
该接口中有四个抽象方法:
componentMoved组件移动时被调用
componentHidden组件隐藏时被调用
componentResized组件缩放时被调用
componentShown组件显示时被调用
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
jFrame.addComponentListener(new ComponentListener() {
public void componentShown(ComponentEvent e) {
System.out.println("shown");
}
public void componentHidden(ComponentEvent e) {
System.out.println("Hidden");
}
public void componentResized(ComponentEvent e) {
System.out.println("Resized");
}
public void componentMoved(ComponentEvent e) {
System.out.println("Moved");
}
});
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.ContainerEvent类
public class ContainerEvent extends ComponentEvent{..}
容器事件类,容器中因为添加或移除组件而更改的事件。
可以处理该类型事件的监听器接口:ContainerListener
接口中有俩个方法:
componentAdded添加组件时被调用
componentRemoved移除组件时被调用
例如:
JFrame jFrame = new JFrame();
final JPanel panel = new JPanel();
JButton btn = new JButton("点击");
final JButton test = new JButton("测试");
btn.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
panel.add(test);
//在运行中动态添加组件
//需要调用容器的validate和repaint
//或者是调用容器的revalidate方法
//否则动态添加的组件不显示
//panel.validate();
//panel.repaint();
panel.revalidate();
}
});
panel.addContainerListener(new ContainerListener() {
@Override
public void componentRemoved(ContainerEvent e) {
System.out.println("removed");
}
@Override
public void componentAdded(ContainerEvent e) {
System.out.println("Added");
}
});
panel.add(btn);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.WindowEvent类
public class WindowEvent extends ComponentEvent{..}
窗口事件类,窗口打开、关闭等操作是会创建该事件对象。
可以处理该类型事件的监听器接口:WindowListener
该接口中有七个方法:
windowOpened窗口打开后被调用
windowClosed窗口关闭后被调用
windowClosing窗口关闭时被调用
windowActivated窗口激活时被调用
windowDeactivated窗口失去焦点时被调用
windowIconified窗口最小化时被调用
windowDeiconified最小化窗口还原时被调用
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
jFrame.addWindowListener(new WindowListener() {
public void windowOpened(WindowEvent e) {}
public void windowIconified(WindowEvent e) {
System.out.println("最小化窗口");
}
public void windowDeiconified(WindowEvent e) {
System.out.println("最小化窗口被还原");
}
public void windowDeactivated(WindowEvent e) {}
public void windowClosing(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
public void windowActivated(WindowEvent e) {}
});
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.FocusEvent类
public class FocusEvent extends ComponentEvent{...}
焦点事件类,当组件获得或者失去焦点的时候回产生该类型的事件对象。
可以处理该类型事件的监听器接口:FocusListener
该接口中有俩个方法:
focusGained组件获得焦点时被调用
focusLost组件失去焦点时被调用
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JTextField field = new JTextField(10);
JButton btn = new JButton("test");
field.addFocusListener(new FocusListener() {
public void focusLost(FocusEvent e) {
System.out.println("失去焦点");
}
public void focusGained(FocusEvent e) {
System.out.println("获得焦点");
}
});
panel.add(field);
panel.add(btn);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.ItemEvent类
public class ItemEvent extends AWTEvent{..}
选择事件类,选择复选框、选项框、单击列表框等时会产该事件对象。
可以处理该类型事件的监听器接口:ItemListener
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JCheckBox jck = new JCheckBox("自动登录");
jck.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
System.out.println(e.getStateChange());
}
});
panel.add(jck);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.TextEvent类
public class TextEvent extends AWTEvent{..}
文本内容类,组件中的文本已改变时会产生该事件对象。
可以处理该类型事件的监听器接口:TextListener
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
TextField field = new TextField(10);
field.setText("hello");
field.addTextListener(new TextListener() {
@Override
public void textValueChanged(TextEvent e) {
System.out.println("改变了");
}
});
panel.add(field);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
注:如果是JTextField,那么需要这样监听内容的改变:
JTextField field = new JTextField(10);
field.getDocument().addDocumentListener(new DocumentListener() {
public void removeUpdate(DocumentEvent e) {
}
public void insertUpdate(DocumentEvent e) {
}
public void changedUpdate(DocumentEvent e) {
}
});
java.awt.event.KeyEvent类
注:InputEvent是ComponentEvent的子类,ComponentEvent类在上面已经介绍过了。
public class KeyEvent extends InputEvent{..}
键盘事件类,键盘输入的时候回产生此事件对象。
可以处理该类型事件的监听器接口:KeyListener
该接口中有三个方法:
keyPressed键按下时被调用
keyReleased键释放时被调用
keyTyped键入某个键时被调用(F1等功能按键时不会触发)
注:KeyEvent类中定义了很多静态常量,几乎把键盘上所以的按键都表示出来了。
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JTextField field = new JTextField(10);
field.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
System.out.println("keyTyped "+e.getKeyCode()+" "+e.getKeyChar());
}
@Override
public void keyReleased(KeyEvent e) {
System.out.println("keyReleased "+e.getKeyCode());
}
@Override
public void keyPressed(KeyEvent e) {
System.out.println("keyPressed "+e.getKeyCode());
}
});
panel.add(field);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
java.awt.event.MouseEvent类
public class MouseEvent extends InputEvent{..}
鼠标事件类,当鼠标在组件中发生鼠标动作的时候会产生此事件对象。
有三个接口可以处理该类型事件:
MouseListener接口
MouseMotionListener接口
MouseWheelListener接口
MouseListener接口中有五个方法:
mouseClicked 鼠标单击时被调用
mouseEntered 鼠标进入时被调用
mouseExited 鼠标离开时被调用
mousePressed 鼠标键按下时被调用
mouseReleased鼠标键释放时被调用
MouseMotionListener接口中有俩个方法:
mouseMoved 鼠标移动时被调用
mouseDragged鼠标拖拽时被调用
MouseWheelListener接口中有一个方法:
mouseWheelMoved 鼠标滚轮滚动时被调用
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
panel.addMouseListener(new MouseListener() {
public void mouseReleased(MouseEvent e) {
System.out.println("mouseReleased");
}
public void mousePressed(MouseEvent e) {
System.out.println("mousePressed");
}
public void mouseExited(MouseEvent e) {
System.out.println("mouseExited");
}
public void mouseEntered(MouseEvent e) {
System.out.println("mouseEntered");
}
public void mouseClicked(MouseEvent e) {
System.out.println("mouseClicked");
}
});
panel.addMouseMotionListener(new MouseMotionListener() {
public void mouseMoved(MouseEvent e) {
System.out.println("mouseMoved");
}
public void mouseDragged(MouseEvent e) {
System.out.println("mouseDragged");
}
});
panel.addMouseWheelListener(new MouseWheelListener() {
public void mouseWheelMoved(MouseWheelEvent e) {
System.out.println("mouseWheelMoved");
}
});
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.add(panel);
jFrame.setVisible(true);
65.适配器
很多监听器接口中都定义了很多个方法,每个方法负责处理一种产生事件的情况,我们编写实现类的时候就需要实现监听器接口中的所有方法,但是很多时候我们其实只需要调用接口中的一个方法,但是由于语法要求我们还是必须把接口中的所有抽象全都实现了。
例如: 点击按钮输出hello world
JButton btn = new JButton("test");
btn.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent e) {
System.out.println("hello world");
}
public void mouseReleased(MouseEvent e) {}
public void mousePressed(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
});
使用了MouseListener接口的匿名内部类对象,并且五个方法全都实现了,但是其实我们只需要调用mouseClicked方法.
为了处理这个代码中出现的情况,又引入了接口的适配器类:XxxxAdapter
MouseAdapter实现了MouseListener, MouseWheelListener, MouseMotionListener三个接口,并且把接口中抽象方法全都进行了空实现,将来我们只需要创建MouseAdapter类的匿名内部类然后重写我们想调用的方法即可
例如: 点击按钮输出hello world
JButton btn = new JButton("test");
btn.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
System.out.println("hello world");
}
});
除了MouseAdapter实现MouseListener, MouseWheelListener, MouseMotionListener三个接口,是一个适配器类之外,还有其他的一些适配器类:
WindowAdapter适配器类实现了WindowListener, WindowStateListener, WindowFocusListener三个接口
ComponentAdapter适配器类实现了ComponentListener接口
ContainerAdapter适配器类实现了ContainerListener接口
FocusAdapter适配器类实现了FocusListener接口
KeyAdapter适配器类实现了KeyListener接口
MouseMotionAdapter适配器类实现了MouseMotionListener接口
HierarchyBoundsAdapter适配器类实现了HierarchyBoundsListener接口
66.定时器Timer
javax.swing.Timer类,可以定时触发事件,调用监听器的指定方法
例如:
JFrame jFrame = new JFrame();
JPanel panel = new JPanel();
JPanel north = new JPanel();
JButton startBtn = new JButton("开始");
JButton endBtn = new JButton("停止");
final Canvas canvas = new Canvas();
final Timer timer = new Timer(500,new ActionListener() {
private int count;
@Override
public void actionPerformed(ActionEvent e) {
if(count*10>=canvas.getWidth()){
return ;
}
Graphics g = canvas.getGraphics();
g.drawLine(count*10, 0, count*10, canvas.getWidth());
count++;
}
});
startBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
timer.start();
}
});
endBtn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
timer.stop();
}
});
north.add(startBtn);
north.add(endBtn);
panel.setLayout(new BorderLayout());
panel.add(canvas);
jFrame.add(north,BorderLayout.NORTH);
jFrame.add(panel);
jFrame.setSize(400, 400);
jFrame.setLocation(700, 300);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setVisible(true);
67.让当前类实现监听器接口
当前对象this就成为了监听器对象
例如:
public class Test extends JFrame implements ActionListener{
private static final long serialVersionUID = 1L;
private JPanel jPanel;
private JButton btn;
public Test() {
setBounds(700, 500, 500, 500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
initComponet();
setVisible(true);
}
private void initComponet(){
//初始化组件
jPanel = new JPanel();
btn = new JButton("测试");
//设置布局管理器并添加组件
jPanel.add(btn);
add(jPanel);
//给组件添加事件监听器
btn.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
System.out.println("hello world");
}
public static void main(String[] args) {
new Test();
}
}
68.进程和线程的概述
1)进程和线程定义
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
2)进程和线程关系
一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行.
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,并且线程拥有自己的栈空间.
一个程序至少有一个进程,一个进程至少有一个线程
3)进程和线程区别
进程和线程的主要区别在于它们是操作系统不同的资源管理方式。进程有独立的地址空间,一个进程崩溃后,一般是不会对其它进程产生影响;而线程只是一个进程中的不同执行路径,线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉.
4)操作系统中的进程和线程
在操作系统中,以多进程形式,允许多个任务同时运行(其实是进程之间切换运行的);以多线程形式,允许单个任务分成不同的部分运行(每个部分的代码由一个线程来负责执行)。
注:可以看出来一个应用程序的代码,主要是由线程负责在内存中执行,同时这些代码可以分为不同的部分交给多个线程分别执行,在线程执行代码过程中,如果需要用到计算的机资源,那么就可以从线程所属的进程中获取,而进程则是操作系统进行资源分配和调度的独立单位。
思考:为什么运行我们编写的java程序的时候要先启动JVM虚拟机?
69.java中的线程
1)Thread类
java.lang.Thread类
public class Thread extends Object implements Runnable{..}
Thread是java中的线程类,是对java中线程的抽象,Thread类型的对象就可以表示java中的一个线程
注:一个线程对象的作用就是可以单独运行我们所交给它的任务
注:Thread类及其子类的对象都可以表示一个线程对象
2)线程的分类
在Java中有两类线程:
用户线程 (User Thread)
也可以称为前台线程、执行线程
守护线程 (Daemon Thread)。
也可以称为后台线程、精灵线程(Daemon有精灵的意思)
守护线程,是指程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的退出: 如果用户线程已经全部处于死亡状态,虚拟机也就退出了,这是也不用管守护线程是否还存在了
注:java中创建出来的线程默认是用户线程,但是在启动线程前可以通过特定方法(setDaemon)把该线程转为守护线程
3)名字叫"main"的线程
当我们运行一个java程序的时候,其实就是让JVM创建一个名字叫"main"的线程,然后让这个线程去执行我们所编写的类中main方法的代码。我们可以把这个线程称之为main线程或主线程,因为这个线程是第一个执行我们编写代码的线程,但是这时候并不是只有这一个线程在JVM中,可以通过jconsole观察到当前的所有线程
注:jconsole是JDK自带的监测java程序运行的工具
4)多线程程序
我们之前所编写的代码绝大多数都是main线程执行的(单线程),但也有一些是多线程的程序,例如在GUI中的定时器Timer的使用,其实就是启动了一个新的线程,要不然怎么可能做到一边打地鼠加分,一边还可以进行倒计时显示.
由于java中允许在一个线程中创建并启动另一个线程,所以我们可以很容易的编程出一个多线程程序来。
思考:为什么要编写多线程程序,单线程程序不好么?
5)多线程程序的执行
为了提高程序执行效率,很多应用中都会采用多线程模式,这样可以将任务分解以及并行执行,从而提高程序的运行效率。但这都是代码级别的表现,而硬件上需要使用CPU的时间片模式来提供支持。程序的任何指令的执行都要竞争CPU这个最宝贵的资源,不论程序分成了多少个线程去执行各自的任务,这些线程都必须通过一定的方式来获取时间片,从而得到CPU的使用权进行代码的执行。
注:时间片就是CPU分配程序的使用时间,每个线程获得一个时间片后,在此段时间内是可以使用CPU进行运算的,但时间用完后就要交出CPU的使用权.
注:不同操作系统中,或者同类操作系统的不同算法中,时间片的大小是不一样的,但是不论哪种情况,对象我们来讲,这个时间片都是一个极短的一段时间.
让线程获得时间片的算法有多种,但是现在一般都是"抢占式",就是默认情况下,多个线程具有同等几率抢占到CPU的下一个时间片,最终谁能抢到那么这个时间片就算是谁的,使用完之后再退出来重新再争夺一下CPU的时间片
70.Thread类和Runnable接口
1)Thread类中的run方法
线程对象中的run方法,就是线程独立运行之后,必须要执行的方法,如果我们有什么代码要交给一个线程独立运行,那么就需要把这些代码放到run中.
2)Thread类中的start方法
在代码中,我们并不能直接调用一个线程对象的run方法,而且需要调用线程对象的start方法来启动这个线程,然后这个线程会自动的调用run方法的,如果直接调用了run方法,那就不是多线程代码了
3)Thread类和Runnable接口的关系
Runnable接口中只有一个方法:
public interface Runnable{
public void run();
}
Thread类是Runnable接口的实现类,大致代码如下:
public class Thread implements Runnable{
private Runnable target;
public Thread(){}
public Thread(Runnable target) {
this.target = target;
}
public void run(){
if (target != null) {
target.run();
}
}
}
4)创建和启动线程
第一种方式:创建Thread的子类对象,子类中重写run方法
例如:
//如果需要,可以考虑使用匿名内部类
Thread t = new Thread(){
public void run(){
//代码...
}
};
//启动线程
t.start();
第二种方式:创建Thread类对象,在构造器中传Runnable接口的实现类,实现类中重写run方法
例如:
//如果需要,可以考虑使用匿名内部类
Thread t = new Thread(new Runnable(){
public void run(){
//代码...
}
});
//启动线程
t.start();
注:观察直接调用线程对象的run方法和start方法后有什么不同?
71.线程对象的状态
在java中使用枚举类型Thread.State可以表示出一个线程对象当前的状态,调用线程对象的getState()方法可以获得线程的当前状态
java.lang.Thread.State枚举类型
public class Thread implements Runnable{
public enum State {
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;
}
}
1)Thread.State枚举类型每个对象表示的状态含义
NEW(新建尚未运行/启动)
A thread that has not yet started is in this state.
一般是还没调用start方法,或者刚刚调用了start方法,start方法不一定"立即"改变线程状态,中间可能需要一些步骤才完成一个线程的启动。
RUNNABLE(可运行状态: 包括正在运行或准备运行)
A thread executing in the Java virtual machine is in this state.
start方法调用结束,线程由NEW变成RUNNABLE.
线程存活着,并尝试抢占CPU资源,或者已经抢占到CPU资源正在运行的状态都显示为RUNNABLE
BLOCKED(等待获取锁时进入的状态)
A thread that is blocked waiting for a monitor lock is in this state.
线程A和线程B都要执行方法test,而且方法test被加了锁,线程A先拿到了锁被运行执行test方法,线程B这时候需要等待线程A把锁释放。这时候线程B就是处理BLOCKED
WAITING(通过wait方法进入"无限期"的等待)
A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
线程A和线程B都要执行方法test,而且方法test被加了锁,线程A先拿到了锁被运行执行test方法,线程B这时候需要等待线程A把锁释放(线程B处于BLOCKED状态),如果这时候线程A调用了wait方法,那么线程A就会马上交出CPU的使用权以及刚才拿到的锁,从而进入到WAITING状态,而线程B发现锁已经被释放了,线程B就从BLOCKED状态进入到了RUNNABLE,如果线程B拿到了锁之后在运行期间,调用了notify或者notifyAll方法,这时候线程A就会从WAITING状态进入到BLOCKED状态,从而等待锁的是释放.
TIMED_WAITING(通过sleep或wait等方法进入的"有限期"等待的状态)
A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
线程对象的sleep或wait等方法都可以传一个时间参数,表示就算没有其他线程调用特定方法来改变自己状态的时候,也可以通过这个时间参数让自己自动改变状态(因为时间到了)。
TERMINATED(线程终止状态)
A thread that has exited is in this state.
线程结束了,就处于这种状态,也就是run方法运行结束了。
2)通常对线程对象状态的描述
为了便于理解和记忆,通过会对Thread.State中定义的状态进行整理归类,最终可得到书中所描述的线程状态图。
图中将线程状态分为:
初始化状态
就绪状态
运行状态
死亡状态
阻塞状态(阻塞在等待池、阻塞在锁池、其他情况阻塞)
这样可以让我们对线程状态有一个更好的理解和掌握。
注:先整体了解下线程有哪些状态,等全面学完后再来验证每个状态的情况。
72.ThreadGroup线程组
Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,对线程组的控管理,即同时控制线程组里面的这一批线程
用户创建的所有线程都属于指定线程组,如果没有显示指定属于哪个线程组,那么该线程就属于默认线程组(即名字叫"main"的线程组)
默认情况下,子线程和父线程处于同一个线程组
只有在创建线程时才能指定其所在的线程组,线程运行中途不能改变它所属的线程组,也就是说线程一旦指定所在的线程组,就直到该线程结束
1)创建线程组
java.lang.ThreadGroup类
创建线程组的时候需要指定一个线程组的名字,或者创建线程组的时候指定名字和它的父线程组。
创建线程组的时候需要指定线程组名字和它的父线程组,如果不指定其父线程组,那么默认是父线程组是当前线程组。(类中提供俩种构造器)
public ThreadGroup(String name);
public ThreadGroup(ThreadGroup parent, String name);
例如:
//获得当前线程的所属的线程组
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
//默认其父线程组是currentGroup
ThreadGroup tg1 = new ThreadGroup("线程组1");
//指定其父线程组tg1
ThreadGroup tg2 = new ThreadGroup(tg1,"线程组1");
2)线程和线程组
例如:
//不指定则属于默认线程组
Thread t1 = new Thread("t1线程");
//也可以指定线程组
ThreadGroup tg = new ThreadGroup("我的线程组");
Thread t1 = new Thread(tg,"t1线程");
73.Thread类中常用方法
注:Thread类中有一些方法已经被标注为过时,不推荐使用.
官网中还给出了弃用哪些方法的原因:
http://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html
第一类:静态方法
public static int activeCount()
返回当前线程的线程组中活动线程的数目
public static Thread currentThread()
返回对当前正在执行的线程对象的引用
public static int enumerate(Thread[] tarray)
将当前线程的线程组及其子组中的每一个活动线程复制到指定的数组中
public static void sleep(long millis)
让当前线程在指定的毫秒数内休眠
public static void yield()
暂停当前运行的线程,让给其他线程使用CPU执行
public static boolean holdsLock(Object obj)
判断当前线程先是否拿着指定的锁
第二类:非静态方法
public void run()
线程要执行的代码在此方法中
public void start()
线程启动时必须调用的方法
public long getId()
返回该线程的标识符,线程ID是一个正的long数,在创建该线程时生成。线程ID是唯一的,线程终止时,该线程ID可以被重新使用
public void setName(String name)
设置该线程的名称
public String getName()
返回该线程的名称
public int setPriority()
设置线程的优先级
public int getPriority()
返回线程的优先级
public Thread.State getState()
返回该线程的状态
public ThreadGroup getThreadGroup()
返回该线程所属的线程组
public boolean isAlive()
测试线程是否处于活动状态
public void setDaemon(boolean on)
将该线程标记为守护线程或用户线程,默认false表示用户线程
public boolean isDaemon()
测试该线程是否为守护线程
public void join()
当前线程等待某个线程执行结束
public void join(long millis)
给定一个等待的限定时间
第三类:容易混淆的方法
public void interrupt()
中断线程
public boolean isInterrupted()
测试线程是否已经中断
public static boolean interrupted()
测试当前线程是否已经中断
如果线程a对象是处于阻塞状态的话,在线程b中调用a.interrupt()是会打断线程a的阻塞状态的(后抛出打断异常)
但是如果线程a对象是处于就绪等状态,在线程b中调用a.interrupt()只是会改变对象a内部的一个boolean类型标识,用来表示线程b想打断线程a
isInterrupted和interrupted的返回值就是这个boolean类型的值
区别在于静态方法interrupted在返回boolean值后,会把这个打断的标示符给清理掉,而且非静态方法isInterrupted不会清理
//观察Thread类中的部分源码
public class Thread implements Runnable{
public void interrupt() {
if (this != Thread.currentThread())checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
//判断当前线程是否是阻塞状态
if (b != null) {
// Just to set the interrupt flag
interrupt0();
b.interrupt(this);
return;
}
}
//如果不是阻塞状态就只set一下打断的flag
interrupt0();
}
private native void interrupt0();
public boolean isInterrupted() {
return isInterrupted(false);
}
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
private native boolean isInterrupted(boolean ClearInterrupted);
}
74.数据共享
俩个或多个线程可以共享对象中的数据
例如:
public class ThreadTest extends Thread{
private Student student;
public ThreadTest3(String name) {
super(name);
}
public Student getStudent() {
return student;
}
public void setStudent(Student student) {
this.student = student;
}
public void run() {
String name = Thread.currentThread().getName();
for(int i=0;i<10;i++){
//先把当前线程的名字设置为student的名字
student.setName(name);
//再马上拿出名字打印出来
System.out.println(name+": "+student.getName());
}
}
public static void main(String[] args) {
Student s = new Student();
ThreadTest t1 = new ThreadTest("线程1");
t1.setStudent(s);
ThreadTest t2 = new ThreadTest("线程2");
t2.setStudent(s);
t1.start();
t2.start();
}
}
例如:
Runnable run = new Runnable() {
private int x;
public void run() {
String name = Thread.currentThread().getName();
for(int i=1;i<=500;i++){
x++;
}
System.out.println(name+" x = "+x);
}
};
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t1.start();
t2.start();
思考:对象中的那些变量会被多个线程所共享,共享数据在多线程环境中会有什么问题?
75.非线程安全和线程安全
在上面的例子中可以看到,在多个线程并发访问的环境中对共享数据进行操作的代码或方法中会出现其结果不确定的情况,那么这段代码或者方法就是非线程安全的。产生这种情况的原因大多是因为一个线程在执行这段代码或方法的时间内,另一个线程也是有可能执行这段代码或方法的(抢夺时间片),从而造成了这段代码或者方法中对共享数据操作的结果
如果一段代码在多个线程并发访问的时候是 非线程安全的,那么就可以采用把这段代码进行线程同步的方式进行处理,最终把这段代码变为线程安全的
线程同步其实就是本来多个线程并发访问这段代码的,同步后就变成了一个线程一个线程的按顺序线程访问这段代码,这样以来一个线程在执行这段代码期间,就不用担心其他线程会来打扰自己在这段代码中对共享数据的操作了。
注:线程同步是牺牲了效率换来了安全
76.线程同步的实现
在java中,使用synchronized关键字来实现线程同步的效果。
synchronized关键字可以用来修饰方法,也可以直接作用到某段代码上
例如:
public class Test{
private int x;
public synchronized void test(){
String name = Thread.currentThread().getName();
for(int i=0;i<100;i++){
x++;
}
System.out.println(name+": x="+x);
}
}
例如:
public class Test{
private int x;
public void test(){
String name = Thread.currentThread().getName();
synchronized(this){
for(int i=0;i<100;i++){
x++;
}
}
System.out.println(name+": x="+x);
}
}
synchronized关键字是加锁的意思,用它来修饰方法就表示给该方法加了锁,从而达到线程同步的效果;用它来修饰代码块就表示给该代码块加了锁,从而达到线程同步的效果。
例如:一个方法test使用synchronized关键字修饰后加了锁,如果这个时候有俩个线程对象t1和t2要并发访问test方法,假设t1先抢到了CPU的执行权,从而率先拿到了test方法上的锁,然后就进到test方法中执行代码,一个时间片用完之后,就退回到就绪状态,但是t1线程却依然拿着锁,那么下次CPU的抢占即使是t2抢到了也没有办法执行test方法,因为t2拿不到锁就没有办法进到test方法中执行代码,这时候t2线程就会进入到锁池里面了。
注:java中任何对象都可以当做锁,能否拿到锁就决定了一个线程是否能进入到被锁的代码块中去执行代码
例如:
public class Test{
//默认使用this来充当这把锁,锁的是test1方法
public synchronized void test1(){
//代码
}
//默认使用this.getClass()来充当这把锁,锁的是test2方法
public static synchronized void test2(){
//代码
}
public void test3(){
//这种形式可以使用任意对象充当锁,锁的是这个代码块
synchronized(任意对象){
//代码
}
}
}
77.线程通信-wait()与notify()/notifyAll()方法
在Object中,三个wait方法(重载),notify()以及notifyAll()方法都是和线程通信有关的方法
注:这几个方法只能是在synchronized关键字使用时,被用来充当锁的对象才能调用,并且只能再加锁的范围内调用,否则其他情况调用会抛出异常。
1)wait方法
当一个线程拿到锁,进入到被锁的代码中执行代码时候,突然调用了锁的wait方法,那么这个线程这时候就交出CPU使用权,并且把锁方法原出,然后由运行状态进入到等待池中,进行"无限期"的等待,直到有其他线程对象调用了特定的打断/唤醒方法后,这个线程才能从等待池中出来。
注:如果使用有参数的wait方法,那就是"有限期"的等待
例如:
//默认使用this充当这把锁
public synchronized void test(){
int a = 1;
if(a>0){
try {
//只能使用锁调用wait方法
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2)notify()方法
当一个线程在被锁的点中使用锁调用了notify()方法,那么可以随机唤醒在等待池等待这把锁的一个线程(如果有多个随机都等这同一把锁的话),这个被唤醒的线程就会从等待池进入到锁池中,如果之后某个时刻这个线程发现等待的池已经被其他线程释放了,那么它就会从锁池进入到就绪状态,准备争夺CPU的使用权并且争取这把锁。
例如:
//默认使用this充当这把锁
public synchronized void test(){
int a = 1;
if(a>0){
try {
//只能使用锁调用notify方法
this.notify();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3)notifyAll()方法
与notify类似,不同之处在于,notifyAll()方法会叫醒等待池中等待同一把锁的所有线程对象。
例如:
//默认使用this充当这把锁
public synchronized void test(){
int a = 1;
if(a>0){
try {
//只能使用锁调用notifyAll方法
this.notifyAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
4)根据以下代码,补全pos和wit方法完成其功能
注:银行账号的余额不能是负数
public class Account {
//账号余额
private int balance;
public Account(int balnace) {
this.balance = balnace;
}
//存钱
public void pos(int money){
}
//消费
public void wit(int money){
}
}
//男孩,负责挣钱
public class Boy extends Thread{
private Account account;
public Boy(Account account, String name) {
this.account = account;
setName(name);
}
public void run() {
while(true){
int money = (int)(Math.random()*10000+1);
account.pos(money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//女孩,负责花钱
public class Girl extends Thread{
private Account account;
public Girl(Account account, String name) {
this.account = account;
setName(name);
}
public void run() {
while(true){
int money = (int)(Math.random()*10000+1);
account.wit(money);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class AccountTest {
public static void main(String[] args) {
Account account = new Account(5000);
Boy boy = new Boy(account, "tom");
Girl lily1 = new Girl(account, "lily1");
Girl lily2 = new Girl(account, "lily2");
Girl lily3 = new Girl(account, "lily3");
boy.start();
lily1.start();
lily2.start();
lily3.start();
}
}
78.死锁
在程序中是不允许出现死锁情况,一旦发生那么只能手动停止JVM的运行,然后查找并修改产生死锁的问题代码。
简单的描述死锁就是:俩个线程t1和t2,t1拿着t2需要等待的锁不释放,而t2又拿着t1需要等待的锁不释放。
注:可以通过jconsole查看到线程死锁的情况
例如:
public class ThreadDeadLock extends Thread{
private Object obj1;
private Object obj2;
public ThreadDeadLock(Object obj1,Object obj2) {
this.obj1 = obj1;
this.obj2 = obj2;
}
public void run() {
String name = Thread.currentThread().getName();
if("Thread-0".equals(name)){
while(true){
synchronized (obj1) {
synchronized (obj2) {
System.out.println(name+" 运行了..");
}
}
}
}
else{
while(true){
synchronized (obj2) {
synchronized (obj1) {
System.out.println(name+" 运行了..");
}
}
}
}
}
public static void main(String[] args) {
Object obj1 = new Object();
Object obj2 = new Object();
Thread t1 = new ThreadDeadLock(obj1,obj2);
Thread t2 = new ThreadDeadLock(obj1,obj2);
t1.start();
t2.start();
}
}
79.volatile关键字
1)停止线程的运行
Thread类中的stop方法可以停止线程的运行,但是该方法已经被弃用了,一般会采用改变对象中的一个标识的方法来停止线程的运行或者使用interrupt方法来中断线程
例如:
public class VolatileTest extends Thread{
private boolean stop;
public void isStop(boolean stop){
this.stop = stop;
}
public void run() {
while(!stop){
System.out.println("hello");
}
}
public static void main(String[] args) {
VolatileTest t = new VolatileTest();
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.isStop(true);
System.out.println("main线程结束");
}
}
2)JVM对代码的执行优化
JVM启动的时候有俩种模式:-server和-client,在第一章的课程里已经介绍过了,其中-server模式下JVM会对热点代码使用JIT编译器以及进行代码优化,所以会出现下面的情况:
例如:
public class VolatileTest extends Thread{
private boolean stop;
public void isStop(boolean stop){
this.stop = stop;
}
public void run() {
int a = 0;
while(!stop){
a++;
}
System.out.println("a = "+a);
}
public static void main(String[] args) {
VolatileTest t = new VolatileTest();
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.isStop(true);
System.out.println("main线程结束");
}
}
注:观察执行结果发现,启动的新线程并不能被停止(注意主线程睡眠时间)
3)产生以上问题的原因
程序入口被执行之后,变量stop及其值是在堆区之中(主内存),新的线程启动运行之后,把堆区中的stop读取并加载到了线程的工作内存之中,虽然t.isStop(true)代码被调用了,但是这只是把主内存中的stop的值给设置为true了,线程工作内存中的stop的值却依然是false,这其实就是主内存和线工作内存中的值不一致造成的。这也是在-server模式下,JVM对代码进行优化提高了代码的执行效率而带来的一些问题。
4)以上问题的解决方案
1.使用synchronized给循环中的代码加锁
例如:
public void run() {
int a = 0;
while(!stop){
synchronized (this) {
a++;
}
}
System.out.println("a = "+a);
}
注:如果synchronized加锁是加在了while外面,则不能达到想要的效果
思考:为什么以上例子中的run方法中如下编写即可停止线程
public void run() {
while(!stop){
System.out.println("hello");
}
}
2.使用volatile关键字
例如:
public class VolatileTest extends Thread{
private volatile boolean stop;
public void isStop(boolean stop){
this.stop = stop;
}
public void run() {
long a = 0;
while(!stop){
a++;
}
System.out.println("a = "+a);
}
public static void main(String[] args) {
VolatileTest t = new VolatileTest();
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.isStop(true);
System.out.println("main线程结束");
}
}
注:从运行结果打印出的数字,可以看到volatile比使用synchronized关键字的效果要高很多。
5)volatile关键字
关键字volatile的作用是强制线程从公共堆栈(主内存)中取得变量的值,而不是从线程私有数据栈中取得变量的值
下面将关键字volatile和synchronized进行比较:
1.关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法和代码块。随着JDK版本的提高,synchronized关键字在执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的
2.多线程程序中访问到volatile不会发生阻塞,而synchronized会出现阻塞
3.volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它也会将线程工作内存和主内存中的数据做同步,但效率比volatile低
注:下面会有例子说明这个原子性的问题
4.关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的多个线程之间访问资源的同步性。
80.java.util.concurrent.locks.Lock接口
JDK5.0后新增了Lock接口,可以是用来实现synchronized关键字的作用,同时功能更加灵活和强大,ReentrantLock类是JDK中提供的Lock接口的默认实现
1)Lock接口的lock()方法和unlock()方法
例如:
public class LockTest extends Thread{
public static void main(String[] args) {
class Run implements Runnable{
private int i;
private Lock lock = new ReentrantLock();
public void run() {
for(int j=0;j<100000;j++){
lock.lock();
try {
i++;
} finally {
lock.unlock();
}
}
}
public int getValue(){
return i;
}
};
Run run = new Run();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
Thread t4 = new Thread(run);
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i = "+run.getValue());
}
}
2)Lock接口的tryLock()方法
尝试着去拿锁,如果拿到了就返回true否则返回false,注意这点和synchronized是不同的,如果是synchronized的话,如果尝试拿不到锁的话,那么当前线程就会变了阻塞状态(阻塞在锁池)
此方法的典型使用语句如下:
Lock lock = new ReentrantLock();
if (lock.tryLock()) {
try {
//安全的同步操作
} finally {
lock.unlock();
}
} else {
//拿不到锁就去做其他事情,但这里就不是线程同步了
}
例如:
main:
class Run implements Runnable{
private Lock lock = new ReentrantLock();
private int i;
public void run() {
boolean flag = true;
String name = Thread.currentThread().getName();
while(flag){
if (lock.tryLock()) {
try {
for(int j=0;j<1000;j++){
i++;
}
} finally {
System.out.println(name+": i = "+i);
flag = false;
lock.unlock();
}
} else {
System.out.println(name+": 郁闷了,没拿到锁,那么就睡会把");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Runnable run = new Run();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t1.start();
t2.start();
3)Lock接口的newCondition方法和Condition接口
Condition接口表示一个条件,Lock接口的newCondition方法可以返回一个Condition接口的实现类对象,表示当前线程执行代码的条件。
Condition接口中的await()方法,相当于Object中的wait方法
Condition接口中的signal()方法,相当于Object中的notify()方法
Condition接口中的signalAll()方法,相当于Object中的notifyAll()方法
例如:这个是使用synchronized关键字实现的
public static void main(String[] args) {
Runnable run = new Runnable() {
private int x;
public void run() {
String name = Thread.currentThread().getName();
synchronized (this) {
for (int i = 0; i < 1000; i++) {
x++;
}
try {
this.notify();
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+" x = "+x);
this.notifyAll();
}
}
};
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t1.start();
t2.start();
}
例如:把上面的例子改为Lock和Condition实现
public static void main(String[] args) {
Runnable run = new Runnable() {
private int x;
private Lock lock = new ReentrantLock();
private Condition con = lock.newCondition();
public void run() {
String name = Thread.currentThread().getName();
lock.lock();
try {
for (int i = 0; i < 1000; i++) {
x++;
}
try {
con.signalAll();
con.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+" x = "+x);
con.signalAll();
} finally {
lock.unlock();
}
}
};
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
t1.start();
t2.start();
}
注:俩种实现方法的效果是一样的,synchronized是java中的关键字,而Lock和Condition和JKD5.0新增的俩个接口,其结合着使用可以代替synchronized的功能,并且功能更加灵活和强大
81.原子操作
1.定义
原子操作(atomic operation)是指不会被线程调度机制打断的操作,这种操作一旦开始,就一直运行到结束,中间不会有任何线程切换。
2.非原子操作i++
在Java里面,++i(i++)或者--i(i--)不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值. 通常情况下,只能使用加锁才能保证读-改-写这三个操作是"原子性"的。也就是说我们平时所用的i++等操作是非原子操作,一般的需要加锁才能保证其"原子性"
例如:
public static void main(String[] args) {
class Run implements Runnable{
private int i;
public void run() {
for(int j=0;j<100000;j++){
i++;
}
}
public int getValue(){
return this.i;
}
};
Run run = new Run();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
Thread t4 = new Thread(run);
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i = "+run.getValue());
}
注:4个线程分别运行for循环10万次,每次i++,i是共享变量,那么最终i的值应该是40万,但是因为i++并非原子操作,所以最终结果也不一定是40万。
注:这里即使使用了volatile修饰共享变量i,也是不行的,因为volatile只是强制线程去主内存中取值和存值,而不能保证i++操作的原子性
3.加锁可以保证i++的原子性
例如:
public static void main(String[] args) {
class Run implements Runnable{
private int i;
public void run() {
for(int j=0;j<100000;j++){
i++;
}
}
public int getValue(){
return this.i;
}
};
Run run = new Run();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
Thread t4 = new Thread(run);
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i = "+run.getValue());
}
4.原子操作类
JDK5.0提供了一组atomic-class来帮助我们简化同步处理。基本工作原理是使用了同步synchronized的方法实现了对一个long, int等类型值的增、减、赋值(更新)操作.
java.util.concurrent.atomic.AtomicXxxxx类
这些类都是类似的使用方法,这里以AtomicInteger为例:
i.get()就是获得该对象对应的int值
i.incrementAndGet() 类似于 ++i
i.getAndIncrement() 类似于 i++
i.getAndDecrement() 类似于 i--
i.decrementAndGet() 类似于 --i
例如:上面的例子可以修改如下
public static void main(String[] args) {
class Run implements Runnable{
private AtomicInteger i = new AtomicInteger(0);
public void run() {
for(int j=0;j<100000;j++){
i.incrementAndGet();
}
}
public int getValue(){
return i.get();
}
};
Run run = new Run();
Thread t1 = new Thread(run);
Thread t2 = new Thread(run);
Thread t3 = new Thread(run);
Thread t4 = new Thread(run);
t1.start();
t2.start();
t3.start();
t4.start();
try {
t1.join();
t2.join();
t3.join();
t4.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i = "+run.getValue());
}
注:保证操作的原子性,每次运行后的结果都是40万
82.线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么如果一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务的话,那么就可以大大提高其运行的效率。
在Java中可以通过线程池来达到这样的效果
1)了解java.util.concurrent包中的一些类和接口
ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor
Executor是一个顶层接口,在它里面只声明了一个方法execute(Runnable),返回值为void,参数为Runnable类型,该方法就是用来执行传进去的任务的
ExecutorService接口继承了Executor接口,并声明了一些方法:submit、invokeAll、invokeAny以及shutDown等;
抽象类AbstractExecutorService实现了ExecutorService接口,基本实现了ExecutorService中声明的所有方法
ThreadPoolExecutor继承了类AbstractExecutorService,并进行了扩展。ThreadPoolExecutor就是JKD中提供的一个线程池类。
ThreadPoolExecutor类中的一个构造器:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
参数的含义:
corePoolSize:线程池维护线程的最少数量
maximumPoolSize:线程池维护线程的最大数量
keepAliveTime:线程池维护线程所允许的空闲时间
unit:线程池维护线程所允许的空闲时间的单位
workQueue:线程池所使用的缓冲队列
例如:
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 2000, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i<15;i++){
MyRun myTask = new MyRun();
executor.execute(myTask);
System.out.println("线程池中线程数目: "+executor.getPoolSize()+
",队列中等待执行的任务数目: "+executor.getQueue().size()+
",已执行完的任务数目: "+executor.getCompletedTaskCount());
}
executor.shutdown();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(executor.getCompletedTaskCount());
}
}
class MyRun implements Runnable{
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("任务结束");
}
}
注:从执行结果可以看出,当线程池中线程的数目大于5时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程。
2)java.util.concurrent.Executors类
这个是一个工厂类,可以便捷的帮我们生产出线程池对象,同时该类中也提供了一些实用的工具方法.
工厂方法1:创建一个定长的线程池,可控制线程最大并发数,超出的线程会在队列中等待
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
例如:
main:
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for(int i = 0; i < 10; i++) {
final int index = i;
fixedThreadPool.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
fixedThreadPool.shutdown();
工厂方法2:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,否则则新建线程
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
例如:
main:
ThreadPoolExecutor cachedThreadPool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int index = i;
try {
Thread.sleep(index * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
cachedThreadPool.execute(new Runnable() {
public void run() {
System.out.println(index);
}
});
System.out.println("线程池中当前线程的数量: "+cachedThreadPool.getPoolSize());
}
cachedThreadPool.shutdown();
工厂方法3:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
例如:
main:
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int index = i;
singleThreadExecutor.execute(new Runnable() {
public void run() {
try {
System.out.println(index);
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
singleThreadExecutor.shutdown();
工厂方法4:创建一个定长线程池,支持定时及周期性任务执行
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
例如:
main:
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println("延迟10秒后执行,之后再按照每3秒执行一次");
}
}, 10, 3, TimeUnit.SECONDS);
83.Callable接口和Future接口
Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且声明无法抛出异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值
java.util.concurrent.FutureTask类实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值.
例如:
假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到.
main:
Callable<Integer> callable = new Callable<Integer>() {
public Integer call() throws Exception {
//返回0-99的随机数,100取不到
return new Random().nextInt(100);
}
};
FutureTask<Integer> future = new FutureTask<Integer>(callable);
new Thread(future).start();
try {
//这里可以做其他的操作,等需要的时候可以调用future的get方法来拿到这个计算的结果
Thread.sleep(2000);
if(future.isDone()){
System.out.println(future.get());
}
}catch (Exception e) {
e.printStackTrace();
}
84.CompletionService接口和实现类ExecutorCompletionService
CompletionService主要用于将新添加的异步任务与已完成任务的结果分离开来
主要方法:
public Future<V> submit(Callable<V> task)
提交要执行任务,并返回表示任务结果的Future
public Future<V> take()
获取并移除表示下一个已完成任务的Future,如果目前不存在这样的任务,则等待。
public Future<V> poll()
获取并移除表示下一个已完成任务的Future,如果不存在这样的任务,则返回null。
例如:
main:
ExecutorService threadPool = Executors.newCachedThreadPool();
CompletionService<Integer> cs = new ExecutorCompletionService<Integer>(threadPool);
for (int i = 0; i < 5; i++) {
final int taskID = i;
cs.submit(new Callable<Integer>() {
public Integer call() throws Exception {
return taskID;
}
});
}
for (int i = 0; i < 5; i++) {
try {
System.out.println(cs.take().get());
} catch (Exception e) {
e.printStackTrace();
}
}
85.流的概念
流是个抽象的概念,是对输入输出设备的抽象,Java程序中,对于数据的输入/输出操作都是以"流"的方式进行。设备可以是文件,网络,内存等
流具有方向性,至于是输入流还是输出流则是一个相对的概念,一般以程序为参考,如果数据的流向是程序至设备,我们成为输出流,如果数据的流向是设备至程序称为输入流。
数据以二进制的形式在程序与设备之间流动传输,就想水在管道里流动一样,所以就把这种数据传输的方式称之为输入流输出流
思考:代码程序中要读取文件中的内容,需要使用什么流?代码程序中要向文件中写内容,又需要使用什么流?
86.流的分类
1)按照流的方向分为输入流和输出流
2)按照处理数据的单位不同分为字节流和字符流
字节流读取的最小单位是一个字节(1byte=8bit),而字符流一次可以读取一个字符(1char = 2byte = 16bit)
3)按照功能的不同分为节点流和处理流
节点流是可以"直接"从一个数据源中读写数据的流。
处理流也可以称为功能流或者包装流,它是可以对节点流进行封装的一种流,封装后可以增加节点流的功能。
例如:FileInputStream是一个节点流,可以直接从文件读取数据,而BufferedInputStream可以包装 FileInputStream,使得其有缓冲数据的功能。
4)除了以上三种分类外,还有其他的一些类型的:对象流、缓冲流、压缩流、文件流等等,其实这些都是节点流或者处理流的子分类。当然还可以分出来其他的流类型,如果有这样需要的话。
5)不管流的分类是多么的丰富和复杂,其根源来自于四个基本的父类
字节输入流:InputStream
字节输出流:OutputStream
字符输入流:Reader
字符输出流:Writer
注:这四个父类都是抽象类
87.字节流中的常用节点流
注:java中常用的io流都在java.io包中
1)InputStream
//从输入流中读取数据的下一个字节
//如果到达流的末尾则返回 -1
public abstract int read();
//把读到的字节存到字节数组b中,并返回本次读到了多少个字节
public int read(byte[] b){..}
//把读到的字节存到字节数组b中,同时指定开始存的位置以及最大字节数,并返回本次读到了多少个字节
public int read(byte[] b,int off,int len){..}
//返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数
public int available(){..}
//跳过此输入流中数据的 n 个字节
public long skip(long n){..}
//关闭此输入流并释放与该流关联的所有系统资源
public void close(){..}
//测试此输入流是否支持 mark 和 reset 方法
public boolean markSupported(){..}
//在此输入流中标记当前的位置
public void mark(int readlimit){..}
//将此流重新定位到最后一次对此输入流调用mark方法时的位置
public void reset(){..}
2)OutputStream
//将指定的字节写入此输出流
public abstract void write(int b);
//将字节数组b中的所有字节写入此输出流
public void write(byte[] b){..}
//将字节数组b中的字节写入此输出流,指定开始位置及最大字节数
public void write(byte[] b,int off,int len){..}
//刷新此输出流并强制写出所有缓冲的输出字节
public void flush(){..}
//关闭此输出流并释放与此流有关的所有系统资源
public void close(){..}
3)InputStream的子类和OutputStream的子类几乎都是成对出现的,一个负责读数据的工作,一个负责写数据的工作
4)System.out和System.in
System类的部分源码:
public final class System{
//标准输入流
public final static InputStream in = null;
//标准输出流。
public final static PrintStream out = null;
//标准错误输出流
public final static PrintStream err = null;
public static void setIn(InputStream in){..}
public static void setOut(PrintStream out){..}
public static void setErr(PrintStream err){..}
}
标准输入流会默认从控制台读取数据
标准输出流会默认把数据输出到控制台
System.out.println(System.in.getClass());
System.out.println(System.out.getClass());
输出结果为:
class java.io.BufferedInputStream
class java.io.PrintStream
5)ByteArrayInputStream和ByteArrayOutputStream
ByteArrayInputStream可以从数组中读取字节
ByteArrayOutputStream可以把字节写到对象中的缓冲区里面,其实就是一个字节数组
6)FileInputStream和FileOutputStream
FileInputStream可以读取文件中的字节
FileOutputStream可以向文件中写进去字节
7)PipedInputStream和PipedOutputStream
PipedInputStream管道字节输入流
PipedOutputStream管道字节输出流
注:使用时需要把俩个管道进行对接
8)ObjectInputStream和ObjectOutputStream
在序列化中要使用的对象输入流和对象输出流,之后再来使用测试
9)java.io.File类
File类型对象可以表示一个文件也可以表示一个目录.
88.字节流中的处理流
也可以称为功能流或者包装流,因为它是对节点流进行包装的一种流,包装后可以增加节点流的功能。但是处理流本身并不能直接读写数据
1)BufferedInputStream和BufferedOutputStream
可以给字节流中的节点流提供代码缓冲区的输入/输出流
2)DataInputStream和DataOutputStream
可以给字节流中的节点流提供输入/输出java中不同类型的数据
3)PrintStream
PrintStream为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式
89.字符流
1)Reader
public int read(){..}
public int read(char[] cbuf){..}
public abstract int read(char[] cbuf, int off,int len){..}
//指定缓冲区
//@since 1.5
public int read(CharBuffer target){..}
abstract public void close();
public long skip(long n){..}
public boolean markSupported(){..}
public void mark(int readAheadLimit){..}
public void reset(){..}
//Tells whether this stream is ready to be read
public boolean ready(){..}
2)Writer
public void write(int c){..}
public void write(char cbuf[]){..}
abstract public void write(char cbuf[], int off, int len);
public void write(String str){..}
public void write(String str, int off, int len){..}
abstract public void flush();
abstract public void close();
//@since 1.5
//和out.write(c)的效果一样
public Writer append(char c){..}
public Writer append(CharSequence csq){..}
public Writer append(CharSequence csq, int start, int end){..}
3)CharArrayReader和CharArrayWriter
CharArrayReader可以读取字符数组中的内容
CharArrayWriter可以向字符数组中写内容
4)FileReader和FileWriter
FileReader读取文件内容的便捷类,InputStreamReader的子类
FileWriter写入文件内容的便捷类,OutputStreamWriter的子类
5)PipedReader和PipedReader
PipedReader管道字符输入流
PipedReader管道字符输出流
6)BufferedReader和BufferedWriter
这个俩个流属于处理流,它们本身并不能读取数据,它们的作用是包装在其他节点流上面,为其提供额外的功能
7)PrintWriter
一般会把BufferedReader和PrintWriter配合在一起使用,因为BufferedReader可以一次读一行字符串,而PrintWriter可以一次写一行字符串(自动换行)。
90.转换流
InputStreamReader和OutputStreamWriter是一对名字中既有Stream,又有Reader或Writer的流,因为它们是转换流,负责把一个字节流转换为字符流。所以它们是字节流和字符串之间的桥梁.
注:在把字节流转换为字符流的过程中,还可以指定字符编码,避免乱码的出现。
91.对象流
1)序列化和反序列化
Java中的序列化是指把Java对象转换为字节序列的过程
对象---序列化--->01010101
Java中的反序列化是指把字节序列恢复为Java对象的过程
01010101---反序列化--->对象
思考:为什么需要序列化和反序列化?
2)如何实现序列化和反序列化
使用对象流即可实现对象的序列化和反序列化
ObjectOutputStream类中的方法可以完成对象的序列化:
public final void writeObject(Object obj){..}
ObjectInputStream类中的方法可以完成对象的反序列化:
public final Object readObject(){..}
注:这俩个对象流都属于字节流
3)序列化的要求
只有实现了java.io.Serializable接口的类的对象才可以被序列化,否则序列化时会报错
思考:测试序列化版本号的作用是什么?
4)transient关键字
在对象序列化的时候,被transient修饰的属性的值,在序列化过程中是会被忽略掉的。
92.随机访问流
java.io.RandomAccessFile类
public class RandomAccessFile extends Object{..}
这是个特殊的流,它不属于之前那些流的体系。
这个流的既可以用来读文件,也可以用来给文件中写内容,并且该类中的方法可以用来定位文件中的位置:
public native void seek(long pos);
构造器中需要设置该流的操作模式:
//对文件只读
RandomAccessFile r = new RandomAccessFile(filePath,"r");
//对文件即可读又可写
//但是一旦确定了读或者写,那么就不能在变
RandomAccessFile rw = new RandomAccessFile(filePath,"rw");
93.网络编程概述
计算机网络,是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在操作系统、网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。这个网络中包含的设备有:计算机、路由器、交换机等等
网络编程的实质就是已经建立好的计算机网络的基础之上,通过使用程序控制的特定方式,让两个(或多个)设备(例如计算机)之间进行数据传输
java中的网络编程并不要求我们之前对计算机网络的知识有那么深入的研究,只需对网络的概念有基本的认识,并了解IP地址、端口号、URL等内容,这样就能够学习并使用JDK中提供的网络编程的API
java中和网络编写相关的API都在java.net包下面
94.OSI七层模型和TCP/IP四层模型
开放系统互连参考模型 (Open System Interconnect 简称OSI)是国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)联合制定的开放系统互连参考模型,为开放式互连信息系统提供了一种功能结构的框架。它从低到高共七层分别是:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
ISO制定的OSI参考模型的过于庞大和复杂。为此,由技术人员自己开发的TCP/IP协议栈获得了更为广泛的应用。
TCP/IP协议栈是美国国防部高级研究计划局计算机网(Advanced Research Projects Agency Network,ARPANET)和其后继因特网使用的参考模型。ARPANET是由美国国防部(U.S.Department of Defense,DoD)赞助的研究网络。最初,它只连接了美国境内的四所大学。随后的几年中,它通过租用的电话线连接了数百所大学和政府部门。最终ARPANET发展成为全球规模最大的互连网络(也就是现在的因特网)。最初的ARPANET于1990年永久性地关闭。
TCP/IP参考模型分为四层:应用层、传输层、网络层和网络接口层.
注:TCP/IP参考模型也可以分为五层,就是再把网络接口层分为数据链路层和物理层。
95.TCP和UDP协议
1)TCP(transmission control protocol),传输控制协议
TCP协议是一个面向连接的、可靠的协议。它将一台主机发出的字节流无差错地发往互联网上的其他主机。在发送端,它负责把上层传送下来的字节流分成报文段并传递给下层。在接收端,它负责把收到的报文进行重组后递交给上层。TCP协议还要处理端到端的流量控制,以避免缓慢接收的接收方没有足够的缓冲区接收发送方发送的大量数据。
特点:传输效率低,可靠性强,用于传输可靠性要求高,数据量大的数据.例如:文件传输
2)UDP(user datagram protocol),用户数据报协议
UDP协议是一个不可靠的、无连接协议,主要适用于不需要对报文进行排序和流量控制的场合。
特点:与TCP特性恰恰相反,用于传输可靠性要求不高的数据.例如:QQ聊天发消息或者进行视频
96.HTTP和TPC/IP以及DNS
TPC/IP协议是传输层协议,主要解决数据如何在网络中传输,而HTTP是应用层协议,主要解决如何包装数据。所以,HTTP协议是建立在TCP/IP之上的一个协议。
DNS(Domain names System)是处于应用层的"服务",提供域名到IP地址之间的解析服务。可以让我们访问网络中的一台主机的时候不需要使用IP地址而是使用便于我们记忆的域名。
互联网之间是通过IP地址通信的,但是IP地址并不符合认得记忆习惯,人喜欢记忆有意义的字词。所以DNS服务就为了解决这个问题的,可以让我们使用域名来访问网络中的一台主机。
97.软件架构:C/S和B/S
C/S和B/S,是两种软件架构方式,都可以进行同样的业务处理,甚至也可以用相同的方式实现共同的逻辑。
C/S架构的全称是Client/Server,即客户端服务器端架构,客户端包含一个或多个在用户的电脑上运行的程序,而服务器端可以使用Socket来实现,服务器端的程序通过Socket与客户端的程序通信。
C/S 架构也可以看做是胖客户端架构,因为客户端需要实现很多的业务逻辑和界面展示功能。这种架构中,作为客户端的部分需要承受很大的压力,因为显示逻辑和事务处理都包含在其中。
优点:
C/S架构的界面和操作可以很丰富。
安全性能可以很容易保证。
响应速度较快。
缺点:
适用面窄,通常用于局域网中。
用户群固定,由于程序需要安装才可使用,因此不适合面向一些不可知的用户。
维护成本高,软件进行一次升级,则所有客户端的程序都需要改变。
B/S架构的全称为Browser/Server,即浏览器/服务器结构。Browser指的是Web浏览器,极少数业务逻辑在前端实现,主要业务功能都在服务器端实现。B/S架构的系统无须特别安装,只有Web浏览器即可。(通常是要基于HTTP协议进行信息交互)
B/S架构中,显示逻辑交给了Web浏览器,业务逻辑处理在放在了服务器端,这样就避免了庞大的胖客户端,减少了客户端的压力。因为客户端包含的逻辑很少,因此也被成为瘦客户端。
优点:
客户端无需安装,有Web浏览器即可。
B/S架构可以直接放在广域网上,通过一定的权限控制实现多客户访问的目的,交互性较强。
B/S架构无需升级多个客户端,升级服务器即可。
缺点:
在跨浏览器上,B/S架构不尽如人意。
显示效果要达到C/S程序的程度需要花费不少精力。
在速度和安全性上需要花费巨大的设计成本。
客户端服务器端的交互一般是请求-响应模式,通常需要刷新页面
98.java中基于TCP协议的网络编程
java.net.Socket类
socket也称为套接字,可以用来表示两台机器间通信的端点,可以用socket对客户端进行实现
java.net.ServerSocket类
ServerSocket可以实现服务器端套接字。
注:java的网络编程也可以称为套接字编程或者socket编程
99.java中基于UDP协议的网络编程
java.net.DatagramSocket类
UDP协议编程中的套接字,可以用来实现客户端也可以实现服务器端
java.net.DatagramPacket类
此类表示UDP协议通信中所传输的数据报包
注:无论客户端还是服务器都会使用这个俩个类
例如:
public class UDPServerTest {
private DatagramSocket socket;
private DatagramPacket packet;
private byte[] buf;
public UDPServerTest(int port) {
try {
socket = new DatagramSocket(port);
buf = new byte[512];
packet = new DatagramPacket(buf, buf.length);
} catch (SocketException e) {
e.printStackTrace();
}
}
public void start(){
try {
//使用packet接收数据
socket.receive(packet);
//也可以使用字节流从buf中读出数据
System.out.println("服务器接收的数据为:"+new String(buf,0,packet.getLength()));
} catch (IOException e) {
e.printStackTrace();
}finally {
if(socket!=null)socket.close();
}
}
public static void main(String[] args) {
UDPServerTest t = new UDPServerTest(9999);
t.start();
}
}
public class UDPClientTest {
private DatagramSocket socket;
private DatagramPacket packet;
public UDPClientTest() {
try {
socket = new DatagramSocket();
} catch (SocketException e) {
e.printStackTrace();
}
}
public void send(String ip,int port){
try {
byte[] buf = "hello world 中国".getBytes();
//打好数据报包,并指定要发生到的ip和端口
packet = new DatagramPacket(buf, buf.length,InetAddress.getByName(ip), port);
//发送数据
socket.send(packet);
System.out.println("客户端发送数据完毕");
} catch (IOException e) {
e.printStackTrace();
}finally {
if(socket!=null)socket.close();
}
}
public static void main(String[] args) {
UDPClientTest t = new UDPClientTest();
String ip = "127.0.0.1";
int port = 9999;
t.send(ip, port);
}
}
100.URI和URL
URI是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。
URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
URI是以一种抽象的,高层次概念定义统一资源标识,而URL则是具体的一种资源标识的方式。
在Java的URI中,一个URI实例可以代表绝对的,也可以是相对的,只要它符合URI的语法规则。而URL类则不仅符合语义,还包含了定位该资源的信息,因此它不能是相对的,schema(访问协议)必须被指定。
//是URI也是URL
ftp://ftp.is.co.za/rfc/rfc1808.txt
http://www.ietf.org/rfc/rfc2396.txt
mailto:John.Doe@example.com
telnet://192.0.2.16:80/
//只是URI
tel:+1-816-555-1212
urn:oasis:names:specification:docbook:dtd:xml:4.1.2
例如:
public static void main(String[] args) {
try {
URL url = new URL("https://www.baidu.com/?name=tom#N1");
//获取此URL的授权部分
System.out.println ("Authority = "+ url.getAuthority ());
//获取与此 URL关联协议的默认端口号
System.out.println ("Default port = " +url.getDefaultPort ());
//获取此 URL的主机名
System.out.println ("Host = " +url.getHost ());
//获取此 URL 的路径部分
System.out.println ("Path = " +url.getPath ());
//获取此 URL 的端口号,如果未设置端口号,则返回 -1
System.out.println ("Port = " +url.getPort ());
//获取此 URL 的协议名称
System.out.println ("Protocol = " +url.getProtocol ());
//获取此 URL 的查询部分
System.out.println ("Query = " +url.getQuery ());
//获取此 URL的锚点
System.out.println ("Ref = " +url.getRef ());
//获取此 URL的 userInfo部分,如果没有用户信息,则返回 null
System.out.println ("User Info = " +url.getUserInfo ());
//-------模拟浏览器向此URL发生请求----------------
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
httpConn.setRequestMethod("GET");
httpConn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0");
InputStream is = httpConn.getInputStream();
Reader in = new InputStreamReader(is);
int data = -1;
while ((data = in.read ()) != -1) {
System.out.print((char)data);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//close
}
}
输出结果:
Authority = www.baidu.com
Default port = 443
Host = www.baidu.com
Path = /
Port = -1
Protocol = https
Query = name=tom
Ref = N1
User Info = null
模拟浏览器向这个URL发送请求之后,接收到响应的内容态多,其结果保存到了文件里(url-response.txt)