第6章:面向对象(下)

6.1 包装类

java为8种基本数据类型分别定义了相应的引用类型,并称之为基本数据类型的包装类

  1. jdk1.5之后提供了自动装箱与自动拆箱功能,即基本类型数据和对应的包装类可以自动转换
//自动装箱:相当于Integer a = Integer.valueOf(5);
Integer a = 5;
//5是int型,所以无法自动装箱成Double
//Double a = 5;
//Boolean = 5;
//自动拆箱:相当于int b = a.intValue();
int b = a;
  1. 字符串与基本类型的相互转换
//a.字符串转基本类型:实际为将String类先转为包装类,再利用其自动装箱,拆箱功能转为基本类型
String intStr = "123";
int int1 = Integer.parseInt(intStr);
int int2 = new Integer(intStr);
//b.基本类型转字符串
int a = 5;
String str = a+"";
  1. jvm在初始化Integer类时,会在堆内存中开辟一块内存空间,存放其内部类IntegerCache中的静态成员变量cache,再开辟一块内存空间存放cache所指向的对象(一个数组,值从-128-127),当自动装箱时(Integer a = 15),实际上代码被转换为Integer a = Integer.valueOf(15),valueOf方法判断如果传入参数为-128-127之间的数字,那么直接就将变量a,指向cache数组中对应的值所在的地址。而128不在范围内,所以不会进行缓存,而是新建一个数组,导致地址不同
//ina与inb指向同一块内存区域
Integer ina = 2;
Integer inb = 2;
//返回true
System.out.println(ina==inb);
Integer ina = 128;
Integer inb = 128;
//返回false
System.out.println(ina==inb);
//不采用自动装箱,而是新建一个对象,一个在堆中,一个在常量池中,所以不等
Integer a = 5;
Integer b = new Integer(5);
//返回false
System.out.println(a==b);

6.2 处理对象

6.2.1 打印对象和toString()方法
  1. 打印对象时,对象与字符串进行连接运算的这种情况,会自动调用对象的toString()方法
  2. toString()为Object的方法,所以所有对象都有该方法,在Object中的toString()方法返回该对象实现类的类名+@+对象hashcode值。如果不想让toString()返回这种信息,应该在子类中重写toString() 方法
6.2.2 ==和equals
  1. ==:判断基本类型变量,值相等即等,判断两个引用类型变量,地址相等才等
  2. equals:Object类中与==完全相同,其他类中:可能重写了equals方法,也可根据自己需要重写该方法
6.2.3 String
  1. JVM虚拟内存
    1. 程序计数器:jvm执行程序的流水线,存放一些跳转指令。
    2. 本地方法栈是:jvm调用操作系统方法所使用的栈。
    3. 虚拟机栈:jvm执行java代码所使用的栈。
    4. 方法区:存放了一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置。
    5. 虚拟机堆是jvm执行java代码所使用的堆。
  2. 常量池:其实是方法区中的一部分,用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java"这种申明方式
//只在常量池中创建对象,s1指向常量池中的对象
String s1 = "wusihan";
String s2 = "wu";
String s3 = "sihan";
String s4 = s2+s3;
//jvm会先在常量池中创建"hello"对象,再在堆内存中创建一个新的String对象,并用构造器为其赋值,所以这种代码相当于产生了两个String对象,s5指向堆中的对象
String s5 = new String("wusihan");
String s6 = "wu"+"sihan";
//true
System.out.println(s1==s6);
//false:由于s2+s3在编译时没有确定,所以s2+s3所产生的对象并不在常量池中,而在堆中。如果s2和s3用final修饰,那么就会返回true
System.out.println(s1==s4);
System.out.println(s1==s5);

6.3 类成员

6.3.1 单例类

如果一个类始终只能创建一个对象,这个类称为单例类

package mytest.javaBase;

public class Singleton {
    //1. private修饰构造器,否则任何类都可以创建该类对象了
    private Singleton() {
	}
    //3. 为该类定义一个static成员变量来缓存已创建的对象,下面的静态方法访问这个成员变量来判断该类是否已经创建过对象,
	private static Singleton instance;
	//多例,数据库连接池就是这么实现的
	private static List<Singleton> instances = new ArrayList<Singleton>();
	//2. 类中提供一个public方法创建该类对象,且该方法用static修饰,因为调用该方法前不确定是否存在对象,所以无法使用对象.方法进行调用
	public static Singleton getInstance() {
		if (instance == null) {
			instance = new Singleton();
		}
		return instance;
	}
}

6.4 final修饰符:不可被修改

  1. final修饰变量:表示该变量不允许被重新赋值或重新指向新的对象
  2. final修饰方法:表示不可以被子类重写
  3. final修饰类:不可以被继承
6.4.1 final修饰成员变量
  1. 正常情况下成员变量无需显式初始化,但final修饰的成员变量必须显示初始化,系统不会对final成员进行隐式的初始化,而由于类变量的初始化在静态初始化块或变量声明中完成,实例变量的初始化在普通初始化块、变量声明、构造器中进行,于是总结出final修饰成员变量指定初始值的地方如下
    1. 静态成员变量:静态初始化块,声明中
    2. 实例成员变量:普通初始化块,声明,构造器,且只有在声明时指定初始值才有宏变量的效果(p179最下)
  2. 由于final修饰的变量的值和引用的对象不可以改变,所以上面final修饰成员变量指定初始值的地点只能任选各自的一个地点进行初始化赋值,且必须选一个地点进行初始化赋值(不可以在普通方法中进行初始化赋值)
6.4.2 final修饰局部变量

局部变量本就必须被显式初始化才可以使用,系统不会默认初始化,所以final 局部变量时既可以在定义时指定默认值,也可以在后面代码中赋初值,即必须赋值,且只赋一次,形参在调用其所在方法时,由系统根据传入参数完成初始化,所以final 形参不可以被赋值

test(final int a){
    //a = 5;a传入时就已经被赋值,不可再次被赋值
}
6.4.3 final修饰基本类型变量和引用类型变量的区别
  1. 基本类型:值不可以改变,不可以被重新赋值
  2. 引用类型:引用的对象不可以改变,不可以指向新的对象,但对象的成员变量值可以改变
6.4.4 可执行宏替换的final变量
  1. final修饰,定义时就初始化
  2. 其值在编译期间就能确定
//编译器会将程序中所有用到该变量的地方直接替换成该变量的值,对编译器来讲它是个常量
String a = "wusihan";
final String a1 = "wu";
final String a2 = "sihan";
//返回true
System.out.println(a==(a1+a2));
6.4.5 final修饰方法

final修饰的方法不可被子类重写,重写会报错,但private final组合在一起修饰方法的时候,可以在子类中定义同名同参方法,因为此时只是定义了一个新的方法。但估计不会有人这么用,因为没必要,已经是private,说明一定无法被重写,也就没有用final修饰的意义

6.4.6 final修饰类

final修饰的类不可以被继承(有子类)

6.4.7 不可变类(与final 类是两个不同概念)

其成员变量的值或指向,以及成员变量指向的对象的成员变量的值或指向都不可以改变

package mytest.javaBase;

public class ImmutableClass {
    //1.private final修饰成员变量,private单纯为了封装考虑,final是为了只允许该成员变量赋值一次,这样才能做到不可变
	private final int a ;
	//2.提供带参构造器,根据参数初始化成员变量,因为final修饰的成员变量只能在固定几个地方赋初值
	public ImmutableClass(int a){
		this.a = a;
	}
	//3.为成员变量提供getter方法,不为其提供setter方法,因为提供了也不好用,被final修饰的成员变量赋值一次
	public int getA() {
		return a;
	}
}
package mytest.javaBase;

public class ImmutableClassNew {
    //1.如果成员变量的类型是一个可变类,那么会导致虽然这个成员变量不能指向新的对象,但是该成员变量对应的对象的成员变量是可变的,不满足不可变类的想法
	private final Student a ;
	//2.以下代码遵循一个原理,不让外界的引用指向ImmutableClassNew的成员变量a所对应的对象
	//这种情况应修改构造函数,新建一个对象赋给其成员变量,如果不这样做,那么可以想象如下代码
	//Student a = new Student();
	//ImmutableClass ic = new ImmutableClass(a);
	//a.setFirstName("wusihan");
	//此时ic中的a的firstName属性也会跟着变化
	public ImmutableClassNew(Student a){
		this.a = new Student(a.getFirstName(),a.getLastName());
	}
	//还应该修改getter方法,如果不这样做可以想象如下代码
	//Student a = new Student();
	//ImmutableClass ic = new ImmutableClass(a);
	//ic.getA().setFirstName("wusihan");
	//此时ic中的a的firstName属性也会跟着变化
	public Student getA() {
		return new Student(a.getFirstName(),a.getLastName());
	}
}
6.4.8 缓存实例的不可变类

不可变类的对象的成员变量的值和指向不可以改变,如果程序经常使用相同值或指向的不可变类的对象,则应考虑缓存该不可变类的对象,来防止相同对象的重复创建。所谓的缓存就是定义一个数组类型的成员变量,将已创建的对象放入数组中,如再需创建对象时,先看数组中是否已经存在相同对象,如果有,返回该对象,如果没有,新建对象并返回,从而达到缓存的效果。Integer.valueOf()用的就是这种机制

6.5 抽象类

某些情况,父类(Shape)只知道子类(Circle)应包含怎样的方法(callPerimeter()),但是不知道子类具体应该如何实现该方法,如果在子类中直接定义该方法则出现以下情况

Shape shape = new Circle();
//shape.callPerimeter();编译时报错
//写法太麻烦
(Circle)shape.callPerimeter();
//如果父类中有callPerimeter方法,则由于多态,可写作
shape.callPerimeter();

为了提高程序灵活,出现了抽象方法的概念

6.5.1 抽象类和抽象方法
  1. 抽象类与抽象方法由abstract修饰,抽象方法不能有方法体
public abstract double callPerimeter();
//public abstract double calPerimeter(){};不可以保留这个大括号
  1. 抽象类不能被实例化,即可以定义构造器,但无法创建对象,因为可以想象如果抽象类有具体的对象,那么其抽象方法该如何调用?即使抽象类中没有抽象方法,也不可以被实例化
  2. 抽象类中可包含非抽象类中的成分。如变量,方法,构造器,初始化块,内部类等。
  3. 类中如果有抽象方法,此类必须为抽象类
  4. 抽象类只能被当做父类被其他类继承
  5. abstract
    1. 只能修饰类和方法
    2. static abstract不能同时修饰方法,但可以同时修饰内部类,因为静态方法是不能被重写的,而abstract方法又必须被重写
    3. private abstract也不能同时修饰方法,因为private阻止了子类对方法的重写
6.5.2 抽象类的作用
  1. 抽象类作为子类的模板,避免子类设计的随意性,这是一种模板模式
  2. 父类只需定义方法,把不能实现部分抽象成抽象方法,留给其子类进行实现
  3. 父类中的非抽象方法其内部如果包含了抽象方法,这种抽象方法必须由其子类实现

6.6 接口

可理解为比抽象类更抽象的东西,接口中所有方法都是抽象方法,java8中才允许加入(default)默认方法和类(static)方法

6.6.1 接口的概念
6.6.2 接口的定义
[修饰符] interface 接口名 extends 父接口1,父接口2 ...{
    零到多个常量;
    零到多个抽象方法;
    零到多个内部类,接口,枚举定义;
    零到多个默认方法或类方法;(java 8 新增)
}
  1. 修饰符:与类一样,只能是public或空
  2. 接口只能继承接口不能继承类
  3. 由于接口是一种规范,其内部不允许包含构造器和初始化块
  4. 接口中成员变量都默认使用public修饰,可以省略其书写
    1. 成员变量:默认public static final定义,且由于接口没有构造器和代码块,且final必须手动初始化,因此必须在定义处手动初始化
    2. 方法:默认public abstract定义,所以一定为抽象方法
    3. 默认方法:default修饰
    4. 类方法:static修饰
    5. 内部类,接口,枚举定义:默认public static修饰
  5. 如果java源文件中定义了一个public接口,则该源文件文件名必须与接口名相同
6.6.3 接口的继承
  1. 同种类继承时,即类继承类、接口继承接口,使用extends。不同种类继承时叫做实现,即类实现接口使用implements
  2. 一个类或接口可以继承或实现多个接口,但只能继承一个类
  3. 接口只能继承接口,不能继承类
6.6.4 类实现接口:implements
[修饰符] classextends 父类,implements 接口1,接口2 ...{
    
}
  1. 接口不能创建实例,但可以用于声明引用类型变量,当使用接口来声明引用类型变量时,这个引用变量必须指向接口实现类对象
//Printer类必须实现Product接口,否则编译报错,且Object也是所有接口类型的父类
Product p = new Printer();
  1. 实现接口与继承类似,可以获得接口中定义的常量与方法
  2. 一个类实现了一个或多个接口后,这个类必须完全实现接口中定义的所有抽象方法,否则该类将保留从父接口中继承到的抽象方法,即该类必须定义为抽象类
  3. 实现接口方法时,必须用public修饰,因为重写时,访问权限必须大于等于父类中方法的访问权限,而接口中方法又都是默认public修饰
6.6.5 接口与抽象类

接口为总纲,抽象类为模板

  1. 相同:都不能实例化,都位于继承树顶端,用于被其他类继承和实现,都包含抽象方法
  2. 不同
    1. 接口中不能有普通方法
    2. 接口中只能定义静态常量,不能定义普通成员变量
    3. 接口中不能含有构造器与初始化块
    4. 一个类只能继承一个抽象父类,但可以实现多个接口
6.6.6 面向接口编程

接口体现了一种规范,实现分离,充分利用接口,降低程序各模块间的耦合,提高系统的可扩展性与可维护性,下面介绍两种面向接口编程的设计模式,设计模式实际上仅仅是对特定问题的一种惯性思维

  1. 简单工厂模式
package mytest.javaBase;

public interface Output {
	abstract void print();
}

package mytest.javaBase;

public class Printer implements Output{
    @Override
	public void print(){
		System.out.println("旧打印机");
	}
}

package mytest.javaBase;

public class BetterPrinter implements Output{
    @Override
	public void print(){
		System.out.println("更好的打印机");
	}
}

package mytest.javaBase;

public class OutputFactory {
	public static Output getOutput(){
		return new Printer();
	}
	public static void main(String[] args) {
		//如果这样写,当想将系统中Printer都换成BetterPrinter时,所有代码都需要改写,所以应该利用多态,让Printer与BetterPrinter都实现同一个接口Output
		//Printer a = new Printer();
		//a.print();
		//代码有所改进,只需要修改new后面的Printer即可,但是工作量还是大
		//Output a = new Printer();
		//a.print()
		//最后利用工厂模式这样创建对象,当有改动时,只需要修改getOutput方法即可
		Output a = OutputFactory.getOutput();
		a.print();
	}
}

  1. 命令模式
package mytest.javaBase;

public interface Command {
	void process(int[] target);
}

package mytest.javaBase;

public class PrintCommand implements Command {
	@Override
	public void process(int[] target) {
		System.out.println("打印数组");
	}

}

package mytest.javaBase;

public class AddCommand implements Command {
	@Override
	public void process(int[] target) {
		System.out.println("数组求和");
	}

}

package mytest.javaBase;

public class ProcessArray {
	public void method(int[] target,Command cmd){
		cmd.process(target);
	}
	public static void main(String[] args) {
		ProcessArray pa = new ProcessArray();
		//定义method方法时,形参列表中传入的参数的类型为一个接口类型,并且在这个方法中调用这个传入的接口对象的方法,由于多态,此时调用的实际是真正实现类的方法,从而达到传入参数不同实现不同
		pa.method(new int[5], new PrintCommand());
		pa.method(new int[5], new AddCommand());
	}
}

6.7 内部类

  1. 只要把一个类放在另一个类的内部定义即可,此处的"类内部"包含类中的任何位置,甚至方法中也可以定义内部类(局部内部类)
  2. 根据是否有static修饰,将内部类分为静态内部类和非静态内部类
  3. 内部类可以使用private和protected修饰
  4. 编译含内部类的外部类时,文件所在路径生成两个class文件,外部类.class和外部类$内部类.class
6.7.1 非静态内部类:属于外部类的对象

不允许在非静态内部类中定义静态成员,可理解为static表示属于类,但非静态内部类又表示属于对象,两者矛盾

6.7.2 静态内部类:属于外部类本身

java中允许在接口中定义内部类,默认为public static修饰

6.7.3 内部类中使用外部类
  1. 调用外部类中与内部类同名的非static方法/变量:Out’te’r’Class.this.方法/变量
  2. 其他:与方法中使用其他方法/变量同等对待
package mytest.javaBase;

public class OutterClass {
	private String a = "外部类成员";
	public class InnerClass{
		private String a = "内部类成员";
		public void method(){
			String a = "局部变量";
			//如果内部类中的方法的局部变量和内部类的成员变量,外部类的成员变量,名相同,那么用this代替内部类对象,外部类类名.this代替外部类对象
			System.out.println(a);
			System.out.println(this.a);
		    System.out.println(OutterClass.this.a);
		}
	}
	public static void main(String[] args) {
		InnerClass jjj = new OutterClass().new InnerClass();
		jjj.method();
	}
}

6.7.3 使用内部类
  1. 外部类内部使用静态内部类与非静态内部类
package mytest.javaBase;

public class OutterClass1 {
	public static class InnerClass1{
		public String a = "静态内部类";
		public static String b = "静态内部类b";
	}
	public class InnerClass2{
		public String a = "非静态内部类";
	}
	public void method(){
	    //如果内部类的成员变量不是static,都需要新建内部类对象进行调用
		System.out.println(new InnerClass1().a);
		System.out.println(new InnerClass2().a);
		//如果为static,直接类名.变量名调用
		System.out.println(InnerClass1.b);
	}
	public static void main(String[] args) {
		OutterClass1 a = new OutterClass1();
		a.method();
	}
}

  1. 外部类以外使用静态内部类与非静态内部类
    1. 非静态
    //非静态内部类属于外部类对象,所以必须先创建外部类对象,再创建其内部类对象
    OutterClass.InnerClass varName = new OutterClass().new InnerClass();
    //继承非静态内部类子类
    public SubClass extends OutterClass.InnerClass{
        //必须手动定义构造器,且由于子类必须先调用父类构造器,而调用非静态内部类构造器时,必须存在一个外部类对象,所以在构造器中传入一个外部类对象,并通过外部类对象.super()调用内部类的构造器
        public SubClass(OutterClass out){
            //out.new InnerClass();
            out.super(); 
        }
    }
    
    1. 静态
    OutterClass.InnerClass varName = new OutterClass.InnerClass();
    //子类继承静态内部类时,无需自定义构造器,且该子类中的内部类不会覆盖父类中的内部类,类似于方法的覆盖,因为一般只有同名方法才可构成重写,而对于类来说,内部类名字为外部类$内部类,所以即使外部类的内部类与其子类的内部类的类名相同,由于各自外部类的类名不同,所以不会出现同名,所以也不会被覆盖
    
6.7.4 局部内部类

可近似当做局部变量,他的上级是方法而不是类,所以不能用public,private,protected,static修饰,在编译局部内部类时,生成class文件名为:外部类.class,外部类$N内部类.class,N是因为同一类中多个局部内部类可以同名(不同方法中相同的名字),用于区分

6.7.5 匿名内部类

一般适用于只用一次的类

  1. 匿名内部类必须继承一个父类或实现一个接口,且最多继承一个父类,实现一个接口
  2. 匿名内部类作用就是创建对象,因此不能为抽象类,即匿名内部类中必须实现父类/接口中所有抽象方法,
  3. 匿名内部类不可以定义构造器,因为没有类名,但可以定义初始化块
  4. java8以前,要求被匿名内部类访问的它外部的(它内部定义的变量没限制)变量必须用final修饰,而java8中,这种外部的变量不需要final修饰,也无法在匿名内部类中被修改值,这种只在匿名内部类代码块中,不允许修改变量值的特性,叫做Effectively final
package mytest.javaBase;

public interface Product {
	public double getPrice();
}

package mytest.javaBase;

public class ProductSon implements Product{
	@Override
	public double getPrice() {
		System.out.println("正常类方法实现");
		return 0;
	}
}

package mytest.javaBase;

public class ProductTest {
	public static void main(String[] args) {
	    //以下两段代码的意义完全相同,下方的意思就是创建一个Product的匿名子类,并调用该子类重写的Product类的方法
	    //通过实现接口创建匿名内部类时,由于匿名内部类本身不能定义构造器,且接口中也不允许定义构造器,所以()中没有参数,如果是通过继承父类创建匿名内部类,那么可以调用父类的构造器,所以()中可以有参数
		new Product(){
			@Override
			public double getPrice() {
				System.out.println("匿名内部类方法实现");
				return 0;
			}
		}.getPrice();
		ProductSon ps = new ProductSon();
		ps.getPrice();
	}
}

6.7.6 内部类总结

内部类实际上就是外部类的一个成员,静态内部类相当于静态成员,外部类初始化后才能存在,非静态内部类相当于实例成员,必须在外部类对象创建后才能存在

  1. 内部类访问外部类:
    1. 静态内部类中不允许访问外部类非静态成员,需创建外部类对象访问(静态中不能使用非静态)
    2. 访问外部类与内部类中同名变量/方法:OutterClass.this.变量/方法
  2. 外部类访问内部类:
    1. 外部类静态成员中不允许使用非静态内部类中成员,需创建非静态内部类对象进行访问(静态中不能使用非静态)
  3. 类外部访问内部类:
    1. 定义变量:OutterClass.InnerClass a;
    2. 创建非静态内部类对象:new OutterClass().new InnerClass();
    3. 创建静态内部类对象:new OutterClass.InnerClass();
    4. 继承非静态内部类:手动创建构造方法,为构造方法传入OutterClass对象,构造方法第一行调用该对象super()方法从而调用内部类InnerClass构造器
  4. 内部类所含成员:
    1. 静态内部类:所有成员
    2. 非静态内部类:不能有静态成员(特殊:非静态中不能使用静态)

6.8 Lambda表达式

  1. 允许使用更简洁的代码创建只有一个抽象方法的接口(函数式接口)的实例
  2. Lambda表达式、方法引用、构造器引用的本质都是通过实现函数式接口,来创建对象
  3. 创建出的对象类型,并不是由Lambda表达式、方法引用、构造器引用决定,而是由接收该表达式的变量的类型决定的
  4. 创建对象的同时,由于该对象实现了函数式接口,因此会为该对象指定其实现的函数式接口的方法,但不会调用该方法,方法的调用,会在使用该对象时发生
6.8.1 入门
  1. 匿名内部类
public class CommandTest {
	public static void main(String[] args) {
		ProcessArray pa = new ProcessArray();
		int[] target = { 3, -4, 6, 4 };
		pa.process(target, new Command() {

			@Override
			public void process(int[] target) {
				int sum = 0;
				for (int tmp : target) {
					sum += tmp;
				}
				System.out.println("数组元素总和为:" + sum);

			}

		});
	}
}

  1. Lambda

public class CommandTest2 {
	public static void main(String[] args) {
		ProcessArray pa = new ProcessArray();
		int[] array = { 3, -4, 6, 4 };
		// 1. 省略new Xxx(){}
		// 2. 省略重写方法的定义,只保留其参数列表,如果参数列表中只有一个参数,形参列表的圆括号(),和该参数的参数类型都可以省略
		// 3. 如果形参列表中没有参数,那么不允许省略形参列表的圆括号()
		// 4. 如果代码块中只包含一条语句,可以省略{}。如果只包含一条return语句,可以省略return关键字。
		pa.process(array, target -> {
			int sum = 0;
			for (int tmp : target) {
				sum += tmp;
			}
			System.out.println("数组元素总和为:" + sum);

		});
	}

}

  1. 省略写法
interface Eatable {
	void taste();
}

interface Flyable {
	void fly(String weather);
}

interface Addable {
	int add(int a, int b);
}

public class LambdaQs {
	// 调用该方法需要Eatable对象
	public void eat(Eatable e) {
		System.out.println(e);
		e.taste();
	}

	// 调用该方法需要Flyable对象
	public void drive(Flyable f) {
		System.out.println("我正在驾驶:" + f);
		f.fly("【碧空如洗的晴日】");
	}

	// 调用该方法需要Addable对象
	public void test(Addable add) {
		System.out.println("5与3的和为:" + add.add(5, 3));
	}

	public static void main(String[] args) {
		LambdaQs lq = new LambdaQs();
		// Lambda表达式的代码块只有一条语句,可以省略花括号。
		lq.eat(() -> System.out.println("苹果的味道不错!"));
		// Lambda表达式的形参列表只有一个形参,省略圆括号
		lq.drive(weather -> {
			System.out.println("今天天气是:" + weather);
			System.out.println("直升机飞行平稳");
		});
		// Lambda表达式的代码块只有一条语句,省略花括号
		// 代码块中只有一条语句,即使该表达式需要返回值,也可以省略return关键字。
		lq.test((a, b) -> a + b);
	}
}

6.8.2 Lambda表达式与函数式接口
  1. Lambda表达式的目标类型必须是明确的函数式接口,否则需要处理
//1. Runnable接口只有一个run方法
Runnable r = () -> {

};
//2. 编译报错,需要强制转换
//		Object r1 = () -> {
//
//		};
Object r1 = (Runnable) () -> {

};
//3. Lambda表达式代表传入Thread构造器中的Runnable对象
new Thread(() -> {

});
//4. 同样的Lambda表达式,可以被当做不同的类型的对象,只要表达式的形参列表,与函数式接口中唯一抽象方法形参列表相同
FkTest r2 = () -> {

};
  1. Java8预定义函数式接口
XxxFunction:通常定义apply()方法,通常对数据进行转换处理,并返回新值
XxxConsumer:accept()方法,与apply()类似,不返回结果(void)
XxxPredicate:test()方法,判断是否满足条件,返回boolean
XxxSupplier:getAsXxx()方法,无输入参数,按某种逻辑算法返回数据
6.8.3 方法引用与构造器引用
  1. Lambda表达式的代码块中只有一条代码时,可以省略方法体对应的花括号{},此时还可以使用方法引用和构造器引用,替代这条Lambda表达式
方法引用类型 方法/构造器引用 Lambda表达式
静态 类名::类方法 (a,b,…)->类名.类方法(a,b,…)
有限制 特定对象::实例方法 (a,b,…)->特定对象.实例方法(a,b,…)
无限制 类名::实例方法 (a,b,…)->a.实例方法(b,…)
构造器 类名::new (a,b,…)->new 类名(a,b,…)
  1. 方法引用类型说明
    1. 静态方法引用:表示函数式接口的函数,返回一个静态方法的结果
    2. 有限制方法引用:返回实例方法的结果,但限制调用的对象必须是方法引用中指定的对象
    3. 无限制的方法引用:返回实例方法的结果,调用的对象是函数式接口中,函数的第一个参数
    4. 构造器引用:返回由构造器创建的对象
  2. 可以发现,当使用"类名::类方法"替代Lambda表达时,要求类方法的返回值、形参列表,必须与函数式接口中的函数的返回值、形参列表完全一致,以此类推
6.8.4 Lambda表达式与匿名内部类区别
  1. 匿名内部类可以为任意接口创建实例,不管该接口有多少抽象方法,Lambda只能为函数式接口创建实例
  2. 匿名内部类甚至可以为抽象类或普通类创建实例
  3. 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法,Lambda表达式不行

6.9 枚举体

一个类的对象是有限且固定的,如季节类,只有四个对象,这种实例有限且固定的类在java中被称为枚举类

6.9.1 手动实现枚举类
package mytest.javaBase;

public class Season {
	//1.隐藏构造器,防止对象被随意的创建
	private Season(){
		
	}
	//2.将该类可以提供的所有实例,用public static final修饰的成员变量进行保存
	public static final Season SUMMER = new Season();
	public static final Season WINTER = new Season();
	public static final Season AUTUMN = new Season();
	public static final Season SPRING = new Season();
	//3.如果有必要,可以提供一些静态方法,根据特定参数返回相应的实例
	public static final Season getSeason(int seasonNum){
		switch(seasonNum){
		case 1:
			return SPRING;
		case 2:
			return SUMMER;
		case 3:
			return AUTUMN;
		case 4:
			return WINTER;
		}
		return null;
	}
}

6.9.2 枚举类入门
  1. enum定义枚举类(与class,interface地位相同),一个java源文件必须与public修饰的枚举类同名
  2. enum定义的枚举类默认已继承java.lang.Enum类,由于java只能单继承,所以枚举类不能继承其他类
  3. 枚举类不可以被继承
  4. 枚举类构造器只能使用private修饰,否则可以创建新的枚举类实例,这与枚举类的设计不符,可以省略,默认使用private
  5. 枚举类所有实例必须在枚举类第一行显式列出,否则这个枚举类永远不可产生实例,列出的这些实例默认使用public static final修饰的
package mytest.javaBase;

public enum SeasonEnum {
    //相当于public static final Season SUMMER = new Season(),这些代表枚举类所有可能的实例,也可以叫枚举值
	SPRING,SUMMER,AULTUM,WINTER;
}

  1. 所有枚举类都继承java.lang.Enum类,Enum中的方法如下
int compareTo():根据枚举值的书写顺序设定索引,得到两个枚举值所对的索引之差,相当于"-"
String name():返回此枚举类实例的名称,但一般用toString()
int ordinal():返回枚举值在枚举类中索引值
public static E valueOf(Class enumType,String name):返回指定枚举类中指定名称的对象
switch中可以放枚举值,case中可根据枚举值来进行判断
6.9.3 枚举类的成员变量,方法,构造器
package mytest.javaBase;

public enum Gender {
	MAN("男"),WOMEN("女");
	//枚举类被设计出时,就是希望它是不可变类,为了达到不可变的目的通常(private final修饰成员变量,通过构造器为成员变量赋值,写getter方法访问成员变量)
	private final String sex;
	//构造器必须有private修饰,在类加载时就会被调用
	private Gender(String sex){
		this.sex = sex;
	}
	public String getSex() {
		return sex;
	}
}

6.9.4 实现接口的枚举类
  1. 由枚举类来重写接口中的方法
package mytest.javaBase;

public enum GenderNew implements InterfaceTop{
	MAN,WOMEN;
	//由枚举类来重写接口中的方法,则每个枚举值调用该方法时,执行结果一致
	@Override
	public void print() {
		System.out.println("MAN和WOMEN都打印这个");
	}
}

  1. 由枚举值分别实现接口中的方法
package mytest.javaBase;

public enum GenderNew implements InterfaceTop{
    //由于枚举类中不实现接口中的方法,此时枚举类变成抽象枚举类,无法创建实例,所以此时的枚举类对象实际上是通过继承父类Gender来创建的匿名内部类,即创建了Gender子类的实例,并重写了接口中的方法
    //以下定义等价于
    /*Gender MALE = new Gender(){
        public void print(){
            System.out.println("我是男生");
        }
    }*/
	MAN{
		@Override
		public void print() {
			System.out.println("我是男生");
		}
	},WOMEN{
		@Override
		public void print() {
			System.out.println("我是女生");
		}
	};
}

6.9.5 包含抽象方法的枚举类
  1. 不能人为使用abstract将枚举类定义为抽象类,系统会为拥有抽象方法的枚举类自动添加abstract
  2. 抽象枚举类中也需创建枚举值(枚举类的实例),但由于抽象类不可以创建实例,所以只能在创建每个枚举值时,利用匿名内部类的原理,通过继承抽象枚举类的方式创建其子类的实例,并重写抽象方法,因为如果不实现这个抽象方法,那么该匿名内部类还是抽象类,还是无法创建对象

6.10 对象与垃圾回收

垃圾回收机制,只回收堆内存中的对象,不回收物理资源(数据库连接,网络IO),程序无法精准控制垃圾回收的运行,垃圾回收机制回收任何对象前,都会调用该对象的finalize方法,该方法啊可能使该对象复活,当对象不再被任何引用变量引用时,才可以被回收

  1. 对象在内存中的状态
//可达
Animal a = new Animal();
//可恢复
a = null;
//不可达:系统调用所有对象的finalize方法,没能让对象重新被引用
  1. 强制垃圾回收:其实只是通知系统应进行垃圾回收,但系统不一定进行回收
Sytem.gc();
Runtime.getRuntime().gc();
//可以使用java -verbose : gc 类名,来查看运行时内存状态
  1. finalize方法
    1. 任何java类都可以重写Object类的finalize方法
    2. 永远不要主动调用某对象的finalize方法,该方法应交给垃圾回收机制调用
    3. jvm执行finalize方法可能使可恢复状态的对象重新变成可达状态
    4. jvm执行finalize方法出现异常,垃圾回收机制不会报异常
  2. 对象的软,弱,虚引用

6.11 使用jar文件

jar文件是一种压缩文件,jar与zip文件区别为多了一个META_INF/MANIFEST.MF清单文件,在CLASSPATH中添加jar所在的路径,则虚拟机可在内存中解压jar包,把该jar文件当成一个路径,在这个路径中查所需类或包层次对应的路径结构

  1. jar cvf jar包名 路径:将路径下所有内容打成jar包,并显示压缩过程
  2. jar xvf jar包名:将对应的jar包解压缩到当前文件夹,并显示解压缩过程
  3. 实际是否打jar包对程序运行并没有影响,例:将D:\test\wu下所有内容打成test.jar,此时将test.jar的路径D:\test\wu\test.jar添加到CLASSPATH中,程序可以正常运行,但即使不打jar包,直接将D:\test\wu这个路径添加到CLASSPATH中,程序一样可以运行
发布了32 篇原创文章 · 获赞 0 · 访问量 950

猜你喜欢

转载自blog.csdn.net/hanzong110/article/details/102468170