分类
修饰符 | 本类 | 同一包 | 不同包 | 全局范围 |
---|---|---|---|---|
private | Y | |||
default | Y | Y | ||
protected | Y | Y | Y | |
public | Y | Y | Y | Y |
类修饰符 | 成员变量 | 成员方法 | 构造方法 | 类 |
---|---|---|---|---|
private | F | T | T | T |
default | T | T | T | T |
protected | T | T | T | F |
public | T | T | T | T |
abstract(抽象修饰符) | F | T | F | T |
final(最终修饰符) | T | T | F | T |
static(静态修饰符) | T | T | F | F |
为什么会有final?
由于继承中有一个方法重写现象,而有时候我们不想让子类去重写父类的方法,针对这种情况java提供了一个关键字final。
概述
final关键字是最终的意思,可以修饰类,变量,成员方法
分类
实例:
Father.class
final class Father {
public Father() {
System.out.println("我是父类构造器");
}
}
Son.class
package org.westos.practice;
public class Son extends Father{
public Son() {
System.out.println("我是子类构造器");
}
public static void main(String[] args) {
Son son = new Son();
}
}
运行结果
Error:(3, 26) java: 无法从最终org.westos.practice.Father进行继承
public class Father {
public Father() {
System.out.println("我是父类构造器");
}
final void work(){
System.out.println("I am working");
}
}
Son.class
public class Son extends Father{
public Son() {
System.out.println("我是子类构造器");
}
@Override
public void work() {
super.work();
}
public static void main(String[] args) {
Son son = new Son();
}
}
Error:(9, 17) java: org.westos.practice.Son中的work()无法覆盖org.westos.practice.Father中的work()
被覆盖的方法为final
final int num=10;
num=20;
System.out.println(num);
Error:(13, 9) java: 无法为最终变量num分配值
形参在调用该方法时,由系统传入的参数来完成初始化,因此使用final修饰的形参不能被赋值。
package org.westos.practice;
public class FinalVarTest {
public void test(final int a){
a=5;
//Error:(5, 9) java: 不能分配最终参数a
}
public static void main(String[] args) {
//定义final修饰的局部变量时,被初始化后就不能再改变其值了
final int b=100;
//非法赋值
b=59;
System.out.println(b);
// Error:(13, 8) java: 无法为最终变量b分配值
}
}
final修饰基本类型和引用类型变量的区别
被final修饰的初始化后的基本类型变量不能再对其进行赋值,但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型的变量所引用的地址不会改变,即一直引用同一个对象,但这个对象本身完全可以改变。
可执行“宏替换”的final变量
final修饰符的一个重要途径就是定义“宏变量”,当定义final变量时就为改变量指定了初始值而且该初始值在编译时就被确定下来,那么这个final变量本质上就是一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。
String s1="我爱java";
//因为s2的字符串在编译的时候就可以确定下来,
//因此s2直接引用常量池中已有的"我爱java"字符串
String s2="我爱"+"java";
System.out.println(s1==s2);//true
String s3="我爱";
String s4="java";
String str=s3+s4;
System.out.println(str==s1);//false
Java会使用常量池来管理曾用过的字符串直接量,例如执行String a=“java”;语句之后,常量池中就会缓存一个字符串“java”,如果程序再次执行String b=“java”,系统将会让b直接指向常量池中的java字符串,因此a==b会返回true;
在上例中str由s3和s4进行连接后得到,它们只是普通变量,编译器不会执行宏替换。因此在编译时无法确定str的值,也就无法让str指向常量值中已有的值。要想输出true则应该对s3和s4执行宏替换,也就是用final来修饰这两个变量。
什么是内部类?
大部分时候,类被定义成一个独立的程序单元,在某些情况下也会把一个类放在另一个类的内部定义,这个定义在其他内部的类就被称为内部类,包含内部类的类也被称为外部类。
内部类的分类
内部类的作用
内部内规则
定义语法:
public class OuterClass
{
class InterClass{}
}
大部分时候,内部类都被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与成员变量、方法、构造器和初始化块相似的类成员;局部内部类和匿名类则不是类成员。
案例:
package org.westos.practice;
public class Cow {
private double weight;
//外部类的两个重载构造器
public Cow(){}
public Cow(double weight){
this.weight=weight;
}
//定义一个非静态内部类
private class CowLeg{
//非静态内部类的两个实例变量
private double length;
private String color;
//非静态内部类里的两个重载构造器
public CowLeg(){}
public CowLeg(double length, String color) {
this.length = length;
this.color = color;
}
public double getLength() {
return length;
}
public void setLength(double length) {
this.length = length;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
//非静态内部类的实例方法
public void info(){
System.out.println("当前牛腿的颜色"+color+"高"+length);
//直接访问外部内的成员变量
System.out.println("当前牛腿的重量"+weight);
}
}
public void test(){
CowLeg cowLeg = new CowLeg(1.12,"white");
cowLeg.info();
}
public static void main(String[] args) {
Cow cow = new Cow(500);
cow.test();
}
}
可以看到在文件所在路径生成的两个class文件,一个是Cow.class,另一个是CowLeg.class。
内部内访问外部类成员变量的机制
在非静态内部类对象里保存了一个它所寄生的外部类对象的引用。
在非静态内部类的方法内访问访问某个变量时,系统由现在该方法内查找是否存在该变量,如果不存在就在内部类中查找成员变量;如果找不到,就在内部类所在的外部类中查找,在如果找不到就出现编译错误。
案例:
一次注释掉局部变量,内部类变量,外部类变量,一次运行可以看到查找顺序。
package org.westos.practice;
public class NumSearch {
// int num=10; //外部内成员变量
class InNumSearch{
// int num=20; //内部类成员变量
public void inNumShow(){
int num=30; //方法内局部变量
System.out.println(num);
}
}
public void test(){
InNumSearch inNumSearch = new InNumSearch();
inNumSearch.inNumShow();
}
public static void main(String[] args) {
NumSearch numSearch = new NumSearch();
numSearch.test();
}
}
如果想一次输出三个位置的num,可以用this和类名
System.out.println(num);
System.out.println(this.num);
System.out.println(NumSearch.this.num);
如果外部内想访问内部类的成员,只能显式的实例化内部类的对象,然后通过对象调用;
public void test(){
// System.out.println(num2);
//Error:(13, 28) java: 找不到符号
// 符号: 变量 num2
// 位置: 类 org.westos.practice.NumSearch
// 创建内部类对象访问
System.out.println(new InNumSearch().num2);
}
非静态内部类对象和外部类对象的关系
非静态内部类对象必须寄生在外部类对象里面,而外部类对象则不必一定有非静态内部类对象寄生其中。接单的说,如果存在一个非静态内部类的对象则一定存在被寄生的外部类对象,而如果存在一个外部类对象则它不一定被内部类对象寄生。这就是为什么外部类不能直接访问非静态内部类成员的原因,因为不确定是否有内部对象。而内部类可以访问外部类成员,因为外部类对象一定存在。
不允许在外部类的静态成员中直接使用非静态成员内部类,这和静态成员不能访问非静态成员原因是一样的;
不允许在非静态内部成员里定义静态成员
为什么外部类就可以定义静态成员,而非静态内部类就不可以?
因为对于外部类不存在静态还是非静态这个问题。在编译时外部内中的静态成员和外部类一起被创建。而非静态内部类是在外部类被实例化时才创建,如果非静态内部类中有静态成员,那创建外部类的时候到底是创建非静态内部类的静态成员还是不创建呢?如果创建了,那非静态内部类都没创建,成员还能在类之前创建吗?这是个矛盾的问题。
访问权限
外部类的上一级程序单元是包,所以它只有2个作用域:同一个包内和任何位置。因此只需要2种访问权限:包访问权限和公开访问权限。而内部类的上一级程序单元是外部类,它有4个作用域:同一个类、同一包、父子类和任何位置,因此可以使用4种访问权限。
如果使用static来修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象。因此使用static修饰的内部类被称为类内部类,有的地方称为静态内部类。
静态内部类可以包含静态成员,也可以包含非静态成员。根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。
package org.westos.practice;
public class OuterClass {
private static int num1=10;
private int num2=20;
public static class InterClass{
public void show(){
// System.out.println(num2);
//Error:(9, 32) java: 无法从静态上下文中引用非静态 变量 num2
System.out.println(num1);
//10
}
}
public static void main(String[] args) {
InterClass interClass = new InterClass();
interClass.show();
}
}
因为静态内部类是与外部类相关的,而不是与外部类的对象相关,当静态内部类的对象存在时并不存在一个被它寄生的外部类对象,静态内部类只保存了外部类的类引用,没有持外部类的对象引用,所以在访问外部类实例成员时找不到外部类的对象。
外部类依然不能访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。
案例;
package org.westos.practice;
public class OuterClass {
public static class InterClass{
static int num1=10;
int num2=20;
}
public static void main(String[] args) {
InterClass interClass = new InterClass();
System.out.println(OuterClass.num1);
System.out.println(OuterClass.num2);
}
}
通过类名和对象名访问:
public static void main(String[] args) {
InterClass interClass = new InterClass();
//通过静态内部类类名访问内部类静态成员
System.out.println(InterClass.num1);
//通过静态内部类对象名访问内部类非静态成员
System.out.println(interClass.num2);
}
接口内部类
Java还允许在接口中定义内部类,接口里定义的内部类默认使用public、static修饰,也就是说,接口内部类只能是静态内部类。这种情况意义不太大。
使用内部类和创建实例与外部类存在差异:
在外部类内部使用内部类
不要在外部类的静态成员中使用非静态内部类,因为静态成员不能访问非静态成员,在外部类内部定义内部类的子类与平常类定义子类没有太大区别。
在外部类以外使用非静态内部类
如果希望在外部类以外访问内部类,则内部类不能用private修饰。
在外部类以外的地方创建非静态内部类必须使用外部类实例和new 来调用非静态内部类的构造器。
案例:
package org.westos.demo7;
public class Out {
class In{
public In(String msg) {
System.out.println(msg);
}
}
}
package org.westos.demo7;
public class CreateInnerInstance {
public static void main(String[] args) {
// Out.In in=new Out().new In("测试信息");
//上面的代码相当于
//1.使用OutterClass.IntterClass的形式来定义内部类变量
Out.In in;
//2.创建外部类的对象,非静态内部类对象将寄生在该对象中
Out out=new Out();
//3.通过外部类实例和new来调用内部类构造器创建非静态内部类实例
in=out.new In("测试信息");
}
}
可以看出非静态内部类的构造器必须使用外部类的实例对象来调用
当创建一个子类时,子类构造器总会调用父类的构造器,因此在创建非静态内部类的子类时,必须保证让子类的构造器可以调用这个静态内部类的构造器,调用非静态内部类的构造器时必须存在一个外部类的对象。
package org.westos.demo7;
public class SubClass extends Out.In {
public SubClass(Out out) {
//传入的out对象显式的调用in的构造器
out.super("hello");
}
}
非静态内部类In对象和其子类SubClass对象都必须持有指向Outer对象的引用,区别是创建两种对象时传入的Out对象的方式不同:当创建非静态内部类In的对象时,必须通过Outer对象来调用new关键字。当创建SubClass类的对象时,必须使用Outer对象作为调用者来调用In类的构造器。
非静态内部类的子类不一定时内部类,但非静态内部类子类的对象必须保留一个指向父类外部类的对象的引用。
在外部类中使用静态内部类
因为静态内部类是与外部类类相关的,因此创建静态内部类对象时无需创建外部类对象,在外部类以外的地方创建静态内部类实例的语法:
new OuterCalss.InnerConstructor()
package org.westos.demo7;
public class StaticOut {
static class StaticIn{
public StaticIn() {
System.out.println("静态内部诶的构造器");
}
}
}
package org.westos.demo7;
public class StaticInnerTest{
public static void main(String[] args) {
// StaticOut.StaticIn in=new StaticOut.StaticIn();
//以上代码可以理解为
//1.使用OuterClass.InnerClass的形式定义内部类变量
StaticOut.StaticIn in;
//2.使用外部类来调用内部类的构造器创建内部类对象
in=new StaticOut.StaticIn();
}
}
结论:
从上面两个例子可以看出,非静态内部类和静态内部类的区别在于,创建内部类对象时,非静态内部类对象和外部类对象关联,所以必须先创建外部类的对象,再有外部类的对象来调用内部类的构造器来创建对象;
而对于静态内部类,直接使用外部类类来调用静态内部类构造器来创建对象即可;
可以看到静态内部类对象的创建方法比较简单,所以如果要用内部类,可以优先考虑建泰内部类。
如果把一个内部类放在方法中定义,那么这个内部类就是局部内部类,仅在该方法中有效。由于局部内部类不能在方法之外的地方使用,所以不能使用访问控制符和static修饰符对其进行修饰。
如果需要用局部内部类定义变量、创建实例或派生子类,那么只能在局部内部类所在的方法内部进行。
package org.westos.demo7;
public class LocalInnerClass {
public static void main(String[] args) {
//定义局部内部类
class InnerClass{
int a;
}
//定义局部内部类的子类
class SonInnerClass extends InnerClass{
int b;
}
//创建局部内部类的对象
SonInnerClass sic=new SonInnerClass();
sic.a=5;
sic.b=6;
System.out.println(sic.a+","+sic.b);
}
}
匿名内部类适合创建那种只需要一次使用的类,创建匿名内部类时会立即创建一个该类的实例,这个类立即消失,匿名内部类不能重复使用。
定义格式:
new 实现接口() | 父类构造器(实参列表){
匿名内部类的类体部分
}
从定义格式可以看出,匿名内部类必须继承一个类或实现一个接口,但最多只能继承一个类,实现一个接口。
关于匿名内部类的规则:
匿名类的特性
如果一个方法需要一个接口类作为参数传入的时候,但是接口类不能创建对象,如果这个对象要反复使用,则创建一个这个接口的实现类;如果只调用一次,则就可以考虑匿名内部类来实现。
当通过实现接口来创建匿名内部类时,匿名内部类也不能显式的创建构造器,因此匿名内部类只有一个隐式的无参数构造器。
如果通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,此处的相似是拥有相同的形参列表。