一、多态
1、多态:某一类事物的多种存在形态,即 一个对象,两(多)种形态
2、多态在代码中的体现:父类或者接口的引用指向其子类对象
3、多态的好处:提高了代码的扩展性,前期定义的代码可以使用后期的内容
4、多态的弊端:前期定义的内容不能使用后期子类的特有内容
5、要使用子类特有功能,还得用子类类型为主去调用特有功能。但如果直接使用子类对象,扩展性不强
6、多态的前提
(1)必须有关系:继承或实现
(2)要有覆盖(父类定义了功能,子类对功能进行实现)
7、向上转型(自动完成):限定子类特有功能的使用,提高扩展性(向上转型:父类的引用指向子类对象。隐藏子类型,统一操作父类)
8、向下转型:使用子类的特有功能
9、对于转型(也叫造型),无论是向上转型还是向下转型,自始至终都是子类对象在做着类型的变化(本类型-->父类型-->本类型)
注意:转型时容易引发一个问题:ClassCastException类型转换异常
10、instanceof:用于判断对象的具体类型,其后可以是类或接口,但必须是具体类型(否则判断无意义)。只能用于引用数据类型的判断。通常在向下转型前用于健壮性判断(因为父类型一出现,它的子类型就不唯一了)
11、如果使用子类的特有方法,意味着要强制转换(向下转型)。但向下转型时容易失败,所以在转型之前通常都会加入逻辑判断(if xxx instanceof xxx子类),为了增强代码的健壮性。如果类型不匹配,可以考虑抛出异常
12、多态时,成员的特点(编译都看左边)
(1)成员变量:编译和运行都看左边(覆盖只发生在方法上,变量没有覆盖)
(2)成员函数(非静态):编译看左边,运行看右边((子类方法覆盖父类方法)非静态方法依赖对象,必须动态绑定到指定对象上--this)
(3)静态函数:编译和运行都看左边(静态方法一进方法区,就有自己的所属,属于该方法所在的类--绑定到类上)
注:对于静态方法,不涉及多态性(对象的多态性)。因为静态加载没有对象,直接用类名调用即可。而成员函数较为特殊,因为是被对象调用的,而子类对象是谁不确定,所以得看当时的对象是谁
class A {
public String show(D obj) {
return ("A and D");
}
public String show(A obj) {
return ("A and A");
}
}
class B extends A {
public String show(B obj) {
return ("B and B");
}
@Override
public String show(A obj) {
return ("B and A");
}
//B类中隐藏了show(D obj)方法,是从A类中继承来的,但是没有被覆盖
// public String show(D obj) {
// return ("A and D");
// }
}
class C extends B {
}
class D extends B {
}
class Test {
public static void main(String[] args) {
A a1 = new A();
A a2 = new B();
B b = new B();
C c = new C();
D d = new D();
System.out.println("1--" + a1.show(b)); //1--A and A
System.out.println("2--" + a1.show(c)); //2--A and A
System.out.println("3--" + a1.show(d)); //3--A and D
System.out.println("4--" + a2.show(b)); //4--B and A
System.out.println("5--" + a2.show(c)); //5--B and A
System.out.println("6--" + a2.show(d)); //6--A and D
System.out.println("7--" + b.show(b)); //7--B and B
System.out.println("8--" + b.show(c)); //8--B and B
System.out.println("9--" + b.show(d)); //9--A and D
}
}
二、内部类
1、内部类:将一个类定义在另一个类里面,里面那个类就称为内部类(内置类、内嵌类)。内部类的出现是为了访问方便
注:内部类提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问该类
2、内部类编译会生成两个 .class 文件,Outer.class 和 Outer$Inner.class(内部类有所属)
注:内部类是个编译时的概念,一旦编译成功后,它就与外部类属于两个完全不同的类(内部类和外部类还有关系)
3、内部类访问的特点
(1)内部类可以直接访问外部类中的成员(包括私有的成员)
(2)外部类要访问内部类,必须在外部类中建立内部类的对象
注:内部类在外部类里面,内部类知道外部类中有什么。外部类在内部类外面,外部类不知道内部类中有什么
4、内部类的设计特点
内部类一般用于类的设计,是分析事物得来的。分析事物时,发现该事物描述中还有事物,而且这个事物还在访问被描述事物的内容,这时就把还有的事物定义成内部类 -- 此种设计方式相当于一种封装
5、eg:
class Outer {
private int num = 3;
class Inner {
void show() {
System.out.println("show run ... " + num); //内部类可以直接访问外部类中的成员
}
}
//使用getXxx()来获取成员内部类的实例
public Inner getInner() {
return new Inner();
}
// public void method() {
// Inner in = new Inner(); //外部类访问内部类,需要创建内部类对象进行访问
// in.show();
// }
}
class InnerClassDemo {
public static void main(String[] args) {
Outer out = new Outer();
// out.method();
out.getInner().show();
}
}
注:推荐使用getXxx()来获取成员内部类的实例,尤其是该内部类的构造函数无参时。之后再调用内部类的方法
6、内部类相当于定义在了外部类的成员位置上,所以内部类可以被成员修饰符所修饰(这是外部类所不具备的)
7、内部类的权限
(1)public:公有的,权限最大,谁都可以访问
(2)private:私有的,权限最小,只在本类中有效(同一个包中的其他类也不能访问)
(3)default:什么都不写,默认权限,比公有权限小,比私有权限大。在这种权限下,如果在其他类中直接访问内部类Inner,是可以完成的
8、直接访问外部类中的内部类
(1)一般情况:class Inner { ... }
格式:外部类名.内部类名 变量名 = 外部类对象.内部类对象
Outer.Inner in = (new Outer()).new Inner();
注:要明确是谁的内部类。此种格式不多见,更多时候内部类会被封装起来,变成私有的private
(2)内部类是静态的: static class Inner { ... }
内部类是静态的,意味着外部类一加载,内部类就存在了(Outer$Inner.class进内存)。此时内部类相当于一个外部类,直接创建内部类对象,不需要建立外部类对象了(内部类名其实是Outer.Inner)
Outer.Inner in = new Outer.Inner();
补充:
a)静态内部类不能直接访问外部类的非静态成员,可以通过 new Outer().成员 的方式访问
class Outer {
private static int num = 3;
private int count = 1;
static class Inner { //静态成员,里面访问的只能是静态变量,所以num要定义成static的
void show() {
System.out.println("show ... " + num);
System.out.println("show ... " + new Outer().count); //对象是万能的
}
}
}
class InnerClassDemo {
public static void main(String[] args) {
//内部类是静态的
Outer.Inner in = new Outer.Inner();
in.show();
}
}
b)如果外部类的静态成员与内部类的成员名称相同,在内部类中可通过“类名.静态成员”访问外部类的静态成员。如果外部类的静态成员与内部类的成员名称不相同,则在内部类中可通过“成员名”直接调用外部类的静态成员
class Outer {
private static int num = 3;
private static int count = 1;
class Inner {
int num = 10;
void show() {
System.out.println("show ... " + num); //10
System.out.println("show ... " + Outer.num); //3
System.out.println("show ... " + count); //1
System.out.println("show ... " + Outer.count); //1
}
}
}
class InnerClassDemo {
public static void main(String[] args) {
Outer.Inner in = new Outer().new Inner();
in.show();
}
}
(3)内部类是静态的,成员也是静态的: static class Inner { static void show() { ... } }
格式:外部类名.内部类名.方法名
不需要对象,直接类名调用:Outer.Inner.show();
class Outer {
private static int num = 3;
static class Inner { //静态成员,里面访问的只能是静态变量,所以num要定义成static的
void show() {
System.out.println("show ... " + num);
}
static void function() { //如果内部类中的成员是静态的,则该内部类也必须是静态的
System.out.println("function run ... " + num);
}
}
}
class InnerClassDemo {
public static void main(String[] args) {
//内部类是静态的,成员也是静态的
Outer.Inner.function();
}
}
注:如果内部类中定义了静态成员,该内部类也必须是静态的,否则编译报错
原因:内部类中的静态成员随着类的加载而加载,直接 外部类名.内部类名.方法名 调用即可,不需要创建对象。此时如果内部类不是静态的,随着类的加载没有加载进来,就不能直接用 外部类名.内部类名.方法名 调用
9、内部类细节
class Outer {
int num = 3;
class Inner {
int num = 4;
void show() {
int num = 5;
System.out.println("num="+num); //num=5
}
}
void method() {
new Inner().show();
}
}
class InnerClassDemo {
public static void main(String[] args) {
new Outer().method();
}
}
如果想打印4:this.num(本类的num,Inner可省) 或 Inner.this.num
如果想打印3:Outer.this.num
注:this.num是本类的num,如果不是本类,就需要指定是哪个类
10、内部类中访问外部类成员的几种情况
(1)Outer.成员
(2)new Outer().成员
(3)Outer.this.成员
11、为什么内部类可以直接访问外部类中的成员?
因为内部类持有了外部类的引用:外部类名.this(子类可以访问父类中的内容,是因为子类持有super引用)
12、每个内部类都能独立地继承一个(接口的)实现。所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响(使用内部类最大的优点就在于它能够很好的解决多重继承的问题)
13、内部类的特性
(1)内部类可以有多个实例,每个实例都有自己的状态信息,并且与其他外部对象的信息相互独立
(2)在单个外部类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类
(3)创建内部类对象并不依赖于外部类对象的创建
(4)内部类没有“is a”关系,它是个独立的实体
(5)内部类提供了更好的封装,除了该外部类,其他类都不能访问
class Father {
public int strong(){
return 9;
}
}
class Mother {
public int kind(){
return 8;
}
}
class Son {
/**
* 内部类继承Father类(内部类可以继承一个与外部类无关的类,保证了内部类的独立性)
*/
class Father_1 extends Father{
@Override
public int strong(){
return super.strong() + 1;
}
}
/**
* 内部类继承Mother类
*/
class Mother_1 extends Mother{
@Override
public int kind(){
return super.kind() - 2;
}
}
public int getStrong(){
return new Father_1().strong();
}
public int getKind(){
return new Mother_1().kind();
}
}
class Test {
public static void main(String[] args) {
Son son = new Son();
System.out.println("Son 的Strong:" + son.getStrong()); //Son 的Strong:10
System.out.println("Son 的kind:" + son.getKind()); //Son 的kind:6
}
}
三、局部内部类
1、内部类除了可以放在成员上(内部类),还可以放在局部位置上(局部内部类)
2、由于局部内部类不能在外部类的方法和属性以外的地方使用,因此局部内部类不能使用访问控制符和static修饰符
3、局部内部类访问该内部类所在方法的局部变量时,局部变量需要被声明为最终类型final(常量)
class Outer {
int num = 3;
void method(final int y) { //给形参传值后,y就不能改变了,是常量
final int x = 9;
class Inner {
void show() {
System.out.println("show ... " + num);
System.out.println("show ... " + x + y);
}
}
Inner in = new Inner();
in.show();
}
// public void func() {
// new Inner(); //报错,func()方法中不知道有Inner类,Inner是method方法中的
// }
}
class InnerClassDemo {
public static void main(String[] args) {
new Outer().method();
}
}
/**
* 内部类编译成功后,会产生一个class文件
* 该class文件与外部类并不是同一个class文件,仅仅只保留对外部类的引用
* 当外部类传入的参数需要被内部类调用时,从java程序的角度来看是直接被调用
*/
class OuterClass {
public void display(final String name,String age){
class InnerClass{
void display(){
System.out.println(name);
}
}
}
}
/**
* 其实不然,在java编译之后实际的操作如下
* 从代码来看,内部类并不是直接调用方法传递的参数,而是利用自身的构造器对传入的参数进行备份
* 自己内部方法调用的实际上时自己的属性而不是外部方法传递进来的参数
*/
class OuterClass$InnerClass {
public InnerClass(String name,String age){
this.InnerClass$name = name;
this.InnerClass$age = age;
}
public void display(){
System.out.println(this.InnerClass$name + "----" + this.InnerClass$age );
}
}
说明:
(1)局部内部类对象访问该局部内部类所在方法的局部变量,不是直接调用方法传递参数,而是利用自身构造器对传入的参数进行了备份,自己内部方法调用的实际上是自己的属性而不是外部方法传递进来的参数,两个参数的变化互不影响。为了保持一致性,避免引用值发生改变,使用final来保证引用不可改变(不显示声明为final,也默认为final类型。如果对局部内部类的方法中的局部变量进行修改,会提示错误final)
(2)简单理解就是拷贝引用。为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变
(3)所以,如果定义了一个匿名内部类,并且希望它使用一个其外部定义的参数,该参数引用需要用final修饰
四、匿名内部类
1、匿名内部类:其实就是匿名子类对象,是内部类的简写格式
(1)一般做法:用一个类(子类)继承或实现父类,覆盖父类中的方法,再创建子类对象
(2)匿名内部类做法:new父类,并把父类中需要被覆盖的方法在父类后的{ }中实现 -- 其实创建的还是子类对象
格式: new 父类/接口名 () { 子类内容 } //注:此处的“子类内容”除了可以覆盖父类中的方法,还可以定义子类自己的方法
2、匿名内部类使用必须有前提:内部类必须继承一个外部类或者实现一个接口,且两者不可同时兼得
3、示例
(1)匿名内部类 -- 不含内部类
interface Inter {
void show();
}
class Inner implements Inter {
@Override
public void show() {
System.out.println("show run ... ");
}
}
public class Demo {
public static void main(String[] args) {
//一般做法
Inner inner = new Inner();
inner.show();
//匿名内部类做法:new父类,并实现父类中需要实现的方法
new Inter() {
@Override
public void show() {
System.out.println("Inter show run ... ");
}
}.show();
}
}
(2)匿名内部类 -- 含有内部类
//一般做法
interface Inter {
void show();
}
class Outer {
class Inner implements Inter {
@Override
public void show() {
System.out.println("show run ...");
}
}
public void method() {
Inner in = new Inner();
in.show();
}
}
//简化成匿名内部类
interface Inter {
void show();
}
class Outer {
public void method() {
//相当于:new Inner().show();
new Inter() {
@Override
public void show() {
System.out.println("Inter show run ... ");
}
//子类特有的方法
public void childrenMethod() {
System.out.println("子类特有的方法");
}
}.show();
}
}
4、应用场景之一
当函数参数是接口类型时,而且接口中的方法不超过3个(不包含3个),可以用匿名内部类作为实际参数进行传递(接口中需要覆盖的方法超过三个,就不用匿名内部类了,因为new的父类太胖,阅读性不好)
5、匿名内部类细节
class InnerClassDemo {
class Inner {
}
public static void main(String[] args) {
//主函数是静态的,不能访问非静态成员。内部类Inner不是静态的,编译报错 -- Inner需要用static修饰
new Inner(); //相当于一个成员。Inner是非静态的,此处相当于this.new Inner(),而在静态的main中是没有this的
}
public void method() { //此方法是非静态的,持有this
new Inner();
}
}
class Outer {
void method() {
new Object() {
public void show() {
System.out.println("run ... ");
}
}.show(); //可以运行,因为创建的是子类对象
//向上转型
Object obj = new Object() {
public void show() {
System.out.println("run ... ");
}
};
obj.show(); //编译报错,父类中没有show()方法
}
}
public class RuYuTest {
public static void main(String[] args) {
new Outer().method();
}
}
6、注意事项
(1)匿名内部类没有访问修饰符,也没有构造函数(因为匿名内部类没有名字)。所以匿名内部类可以用构造代码块进行初始化
(2)匿名内部类只能继承一个类或者实现一个接口。匿名内部类不能是抽象的,它必须要实现类或接口中的所有抽象方法
(3)匿名内部类中不能有静态static成员
(3)匿名内部类不能被外部类直接调用
(4)匿名内部类使用该匿名内部类所在方法的局部变量时,该局部变量需要被final修饰,变成常量。而匿名内部类使用自己类中的变量时,不需要final修饰
五、Object类
1、Object:java中所有类的根类(创建的任何类都是Obiect类的子类 -- 直接或间接),Object中的方法所有对象都具备。Object没有父类
2、Object是不断抽取而来的,具备着所有对象都具备的共性内容
3、Object类一加载就必须执行,而不管这个类创不创建对象(Object类有一个空参数的构造函数,供子类的构造函数调用)
4、equals()方法:比的是地址值,可以实现任何对象的比较。 a.equals(b) 与 a==b 等价
public boolean equals(Object obj) {
return (this == obj);
}
5、只要是类,就具备equals()方法。但equals()只判断地址没有太大意义,一般都会覆盖此方法,根据对象的特有内容,建立对象特有的比较相同的依据
注:覆盖时,用到了多态(equals()方法的参数类型是Object)。要判断类型,然后向下转型。同时要注意异常的处理,只能try/catch,不能用throws在equlas()方法上声明。因为子类覆盖父类,子类异常要在父类异常范围内。父类没有抛出异常,子类也不能抛出,否则报错
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(obj == null) {
return false;
}
if(getClass() != obj.getClass()) {
return false;
}
//Xxx是子类型,xxx是子类型的属性
Xxx other = (Xxx)obj;
if(xxx != other.xxx) {
return false;
}
return true;
}
6、覆写equals()方法时,推荐使用getClass()方法来进行类型判断,而不是使用instanceof
7、Object有一个带内容的equals()方法,而不是把它定义成抽象方法,是因为:如果Object类中有抽象方法,意味着每建立一个类,就必须什么都不做的情况下去覆盖它的方法,但这样没有必要
8、public int hashCode():返回对象的哈希码值(针对不同的对象返回不同的整数)。在开发时,有时会进行hash值的覆写
注:hash编码独一无二的代表了一个对象,并且通过hash编码可以找到对象在内存中的位置
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
//xxx是其他值
result = prime * result + xxx;
return result;
}
9、补充
public native int hashCode();
//根据对象的地址计算得到的hashCode值
System.identityHashCode(Object x);
public static native int identityHashCode(Object x);
10、hashCode()方法:当equals()方法被重写时,通常有必要重写hashCode()方法,以维护hashCode()方法的常规协定。该协定声明相等的对象必须具有相等的哈希码 -- 应用在集合框架中(哈希表)
11、hashCode的常规协定
在java应用程序执行期间,在对同一对象多次调用hashCode()方法时,必须一致地返回相同的整数。前提是将对象进行equals()比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致
12、如果两个对象真正相同,依据主要是两个:
(1)这两个对象的hash值(hashCode())是同一个 -- 地址/位置 相同
(2)内容(equals())是一样的
注:只判断equals()不够准确
13、final Class<?> getClass():返回此Object的运行时类(当前对象所属的字节码文件对象) -- 反射机制
14、xxx.class文件是java的运行文件,专门用于描述字节码文件的类Class。其组成部分有:名称name、属性field、构造器constructor、方法method。Class对象不需要new来创建,而是字节码文件(xxx.class)一进内存就被封装成了对象
即 JVM在调用main()函数时会去找所需的xxx.class文件加载进内存。xxx.class文件一加载进内存,就被封装成xxx.class对象,即字节码文件对象(该字节码文件对象是Class类型的)。根据xxx.class字节码文件对象,可以创建多个xxx对象(内存中任意一个对象都有自己所属的字节码文件对象,对象都是依据字节码文件对象创建的)
class Class --> n个xxx.class对象(不需要new来创建,一进内存就被封装成了对象)
一个类只有一个xxx.class对象(xxx.class文件内存中只有一份)
xxx.class --> n个xxx对象(通过new创建)
class Class {
name
field
constructor
method
}
eg:
Person p1 = new Person();
Person p2 = new Person()
Class clazz1 = p1.getClass();
Class clazz2 = p2.getClass();
Syetem.out.println(clazz1 == clazz2); //true
解释:p1、p2都是Person.class对象产生的,所以getClass()方法得到的都是Person.class这个字节码文件对象的地址。一个Person.class文件对象可以产生多个对象,它们都来自于Person.class这一个文件
getClass():拿到的是Xxx.class(对象.getClass() == Xxx.class)
getName():以String的形式返回此Class对象所表示的实体名称(全类名:包名+类名)。如果只要类名(不含包名),可以用getSimpleName()
getClass().getName():拿到的是Xxx,即类名
15、public String toString():返回对象的字符串表示形式。java中认为,所有的对象都能变成字符串
16、p.toString() == p.getClass().getName() + "@" + Integer.toHexString(p.hashCode()) == Person@61de33
即 运行时类名 + @ + 十六进制的hashCode值
17、任何一个对象,如果想建立其特定的字符串表现形式,直接覆盖toString()方法即可。建议所有子类都重写此方法(通常可返回如下格式的字符串:类名[Field1=值1, Field2=值2, ...])
18、所有的java对象都可以和字符串进行连接运算(+)。当对象和字符串进行连接运算时,系统会自动调用对象的toString()方法,将得到的返回值和字符串进行连接运算
System.out.println()方法只能在控制台输出字符串,实际上输出的是对象的toString()方法的返回值
19、开发中,通常都会覆盖equals()、hashCode()、toString()方法。所有对象都具备这些功能,但都会建立该功能的特有内容