4.1 类的继承
4.1.1 继承的概念
在程序中,继承描述的是事物之间的所属关系,通过继承可以使多种事物之间形成一种关联体系。
在Java中,类的继承是指在一个现有类的基础上去构建一个新的类,构建出来的新类被称作子类,现有类被称作父类或基类,子类会自动拥有父类所有可继承的属性和方法。声明一个类继承另一个类,用extends关键字,语法如下:
[修饰符] class 子类名 extends 父类名{
//程序核心代码
}
其中,类的修饰符是可选的,用来指定类的访问权限,可以使用public或省略不写;子类名和父类名都是必选的,并且子类与父类之间要用extends关键字表示继承关系。
class Animal{
String name;
//定义动物叫的方法
void shout(){
System.out.println("动物发出叫声");
}
}
class Dog extends Animal{
public void printNmae(){
System.out.println("name="+name);
}
}
public class Example01 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "萨摩耶";
dog.printNmae();
dog.shout();
}
}
/**
* 输出结果:
* name=萨摩耶
* 动物发出叫声
*/
Dog类通过extends关键字继承Animal类,成为Animal类的子类。子类没有声明name属性和shout()方法,但是却能访问这两个成员。这说明子类在继承父类的时候,会自动拥有父类的公共成员。
实现类的继承时注意:
-
在Java中,类只支持单继承,不允许多重继承,一个类只能有一个直接父类。
class A{} class B{} class C extends A,B{}//是不允许的
-
多个类可以继承同一父类。
class A{} class B extends A{} class C extends A{}//B和C都可以继承A
-
在Java中,多层继承是可以的,一个类可以再去继承另外的父类。
class A{} class B extends A{} class C extends B{}
-
在Java中,子类和父类是一个相对概念,一个类是某个类父类的同时,也可以是另一个类的子类。
在继承关中,子类会自动继承父类中公共的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。子类中重写的方法需要和父类被重写的方法具有相同的方法名、参数列表以及返回值类型。
class Animal{
String name;
//定义动物叫的方法
void shout(){
System.out.println("动物发出叫声");
}
}
class Dog extends Animal{
void shout(){
System.out.println("汪汪汪");
}
}
//定义测试类
public class Example02 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.shout();
}
}
注意:子类重写父类方法时,不能使用比父类中被重写方法更加严格的访问权限。
4.1.3 super关键字
子类重写父类反复后,子类对象将无法直接访问父类被重写的方法,为解决这个问题,在Java中专门提供super关键字来访问父类的成员,例如访问父类的成员变量、成员方法和构造方法。
1.使用super关键字调用父类的成员变量和成员方法,具体格式如下:
super.成员变量 super.成员方法([参数1,参数2...])
案例:
class Animal{
String name = "动物";
//定义动物叫的方法
void shout(){
System.out.println("动物发出叫声");
}
}
class Dog extends Animal{
String name = "犬类";
//重写父类的shout()方法
void shout(){
super.shout();
}
//定义打印name的方法
void printName(){
System.out.println("name="+super.name);
}
}
public class Example03 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.shout();
dog.printName();
}
}
/**
*输出结果:
*动物发出叫声
* name=动物
*/
子类Dog的shout()方法中使用“super.shout()"调用了父类被重写的方法,使用super.name"访问父类的成员变量。
2.使用super关键字调用父类的构造方法,具体格式如下:
super([参数1,参数2,...])
案例:
class Animal{
//定义Animal类有参构造方法
public Animal(String name){
System.out.println("我是一只"+name);
}
}
class Dog extends Animal{
public Dog(){
super("萨摩耶");
}
}
public class Example04 {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
创建实例对象时一定会调用Dog类的构造方法,执行了内部的“super("萨摩耶")"方法,从而掉员工了父类的有参构造方法。注意:通过super调用父类构造方法的代码必须位于子类构造方法的第一行,并且只能出现一次,否则编译报错。
"Implicit super constructor Animal() is undefined .Must explicitly invoke another constructor" (未定义隐式无参构造方法,必须显式地调用另一个构造方法)。
此处错误原因是,子类构造方法中一定会调用父类的某个构造方法。这是可以在子类构造方法中通过super关键字指定调用父类的哪个构造方法,如果没有指定,在实例化子类对象时,会默认调用父类无参的构造方法,而案例中未定义无参构造方法。
class Animal{
//定义Animal类无参构造方法
public Animal(){
System.out.println("我是一只动物");
}
//定义Animal类有参构造方法
public Animal(String name){
System.out.println("我是一只"+name);
}
}
class Dog extends Animal{
//定义Dog类无参的构造犯非法
public Dog(){
}
}
public class Example05 {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
在定义一个类时,如果没有特殊的需求,当定义了有参构造方法后,尽量在类中再显式地定义一个无参构造方法。
4.1.4 Object类
Java中提供了一个Object类,它是所有类的父类,即每个类都直接或间接地集成自该类,因此它被称为超类、基类或者根类。定义一个类时,没有使用extends关键字为这个类显式地指定父类,就默认集成Object类。
Object中自定义了一些方法,常用的方法:
方法声明 | 功能描述 |
---|---|
boolean equsla(Object object) | 判断某个对象与此对象是否相等 |
final Class<?>getClass() | 返回次Object的运行时类 |
int hashCode() | 返回该对象的哈希吗值 |
String toString() | 返回该对象的字符串表示 |
void finalize | 垃圾回收期调用此方法来清理没有被任何引用变量所引用对象的资源 |
案例:Object类中方法的使用(toStirng())
class Animal{
//定义动物叫的方法
void shout(){
System.out.println("动物叫");
}
}
public class Example06 {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.toString());//Animal类默认集成了Object类
}
}
Object类中得toStrin()方法中输出信息具体格式
geClass().getName()+"@"+Integer.toHexString(hashCode());
-
getClass().getName()代表返回对象所属类的类名,即包名+类名的全限定名称:
-
hashCode()代表返回该对象的哈希值;
-
Integer.toHashString(hashCode())代表将对象的哈希值用十六进制表示。
实际开发中,希望toString()返回的不仅是对象的基本信息,可以 重写它
class Animal{
//重写Object类的toString()方法
public String toString(){
return "这是一只动物";
}
}
public class Example07 {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.toString());
}
}
4.2 final关键字
final关键字可用于修饰类、变量和方法。有“不可更改”或者“最终”的含义,因此被final修饰的类、变量和方法具有以下特性:
-
final修饰的类不能被继承
-
final修饰的方法不能被子类重写
-
final修饰的变量(成员变量和局部变量)是常量,只能赋值一次。
4.2.1 final关键字修饰类
final修饰的类不能被继承
final class Animal{
}
class Dog extends Animal{
}
public class Example08 {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
//报错"The type Dog cannot subclass the final class Animal(无法从最终类Animal进行继承)"
4.2.2 final关键字修饰方法
final修饰的方法不能被子类重写
class Animal{
//使用final关键字修饰shout()方法
public final void shout(){
}
}
class Dog extends Animal{
//重写Animal类的shout方法
public void shout(){
}
}
public class Example09 {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
//报错"cannot override the final method from Animal"
4.2.3 final关键字修饰变量
final修饰的变量(成员变量和局部变量)是常量,只能赋值一次。
public class Example10 {
public static void main(String[] args) {
final int num = 2;
num = 4;
}
}
//报错"The final local variable num cannot be assigned.It must be blank and not using a compound assignment(final变量num无法被重新赋值)"
局部变量使用final修饰可以在声明变量的同时对变量进行赋值,也可以先声明变量然后再进行有且只有一次的赋值。
成员变量被final锈蚀时,声明变量的同时必须进行初始化复制,否则编译报错
public class Example11 {
final int m;
public static void main(String[] args) {
final int n;
n=4;
}
}
//报错"The blank final field m may not have been initialized(final修饰的变量m没有初始化)"
4.3 抽象类和接口
4.3.1 抽象类
不提供方法的实现。
抽象方法必须使用abstract关键字来修饰,并且在定义方法时不需要实现方法体。当一个类中包含了抽象方法,那么该类也必须使用abstract来修饰,这种使用abstract关键字修饰的类就是抽象类。基本语法格式如下:
//定义抽象类 [修饰符] abstract class 类名{ //定义抽象方法 [修饰符] abstract 方法返回值类型 方法名([参数列表]); //其他方法或属性 }
注意:包含抽象方法的类必须定义为抽象类,但抽象类中可以不包含任何抽象方法。另外,抽象类不可以被实例化,因为抽象类中有可能包含抽象方法,抽象方法是没有方法体的,不可以被调用。如果想调用抽象类中定义的抽象方法,需要先创建一个子类,在子类中实现抽象类中的抽象方法。(定义抽方法只需在普通方法上加abstract关键字,并把普通方法中的方法体去掉,然后在方法名称后增加英文分号)
abstract class Animal{
//定义抽象方法abstract
public abstract void shout();
}
class Dog extends Animal{
//实现抽象方法shout().编写方法体
public void shout(){
System.out.println("汪汪汪");
}
}
public class Example12 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.shout();
}
}
4.3.2 接口
如果一个抽象类中所有方法都是抽象的,可以将这个类定义为Java中的另一种形式——接口。接口是一种特殊的抽象类,不能包含普通方法,其内部所有的方法都是抽象方法,它将抽象进行得更彻底。
JDK8中对接口进行了重新定义,接口中除了抽象方法外,还可以有默认方法和静态方法(也叫类方法),默认方法用default修饰,静态方法用是他提修饰,并且这两种方法都允许有方法体。
定义接口不使用class关键字,而使用interface关键字。基本语法如下
[修饰符] interface 接口名 [extends 父接口1,父接口2,...]{//接口内部可以定义多个常量和抽象方法 [public][static] [final] 常量类型 常量名=常量值;//定义和苍凉时必须进行初始化赋值 [public] [abstract] 方法返回值类型 方法名([参数列表2]); [public] default 方法返回值类型 方法名([参数列表1]){ //默认方法的方法体 } [public] static 方法返回值类型 方法名([参数列表1]){ //静态方法的方法体 } }
接口包含三类方法:
-
抽象方法--通过接口实现类的实例对象调用
-
默认方法--通过接口实现类的实例对象调用
//定义接口的实现类,通过implements关键字实现当前接口,并实现接口中所有方法。 //一个类可以在继承另一个类的同时实现多个接口,并且多个接口之间需要使用英文逗号分割。 //类与接口的实现关系: interface Animal{ int ID=1; void breathe(); default void getType(String type){ System.out.println("该动物属于"+type); } static int getID(){ return Animal.ID; } } class Dog implements Animal{ //实现breathe()方法 public void breathe(){ System.out.println("狗在呼吸"); } } public class Example13 { public static void main(String[] args) { System.out.println(Animal.getID()); Dog dog = new Dog(); System.out.println(dog.ID); dog.breathe(); dog.getType("犬科"); } } /** *输出结果 * 1 * 1 * 狗在呼吸 * 该动物属于犬科1 * 1 * 狗在呼吸 * 该动物属于犬科 */
通过接口实现类Dog的实例化对象可以访问接口中的常量,接口实现方法以及默认方法,而接口中的静态方法可以直接使用接口名调用。接口的实现类,必须实现接口中的所有抽象方法,否则编译报错。
//接口之间的继承关系 interface Animal{ int ID=1; void breathe(); //定义一个默认方法 default void getType(String type){ System.out.println("该动物属于"+type); } //定义一个静态方法 static int getID(){ return Animal.ID; } } //定义LandAnimal接口,并集成Animal接口 interface LandAnimal extends Animal{ void run(); } class Dog implements LandAnimal{ //实现breathe()方法 public void breathe(){ System.out.println("狗在呼吸"); } //实现run方法 public void run(){ System.out.println("狗在陆地上跑"); } } public class Example14 { public static void main(String[] args) { System.out.println(Animal.getID()); Dog dog = new Dog(); System.out.println(dog.ID); dog.breathe(); dog.getType("犬科"); dog.run(); } } /** *输出结果; * 1 * 1 * 狗在呼吸 * 该动物属于犬科 * 狗在陆地上跑 */ //LandAnimal接口继承了Animal接口,所以LandAnimal有1+1=2个抽象方法,Dog类实现LandAnimal接口时,需要实现这两个抽象方法。
-
静态方法--通过"接口名.方法名"的形式调用
接口特点:
-
从JDK8开始接口中的方法可以包含抽象方法、默认方法和静态方法,默认方法和静态方法都可以有方法体,并且静态方法可以直接通过"接口.方法名"来调用;
-
当一个类实现接口时,如果这个类是抽象类,只需要接口中的部分抽象方法即可,否则需要实现接口中的所有抽象方法;
-
一个类可以通过implements关键字同时实现多个接口,被实现的多个接口之间用英文逗号隔开;
-
接口之间可以通过extends关键字实现继承,并且一个接口可以同时集成多个接口,接口之间用英文逗号隔开;
-
一个类在继承一个类的同时还可以实现接口,此时extends关键字必须位于implements关键字之前
class A extends B implements C{ //先继承,再实现 ... }
4.4 多态
4.4.1 多态概述
在Java中,多态指不同类在调用同一个方法时所呈现出的多种不同行为。通过多态,消除了类之间的耦合关系,提高了程序的可扩展性和可维护性。
Java的多态是由类的继承、方法重写以及父类引用指向子类对象体现的。由于一个父类可以有多个子类,多个子类都可以重写父类方法,并且多个不同的子类对象也可以指向同一个父类;这样,程序只在运行时才能知道具体代表的是哪个子类,就体现了多态性。
abstract class Animal{
abstract void shout();
}
class Cat extends Animal{
public void shout(){
System.out.println("喵喵喵");
}
}
class Dog extends Animal{
public void shout(){
System.out.println("汪汪汪");
}
}
public class Example15 {
public static void main(String[] args) {
Cat an1 = new Cat();
Dog an2 = new Dog();
an1.shout();
an2.shout();
}
}
4.4.2对象的类型转换
将子类对象当做父类类型使用的情况,在Java中称为"向上转型"
Animal an1 = new Cat();//将Cat类对象当做Animal类型来使用 Animal an2 = new Dog();
将子类当做父类型使用不需要任何显式声明,,但此时不能通过父类变量调用子类特有的方法:
interface Animal{
void shout();
}
class Cat implements Animal{
public void shout(){
System.out.println("喵喵喵");
}
public void catchMouse(){
System.out.println("猫猫抓老鼠");
}
}
public class Example16 {
public static void main(String[] args) {
Animal an1 = new Cat();
an1.shout();
an2.catchMouse();//报错
}
}
可以将父类型Animal强转为Cat类型
public class Example16 {
public static void main(String[] args) {
Animal an1 = new Cat();
Cat cat =(Cat) an1;
cat.shout();
cat.catchMouse();
}
}
但是进行对象向下类型转换时,必须转换为本质类型:
interface Animal{
void shout();
}
class Cat implements Animal{
public void shout(){
System.out.println("喵喵喵");
}
public void catchMouse(){
System.out.println("猫猫抓老鼠");
}
}
class Dog implements Animal{
public void shout(){
System.out.println("汪汪汪");
}
}
public class Example17 {
public static void main(String[] args) {
Animal an1 = new Dog();
Cat cat = (Cat) an1;
cat.shout();
cat.catchMouse();
}
}
//报错,此处创建的Animal对象本质是一个Dog对象,无法转换为Cat类型
Java提供了一个关键字instanceof,可以判断一个对象是否为某个类(或接口)的实例或者子类实例,语法:
对象(或者对象引用变量) instanceof 类(接口)
将Example17改为
public class Example17 {
public static void main(String[] args) {
Animal an1 = new Dog();
if(an1 instanceof Cat){
Cat cat = (Cat) an1;
cat.shout();
cat.catchMouse();
}else{
System.out.println("该类型的对象不是Cat类型");
}
}
}
4.5 内部类
Java中,允许在一个类的内部定义类,这样的类被称为内部类,这个内部类所在的类称为外部类。
分为:成员内部类、局部内部类、静态内部类和匿名内部类。
4.5.1 成员内部类
//定义外部类Outer
class Outer{
int m = 0;//定义外部类的成员变量
void test1(){
System.out.println("外部类成员方法");
}
//定义内部类Inner
class Inner{
int n = 1;//定义内部类的成员变量
//定义内部类成员方法,访问外部类成员变量和方法
void show1(){
System.out.println("外部类成员变量m="+m);
test1();
}
void show2(){
System.out.println("内部类成员方法");
}
}
//定义外部类方法,访问内部类成员变量和方法
void test2(){
Inner inner = new Inner();
System.out.println("内部成员变量n="+inner.n);
inner.show2();
}
}
public class Example18 {
public static void main(String[] args) {
Outer outer = new Outer();//创建外部类对象
Outer.Inner inner = outer.new Inner();//通过外部类对象创建内部类对象
inner.show1();//测试在成员内部类中访问外部类成员变量和方法
outer.test2();//测试在外部类中访问内部类成员变量和方法
}
}
/**
*输出结果:
* 外部类成员变量m=0
* 外部类成员方法
* 内部成员变量n=1
* 内部类成员方法
*/
创建内部类对象的具体语法格式
外部类名.内部类名 变量名 = new 外部类名().new 内部类名();
4.5.2 局部内部类
局部内部类,也叫方法内部类,就是定义在某个局部范围中的类,和局部变量一样,都是在方法中你定义的,其有限范围仅限于方法内部。
在局部内部类汇总,局部内部类可以访问外部类的所有成员变量和方法,而局部内部类中的变量和方法只能在创建该局部内部类的方法中进行访问。
class Outer{
int m = 0;
void test1(){
System.out.println("外部成员方法");
}
void test2(){
class Inner{
int n = 1;
void show(){
System.out.println("外部变量m="+m);
test1();
}
}
Inner inner = new Inner();
System.out.println("局部内部类变量n="+inner.n);
inner.show();
}
}
public class Example19 {
public static void main(String[] args) {
Outer outer = new Outer();
outer.test2();
}
}
/**
* 输出结果
* 局部内部类变量n=1
* 外部变量m=0
* 外部成员方法
*/
4.5.3 静态内部类
所谓静态内部类,就是使用static关键字修饰的成员内部类。与成员内部类相比,静态内部类只是在内部类前增加了static关键字,但在功能上,静态内部类只能访问外部类的静态成员,同时通过外部类访问静态内部类成员时,可以跳过外部类从而直接通过内部类访问静态内部类成员,创建静态内部类语法格式如下:
外部类名.静态内部类名 变量名 = new 外部类名》静态内部类名();
class Outer{
static int m = 0;
static class Inner{
void show(){
//静态内部类访问外部类静态成员
System.out.println("外部类静态成员m="+m);
}
}
}
public class Example20 {
public static void main(String[] args) {
//静态内部类可以直接通过外部类创建
Outer.Inner inner = new Outer.Inner();
inner.show();
}
}
4.5.4 匿名内部类
Java中调用某个方法时,如果该方法的参数是一个接口类型,除了可以传入一个参数接口实现类,还可以使用匿名内部类实现接口来作为该方法的参数。匿名内部类就是没有名称的内部类,在调用包含有接口类型参数的方法时,为简化代码,不会创建一个接口的实现类作为方法参数传入,而是直接通过匿名内部类的形式传入一个接口类型参数,在匿名内部类中直接完成方法的实现。语法格式
new 父接口(){ //匿名内部类实现部分 }
interface Animal{
void shout();
}
public class Example21 {
public static void main(String[] args) {
String name = "小花";//JDK8开始有的特性,允许在局部内部类、匿名内部类中访问非final修饰的局部变量,在JDK8之前局部变量前必须加final,否则报错
animalShout(new Animal(){
public void shout(){
System.out.println(name+"喵喵喵");
}
});
}
public static void animalShout(Animal an){
an.shout();
}
}
4.6 JDK8的Lambda表达式
Lambda表达式是JDK8中一个重要特性,使用一个清晰简洁的表达式来表达欲一个接口,也简化了对集合以及数组数据的遍历、过滤和提取等操作。
4.6.1 Lambda表达式入门
一个Lanvda表达式由3个部分组成:参数列表、"->"和表达式主体,语法如下:
([数据类型 参数名,数据类型 参数名,...])->{表达式主体}
-
([数据类型 参数名,数据类型 参数名,...]):用来向表达式主体传递接口需要的参数,参数中间用英文逗号分割。可以省略参数类型,后面的表达式自动进行校对和匹配,如果只有一个参数,可以省略括号;
-
->:表示Lambda表达式箭牌,用来指定参数数据指向,不能省略,且必须用英文横线和大于号;
-
{表达式主体}:由单个表达式或语句块组成的主体,本质就是接口中抽象方法具体实现,如果表达式主体只有一条语句可以省略包含主体的大括号。表达式主体中允许有返回值,当只有一条return语句时,也可省略return关键字。
interface Animal{
void shout();
}
public class Example22 {
public static void main(String[] args) {
String name="小花";
animalShout(new Animal(){
public void shout(){
System.out.println("匿名内部类输出:"+name+"喵喵喵");
}
});
animalShout(()-> System.out.println("Lamadba表达式输出:"+name+"喵喵喵"));
}
public static void animalShout(Animal an){
an.shout();
}
}
4.6.2 函数式接口
Lambda的局限是接口中有且只有一个抽象方法时才能使用Lambda表达式代替匿名内部类。因为Lambda表达式是基于函数式接口实现的。所谓函数式接口是指有且仅有一个抽象方法的接口。
JDK8中,为函数式接口引入一个@FunctionInterface注解,显式地标注了接口是一个函数式接口,并强制编辑器进行更严格的检查,确保该接口是函数式借口,如果不是,编译器会报错,对程序运行没有实质影响。
@FunctionalInterface
interface Animal{
void shout();
}
interface Calculate{
int sum(int a,int b);
}
public class Example23 {
public static void main(String[] args) {
animalShout(() -> System.out.println("无参、无返回值的函数式接口调用"));
showSum(10, 20, (x, y) -> x + y);
}
private static void animalShout(Animal animal){
animal.shout();
}
private static void showSum(int x,int y,Calculate calculate){
System.out.println(x+"+"+y+"的和为:"+calculate.sum(x,y));
}
}
4.6.3 方法引用与构造器引用
Lambda表达式的主体只有一条语句时,程序不仅可以省略包含主体的大括号,还可以通过英文双冒号的语法格式来引用方法和构造器(即构造方法),这两种形式都简化书写, 本质都是对Lambda表达式的主题部分已存在的方法进行直接引用,主要区别就是对普通方法与构造方法的引用而已。
种类 | Lambda表达式示例 | 对应的引用示例 |
---|---|---|
类名引用普通方法 | (x,y,...)->对象名 x.类普通方法名(y,...) | 类名::类普通方法名 |
类名引用静态方法 | (x,y,...)->类名 x.类静态方法名(x,y,...) | 类名::类静态方法名 |
对象名引用方法 | (x,y,...)->对象名 x.实例方法名(x,y,...) | 类名::实例方法名 |
构造器引用 | (x,y,...)->new 类名(x,y,...) | 类名::new |
1.类名引用普通方法
可以是Java自带的特殊类,也可以是自定义的普通类
//类名引用普通方法
//定义一个函数式接口
@FunctionalInterface
interface Printable{
void print(StringUtils su,String str);
}
class StringUtils{
public void printUpperCase(String str){
System.out.println(str.toUpperCase());
}
}
public class Example24 {
private static void printUpper(StringUtils su,String text,
Printable pt){
pt.print(su,text);
}
public static void main(String[] args) {
printUpper(new StringUtils(),"Hello",
(Object,t) -> Object.printUpperCase(t));
printUpper(new StringUtils(),"Hello",
StringUtils::printUpperCase);
}
}
2.类名引用静态方法
//类名引用静态方法
interface Calcable{
int calc(int num);
}
class Math{
public static int abs(int num){
if(num<0){
return -num;
}else{
return num;
}
}
}
public class Example25 {
private static void printAbs(int num,Calcable calcable){
System.out.println(calcable.calc(num));
}
public static void main(String[] args) {
//使用Lambda表达式
printAbs(-10,n->Math.abs(n));
//使用方法引用的方式
printAbs(-10,Math::abs);
}
}
3.对象名引用方法
对象名引用方法指的是通过实例化对象的名称来对齐方法进行的引用。
//对象名引用方法
//定义一个函数式接口
@FunctionalInterface
interface Printable{
void print(String str);
}
class StringUtils{
public void printUpperCase(String str){
System.out.println(str.toUpperCase());
}
}
public class Example26 {
private static void printUpperCase(String text,Printable pt){
pt.print(text);
}
public static void main(String[] args) {
StringUtils stu = new StringUtils();
//使用Lambda表达式
printUpperCase("Hello",t->stu.printUpperCase(t));
//使用方法引用的方式
printUpperCase("Hello",stu::printUpperCase);
}
}
4.构造器引用
构造器引用指的是对类自带的构造器的引用。
//构造器方法
//定义一个函数式接口
@FunctionalInterface
interface PersonBuilder{
javabasic.chapter04.Person buildPerson(String name);
}
//定义一个Person类,并添加有参构造方法
class Person{
private String name;
public Person(String name){
this.name = name;
}
public String getName(){
return name;
}
}
public class Example27 {
public static void printName(String name, javabasic.chapter04.PersonBuilder builder){
System.out.println(builder.buildPerson(name).getName());
}
public static void main(String[] args) {
//使用Lambda表达式
printName("Lambda",name->new javabasic.chapter04.Person(name));
//使用构造器方法
printName("构造器", javabasic.chapter04.Person::new);
}
}
4.7 异常
4.7.1什么是异常
Java语言中引入了异常(Exception),以异常类的形式对这些非正常情况进行封装,并通过异常处理机制对程序运行时发生的各种问题进行处理。
public class Example28 {
public static int divide(int x,int y){
int result = x/y;
return result;
}
public static void main(String[] args) {
int reuslt = divide(4,0);
System.out.println(result);
}
}
//报错"java.lang.ArithmeticException: / by zero(被0除的算数运算异常)"
Java中提供了大量异常类,这些类都集成自java.lang.Throwable类
Throwable有两个直接子类Error和Exception,其中Error代表程序中产生的错误,Exception代表程序中产生的异常。
-
Error称为错误类,表示Java允许时产生的系统内部错误或资源耗尽的错误,是比较严重的,仅靠修改程序是不能恢复执行的,如系统崩溃、虚拟机错误等;
-
Exception类称为异常类,表示程序本身可以处理的错误,在Java程序开发中进行的异常处理,是针对Exception类及其子类的。该类中除了表示运行时异常的RuntimeException类,其他子类都用于表示编译时异常。
Throwable类常用方法
方法声明 | 功能描述 |
---|---|
String getMessage() | 返回次throwable的详细信息字符串 |
void printStackTrace() | 将次theowable及其追踪输出至标准错误流 |
void printStackTeace(PrintStream s) | 将次theowable及其追踪输出值到指定的输出流 |
4.7.2异常的类型
1.编译时异常
编译时异常的她点是在程序编写过程中,Java编译器就会对编写的代码进行检查,如果出现比较明显的异常就必须对异常进行处理,否则无法通过编译。处理编译时异常的方式:
-
使用try...catch语句对异常进行捕获处理
-
使用throws关键字声明抛出异常,让调用者对其处理
2.运行时异常
运行时异常是在程序运行时由Java虚拟机自动进行捕获处理的,即使没有try...catch语句捕获或使用throws关键字声明抛出,程序也能编译通过,只是在运行过程中可能报错。
常见的运行时异常
异常类名称 | 异常类说明 |
---|---|
ArithmeticException | 算数异常 |
IndexOutOfBoundsException | 角标越界异常 |
ClassCastException | 类型转换异常 |
NullPointException | 空指针异常 |
NumberFormatException | 数字格式化异常 |
运行时异常一般是由于程序中的逻辑错误引起的, 在程序运行时无法恢复。
4.7.3try...catch和finally
一种对异常进行处理的方式--异常捕获
try{ //可能发生异常的语句 }catch(Exception类及其子类 e){ //对捕获的异常进行相应处理 }
public class Example29 {
public static int divide(int x,int y){
try{
int result = x/y;
return result;
}catch (Exception e){
System.out.println("捕获的异常信息为:"+e.getMessage());
}
return -1;
}
public static void main(String[] args) {
int result = divide(4,0);
if(result==-1){
System.out.println("程序发生异常");
}else{
System.out.println(result);
}
}
}
/**
* 输出结果:
* 捕获的异常信息为:/ by zero
* 程序发生异常
*/
public class Example30 {
public static int divide(int x,int y){
try{
int result = x/y;
return result;
}catch (Exception e){
System.out.println("捕获的异常信息为:"+e.getMessage());
}finally {
System.out.println("执行finally代码块,无论程序是否异常,都会执行");
}
return -1;
}
public static void main(String[] args) {
int result = divide(4,0);
if(result==-1){
System.out.println("程序发生异常");
}else{
System.out.println(result);
}
}
}
4.7.4throws关键字
不急于处理异常时,Java允许将这种异常从当前方法中抛出,然后让后续的调用者在使用时再进行异常处理。
抛出异常使用throws关键字是吸纳,在抛出异常的方法名称后,同时支持一次性抛出多种类型的异常
[修饰符] 返回值类型 方法名([参数类型 参数名,...])throws 异常类1,异常类2,...( 方法体... )
public class Example31 {
public static int divide(int x,int y) throws Exception{
int result = x/y;
return result;
}
public static void main(String[] args) {
int result = divide(4,0);//"Unhandled exception type Exception(未处理的异常类型)"
System.out.println(result);
}
}
"Add throws declaration"表示在方法上继续使用throws关键字抛出异常
"Surround with try/catch"表示在出现异常的代码处使用try...catch代码块进行捕获处理
public class Example32 {
public static int divide(int x,int y) throws Exception{
int result = x/y;
return result;
}
public static void main(String[] args) {
try {
int result = divide(4,0);
System.out.println(result);
} catch (Exception e) {
System.out.println("捕获的异常信息为:"+e.getMessage());
}
}
}
也可以根据提示使用throes关键字继续将异常进行抛出,这样也会编译通过,但是程序发生了异常,终究是要处理的, 如果不处理,程序会非正常终止。
public class Example33 {
public static int divide(int x,int y) throws Exception{
int result = x/y;
return result;
}
public static void main(String[] args) throws Exception {
int result = divide(4,0);
System.out.println(result);
}
}
//通过了编译但是忧郁没有对“/by zero”的异常进行处理,最终导致程序终止运行
4.7.5throw关键字
throw用于方法体内,而且抛出的是一个异常类对象,throws关键字用于方法声明中,用来指明方法可能抛出的多个异常。
通过throw关键字抛出异常后,还需要使用throws关键字或try...catch对异常进行处理。需要注意,如果throw抛出的是Error、RuntimeException或它们的子类异常对象,则无需使用throws关键字或try...catch对异常进行处理。
public class Example34 {
public static void printAge(int age) throws Exception{
if(age<=0){
throw new Exception("输入的年龄有误,必须是正整数!");
}else{
System.out.println("此人的年龄为:"+age);
}
}
public static void main(String[] args) {
int age = -1;
try{
printAge(age);
}catch (Exception e){
System.out.println("捕获的异常信息为:"+e.getMessage());
}
}
}
throw关键字除了可以抛出代码的逻辑性异常外,也可以抛出Java能够自动识别的异常。
4.7.6自定义异常
Java允许用户自定义异常,但自定义的异常类必须继承自Exception或其子类。
public class DivideByMinusException extends Exception{
public DivideByMinusException(){
super();
}
public DivideByMinusException (String message){
super(message);
}
}
public class Example36 {
public static int divide(int x,int y)throws DivideByMinusException{
if(y==0){
throw new DivideByMinusException("除数是0");
}
int result = x/y;
return result;
}
public static void main(String[] args) {
try {
int result = divide(4,0);
System.out.println(result);
} catch (DivideByMinusException e) {
System.out.println("捕获的异常信息为:"+e.getMessage());
}
}
}
4.8 垃圾回收
在Java中,当一个对象称为垃圾后仍会占用内存空间,时间一场, 就会导致内存空间的不足,针对这种情况,引入垃圾回收机制(Java GC)。Java虚拟机会自动回收垃圾对象所占用的内存空间。
当一个对象在堆内存空间中运行时,根据它被引用变量所引用的状态,可以把它所处的状态分成如下三种:
-
可用状态:当一个对象被创建后,如果有一个或以上的引用变量引用它,那么这个对象在程序中处于可用状态,程序可以通过引用变量来调节该对象的实例变量和方法。
-
可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它就进入了可恢复状态。这种状态下, 系统的垃圾回收机制准备回收该对象所占用的内存,在回收该对象之前,系统会调用所有可恢复状态对象的finalize()方法进行资源清理。如果系统在调用finalize()方法前重新使一个应用变量引用该对象,则这个对象再次变为可用状态,否则进入不可用状态。
-
不可用状态:当对象失去了所有引用变量的关联,且系统已经调用所有对象的finalize()方法后依然没有使该对象变成可用状态, 那么这个对象将永久地失去引用,变成不可用状态。只有当一个对象处于不可用状态时,系统才会真正地回收该对象所占用的内存空间。
一个对象在彻底失去引用或成为垃圾后会暂时地保留在内存中,当这样的垃圾堆积到一定程度时,Java虚拟机就会启动垃圾回收器将这些垃圾对象从内存中释放,从而使程序获得更多可用的内存空间。虽然通过程序可以控制一个对象何时不再被任何引用变量所引用,但是无法精确地控制Java垃圾回收的实际。除了等待Java虚拟机进行自动垃圾回收,还可以通过一下了两种方式强制系统进行垃圾回收。
-
调用System类的gc()静态方法:System.gc()
-
调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()
事实上,调用System.gc()时,执行的也是Runtime.getRuntime().gc()。但系统是否立即进行垃圾回收依然具有不确定性。大多数情况下,强制系统垃圾回收后总是有一定的效果。
当一个对象在内存中被释放时,它的finalize()方法会被自动调用,finalize()方法是定义在Object类中的实例方法。方法原型:
protected void finalize() throws Throwable()
任何Java类都可以重写Object类的finalize()方法,在该方法中清理该对象占用的资源。如果程序终止之前仍然没有进行垃圾回收,就不会调用失去引用对象的finalize()方法来清理资源。
只有当程序认为需要更多的额外内存时,垃圾回收器才会自动进行垃圾回收,在一些情况下,对象的finalize()方法不一定会被调用,例如某个失去了引用对象的对象只占用了少量的内存,而系统没有眼中的内存需求的时候。
案例:Java虚拟机进行垃圾回收的过程
class Person{
public void finalize(){
System.out.println("对象将被作为垃圾回收...");
}
}
public class Example37 {
//1、一个不通知强制垃圾回收的方法
public static void recycleWaste1(){
Person p1 = new Person();
p1 = null;
int i = 1;
while(i<10){
System.out.println("方法1循环中...");
i++;
}
}
//2.一个通知强制垃圾回收的方法
public static void recycleWaste2(){
Person p2 = new Person();
p2 = null;
System.gc();
// Runtime.getRuntime().gc();
int i = 1;
while (i<10){
System.out.println("方法2循环中...");
i++;
}
}
public static void main(String[] args) {
recycleWaste1();
System.out.println("------------------------------------");
recycleWaste2();
}
}