1、成员方法传参机制
对于基本数据类型,传递的是值(值拷贝)
AA aa = new AA(); int x = 10; int y = 20; aa.swape(x,y);//输出a = 20,b = 10但是x,y的值依然不变 System.out.println("x的值:" + x +"\n"+"y的值:" + y); |
AA aa = new AA(); int x = 10; int y = 20; aa.swape(x,y);//输出a = 20,b = 10但是x,y的值依然不变 System.out.println("x的值:" + x +"\n"+"y的值:" + y); |
2、引用数据类型的传参机制
引用类型传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参!
3、递归重要规则
- 执行一个方法时,就创建了一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响,比如n变量
- 如果方法中使用的是引用数据类型(比如数组,对象等)就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归
- 当一个方法执行完毕,或者遇到return就会返回,遵守谁调用就把结果返回给谁,同时方法执行完毕或者返回时,该方法也就执行完毕了。
4、 方法重载和方法重写的区别
- 方法重载:
方法名必须相同
形参列表必须不同(形参个数或顺序,至少有一样不同,参数名无要求)
返回类型无要求
5、可变形式参数
基本语法:
访问修饰符 返回类型 方法名(数据类型... 形参名) {}public int sum(int... nums) { //System.out.println("接收的参数个数=" + nums.length); int res = 0; for (int i = 0; i < nums.length; i++) { res += nums[i]; } return res; }
使用可变参数时,可以当做数组来使用 即 nums 可以当做数组,遍历 nums
6、java变量作用域
java编程中,主要的变量就是属性(成员变量)和局部变量(定义在成员方法中的变量)
- 全局变量:也就是属性,成员变量,作用域为整个类体
- 局部变量:也就是除了属性之外的变量,作用域在它的代码块中!
- 全局变量的属性可以不用赋值直接使用,因为有默认值。局部变量必须复制后才可以使用,没有默认值。
- 属性和全局变量可以重名,访问遵循就近原则,同一个作用域内不可重名
- 修饰符不同:全局变量/属性可以加修饰符,局部变量不可以加修饰符
7、构造器/构造方法
- 构造器没有返回值,也不用写void
- 方法名必须和类名一致
- 参数列表和成员方法一样的规则,构造器调用由系统完成,主要是初始化成员变量参数不是创建对象,在创建对象时,系统自动调用该类的构造方法。当程序员调用构造器时,系统会先为该对象分配内存空间,并为这个对象执行默认初始化。当系统开始执行构造器的构造体之前,系统已经创建一个对象,只是这个对象还不能被外部程序访问,只能在该构造器中通过this来引用
- 为对象分配空间——>实例变量默认初始化——>执行构造器的执行体——>通过this给实例变量赋值——>构造器返回对象给引用变量
- 用this调用另一个重载的构造器只能在构造器中使用,而且必须作为构造器执行体的第一条语句
- 使用super调用父类构造器也必须出现在子类构造器执行体的第一行,所以this调用和super调用不会同时出现
- 子类构造器执行体中既没有super调用,也没有this调用,系统将会执行在执行子类构造器之前,隐式调用父类无参数的构造器。
- 一个类可以有多个构造方法,重载。程序员没有定义构造器,系统会自动给类生成一个无参构造器。一旦自己定义了构造器,默认构造器就被覆盖了,不能使用无参构造器了,除非显示的定义一下无参构造器(建议,很重要)
对象创建的流程分析 :
- 加载Person类的信息(Person.class),类加载阶段,只会加载一次
- 在堆内存中分配空间,即对象的地址----->对象创建了
- 完成对象的初始化--->默认初始化(属性不复制,默认值)、显式初始化(显式赋值)、构造器初始化
- 把对象在堆内存中的地址,返回给p(p时对象名,也可以理解为对象引用)
Java中是构造器创建对象 “这句话是完全错误的。Java中构造器的作用主要是为了初始化变量的值...其实在执行构造器之前,Java对象所需要的内存空间,已经产生了...一般可以理解为由new关键字创建出来的哦。
常见的两种不通过new 关键字创建对象的方式如下:
1)通过Java的序列化和反序列化,来创建相关的对象...
需要注意的是在使用java的序列化和反序列化的时候要使对应的实体类实现Serializable序列化接口哦...
2)通过Java的clone来创建相关的对象...
8、this关键字
this关键字的意义被解释为“指向当前对象的引用”
1、this:表示自身对象,也就是本对象自己
2、this.属性名:表示本对象自己的属性
3、this.方法名:表示本对象自己的方法
4、this(参数)表示本对象自身的构造方法(注:”构造方法”这个概念是相对于”类”而言的,但具体到this(参数)这种用法时,表示”我这个对象自己的构造方法”)
5、外部类名.this.属性:表示在内部类中调用的是外部类的某个属性(调用外部类方法亦同)
9、访问修饰符
java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
- 公开级别:用 public 修饰,对外公开
- 受保护级别:用 protected 修饰,对子类和同一个包中的类公开
- 默认级别:没有修饰符号,向同一个包的类公开.
- 私有级别:用 private 修饰,只有类本身可以访问,不对外公开
修饰符可以用来修饰类中属性、方法以及类。只有默认的和public可以修饰类
10、面向对象编程有三大特征:封装、继承和多态
封装:把抽象出的数据(属性)和对数据的操作(方法)封装在一起,数据被保护在方法内部,程序的其他部分只有通过被授权的方法才能对数据进行操作
- 将属性私有化private(不能直接修改属性)
- 提供一个公共的public方法setXxx(),用于对属性的赋值
- 提供一个公共的public的方法getXxx(),用于对属性值的获取
继承:可以解决代码复用,让我们的编程更加靠近人类思维.当多个类存在相同的属性(变量)和方法时,可以从这些类中 抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过 extends 来 声明继承父类即可。子类会自动拥有父类属性和方法。
- 子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访 问,要通过父类提供公共的方法去访问
- 非私有的属性和方法可以在子类直接访问,但是私有属性和方法不能在子类直接访问
- 子类必须调用父类的构造器, 完成父类的初始化
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无 参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不通过。(最好显式声明无参构造器)
- 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
super 在使用时,必须放在构造器第一行( super 只能在构造器中使用) 子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。 super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器 父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类) java 所有类都是 Object 类的子类, Object 是所有类的基类
super 关键字:super 代表父类的引用,用于访问父类的属性、方法、构造器
不能访问父类私有属性和私有方法。
- 调用父类构造器,分工明确,父类属性由父类初始化,子类属性由子类初始化
- 当子类中有和父类重名的成员(属性和方法),为了访问父类成员,必须使用super。如果没有重名,使用super,this,直接访问是一样的效果。
- super的访问不限于直接父类,多个父类都重名,就近原则访问
super和this的比较
11、方法重写--override
方法重写(方法覆盖)就是子类有一个方法和父类某个方法的名称,返回类型,参数都一样
注意:子类方法的返回类型和父类方法返回类型一样, 或者是父类返回类型的子类
如父类方法返回Object,子类重写该方法可以返回任意引用类型,如String
子类方法不能缩小父类方法的访问权限,public > protected > 默认>private
12、多态
方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。多态的具体体现:
方法的多态:重写和重载就体现多态,我们传入不同的参数,就会调用不同 sum 方法,就体现多态。 对象的多态
一个对象的编译类型和运行类型可以不一致 编译类型在定义对象时就确定了,不能改变 运行类型时可以变化的 编译类型看定义时 = 号的左边,运行类型看 = 号的右边Animal animal = new Dog();编译类型是Animal,运行时类型是Dog
多态细节注意:多态的前提是两个对象存在继承关系,子类重写父类的方法
- 本质:父类引用指向子类的对象(向上转型)
- 语法:父类类型 引用名 = new 子类类型();
- 特点:编译类型看左边,运行类型看右边,可以调用父类中的所有成员(需遵守访问权限),不能调用子类中特有的成员(需要向下转型),最终运行效果看子类的具体实现。
instanceOf 比较操作符,用于判断对象的 运行类型是否为 XX 类型或 XX 类型的子类型 System.out.println(str instanceof Object);//true属性没有重写之说!属性的值看编译类型,当父类引用指向子类对象时,子类中有父类同名属性,访问的依然时父类属性,要想访问子类属性,new子类对象。
public class PolyDetail02 { public static void main(String[] args) { //属性没有重写之说!属性的值看编译类型 Base base = new Sub();//向上转型 System.out.println(base.count);// ? 看编译类型 10 Sub sub = new Sub(); System.out.println(sub.count);//? 20 } } class Base { //父类 int count = 10;//属性 } class Sub extends Base {//子类 int count = 20;//属性 }
13、动态绑定机制
静态绑定是指在编译期就可以明确方法调用,而动态绑定是要等到运行时才能确定。一静一动,编译期是“静”,运行时是“动”;一前一后,编译在前,运行在后。
方法定义的形参类型是父类类型,实参允许是子类类型。
14、Object类详解
14.1、==和 equals 的对比 [面试题]
equals()方法是顶级父类Object类中的方法,用来判断引用类型是否相等,默认是判断地址是否相等,子类中往往重写该方法,用于判断内容是否相等,如String,IntegerInteger integer1 = new Integer(1000);Integer integer2 = new Integer(1000);System.out.println(integer1 == integer2);//falseSystem.out.println(integer1.equals(integer2));//true== 是一个比较运算符,既可以判断基本类型,又可以判断引用类型
- 如果判断的是基本类型,判断值是否相等
- 如果判断引用类型,判断地址是否相等,即判断是否为一个对象。与默认的equals()方法用法一致。equals与hashcode是有契约的(无论什么时候你重写了equals方法,你同样要重写hashcode()方法)
- 字符串的==和equals对比:
- 字符串的比较是一个常见的情景,因为java.lang.String类重写了equals方法,它返回true如果两个字符串对象包含有相同的内容,但是==只有他们的引用地址相同时才返回true
总结:
使用==比较原生类型如:boolean、int、char等等,使用equals()比较对象。
==返回true如果两个引用指向相同的对象,equals()的返回结果依赖于具体业务实现
字符串的对比使用equals()代替==操作符
instanceof是用于判断对象的运行类型是否为 XX 类型或 XX 类型的子类型
使用方法:对象 instanceof 类名
返回值:boolean类型true 或者 false
int it = 65;float fl = 65.0f;System.out.println(“65 和 65.0f 是否相等?” + (it == fl));//Tchar ch1 = ‘A’; char ch2 = 12;System.out.println(“65 和‘A’是否相等?” + (it == ch1));//TSystem.out.println(“12 和 ch2 是否相等?” + (12 == ch2));//TString str1 = new String("hello");String str2 = new String("hello");System.out.println("str1 和 str2 是否相等?"+ (str1 == str2)); //FSystem.out.println(“str1 是否 equals str2?”+(str1.equals(str2)));//TSystem.out.println(“hello” == new java.sql.Date()); //编译错误
14.2、 hashCode 方法
- 提高具有哈希结构的容器的效率!
- 两个引用,如果指向的是同一个对象,则哈希值肯定是一样的!
- 两个引用,如果指向的是不同对象,则哈希值是不一样的
- 哈希值主要根据地址号来的!, 不能完全将哈希值等价于地址。
hashCode()和equals()的关系
只要重写 equals,就必须重写 hashCode
。
- HashSet、HashMap底层在添加元素时,会先判断对象的hashCode是否相等,如果hashCode相等才会用equals()方法比较是否相等。换句话说,HashSet和HashMap在判断两个元素是否相等时,会先判断hashCode,如果两个对象的hashCode不同则必定不相等
- 复习集合以及数据结构的时候再重新整理hash
14.3、 toString 方法
默认返回:全类名+@+哈希值的十六进制,子类往往重写 toString 方法,用于返回对象的属性信息。
当直接输出一个对象时,toString 方法会被默认的调用, 比如 System.out.println(monster); 就会默认调用monster.toString()
15、类变量和类方法 static
类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问 ,在类加载时就初始化了,也就是说你没有创建对象,只要类加载了就可以使用。
静态方法只能访问静态成员,普通成员方法,既可以访问 非静态成员,也可以访问静态成员。
在 static 方法中,不可以使用 this 关键字
public static int getTotalPerson() {id++;// 错误, id是成员变量没用static修饰,不能在静态方法中访问return total;}
结论:static方法不能被重写!
public class Main { public static void main(String[] args) { A a = new A(); a.test();// 输出 A=>test() B b = new A(); b.test();// 输出 B=>test(),说明父类中的test()方法没有被重写 } } // 父类 class B { public static void test(){ System.out.println("B=>test()"); } } // 子类 class A extends B { public static void test(){ System.out.println("A=>test()"); } } 运行结果:父类中的test()方法没有被重写!
在Java中,如果子类和父类中各有一个static方法,且它们的返回值类型、方法名、参数列表都相同(具有相同的签名),那么这两个static方法并不具有重写关系。
- 静态方法不具有多态性。子类可以继承父类中的静态方法,但是不能重写父类中的静态方法。
- 重写的前提:有继承关系,子类重新编写父类的方法(注意不重写属性)
- 子、父类中两个方法的方法名、参数列表必须相同,方法体不同。
- 重写注意事项:
- 子类中重写后方法的访问修饰符 不能小于 父类方法的访问修饰符(public > protected > default > private)
- 子类方法的返回值以及抛出的异常要 小于等于 父类方法。比如:父类中throws IOException,子类中不能throws Exception,因为Exception > IOException
- 若父类方法为private,则子类对其重写无效。
- 父类方法用final修饰也不能被重写
因此不能用static、private、final修饰抽象方法。
16、代码块
代码块又称作初始化块,述域类中的成员,是类的一部分,直接由{}包围起来,没有方法名,没有返回值,没有参数,只有方法体,在类加载阶段或创建对象时隐式调用。
- 静态代码块
- static{}
- 对类进行初始化,随着类加载而执行,而且只会执行一次
- 非静态代码块
- {}
- 每创建一个对象就会执行一次
- 相当于另一种形式的构造器,对构造器的补充机制,做类加载时的初始化操作。
创建对象,都会先调用代码块的内容 类什么时候被加载?
创建对象实例时(new) 创建子类对象实例,父类也会被加载 使用静态成员(静态属性,静态方法) 创建一个对象时,在一个类中调用顺序是:
调用静态代码块和静态属性初始化(优先级一样,同时进行)如果有多个就按他们定义的顺序进行 调用普通代码块和普通属性的初始化(优先级一样,同时进行)如果有多个就按他们定义的顺序进行 调用构造方法 构造器最前面其实隐含了super()和调用普通代码块,静态代码块,静态属性初始化在类加载的时候就执行完毕了,因此优先于构造器和普通代码块执行的 创建子类对象时,静态代码块、静态属性初始化、普通代码块、普通属性初始化、构造方法调用顺序:
父类的静态代码块和父类的静态属性初始化先执行 子类的静态代码块和子类的静态属性初始化执行 父类的普通代码块和普通属性初始化执行- 父类的构造方法执行
子类的普通代块和普通属性初始化执行 子类的构造方法执行
17、单例设计模式 --静态属性和静态方法的经典应用
单例模式:采取一定的方法保证整个软件系统中,对某个类只能存在一个对象实例,并且该类只能提供一个取得其对象的实例方法
分类
- 饿汉式-------定义static对象并初始化,private构造器
将构造器私有化 在类的内部直接创建对象(该对象是 static),定义一个static静态属性对象,且使用构造器初始化 在类的内部直接创建对象(该对象是 static)private static GirlFriend gf = new GirlFriend("小红红");//初始化了一个对象 //如何保障我们只能创建一个 GirlFriend 对象 //步骤[单例模式-饿汉式] //1. 将构造器私有化 //2. 在类的内部直接创建对象(该对象是 static) //3. 提供一个公共的 static 方法,返回 gf 对象 private GirlFriend(String name) { System.out.println("構造器被調用."); this.name = name; } public static GirlFriend getInstance() { return gf; }
- 懒汉式-------定义static对象未初始化,默认null,private构造器
仍然构造器私有化 定义一个static静态属性对象,默认不初始化为null 提供一個 public 的 static 方法,先判断对象是否为null,为null说明第一次创建,不为null,直接返回这个对象。可以返回这个静态对象 懒汉式,只有当用户使用 getInstance 时,才返回 对象, 后面再次调用时,会返回上次创建的静态对象。private String name; public static int n1 = 999; private static Cat cat ; //默认是 null,未初始化 //步驟 //1.仍然構造器私有化 //2.定義一個 static 靜態屬性對象 //3.提供一個 public 的 static 方法,可以返回一個 Cat 對象 //4.懶漢式,只有當用戶使用 getInstance 時,才返回 cat 對象, 後面再次調用時,會返回上次創建的 cat 對象 // 從而保證了單例 private Cat(String name) { System.out.println("構造器調用..."); this.name = name; } public static Cat getInstance() { if(cat == null) {//如果還沒有創建 cat 對象 cat = new Cat("小可愛"); } return cat; }
18、final 关键字
- final可以修饰类、属性、方法和局部变量
- 使用final的时机:
- 当不希望类被继承时,可以用final修饰类
- 当不希望父类的某个方法被子类重写时,可以用final关键字
- 当不希望类的某个属性被修改时,可以用final修饰为一个常量
- 当不希望某个局部变量被修改时,可以使用final修饰
- 使用细节
- final修饰的属性在定义时,必须赋值,并且不可修改,可以在静态代码块中、构造器中或者定义时赋值
- 如果final修饰的属性为静态的,则初始化位置只能是定义时、静态代码块中,不能在构造器中赋值
- final修饰的类不能被继承,但是可以实例化对象
- 如果类不是final类,但是含有final修饰的方法,则该方法不能重写,但可以被继承
- 一般来说如果类是final的就不用再把方法修饰成final了
- final不能修饰构造方法
- final往往和static搭配使用,效率更高,不会导致类加载
- 包装类(Integer、Boolean、Double、Float等都是final类),String也是final类
19、抽象类 和抽象方法
当父类某些方法需要声明,但又不确定如何实现时,可以将其声名为抽象方法,那么这个类就是抽象类。abstract修饰
所谓没有实现就是指,没有方法体, 当一个类中存在抽象方法时,需要将该类声明为 abstract 类。一般来说,抽象类会被继承,有其子类来实现抽象方法.
- 用abstract关键字修饰一个类,就是抽象类
- 访问修饰符 abstract 类名{}
- 用abstract关键字修饰的方法就是抽象方法
- 访问修饰符 abstract 返回值类型 方法名(参数列表);//没有方法体
- 抽象类的价值更多在于模式架构设计,让子类继承并实现抽象类
抽象类注意事项:
- 抽象类不能被实例化
- 抽象类不一定要包含abstract方法
- 一旦类包含了abstract方法,则这个类必须声明为abstract
- abstract只能修饰类和方法,不能修饰属性和其他的
- 抽象类可以由任意成员,和其他类一样
- 抽象方法不能有主体,即不能实现abstract修饰的抽象方法
- 访问修饰符 abstract 返回值类型 方法名(参数列表);
- 如果一个类继承了抽象类,则它必须要实现抽象类中的所有抽象方法,除非它自己也声明为抽象类
- 抽象方法不能使用private、final和static修饰,因为这些关键字都是和方法重写违背的。
20、模板设计模式---抽象类的经典实践
抽象类体现的就是一种模板设计模式,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式
- 当功能内部一部分实现是确定,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现
- 编写一个抽象父类,父类提供了多个子类的通用方法,并把一个或多个方法留给其子类实现,就是一种模板模式。
在抽象父类中调用抽象方法,完成方法增强,子类实现该方法,并调用父类方法,利用多态oop思想实现子类方法增强
abstract public class Template { //抽象类-模板设计模式 public abstract void job();//抽象方法 public void calculateTime() {//实现方法,调用job方法 //得到开始的时间 long start = System.currentTimeMillis(); job(); //动态绑定机制 //得的结束的时间 long end = System.currentTimeMillis(); System.out.println("任务执行时间 " + (end - start)); } }
public class AA extends Template { //计算任务 //1+....+ 800000 @Override public void job() { //实现Template的抽象方法job long num = 0; for (long i = 1; i <= 800000; i++) { num += i; } } }
public class TestTemplate { public static void main(String[] args) { AA aa = new AA(); aa.calculateTime(); //这里还是需要有良好的OOP基础,对多态 BB bb = new BB(); bb.calculateTime(); } }
21、接口
接口就是一套规范,给出一些没有实现的方法,封装在一起,到某个类使用时,在根据具体情况把这些方法实现。
语法:
interface 接口名{ //属性 //抽象方法 } class 类名 implements 接口{ //自己的属性 //自己的方法 //必须实现的接口中的抽象方法 }
接口时更加抽象的抽象类,抽象类里可以有非抽象方法写具体的有方法体,但是接口中所有的方法都时抽象方法,jdk8之后,可以是静态方法,也可以是用default修饰的默认方法可以有方法体,不用被实现类实现。
default String setTime(String newTime) { return "time set to " + newTime; }
jdk8之后,接口中可以有静态方法,也可以用default修饰的默认方法,也就是说接口中可以有方法的具体实现了。默认方法在实现类中可以直接使用
注意:如果一个类实现了多个接口,多个接口中有同名默认方法,则在实现类中必须重写该默认方法。
注意:
- 接口不能被实例化
- 接口中所有方法都是public方法,接口中的抽象方法,可以不用abstract修饰 void a();
- interface中的方法自动就是public的,即使不显式的声明。
- 如果实现类implementation和接口interface不在一个包package下面,则无法实现类无法实现除了public之外的其他访问权限。
- 一个普通类实现接口,就必须将该接口的所有方法都实现
- 抽象类实现接口,可以不用实现接口的方法
- 一个类可以同时实现多个接口
- 接口中的属性,只能是final的,而且是public、static、final修饰符。比如int a = 1;实际上是public static final int a = 1,必须初始化
- 接口中的属性访问方式:接口名.属性名
- 接口不能继承其他的类,但是可以继承多个别的接口
- 接口的修饰符只能是public和默认,这点和类的修饰符一致
实现接口和继承类
- 实现机制:是对单继承的一种补充
- 例如一只小猴子,他爸爸是只大猴子,小猴子可以继承大猴子的一些特性和特征,这就是继承的特性。再引入鱼、鸟、兔子、猫等动物,小猴子就无法通过继承来获取他们身上的特性和特征了,这就引入了接口,通过接口小猴子就可以获取他们身上的特征了,也就是对小猴子的一个扩展,这样,小猴子成功的学会了游泳。
- 当子类继承了父类,就自动拥有了父类的功能
- 子类不能继承父类的构造器:子类有自己的构造器
- 子类是可以继承父类的私有成员的,只是不能直接访问而己。
- 子类是不能继承父类的静态成员的。子类只是可以访问父类的静态成员,父类的静态成员只有一份可以被子类共享访问,共享并非继承。
- 子类是不继承父类的static变量和方法的。因为这是属于类本身的。但是子类是可以访问的。
子类和父类中同名的static变量和方法都是相互独立的,并不存在任何的重写的关系。- 如果子类需要扩展功能,可以通过实现接口的方式扩展
- 可以理解为java接口是单继承的补充
接口的多态特性
- 接口名 变量名 = new 实现类名();接口类型的变量可以指向,实现了该接口的类的对象实例
- public void m(接口名 变量名){},形参可以传实现类的实例对象,体现多态
- 接口可以传递,A接口继承B接口,,C类实现A接口同时也实现B接口
//多态数组 -> 接口类型数组 Usb[] usbs = new Usb[2]; usbs[0] = new Phone_(); usbs[1] = new Camera_(); /* 给 Usb 数组中,存放 Phone 和 相机对象,Phone 类还有一个特有的方法 call(), 请遍历 Usb 数组,如果是 Phone 对象,除了调用 Usb 接口定义的方法外, 还需要调用 Phone 特有方法 call */ for(int i = 0; i < usbs.length; i++) { usbs[i].work();//动态绑定.. //和前面一样,我们仍然需要进行类型的向下转型 if(usbs[i] instanceof Phone_) {//判断他的运行类型是 Phone_ ((Phone_) usbs[i]).call(); } } interface Usb{ void work(); } class Phone_ implements Usb { public void call() { System.out.println("手机可以打电话..."); } @Override public void work() { System.out.println("手机工作中..."); } }
22、内部类
一个类的内部又完整的嵌套了另一个类结构。被嵌套的类称为内部类,嵌套内部类的类称为外部类。
类的五大成员:属性、方法、构造器、代码块、内部类
内部类最大的特点就是可以直接访问私有属性,并且可以体现类与类之间的包含关系
内部类分类:
- 定义在类中的局部位置(方法体或者代码块中)
- 局部内部类
- 局部内部类定义在外部类的局部位置,如方法体,并且有类名
- 可以直接访问外部类的所有成员,包含私有的,直接访问
- 不能添加访问修饰符,因为它本身地位就是一个局部变量,局部变量是不能使用修饰符的,但是可以用final修饰
- 作用域:仅仅在定义它的方法或者代码块中
- 外部类访问内部类成员,需要创建内部类对象再访问。在定义局部内部类的方法体中创建对象
- 外部其他类不能访问局部内部类
- 如果外部类和局部类的成员重名,默认就近原则,如果此时想访问外部类成员可以使用(外部类名.this.成员)去访问
- 匿名内部类-----没有类名(重要)
- 本质是类,该类没有名字,同时还是一个对象
- 匿名内部类是定义在外部类的局部位置,比如方法体,以形参形式创建。
- new 类或者接口(参数列表){类体}
实现接口的匿名内部类,没有类名,直接实现并创建对象。可以直接使用匿名内部类实现接口,系统底层自动创建实现类
//实现接口的匿名类: interface IA {//接口 public void cry(); } class Outer04{ //jdk底层在创建匿名内部类 Outer04$1,立即马上就创建了Outer04$1实例(运行类型),并且把地址 //返回给 tiger引用 //匿名内部类使用一次,就不能再使用 IA tiger = new IA() { @Override public void cry() { System.out.println("老虎叫唤..."); } }; }
匿名子类(继承父类):
class Animal { private String name; public Animal(String name) { this.name = name; } public void printAnimalName() { System.out.println(bird.name); } } // 鸟类,匿名子类,继承自Animal类,可以覆写父类方法 Animal bird = new Animal("布谷鸟") { @Override public void printAnimalName() { accessTest(); // 访问外部类成员 System.out.println(ANIMAL); // 访问外部类final修饰的变量 super.printAnimalName(); } };
- 操作符:new;
- 一个要实现的接口或要继承的类,案例一中的匿名类实现了HellowWorld接口,案例二中的匿名内部类继承了Animal父类;
- 一对括号,如果是匿名子类,与实例化普通类的语法类似,如果有构造参数,要带上构造参数;如果是实现一个接口,只需要一对空括号即可;
- 一段被"{}"括起来类声明主体;
- 末尾的";"号(因为匿名类的声明是一个表达式,是语句的一部分,因此要以分号结尾)。
匿名内部类既是一个类的定义同时它本身又是一个对象,既有定义类的特征又有创建对象的特征。
可以直接访问外部类的所有成员,包含私有的
不能添加访问修饰符,因为它的地位就是一个局部变量
- 定义在类的成员位置
- 成员内部类
- 静态内部类
23、匿名内部类的最佳实践 ----当实参直接传递
//接口
interface IL {
void show();
}
class Picture implements IL {
@Override
public void show() {
System.out.println("这是一副名画XX...");
}
}
//静态方法,形参是接口类型
public static void f1(IL il) {
il.show();
}
//当做实参直接传递,简洁高效
f1(new IL() {
@Override
public void show() {
System.out.println("这是一副名画~~...");
}
});
//传统方法
f1(new Picture());//动态绑定,接口引用指向实现类对象
24、枚举类
- 对于季节而已,他的对象(具体值),是固定的四个,不会有更多
- 枚: 一个一个 举: 例举 , 即把具体的对象一个一个例举出来的类,就称为枚举类
创建 Season 对象有如下特点:
季节的值是有限的几个值(spring, summer, autumn, winter) 只读,不需要修改。- 枚举对应英文(enumeration, 简写 enum),枚举是一组常量的集合。枚举属于一种特殊的类,里面只包含一组有限的特定的对象
枚举的二种实现方式
自定义类实现枚举
不需要提供setXxx方法,因为枚举对象值通常为只读 对枚举对象/属性使用final+static共同修饰,实现底层优化 枚举对象名通常使用全部大写,常量命名规范 枚举对象根据需要,也可以有多个属性public class Enumeration02 { public static void main(String[] args) { System.out.println(Season.AUTUMN); System.out.println(Season.SPRING); } } class Season {//类 private String name; private String desc;//描述 //定义了四个对象, 固定. public static final Season SPRING = new Season("春天", "温暖"); public static final Season WINTER = new Season("冬天", "寒冷"); public static final Season AUTUMN = new Season("秋天", "凉爽"); public static final Season SUMMER = new Season("夏天", "炎热"); //1. 将构造器私有化,目的防止 直接 new //2. 去掉setXxx方法, 防止属性被修改 //3. 在Season 内部,直接创建固定的对象 //4. 优化,可以加入 final 修饰符 private Season(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } public String getDesc() { return desc; } }
- 构造器私有化
本类内部创建一组对象[四个 春夏秋冬] 可以提供 get 方法,但是不要提供 set 对外暴露对象(通过为对象添加 public final static 修饰符) 使用 enum 关键字实现枚举public class Enumeration03 { public static void main(String[] args) { System.out.println(Season2.AUTUMN); System.out.println(Season2.SUMMER); } } enum Season2 {//类 //定义了四个对象, 固定. // public static final Season SPRING = new Season("春天", "温暖"); // public static final Season WINTER = new Season("冬天", "寒冷"); // public static final Season AUTUMN = new Season("秋天", "凉爽"); // public static final Season SUMMER = new Season("夏天", "炎热"); //如果使用了enum 来实现枚举类 //1. 使用关键字 enum 替代 class //2. public static final Season SPRING = new Season("春天", "温暖") 直接使用 // SPRING("春天", "温暖") 解读 常量名(实参列表) //3. 如果有多个常量(对象), 使用 ,号间隔即可 //4. 如果使用enum 来实现枚举,要求将定义常量对象,写在前面 //5. 如果我们使用的是无参构造器,创建常量对象,则可以省略 () SPRING("春天", "温暖"), WINTER("冬天", "寒冷"), AUTUMN("秋天", "凉爽"), SUMMER("夏天", "炎热")/*, What()*/; private String name; private String desc;//描述 private Season2() {//无参构造器 } private Season2(String name, String desc) { this.name = name; this.desc = desc; } public String getName() { return name; } public String getDesc() { return desc; } }
- 如果使用了enum 来实现枚举类
- 使用关键字 enum 替代 class
- public static final Season SPRING = new Season("春天", "温暖") 直接使用:SPRING("春天", "温暖") 解读 常量名(实参列表)
- 如果有多个常量(对象), 使用 ,号间隔即可
- 如果使用enum 来实现枚举,要求将定义常量对象,写在前面
- 如果我们使用的是无参构造器,创建常量对象,则可以省略 ()
表 1 Enum类的常用方法 方法名称 描述 values() 以数组形式返回枚举类型的所有成员 valueOf() 将普通字符串转换为枚举实例 compareTo() 比较两个枚举成员在定义时的顺序 ordinal() 获取枚举成员的索引位置 Season2.values()-----返回枚举类中所有元素{SPRING,WINTER,AUTUMN,SUMMER}
25、注解
注解(Annotation)也被称为元数据(Metadata),用于修饰解释 包、类、方法、属性、构造器、局部变量等数据信息。和注释一样,注解不影响程序逻辑,但注解可以被编译或运行,相当于嵌入在代码中的补充信息。三个基本的 Annotation:
@Override: 限定某个方法,是重写父类方法, 该注解只能用于方法 @SuppressWarnings: 抑制编译器警告
- ,@SuppressWarnings({"rawtypes", "unchecked", "unused"}),在{""} 中,可以写入你希望抑制(不显示)警告信息
- all,抑制所有警告
boxing,抑制与封装/拆装作业相关的警告 cast,抑制与强制转型作业相关的警告 finally,抑制与未传回 finally 区块相关的警告 fallthrough,抑制与 switch 陈述式中遗漏 break 相关的警告 @Deprecated: 用于表示某个程序元素(类, 方法等)已过时元注解的种类
Retention //指定注解的作用范围,三种 SOURCE,CLASS,RUNTIME
只能用于修饰一个 Annotation 定义, 用于指定该 Annotation 可以保留多长时间 @Retention 的三种值
RetentionPolicy.SOURCE: 编译器使用后,直接丢弃这种策略的注释 RetentionPolicy.CLASS: 编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 不会保留注解。 这是默认值 RetentionPolicy.RUNTIME:编译器将把注解记录在 class 文件中. 当运行 Java 程序时, JVM 会保留注解. 程序可以通过反射获取该注解
26、java异常处理
26.1、异常介绍
执行过程发生的异常事件分为两大类:
- Error(错误):Java虚拟机无法解决的严重问题,如JVM系统内部错误,资源耗尽等。比如栈溢出、OOM,程序会崩溃。
- Exception:其他因变成错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如,空指针访问、视图读取不存在的文件、网络中断等。Exception分为两大类:
- 运行时异常
- 编译器一般检查不出,一般是编程时的逻辑错误,是程序员应该避免出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常
- 对于运行时异常,可以不做处理,因为这类异常很普遍,若全处理可能对程序的可读性和循行效率产生影响。
- 常见的运行时异常:
- NullPointerException 空指针异常
- ArithmeticException 数学运算异常
- ArrayIndexOutOfBoundsException 数组下标越界异常
- ClassCastException 类型转换异常
- NumberFormatException 数字格式不正确异常[]
- 编译时异常-----主要处理的异常
- 是编译器必须处理的异常,否则代码不能编译通过
常见的编译异常
SQLException数据库操作异常,如获取连接,可能没有 IOException操作文件时发生的异常,哟可能文件不存在 FileNotFoundException操作一个不存在的文件发生的异常 ClassNotFoundException加载类,而类不存在发生的异常
26.2、异常处理机制
异常出现时,对异常处理的机制
异常处理的方式
- try-catch-finally
- 程序员在代码中捕获发生的异常,自行处理
- try块用于包含可能出错的代码,catch块用于处理try块里发生的异常
- 如果try块中异常发生了,则异常发生后面的代码不会执行,直接进入到catch块中,如果异常没发生,则顺序执行try的代码块,不会进入catch
- 如果希望不管是否发生异常,都执行某段代码,则用finally{}
- 可以有多个catch语句,捕获不同的异常,要求父类异常在后,子类异常在前,如果发生异常,只会匹配一个catch
- catch住编译时异常,不会影响try-catch后面代码执行
- throws
- 将发生的异常抛出,交给调用者(方法)处理,最顶级的处理者就是JVM
- 如果一个方法中语句执行时可能生成某种异常,但是并不能确定是如何处理这种异常,此方法应显示的声明抛出异常,表明该方法不对这些异常进行处理,而由该方法的调用者负责处理
- 在方法声明中使用throws语句可以声明抛出异常的列表,throws后面的异常类型可以是方法中产生的异常类型,也可以是它的父类。如最大的Exception
- 注意:
- 对于编译异常,程序中必须处理,比如try-catch或者throws
- 对于运行时异常,程序中如果没有处理,默认就是throwa处理
- 子类重写父类方法时,对于异常抛出的规定:子类重写父类方法,所抛出的异常类型要么和父类抛出的异常一致,要么为父类抛出异常的子类型。
- 在throws过程中,如果有方法try-catch,相当于处理异常,就可不必throws
26.3、自定义异常
自定义异常:
- 定义类:自定义异常类名继承Exception或RuntimeException
- 如果继承Exception就属于编译异常
- 如果继承RuntimeException,属于运行时异常(一般来说继承RuntimeException)
throw 和 throws 的区别
public class CustomException { public static void main(String[] args) /*throws AgeException*/ { int age = 180; //要求范围在 18 – 120 之间,否则抛出一个自定义异常 if(!(age >= 18 && age <= 120)) { //这里我们可以通过构造器,设置信息 throw new AgeException("年龄需要在 18~120之间"); } System.out.println("你的年龄范围正确."); } } //自定义一个异常 //老韩解读 //1. 一般情况下,我们自定义异常是继承 RuntimeException //2. 即把自定义异常做成 运行时异常,好处时,我们可以使用默认的处理机制 //3. 即比较方便 class AgeException extends RuntimeException { public AgeException(String message) {//构造器 super(message); } }
自定义异常步骤:
- 编写一个类继承Exception或者RuntimeException
- 提供两个构造方法,一个无参构造,一个带有String message参数的
class AgeException extends RuntimeException { public AgeException(String message) {//构造器 super(message); } }
26.4、异常常用方法
1、e.getMessage()------获取异常简单信息
2、e.printStackTrace()-----打印异常追踪的堆栈信息
出异常可以看出哪些原因,从上往下看,从自己写的程序开始主要第一行,跳过sun公司程序
java.io.FileNotFoundException: \\sss (指定的路径无效。)
at java.io.FileInputStream.open0(Native Method)
at java.io.FileInputStream.open(FileInputStream.java:195)
at java.io.FileInputStream.<init>(FileInputStream.java:138)
at java.io.FileInputStream.<init>(FileInputStream.java:93)
//我们自己写的包下的程序:17行at com.usst.exercise.exception_.ExceptionTest01.main(ExceptionTest01.java:17)
26.5、异常与方法覆盖
子类重写的方法,不能比父类抛出更多异常或更宽泛异常,可以更少,且为父类异常及其子类:父类方法抛异常,子类可以不抛,也可以抛相同异常或者子类异常
父类不抛,子类重写不能抛
注意:一般父类方法抛什么异常,子类重写该方法就抛什么异常就行。直接拷贝父类方法定义样式,重写方法体,即可。