继承 Inheritance

继承就是服用已存在的类,继承原有类的方法和域。并可以加一些新的方法或者覆盖原有的方法。

子类与超类

Java中类继承是单亲模型,即类只能继承一个类。

理论上加ManagerEmployee之间是is-a的关系,即每一个经理都是一个员工。

class Manager extends Employee{
}

​ java使用extends关键字来表示继承关系。已存在的类被称为超类,基类或父类,新类称之为子类,派生类或孩子类。Java程序员一般使用超类和子类术语表达这种关系。

覆盖方法 override

子类覆盖父类的方法,例如

public class Manager extends Employee
{
	public double getSalary()
	{
	
	}
}

子类不能直接访问父类中私有域和方法,访问父类中的方法要使用super关键字

    @Override
    public double getSalary() {
        return super.getSalary() ;
    }

子类构造器

可以调用super()来调用父类构造器,super初始化必须是子类构造器的第一条语句。

    public Manager(String name, double salary, int year, int month, int dayOfMonth) {
        super(name, salary, year, month, dayOfMonth);
    }

多态 polymorphism

继承是is-a的关系,每一个Manager都是Employee的子类,所以程序中任何出现超类的地方都可以用子类置换。

        Employee e = null;
        e = new Employee();
        e = new Manager();

我们称之为对象对变是多态的,即一个Employee变量既可以引用一个Employee对象,也可以引用一个Employee类的任何一个子类的对象。

当子类覆盖了父类方法时,会调用子类的覆盖后的方法。

方法调用

  1. 编译器查看对象的声明类型与方法名,假设调用x.f(param),且隐式参数x声明为C类的对象,如果有多个方法名为f的方法,编译器会一一列举C类及其超类中访问属性为public且名为f的方法。(私有方法不可访问) 编译器获得了所有可能调用的候选方法。

  2. 编译器查看调用方法的参数类型,在所有方法f中寻找一个参数类型完全匹配的方法,这个过程称之为重载解析overloading 例如调用x.f("Hello"),编译器会调用f(String)而不是f(int)由于可以类型转换int=>double,Manager=>Employee,这个过程会很复杂。

    子类覆盖父类方法时,允许返回类型为原类型的子类型。

    例如父类有

    public Employee getBuddy(){ . . . }

    子类可以覆盖为

    public Manager getBuddy(){}

  3. 如果是private``static``final``构造器方法,编译器可以准确的知道调用哪个方法,这种方式成为静态绑定static binding.于此对应,调用方法依赖隐式参数的实际类型,并在运行时动态绑定。

  4. 当程序运行,并且采用动态绑定调用方法时,虚拟机一定会调用与x所引用对象的实际类型最合适的那个类的方法。假设x的实际类型时DDC的子类,如果D定义了f(String),就直接调用它,否走将在C中寻找f(String),以此类推。

final 类和方法和域

不允许被继承的类称为final类,例如定一个了final

public final class FinalEmploee extends Employee {
}

类中的方法也可以声明final,怎么做后子类不能覆盖这个方法

final 类中所有方法会自动成为final方法

将域声明为final表示构造对象之后就不运行改变他们的值了。将类声明为final,只有其中的方法会自动成为final,不包括域。

域为final即只允许赋值一次。

    public final int a;

    public FinalEmploee() {
        a = 3;
    }

    public final int a = 3;

下面这种是错误的语法

  public final int a = 1;

    public FinalEmploee() {
        a = 3;
    }

强制类型转换

将一个类型强制转换称另一个类型的过程成为类型转换。

例如将double转换为int

double x = 3.1415;
int nx = (int) x;

转换后nx=3,转换丢弃了小数部分。

一个类对象的引用可以转换称其子类的对象引用。

Employee可以直接引用Manager

Employee e = new Manager();
Manager m = (Manager) e;

如果Employee不能转换为Manager,就会产生一个ClassCastException异常。

例如,会抛出一个ClassCastException异常

    Employee    e = new Employee();
    Manager m2 = (Manager) e;

一次在经行类型转换之前,先检查一下能否转换成功, 使用instanceof判断类型。

        if(e instanceof  Manager)
        {
            Manager m2 = (Manager) e;  
        }

只能在继承层次内进行类型转换

超类转换为子类前,应使用instanceof检查

抽象类

抽象方法

只定义不实现的方法,并且没有{}

  public abstract String getName();

使用abstract定义的对象,包含抽象方法的类必须被声明为抽象类。

public abstract class AbstractPerson {
    public abstract String getName();
}

抽象类中可以包含普通方法.

protected

protected 作用域可以声明域或方法只有包和子类可用。

Object 类

Object类是Java中类的祖先,如果没有明确指定超类,Object会被认为是这个类的超类。

可以用Object类型变量引用任何类型的对象

Obejct obj = new String("123")

在需要的时候可以将Object转换成原始类型

        Object object = new String("123");
        if(object instanceof  String)
        {
            String s = (String) object;
        }

Java中只有基本类型不是对象。

所有的数组类型(对象数组,基本类型数组)也都扩展了Object类。

以下语法是正确的

Object obj;
String[] ss = new String[10];
obj = ss;
obj = new int[10];

equals 方法

Object用来检测一个对象是否等于一个对象,Object中判断两个对象是否有相同的引用。

可以重载equals方法来实现自己的相等逻辑。

Object中的equals方法用来检测另一个对象是否等于当前对象。

Java 要求 equals方法有以下特性:

  1. 自反性 任何非空x x.equals(x) 返回 true
  2. 对称性 x.equals(y) == y.equals(x)
  3. 传递性 如果x.equals(y) y.equlas(z) 则 x.equals(z);
  4. 一致性 如果x,y引用对象无变化,反复调用x.equals(y)结果相同
  5. 非空引用x x.equals(null) 返回false

设计一个完美equals的步骤

  1. 显式参数命名为otherObject

  2. 检测thisotherObject是否引用同一个对象

    if (this == otherObject) return true;

  3. 检测otherObject是否为null,为null返回false

    if (null == otherObject) return true;

  4. 检测thisotherObject是否为同一个类,如果equals语义在之类中要有所改变,就使用getClass检测

            if(getClass() != otherObject.getClass())
            return false;
    

如果之类语义相同,使用instanceof检测

    //true
    new Person() instanceof Object
  ```
  if (!(otherObject instanceof ClassName))
  	return false;
  ```
    
   ```
    
   ```
  1. otherObject转换为需要的类型变量
CLassName other = (ClassName) otherObject;
  1. 进行域比较,使用==比较基本类型 , 使用equals比较对象域,数组可以使用Arrays.equlas比较,如果所有域都相等,返回true,否则返回false;

hashCode 方法

hashCode是由一个对象导出的一个int值,散列码是没有规律的。

Object默认的hashCode就是对象内存存储地址。

如果重新定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入到散列表中。

数组的hashCode可以使用Arrays.hashCode计算。

toString方法

类用来返回对象值的字符串。

Java重载了String类型的+操作,当一个字符串 +一个对象时,会调用该对象的toString()方法。

数组可以使用Arrays.toString()来展示。

clone

创建一个对象的副本。

泛型数组

使用底层数组必须在使用时确定数组的大小,使用ArrayList类可以不用关心底层细节,类会自动调节数组容量。

ArrayList是一个采用了类型参数的泛型类(generic class)。即可以指定数组列表保存的元素对象类型。

使用<>将类名包裹起来,例如ArrayList<String>

声明一个保存String对象的数组列表:

ArrayList<String> list = new ArrayList<String>();

Java SE 7后可以省略右边的类型参数

ArrayList<String> list = new ArrayList<>();

CRUD

添加

使用add方法将元素添加到数组列表。

list.add("123")

固定空间

如果可以估算出数组要存储的元素数量,可以使用

list.ensureCapacity(100);来分配数组空间,这样add100个元素就不会重新分配空间。

初始化时也可以传递初始容量

ArrayList<String> list = new ArrayList<>(100);

消减空间

trimToSize将数组列表的存储容量削减到当前尺寸。

获取长度

使用size方法获取数组列表的长度

添加元素

add(E obj)在列表尾端添加一个元素

add(int index, E element)在指定下标插入一个元素,原有下标及其后元素后移一位。

设置元素

``set(int index, E element)设置下标indexelement`元素。

获取元素

get(int index)获取元素。

拷贝到一个新数组

toArray方法

删除元素

remove(index)

遍历

可以使用for each循环遍历数组

for(String s: list)
{
	...
}

历史遗留代码

添加@SuppressWamings("unchecked")并使用强制类型转换。

@SuppressWarnings("unchecked") ArrayList<Employee> result =
(ArrayList<Employee>) employeeDB.find(query) ; // yields another warning

自动装箱

Java中基本类型都有一个与之对应的类,这些类被称为包装器(wrapper)。这些类分别为:Integer、Long、Float、Double、Short、Byte、Character 、Void 和Boolean ( 前 6 个类派生于公共的超类Number)。

例如要订一个整型数组列表,<>中类型参数不允许是基本类型,就必须这样写

List<Integer> list = new ArrayList<>();

由于每个值分别包装在对象中, 所以 ArrayList<lnteger> 的效率远远低于int[ ] 数 组。因此, 应该用它构造小型集合, 其原因是此时程序员操作的方便性要比执行效率更 加重要。

装箱

这个数组列表可以直接添加int元素到ArrayLis<lnteger>

list.add(3)

将自动转换为

list.add(Integer.valueOf(3));

这种变换称为自动装箱autoboxing

拆箱

与之相反,将Integer对象赋值给int值时,会自动拆箱

int n = list.get(i)

会转换为

int n = list.get(i).intValue()

普通算数表达式也能自动拆装箱。

        Integer n = 3;
        System.out.println(n);
        n++;
        System.out.println(n);

编译器将自动地插人一条对象拆箱的指令, 然后进行自增计算, 最后再将结果装箱。

自动装箱的==操作没有被重载,所以不能用于判断内部值是否相等,==只是比较对象是否执行通过同一个内存区域。

        Integer a = 200;
        Integer b = 200;
        System.out.println(a==b);//false

如果要比较包装类型内部值,要使用equals方法比较。

自动装箱规范要求boolean、byte、char 127, 介于-128 ~ 127 之间的short 和 int 被包装到固定的对象中。例如, 如果在前面的例子中将a 和b 初始化为100, 对它们 进行比较的结果一定成立。

另外, 如果在一个条件表达式中混合使用Integer 和Double 类型, Integer 值就会拆箱,提升为double, 再装箱为Double.

        Integer intA = 1;
        Double douD = 2.0;
        System.out.println(true?intA:douD);// 输出 1.0

装箱和拆箱是编译器认可的, 而不是虚拟机。编译器在生成类的字节码时, 插人必要的方法调用。虚拟机只是执行这些字节码。

修改数值参数

Java传参都是值传递,所以不可能编写一个修改整型参数x值的方法。

public static void triple(int x) // won't work
{
	x = 3 * x; // modifies local variable
}

使用Integer也不能改变,应该Integer对象是不可变的.

如果要修改数值参数值,可以使用org.omg.CORBA中的holder类型,包括IntHolder,BooleanHolder等。每种持有者都有一个公共值域,通过它修改存储其中的值。

    public static void testHolder(IntHolder intHolder){
        intHolder.value++;
    }

解析String

Integer有静态方法parseInt(String s)parseInt(String s,int radix)将字符串解析为int类型,radix是进制。

static Integer valueOf(String s) Static Integer value Of(String s, int radix)

是返回一个Integer对象。

可变参数

例如

    public PrintStream printf(String format, Object ... args) {
        return format(format, args);
    }

printf方法接受两个参数,一个是String类型,一个是Object[]数组,即Object ... args等于Object[] args.

当只传入一个Object[]数组时,该数组将重新定义为可变参数列表。

    public static void test(String s, Object... args) {
        System.out.println(args.length);
    }

    public static void main(String[] args) {
        TestArgs.test("1",new Object[]{"2","3"}); // args = ["2","3"]
                TestArgs.test("1",new Object[]{"2","3"},"4","5");//args = [["2","3"],"4","5"]
    }

枚举

定义一个枚举

public enum Size {
    SMALL, MEDIUM, LARGE, EXTRA_LARGE
}

实际上,枚举也是声明了一个类,刚好有4个实例。

因此在比较枚举类型值时,直接使用==就可以。

因为枚举也是类,所以可以在枚举类型添加构造器,方法和类.构造器只在构造枚举常量时被调用。


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

    private String abbreviation;

    private Size(String abbreviation) {
        this.abbreviation = abbreviation;
    }

    public String getAbbreviation() {
        return abbreviation;
    }

    public static void main(String[] args) {
        System.out.println(EXTRA_LARGE.getAbbreviation());
    }
}

枚举类的构造器只能为private。所有枚举类型都是Enum的子类,其中最有用的方法是toString,它返回枚举常量名。

例如, Size.SMALL.toString() 将返回字符串"SMALL"

toString的逆向方法为静态方法valueOf,他将字符串解析为枚举类型。

Enum.valueOf(Size.class,"SMALL")

枚举类也可直接调用

Size.valueOf("SMALL")

每个枚举类有一个静态的values方法,他返回给包含全部枚举值的数组。

ordinal()返回枚举常量位置,从0开始计数。

反射

赋予程序员动态操作Java代码的能力,此功能大量应用于框架。反射机制可以用来:

  • 在运行时分析类的能力。
  • 在运行时查看对象, 例如, 编写一个toString 方法供所有类使用。
  • 实现通用的数组操作代码。
  • 利用Method 对象, 类似于javascrpit中变量引用函数

Class类

程序运行时,Java 运行时系统为所有对象维护一个称之为运行时的类型标识。这个信心保存每个对象所属的类足迹,虚拟机利用运行时信息选择相应方法执行。

Java用Class类保存这些信息,Object类中的getClass()方法返回一个Class类型实例。

Class cl = new Object().getClass()

获取Class对象的方式

  • 类获取 通过 T.class 例如 String.class int[].class T可以时任意Java类型,包括私有类型。 例如String.class,int.class,int[].class

  • 对象获取 通过实例对象的getClass()方法获取Class对象

  • Class.forName(String) 通过字符串类名获取

Class.forName(String)

根据字符串生成类信息,例如JDBC驱动的注册采用此方式。

生成类信息时,会调用要生成类的静态初始化方法并且初始化静态域。

可能会抛出ClassNotFoundException异常

getName

调用Class对象的getName方法,返回这个类的名字.如果类在一个包里,包名也会作为类名的一部分。

    Class cl = new Object().getClass();
//java.lang.Object System.out.println(cl.getName());

通过Class对象生成实例

使用newInstance()方法生成实例

        Object object2 = (Object) cl.newInstance();
        System.out.println(object2);

这个方法可能会抛出 InstantiationException, IllegalAccessException异常

如果类的构造器有参数,就必须使用

Constructor类中的newInstance方法

通过getConstructor获取Constructor对象

Constructor Field Method 通用属性

getName返回名称

getModifiers返回一个整型值,不同位描述publicstatic这类修饰符。通过Modifier中的静态方法toString分析这个整型值,例如

Modifier.isFinal()

Modifier.isPublic()

Modifier.toString()

构造器和方法的getParameterTypes()返回参数Class[]数组,getReturnType返回返回类型的Class

使用setAccessible(true)可以覆盖Java访问控制机制,访问privatep rotected和默认域

Constructor

描述类的构造器

getConstructors返回所有public构造器

getDeclaredConstructors全部构造器(包括私有,默认,公有,保护,但不包括超类的构造器)

可以通过newInstance(Object[] args)创建一个对象实例

Field

描述类的域

getFields返回所有public

getDeclaredFields全部域(包括私有,默认,公有,保护,但不包括超类的成员)

getName返回域名称

getType返回域类型(Class对象)

get(Object)方法获取实例对象的值,null位类的静态域值。

set(obj,value)方法用来设置域值。

Method

描述域的方法

getMethods返回所有public方法

getDeclaredMethods全部方法(包括私有,默认,公有,保护,但不包括超类的方法)

getName返回域名称

数组反射

通过class.getComponentType获取数组类内部类型。

通过Array.newInstance(Class<?> componentType, int length)创建一个对象

Last Updated:
Contributors: himcs, himcs