Java之面向对象
一切皆对象。
编译型语言:编译器会将我们编写的源码一次性地编译成计算机可识别的二进制文件,然后计算机直接执行。如c、c++等。
解释型语言:在程序运行时,解释器会一行行读取我们的代码,然后实时地将这些源码解释成计算机可识别的二进制后再执行,因此解释型语言的效率通常较差一些。如Python、JavaScript等。
Java属于解释型语言,因为JVM就是解释器,并将class文件解释成计算机可识别的二进制数据后再执行。
类
- 定义:类是构造对象的模板或蓝图。
- 组成:类的主体声明了成员(变量,方法,内部类和接口)、实例初始化、静态初始化和构造器。
- 一些叫法:“类构造对象”、“类创建实例”。
- 类默认继承自Object类。
- Object类的方法:
- toString()方法:
- 默认的实现:返回一个由类名加@符号,再加hashCode()方法产生的散列码的十六进制形式所组成的字符串。一般情况下,我们是要打印对象的各个属性,因此会重写该方法。
-
public String toString() { return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode()); }
-
- 默认产生的哈希码是十进制的,默认的toString()方法进行了转换,我们测试一下:
-
A a = new A(); System.out.println(a); System.out.println(a.hashCode()); System.out.println("将默认的十进制哈希码转换成十六进制形式:" + Integer.toHexString(a.hashCode())); 输出: com.eos.javalearn.A@2401f4c3 604107971 将默认的十进制哈希码转换成十六进制形式:2401f4c3
-
- 默认的实现:返回一个由类名加@符号,再加hashCode()方法产生的散列码的十六进制形式所组成的字符串。一般情况下,我们是要打印对象的各个属性,因此会重写该方法。
- hashCode()方法:原生方法,产生的一个对象的散列码。一般情况下,我们如果要确保一个对象在不同位置(代码运行前后)是否是同一个对象可以用该方法打印对象的哈希值确定。但是自己有一套生成哈希码的算法,因此可以自己进行重写。
- equals()方法:
- 默认的实现:因为==操作符用在两个对象之间是比较引用是否相同,所以默认的就是比较两个对象的引用是否相同。一般情况下,两个对象的引用是不同的,比较起来没什么意义,主要是要比较两个对象的内容是否相同(就是其中的字段属性等),这样判断两个对象相同才有意义,这也就是我们为什么要重写这个方法的原因。
-
public boolean equals(Object obj) { return this == obj; }
-
- 默认的实现:因为==操作符用在两个对象之间是比较引用是否相同,所以默认的就是比较两个对象的引用是否相同。一般情况下,两个对象的引用是不同的,比较起来没什么意义,主要是要比较两个对象的内容是否相同(就是其中的字段属性等),这样判断两个对象相同才有意义,这也就是我们为什么要重写这个方法的原因。
- toString()方法:
- Object类的方法:
- 如果没写构造方法时,会默认提供一个无参构造方法。它会给字段设置默认值。
- 创建:使用class关键字定义一个类。
- 类的字段会自动初始化。
- 方法中的局部变量不会自动初始化,如果不初始化,编译器会报错,更别说运行了。
- 对应用初始化的位置:
- ①在定义对象的地方;
- ②在类的构造器中;
- ③在使用这些对象之前,这种方式称为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担;
- ④使用实力初始化。
- Java语言拥有三种专门用来保证恰当地进行对象初始化的机制:实例初始化(也称为实例初始化块)、实例变量初始化和构造器。(实例初始化和实例变量初始化统称为“initializers”)。
- 当创建java对象时,总体分为三个步骤:静态内容的加载、父类的初始化和类的初始化。详细按照以下顺序执行初始化:
- (1)初始化静态变量;
- 注:这些静态变量或下面说的静态变量块等等这些静态的东西,只会在类加载时执行一次。
- 会导致类加载的时机:
- ①创建类的实例:当程序通过new创建实例时,JVM将先尝试查找该类的Class对象,如果该类尚未被加载,则将该类的二进制文件加载到内存中,并生成该类的Class对象;
- ②使用类的静态变量或静态方法:当程序访问某个类的静态变量或静态方法时,JVM会检查该类是否已被加载,否则就会进行类的加载;
- ③使用反射创建类的实例时:当程序通过反射机制创建某个类的实例时,也会先检查,如果发现没有就会进行类的加载;
- ④当程序主动调用Class.forName()方法时:该方法可以根据指定类的名称获取到指定的Class对象,并将类进行加载。
- 注:虽然Java虚拟机负责加载类,但类加载器ClassLoader是根据Java的双亲委派模型来进行加载的,即:当一个类需要被加载时,先委派给父类加载器进行加载,如果父类加载器无法加载,则交由子类加载器进行加载。这种委派机制保证了类的唯一性,并确保了Java程序的安全性。
- 总结:其实,总的一句:“类是在其任何static成员(静态变量、静态普通方法、构造方法也是静态的)被访问时加载的”。
- (2)执行静态初始化块;
- 注:代码块如果有修饰的话,只能是static。同时被static修饰的块称为静态初始化块,否则就是实例初始化块。
- 静态代码块在类加载时执行,并且只执行一次。
- 静态代码块可以在一个类中编写多个,并且遵循自上而下的顺序依次执行。
- 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构。
-
static { System.out.println("执行:静态初始化块"); }
- (3)执行父类的构造方法;
- (4)调用对象的构造方法创建实例(注:但不执行构造方法体);
- (5)执行实例变量初始化,设置成员变量为默认值(0,false,null);
- (6)执行实例初始化块来初始化变量;
- 实例代码块可以编写多个,遵循自上而下的顺序执行;
- 实例代码块在构造方法执行之前执行,构造方法每执行一次,实例代码块就对应执行一次;
- 实例代码块也是java语言为程序员准备的一个特殊的时机;这个特殊时机被称为对象初始化时机;
- 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。
-
{ System.out.println("执行:实例初始化块"); }
- 不能在initializer中进行前向引用。如果你不遵守这条规则,编译器会给你一个错误信息并拒绝生成class文件。
-
class Dog { { //这里会报错:llegal forward reference System.out.println("tag = " + tag); } public String tag; }
-
- 以下例子可以看到前向引用和默认值初始化。由于从上往下执行,当调用getI()方法时,i的值还是执行实例变量初始化时设置的默认值,而到下面一行才给i赋值为10。
-
class Cat { int j = getI(); int i = 10; public static void main(String[] args) { System.out.println(new Cat().j); } public int getI() { return i; } } 输出: 0
-
- 除了匿名内部类,实例初始化只有在类的每个构造器都显示抛出已检查异常的情况下才能抛出异常。而匿名内部类的实例初始化可以抛出任何异常。
- 实例初始化块可以抛出异常,但是每个构造器都必须声明抛出指定的异常或者其父类。
-
class Cat { { if(true) { throw new FileNotFoundException(); } } public Cat() throws FileNotFoundException {} public static void main(String[] args) throws FileNotFoundException { System.out.println(new Cat()); } }
-
- (7)执行构造方法体;
- (8)
- (9)
- 如果想初始化类的引用变量,可以在以下位置进行:
- ①在定义对象的地方;
- ②在类的构造器中;
- ③在使用这些对象之前,这种方式称为惰性初始化。在生成对象不值得及不必每次都生成对象的情况下,这种方式可以减少额外的负担;
- ④使用实力初始化块。
-
class Dog { String name; //1.定义变量时就进行初始化 int age = 2; public static int ID = 1; static { System.out.println("执行:静态初始化块1,ID = " + ID); } { System.out.println("执行:实例初始化块1,name = " + name); //4.使用实例初始化 name = "wangwang"; } static { System.out.println("执行:静态初始化块2"); } { System.out.println("执行:实例初始化块2,name = " + name); name = "shasha"; } public Dog(String name) { System.out.println("执行:构造方法,name = " + this.name); //2.在类的构造器中初始化 this.name = name; System.out.println("构造方法执行完成,name = " + this.name); } public void say() { //3.惰性初始化 if (name == null) { name = "Jojo"; } System.out.println("执行:say()成员方法, name = " + name); } public static void run() { System.out.println("执行:run()类方法"); } } //调用: Dog dog = new Dog("lala"); Dog.run(); 输出如下: 执行:静态初始化块1,ID = 1 执行:静态初始化块2 执行:实例初始化块1,name = null 执行:实例初始化块2,name = wangwang 执行:构造方法,name = shasha 构造方法执行完成,name = lala 执行:run()类方法
- (1)初始化静态变量;
构造方法
- 定义:一种特殊的方法,用来构造并初始化对象。
- 构造器与类同名;
- 可以有一个以上的构造器;
- 构造器也是static方法,虽然它的static关键字没有显示地写出来。
- 构造器可以有0个、1个或多个参数。
- 构造器没有返回值。
- 构造器总是和new操作符一起调用。
-
Date date = new Date();
- 默认构造器,又名无参构造器。它是没有参数的构造方法。
- 如果你没有写构造方法,编译器会自动帮你创建一个默认构造器。如果你已经定义了构造方法,编译器就不会帮你自动创建默认构造器了。
创建对象
Srting str = new String("123adb");
- 等号左边是引用,等号右边是实际的对象。
- 可以用引用来操作实际的对象。实际上引用也不是必须要引用一个对象的,但是你如果用一个空的引用来做一件事那么就会出现常见的空指针异常NullPointerException。(引用表示wtf???不给钱还让我买菜)
- 创建对象用new关键字,表示给我一个新的东西。新的什么东西呢?一般Java中用东西的构造方法来创建实例对象,因此new后面就跟东西的构造方法,同时构造方法一般会让你传入初始化的信息,表示要创建一个带有什么属性的什么东西。
引用存储在堆栈中,对象存储在堆中。堆和堆栈都在RAM中。
堆栈:栈又名堆栈。Java系统必须知道存储在堆栈内所有项的确切生命周期,以便上下移动堆栈指针,向上移动释放内存,向下移动分配新内存。
堆:new一个对象时,会自动在堆里进行存储分配。所有的Java对象都存储在堆中。
注:对于特别小又很简单的对象,为了高效起见,java和c++采用了相同的办法——基本类型。
对象的作用域
Java对象和基本类型的生命周期不一样,它可以存活于作用域之外。
{
String s = new String("abc");
}
引用s在作用域终点就消失了,但是,s引用的String对象仍继续占据内存空间。
Java有一个垃圾回收器GC,用来监视new创建的所有对象,并辨别那些不会再被引用的对象,然后释放这些对象的内存空间,以便其他新的对象使用。所以你不必像c++那样考虑对象的回收问题。
接口
- 抽象方法:仅有声明而没有方法体。
abstract void f();
。
- 抽象类:包含抽象方法的类。
- 如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的,否则,编译器会报错。
- 扩展抽象类:要么不定义或只定义一部分方法,此时子类必须为抽象类;要么定义全部方法,就可以不将子类标记为抽象类。
- 注:
- ①即使不包含抽象方法,也可以将类声明为抽象类。
- ②抽象类不能被实例化。
-
abstract class Animal { @Override public String toString() { return getClass().getSimpleName(); } }
- 接口:被用来建立类与类之间的协议。
- 使用interface关键字进行定义。
-
interface AnimalBehavior { void run(); void eat(String food); } abstract class Animal { String name; public Animal(String name) { this.name = name; } } class Cat extends Animal implements AnimalBehavior { public Cat(String name) { super(name); } @Override public void run() { System.out.println(name + " start run!"); } @Override public void eat(String food) { System.out.println(name + " start eat " + food + "!"); } } //调用: Cat cat = new Cat("wangwang"); cat.eat("meat"); cat.run(); 输出: wangwang start eat meat! wangwang start run!
-
- 接口中可以添加字段,但是这些字段默认是public static final的。
- 接口中的字段不能是“空final”,如下,如果只定义不初始化则会报错。但是,可以被非常量表达式初始化。
int A;
。
- 同类中的静态常量,接口的字段也是在类被第一次加载时初始化。
- 接口的字段被存储在该接口的静态存储区域内。
- 接口中的字段不能是“空final”,如下,如果只定义不初始化则会报错。但是,可以被非常量表达式初始化。
- 接口中的方法默认是public的,。
- 使用接口时需要用到implements关键字。
- 接口可以被嵌套在类或其它接口中。
- 这种现象很常见,主要用于回调,如安卓中的View的点击等等。
-
class A { interface B { void exec(); } private B b; public void setB(B b) { this.b = b; } public void exec() { b.exec(); } } class C implements A.B { private A a; @Override public void exec() { System.out.println("C exec()"); } public void set() { a = new A(); a.setB(this); } public A getA() { return a; } } 调用: C c = new C(); c.set(); A a = c.getA(); a.exec(); 输出: C exec()
- 嵌套在类中的接口可以被任何访问修饰符修饰。
- 如果是嵌套的接口时private的,则不能被其他类访问。
-
class A { private interface B { void run(); } private class C implements B { @Override public void run() {} } public class D implements B { @Override public void run() {} } public B getB_C() { return new C(); } public B getB_D() { return new D(); } //接收B private B b; public void setB(B b) { this.b = b; b.run(); } } public class JavaLearnClass { public static void main(String[] args) { A a = new A(); //父接口是private,因此外部无法引用其子类的对象,不管是public还是private的 //A.B b_c = a.getB_C(); //A.B b_d = a.getB_D(); //A.C a_c = a.getB_C(); //A.D a_d = a.getB_D(); //也不能访问其子类对象的方法 //a.getB_C().run(); //a.getB_D().run(); a.setB(a.getB_C()); a.setB(a.getB_D()); } }
-
- 如果是嵌套的接口时private的,则不能被其他类访问。
- 嵌套在类中的private类不能被外部访问。
- 嵌套在接口中的接口默认是public的,而且不用写public,同时不能被其他访问修饰符修饰。
-
interface AA { interface BB { } }
-
- 接口可以继承自另一个接口,使用extends关键字,此时如果是多个接口,之间用逗号分隔。
-
interface AnimalBehavior { void eat(String food); } interface FlyAnimalBehavior extends AnimalBehavior { void fly(); } interface WaterAnimalBehavior extends AnimalBehavior { void swim(); } class Bird implements FlyAnimalBehavior { @Override public void eat(String food) {} @Override public void fly() {} } class Fish implements WaterAnimalBehavior { @Override public void eat(String food) {} @Override public void swim() {} }
-
- 使用interface关键字进行定义。
- 多重继承:一个类可以继承一个类同时能实现多个接口。
- 多个接口之间使用逗号分隔。
- 当使用多继承时,如果父接口与父接口之间、父接口与父类之间如果定义了方法签名的方法,那么子类中只有一个该方法签名的方法,该方法即可以说是来自于父接口,也可以说来自父类。
- 注:实际写代码时应避免使用这种影响代码可读性的写法。
-
interface A { void run(); } interface B { void run(); } interface C { void run(); } class D { void run() { } } class E extends D implements A,B,C { //run方法共同来自于D类、A接口、B接口、C接口 @Override public void run() { } }
- 实用技巧:
- 安卓中的监听动画的listener有几个方法,有时候我们不想都重写,可以使用一个抽象类去实现系统的接口,并添加默认空实现,在实际使用时根据需要选择想重写的方法即可。(系统其实提供了相关实现AnimatorListenerAdapter)
-
//系统动画监听类:Animator.AnimatorListener public interface AnimatorListener { default void onAnimationStart(@NonNull Animator animation, boolean isReverse) { throw new RuntimeException("Stub!"); } default void onAnimationEnd(@NonNull Animator animation, boolean isReverse) { throw new RuntimeException("Stub!"); } void onAnimationStart(@NonNull Animator var1); void onAnimationEnd(@NonNull Animator var1); void onAnimationCancel(@NonNull Animator var1); void onAnimationRepeat(@NonNull Animator var1); } //我们进行实现 class MyAnimatorListener implements Animator.AnimatorListener { @Override public void onAnimationStart(@NonNull Animator animator) {} @Override public void onAnimationEnd(@NonNull Animator animator) {} @Override public void onAnimationCancel(@NonNull Animator animator) {} @Override public void onAnimationRepeat(@NonNull Animator animator) {} } //使用: binding.lavStatus.addAnimatorListener(new MyAnimatorListener() { @Override public void onAnimationEnd(@NonNull Animator animator) { super.onAnimationEnd(animator); //xxx... } });
继承
- 当创建一个类时,除非已明确指出要从其他类继承,否则就是在隐式地从Java的标准根类Object进行继承。
- 继承是对类的复用。
- 创建:使用extends关键字让一个类去继承另一个类。
- 与继承常联系在一起谈的是组合:
- 组合是使用现有的类来开发新的类。说白了就是在一个类中创建另一个类的对象,并使用它。
- 继承虽然经常强调,但不意味着经常使用它。
- 如何决定用组合还是用继承?
- 是否需要从子类向基类进行向上转型,如果必须的话就用继承,否则就得考虑继承是否需要了。
- 调用基类构造器必须是子类构造器中第一件事。
- 尽管在子类中看不到方法的显示定义,却能使用这些方法。
- 可以在子类中调用父类的方法,也可以重写该方法。调用父类的方法用super关键字。
- 没有加任何访问修饰词,那么成员默认的访问权限是包访问权限,它允许包内的成员访问。也就是default。
- 但是其他包中某个类如果继承这个类,则只能访问public成员。
- 所以,为了继承,一般的规则是将所有的数据成员都指定为private,将所有的方法指定为public。
- protected成员也可以借助子类访问。
- private成员方法默认是final的,不可以被继承。
- 方法注解@Override表示重写父类的方法。它可以防止你在不想重载时而进行了重载。
- 如果在子类中定义与父类同名的方法,如果参数列表相同则是重写,否则就是重载。
-
class Cat { //重载 public void test(int a) { System.out.println("Cat test(int)"); } //重载 public void test(boolean a) { System.out.println("Cat test(boolean)"); } //重载 public void test(long a) { System.out.println("Cat test(long)"); } } class BBB extends Cat { //重写 public void test(int a) { System.out.println("BBB test(int)"); } //重载 public void test(short a) { System.out.println("BBB test(short)"); } }
-
- 重写:是指子类覆盖父类的方法,使得子类对象调用该方法时,将执行子类中重写的方法而非父类中的方法。
- 条件如下:
- ①子类与父类有相同的方法签名;
- ②子类方法的访问权限必须大于等于父类方法的访问权限(public>protected>default>private);
- ③子类方法的异常类型必须小于等于父类方法的异常类型,即子类不能抛出比父类方法更多或更高级别的异常。
- 注:静态方法可以被继承,但不能被覆盖。当子类定义与父类同名的静态方法时,父类的静态方法会被隐藏(hiden)。
- 条件如下:
- 如果在子类中定义与父类同名的方法,如果参数列表相同则是重写,否则就是重载。
this关键字
- this表示对当前对象的引用。
- this只能在方法内部使用,表示对调用方法的那个对象的引用。
- 如果在方法内部调用同一个类的另一个方法,就不必使用this,直接调用即可。
- 可以在构造方法中用this调用其他构造方法。但是不能同时调用多个,只能调用其中一个,如果调用多个编译器会报错。
super关键字
- 用来调用父类的成员方法、构造方法以及隐藏的成员变量。
- super与this对比:
- this是一个对象的引用,而super不能赋给其他变量,因为它只是一个指示编译器调用父类方法的关键字。
- this的含义:
- ①指示隐式参数的引用;
- ②调用该类的其他构造器。
- super的含义:
- ①调用父类的方法;
- ②调用父类的构造器。
多态
- 多态,也称动态绑定、后期绑定或运行时绑定。
- 多态:一个对象变量可以指示多种实际类型的现象。
- 绑定:将一个方法调用同一个方法主体关联起来。
- 前期绑定,在程序执行前根据编译时类型绑定,调用开销较小,如C语言只有前期绑定这种方法调用。
- 后期绑定,是指在运行时根据对象的类型进行绑定,又叫动态绑定或运行时绑定。实现后期绑定,需要某种机制支持,以便在运行时能判断对象的类型,调用开销比前期绑定大。
- Java中的static方法和final方法(private属于final方法)属于前期绑定,子类无法重写final方法,成员变量(包括静态及非静态)也属于前期绑定。除了static方法和final方法(private属于final方法)之外的其他方法属于后期绑定,运行时能判断对象的类型进行绑定。
方法重载
- 多个方法有相同的名字、不同的参数。
- 这里的方法不仅仅是普通方法,也可以是构造方法。
- 注:返回值不可用于区分方法是否重载。
-
class Dog { String name; public Dog() { } public Dog(String name) { this.name = name; } public void say() { System.out.println("say what?"); } public void say(String text) { System.out.println("say " + text); } }
协变返回类型
-在子类中的被覆盖方法可以返回基类方法的返回类型的某种子类型。
-
class Animal { @Override public String toString() { return getClass().getSimpleName(); } } class Cat extends Animal { @Override public String toString() { return getClass().getSimpleName(); } } class GetAnimal { public Animal getAnimal() { return new Animal(); } } class GetCat extends GetAnimal{ @Override public Cat getAnimal() { return new Cat(); } public static void main(String[] args) { GetAnimal getAnimal = new GetAnimal(); Animal animal = getAnimal.getAnimal(); System.out.println(animal); getAnimal = new GetCat(); animal = getAnimal.getAnimal(); System.out.println(animal); } }
static关键字
- 希望类的某个字段或方法不与包含它的类的任何对象关联在一起就可以用static关键字。
- 静态方法是不在对象上执行的方法,它没有隐式参数。即:就是没有this的方法。
- 在static方法中不能调用非静态方法,反过来是可以的。
- 即没有创建对象也能使用该字段或方法。
- 可以称作类字段和类方法。对应成员字段和成员方法。
- 静态字段属于类,而不属于任何对象。
- 类字段和类方法既可以通过类名引用,也可以通过类实例引用(但不建议)。
- 成员字段对每个对象都有一个存储空间,而类字段对于每个类只有一份存储空间。
- static不能应用于局部变量。
- 被static修饰的变量如果没有被初始化,那它会被初始化为默认值。
- 可以使用静态方法的情况:
- ①方法不需要访问对象的状态,因为它需要的所有参数都通过显示参数提供;
- ②方法只需要访问类的静态字段。
- 静态方法一个常见的用途:
- 用静态工厂方法来构造对象。
- (1)无法命名构造器,构造器的名字必须和类名相同,但实际希望有两个不同的名字;
- (2)使用构造器时,无法改变所构造对象的类型。
- 用静态工厂方法来构造对象。
class Test {
//类字段
public static int a;
//成员字段
public int b;
//类方法
public static int test() {
return 0;
}
//成员方法
public int getB() {
return b;
}
}
final关键字
- final表示无法改变的。
- final可作用于:变量、方法和类。
- final变量:
- 对基本类型,final使数值恒定不变;
- final常量:一般使用public static final修饰。
- public:可以被用于包之外;
- static:只有一份;
- final:说明它是一个常量。
public static final int TEN_NUMBER = 10;
- 注:带有恒定初始值的(也叫编译期常量)static final基本类型全用大写字母命名,并且单词之间使用下划线隔开。
- 下面有一个例子来说明:
- 对于下面两个变量,被static修饰的变量会在类第一次被加载时赋值,后面不论创建多少个对象它都是不会变的,而没有被static修饰的变量会在每创建一次对象时发生改变。
-
private final int intA = rand.nextInt(20); private static final int INT_A = rand.nextInt(20);
- 空白final:被声明为final但又未给定初始值的变量。
- 如果定义了空白final变量,会出现以下提示:
- ①定义时就初始化;
- ②添加到每个构造方法的参数列表中,通过外部传入初始化;
- ③在每个构造方法对其初始化;
- ④或者把它指定为非final变量。
- 如果同时加上static,此时就只有两个选择,要么在定义时进行初始化,要么指定位非final。因为static变量不需要创建对象就能使用,因此没有在构造方法中对其初始化的选项。
public final String name;
- 如果定义了空白final变量,会出现以下提示:
- final参数:可以将方法的参数指定为final形式。因此,你只能从中读取数据,却不能在方法内部修改这个参数的值或引用。
- 这个主要用来向匿名内部类传递数据。
-
public void run(final String a) { //会报错 a = "0"; }
- final常量:一般使用public static final修饰。
- 对对象引用,final使引用恒定不变。
- 一旦引用被初始化指向一个对象,就无法再把它改为指向另一个对象。但是对象本身是可以被修改的。
- 对基本类型,final使数值恒定不变;
- final方法:
- 用于禁止子类重写该方法。
- 被final修饰的方法无法被覆盖。同时被private修饰的方法是隐式指定为final的,因此不用写final,如果写上会出现以下提示,所以一般建议private方法不用写final。
'private' method declared 'final'
。
- final类:
- 用于阻止其他类继承该类,同时该类中的方法都隐式指定为final的,但是成员字段不受影响,你可以设置其是否为final。
- final变量:
访问权限控制
- public修饰的成员可以在任何地方被访问。
- 不加访问修饰词的成员只能被同一个包的其他类访问。
- protected允许子类访问其成员。也允许同一个包内的类访问其成员。
- private只允许自己访问该成员。
- 类既不可以是private的,也不可以是protected的。
- 向上转型:由子类转型成基类,在继承图上是向上移动的,因此一般称为向上转型。
Animal cat = new Cat();
。- 我这种把某个对象的引用视为对其基类型的引用的做法称作向上转型。
-
class Animal { public void run() {} public static void toDo(Animal animal) { animal.run(); } public static void main(String[] args) { Cat cat = new Cat(); //向上转型 Animal.toDo(cat); } } class Cat extends Animal { }
- 访问修饰符功能一览表:
修饰符 | 可见性 |
---|---|
public | 所有类可见 |
protected | 当前类、子类、同一包路径下的类可见 |
default | 同一包路径下的类可见 |
private | 当前类可见 |
内部类
- 分为4种:成员内部类、局部内部类、匿名内部类、静态内部类。
- ①成员内部类:可以将一个类当作成员定义放在另一个类的内部。
- 在其他的类中指明B的类型时使用:
OuterClassName.InnerClassName
。 - 如果需要再内部类中获取外部类的对象,使用外部类的名字加.this即可。
- 如果想直接创建非静态内部类的对象,必须使用外部类的对象来创建该内部类对象,使用外部类对象加.new,后面跟空格,再加上内部类的构造方法即可。
- 在拥有外部类对象之前是不可能创建内部类对象的,因为内部类对象会隐式地保存了一个外部类对象的引用。
- 内部类可以访问外部类的方法和字段,就像自己拥有它们似的,可忽略任何可见性修饰符。
- 内部类的访问修饰符可以任意,但是访问权限会受影响。
- 内部类可以直接访问外部类的成员,如果出现同名属性和方法会优先访问内部类中定义的。可以使用:
外部类.this.成员
这种方式访问同名的成员或者方法。 - 外部类访问内部类信息,需要通过内部类实例,否则是无法进行直接访问的。
- 内部类编译后 .class 文件名字会为: 外部类$内部类.class。
-
class A { public void test() { } class B { private int b = 2; public int value() { return b; } public A getA() { //引用外部类的对象 return A.this; } } } public class JavaLearnClass { public static void main(String[] args) { A a = new A(); //创建内部类的对象 A.B b = a.new B(); b.getA().test(); } }
- 内部类的继承:
- 必须在构造器中使用外部类对象加.super()。
-
class A { class B { } } class C extends A.B { public C(A a) { a.super(); } public static void main(String[] args) { A a = new A(); C c = new C(a); } }
- 内部类的覆盖问题:
- 可以用以下方式覆盖A中的B吗?
- 答:不能。
-
class A { class B { public B() { System.out.println("Old B"); } } public A() { System.out.println("Old A"); B b = new B(); } } class AA extends A { class B { public B() { System.out.println("New B"); } } public AA() { System.out.println("New AA"); } public static void main(String[] args) { new AA(); } } 输出: Old A Old B New AA
-
- 如果要覆盖内部类,需要这样做:
-
class A { private B b; class B { public B() { System.out.println("Old B"); } public void test() { System.out.println("Old B test()"); } } public A() { System.out.println("Old A"); b = new B(); } public void setB(B b) { this.b = b; } public void test() { b.test(); } } class AA extends A { class BB extends B{ public BB() { System.out.println("New BB"); } @Override public void test() { System.out.println("New BB test()"); } } public AA() { System.out.println("New AA"); setB(new BB()); } public static void main(String[] args) { AA aa = new AA(); aa.test(); } } 输出: Old A Old B New AA Old B New BB New BB test()
-
- 在其他的类中指明B的类型时使用:
- ②局部内部类:类除了可以定义在类中,还能定义在方法中、if作用域中等等这些有大括号的地方。同时,只能在当前大括号结束前使用它,除了这一点,它和其他普通类一样。
- 局部内部类不能有访问修饰符(public、private、protected、default),因为它不属于外围类的一部分。同时也不能使用静态修饰符(static),但可以使用final修饰符。
- 局部内部类中不能定义静态成员,可以包含final、abstract修饰的成员。
- 局部内部类可以访问当前代码块内的常量以及外部类的所有成员。
- 在方法中定义类:
-
public void test() { class C { } C c = new C(); }
-
- 在if作用域中定义:
-
public void test(int a, int b) { if (a > b) { class C { } C c = new C(); } }
-
- 局部内部类访问外部变量的规则和匿名内部类完全一样。
-
interface String { void string(java.lang.String string); } class A implements String { String s1; @Override public void string(final java.lang.String s2) { final java.lang.String s3 = ""; new String() { String s4; java.lang.String str = s2; @Override public void string(java.lang.String s5) { java.lang.String s6 = ""; System.out.println("类的成员变量 s1 = " + s1 + ", 不需要指定为final, 不强制初始化"); System.out.println("方法的参数 s2 = " + s2 + ", 需要指定为final"); System.out.println("方法的局部变量 s3 = " + s3 + ", 需要指定为final, 强制初始化"); System.out.println("匿名内部类的成员变量 s4 = " + s4 + ", 不需要指定为final, 不强制初始化"); System.out.println("匿名内部类的方法的参数 s5 = " + s5 + ", 不需要指定为final"); System.out.println("匿名内部类的方法的局部变量 s6 = " + s6 + ", 不需要指定为final, 强制初始化"); } }; } }
-
- 局部内部类对比匿名内部类:
- 当我们需要一个已命名的构造器或需要重载构造器时使用局部内部类,因为匿名内部类只能用于实例初始化,而不能自定义构造器或重载构造器。
-
interface Count { int next(); } class A { int count; public Count getCount(final String name) { class LocalClass implements Count { @Override public int next() { System.out.print(name); return count++; } } return new LocalClass(); } public Count getCount2(final String name) { return new Count() { @Override public int next() { System.out.print(name); return count++; } }; } public static void main(String[] args) { A a = new A(); Count count1 = a.getCount("局部内部类 "); Count count2 = a.getCount2("匿名内部类 "); for (int i = 0;i < 10;i++) { if (i < 5) { System.out.println(count1.next()); } else { System.out.println(count2.next()); } } } } 输出: 局部内部类 0 局部内部类 1 局部内部类 2 局部内部类 3 局部内部类 4 匿名内部类 5 匿名内部类 6 匿名内部类 7 匿名内部类 8 匿名内部类 9
- ③匿名内部类:
-
abstract class A { } interface B { } class C { private String str1; public A a = new A() { @Override public String toString() { return this.getClass().getSimpleName(); } }; public B b = new B() { @Override public String toString() { return this.getClass().getSimpleName(); } }; public B getNewB(final String str2) { final String str3 = "1"; return new B() { String str4; String str = str2; public void test(String str5) { String str6 = ""; System.out.println("类的成员变量 str1 = " + str1 + ", 不需要指定为final, 不强制初始化"); System.out.println("方法的参数 str2 = " + str2 + ", 需要指定为final"); System.out.println("方法的局部变量 str3 = " + str3 + ", 需要指定为final, 强制初始化"); System.out.println("匿名内部类的成员变量 str4 = " + str4 + ", 不需要指定为final, 不强制初始化"); System.out.println("匿名内部类的方法的参数 str5 = " + str5 + ", 不需要指定为final"); System.out.println("匿名内部类的方法的局部变量 str6 = " + str6 + ", 不需要指定为final, 强制初始化"); } }; } public void test() { System.out.println("a = " + a.toString() + ", b = " + b.toString()); } public static void main(String[] args) { C c = new C(); c.test(); } }
- 如果在匿名内部类中使用一个定义在其外部定义的对象,编译器会要求其参数引用是final的。
- 匿名内部类与继承相比还是很受限制,因为它只能同时扩展一个类或实现一个接口。
- 匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。
- 匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。
- 同样的,匿名内部类也是不能有访问修饰符和static修饰符的。
-
- ④静态内部类:
- 创建静态内部类时,不需要外部类的对象就能创建。
-
A.B b = new A.B();
-
- 静态内部类不能访问非静态外部类对象,但可以通过
new 外部类().成员
的方式访问 。 - 如果外部类的静态成员与内部类的成员名称相同,可通过
外部类名.静态成员
访问外部类的静态成员;如果外部类的静态成员与内部类的成员名称不相同,则可通过成员名
直接调用外部类的静态成员。 - 创建静态内部类的对象时,分为两种:
- a、在外部类中,不需要创建外部类的对象,可以直接使用
Inner in = new Inner();
- b、在其他类需要这样:
Outer.Inner in = new Outer.Inner();
。
- a、在外部类中,不需要创建外部类的对象,可以直接使用
- 成员内部类与静态内部类对比:
- 成员内部类除了不能有静态的东西以外其他都能做,静态内部类除了不能访问非静态外部类对象的数据外其他都能做。
-
class A { //外部类的成员变量 String s; //外部类的静态成员变量 static String ss; //外部类的成员方法 void test(int i) {} //外部类的静态方法 static void test() {} static class B { //静态内部类可以定义静态变量 static String s1; //静态内部类可以定义成员变量 String s2; //静态内部类不可以使用外部类的成员变量 //String s3 = s; //但可以使用创建的外部类的实例来访问外部类的成员变量 String s3 = a.s; //静态内部类可以使用外部类的静态变量 String s4 = ss; //静态内部类不可以访问非静态外部类对象 //A a = A.this; //但可以创建外部类的实例 A a = new A(); //静态内部类可以定义静态方法 static void show() {} //静态内部类可以定义成员变量 void exec() { //静态内部类不能访问非静态外部类对象,因此不能调用其的成员方法 //a.test(1); //但可以通过创建的外部类的实例来访问其成员方法 a.test(1); //静态内部类可以调用非静态外部类的静态方法 test(); } //静态内部类可以定义成员内部类 class D { } //静态内部类可以定义静态内部类 static class E { } } class C { //成员内部类不可以定义静态变量 //static String s1; //成员内部类可以定义成员变量 String s2; //成员内部类可以使用外部类的成员变量 String s3 = s; //成员内部类可以使用外部类的静态变量 String s4 = ss; //成员内部类可以访问非静态外部类对象 A a = A.this; //成员内部类不可以定义静态方法 //static void show() {} //成员内部类可以定义成员方法 void exec() { //成员内部类可以调用非静态外部类对象的成员方法 a.test(1); //成员内部类可以调用非静态外部类对象的静态方法 test(); } //成员内部类可以定义成员内部类 class D { } //成员内部类可以不可以定义静态内部类 //static class E { // //} } }
- 接口内部的类:
- 接口中的任何类都默认是public static的,甚至可以让这个类实现外部接口。
-
interface A { void test(); class B implements A { @Override public void test() { } } }
-
- 接口中的任何类都默认是public static的,甚至可以让这个类实现外部接口。
- 创建静态内部类时,不需要外部类的对象就能创建。
- 其他:外部类及内部类的类名称。
- 最外层的类就是包名加类名。
- 成员内部类和静态内部类是包名加外部类的类名再跟一个$符号,最后再跟自己的类名。
- 局部内部类是包名加外部类的类名再跟一个$符号,最后跟一个数字加自己的类名。
- 匿名内部类是包名加外部类的类名再跟一个$符号,最后跟一个编译器产生的一个数字(这个数字是匿名内部类的标识符)。
-
interface E { } class A { //成员内部类 class B {} //静态内部类 static class C {} public void test() { A a = new A(); System.out.println("外部类的类名称:" + a.getClass().getName()); B b = new B(); System.out.println("成员内部类的类名称:" + b.getClass().getName()); C c = new C(); System.out.println("静态内部类的类名称:" + c.getClass().getName()); //局部内部类 class D { } D d = new D(); System.out.println("局部内部类的类名称:" + d.getClass().getName()); //匿名内部类 E e = new E() { }; System.out.println("匿名内部类的类名称:" + e.getClass().getName()); } public static void main(String[] args) { A a = new A(); a.test(); } } 输出: 外部类的类名称:com.eos.javalearn.A 成员内部类的类名称:com.eos.javalearn.A$B 静态内部类的类名称:com.eos.javalearn.A$C 局部内部类的类名称:com.eos.javalearn.A$1D 匿名内部类的类名称:com.eos.javalearn.A$1