《疯狂java讲义》第六章 面向对象(下)

第六章 面向对象(下)

abstract和interface分别定义抽象类和接口。抽象类主要作为多个类的模板,接口定义了多类应该遵守的规范。

6.1 增强的包装类

  • 自动装箱:把一个基本类型变量直接赋给对应的包装类变量;
  • 自动拆箱:包装类对象直接赋给一个对应的基本类型变量;
    《疯狂java讲义》第六章 面向对象(下)_第1张图片
Integer inObj = 5;//基本类型变量赋给Integer对象
object boolObj = ture;
int it = inObj;//Integer对象赋给int类型变量
  • 基本类型变量和字符串之间的转换:parseXxx(String s);valueOf(String s);
String intStr = "123";//将字符串“123”转换成int变量
int it1 = Integer.parseInt(intStr);
int it2 = Integer.valueOf(intStr);
//把float变量转换成String变量
String ftStr = String.valueOf(2.345f);
String intStr = 5 + "";//int变量转换为String变量
  • 包装类的实例可以与数值类型的值进行比较。
  • 支付无符号算术运算(略)

6.2 处理对象

  • 所有java对象都有toString()方法,返回“类名+@+hashCode”(重写toString()方法)
  • ==和equals方法在Object类中是完全一样的,在实际中常常重写equals()方法。
  • String重写了equals()方法,:两个字符串所包含的字符序列相同,返回true。
  • JVM常量池保证相同的字符串直接量只有一个,不会产生副本。

6.3 类成员

static修饰的成员就是类成员,包括类变量、类方法、静态初始化块。

  • 通过对象来访问类变量(方法)时,系统会在底层转换为通过该类来访问类变量。
  • 类成员不能访问实例变量。
  • 单例类:一个类始终只能创建一个实例,用private修饰隐藏构造器。
  • 根据良好封装原则:一旦把该类的构造器隐藏起立,就要提供一个public方法作为该类的访问点,用于创建该类的对象,且必须用static修饰(调用该方法的只能是类)。
class Singleton {
	private static Singleton instance;//使用类变量缓存曾创建的实例
	private Singleton(){};//对构造器隐藏
	private static Singleton getInstance(){//静态方法,返回Singleton实例
		if(instance == null)
			instance = new Singleton();//创建一个对象,并缓存
		return instance;
	}
}
public class SingletonTest{
	public static void main(Sitng[] args){
		Singleton s1 = Singleton.getInstance();//创建Singleton对象不能通过构造器
		Singleton s2 = Singleton.getInstance();//只能通过getInstance方法得到实例
		System.out.println(s1 == s2);//输出true
	}
}

6.4 final修饰符

  • final关键字可修饰类、变量、方法,表示类、方法、变量不可改变。
  • final修饰的成员变量必须由程序员显示的指定初始值。
  • 建议尽量避免在final变量显示初始化之前访问它。
  • final修饰基本类型变量时,不能对基本类型变量重新赋值;对引用类型变量,保存的仅仅是一个引用,final只保证引用的地址不变,但这个对象可以改变。
  • java编译器会将final变量当成“宏变量”处理。
  • 对于final实例变量,只有在定义该变量时指定初始值才会有“宏变量”的效果。
public class StringJoinTest{
	public static void main(String[] args){
		String s1 = "疯狂java";
		String s2 = "疯狂" + "java";
		System.out.println(s1 == s2);//true

		String c1 = "疯狂java";
		String c2 =  "java";
		String c3 = c1 + c2;
		System.out.println(c1 == c3);//flase
	}
}

编译器在编译阶段就确定s2为“疯狂java”,系统会让s2直接指向常量池中的缓存的“疯狂java”字符串,因此s1 == s2;对c3,值是由c1c2连接运算后得到的,c1c2只是普通变量,编译时不会执行“宏替换”,因此编译器无法在编译时确定c3 的值,c3无法指向“疯狂java”字符串,所以false;(将c1c2使用final修饰,输出true)。

  • final修饰的方法不可被重写,可以被重载。
  • final修饰的类不可以有子类。
  • 不可变类*:创建该类的实例后,该实例的实例变量是不可改变的。
  • 创建不可变类规则:使用private和final修饰;提供带参数构造器;仅提供getter方法;如果有必要,重写hashCode()和equals()方法。(注意引用类型的成员变量)
  • 缓存实例的不可变类,某个对象需要频繁的重复使用就缓存。(P185没太懂)

6.5 抽象类

6.5.1 抽象方法和抽象类

  • 使用abstract修饰符定义。
  • 规则:必须使用abstract修饰,抽象方法不能有方法体;抽象类不能被实例化;抽象类的构造器不能创建实例,主要用于被其子类调用;含有抽象方法的类只能被定义成抽象类。
  • 抽象类“有得有失”:“得”抽象类可以包含抽象方法;“失”抽象类不能用于创建实例。
  • 抽象方法和空方法体不是一个概念。
  • final修饰的类不能被继承,修饰的方法不能被重写。因此final和abstract永远不能同时使用。
  • abstract不能修饰成员变量,局部变量,构造器,(抽象类里定义的构造器只能是普通构造器)
  • static和abstract不能同时修饰某个方法,即没有所谓的类抽象方法。(但它们可以同时修饰内部类)

6.5.2 抽象类的作用

  • 抽象类是从多个具体类中抽象出来的父类吗,具有更高层次的抽象。(模板模式)
  • 模式一:父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现。
  • 模式二:父类的普通方法依赖于一个抽象方法,而抽象方法则推迟到子类中实现。
//定义一个抽象类
public abstract class Shape{
	private String color;
	//定义一个计算周长的抽象方法
	public abstract double calPerimeter();
	...
}

//定义Triangle类继承Shape抽象类
public class Triangle extends Shape{
	private double a,b,c;
	//重写Shape类的计算周长的抽象方法
	public double calPerimeter(){
		return a+b+c;
	}
}

6.6 接口

将这种抽象进行得更彻底,则可以提炼出一种更加特殊的“抽象类”——接口(interface)

6.6.1 接口的概念和定义

  • 接口体现的是规范现实分离的设计哲学。
  • 只有在相同包结构下才可以访问该接口。
  • 一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
  • 接口里定义的内部类、内部接口、内部枚举默认采用public static两个修饰符,不管定义时是否指定这两个修饰符,系统会自动使用public static对它们进行修饰。
  • 接口的默认方法其实就是实例方法。
  • 从某个角度来看,接口可被当成一个特殊的类。

6.6.3 接口的继承

  • 接口完全支持多继承。
interface InterfaceC extends InterfaceA, InterfaceB{...}
  • 接口的作用:定义变量,用于进行强制类型转换;调用接口中定义的常量;被其他类实现。
  • 继承使用extends关键字,实现使用implements关键字。
[修饰符] class 类名 extends 父类 implements 接口1,接口2..{
	...
}
  • 实现接口和继承父类相似,一样可以获得所实现接口里定义的常量、方法。
  • 一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(重写这些抽象方法);否则该类将保留从父接口继承到的抽象方法,该类也必须定义成抽象类。
  • 一个类实现某个接口时,该类将会获得接口中定义的常量、方法等,因此可以把实现接口理解为一种特殊的继承。

6.6.5 接口和抽象类

  • 相同点:都不能被实例化,都位于继承树的顶端,用于被其他类实现和继承;都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。
  • 接口:体现的是一种规范,类似于整个系统的“总纲”。
  • 抽象类:体现的是一种模板式的设计,作为系统中多个子类的共同父类。

6.6.6 面向接口编程

  • 设计模式:对经常出现的软件设计问题的成熟解决方案。(对特定问题的一种惯性思维,理解需以足够的代码量为基础)
  • 简单工厂模式:定义一个工厂类,他可以根据参数的不同返回不同类的实例,被创建的实例通常都具有共同的父类。(需要什么,只需要传入一个正确的参数,就可以获取所需要的对象,而无需知道其实现过程)
  • 命令模式:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志。以及支持可撤销的操作。(就是将一系列的请求命令封装起来,不直接调用真正执行者的方法,这样比较好扩展)

6.7 内部类

  • 定义在其他类内部的类就称为内部类(也叫嵌套类)
  • 作用:提供了更好的封装,不允许同一包中的其他类访问该类;内部类成员可以直接访问外部类的私有数据;匿名内部类适合用于创建那些仅需使用一次的类;
  • 内部类和外部类的区别:内部类多了三个修饰符:private、protected、static;非静态内部类不能拥有静态成员。

6.7.1 非静态内部类

  • 使用static修饰的成员内部类是静态内部类,没有使用的是非静态内部类。
  • 在外部类里使用非静态内部类时,与使用普通类并没有太大的区别。
  • 编译程序,生成两个class文件,Cow.class、Cow$CowLeg.class。
  • 使用this外部类名.this 来区分外部内部成员变量。
  • 如果外部类需要访问非静态内部类的成员,必须显式创建非静态内部类对象来调用访问其实例成员。
  • 当调用非静态内部类的实例方法时,必须有一个非静态内部类实例,非静态内部类实例必须寄生在外部类实例里。
  • 非静态内部类里不可以有今天初始化块,可以有普通初始化块。

6.7.2 静态内部类

  • 使用static修饰一个内部类,这个内部类就属于外部类本身(不属于外部类的某个对象)。因此使用static修饰的内部类被称为类内部类,也称为静态内部类。
  • 静态内部类对象寄生在外部类的类本身中(不是寄生在外部类的实例中)
public class StaticInnerClassTest{
	private int propl = 5;
	private static int prop2 = 9;
	static class StaticInnerClass{
		//静态内部类里可以包含静态成员
		private static int age;
		public void accessOuterProp(){
			//静态内部类无法访问外部类的**实例变量**
			System.out.println(propl);//×
			//静态内部类访问外部类的**静态成员**
			System.out.println(prop2);//√
		}
	}
}

public class AccessStaticInnerClass{
	static calss StaticInnerClass{
		private static int prop1 = 5;
		private int prop2 = 9;
	}
	public void accessInnerProp(){
		System.out.println(propl);//×
		//通过**类名**访问静态内部类的类成员
		System.out.println(StaticInnerClass.propl);//√
		
		System.out.println(prop2);//×
		//通过**实例**访问静态内部类的实例成员
		System.out.println(new StaticInnerClass().propl);//√
	}
}

6.7.3 使用内部类

  1. 在外部类内部使用内部类:与普通类没有太大区别(不要再外部类的静态成员中使用非静态内部类,以为静态成员不能访问非静态成员)
  2. 在外部类以外使用非静态内部类:内部类不能使用private访问控制权限;
    在外部类以外的地方定义内部类:OuterClass.InnerClass varName
    在外部类以外的地方创建非静态内部类实例:OuterINstance.new InConstructor()
  • 非静态内部类的构造器必须使用外部类对象来调用。
class Out{
	class In{//内部类
		public In(String msg){
			System.out.println(msg);
		}
	}
}
public class CreateInnerInstance{
	public static void main(String[] args){
		Out.In in = new Out().new In("测试信息");
		//上面代码等同于下面三句代码
		Out.In in ;//使用OutterClass.InnerClass形式定义内部类变量
		Out out = new Out();//创建外部类实例,非静态内部类实例将寄生在该实例中
		//通过**外部类实例**和**new**来调用内部类**构造器**创建非静态内部类实例
		in = out.new In("测试信息");
	}
}
public class SubClass extends Out.In{
	//显示定义SubClass的构造器
	public SubClass(Out out){
		//通过传入的Out对象显示调用In的构造器
		out.super("hello");
	}
}

非静态内部类In类的构造器必须使用外部对象来调用,代码中super代表调用In类的构造器,out代表外部类对象。

  • 非静态内部类的子类不一定是内部类,它可以是一个外部类。
  • 如果有一个内部类子类的对象存在,则一定存在与之对应的外部类对象。
  1. 在外部类以外使用静态内部类
  • 静态内部类是外部类类相关的,因此创建静态内部类对象时无需创建外部类对象。语法:new OuterClass.InnerConstructor()
class StaticOut{
	//定义一个静态内部类
	static class StaticIn{
		public StaticIn(){]
		System.out.println("静态内部类的构造器");
		}
	}
}
public class CreateStaticInnerInstance{
	public static void main(Sting[] args){
	StaticOut.StaticIn in = new StaticOut.StaticIn();
	//上面代码等于下面两行代码
	StaticOut.StaticIn in;//使用OuterClass.InnerClass的形式定义内部类变量
	in = new StaticOut.StaticIn();//通过new来调用内部类构造器创建静态内部类实例
	}

}
  • 相比之下,静态内部类比使用非静态内部类要简单很多,只要把外部类当成静态内部类的包空间即可。因此使用内部类时,应该优先考虑静态内部类。

6.7.4 局部内部类

  • 局部内部类:把一个内部类放在方法里定义,仅在该方法里有效,不能使用访问控制符和static修饰符修饰。
  • 局部内部类是一个非常“鸡肋”的语法,实际开发中很少定义局部内部类(因为作用域太小了,只能在当前方法中使用)

6.7.5 匿名内部类

  • 匿名内部类适合创建那种只需要一次使用的类。创建匿名内部类时会立即创建一个该类的实例,这个类定义立即消失,匿名内部类不能重复使用
  • 匿名内部类必须继承一个父类,或实现一个接口(最多一个);匿名内部类不能是抽象类;匿名内部类不能定义构造器,可以定义初始化块。
//定义一个Product接口
interface Product{
	public double getPrice();
	public string getName();
}
public class AnonymosTest{
	public void test(Product p){
		System.out.println("买了" + p.getName() + "花了" + p.getPrice());
	}
	public static void main(String[] args){
		AnonymousTest ta = new AnonymousTest();
		//调用test方法时,需传入一个Product参数,此处传入**匿名实现类的实例**
		ta.test( new Product(){
			public double getPrice(){
				return 567.8;	
			}
			public String getName(){
				return "AGP显卡";
			}
		} );
	}
}

上面程序中的test()方法需要一个Product对象作为参数,但Product只是一个接口,无法直接创建对象,因此考虑创建一个Product接口实现类的对象传入该方法:如果Product接口实现类需要重复,将该类定义成一个独立类;如果Product接口实现类只需一次使用,定义一个匿名内部类。

  • 匿名内部类:必须实现它的抽象父类或者接口里包含的所有抽象方法(有需要可以重写父类的方法)。
  • 也可以拆开来写,但显然匿名内部类的写法更加简洁。
  • java 8 之前被局部内部类、匿名内部类访问的局部变量必须使用final修饰,java 8 之后系统自动修饰(但赋值之后,以后不能重新赋值)

6.8 Lambda表达式

Lambda表达式支持将代码块作为方法参数,允许使用更简洁的代码来创建只有一个抽象方法的接口(被称为函数式接口)的实例。

6.8.1 Lambda表达式入门

  • Lambda表达式就相当于一个匿名方法。
  • 作用:代替匿名内部类的繁琐语法。
  • 部分:形参列表(允许省略形参类型,圆括号);箭头 -> ;代码块(可以省略花括号,return)
pa.process(target, new Command(){
	public void process(int[] target){
		int sum = 0;
		System.out.println(sum);
	}
});
//下式一样,**Lambda表达式**
pa.process(array, (int[] target) -> {
	int sum = 0;
	System.out.println(sum);
});
  • Lambda表达式实际上会被当成一个“任意类型”的对象。

6.8.2 Lambda表达式与函数式接口

  • Lambda表达式的类型(目标函数),必须是“函数式接口”(代表只含一个抽象方法的接口)
  • java 8 专门为函数式接口提供了@FunctionInterface注释。
  • 可以使用Lambda表达式进行赋值(Lambda表达式的结果就是被当成对象)。
  • Runnable是java本身提供的一个函数式接口。
  • Lambda表达式两个限制:目标类型必须是明确的函数式接口;只能为函数式接口创建对象。
  • 为了保证为明确的函数式接口,将Lambda表达式:赋值给函数式接口类型的变量;作为函数式接口类型的参数传给某个方法;进行强制类型转换。
  • Lambda表达式的目标函数类型完全可能是变化的,唯一要求:Lambda表达式实现的匿名方法与目标类型(函数式接口)中唯一的抽象方法有相同的形参列表。
Runnable r = () ->{
	for(int i = 0 ; i < 100; i++)
		System.out.println();
}
//强制类型转换
Object obj = (Runnable)() ->  {
	for(int i = 0 ; i < 100; i++)
		System.out.println();
}

6.8.3 方法引用与构造器引用(P218 不太懂)

Lambda表达式支持的方法引用和构造器引用
《疯狂java讲义》第六章 面向对象(下)_第2张图片

  1. 引用类方法
//函数式接口
interface Converter{
	Integer convert(String from);
}
//使用Lambda表达式创建一个Converter对象
Converter converter1 = from -> Interger.valueOf(from);
//调用converter1对象的convert()方法将字符串转换为整数
Integer val = converter1.convert("99");

//**等于上面两句**
//方法引用代替Lambda表达式:**引用类方法**
//函数式接口中被实现方法的全部参数传给该**类方法**作为参数
Converter converter1 = Integer :: valueOf;
  1. 引用特定对象的实例方法
//使用Lambda表达式创建一个Converter对象
Converter converter2 = from -> "fkit.org".indexOf(from);
//调用converter1对象的convert()方法将字符串转换为整数
Integer value = converter2.convert("it");

//**等于上面两句**
//方法引用代替Lambda表达式:**引用特定对象的实例方法**
//函数式接口中被实现方法的全部参数传给该**方法**作为参数
Converter converter2 = "fikt.org" :: valueOf;
  1. 引用某类对象的实例方法
//函数式接口
interface MyTest{
	String test(String a, int b, int c);
}
//使用Lambda表达式创建一个MyTest对象
MyTest mt = (a, b, c) -> a.substring(b, c);
//调用mt对象的test()方法
String str = mt.test("java i love you", 2, 9);

//**等于上面两句**
//方法引用代替Lambda表达式:**引用某类对象的实例方法**
//函数式接口中被实现方法的**第一个参数**作为调用者,后面参数全部传给该方法作为参数
MyTest mt = Strign :: substring;
  1. 引用构造器
//函数式接口
interface YourTest{
	JFrame win(String title)
}
//使用Lambda表达式创建一个YourTest对象
YourTest yt = (String a) -> JFrame(a);
//调用yt对象的win()方法
JFrame jf = yt.win("我的窗口");

//**等于上面两句**
//方法引用代替Lambda表达式:**引用构造器**
//函数式接口中被实现方法的全部参数传给该**构造器**作为参数
YourTest yt = JFrame :: new;

6.8.4 Lambda表达式与匿名内部类的联系和区别

  • Lambda表达式是匿名内部类的一种简化。
  • 相同点:都可以直接访问“effectively final”的局部变量,以及外部类的成员变量;都可以直接调用从接口中继承的默认方法。
  • 区别:匿名内部类可以为任何接口创建实例,Lambda表达式只能为函数式接口创建实例;匿名内部类可以为抽象类甚至不同类创建实例;匿名内部类允许调用接口中定义的默认方法。

6.8.5 使用Lambda表达式调用Arrays的类方法

Arrays类的有些方法需要Comparator、XXXOperator、XXXFunction等接口的实例,都是函数式接口,因此可以使用Lambda表达式来调用Arrays的类方法。

6.9 枚举类

6.9.2 枚举类入门

  • 关键字enum用于定义枚举类,默认继承了java.lang.Enum类(而不是Object类),因此枚举类不能显示继承其他父类。
  • 使用enum定义、非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。
  • 枚举类的构造去器只能使用private访问控制符(若省略,默认加)。
  • 枚举类的所有实例必须在枚举类的第一行显示列出(系统自动加public static final)。
  • 打印枚举类时,实际上输出的是该枚举值的toString()方法,输出枚举值的名字。

6.9.3 枚举类的成员变量、方法和构造器

枚举类也是一种类,只是它比较特殊的类

public enum Gender{
	MALE,FMALE;
	public String name;//public修饰的实例变量
}
public class GenderTest{
	public static void main (String[] args){
		//通过Enum的valueOf()方法获取指定枚举类的值
		Gender g = Enum.valueOf(Gendder.class, "FEMALE");
		//直接为枚举值的name实例变量赋值
		g.name = "女";
		//直接访问枚举值的name实例变量
		System.out.println(g + "代表" + g.name);
	}
}

public enum Gender{
	//此处的枚举值必须调用对应的构造器来创建
	MALE("男"),FEMALE("女");
	pivate final String name;
	//枚举类的构造器只能使用private修饰
	private Gender(String name){
		this.name = name;
	}
	public String getName(){
		return this.name;
	}
}

MALE("男");//等同于下面语句
public static final Gender MALE = new Gender("男");

6.9.4 实现接口的枚举类

  • 枚举类也可以实现一个或多个接口。枚举类实现接口与普通类实现接口完全一样。
  • 并不是所有的枚举类都使用了final修饰,非抽象的枚举类才默认使用final修饰。(它包含了抽象方法,就是抽象枚举类,系统默认使用abstract修饰)
public interface GenderDesc{
	void info();
}
public enum Gender implements GenderDesc{
	...
	public void info(){
		...//实现GenderDesc接口必须实现的方法
	}
}

6.9.5 包括抽象方法的枚举类

public enum Operation{
	PLUS{
		public double eval(double x, double y){
			return x + y;
		}
	},
	MINUS{
		public double eval(double x, double y){
			return x - y;
		}
	};
	//为枚举类定义一个抽象方法
	//这个抽象方法由不同的枚举值提供不同的实现
	public abstract double eval(double x , double y);
	plublic static void main(String[] args){
		System.out.println(Operation.PLUS.eval(3,4));
		System.out.println(Operation.MINUS.eval(5,4));
	}
}

6.10 对象与垃圾回收

垃圾回收特征:回收堆内存中的对象;在合适的时候进行;先调用它的finallize()方法,可能使该对象复活,导致回收机制取消回收。

  • 对象在堆内存中的状态:可达状态;可恢复状态;不可达状态。《疯狂java讲义》第六章 面向对象(下)_第3张图片

  • 只有当对象处于不可达状态时,系统才会真正回收该对象所占有的资源。

  • 强制垃圾回收:知识通知系统,是否回收依然不确定。

  • finalize()方法:java提供的默认机制来清理该对象的资源。

  • finalize()方法特点:永远不要主动调用某个对象的finalize()方法;何时调用,是否调用不确定性;JVM执行finalize()方法时,可能使对象重新变成可达状态;JVM执行finalize()方法时出现异常,不会报告,程序继续执行。

  • 强引用,软引用,弱引用,虚引用
    《疯狂java讲义》第六章 面向对象(下)_第4张图片
    《疯狂java讲义》第六章 面向对象(下)_第5张图片

  • java修饰符适用范围
    《疯狂java讲义》第六章 面向对象(下)_第6张图片

  • Jar文件的全称是Java Archive File,意思是Java档案文件。通常Jar文件是一种压缩文件,与我们常见的ZIP压缩文件兼容,通常也被称为jar包。当然也是有区别的,JAR包中有一个META-INF\MANIFEST.MF文件,当你找成JAR包时,它会自动生成。

  • 当开发了一个应用程序后,这个应用程序包含了很多类,如果需要把这个应用程序提供给别人使用,通常会将这些类文件打包成一个JAR文件,把这个JAR文件提供给别人使用。只要别人在系统的CLASSPATH环境变量中添加这个JAR文件,则Java虚拟机就可以自动在内存中解压这个JAR包,把这个JAR文件当做一个路径,在这个路径中查找所需要的类或包层次对应的路径结构。

  • JAR命令(简略举例):以文件test创建test.jar

jar cf test.jar test
查看jar文件列表

jar tf test.jar
向test.jar中添加或更新文件

jar vuf test.jar a.txt 
将命令执行的过程输出输出到文件a.txt中

jar vtf test.jar > a.txt
将jar中的文件解出到当前目录下

jar xf test.jar a.txt

你可能感兴趣的:(java基础,1024程序员节,java,后端)