本节目标:
- 代码块
- 继承的定义与使用
- 重写(override)
- final关键字
- 多态性
- 内部类的定义与使用
1. 代码块(笔试、面试常考知识点)
主类的静态代码块(1次)—> 主方法—>非主类的静态块( 1次 )—>构造块(即非静态块)—> 构造方法
1.1普通代码块:定义在方法中的代码块
public class Test{
public static void main(String[] args) {
{ //直接使用{}定义,普通方法块
int x = 10 ;
System.out.println("x = " +x);
}
int x = 100 ;
System.out.println("x = " +x);
}
}
一般如果方法中代码过长,为避免变量重名,使用普通代码块。(使用较少,了解概念即可)
1.2构造块(重点):定义在类中的代码块(不加修饰符)
class Person{
{ //定义在类中,不加任何修饰符,构造块
System.out.println("1.Person类的构造块");
}
public Person(){
System.out.println("2.Person类的构造方法");
}
}
public class Test{
public static void main(String[] args) {
new Person();
new Person();
}
}
构造块需要注意:
- 在对象产生时,构造块优先于构造方法执行,有几个对象就调用几次构造块
- 构造块在调用构造方法执行前,作用是完成一些属性的初始化操作
1.3静态块:使用static定义的代码块
1>在非主类中的静态代码块
class Person{
{ //定义在类中,不加任何修饰符,构造块
System.out.println("1.Person类的构造块");
}
public Person(){
System.out.println("2.Person类的构造方法");
}
static { //定义在非主类中的静态块
System.out.println("3.Person类的静态块");
}
}
public class Test{
public static void main(String[] args) {
System.out.println("--start--");
new Person();
new Person();
System.out.println("--end--");
}
}
上述代码运行结果为:
此代码运行结果:
--start--
3.Person类的静态块
1.Person类的构造块
2.Person类的构造方法
1.Person类的构造块
2.Person类的构造方法
--end--
所以可以得出结论:
- 在类加载时执行(什么时候用到这个类 – 类加载),优先于构造块执行
- 无论有多少对象产生,只会调用一次。new Person(); 每出现一次,就会有1.2.各一次(构造块和构造方法一起出现)
- 静态块的主要作用是为static属性进行初始化
2> 在主类中的静态代码块
public class Test{
{
System.out.println("1.Test的构造块");
}
public Test(){
System.out.println("2.Test的构造方法");
}
static{
//在主类中的静态代码块
System.out.println("3.Test的静态块");
}
public static void main(String[] args) {
System.out.println("--start--");
new Test();
new Test();
System.out.println("--end--");
}
}
上述代码的运行结果:
3.test的静态块
--start--
1.test的构造块
2.test的构造方法
1.test的构造块
2.test的构造方法
--end--
可以得出结论:在主类中定义的静态块,优先于主方法(main)执行(执行一定在第一位!)
综合可以得出:如果一些属性需要在使用前做处理,可以考虑使用代码块。
1.4同步代码块(多线程部分记录)
2. 继承的定义与使用
面向对象的第二大特征:继承。
继承的主要作用在于,在已有基础上继续进行功能的扩充。
引出继承:单独的java类,含有大量重复性代码。比如,按照之前方法定义一个人类和学生类,不仅代码上重复,而且从概念上讲,学生一定是人,学生和人相比学生更加具体,学生类描述的范围更小,具备的属性更多,具有的方法也更多。
这个时候要想消除结构定义上的重复,就要用到继承。
2.1继承的实现:继承使用extends关键字来实现
class 子类 extends 父类
1>子类在一些书上也被称为派生类,父类也被称为超类(Super Class)
2>当发生了类继承关系之后,子类可以直接继承父类的操作,可以实现代码的重用。子类最低也维持和父类相同的功能。子类可以进行功能的扩充。例如:扩充属性和方法。
范例:子类进行功能的扩充
class Student extends Person{ //定义了一个子类
private String school; //扩充的新属性
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.setName("Steven");
student.setAge(18);
student.setSchool("高新一中");
System.out.println("姓名:"+student.getName()+",年龄:"+student.getAge()+",学
校:"+student.getSchool());
}
}
由以上代码也可以体现出继承的主要作用是对类进行扩充以及代码的重用。
2.2继承的限制
- 子类对象在进行实例化前一定会首先实例化父类对象。默认调用父类的构造方法后再调用子类构造方法进行子类对象初始化。
- Java只允许单继承,不允许多继承。(一个子类只能继承一个父类。)
- 在进行继承的时候,子类会继承父类的所有结构。(包含私有属性、构造方法、普通方法)但是这个时候需要注意的是:
显示继承(可以直接调用): 所有的非私有操作,
隐式继承(通过其他形式调用,例如setter或getter):所有的私有操作
以下详细分析以上三点
1>范例:观察子类对象创建
class Person{
public Person(){
System.out.println("**Person类对象产生**");
}
}
class Student extends Person{
public Student(){
super() ; //此语句在父类构造方法无参时写于不写一样
//同时需要注意的是,如果父类里没有提供无参构造,那么这个时候就必须使用super()明确指明你要调用的父类构造方法。
System.out.println("**Student类对象产生**");
}
}
public class Test{
public static void main(String[] args) {
new Student();
}
}
super( ) ;
//此语句在父类构造方法无参时写于不写一样
//同时需要注意的是,如果父类里没有提供无参构造,那么这个时候就必须使用super()明确指明你要调用的父类构造方法。
2>若要多层继承,可以写成这样(Java不允许多重继承,但允许多层继承):
class A{}
class B extends A{}
class C extends B{}
3>隐式继承范例:
class Person{
private String name; //继承私有属性使用getter、setter
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Student extends Person{
public void fun(){
System.out.println(getName());
}
}
public class Test {
public static void main(String[] args) {
Student student = new Student();
student.setName("Steven");
System.out.println(student.getName());
student.fun();
}
}
继承总结:
- 继承的语法以及继承的目的(扩展已有类的功能,使代码重用)
- 子类对象的实例化流程:不管如何操作,一定要先实例化父类对象。
- 不允许多重继承,只允许多层继承。
3. 覆写(override)
子类定义了与父类相同的方法或属性(不算权限)的时候,这样的操作就称为覆写
3.1方法的覆写(重点)【研究抽象类和接口的第一步】
1>概念:子类定义了与父类方法名称、参数类型及个数完全相同的方法。但是被覆写不能够拥有比父类更为严格的访问控制权限。
范例:一个简单的覆写
class Person{
public void print(){
System.out.println("1.[Person]类的print方法");
}
}
class Student extends Person{
public void print(){
System.out.println("2.[Student]类的print方法");
}
}
public class Test{
public static void main(String[] args) {
new Student().print();
}
}
2>判断到底是父类方法 or 子类方法:
a.看new在哪(当前使用的对象是通过哪个类new的)
b.调用的方法有没有被子类覆写,如果被覆写,调用的一定是被覆写后的方法。
private<default<public。那么也就意味着如果父类使用public进行方法声明,那么子类必须也使用public;如果父类使用default,那么子类可以使用default或者public。
建议(方法覆写不能出现private关键字):
- 以后写方法时,99.99%的情况下建议使用public。
- 写属性,98%情况下建议使用private。
3>问题:如果现在父类方法使用private定义,子类中使用public覆写,对吗?
答:
-如果现在父类方法使用了private定义,那么就表示该方法只能被父类使用,子类无法使用。换言之,子类根本就不知道父类有这样的方法。
-这个时候该方法只是子类定义的新方法而已,并没有和父类的方法有任何关系。
4>面试题:请解释重载(overload)和覆写(override)的区别
再次强调:为了良好的设计,在重载时请保持方法返回类型一致。
3.2 属性的覆写(了解)
-当子类定义了和父类属性名称完全相同的属性的时候,就成为属性的覆盖。
-这种操作本身没有任何意义,其核心的原因在于:类中的属性都要求使用private封装,一旦封装了,子类不知道父类具有什么属性,那么也就不存在属性覆盖的问题了。了解概念即可。
3.3super关键字
super的两个作用:
- 上面在子类对象实例化操作的时候提到过super(),当时的主要作用是子类调用父类构造方法时才使用的。
- 那么在进行覆写的操作过程之中,子类也可以使用super.方法()/super.属性明确调用父类中的方法或属性
1>super用于方法
①用于构造方法(重要)
当子类构造无参构造时,super( )可写可不写,表示调用父类无参构造
当子类调用父类有参构造时,super(参数列表)必须要写,要告诉编译器当前调用的是哪个有参构造。
a.子类构造方法中调用父类构造必须是第一行语句
b.this与super不能同时调用
②用于普通方法 super.方法名(参数)
用于在子类中明确调用父类被覆写的方法 – 在子类覆写父类的方法中加上super.fun() 即可调用父类中的fun
2>super用于属性(了解)
super.属性名 — 表示调用父类中被覆写的属性(权限不是private)
3>this和super的区别
最后注意:
能使用super.方法()一定要明确标记出是父类的操作。
1.子类覆写父类的方法是因为父类的方法功能不足才需要覆写。
2.方法覆写的时候使用的就是public权限
4. final关键字 - 终结器
1> final修饰类(String类以及8大基本数据类型的包装类,Integer)
当一个类被final修饰,表示该类不允许被继承(不能拥有子类)
一旦一个类被final修饰,该类的所有方法都会默认加上final。(成员变量不会加final)
2> final修饰方法
当一个方法被final修饰,明确表示该方法不允许被覆写。
当一个方法被private修饰后,相当于加上了final,不能被覆写
3> final修饰属性 - 常量
①final修饰普通数据类型的成员变量(最主要的用途)
被final修饰的成员变量必须在声明时初始化(构造块或构造方法中初始化)
,并且初始化后值无法被修改
- final变量- 即常量(值不能改变,每个对象都有自己的final变量,在对象产生时初始化)
- static final - 全局常量(所有对象共享此变量,并且在类加载时(静态块初始化)初始化,效率较高,通过类名调用)
全局常量命名规范:多个单词全大写,单词间_分隔
②final修饰引用数据类型变量 (值不能改变)
可以修改一个new中的属性值,只是地址值不能变,不能重新指向(new)其它一个地址。
被final修饰的变量,值与类型都不能变
eg:byte b = 2; //此时b为int型
final byte b = 2; //此时b仍为byte型
有一个是double类型的,另一个将会被转换成double类型,并且结果也是double类型
有一个是float类型的,另一个将会被转换为float类型,并且结果也是float类型
有一个是long类型的,另一个将会被转换成long类型,并且结果也是long类型
操作数为:byte、short、int 、char,两个数都会被转换成int类型,并且结果也是int类型。
5. 多态性
在Java中,对于多态的核心表现主要有以下两点:
- 方法的多态性: ①方法的重载:同一个方法名称可以根据参数的类型或个数不同调用不同的方法体 ②方法的覆写:同一个父类的方法,可能根据实例化子类的不同也有不同的实现。
- 对象的多态性【抽象类和接口才能体会到实际用处】(前提:方法覆写):【自动,90%】①对象的向上转型:父类 父类对象 = 子类实例。 【强制,1%】②对象的向下转型:子类 子类对象 = (子类)父类实例。
1>向上转型(90%):用于参数统一化
父类 父类引用 = new 子类();//子类对象给父类用 --- 自动
2>向下转型:当父类引用需要调用子类扩充方法时,才需要向下转型
子类 子类引用 =(子类)父类引用; //强转 -- 容易运行时异常
要发生向下转型,必须先发生向上转型(认爹)
运行时异常:ClassCastException (类型转换异常)
引用名 instanceof 类:表示该引用是否能表示该类实例(向下转型之前用instanceof判断)boolean —> 表示该引用是否能表示该类实例(向下转型之前用instanceof判断)
多态性总结:
- 对象多态性的核心在于方法的覆写。
- 通过对象的向上转型可以实现接收参数的统一,向下转型可以实现子类扩充方法的调用(一般不操作向下转型,有安全隐患)。
- 两个没有关系的类对象是不能够进行转型的,一定会产生ClassCastException。
6. 内部类的定义与使用
6.1内部类的基本概念
所谓内部类是指在类内部进行其他类结构嵌套操作
范例:一个简单的内部类
class Outer{
private String msg = "Hello World" ;
// ********************************
class Inner{ //定义一个内部类
public void print(){ //定义一个普通方法
System.out.println(msg); //调用msg属性
}
}
// ********************************
//在外部类中定义一个方法,该方法负责产生内部类对象并且调用print()方法
public void fun(){
Inner in = new Inner(); //内部类对象
in.print(); // 内部类提供的print()方法
}
}
public class Test{
public static void main(String[] args) {
Outer out = new Outer(); //外部类对象
out.fun(); //外部类方法
}
}
从以上代码可以看出:内部类可以方便的操作外部类的私有访问。
6.2内部类为什么存在
内部类的优点:
a.内部类与外部类可以方便的访问彼此的私有域(包含私有方法、私有属性)
b.内部类是另外一种封装(保护性),对外部的其他类隐藏(心脏包在人身体里)
c.内部类可以实现Java单继承的局限(使用内部类来实现"多继承")
缺点:结构复杂
范例:使用内部类实现多继承
class A {
private String name = "A类的私有域";
public String getName() {
return name;
}
}
class B {
private int age = 20;
public int getAge() {
return age;
}
}
class Outter {
private class InnerClassA extends A {
public String name() {
return super.getName();
}
}
private class InnerClassB extends B {
public int age() {
return super.getAge();
}
}
public String name() {
return new InnerClassA().name();
}
public int age() {
return new InnerClassB().age();
}
}
public class Test {
public static void main(String[] args) {
Outter outter = new Outter();
System.out.println(outter.name());
System.out.println(outter.age());
}
}
6.3内部类与外部类的关系
a.对于非静态内部类,内部类的创建需要依赖外部类对象,在没有外部实例之前无法创建非静态内部类
b.内部类是一个相对独立的个体,与外部类没有 is-a 关系
c.内部类可以直接访问外部类的元素(包含私有域),但是外部类不可以直接访问内部类元素,需要通过内部类的引用间接访问。
范例1:内部类可以直接访问外部类元素
class Outter {
private String outName;
private int outAge;
class Inner {
private int InnerAge;
public Inner() {
Outter.this.outName = "I am Outter class";
Outter.this.outAge = 20;
}
public void display() {
System.out.println(outName);
System.out.println(outAge);
}
}
}
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter().new Inner();
inner.display();
}
}
范例2:外部类可以通过内部类引用间接访问内部类元素
class Outter {
public void display() {
// 外部类访问内部类元素,需要通过内部类引用来访问
Inner inner = new Inner();
inner.display();
}
class Inner {
public void display() {
System.out.println("I am InnerClass");
}
}
}
public class Test {
public static void main(String[] args) {
Outter out = new Outter();
out.display();
}
}
6.4创建内部类
1> 在外部类外部创建内部类语法
①创建非静态内部类
外部类.内部类 内部类引用 = new 外部类().new 内部类() ;
eg:Outter.Inner in = new Outter().new Inner() ;
//先声明外部类对象,再声明内部类对象 new
②创建静态内部类
外部类.内部类 内部类引用 = new 外部类.内部类();
eg: Outter.Inner iin = new Outter.Inner();
2>在外部类内部创建内部类语法
像创建普通类对象一样
Inner in = new Inner();
6.5内部类的分类
存在私有内部类(静态、成员均可)
ArrayList中Node内部类、HashMap中Entry内部类(私有内部类)
①成员内部类(成员方法)
a.成员内部类内部不能存在任何static变量或方法,但是可以访问外部类的静态域(static静态变量一定不依赖对象,即不能在类方法中使用)
b.成员内部类依附外部类,所以只有创建了外部类才能创建内部类
②静态内部类(类比静态方法)
a.静态内部类的创建不需要依赖外部类,可以直接创建
b.静态内部类不可以使用任何外部类的非static域(包含属性与方法),但可以存在自己的成员变量
③方法内部类
a.方法内部类不允许使用访问权限修饰符 public private protected均不允许
b.方法内部类对外部完全隐藏,除了创建这个类的方法可以访问它以外,其他地方均不能访问
c.方法内部类如果想要使用方法形参,该形参必须使用final声明(JDK8将形参变为隐式final声明)
④匿名内部类(lamdba表达式前身)
即没有名字的方法内部类。因此特点与方法内部类完全一样,除此之外,还有两个自己的特点:
a.匿名内部类必须继承一个抽象类或者实现一个接口
b.匿名内部类是没有构造方法的,因为它没有类名。
范例:使用static创建静态内部类
class Outer{
private static String msg = "Hello World" ;
// ********************************
static class Inner{ //定义一个内部类
public void print(){ //此时只能使用外部类中的static操作
System.out.println(msg); //调用msg属性
}
}
// ********************************
//在外部类中定义一个方法,该方法负责产生内部类对象并且调用print()方法
public void fun(){
Inner in = new Inner(); //内部类对象
in.print(); // 内部类提供的print()方法
}
}
public class Test{
public static void main(String[] args) {
Outer.Inner in = new Outer.Inner();
in.print();
}
}
范例2:方法内部类
class Outter {
private int num;
public void display(int test) { //此处隐式final声明
class Inner {
private void fun() {
num++;
System.out.println(num);
System.out.println(test);
}
}
new Inner().fun();
}
}
public class Test {
public static void main(String[] args) {
Outter out = new Outter();
out.display(20);
}
}
总结:内部类
成员内部类(成员方法):
成员内部类不能拥有静态域,但可以访问外部静态域
静态内部类(静态方法):
静态内部类不能访问非static成员变量,但可以拥有成员变量
内部类特点:
- 破坏了程序的结构
- 方便进行私有属性的访问。(外部类也可以访问内部类的私有域)
- 如果发现类名称上出现了".",应当立即想到内部类的概念。