cuiter  

二、面向对象下

1、自动装箱

public static void main(String[] args)
{
   var a = Integer.valueOf(6);
   // 输出true
   System.out.println("6的包装类实例是否大于5.0" + (a > 5.0));
   System.out.println("比较2个包装类的实例是否相等:"
      + (Integer.valueOf(2) == Integer.valueOf(2))); // 输出false
   // 通过自动装箱,允许把基本类型值赋值给包装类的实例
   Integer ina = 2;
   Integer inb = 2;
   System.out.println("两个2自动装箱后是否相等:"
      + (ina == inb)); // 输出true
   Integer biga = 128;
   Integer bigb = 128;
   System.out.println("两个128自动装箱后是否相等:"
      + (biga == bigb)); // 输出false
}

同样是两个int类型的数值自动装箱成Integer实例后,为什么2装箱后相等,而128装箱后不等?

// 定义一个长度为256的Integer数组
static final Integer[] cache = new Integer[-(-128) + 127 + 1];
static {
    //执行初始化,创建-128到127的Integer实例,并放入cache数组中
    for (int i = 0; i < cache.length; i++) {
        cache[i] = new Integer(i - 128);
    }
}

根据java.lang.Integer类源码解析,系统把一个-128~127之间的整数自动装箱成Integer实例,并缓存在cache数组中;如果以后将一个-128 ~ 127之间的数封装成Integer实例,直接指向对应的数组元素,2装箱后相等;但每次把一个不在-128 ~ 127范围内的整数自动装箱成Integer实例,系统会重新创建一个Integer实例,所以128装箱后不等。

2、==、object的equals()是进行栈值比较,不可用于比较没有父子关系的两个对象,而equals()一般都会重写。

3、当实例访问类成员时,实际上是委托类来访问类成员,因此即使某个实例为null也可以访问它所属的类成员。

4、一个只能创建一个实例的类叫做单例类。

class Singleton
{
	// 使用一个类变量来缓存曾经创建的实例
	private static Singleton instance;
	// 将构造器使用private修饰,隐藏该构造器
	private Singleton(){}
	// 提供一个静态方法,用于返回Singleton实例
	// 该方法可以加入自定义的控制,保证只产生一个Singleton对象
	public static Singleton getInstance()
	{
		// 如果instance为null,表明还不曾创建Singleton对象
		// 如果instance不为null,则表明已经创建了Singleton对象,
		// 将不会重新创建新的实例
		if (instance == null)
		{
			// 创建一个Singleton对象,并将其缓存起来
			instance = new Singleton();
		}
		return instance;
	}
}
public class SingletonTest
{
	public static void main(String[] args)
	{
		// 创建Singleton对象不能通过构造器,
		// 只能通过getInstance方法来得到实例
		Singleton s1 = Singleton.getInstance();
		Singleton s2 = Singleton.getInstance();
		System.out.println(s1 == s2); // 将输出true
	}
}

5、final修饰的成员变量必须由程序员显式指定初始值。

​ 类变量:声明该类变量时、静态初始化块中 指定初始值

​ 实例变量:声明该实例变量时、初始化块中、构造器中 指定初始值

6、final成员变量在显示初始化前不能直接访问,但可以通过方法访问,基本上是Java设计的一个漏洞,使用final成员变量时尽可能避免。

public class FinalErrorTest
{
	// 定义一个final修饰的实例变量
	// 系统不会对final成员变量进行默认初始化
	final int age;
	{
		// age没有初始化,所以此处代码将引起错误。
//		System.out.println(age);
		printAge();//0
		age = 6;
		System.out.println(age);//6
	}
	public void printAge(){
		System.out.println(age);
	}
	public static void main(String[] args)
	{
		new FinalErrorTest();
	}
}

7、final修饰引用类型变量时,引用地址不能改变,但对象内容可以改变。

8、final修饰、声明变量时指定了初始值、初始值在编译时就能够确定,那么该变量本质就是一个“宏变量”,相当于一个直接量。

	public static void main(String[] args)
	{
		// 下面定义了4个final“宏变量”
		final var a = 5 + 2;
		final var b = 1.2 / 3;
		final var str = "疯狂" + "Java";
		final var book = "疯狂Java讲义:" + 99.0;
		// 下面的book2变量的值因为调用了方法,所以无法在编译时被确定下来
		final var book2 = "疯狂Java讲义:" + String.valueOf(99.0);  //
		System.out.println(book == "疯狂Java讲义:99.0");//true
		System.out.println(book2 == "疯狂Java讲义:99.0");//false
	}

9、创建该类实例后,该实例的实例变量是不可改变的就是不可变类。自定义不可变类方法如下。

​ a、使用private和final修饰该类成员变量

​ b、提供带参数的构造器

​ c、仅为该类成员变量提供getter方法,不提供setter方法

​ d、有必要的话,重写hashCode()和equals()方法

10、不可变类的成员变类含有引用类型,若不处理,那么这个不可变类就是失败的。可以利用缓存实例的方式解决,如下:

class CacheImmutale
{
	private static int MAX_SIZE = 10;
	// 使用数组来缓存已有的实例
	private static CacheImmutale[] cache
		= new CacheImmutale[MAX_SIZE];
	// 记录缓存实例在缓存中的位置,cache[pos-1]是最新缓存的实例
	private static int pos = 0;
	private final String name;
	private CacheImmutale(String name)
	{
		this.name = name;
	}
	public String getName()
	{
		return name;
	}
	public static CacheImmutale valueOf(String name)
	{
		// 遍历已缓存的对象,
		for (var i = 0; i < MAX_SIZE; i++)
		{
			// 如果已有相同实例,直接返回该缓存的实例
			if (cache[i] != null
				&& cache[i].getName().equals(name))
			{
				return cache[i];
			}
		}
		// 如果缓存池已满
		if (pos == MAX_SIZE)
		{
			// 把缓存的第一个对象覆盖,即把刚刚生成的对象放在缓存池的最开始位置。
			cache[0] = new CacheImmutale(name);
			// 把pos设为1
			pos = 1;
		}
		else
		{
			// 把新创建的对象缓存起来,pos加1
			cache[pos++] = new CacheImmutale(name);
		}
		return cache[pos - 1];

	}
	public boolean equals(Object obj)
	{
		if (this == obj)
		{
			return true;
		}
		if (obj != null && obj.getClass() == CacheImmutale.class)
		{
			var ci = (CacheImmutale) obj;
			return name.equals(ci.getName());
		}
		return false;
	}
	public int hashCode()
	{
		return name.hashCode();
	}
}
public class CacheImmutaleTest
{
	public static void main(String[] args)
	{
		var c1 = CacheImmutale.valueOf("hello");
		var c2 = CacheImmutale.valueOf("hello");
		// 下面代码将输出true
		System.out.println(c1 == c2);
	}
}

11、抽象类的构造器不能用于创建实例,主要是用于被其子类调用。

12、含有抽象方法的类只能被定义为抽象类。

​ a、直接定义了抽象方法。

​ b、继承了抽象类,抽象方法没有被完全实现。

​ c、实现接口,没有完全实现接口的抽象方法。

13、抽象类可以不包含抽象方法,但是含有抽象方法的类必须定义为抽象类。

14、final不能和abstract一起使用。

​ 理由:final修饰的方法不能够重写,abstract修饰的方法必须被子类实现;final修饰的类不能被继承,abstract修饰的类只能被继承。

15、static和abstract不能同时修饰一个方法。

​ 理由:static修饰的方法由该类调用,而abstract修饰的方法没有方法体,所以调用会出错。

16、static和abstract不是绝对互斥的,可以同时修饰内部类。

17、private和abstract不能同时修饰方法。

​ 理由:private修饰的方法不能被子类访问,abstract修饰的方法又需要被重写。

18、抽象类体现的是一种模板模式的设计。

public abstract class SpeedMeter
{
   // 转速
   private double turnRate;
   public SpeedMeter(){}
   // 把计算车轮周长的方法定义成抽象方法
   public abstract double calGirth();
   public void setTurnRate(double turnRate)
   {
      this.turnRate = turnRate;
   }
   // 定义计算速度的通用算法
   public double getSpeed()
   {
      // 速度等于 周长 * 转速
      return calGirth() * turnRate;
   }
}
public class CarSpeedMeter extends SpeedMeter
{
	private double radius;
	public CarSpeedMeter(double radius)
	{
		this.radius = radius;
	}
	public double calGirth()
	{
		return radius * 2 * Math.PI;
	}
	public static void main(String[] args)
	{
		var csm = new CarSpeedMeter(0.34);
		csm.setTurnRate(15);
		System.out.println(csm.getSpeed());
	}
}

19、接口可以继承多个接口,不能继承类。

20、接口只能包含成员变量(public static final)、抽象实例方法(public abstract)、类方法(必须有方法体)、默认方法(必须有方法体,必须使用default修饰,不能用static修饰,总是public修饰的)、私有方法(必须有方法体)、内部接口、枚举类。

public interface Output
{
   // 接口里定义的成员变量只能是常量
   int MAX_CACHE_LINE = 50;
   // 接口里定义的普通方法只能是public的抽象方法
   void out();
   void getData(String msg);
   // 在接口中定义默认方法,需要使用default修饰
   default void print(String... msgs)
   {
      for (var msg : msgs)
      {
         System.out.println(msg);
      }
   }
   // 在接口中定义默认方法,需要使用default修饰
   default void test()
   {
      System.out.println("默认的test()方法");
   }
   // 在接口中定义类方法,需要使用static修饰
   static String staticTest()
   {
      return "接口里的类方法";
   }
   // 定义私有方法
   private void foo()
   {
      System.out.println("foo私有方法");
   }
   // 定义私有静态方法
   private static void bar()
   {
      System.out.println("bar私有静态方法");
   }
}

21、实现接口方法时必须使用public访问控制修饰符。

​ 理由:(实现相当于一种重写)重写方法时修饰符只能相等或者更大。

23、接口和抽象类的相似性:

  1. 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
  2. 接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。

24、接口与抽象类的区别:

  1. 接口里只能包含抽象方法、静态方法、默认方法和私有方法,不能为普通方法提供方法实现;抽象类则完全可以包含普通方法。
  2. 接口里只能定义静态常量,不能定义普通成员变量;抽象类里则既可以定义普通成员变量,也可以定义静态常量。
  3. 接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
  4. 接口里不能包含初始化块;但抽象类则完全可以包含初始化块。
  5. 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足。

25、非静态内部类不能拥有静态成员。

26、外部类成员变量、内部类成员变量与内部类里方法的局部变量同名,则可以通过使用this、外部类类名.this作为限定来区分。

public class DiscernVariable
{
	private String prop = "外部类的实例变量";
	private class InClass
	{
		private String prop = "内部类的实例变量";
		public void info()
		{
			var prop = "局部变量";
			// 通过 外部类类名.this.varName 访问外部类实例变量
			System.out.println("外部类的实例变量值:"
				+ DiscernVariable.this.prop);
			// 通过 this.varName 访问内部类实例的变量
			System.out.println("内部类的实例变量值:" + this.prop);
			// 直接访问局部变量
			System.out.println("局部变量的值:" + prop);
		}
	}
	public void test()
	{
		var in = new InClass();
		in.info();
	}
	public static void main(String[] args)
	{
		new DiscernVariable().test();
	}
}

27、接口中的内部类只能是public static修饰的,也就是说接口中的内部类只能是静态内部类。

28、外部类以外使用非静态内部类——outerInstance.new InnerConstructor();

29、外部类以外使用静态内部类——new outerClass.InnerConstructor();

30、既然内部类是外部类的成员,那么是否可以为外部类定义子类,在子类中在定义一个内部类来重写其父类的内部类呢?

​ 解答:不能。内部类的类名不再是简单的由内部类类名组成,而是有外部类类名作为命名空间限制的。因此子类的内部类与父类的内部类不能完全同名,所以不能重写。

31、匿名内部类格式:

new 实现接口() | 父类构造器(实参列表)
{
	//匿名内部类类体
}
//规则:
//	匿名内部类必须继承一个父类,或实现一个接口,但最多只能继承一个父类或实现一个接口。
//	匿名内部类不能是抽象类:因为系统在创建匿名内部类时,会立即创建其对象;而且必须实现父类或接口中所有的抽象方法。
//	匿名内部类不能定义构造器:因为没有类名,无法定义构造器;可以定义初始化块完成构造器要做的事。

32、局部变量被匿名内部类访问,那么该局部变量自动被final修饰。

interface A
{
	void test();
}
public class ATest
{
	public static void main(String[] args)
	{
		int age = 8;     //
		// 下面代码将会导致编译错误
		// 由于age局部变量被匿名内部类访问了,因此age相当于被final修饰了
//		age = 2;
		var a = new A()
		{
			public void test()
			{
				// 在Java 8以前下面语句将提示错误:age必须使用final修饰
				// 从Java 8开始,匿名内部类、局部内部类允许访问非final的局部变量
				System.out.println(age);
			}
		};
		a.test();
	}
}

33、Lambda表达式是用来创建函数式接口(只有一个抽象方法的接口)的实例。

  • Lambda表达式的目标类型必须是明确的函数式接口。
  • Lambda表达式只能为函数式接口创建对象。
  • Lambda表达式目标类型变化的唯一要求:实现的匿名方法与目标类型的唯一抽象方法有相同的形参列表。

34、枚举类与普通类的区别:

  • 使用enum定义的枚举类默认继承的是java.lang.Enum类,因此枚举类不能显式继承其他父类;java.lang.Enum实现了java.lang.Serializable和java.lang.Comparable两个接口。
  • 使用enum定义、非抽象的枚举类默认会使用final修饰;若是含有抽象方法,默认会使用abstract修饰。
  • 枚举类的构造器只能使用private修饰符。
  • 枚举类所有实例必须在第一行显式列出。
  • 枚举类提供了values()方法,该方法可以很方便的遍历所有的枚举值。
public enum Gender
{
   // 此处的枚举值必须调用对应构造器来创建
   MALE("男"), FEMALE("女");
   private final String name;
   // 枚举类的构造器只能使用private修饰
   private Gender(String name)
   {
      this.name = name;
   }
   public String getName()
   {
      return this.name;
   }
}

35、实现接口的枚举类:

public interface GenderDesc
{
   void info();
}

public enum Gender implements GenderDesc
{
//	// 此处的枚举值必须调用对应构造器来创建
//	MALE("男"), FEMALE("女");
	// 此处的枚举值必须调用对应构造器来创建
	MALE("男")
	// 花括号部分实际上是一个类体部分
	{
		public void info()
		{
			System.out.println("这个枚举值代表男性");
		}
	},
	FEMALE("女")
	{
		public void info()
		{
			System.out.println("这个枚举值代表女性");
		}
	};
	// 其他部分与上面的Gender类完全相同
	private final String name;
	// 枚举类的构造器只能使用private修饰
	private Gender(String name)
	{
		this.name = name;
	}
	public String getName()
	{
		return this.name;
	}
}

36、包含抽象方法的枚举类:

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;
      }
   },
   TIMES
   {
      public double eval(double x, double y)
      {
         return x * y;
      }
   },
   DIVIDE
   {
      public double eval(double x, double y)
      {
         return x / y;
      }
   };
   // 为枚举类定义一个抽象方法
   // 这个抽象方法由不同的枚举值提供不同的实现
   public abstract double eval(double x, double y);
   public static void main(String[] args)
   {
      System.out.println(Operation.PLUS.eval(3, 4));
      System.out.println(Operation.MINUS.eval(5, 4));
      System.out.println(Operation.TIMES.eval(5, 4));
      System.out.println(Operation.DIVIDE.eval(5, 4));
   }
}
posted on 2021-03-31 15:45  jiaotong  阅读(50)  评论(0编辑  收藏  举报