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

news/2024/4/28 16:35:20/文章来源:https://blog.csdn.net/qq_42295590/article/details/130017520

文章目录

  • Java与C++不同
  • 基本概念
    • 继承:基于已有的类创建新的类。
    • 构造器
    • 多态
      • 定义
      • `超类变量可以引用所有的子类对象,但子类变量不能引用超类对象。`
      • 子类引用的数组可以转换成超类引用的数组
      • 覆写返回子类型
    • 强制类型转换
    • 阻止继承:final类和方法
    • 多态 vs final ?
    • abstract抽象方法
    • 访问控制修饰符
  • object所有类的超类
    • 优先使用对空安全的Objects.equals()、Objects.hashCode()方法。
    • euqals()方法
      • 举例覆写Employee的equals方法
      • 子类equals方法
      • 编写完美的equals方法:
      • 常见错误
    • hashcode()方法
      • equals与hashCode的定义必须相容
      • objects.hash()
    • toString()方法
  • 对象包装器与自动装箱
  • 参数数量可变的方法
  • 枚举类
  • 反射
    • Class类
      • 获得class类对象的方法:
      • ==比较两个类对象
      • 用class对象构造类实例。
    • 声明异常入门
    • 资源
    • 利用反射分析类的能力
    • 使用反射在运行时分析对象
    • 利用反射编写泛型数组
    • 调用任意方法和构造器
  • 继承的设计技巧
  • 问题:
    • Protected的访问权限
        • package test1中的类Neighbor,可以访问同一个包中,Person的getMoney方法。
      • package test2中的类Son,可以访问不同包中,自己继承的getMoney方法。但不能通过Person对象访问getMoney方法。
      • 理解Son自己的getMoney方法
    • private为什么能保护数据安全?即封装的优点
    • 封装理解

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 objectvar 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 objectsstaff[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 objectsfor (Employee e : staff)System.out.println("name=" + e.getName() + ",salary=" + e.getSalary());}
}

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

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

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

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

在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 objectvar 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 objectsstaff[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 classManager 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 exceptionSystem.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 inputString 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 typesClass[] 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 nameString modifiers = Modifier.toString(m.getModifiers());if (modifiers.length() > 0) System.out.print(modifiers + " ");         System.out.print(retType.getName() + " " + name + "(");// print parameter typesClass[] 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 EmployeeField f = cl.getDeclaredField("name");// the name field of the Employee classf.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();if (!cl.isArray()) return null;Class componentType = cl.getComponentType();int length = Array.getLength(a);Object newArray = Array.newInstance(componentType, newLength);System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength));return newArray;}

调用任意方法和构造器

在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 methodsMethod square = MethodTableTest.class.getMethod("square", double.class);Method sqrt = Math.class.getMethod("sqrt", double.class);// print tables of x- and y-valuesprintTable(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 headerSystem.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 stubthis.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,可以访问不同包中,自己继承的getMoney方法。但不能通过Person对象访问getMoney方法。

package test2;import test1.Person;public class Son extends Person {public Son(String name, int money) {// TODO Auto-generated constructor stubsuper(name, money);}public static void main(String[] args) {Son son = new Son("B", 20);System.out.println(son.getMoney());   //workPerson person = new Person("A", 1000);System.out.println(person.getMoney());  //errorSon sperson = new Person("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 stubsuper(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的属性在任意位置固定的方式被访问和修改。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_286181.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ROS学习-ROS简介

文章目录1.ROS1.1 ROS概念1.2 ROS特征1.3 ROS特点1.4 ROS版本1.5 ROS程序其他名词介绍1. 元操作系统2. IDL 接口定义语言一些网站1.ROS 1.1 ROS概念 ROS(Robot Operating System&#xff0c;机器人操作系统) ROS 是一个适用于机器人的开源的元操作系统&#xff0c;提供一系列…

linux驱动开发 - 04_Linux 设备树学习 - DTS语法

文章目录Linux 设备树学习 - DTS语法1 什么是设备树&#xff1f;2 DTS、DTB和DTC3 DTS 语法3.1 dtsi 头文件3.2 设备节点3.3 标准属性1、compatible 属性2、model 属性3、status 属性4、#address-cells 和#size-cells 属性5、reg 属性6、ranges 属性7、name 属性8、device_type…

人工智能专题-知识表示

文章目录人工智能专题-知识表示大纲2.1 知识表示的概念2.1.1 知识表示观点2.1.2 知识表示的要求2.2 一阶谓词逻辑表示法2.2.1 一阶谓词概念2.2.2 谓词逻辑表示方法2.3 产生式表示法2.4 语义网络表示法2.5 框架表示法人工智能专题-知识表示 大纲 大纲&#xff1a;掌握知识表示方…

思科路由器发现重大漏洞,解决方法是……

晚上好&#xff0c;我是老杨。 思科知名度高&#xff0c;待遇也好&#xff0c;很多网工心生向往&#xff0c;也有很多人考过思科认证的相关证书&#xff0c;对思科的印象还是不错吧&#xff1f; 而且&#xff0c;作为美国著名的网络设备厂商&#xff0c;思科是全球路由器巨头…

【面试】如何设计SaaS产品的数据权限?

文章目录前言数据权限是什么&#xff1f;设计原则整体方案RBAC模型怎么控制数据权限&#xff1f;1. 数据范围权限控制2. 业务对象操作权限控制3. 业务对象字段权限控制总结前言 一套系统的权限可以分为两类&#xff0c;数据权限和功能权限&#xff0c;今天我们从以下几个点&am…

【RabbitMQ】初识消息中间件MQ

目录 一、什么是MQ 二、MQ的优缺点 1、MQ的优点 1.应用解耦 2.削峰填谷 3.异步提速 2、MQ的缺点 1.可用性低 2.系统复杂度高 3.数据一致性问题 三、MQ使用场景 四、常见的MQ 一、什么是MQ MQ&#xff08;Message Queue&#xff09;&#xff1a;消息队列&#xff0c…

代码随想录_二叉树_leetcode654 617

leetcode654 最大二叉树 654. 最大二叉树 给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建: 创建一个根节点&#xff0c;其值为 nums 中的最大值。递归地在最大值 左边 的 子数组前缀上 构建左子树。递归地在最大值 右边 的 子数组后缀上 …

【FPGA实验4】举重比赛机制

举重比赛有三名裁判&#xff0c;当运动员将杠铃举起后&#xff0c;须有两名或两名以上裁判认可&#xff0c;方可判定试举成功&#xff0c;若用A、B、C分别代表三名裁判的意见输入&#xff0c;同意为1&#xff0c;否定为0;F为裁判结果输出&#xff0c;试举成功时F1&#xff0c;试…

iPhone如何不用iTunes将视频传输到电脑上?

随着智能手机的普及&#xff0c;iPhone已经成为了人们生活中必不可少的一部分。而随着iPhone摄像功能的逐渐完善&#xff0c;越来越多的用户开始将iPhone作为拍摄视频的工具。 但是&#xff0c;将iPhone中的视频传输到电脑并进行后续编辑处理或者备份储存&#xff0c;对于许多…

社科院与杜兰大学中外合作办学金融管理硕士项目——比起过往,前路更值得期待

当结束一天工作陷入沉思时&#xff0c;你有没有特别遗憾的事情呢&#xff0c;人生有太多的不确定性&#xff0c;比起过往&#xff0c;未知的人生更值得我们期待。与其懊恼没完成的遗憾&#xff0c;不如珍惜当下&#xff0c;努力创造未来。人生没有太晚的开始&#xff0c;在职读…

人工智能发展到GPT4经历了什么,从专家系统到机器学习再到深度学习,从大模型到现在的GPT4

大家好&#xff0c;我是微学AI&#xff0c;今天给大家讲一下人工智能的发展&#xff0c;从专家系统到机器学习再到深度学习&#xff0c;从大模型到现在的GPT4&#xff0c;讲这个的目的是让每个人都懂得人工智能&#xff0c;每个人都懂得人工智能的发展&#xff0c;未来人工智能…

openpnp - 顶部相机辅助光的选择

文章目录openpnp - 顶部相机辅助光的选择概述折腾的过程简易灯板市售的环形灯(不带漫射板)市售的环形灯(不带漫射板) LED单色光调光控制器.市售的环形灯(带漫射板)市售的环形灯(带漫射板) 自己拆解(降低LED灯路数)ENDopenpnp - 顶部相机辅助光的选择 概述 终于将顶部相机辅…

数值区间的模糊匹配,二分查找的应用

先看图: 需求很明确,要根据左边的值,显示右边的值。 比如,现在拿到的值是 17.12,那么应该显示成 15;拿到 17.599 ,那么应该显示成 20. 先找规律: 为了便于说明,暂且将左边的值设为 x, 右边的值设为 y. 第一行和最后一行可以写死成 0 与 1500;余下的每行,x 的区间是…

【华为机试真题详解JAVA实现】—学英语

目录 一、题目描述 二、解题代码 一、题目描述 Jessi初学英语,为了快速读出一串数字,编写程序将数字转换成英文: 具体规则如下: 1.在英语读法中三位数字看成一整体,后面再加一个计数单位。从最右边往左数,三位一单位,例如12,345 等 2.每三位数后记得带上计数单位 分别是…

四、数组、切片,映射

一、一维数组 //声明一个包含5个元素的整型数组 var array [5]int //具体数值填充数组 array : [5]int{1, 2, 3, 4, 5} //容量由初始化值的数量决定 array : [...]int{1, 2, 3, 4, 5) //只初始化索引为1和2的元素 array : [5]int{1: 10, 2: 20} //修改索引为2的元素的值 array…

Spring学习(五):一篇讲清楚动态代理(jdk和cglib)的使用、原理和源码

目录 一、jdk动态代理的基本使用 二、cglib动态代理的基本使用 2.1 方法一&#xff1a;method.invoke() 方法反射调用 2.2 方法二&#xff08;spring使用的这个方法&#xff09;&#xff1a; methodProxy.invoke() 2.3 方法三&#xff1a;methodProxy.invokeSuper() 三、…

ABP vNext电商项目落地实战(一)

一、落地条件&#xff1a; 1. .NET5版本 2. DDD 3. ABP vNext 4.ABP CLI &#xff08;ABP的命令行工具&#xff0c;包括ABP的各种模板&#xff09; 5.SQL Server 写在前面&#xff1a;我觉得这个框架的文件分层很凌乱&#xff0c;在企业的实际业务场景中&#xff0c;一般…

RHCSA练习作业(二)

目录 题目一 题目二 题目三 第四题 第五题 题目一 文件查看&#xff1a;查看/opt/passwd文件的第六行&#xff08;使用head和tail指令&#xff09; 代码如下&#xff1a; head -6 /opt/passwd | tail -1 题目二 在/etc及其子目录中&#xff0c;查找host开头的文件&#x…

虚拟机Ubuntu 18.04安装dpdk18.11

1 虚拟机用了2个核&#xff0c;2G内存&#xff0c;3个网卡 2 去http://core.dpdk.org/download/下载了18.11 3 安装apt-get install libnuma-dev 4 运行ifconfig down ***停掉2个网卡&#xff0c;方便后面添加到dpdk 5 解压后&#xff0c;进入usertools&#xff0c;运行dpd…

用AI帮我写一篇关于FPGA的文章,并推荐最热门的FPGA开源项目

FPGA定义 FPGA&#xff08;Field Programmable Gate Array&#xff09;是一种可编程逻辑器件&#xff0c;可以在硬件电路中实现各种不同的逻辑功能。与ASIC&#xff08;Application Specific Integrated Circuit&#xff0c;特定应用集成电路&#xff09;相比&#xff0c;FPGA…