文章目录
一、Java语言
Java语言编写的程序是使用编译和解释相结合的方式来运行的。
最基本的Java程序:
public class HelloWorld{
public static void main(String[] args){
System.out.println("hello world!");
}
}
switch语句
int a = 2;
switch(a){
case 1:
System.out.println("a is 1.");
break;
case 2:
System.out.println("a is 2.");
a = 3;
case 3:
System.out.println("a is 3.");
a = 4;
default:
System.out.println("a is nothing.");
}
输出:
a is 2.
a is 3.
a is nothing.
switch语句如果在一个case里面没有写break,那么就会继续执行下一个case中的语句,并且根本不会判断条件(就离谱),如
int a = 1;
switch(a){
case 1:System.out.println("a is 1.");
case 2:
System.out.println("a is 2.");
break;
default:
System.out.println("a is nothing.");
}
//输出
//a is 1.
//a is 2.
可变参数
public static void f(int ... x){
for(int i = 0; i < x.length; i++){
System.out.println(x[i]);
}
}
//x是可变参数的代表,代表传入的参数是若干个int型的值
//类似数组
Java的默认初始化
成员变量会默认初始化,局部变量不会默认初始化。
二、什么是面向对象的程序设计
类和对象的概念
从需求中抽取出类,一个类相当于一个新的复杂数据类型,类的构成有两部分:成员变量和成员方法
对象又名实例(instance
)
由自定义类创建对象
public class TestMain(){
public static void main(String[] args){
Hello hello = new Hello();
}
}
class Hello{
// TODO
}
面向对象的程序设计:
一个工具:抽象
两个概念:类和对象
三个特性:封装、继承、多态
四个步骤:分析、设计、实现、测试
软件开发追求可重用性和可扩展性,oop更加容易实现可重用性。因为面向过程的程序设计只能实现对方法的复用,而面向对象的程序设计将方法和属性整合在类中,能够实现类和方法两方面的复用。
Java关键字和标识符:
true flase null this super //注意都是小写的
…………
Java允许一个文件中定义多个类,但是只能有一个类被public修饰,并且这个类的类名必须与文件相同。
三、类和对象
类的定义:
[类修饰符] class 类名 extends 父类类名
implements 接口列表{
[成员变量定义]
[成员方法定义]
}
1. 类修饰符
类的访问控制符:
public:能够被其他所有的类访问和引用
默认:能被与该类在一个包中的程序访问和引用
类的类型说明符
final:不能有子类,其成员方法都会被隐式地指定为final方法
abstract:抽象类
2. 成员变量的定义
[修饰符] 数据类型 变量名[ = 初始值]
3. 成员方法的定义
[修饰符] 返回值类型 方法名([形参说明])
[throws 异常名]{
局部变量说明;
执行语句组;
}
成员变量和成员方法的修饰符:
public:可以被任何类通过对象名.变量名/对象名.方法名
访问
protected:可以被子类和同一个包中的类访问(如果使用对象名.属性/对象名.方法
访问,要算包间访问,如果在子类中直接用属性名或方法名访问,则算子类调用父类)
默认:可以被同一个包中的方法使用
private:只能被自己类中的方法访问,其他类想访问、修改必须通过本类中定义的getter
、setter
方法
总结
对于没有父子关系的类,如果在同一个包中,public、protected和默认的属性和方法都能通过对象名.属性名/对象名.方法名
的方式访问。如果不在同一个包中,就只能访问public类型的属性和方法。
对于父子类,如果在同一个包中同上;如果不在同一个包中,子类可以通过直接使用属性名和方法名的方式访问父类中protected的属性和方法,但不能使用父类的实例访问protected类型的属性和方法。
如下:
package myPackage1;
public class TestPackage1{
protected int a = 1;
}
package myPackage2;
import myPackage1.*;
public class TestPackage2 extends TestPackage2{
public void method(){
int t;
t = a; //合法
TestPackage1 test = new TestPackage1();
t = test.a; //不合法
//因为此时不算子类调用,而TestPackage1和TestPackage2又不在一个包中
}
}
static:静态变量,见后
final关键字修饰类:该类不可以再被继承
final关键字修饰方法:该方法可以被重载,但是不能被子类方法覆盖
final关键字修饰变量:如果进行了变量的初始化,则该变量不能再被修改;如果没有进行变量的初始化,则一旦进行了赋值不能再被修改
toString()方法
所有类都有toString()方法,输出的是对象的内存地址。
this关键字
指对当前对象的引用
深拷贝和浅拷贝
基本类型作为参数传递时,是将值复制了一份给新变量,这种类型的传参是深拷贝
引用类型作为参数传递时,是将原变量的内存地址复制了一份给新变量,此时原变量和新变量引用的是其实是一个变量,一个修改另一个的值也会改
方法的重载
方法的重载是指在一个类中定义多个名字相同但参数列表不同的成员方法,在调用时会自动匹配参数列表调用对应的方法。
注意两个成员方法必须是参数列表不同,返回值不用、访问控制符不同都不能构成方法重载,都会被系统视为相同的方法而报错。
构造方法
特点:没有返回值,与类同名,每个类都有默认构造方法,默认的构造方法没有方法体,在使用new关键字创建对象的时候会自动调用构造方法产生一个对象并进行初始化
构造方法的重载:
构造方法可以通过重载进行自定义,也可以重载多个构造方法,通过传入不同的参数列表来进行不同的对象构造。
如果类中写了自定义构造方法,Object父类就不会再为该类创建默认构造方法了,所有如果对构造方法的所有重载中都没有不带参数的方法,却企图调用不带参数的构造方法,就会产生错误。
Java垃圾自动回收机制
判断一个存储单元是否是垃圾的依据是:该存储单元所对应的对象是否仍被程序所用。如一个引用类型如果被赋值为null了,可能就会对其进行垃圾回收。
Java提供了System.gc()
和Runtime.gc()
来强制回收垃圾,但是系统并不保证会立即进行垃圾回收。
在进行垃圾回收之前会调用finalize方法。
如果想提高System.gc()的回收概率,可以在gc函数后让程序sleep,从而保证在睡眠的时间里gc会发现垃圾并回收
System.gc();
Thread.sleep(100);
匿名对象
通过类似new Student()
的方式来创建匿名对象,匿名对象用完了就是垃圾了
如下,假设要创建的匿名对象的父类是Father
Father test = new Father() {
//类内部代码
//这个类继承了Father
};//注意分号
换成接口也是一样的
static关键字
static修饰变量的静态属性/类属性:
保存在全局数据区,是所有对象共享的;即使没有创建类的具体对象,静态属性仍然存在;可以通过类名.静态变量的方法访问。
static修饰方法的静态方法/类方法:
可以通过类名直接访问;即使没有创建类的具体对象,静态方法仍然存在;静态方法中不能直接访问非静态变量或非静态方法,必须通过对象名引用(因为非静态的变量是和对象绑定的);
因为静态方法是属于类的,而非静态属性/方法是属于对象的,如果在类方法中调用对象属性/方法,类会不知道该调用哪个对象的属性/方法,而实例化之后系统就知道了。
class Test{
int a;
public static void t(){
a = 1; //报错
Test test = new Test();
test.a = 1; //不报错
}
}
static修饰初始化语句块的静态代码块:
静态代码块都在该类第一次创建对象的时候执行一次,再创建对象的时候不再执行;静态代码块只能定义在类里面,不能定义在任何方法中;
main方法必须是static的
因为main方法是入口函数,在类没有创建对象的时候应该仍然能够调用入口函数,所有main方法应该是静态的
小结:
对于某一个类,Java在第一次用到该类的时候会进行类的加载(指第一次使用该类,或第一次使用该类的子类时)(这是一种懒加载的方式,即用到才加载,相对应的是预加载,即不管会不会用到,都预先加载出来)。在进行类的加载时,会将类的静态属性和静态方法加载出来等待使用,并将静态代码块中的语句执行。然后再进行对该类的使用。所以说静态属性和静态方法可以直接通过类名.属性名/类名.方法名的方式使用。并且在类再次被使用时,静态代码块中的语句不会再执行,而非静态代码块中的语句会在类每被使用一次就被执行一次。
关于执行顺序的总结见下
四、封装
为了实现数据的隐藏,使外部类只能通过setter/getter
函数修改、获取属性。并在setter
函数中添加判断条件,从而避免恶意修改数据。
单例模式
public class Singleton{
public static Singleton singleton = new Singleton();
private Singleton(){}
public static Singleton getSingleton(){
return singleton;
}
}
将类的构造方法重载为私有方法,并在类的内部定义唯一一个实例,提供该实例的getter方法,这样就获得了一个单例模式的类。注意:实例和定义和getter方法的定义都必须是static的,否则无法访问实例。
懒汉式的单例模式
public class Singleton{
public static Singleton singleton = null;
private Singleton(){}
public Singleton getSingleton(){
if(singleton == null)
singleton = new Singleton();
return singleton;
}
}
五、继承
继承是一种is-a关系
通过继承,子类自动具有了父类的成员变量和成员方法,但子类不能继承父类的构造方法。所以子类要么使用系统默认的无参构造方法,要么就必须编写自己的构造方法。在子类自己的构造方法中,子类可以通过super()显式调用父类的构造方法,如果没有显式调用的话,系统会自动隐式地调用。而且super()函数必须在子类的第一行。
如果父类没有不带参数的构造方法,必须显示调用super()。如果父类有不带参数的构造方法,可以让系统隐式调用super()。
Java不支持类的多继承,但支持接口的多继承。
向上转型/向上映射
子类转换为父类是自动进行的。
抽象类abstract
在父类中只定义了函数签名,没有真实的函数体,等待子类将他们具体化。在类前标注abstract。子类需要将抽象父类中所有的抽象方法具体化,否则子类也要标注abstract,等待再下一级的子类将他们具体化。
抽象类有构造方法,但是不能被实例化。
抽象方法不能被private、final或static修饰。
抽象类不一定具有抽象方法,但是有抽象方法的类一定要声明成抽象类。
public abstract class A{
public A(){
// TODO
}
public abstract void method();
public abstract int add(int a, int b){
return a + b;
}
}
概念区分
重载:本类内或子类中,方法名相同,参数列表不同的方法
非静态方法可以重写/覆盖(编译时看左,运行时看右)
静态方法可以隐藏(编译运行都看左)
静态属性和非静态属性没有区别,都是隐藏(编译运行都看左)
override:
重写/覆盖:子类中与父类的方法名、参数列表和返回类型都相同,而且子类中方法的访问控制符只能比父类中的范围扩大或相同,而不能缩小。静态方法不能重写/覆盖。
方法隐藏:由于静态方法不能被覆盖,因此如果子类中出现了跟父类中静态方法同名的方法,就称为方法隐藏
变量隐藏:如果子类中定义了和父类一样的成员变量,就称为子类将父类的变量隐藏了,若希望再调用父类同名变量,需使用super.变量名的方式
继承破坏了类的封装性,因为继承很容易改变父类实现的细节,因此父类中能写成final的尽量写成final,继承是一种强耦合关系。
复合
复合是一种has-a关系,即在一个类中创建了另一个类的实例作为成员变量。
六、多态
多态就是对于同一个引用类型的不同实例,执行不同的操作。比如在pet[]数组中多个不同类型的动物,执行该子类动物对应的操作。
Java的两个变量类型:
编译时类型:声明的类型,即创建变量时变量名前面写的类型
运行时类型:对象的类型,即创建变量时使用的类的类型
编译时类型 变量名 = new 运行时类型();
当编译时类型和运行时类型不一致时就会产生多态。
多态的两种形式:
静多态/编译时多态/静态联编:只要发生了变量的隐藏或方法的重载,就认为产生了静多态;静多态的产生与继承没有必然联系。
动多态/运行时多态/动态联编:在编译时不能确定要调用的方法,只有在运行时才能确定所要调用的方法,又称为运行时多态。
动多态的发生条件
①存在继承关系
②子类对父类的方法进行了覆盖
③存在向上转型,如Pet pet = new Dog();
工厂模式
工厂模式就是创建一个工厂接口(相当于总工厂),然后为每一个类创建一个分工厂类,分工厂实现总工厂的接口。将类的实例化放在分工厂中进行,并在测试的时候只传入工厂接口(实际上是分工厂)。这样如果再有其他的类产生,只需要再增加一个分工厂,其他的都不用变。
七、接口
public interface 接口名{
常量定义;
public void method([参数列表]);
}
public class 类名 implements 接口名{
public void method([参数列表]){
//方法体
}
}
接口是一种能力
接口中的所有方法都默认是public abstract的,接口中所有属性都默认是public static final的
接口中没有构造方法(抽象类有,但是抽象类也不能实例化)
接口中的方法体可以用Java语言书写,也可以用其他语言书写,用其他语言书写时接口方法需要用native修饰
类在实现接口中的抽象方法时,必须显示地将方法声明为public类型。否则将被警告缩小了方法的访问控制权限。
接口回调
public interface Run{
void run();
}
public interface Swim{
void swim();
}
public class Person implements Run, Swim{
public void run(){
System.out.println("I am running.");
}
public void swim(){
System.out.println("I am swimming.");
}
}
public class Test{
public void runTest(Run r){ //接口作为参数
r.run(); //接口回调
}
public void swimTest(Swim s){
s.swim();
}
public static void main(String[] args){
Person person = new Person();
runTest(person); //接口回调
swimTest(person);
}
}
接口回调就是将实现接口的类的对象重新映射为接口变量,并调用接口中定义的函数。
接口的继承
interface Interface1{
……
}
interface Interface1{
……
}
interface Interface3 extends Interface1, Interface2{
……
}
八、完善类设计
多态
父类可以是普通类,可以是抽象类,也可以是接口
对于成员方法:编译看左边,运行看右边
对于成员变量:编译运行都看右边
即对于成员方法,编译时看父类有没有该方法,如果没有会报错;而运行时如果子类重写了方法,就使用子类的方法,如果子类没有重写该方法,还是使用父类中的方法。
对于成员变量,编译、运行都是使用父类的
上转型对象不能操作子类新增的成员变量,不能调用子类新增的成员方法
向下映射
对于一个由子类向上映射得到的父类,可以通过强制类型转换再将其转为子类,称为向下映射。(直接创建的父类不能强制类型转换为子类)
通过instanceof操作符判断一个父类是不是由子类转化而来的
class Father(){
//父类方法
}
class Son extends Father{
//子类方法
}
public class Test{
public static void main(String[] args){
Father t = new Son();
t.fatherMethod();
if(!t instanceof Son){
System.out.println("类型不匹配,不能转换");
}
else{
Son q = (Son)t;
q.sonMethod();
}
}
}
如果没有instanceof判断直接强制类型转换了,会发生ClassCastException的运行时异常
class A{}
class B extends A{}
class Test{
public static void main(String[] args){
A t = new B();
System.out.println(t instanceof A);
System.out.println(t instanceof B);
System.out.println(t.getClass());
}
}
Objects类
Java中的祖先类
equals()和==
普通类型使用==判断值是否相等,引用类型使用==判断的是所引用的内存地址是否相等
引用类型使用equals()判断值是否相等
引用类型变量如果被赋值为了null,那变量==null是真的
Object o = null;
System.out.println(o==null); //真
内部类
class Outer{
public class Inner{
public int add(int a, int b){return a+b;}
//内部类
}
public static class StaticInner{
//静态内部类
}
public void method(){
final int a = 1;
final int b = 1;
class MethodInner{
int c = a;
int d = b;
//局部内部类
}
}
}
public class Test{
public static void main(String[] args){
Outer o = new Outer();
Outer.Inner oi = new Outer().new Inner();
//实例化内部类
}
}
内部类中可以直接访问外部类的所有成员变量和成员方法。
非静态内部类在实例化时必须保证外部类已经实例化,静态内部类可以在外部类没有实例化的情况下通过外部类类名直接使用,跟静态属性和非静态属性的使用一样
局部内部类只能在当前方法中使用;局部内部类可以使用当前类的所有成员变量和成员方法,以及当前方法中用final修饰的局部变量,当前方法中没有用final修饰的局部变量不能使用。???
匿名类
class Outer{
public void method(){
[父类] 类名 = new [父类]() {
//类的属性和方法
}
}
}
public class Outer{
public static void main(String[] args){
Object o = new Object() {
void toString(){
return "这里是匿名类内部";
}
}; //注意分号!!!
System.out.println(o.toString());
//注意这里匿名类o是继承了父类Object,并进行了向上映射,对于非静态方法,秉承“编译时看左,运行时看右”的原则,必须在父类中有的方法才能在子类中调用
}
}
匿名类就是内部类的定义与生成实例的语句合在一起,从而省去了类名。在执行时,先执行外部类的构造方法,再用匿名类语句构造一个内部类。
软件和包
包定义语句必须是Java文件中的第一条语句,如果没有声明,系统会为每一个.java文件自动创建于给无名包,但是无名包不能被其他包引用
包的引用
import 包名[.下一级包名].类名;
import 包名[.下一级包名].*;
不使用import引用,而是在使用类时前面加包名,如:
public class Student extends myPackage.Person{}
java.lang; //Java语言核心类库
java.util; //Java中各种实用工具
java.io; //Java输入输出类库
java.net; //Java网络功能类库
java.awt; //Java图形界面常用的类和接口
内聚和耦合
内聚描述类内部独立完成功能而不影响其他模块的能力
耦合程度描述类间依赖关系的强弱,耦合度越低越好
关于覆盖/隐藏/重载等概念的终极总结
重载:在同一个类中(或子类中,本质上是一样的),写一个跟另一个方法方法名相同,参数列表不同的方法,就构成了方法的重载。系统在调用时会根据使用的参数自动匹配参数列表,从而匹配该用哪一个方法。(不能保留相同的函数名、参数列表和不同的返回值类型或不同的访问控制符,这样程序会将两个函数判定为同一个而报错)
覆盖:子类中写一个与父类的名称、参数列表、返回值类型全部相同,且访问权限不低于父类方法的访问权限的方法,就构成了方法的覆盖
隐藏:父类中private、final、static的方法以及父类中的成员变量不能被覆盖,只能被隐藏
对于方法的覆盖,当构成向上映射时,适用“编译时看左,运行时看右”的原则,在编译的时候会查找父类(编译时类型)中有没有该方法,如果没有会报错;在运行的时候会查找子类(运行时类型)中有没有该方法,如果有,就执行子类中的方法,如果没有,还是执行父类中的方法。
对于属性和方法的隐藏,使用的是编译时类型(声明类型)中的属性和方法。
对于方法的覆盖和隐藏,都有访问控制权限不能降低的要求,但是对于属性的隐藏没有。
关于静态属性、静态方法、静态代码块、非静态属性、非静态方法、非静态代码块执行顺序的终极总结
Java加载顺序:
父类静态变量和静态代码块
子类静态变量和静态代码块
//如果用到了类的构造方法,就继续执行下面的语句;如果没有用到构造方法,只用到了类的静态属性,就在此停止了。
父类非静态变量和非静态代码块
父类构造函数
子类非静态变量和非静态代码块
子类构造函数
在父类中如果调用到了被子类重写的方法,会调用子类的中的重写方法
难题分析
//from lab05 4
// Test.java
class A {
void draw() {
System.out.println("A.draw()");
}
A() {
System.out.println("A() before draw()");
draw();
System.out.println("A() after draw()");
}
}
class B extends A {
private int b = 1;
B(int b) {
this.b = b;
System.out.println("B(), b = " + this.b);
}
void draw() {
System.out.println("B.draw(), b = " + this.b);
}
}
public class Test {
public static void main(String[] args) {
new B(5);
}
}
- 执行父类中静态属性和静态代码块,发现并没有,略过。
- 执行子类中静态属性和静态代码块,发现还是没有。
- 执行父类中的非静态属性和非静态代码块,还是没有
- 执行父类的构造方法,输出
A() before draw()
;执行draw函数,由于draw函数被子类重写了,因此执行子类的draw函数,输出B.draw(), b = 0
(因为此时还没有进行b的初始化);最后,输出A() after draw()
。 - 执行子类中的非静态属性和非静态代码块,将b修改为1
- 执行子类的构造方法,输出
B(), b = 5
。
九、Java集合与映射
集合框架
集合框架包括接口、具体类和算法。
Java集合框架位于java.util
包中
——Collection
//Collection接口是List、Set、Queue的父接口
//Collection接口存储不唯一、无序的对象
—— List(ArrayList、LinkedList、Vector(Stack))
//有序(指保留插入顺序),可以包含重复元素,提供了按索引查询的方式
//List是一个接口,ArrayList和LinkedList实现了这个接口
//ArrayList是变长数组,在内存中分配连续的空间,遍历元素和随机访问元素的效率比较高
//LinkedList是链表,内存地址不连续,插入、删除效率比较高
—— Set(HashSet(LinkedHashSet)、SortedSet(TreeSet))
//不能包含重复元素,不保留输入顺序,Set是一个接口,其中的具体类实现了这个接口
//其中SortedSet是升序的set
—— Queue(PriorityQueue)
——Map //key-value对的形式,key不能重复
—— HashTable
—— HashMap
//HashMap允许键或值为空,HashTable不允许
—— WeekHashMap
—— SortedMap(TreeMap)
//SortedMap是按照key升序排列的Map
集合类的特点:
- 只容纳对象(数组可以容纳基本数据类型和对象,而集合类只能容纳对象),如果想容纳基本数据类型,可以将基本数据类型转化为对象再放入集合中
- 集合类的对象都是Object的实例,一旦将一个对象置于集合中,就相当于发生了向上映射
常用方法
List常用方法
//ArrayList常用方法
boolead add(Object o);
void add(int index, Object o); //index参数也可以没有
int size();
Object get(int index);
boolean contains(Object o);
boolean remove(Object o);
boolean remove(int index);
boolean hasNext();
int indexOf(Object o);
//LinkedList还需要额外添加将元素添加到头尾的方法
boolean addFirst(Object o);
boolean addLast(Object o);
Object getFirst();
Object getLast();
boolean removeFist();
boolean removeLast();
//vector
与ArrayList相比,vector线程安全
Map常用方法
//HashMap
Object put(Object key, Object val); //将键值对放在Map中
Object get(Object key);
Object remove(Object key);
int size();
Set keySet(); //返回键的集合
Collection values(); //返回值的集合
Boolean containsKey(); //判断是否存在键
循环
迭代器iterator
使用boolean hasNext()判断是否存在下一个元素;
使用Object next()返回下一个元素;
ArrayList<String> list = new ArrayList<String>();
Iterator it = list.iterator(); //迭代器
while(it.hasNext())
{
String s = (String)it.next(); //必须进行强制类型转换才能调用子类特有的方法
System.out.println(s);
}
for-each循环遍历
ArrayList<String> list = new ArrayList<String>();
for(String s : list){
//do-something
}
泛型集合
import java.util.*;
public class GenericTest {
public static void main(String[] args) {
List<String> name = new ArrayList<String>();
List<Integer> age = new ArrayList<Integer>();
List<Number> number = new ArrayList<Number>();
name.add("icon");
age.add(18);
number.add(314);
getData(name);
getData(age);
getData(number);
}
public static void getData(List<?> data) {
System.out.println("data :" + data.get(0));
}
}
十、异常处理
异常是一个可以正确运行的程序在运行中发生的错误
Throwable类是所有异常类的父类,Throwable类中包含Exception和Error两个子类。Exception中包含IOException和RuntimeException两个子类。
异常分检查性异常、运行时异常和错误三类。
异常分类
RuntimeException运行时异常,由系统检测,用户可以不做处理,系统将他们交给缺省的异常处理程序处理
非RuntimeException:用户的程序必须捕获并进行处理,否则不能通过编译
//常见RuntimeException
ArithmeticException;
NullPointerException;
ArrayIndexOutOfBandsException;
ClassCastException //类型转换错误
//常见非RuntimeException
IOException
FileNotFoundException;
ClassNotFoundException
try-catch-finally机制
finally的执行时间
在return语句将返回值交付但是函数还没有结束的时候执行finally中的语句。
这意味着返回的变量如果又在finally中被修改了,返回值不会被修改,仍然是原来的那个。但是如果finally中存在return语句,finally中的return语句会将原来的返回值覆盖。
try{
int a = 1;
return a;
}finally{
a++;
}//返回值是1
try{
int a = 1;
return a;
}finally{
return 10;
}//返回值是10
注意,如果返回值的类型是StringBuilder或StringBuffer的话,finally中的修改是会被返回的,因为StringBuilder和StringBuffer是引用类型,而且修改是在原字符串的基础上进行的。如果是String不会返回修改之后的,因为String的修改是产生了新的字符串。
如果一个异常类及其子类都出现在catch子句中,应当将子类放在前面,否则将永远不会到达子类。
try后面要么有catch要么有finally,不能一个都没有。
try-catch-finally之间不能添加任何代码。
throws语句声明异常
声明异常指的是一个方法只抛出异常,但是不捕获异常(也不处理异常),相当于报告给上级,让上级处理。
throws语句用于声明可能会发生的异常。
一旦某方法用throws语句声明了异常,调用这个方法的语句要么处理异常,要么也用throws抛出异常,否则会报错。如果最终方法也没有处理异常,异常将交给系统处理。
public compute(int x) throws ArithmeticException{
return z = 100 / x;
}
重写方法不能比原方法抛出范围更大的异常类型
用户自定义异常-throw语句抛出异常
当希望自定义检查性异常时,需要继承Exception类;当希望自定义运行时异常时,需要继承RuntimeException类。
class MyException extends Exception{
public MyException(int number){
System.out.println(number);
}
}
class Test throws MyException{
public void method(int number){
if(number <= 0){
throw new MyException(number);
//其实就是创建自定义异常类的对象并用throw抛出
}
}
}
//注意throws和throw的区别:throws是在方法上声明抛出异常,throw是实际地抛出异常
如果没有捕获并处理异常,程序会输出异常信息并终止;如果用try-catch捕获异常了,程序会继续正常运行,不会中止。
public class Test{
public static void main(String[] args){
System.out.println(A.method);
}
}
class A{
public static int method(){
try{
return 3 / 0;
}catch(Exception e){
System.out.println("Exception");
}finally{
System.out.println("finally");
return 0;
}
}
}
//程序输出:
Exception
finally
0
十一、图形界面程序设计
前端语言Java awt、Java Swing
Java Swing大部分方法就是在Java awt的基础上前面加了个J
容器
顶层容器
可以独立的窗口,如Frame、Window、Dialog、FileDialog,默认是BorderLayout
非顶层容器
必须放在顶层容器之中,如Panel、ScrollPane、Applet,默认是FlowLayout
组件
Button RadioBox(单选框) CheckBox(复选框) Choice(下拉列表框) Canvas List Label TextField TextArea ScrollBar Menu等
容器方法
add(Component comp);
remove(Component comp);
setLayout(LayoutManager mgr);
setLocation(int x, int y);
setSize(int x, int y);
setBackground(Color color);
show();
setVisiable(boolean b);
validate();
组件方法
add(PopupMenu popup);
setBackground(Color c);
getBackground();
setForeground(Color c); //设置前景色
setFont(Font f); //设置字体
getFont();
事件处理
import java.awt.event.*
JButton button = new JButton();
button.addActionListener(aMyClass);
class MyClass implements ActionListener{
public void actionPerformed(ActionEnvent e){
System.out.println("The button is pressed.");
}
}
布局
JPanel的缺省布局为FlowLayout
JFrame的缺省布局为BorderLayout
十二、多线程程序设计
多线程是指单个线程包含并发执行的多个线程。
微观上来说,多个线程在CPU中应当轮流执行;宏观上来说,相当于多个作业被同时执行了。有利于提高程序执行的效率。
对于隶属于同一进程的线程,他们之间的切换效率比进程间切换快得多
实例变量存储在进程的堆区,局部变量存储在自己线程的方法栈中
Java中线程调度的方式是抢占式。
线程的优先级1~10
MIN_PRIORITY
MAX_PRIORITY
NORM_PRIORITY,默认为5
getPriority()获得线程优先级
setPriority()设置线程优先级
线程的状态
新建
就绪
运行
阻塞
终止
线程状态转换
新建线程后调用start()方法,由新建状态转为就绪状态,此时线程在线程队列中排队等待。
当就绪状态的线程被CPU调用并获得处理器资源时,便进入运行状态,此时执行本线程的run()方法。
一个正在执行的线程遇到以下情况时可能会进入阻塞状态:
等待IO操作
网络操作
为了获取锁而进入阻塞状态
调用了sleep()方法
调用了wait()方法
让正处于运行中的线程调用另一个线程的join()方法
对应于进入阻塞操作的不同情况,也有不同的回到就绪状态的情况
IO阻塞对应于IO操作完成
sleep()方法产生的阻塞,在等待其指定的休眠事件结束后,自动脱离阻塞状态
wait()方法产生的阻塞,在调用notify()或notifyAll()方法后回到就绪状态
终止:
自然终止:线程完成了自己的全部工作
强制终止:在线程执行完之前,调用stop()或destroy()终止线程
sleep和yield的区别
sleep使当前线程由运行状态转为阻塞状态,而yield将当前线程由运行状态转为就绪状态。由于当前线程仍然在就绪状态中,所以只有与当前线程优先级相同或比当前线程优先级高的线程才有可能获得机会,如果没有这样的线程的话,yield方法就不起任何作用,仍然进行当前线程;但是sleep方法将当前线程放在了阻塞队列里,这意味着当前线程阻塞的时候,比当前线程优先级低的线程有可能获得机会。
线程的创建
- 继承Thread类
class ThreadTest extends Thread{
public void run(){
//线程要执行的代码
}
}
public class Test{
public static void main(String[] args){
ThreadTest tt = new ThreadTest();
tt.start();
}
}
- 实现Runnable接口
class ThreadTest implements Runnable {
public void run(){
//线程要执行的代码
}
}
class Test{
Thread t = new Thread(new ThreadTest());
t.start();
}
线程的同步与互斥
synchronized
互斥锁的两个概念:
等待池/锁等待队列:相当于阻塞的
锁池/锁申请队列:相当于等待使用锁的
wait():释放对象的锁,并将线程放到对象的等待池中
notify():在对象的等待池中随机选择一个,放到锁池中
notifyAll():将等待池中所有线程转移到锁池中
wait()、notify()、notifyAll()方法都是在synchronized方法中才能使用的
生产者-消费者模型与PV操作
十三、Java IO
标准输入
io输入
import java.io.*;
InputStreamReader ir = new InputStreamReader(System.in);
BufferedReader in = new BufferedReader(ir);
s = in.readLine();
while( s != null)
{
System.out.println("Read : " + s);
s = in.readLine();
}
in.close();
Scanner API输入
import java.util.Scanner;
Scanner s = new Scanner(System.in);
String param = s.next();
System.out.println("The first param is " + param);
int value = s.nextInt();
System.out.println("The second param is " + value);
文件IO
File类表示文件,能进行新建、删除、重命名文件和目录。但是不能访问文件内容本身,要访问文件必须使用文件输入/输出流。
字节流输入输出:
InputStream、OutputStream的子类
即 FileInputStream、FileOutputStream
字符流输入输出:
Reader、Writer的子类
即 FileReader、FileWriter
FileInputStream
FileInputStream f = new FileInputStream(url);
char b = f.read();
int read(); //一次读一个字节
int read(byte[] buffer);
int read(byte[] buffer, int offset, int length);
void close();
FileOutputStream
构造方法:
FileOutputStream(String name);
FileOutputStream(String name, boolean append);
过滤流——缓冲流、数据流、回压流、打印流
过滤流:FilterInputStream/FilterOutputStream
缓冲流:BufferedInputStream、BufferedOutputStream
带缓存的输入/输出流,flush()方法
数据流:DataInputStream、DataOutputStream
用于读或写基本数据类型的数据
回压流:PushbackInputStream
能将读出的数据重新压入流中
打印流:PrintStream
具有回车自动flush()的功能
对象流
ObjectInputStream和ObjectOutputStream
使用对象流读取、写入数据要保证对象的串行化
实现Serializable接口
管道流
Reader类和Writer类
System.in是InputStream的实例
System.out是PrintStream的实例
十四、网络编程
客户机/服务器模式 C/S
TCP/IP协议:物理层、网络层、传输层、应用层
两种传输层协议:TCP和UDP
TCP:面向连接的传输层协议
UDP:无连接的传输层协议
URL的基本格式:
Protocol://hostname:post/resourcename#anchor
Protocol:使用的协议,可以是http、ftp、news等
hostname:主机名
post:端口号
resourcename:资源名
anchor:标记
TCP可靠,UDP不可靠
TCP传输效率高于UDP
补充
- String类型的常见构造方法
String(char[] value, int offset, int count);
String(int[] codePoints, int offset, int count);
String(String original);
String(StringBuffer buffer);
StringBuffer和StringBuilder可用于多次修改字符串,且这样的修改是在原字符串的基础上进行的,不会产生新的字符串。其中StringBuffer是线程安全的。
-
二维数组的行数必须确定,列数可以不确定。
-
Java的命令行传参是从0开始的
-
如果父类有一个private的方法,子类可以再写一个一模一样的方法,因为子类没有继承到父类这个方法,所以不会报错;如果父类的方法是final的,子类就只能重载该方法,而不能覆盖该方法。
-
String s = null; System.out.println(s==null); //输出true
-
多态的表现形式有重载、覆盖、接口
-
Java数据类型
float 单精度浮点型,初始化的时候必须写成float f = 1.3f; 或float f = 1.3F;否则会报错
byte字节型,取值范围为-128~127,取大于128的值会报错
boolean不能取null值,但是Boolean可以,因为boolean是基本数据类型,就像int不能取null一样。但是Boolean是引用类型。
int arr[] = new int[5];就是进行了初始化了,都被初始化为0.
&
和&&
的区别,&&
是短路与,第一个条件如果为false,就不会判断第二个条件了;而&
无论第一个条件是否为真,都会去判断第二个条件。
难题
class Test extends Thread{
static String sName = "vandeleur";
public static void main(String[] args) throws InterruptedException {
Test t = new Test();
t.piggy(sName);
//t.join(); 标号1
System.out.println(sName);
}
public void piggy(String sName){
sName = sName + " wiggy";
//这里修改的是传入的sName,不会对全局变量sName造成影响
start();
}
public void run(){
for (int i = 0; i < 3; i++)
sName = sName + " " + i;
}
}
如果标号1处保持注释给出输出:
可能是vandeleur,也可能是vandeleur 0,也可能是vandeleur 0 1,也可能是vandeleur 0 1 2
如果标号1处不注释给出输出:
vandeleur 0 1 2
易错点:
静态方法中不能直接使用类的非静态属性和非静态方法,必须通过实例来使用
实现接口中的方法必须声明成public的
继承抽象类要么实现抽象方法要么仍然声明成抽象类