第5章 继承-Java核心技术·卷1

文章探讨了Java和C++在继承、构造器、多态、final关键字、抽象方法、访问控制、Object类、优先使用Objects.equals()方法、equals()和hashCode()方法、toString()方法、对象包装器、自动装箱、参数数量可变的方法、枚举类、反射以及阻止继承等方面的区别和特性。特别强调了Java中的动态绑定、多态性以及封装的重要性。
摘要由CSDN通过智能技术生成

Java与C++不同

  1. 在Java中,所有的继承都是公共继承,而没有C++中的私有继承和 保护继承。
  2. 在Java中使用关键字super调用超类的方法,而在C++中则采用超类名加::操作符的形式。例如,Manager类的getSalary方法要调用Employee::getSalary而不是super.getSalary().
  3. 在C++中,如果希望实现动态绑定,需要将成员函数声明为virtual。在Java中,动态绑定是默认的行为。如果不希望让一个方法是虚拟的,可以将它标记为final。
  4. 在C++中,一个类可以有多个超类。Java不支持多重继承,但可以实现多个接口。
  5. Java用abstract关键字表示抽象类和方法,C++用纯虚函数表示抽象方法。
  6. 在Java中,object是所有类的超类;在C++中没有所有类的根类,不过,每个指针都可以转换成void*指针。
  7. 在Java的老版本中,程序员使用Vector类实现动态数组。不过,ArrayList类更加高效,没有任何理由再使用Vector类。ArrayList类似于C++的vector模板。但是C++的vector模板重载了[]运算符。由于Java没有运算符重载,所以必须调用显式的方法(get、set)。此外,C++向量是按值拷贝。赋值操作a = b将会构造一个与b长度相同的新向量a,并将所有的元素由b拷贝到a,而在Java中,这条赋值语句的操作结果是让a和b引用同一个数组列表。

基本概念

继承:基于已有的类创建新的类。

构造器

  • 由于Manager类的构造器不能访问Employee类的私有字段,所以必须通过一个构造器来初
    始化这些私有字段。可以利用特殊的super语法调用这个构造器。使用super调用构造器的语
    句必须是子类构造器的第一条语句。
    ps:一个类的多个构造器之间,也可以互相调用,也必须是第一条语句。

  • 如果子类的构造器没有显式地调用超类的构造器,将自动地调用超类的无参数构造器。 如果超类没有无参数的构造器,并且在子类的构造器中又没有显式地调用超类的其他构造 器,Java编译器就会报告一个错误。

关键字【this】有两个含义:一是指示隐式参数的引用,二是调用该类的其他构造器。
类似地,【super】关键字也有两个含义:一是调用超类的方法,二是调用 超类的构造器。
在调用构造器的时候,this和super这两个关键字紧密相关。调用构造器的语句只能作为另一个构造器的**第一条语句**出现。构
造器参数可以传递给当前类(this)的另一个构造器,也可以传递给超类(super)的构造器。

多态

定义

  • 尽管这里将e声明为Employee类型,但实际上e既可以引用Employee类型的对象,也可以引用Manager类型的对象。
  • 当e引用Employee对象时,e.getSalary()调用的是Employee类中的getSalary()方法;当e引用Manager对象时,e.getSalary()调用的是Manager类中的getSalary方法。虚拟机知道e实际引用的对象类型,因此能够正确地调用相应的方法。
  • 一个对象变量可以指示多种实际类型的现象称为多态,在运行时能够自动地选择适当的方法,称为动态绑定
  • 在C++中,如果希望实现动态绑定,需要将成员函数声明为virtual。在Java中,动态绑定是默认的行为。如果不希望让一个方法是虚拟的,可以将它标记为final。
public class ManagerTest
{
   public static void main(String[] args)
   {
      // construct a Manager object
      var boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
      boss.setBonus(5000);

      var staff = new Employee[3];   

      // fill the staff array with Manager and Employee objects

      staff[0] = boss;  // staff[0]是employee类型,但指向了boss类型,而且可以调用boss的属性和方法
      staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
      staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);

      // print out information about all Employee objects
      for (Employee e : staff)
         System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());
   }
}

动态绑定有一个非常重要的特性:无须对现有的代码进行修改就可以对程序进行扩展。 假设增加一个新子类Executive,如果e恰好引用一个Executive类的对象,就会自动地调用 Executive.getSalary()方法。

超类变量可以引用所有的子类对象,但子类变量不能引用超类对象。

因为manager实际是一个Employee对象,无法调用Manager特有的方法。
在这里插入图片描述

为什么子类变量不能引用超类对象?

  • 当超类变量Employee引用子类对象Manager时,Employee只能调用Employee的方法,而这些方法的实现Manager一定有,只是可能Manager重写了Employee的方法。会带来多态和动态绑定。
  • 如果用子类变量指向超类对象时,子类变量manager会调用manager特有的方法如getBonus、setBonus,但超类对象实际并没有这两个方法。这是极大的问题。

子类引用的数组可以转换成超类引用的数组

在Java中,子类引用的数组可以转换成超类引用的数组,而不需要使用强制类型转换。因为子类Manager一定是一个超类Employee。注意此时:staff和managers引用的是同一个数组。
但会有奇怪的问题,staff[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1); // 编译器竟然接纳了这个赋值操作。当调用 managers[0].setBonus(1000)的时候,将会试图调用一个不存在的实例字段,进而搅乱相邻存储空间的内容。为了确保不发生这类破坏,所有数组都要牢记创建时的元素类型,并负责监督仅将类型兼容的引用存储到数组中。

  Employee[] staff = new Employee[3];    
  Manager[] managers=new Manager[3];   //子类引用的数组
  staff=managers;   // 子类引用的数组被转换成超类引用的数组
  staff[0] = new Employee("Harry Hacker", 50000, 1989, 10, 1);   // 编译器竟然接纳了这个赋值操作

覆写返回子类型

方法的名字和参数列表称为方法的签名。例如,f(int)和f(String)是两个有相同名字、不同签名的方法。
返回类型不是签名的一部分。两个方法不能名字和参数相同,但返回类型不同。但允许子类将覆盖方法的返回类型改为原返回类型的子类型。

public Employee getBuddy()    { . . . }
public Manager getBuddy()       { ... } // OK to change return type

强制类型转换

  • 只能在继承层次内进行强制类型转换。
  • 在将超类强制转换成子类之前,应该使用instanceof进行检查。

ps:超类变量引用子类对象,由于多态和自动绑定,不需要强制类型转换,直接可用。 staff[0] = boss;
只有当,用子类变量引用超类对象时,需要强制类型转换。但有个前提:这个超类对象实际引用的必须是对应的子类对象,否则会抛出ClassCastException异常。所以应该先用instanceof运算符进行检查。

在这里插入图片描述

instanceof是Java中的二元运算符,左边是对象,右边是类;当对象是右边类或子类所创建对象时,返回true;否则,返回false。如果左边对象为null,返回false,不抛异常。

 public static void main(String[] args)
   {
	// construct a Manager object
	      var boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
	      boss.setBonus(5000);

	      var staff = new Employee[3];   

	      // fill the staff array with Manager and Employee objects

	      staff[0] = boss;  // staff[0]是employee类型,但指向了boss类型,而且可以调用boss的属性和方法
	      staff[1] = new Employee("Harry Hacker", 50000, 1989, 10, 1);
	      staff[2] = new Employee("Tommy Tester", 40000, 1990, 3, 15);
	      if(staff[0] instanceof Manager)  // instanceof运算符
	      {
	    	  System.out.println("staff[0]可强制转换");
	    	  Manager manager=(Manager)staff[0];  //可行
	      }
	      else {
	    	  System.out.println("staff[0]不可强制转换");
		}
	      if(staff[1] instanceof Manager)
	      {
	    	  System.out.println("可强制转换");
	    	  Manager manager1=(Manager)staff[1];
	      }
	      else {
	    	  System.out.println("staff[1]不可强制转换");
		}
	      // print out information about all Employee objects
   }  

在这里插入图片描述

阻止继承:final类和方法

  • final类:阻止派生Executive类的子类,就可以在声明这个类的时候使用final修饰符。声明格式如下所示:
    如String类是final类,这意味着不允许任何人定义String的子类。
public final class Executive extends Manager { ... }
  • final方法:子类就不能覆盖这个方法。final类中所有方法自动变成final方法。
public class Employee
{
	public final String getName()
	{
		return name;
	}
}

final字段表示构造对象之后就不允许改变它们的值了。final类中所有方法自动变成final方法,final类中的字段不会自动变成final字段。

多态 vs final ?

多态的动态绑定会带来额外开销,特别是不被覆写的简单方法可以被内联进一步优化性能,所以以前final确实可以提高效率。
但是虚拟机中的即时编译器比传统编译器的处理能力强得多,可以准确地知道类之间的继承关系,并能够检测出是否有类确实覆盖了给定的方法,基本消除了性能问题。

abstract抽象方法

  • 抽象方法不需要实现。留给子类实现。public abstract String getDescription();
  • 包含一个或多个抽象方法的本身必须被声明为抽象的。
  • 除了抽象方法之外,抽象类还可以包含字段和具体方法。\
  • 即使不含抽象方法,也可以将类声明为抽象类。
  • 抽象类不能实例化。也就是说,如果将一个类声明为abstract,就不能创建这个类的对象Person p = new Person(“Vince Vu”) ; // error 。可以定义一个抽象类的对象变量,但是这样一个变量只能引用非抽象子类的对象。Person p = new Student("Vince Vu", "Economics");
public abstract class Person {
	private String name;

	public Person(String name) {
		this.name = name;
	}

	public abstract String getDescription();

	public String getName() {
		return name;
	}
}

在C++中,有一种抽象方法称为纯虚函数,要在末尾用=0标记.如果至少有一个纯虚函数,这个C++类就是抽象类。在C++中,没有提供表示抽象类的特殊关键字。例如:

class Person // C++
{
public:
virtual string getDescription() = 0;
}

访问控制修饰符

修饰符控制
private仅对本类可见
public对外部完全可见
public对外部完全可见
protected对本包所有类和所有子类可见
默认对本包可见

object所有类的超类

在Java中,只有基本类型不是对象,例如,数值、字符和布尔类型的值都不是对象。
所有的数组类型,不管是对象数组还是基本类型的数组都扩展了 Object类。

Employee[] staff = new Employee[10];
obj = staff; // OK
obj = new int[10]; // OK

在C++中没有所有类的根类,不过,每个指针都可以转换成void*指针。

优先使用对空安全的Objects.equals()、Objects.hashCode()方法。

为了防备可能为null的情况,需要使用Objects. equals方法。如果两个参数都为null, Objects.equals(a, b)调用将返回true ;如果其中一个参数为null,则返回false ;否则,如果两个参数都不为null,则调用a.equals(b)。

euqals()方法

举例覆写Employee的equals方法

Object类中实现的equals方法将确定两个对象引用是否相等。
判断两个对象内容相等,需要自己覆写equals方法。如下:
在这里插入图片描述

为了防备name或hireDay可能为null的情况,需要使用Objects. equals方法。如果两个参数都为null, Objects.equals(a, b)调用将返回true ;如果其中一个参数为null,则返回false ;否则,如果两个参数都不为null,则调用a.equals(b)。
Employee.equals方法的最后一条语句要改写为:

return Objects.equals(name, other.name)
&& salary == other.salary
&& Objects.equals(hireDay, other.hireDay);

子类equals方法

在子类中定义方法时,首先调用超类的equals()方法。如果检测失败,对象就不可能相等。如果超类中的字段都相等,就需要比较子类中的实例字段。

public class Manager extends Employee
{
	public boolean equals(Object otherObject)
	{
		if (!super.equals(otherObject)) return false;
		// super.equals checked that this and otherObject belong to the same class
		Manager other = (Manager) otherObject;
		return bonus = other.bonus;
	}
}

编写完美的equals方法:

在这里插入图片描述
在这里插入图片描述

多态参数类型不同的错误

在这里插入图片描述

hashcode()方法

equals与hashCode的定义必须相容

equals与hashCode的定义必须相容:如果x.equals(y)返回true,那么x.hashCode()就必须与y.hashCode()返回相同的值。例如,如果定义Employee.equals比较员工的ID,那么hashCode方法就需要散列ID,而不是员工的姓名或存储地址。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

objects.hash()

在这里插入图片描述

toString()方法

在这里插入图片描述

打印数组,调用Arrays.toString()。打印多维数组,调用Arrays.deepToString()。
Object类定义了 toString方法,可以打印对象的类名和散列码。例如,调用

System.out.println(System.out)
// 将生成以下输出:
// java.io.PrintStream@2f6684

对象包装器与自动装箱

  • 对象包装器:基本类型都有一个与之对应的类。

包装器类是不可变的(同String),即一旦构造了包装器,就不允许更改包装在其中的值。同时,包装器类还是final,因此不能派生它们的子类。

  • 尖括号中的类型参数不允许是基本类型, 要用包装器。

var list = new ArrayList <int>();
var list = new ArrayList<Integer>();

  • 自动装箱(如:int → Integer);自动拆箱(如:Integer → Integer)。

  • 包装器和基本类型不同:以下代码检测a和b的地址是否相同。因为a和b是对象。应该用equals方法。

Integer a = 1000;
Integer b = 1000;
if (a == b)...
  • 包装器类可以引用null,可能NullPointerException。
Integer n = null;
System.out.printin(2 * n); // throws NullPointerException
  • 在一个条件表达式中混合使用Integer和Double类型,Integer值就会拆箱,提升为double,再装箱为Double:
Integer n = 1;
Double x = 2.0;
System.out.printIn(true ? n : x); // prints 1.0

Integer的方法:static String toString(int i)、static int parseInt(String s)

参数数量可变的方法

这里的省略号…是Java代码的一部分,它表明这个方法可以接收任意数量的对象(除fmt参数之外)。

public class PrintStream
{
	public Printstream printf(String fmt, Object... args) { return formatffmt, args); }
}

在这里插入图片描述

枚举类

  1. 枚举类可以简单定义及使用。SMALL, MEDIUM, LARGE, EXTRALARGE表示Size类的四个对象实例
public class EnumTest {
	public static void main(String[] args) {
		Size small = Size.SMALL;
		System.out.println(small);
	}
}

enum Size{
	SMALL, MEDIUM, LARGE, EXTRALARGE
}
  1. 所有枚举类都继承了Enum类。
    在这里插入图片描述
    在这里插入图片描述

  2. 枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。

  3. 枚举类一旦被使用,就会用private构造函数构造所所有的实例。如图call了SMALL, MEDIUM, LARGE, EXTRALARGE。在SMALL(“S”)中,SMALL是枚举常量名,S表示枚举类的字段abbreviation。

import java.util.*;

/**
 * This program demonstrates enumerated types.
 * 
 * @version 1.0 2004-05-24
 * @author Cay Horstmann
 */
public class EnumTest {
	public static void main(String[] args) {
		var in = new Scanner(System.in);
		System.out.print("Enter a size: (SMALL, MEDIUM, LARGE, EXTRA_LARGE) ");
		String input = in.next().toUpperCase();
		Size size = Enum.valueOf(Size.class, input);
		System.out.println("size=" + size);
		System.out.println("abbreviation=" + size.getAbbreviation());
		if (size == Size.EXTRA_LARGE)
			System.out.println("Good job--you paid attention to the _.");

	}
}

enum Size {
	SMALL("S"), MEDIUM("M"), LARGE("L"), EXTRA_LARGE("XL");

	private Size(String abbreviation) {
		this.abbreviation = abbreviation;
		System.out.println("Size() is called for " + this);
	}

	public String getAbbreviation() {
		return abbreviation;
	}

	private String abbreviation;

	public String toString() {
		return super.toString() + "(\"" + this.abbreviation + "\")";

	}
}

在这里插入图片描述

  1. 枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。

反射

反射:能够分析类能力的程序称为反射。
反射机制可以用来:

  • 在运行时分析的能力。
  • 在运行时检查对象,例如,编写一个适用于所有类的toString方法。
  • 实现泛型数组操作代码。
  • 利用Method对象,调用任意方法和构造器,这个对象很像C++中的函数指针。

Class类

获得class类对象的方法:

  1. Object类中的getClass()方法将会返回一个Class类型的实例。getNanie()方法将返回类的名字。
Employee e;
Class cl = e.getClass();   
  1. 静态方法forName获得类名对应的Class对象
String className = "java.util.Random";
Class cl = Class.forName(className);
  1. 用.class产生匹配的类对象。
Class cl1 = Random.class; // if you import java.util.*;
Class cl2 = int.class;
Class cl3 = Double[].class;

请注意,一个Class对象实际上表示的是一个类型,这可能是类,也可能不是类。例如,int不是类,但int.class是一个Class类型的对象。

==比较两个类对象

if (e.getClass() == Employee.class)...

如果e是一个Employee实例,这个测试将通过。与条件e instanceof Employee不同,如果e
是某个子类(如Manager)的实例,这个测试将失败。

用class对象构造类实例。

var className = "java.util.Random"; // or any other name of a class with
		// a no-arg constructor
cl = Class.forName(className);
Object obj = cl.getConstructor().newInstance();

在这里插入图片描述

声明异常入门

如果一个方法包含一条可能抛出检查型异常的语句,则在方法名上增加一个throws子句。

public static void doSomethingWithClass(String name)
			throws ReflectiveOperationException
			{
			Class cl = Class.forName(name); // might throw exception
			System.out.println(cl.getName());
			//do something with cl
			}

调用这个方法的任何方法也都需要一个throws声明。这也包括main方法。如果一个异常确实出现,main方法将终止并提供一个堆栈轨迹。

资源

package corejava.v1ch05.resources;

import java.io.*;
import java.net.*;
import java.nio.charset.*;
import javax.swing.*;

/**
 * @version 1.5 2018-03-15
 * @author Cay Horstmann
 */
public class ResourceTest
{
   public static void main(String[] args) throws IOException
   {
      Class cl = ResourceTest.class;
      URL aboutURL = cl.getResource("about.gif");
      var icon = new ImageIcon(aboutURL);
      System.out.println(System.getProperty("user.dir"));
      InputStream stream = cl.getResourceAsStream("data/about.txt");  //相对路径
      var about = new String(stream.readAllBytes(), "UTF-8");
      InputStream stream2 = cl.getResourceAsStream("/corejava/v1ch05/resources/data/title.txt");   //绝对路径前缀和包名相同     
      var title = new String(stream2.readAllBytes(), StandardCharsets.UTF_8).trim();

      JOptionPane.showMessageDialog(null, about, title, JOptionPane.INFORMATION_MESSAGE, icon);
   }
}

利用反射分析类的能力

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个程序将提醒用户输入类名,然后输出类中所有的方法和构造器的签名,以及全部实例字段名。

package corejava.v1ch05.reflection;

import java.util.*;
import java.util.ArrayList;
import java.lang.reflect.*;

/**
 * This program uses reflection to print all features of a class.
 * @version 1.11 2018-03-16
 * @author Cay Horstmann
 */
public class ReflectionTest
{
   public static void main(String[] args)
         throws ReflectiveOperationException      
   {
      // read class name from command line args or user input
      String name;
      if (args.length > 0) name = args[0];
      else
      {
         var in = new Scanner(System.in);
         System.out.println("Enter class name (e.g. java.util.Date): ");
         name = in.next();
      }

      // print class name and superclass name (if != Object)
      Class cl = Class.forName(name);
      Class supercl = cl.getSuperclass();
      String modifiers = Modifier.toString(cl.getModifiers());
      if (modifiers.length() > 0) System.out.print(modifiers + " ");
      System.out.print("class " + name);
      if (supercl != null && supercl != Object.class) System.out.print(" extends "
            + supercl.getName());

      System.out.print("\n{\n");
      printConstructors(cl);
      System.out.println();
      printMethods(cl);
      System.out.println();
      printFields(cl);
      System.out.println("}");
   }

   /**
    * Prints all constructors of a class
    * @param cl a class
    */
   public static void printConstructors(Class cl)
   {
      Constructor[] constructors = cl.getDeclaredConstructors();

      for (Constructor c : constructors)
      {
         String name = c.getName();
         System.out.print("   ");
         String modifiers = Modifier.toString(c.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");         
         System.out.print(name + "(");

         // print parameter types
         Class[] paramTypes = c.getParameterTypes();
         for (int j = 0; j < paramTypes.length; j++)
         {
            if (j > 0) System.out.print(", ");
            System.out.print(paramTypes[j].getName());
         }
         System.out.println(");");
      }
   }

   /**
    * Prints all methods of a class
    * @param cl a class
    */
   public static void printMethods(Class cl)
   {
      Method[] methods = cl.getDeclaredMethods();

      for (Method m : methods)
      {
         Class retType = m.getReturnType();
         String name = m.getName();

         System.out.print("   ");
         // print modifiers, return type and method name
         String modifiers = Modifier.toString(m.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");         
         System.out.print(retType.getName() + " " + name + "(");

         // print parameter types
         Class[] paramTypes = m.getParameterTypes();
         for (int j = 0; j < paramTypes.length; j++)
         {
            if (j > 0) System.out.print(", ");
            System.out.print(paramTypes[j].getName());
         }
         System.out.println(");");
      }
   }

   /**
    * Prints all fields of a class
    * @param cl a class
    */
   public static void printFields(Class cl)
   {
      Field[] fields = cl.getDeclaredFields();

      for (Field f : fields)
      {
         Class type = f.getType();
         String name = f.getName();
         System.out.print("   ");
         String modifiers = Modifier.toString(f.getModifiers());
         if (modifiers.length() > 0) System.out.print(modifiers + " ");         
         System.out.println(type.getName() + " " + name + ";");
      }
   }
}
Enter class name (e.g. java.util.Date): 
java.util.ArrayList
public class java.util.ArrayList extends java.util.AbstractList
{
   public java.util.ArrayList(java.util.Collection);
   public java.util.ArrayList();
   public java.util.ArrayList(int);

   public boolean add(java.lang.Object);
   private void add(java.lang.Object, [Ljava.lang.Object;, int);
   public void add(int, java.lang.Object);
   public boolean remove(java.lang.Object);
   public java.lang.Object remove(int);
   public java.lang.Object get(int);
   public boolean equals(java.lang.Object);
   public int hashCode();
   public java.lang.Object clone();
   public int indexOf(java.lang.Object);
   public void clear();
   public int lastIndexOf(java.lang.Object);
   public boolean isEmpty();
   public void replaceAll(java.util.function.UnaryOperator);
   public int size();
   public java.util.List subList(int, int);
   public [Ljava.lang.Object; toArray([Ljava.lang.Object;);
   public [Ljava.lang.Object; toArray();
   public java.util.Iterator iterator();
   public boolean contains(java.lang.Object);
   public java.util.Spliterator spliterator();
   public boolean addAll(java.util.Collection);
   public boolean addAll(int, java.util.Collection);
   public java.lang.Object set(int, java.lang.Object);
   private void readObject(java.io.ObjectInputStream);
   private void writeObject(java.io.ObjectOutputStream);
   public void forEach(java.util.function.Consumer);
   public void ensureCapacity(int);
   public void trimToSize();
   java.lang.Object elementData(int);
   private [Ljava.lang.Object; grow(int);
   private [Ljava.lang.Object; grow();
   int indexOfRange(java.lang.Object, int, int);
   int lastIndexOfRange(java.lang.Object, int, int);
   private void rangeCheckForAdd(int);
   private void fastRemove([Ljava.lang.Object;, int);
   private boolean equalsArrayList(java.util.ArrayList);
   boolean equalsRange(java.util.List, int, int);
   private void checkForComodification(int);
   int hashCodeRange(int, int);
   private static java.lang.String outOfBoundsMsg(int, int);
   private java.lang.String outOfBoundsMsg(int);
   private void shiftTailOverGap([Ljava.lang.Object;, int, int);
   boolean batchRemove(java.util.Collection, boolean, int, int);
   static java.lang.Object elementAt([Ljava.lang.Object;, int);
   public boolean removeIf(java.util.function.Predicate);
   boolean removeIf(java.util.function.Predicate, int, int);
   private static [J nBits(int);
   private static void setBit([J, int);
   private static boolean isClear([J, int);
   private void replaceAllRange(java.util.function.UnaryOperator, int, int);
   public void sort(java.util.Comparator);
   protected void removeRange(int, int);
   public boolean removeAll(java.util.Collection);
   public boolean retainAll(java.util.Collection);
   public java.util.ListIterator listIterator();
   public java.util.ListIterator listIterator(int);
   void checkInvariants();

   private static final long serialVersionUID;
   private static final int DEFAULT_CAPACITY;
   private static final [Ljava.lang.Object; EMPTY_ELEMENTDATA;
   private static final [Ljava.lang.Object; DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
   transient [Ljava.lang.Object; elementData;
   private int size;
}

使用反射在运行时分析对象

利用反射机制可以查看在编译时还不知道的对象字段。

  public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException
   {
	   var harry = new Employee("Harry Hacker", 50000, 1989, 1, 19);
			   Class cl = harry.getClass();
			   // the class object representing Employee
			   Field f = cl.getDeclaredField("name");
			   // the name field of the Employee class
			   f.setAccessible(true); // now OK to call f.get(harry)
			   Object v = f.get(harry);
			   System.out.println(v);
			   // the value of the name field of the harry object, i.e.,
			   // the String object "Harry Hacker"
			   f.set(harry, "harry Junior");
			   System.out.println(harry.getName());

   }   

在这里插入图片描述
一个可用于任意类的通用toString方法(见程序清单5-15)。这个通用toString方法使用getDeclaredFileds获得所有的数据字段,然后使setAccessible便利方法将所有的字段设置为可访问的。对于每个字段,将获得名字和值。通过递归调用toString方法,将每个值转换成字符串。可用于

利用反射编写泛型数组

在这里插入图片描述

最关键的是Array类的静态方newlnstance,这个方法能够构造一个新数组。在调用这个方法时必须提供两个参数,一个是数组的元素类型,一个是数组的长度。

Object newArray=Array.newlnstance(componentType,newLength);

为了能够具体实现,需要获得新数组的长度和元素类型。
可以通过调用Array.getLength(a)获得数组的长度。Array类的静态getLength方法会返回一
个数组的长度。要获得新数组的元素类型,就需要完成以下工作:

  1. 首先获得a数组的类对象。
  2. 确认它确实是一个数组。
  3. 使用Class类的getComponentType方法(只为表示数组的类对象定义了这个方法)确定数组的正确类型。
 public static Object goodCopyOf(Object a, int newLength) 
   {
      Class cl = a.getClass();  // 虽然a是object类型,但实际指向子类型即一个数组
      if (!cl.isArray()) return null;
      Class componentType = cl.getComponentType();
      int length = Array.getLength(a); //不可能调用a.getLength,因为此时a为object类型
      Object newArray = Array.newInstance(componentType, newLength); // 虽然a是object类型,但实际指向子类型即一个数组
      System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength)); // System.arraycopy的前两个参数都是object类型 
      return newArray;
   }

在这里插入图片描述

Object[] ArrayList.toArray()源代码里创建的就是Object[],不能强转成T类型。
在这里插入图片描述

调用任意方法和构造器

在C和C++中,可以通过一个函数指针执行任意函数。从表面上看,Java没有提供方法指针,也就是说,Java没有提供途径将一个方法的存储地址传给另外一个方法,以便第二个方法以后调用。事实上,Java的设计者曾说过:方法指针是很危险的,而且很容易出错。他们认为Java的接口 (interf ace)和lambda表达式(将在下一章讨论)是一种更好的解决方案。不过,反射机制允许你调用任意的方法,Method类有一个invoke方法。
invoke的参数和返回值必须是Object类型。这就意味着必须来回进行多次强制类型转换。这样一来,编译器会丧失检査代码的机会,以至于等到测试阶段才会发现错误,而这个时候查找和修正错误会麻烦得多。不仅如此,使用反射获得方法指针的代码要比直接调 用方法的代码慢得多。
有鉴于此,建议仅在绝对必要的时候才在你自己的程序中使用Method对象。通常更好的做法是使用接口以及Java 8引人的lambda表达式(第6章中介绍)。特别要强调:我们建议Java开发者不要使用回调函数的Method对象。可以使用回调的接口,这样不仅代码的执行速度更快,也更易于维护。
在这里插入图片描述

import java.lang.reflect.*;

/**
 * This program shows how to invoke methods through reflection.
 * @version 1.2 2012-05-04
 * @author Cay Horstmann
 */
public class MethodTableTest
{
   public static void main(String[] args)
         throws ReflectiveOperationException
   {
      // get method pointers to the square and sqrt methods
      Method square = MethodTableTest.class.getMethod("square", double.class);
      Method sqrt = Math.class.getMethod("sqrt", double.class);

      // print tables of x- and y-values
      printTable(1, 10, 10, square);
      printTable(1, 10, 10, sqrt);
   }

   /**
    * Returns the square of a number
    * @param x a number
    * @return x squared
    */
   public static double square(double x)
   {
      return x * x;
   }

   /**
    * Prints a table with x- and y-values for a method
    * @param from the lower bound for the x-values
    * @param to the upper bound for the x-values
    * @param n the number of rows in the table
    * @param f a method with a double parameter and double return value
    */
   public static void printTable(double from, double to, int n, Method f)
         throws ReflectiveOperationException
   {
      // print out the method as table header
      System.out.println(f);

      double dx = (to - from) / (n - 1);

      for (double x = from; x <= to; x += dx)
      {
         double y = (Double) f.invoke(null, x);
         System.out.printf("%10.4f | %10.4f%n", x, y);
      }
   }
}

继承的设计技巧

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

问题:

Protected的访问权限

在Java中,保护字段只能由同一个包中的类访问。现在考虑一个Administrator子类,这个子类在另一个不同的包中。Administrator类中的方法只能查看Administrator对象自己的hireDay字段,而不能查看其他Employee对象的这个字段。有了这个限制,就能避免滥用保护机制,不能通过派生子类来访问受保护的字段。

package test1中的类Neighbor,可以访问同一个包中,Person的getMoney方法。
package test1;

public class Person {
	private String name;
	private int money;

	public Person(String name,int money) {
		// TODO Auto-generated constructor stub
		this.name=name;
		this.money=money;
	}

	public String getName() {
		return name;
	}

	protected int getMoney() {
		return money;
	}

}
package test1;

public class Neighbor {

	public Neighbor() {
		// TODO Auto-generated constructor stub
	}
	
	public static void main(String[] agrs)
	{
		Person person = new Person("A", 1000);
		System.out.println(person.getMoney());
	}

}

package test2中的类Son,可以访问不同包中,Person类的getMoney方法。但不能通过Person对象访问getMoney方法。

package test2;

import test1.Person;

public class Son extends Person {

    public Son(String name, int money) {
        // TODO Auto-generated constructor stub
        super(name, money);
    }

    public static void main(String[] args) {
        Son son = new Son("B", 20);
        System.out.println(son.getMoney());   //work
       /Person person = new Person("A", 1000);
        System.out.println(person.getMoney());  //error
        Person sperson = new Son("A", 1000);
        System.out.println(sperson.getMoney());  //error
    }
}

理解Son自己的getMoney方法

  • 在package test2中,新建一个类Nobody。Nobody和Son在同一个包中,但不能访问getMoney方法。因为此时访问的是父类Person的getMoney方法,不能在test1包外访问,子类也只能访问自己的getMoney方法。
package test2;

import test1.Person;

public class Nobody {

	public Nobody() {
		// TODO Auto-generated constructor stub
	}
	public static void main(String[] args) {
		Son son = new Son("B", 20);  
		System.out.println(son.getMoney());   //error,这里访问的是父类Person的getMoney方法,不能在这里访问
		Person person = new Person("A", 1000);  
		System.out.println(person.getMoney());  //error,这里访问的是父类Person的getMoney方法,不能在这里访问
		Son sperson = new Person("A", 1000);
		System.out.println(sperson.getMoney());  //error,这里访问的是父类Person的getMoney方法,不能在这里访问
	}

}
  • 如果给Son重写getMoney方法,在Nobody类中,就可以访问Son自己的getMoney方法。
package test2;
import test1.Person;

public class Son extends Person {

	public Son(String name, int money) {
		// TODO Auto-generated constructor stub
		super(name, money);
	}
	
	protected int getMoney() {  //重写
		return super.getMoney();
	}
	
}
package test2;

import test1.Person;

public class Nobody {

	public Nobody() {
		// TODO Auto-generated constructor stub
	}
	public static void main(String[] args) {
		Son son = new Son("B", 20);  
		System.out.println(son.getMoney());   //work,这里访问的是Son自己的getMoney方法,可被同一个包中的Nobody访问
		Person person = new Person("A", 1000);  
		System.out.println(person.getMoney());  //error,这里访问的是父类Person的getMoney方法,不能在这里访问
		Son sperson = new Person("A", 1000);
		System.out.println(sperson.getMoney());  //error,这里访问的是父类Person的getMoney方法,不能在这里访问
	}

}

private为什么能保护数据安全?即封装的优点

因为只能通过指定的方法如get、set访问,而set里会有相应的处理逻辑,防止任意改变private值,出现错误。例如set(pos, ‘a’)方法,会检查pos是否合法,如果直接操作数组,可能越界等错误。

如果将name、salary和hi reDay字段标记为公共,而不是编写单独的访问器方法,难道不是更容易一些吗?
不过,name是一个只读字段。一旦在构造器中设置,就没有任何办法可以对它进行修改, 这样我们可以确保name字段不会受到外界的破坏。
虽然salar y不是只读字段,但是它只能用rai seSalar y方法修改。特别是一旦这个值出现了错误,只需要调试这个方法就可以了。如果salary字段是公共的,破坏这个字段值的捣乱者有可能会出没在任何地方(那就很难调试了)。

封装理解

public、private、protected、default不是谁能访问,能不能访问的意思,而是在哪里可以写访问代码。
如果是private,访问的代码一定写在类里。如果是public,所有import了的位置,都可以写访问代码。而pulic的方法可以让private的属性在任意位置固定的方式被访问和修改。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值