Java面向对象高级
1. 继承(子类与父类)
概念:
- 继承是Java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
- 继承就是子类继承父类的特征(属性)和行为(方法),使得子类对象(实例)具有父类的实例和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
格式:
class父类{
}
class子类extends父类{
}
继承的限制:
- Java中只有单继承、多重继承,没有多继承。
- 即继承是线性的。子类c只有一个父b类,子类c可以继承父类b继承的父类a,子类不能同时继承两个父类。
内存分析:
- 创建子类对象时,会在堆中首先创建其父类对象内存,再创建子类对象内存,且父类对象内存地址存储在子类对象的super中。(继承即子类拥有父类地址)
- 子类调用属性时首先会在父类中寻找,其次再从父类的父类中找。是谁的属性就存储再谁的地址当中。
2. 子类中的super
概念: 在子类中super表示自动被创建的父类对象,子类创建多少个就会自动创建多少个父类对象。
super作用: 访问父类的构造方法、属性、方法。
访问父类的构造方法:super();
eg(默认格式,可省略):
class Student extends Person{
public Student(){
super();
}
}
eg:(当父类只有有参构造方法时,不可省略,需要用super调用父类):
class Student extends Person{
public Student(){
super(“abc”);
}
}
注意:调用super构造方法的代码,必须写在子类构造方法的第一行!
访问父类的属性:super.父类中的属性
访问父类的方法:super.父类中的方法()
3. 重写,重写与重载的区别
重写的概念:
- 参数列表必须完全与被重写的方法相同;
- 返回类型必须完全与被重写的方法的返回类型相同;
- 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected;
- 父类的成员方法只能被它的子类重写;
- 声明为static和private的方法不能被重写,但是能够被再次声明;
eg:
class Person{
public void say(int age){}
}
Class Student extends Person{
public void say(int age){}
}
重写的注解:@Override
注解可省略,位于重写方法的上方,但是注解下的方法必须为重写的方法,否则报错。
Java中重写(Override)与重载(Overload)的区别:
- 发生的位置:
重写:子父类中
重载:一个类中 - 参数列表限制:
重写:必须相同
重载:必须不同 - 返回值类型:
重写:返回值类型必须一致
重载:与返回值类型无关 - 访问权限:
重写:子的方法权限必须不小于父的方法权限
重载:与访问权限无关 - 异常处理:
重写:异常范围可以更小,但是不能抛出新的异常
重载:与异常无关
4. final关键字
final的作用: 用于修饰属性、变量;类;方法。
final用于修饰属性、变量: 变量成为了常量,无法对其再次进行赋值
- fianl修饰的局部变量。只能赋值一次(可以先声明后赋值)
- final修饰的是成员属性,必须在声明时赋值。
- 全局常量(public static final)
常量命名规范: 由1个或多个单词组成,单词与单词之间必须使用下划线隔开,单词中所有字母大写。
eg:SQL_INSERT
final用于修饰类: final修饰的类,不可以被继承。
final用于修饰方法: final修饰的方法,不能被子类重写。
5. 抽象类
概念: 一个类中存在不能确定的方法称为抽象类。抽象类必须使用abstract class声明一个抽象类中可以没有抽象方法。抽象方法必须写在抽象类或者接口中。
抽象类的格式:abstract class类名{//抽象类}
抽象方法: 只声明而未实现的方法称为抽象方法(未实现指的是:没有“{}”方法体),抽象方法必须使用abstract关键字声明。
抽象方法的格式:
abstract class类名{//抽象类
public abstract void方法名();//抽象方法,只声明而未实现
}
不能被实例化:
- 抽象类本身是不能直接进行实例化操作的,即:不能直接使用关键字new完成。
- 一个抽象类必须被子类所继承,被继承的子类(如果不是抽象类)则必须覆写(重写)抽象类中的全部抽象方法。
常见问题:
- 抽象类能否使用final声明?
不能,因为final属性修饰的类是不能有子类的,而抽象类必须有子类才有意义,所以不能。 - 抽象类能否有构造方法?
能有构造方法,而且子类对象实例化的时候的流程与普通类的继承是一样的,都是要先调用父类中的构造方法(默认是无参的),之后再调用子类自己的构造方法。
抽象类和普通类的区别:
- 抽象类必须用public或protected修饰(如果为private修饰,那么子类则无法继承,也就无法实现其抽象方法)。默认缺省为public
- 抽象类不可以使用new关键字创建对象,但是在子类创建对象时,抽象父类也会被JVM实例化。
- 如果一个子类继承抽象类,那么必须实现其所有的抽象方法。如果有未实现的抽象方法,那么子类也必须定义为abstract类。
6. 接口interface定义—implements实现
概念: 如果一个类中的全部方法都是抽象方法,全部属性都是全局常量,那么此时就可以将这个类定义成一个接口。(全为抽象)
格式:
interface接口名称{
全局常量;
抽象方法;
}
面向接口编程思想:
这种思想是接口是定义(规范,约束)与实现(名实分离的原则)的分离。
优点:
- 降低程序的耦合性
- 易于程序的扩展
- 有利于程序的维护
全局常量和抽象方法的简写: 因为接口本身都是由全局常量和抽象方法组成,所以接口中的成员定义可以简写:
1、 全局常量编写时,可以省略public static final关键字,例如:
public static final String INFO="内容";
简写后:
String INFO="内容";
2、 抽象方法编写时,可以省略publicabstract关键字,例如:
public abstract void print();
简写后:
void print();
接口的实现implements:
注意:接口可以多实现!
格式:
class子类implements父接口1,父接口2...{}
以上的代码称为接口的实现。那么如果一个类即要实现接口,又要继承抽象类的话,则按照以下的格式编写即可:
class子类extends父类implements父接口1,父接口2...{}
接口的继承: 接口因为都是抽象部分,不存在具体的实现,所以允许多继承。
interface C extends A,B{ }
接口和抽象类的区别:
- 抽象类要被子类继承,接口要被类实现。
- 接口只能声明抽象方法,抽象类中可以声明抽象方法,也可以写非抽象方法。
- 接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
- 抽象类使用继承来使用,无法多继承。接口使用实现来使用,可以多实现。
- 抽象类中可以包含static方法,但是接口中不允许(静态方法不能被子类重写,因此接口中不能声明静态方法)。
- 接口不能有构造方法,但是抽象类可以有。
7. 多态
概念: 多态就是对象的多种表现形式,(多种体现形态)
多态的体现:
-
对象的多态性,从概念上非常好理解,在类中有子类和父类之分,子类就是父类的一种形态,对象多态性就从此而来。
-
ps:方法的重载和重写也是多态的一种,不过是方法的多态(相同方法名的多种形态)。
- 重载:一个类中方法的多态性体现
- 重写:子父类中方法的多态性体现。
多态的使用:对象的类型转换: 类似于基本数据类型的转换
-
向上转型:将子类实例变为父类实例
父类父类对象=子类对象实例;
-
向下转型:将父类实例变为子类实例
子类子类对象=(子类)父类对象实例;
注意:向下转型为子类的父类实例对象必须由相同的子类对象实例向上转型而来。
eg:
Student s=new Student();
Nurse n=new Nurse();
Personp1=s;
Personp2=n;
Studenta1=(Student)p1;
Nursea2=(Nurse)p2;
8. 判断类实例instanceof
作用: 判断某个对象是否是指定类的实例,则可以使用instanceof关键字,返回boolean类型。
格式: 实例化对象instanceof类 //此操作返回boolean类型的数据
9. 公共类Object类
概念:
- Object类是所有类的父类(基类),如果一个类没有明确的继承某一个具体的类,则将默认继承Object类。
- 例如我们定义一个类:
public class Person{}
- 其实它被使用时是这样的:
public class Person extends Object{}
Object的多态: 使用Object可以接收任意的引用数据类型
10. 类中toString
Object的toString方法,返回对象的内存地址。建议重写Object中的toString方法,用来描述该类。此重写的方法的作用:返回对象的字符串表示形式。
eg(结果为:这是一个人):
Public class Person{
Public String toString(){
return“这是一个人”;
}
}
//另一个.java文件
Public class Main{
public void main(String args[]){
Personp=new Person();
System.out.println(p);
}
}
11. 类中equals
Object的equals方法: 实现了对象上最具区别的可能等价关系;也就是说,对于任何非空引用值x和y,当且仅当x和y引用同一对象(x==y具有值true)时,此方法返回true。比较的是内存地址。
建议重写Object中的equals(Objectobj)方法,重写的方法的作用:指示某个其他对象是否“等于”此对象。
equals方法重写时的五个特性:
- 自反性:对于任何非空的参考值x,x.equals(x)应该返回true。
- 对称性:对于任何非空引用值x和y,x.equals(y)应该返回true当且仅当y.equals(x)回报true。
- 传递性:对于任何非空引用值x,y和z,如果x.equals(y)回报true且y.equals(z)回报true,然后x.equals(z)应该返回true。
- 一致性:对于任何非空引用值x和y,多次调用x.equals(y)始终返回true或始终返回false,前提是未修改对象上的equals比较中使用的信息。
- 非空性:对于任何非空的参考值x,x.equals(null)应该返回false。
eg:
Public class Person{
public boolean equals(Objecto){
if(this==o){
return true;
}
if(o==null){
return false;
}
if(o instanceof Person){
Personp2=(Person)o;
if(this.name.equals(p2.name)){
return true;
}
}
return false;
}
}
12. 内部类概述(内部类实际应用较少)
概念: 在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:
- 成员内部类
- 局部内部类
- 匿名内部类
- 静态内部类
13. 成员内部类
概念: 成员内部类是最普通的内部类,它的定义为位于另一个类的内部。
classOuter{
private double x=0;
public Outer(double x){
this.x=x;
}
Class Inner{//内部类
public void say(){
System.out.println("x="+x);
}
}
}
特点: 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。
内部类使用外部类的同名成员:
外部类.this.成员变量
外部类.this.成员方法
外部使用成员内部类(需要依赖外部类):
Outter outter=new Outter();
Outter.Inner inner=outter.new Inner();
14. 局部内部类
概念: 局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
eg:
class Person{
public Person(){
}
}
class Man{
public Man(){
}
public People get Person(){
class Student extends People{//局部内部类
int age=0;
}
return new Student();
}
}
注意: 局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
15. 匿名内部类
概念: 可以用于创建抽象类或接口的匿名对象,匿名内部类由于没有名字,所以它的创建方式有点儿奇怪。
格式:
new 父类构造器(参数列表)|实现接口(){
//匿名内部类的类体部分
}
在这里我们看到使用匿名内部类我们必须要继承一个父类或者实现一个接口,当然也仅能只继承一个父类或者实现一个接口。同时它也是没有class关键字,这是因为匿名内部类是直接使用new来生成一个对象的引用。当然这个引用是隐式的。
注意:
- 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
- 匿名内部类中是不能定义构造函数的。
- 匿名内部类中不能存在任何的静态成员变量和静态方法。
- 匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
- 只能访问final型的局部变量
16. 局部+匿名内部类只能访问fianl型的局部变量
局部内部类访问的局部变量会默认为fianl型(可省略不写),因为在程序运行时,局部内部类会单独生成一个.class文件,此时会把访问的局部变量备份到该文件的存储地址中,因此该局部变量不能在运行过程中进行更改,否则会造成运行出错。
eg(此时局部变量a和b均为final型局部变量):
public static void main(String[] args){
int a;
final int b;
class inter{
System.out.println(a+b);
}
}
17. 静态内部类
概念: 静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类对象的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法.
外部使用成员静态内部类(不需要依赖外部类): Outter.Inner inner=new Outter.Inner();
18. 包装类
概述:
- 包装类存储在堆中
- 可以将基本数据类型包装为类对象,用于传递类变量。
- 在Java中有一个设计的原则“一切皆对象”,那么这样一来Java中的一些基本的数据类型,就完全不符合于这种设计思想,因为Java中的八种基本数据类型并不是引用数据类型,所以Java中为了解决这样的问题,引入了八种基本数据类型的包装类。
序号 | 基本数据类型 | 包装类 |
---|---|---|
1 | int | Integer |
2 | char | Character |
3 | float | Float |
4 | double | Double |
5 | boolean | Boolean |
6 | byte | Byte |
7 | short | Short |
8 | long | Long |
以上的八种包装类,可以将基本数据类型按照类的形式进行操作。
但是,以上的八种包装类也是分为两种大的类型的:
- Number:Integer、Short、Long、Double、Float、Byte都是Number的子类表示是一个数字。
- Object:Character、Boolean都是Object的直接子类。
装箱和拆箱操作:
- 装箱操作:将一个基本数据类型变为包装类
- 拆箱操作:将一个包装类变为一个基本数据类型
以下以Integer和Float为例进行操作
因为所有的数值型的包装类都是Number的子类,Number的类中定义了如下的操作方法,以下的全部方法都是进行拆箱的操作。
序号 | 方法 | 描述 |
---|---|---|
1 | public bytebyteValue() | 用于Byte->byte |
2 | public abstract doubledoubleValue() | 用于Double->double |
3 | public abstract floatfloatValue() | 用于Float->float |
4 | public abstract intintValue() | 用于Integer->int |
5 | public abstract longlongValue() | 用于Long->long |
6 | public shortshortValue() | 用于Short->short |
在JDK1.4之前,如果要想装箱,直接使用各个包装类的构造方法即可。
eg:
int temp=10;//基本数据类型
Integer x=new Integer(temp);//将基本数据类型变为包装类
在JDK1.5,Java新增了自动装箱和自动拆箱,而且可以直接通过包装类进行四则运算和自增自建操作。
eg:
Float f=10.3f;//自动装箱
float x=f;//自动拆箱
System.out.println(f*f);//直接利用包装类完成
System.out.println(x*x);//直接利用包装类完成
字符串转换: 使用包装类还有一个很优秀的地方在于:可以将一个字符串变为指定的基本数据类型,此点一般在接收输入数据上使用较多。
格式: 包装类.parseInt(字符串) 或 包装类.valueOf(字符串)
在Integer类中提供了以下的操作方法:
将String变为int型数据:public static integer.parseInt(Strings)
eg:intx=Integer.parseInt(“str”)
在Float类中提供了以下的操作方法:
将String变为Float:public static float parseFloat(String s)
在Boolean类中提供了以下操作方法:
将String变为boolean:public static boolean parseBoolean(String s)
…
19. 可变参数类
概念: 一个方法中定义完了参数,则在调用的时候必须传入与其一一对应的参数,但是在JDK1.5之后提供了新的功•能,可以根据需要自动传入任意个数的参数。
格式:
返回值类型 方法名称(数据类型…参数名称){
//参数在方法内部,以数组的形式来接收
}
注意:可变参数只能出现在参数列表的最后。
eg:
int numGet(int a, int…num){
int a;
for(int i=0;i<num.length;i++){
a = num[i];
}
}
20. 递归
注意: 能使用循环尽量不要用递归!容易造成内存溢出,比如栈只有2M。
概念: 递归,在数学与计算机科学中,是指在方法的定义中使用方法自身。也就是说,递归算法是一种直接或者间接调用自身方法的算法。