java-面向对象

面向对象详解

前言:接触项目开发也有很长一段时间了,最近开始萌发出想回过头来写写以前学过的基础知识的想法。

  • 一是原来刚开始学习接触编程,一个人跌跌撞撞摸索着往前走,初学的时候很多东西理解的也懵懵懂懂,后来实践的多了,有些东西才慢慢清楚;
  • 二是经过一定的实践之后,反过头来再去学习一些基础东西才能够理解的更透彻;
  • 三是有些东西基础但是确很重要,是值得好好搞一搞的。

1、面向对象

  面向对象(Object Oriented)是一种新兴的程序设计方法,或者是一种新的程序设计规范(paradigm),其基本思想是使用对象、类、继承、封装、多态等基本概念来进行程序设计。从现实世界中客观存在的事物(即对象)出发来构造软件系统,并且在系统构造中尽可能运用人类的自然思维方式。

2、对象

  对象是系统中用来描述客观事物的一个实体,它是构成系统的一个基本单位。一个对象由一组属性和对这组属性进行操作的一组服务组成。

    类的实例化可生成对象,一个对象的生命周期包括三个阶段:生成、使用、消除。

  当不存在对一个对象的引用时,该对象成为一个无用对象。Java的垃圾收集器自动扫描对象的动态内存区,把没有引用的对象作为垃圾收集起来并释放。当系统内存用尽或调用System.gc( )要求垃圾回收时,垃圾回收线程与系统同步运行。

3、类

  类是具有相同属性和方法的一组对象的集合,它为属于该类的所有对象提供了统一的抽象描述,其内部包括属性和方法两个主要部分。在面向对象的编程语言中,类是一个独立的程序单位,它应该有一个类名并包括属性和方法两个主要部分。

    Java中的类实现包括两个部分:类声明和类体。

类声明

public][abstract|final] class className [extends superclassName] [implements interfaceNameList]{……}

类体

class className{
    [public | protected | private ] [static] [final] [transient] [volatile] type variableName;//成员变量
    [public | protected | private ] [static] [final | abstract] [native] [synchronized] returnType methodName([paramList]) [throws exceptionList]{
        statements
    }//成员方法
}

成员变量限定词的含义

  • static: 静态变量(类变量)
  • final: 常量;transient: 暂时性变量,用于对象存档,用于对象的串行化
  • volatile: 贡献变量,用于并发线程的共享

  方法的实现也包括两部分内容:方法声明和方法体。

方法声明

方法声明包括方法名、返回类型和外部参数。其中参数的类型可以是简单数据类型,也可以是复合数据类型(又称引用数据类型)。
  对于简单数据类型来说,java实现的是值传递,方法接收参数的值,但不能改变这些参数的值。如果要改变参数的值,则用引用数据类型,因为引用数据类型传递给方法的是数据在内存中的地址,方法中对数据的操作可以改变数据的值。

  方法声明中的限定词的含义:

  • static: 类方法,可通过类名直接调用

  • abstract: 抽象方法,没有方法体

  • final: 方法不能被重写

  • native: 集成其它语言的代码

  • synchronized: 控制多个并发线程的访问

方法体

方法体是对方法的实现,它包括局部变量的声明以及所有合法的Java指令。方法体中声明的局部变量的作用域在该方法内部。若局部变量与类的成员变量同名,则类的成员变量被隐藏。
  为了区别参数和类的成员变量,我们必须使用thisthis用在一个方法中引用当前对象,它的值是调用该方法的对象。返回值须与返回类型一致,或者完全相同,或是其子类。当返回类型是接口时,返回值必须实现该接口。

main方法

主方法:

public static void main(String[] args){
  //代码块
}
  • public:公有的,最大的访问权限
  • static:静态的,无需创建对象
  • void:表示没有返回值,无需向JVM返回结果
  • main:方法名,固定的方法名
  • String[] args:表示参数为字符串数组,可以在调用方法时传入参数

构造方法

  • 构造方法是一个特殊的方法。Java 中的每个类都有构造方法,用来初始化该类的一个对象。
  • 构造方法具有和类名相同的名称,而且不返回任何数据类型。
  • 重载经常用于构造方法。
  • 构造方法只能由new运算符调用

无参构造方法:

public Dog(){} //如果一个类没有定义构造方法,则默认无无参构造,如果有定义有参构造,最好再显示定义一个无参构造方法

带参构造方法

public Dog(String name){
this.name = name;
}

多参构造方法

public Dog(String name,int age){
this.name = name;
this.age = age;
}

(1)构造方法名称与类名相同,没有返回值声明(包括 void)
(2)构造方法用于初始化数据(属性)
(3)每一个类中都会有一个默认的无参的构造方法
(4)如果类中有显示的构造方法,那么默认构造方法将无效
(5)如果有显示的构造方法,还想保留默认构造 方法,需要显示的写出来。
(6)构造方法可以有多个,但参数不一样,称为构造方法的重载
(7)在构造方法中调用另一个构造方法,使用this(...),该句代码必须在第一句。
(8)构造方法之间的调用,必须要有出口。
(9)给对象初始化数据可以使用构造方法或setter方法,通常情况下,两者都会保留。
(10)一个好的编程习惯是要保留默认的构造方法。(为了方便一些框架代码使用反射来创建对象)
(11)private Dog(){},构造方法私有化,当我们的需求是为了 保正该类只有一个对象时(单例模式就是私有化构造器)。

对象初始化

例1

public class CodeBlockTest {
  public static void main(String[] args) {
  Child child = new Child();
}
}
class Father {
  public static String fatherStr1 = "fatherStr1(静态字段初始化值)";

  public String fatherStr2 = "fatherStr2(字段初始化值)";
  static {
    System.out.println("父类静态代码块:" + fatherStr1);
    fatherStr1 = "fatherStr1(静态代码块赋值)";
  }
  {
    System.out.println("父类构造代码块:" + fatherStr2);
    fatherStr2 = "fatherStr2(构造代码块赋值)";
  }
  public Father() {
  System.out.println("父类构造函数块:" + fatherStr2);
  fatherStr2 = "fatherStr2(构造函数赋值)";
  }
}


class Child extends Father {
  public static String childStr1 = "childStr1(静态字段初始化值)";
  public String childStr2 = "childStr2(字段初始化值)";
  static {
    System.out.println("子类静态代码块:" + childStr1);
    childStr1 = "childStr1(静态代码块赋值)";
  }
  {
    System.out.println("子类构造代码块:" + childStr2);
    childStr2 = "childStr2(构造代码块赋值)";
  }
  public Child() {
  System.out.println("子类构造函数:" + childStr2);
  childStr2 = "childStr2(构造函数赋值)";
  }
}
// 输出结果:
// 父类静态代码块:fatherStr1(静态字段初始化值)
// 子类静态代码块:childStr1(静态字段初始化值)
// 父类构造代码块:fatherStr2(字段初始化值)
// 父类构造函数块:fatherStr2(构造代码块赋值)
// 子类构造代码块:childStr2(字段初始化值)
// 子类构造函数:childStr2(构造代码块赋值)

通过每执行一个代码块或构造函数,输出字段在上一代码块执行后的值,以此来探究对象的初始化顺序。
由目前的输出结果可知,对于对象的初始化顺序,我们可以得出以下结论:

(1) 父类静态字段初始化

(2)父类静态代码块、子类静态字段初始化 (接下来探究两者的顺序)

(3)子类静态代码块

(4) 父类普通字段初始化

(5) 父类构造代码块

(6) 父类构造函数

(7)子类普通字段初始化

(8) 子类构造代码块

(9) 子类构造函数

例2

public class CodeBloacTest2 {
  public static void main(String[] args) {
  Child child = new Child();
}
}
class Father {
  public static String fatherStr = "(静态字段初始化值)";
  static {
    System.out.println("父类静态代码块:fatherStr" + fatherStr);
    fatherStr = "(静态代码块赋值)";
  }
}


class Child extends Father {
  public static String childStr = fatherStr;
  static {
    System.out.println("子类静态代码块:childStr = fatherStr" + childStr);
    childStr = "(静态代码块赋值)";
  }
}
// 输出结果:
// 父类静态代码块:fatherStr(静态字段初始化值)
// 子类静态代码块:childStr = fatherStr(静态代码块赋值

我们在子类静态字段childStr初始化的时候,赋的是父类静态字段fatherStr的值。由输出结果可知,childStr初始化后的值是父类静态代码块执行后赋予fatherStr的值。由此可知两者的执行顺序为:父类静态代码块==>子类静态字段初始化

初始化顺序(最终结果):

父类静态字段初始化
父类静态代码块
子类静态字段初始化
子类静态代码块
父类普通字段初始化
父类构造代码块
父类构造函数
子类普通字段初始化
子类构造代码块
子类构造函数

引用https://www.jb51.net/article/111157.htm

方法的重写

方法重写(overriding method)

在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想做一定的修改,这就需要采用方法的重写。方法重写又称方法覆盖。
在子类和父类中,重写方法后,在调用时,以创建的对象类型为准,会调用谁的方法。

重写特性:

a、发生在子父类中,方法重写的两个方法返回值、方法名、参数列表必须完全一致(子类重写父类的方法)
b、子类抛出的异常不能超过父类相应方法抛出的异常(子类异常不能大于父类异常)
c、子类方法的访问级别不能低于父类相应方法的访问级别(子类访问级别不能低于父类访问级别)
d、父类中的方法若使用privatestaticfinal任意修饰符修饰,那么,不能被子类重写。

4、面向对象的基本特性

封装

 封装性就是尽可能的隐藏对象内部细节,对外形成一道边界,只保留有限的接口和方法与外界进行交互。封装的原则是使对象以外的部分不能随意的访问和操作对象的内部属性,从而避免了外界对对象内部属性的破坏。

可以通过对类的成员设置一定的访问权限,实现类中成员的信息隐藏。

  • private:类中限定为private的成员,只能被这个类本身访问。如果一个类的构造方法声明为private,则其它类不能生成该类的一个实例。
  • default:类中不加任何访问权限限定的成员属于缺省的(default)访问状态,可以被这个类本身和同一个包中的类所访问。
  • protected:类中限定为protected的成员,可以被这个类本身、它的子类(包括同一个包中以及不同包中的子类)和同一个包中的所有其他的类访问。
  • public:类中限定为public的成员,可以被所有的类访问。

封装之前:属性都可以直接访问和修改

class Person{
  String name;
  int age;
}

封装之后:

class Person{
  //属性是成员变量,私有化属性,使得其他对象不能直接访问属性
  private String name;
  private int age;
  //参数及方法内定义的变量是局部变量
  public void setName(String name){
  this.name = name;}
  public String getName(){
  return name;}
}

继承

子类的对象拥有父类的全部属性与方法,称作子类对父类的继承。

  • Java中父类可以拥有多个子类,但是子类只能继承一个父类,称为单继承。
  • 继承实现了代码的复用。
  • Java中所有的类都是通过直接或间接地继承java.lang.Object类得到的。
  • 子类不能继承父类中访问权限为private的成员变量和方法。
  • 子类可以重写父类的方法,即命名与父类同名的成员变量。

  Java中通过super来实现对父类成员的访问,super用来引用当前对象的父类。super 的使用有三种情况:

  • 访问父类被隐藏的成员变量,如:super.variable
  • 调用父类中被重写的方法,如:super.Method([paramlist]) ,super()调用父类构造方法;
  • 调用父类的构造函数,如:super([paramlist]);

语法:[访问权限] class 子类名 extends 父类名{
  类体定义;}

示例

public class Dog{
private String name;
private String sex;
public void eat(){System.out.println(“吃饭”);}
}
public class HomeDog extends Dog{
  //类的定义
}
public class HuskyDog extends Dog{
  //类的定义
}

protected(受保护的访问权限修饰符,用于修饰属性和方法,使用protected修饰的属性和方法可以被子类继承)

(1)继承是发生在多个类之间
(2)继承使用关键字extends
(3)JAVA只能单继承,允许多层继承
(4)被继承的类叫父类(超类),继承父类的类叫子类(派生类)
(5)在父类中的非私有属性和方法可以被子类继承
(6)protected(受保护的访问权限修饰符),修饰的属性或方法可以被子类继承
(7)构造方法不能被继承
(8)创建对象会调用构造方法,调用构造方法不一定就是创建对象
(9)实例化子类对象,会先调用父类的构造方法,如果父类中没有默认的构造方法,那么子类必须显示的通过super(...)来调用父类的带参构造方法,super也只能在子类构造方法中的第一句

继承的好处:

a、提高代码的复用性
b、提高代码的维护性
c、让类与类之间产生关系,是多态的前提
继承的缺点:增强了类与类之间的耦合性

多态

对象的多态性是指在父类中定义的属性或方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或方法在父类及其各个子类中具有不同的语义。例如:"几何图形"的"绘图"方法,"椭圆"和"多边形都是几何图"的子类,其"绘图"方法功能不同。

Java的多态性体现在两个方面:由方法重载实现的静态多态性(编译时多态)和方法重写实现的动态多态性(运行时多态)。

  • 编译时多态:在编译阶段,具体调用哪个被重载的方法,编译器会根据参数的不同来静态确定调用相应的方法。
  • 运行时多态:由于子类继承了父类所有的属性(私有的除外),所以子类对象可以作为父类对象使用。程序中凡是使用父类对象的地方,都可以用子类对象来代替。一个对象可以通过引用子类的实例来调用子类的方法。

  重载(Overloading)

  • 方法重载是让类以统一的方式处理不同数据类型的手段。
  • 一个类中可以创建多个方法,它们具有相同的名字,但具有不同的参数和不同的定义。调用方法时通过传递给它们的不同参数个数和参数类型来决定具体使用哪个方法。
  • 返回值类型可以相同也可以不相同,无法以返回型别作为重载函数的区分标准。

  重写(Overriding)

子类对父类的方法进行重新编写。如果在子类中的方法与其父类有相同的的方法名、返回类型和参数表,我们说该方法被重写 (Overriding)。

如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。

  • 子类函数的访问修饰权限不能低于父类的。

比如你是一个酒神,对酒情有独钟。某日回家发现桌上有几个杯子里面都装了白酒,从外面看我们是不可能知道这是些什么酒,只有喝了之后才能够猜出来是何种酒。你一喝,这是剑南春、再喝这是五粮液、再喝这是酒鬼酒….在这里我们可以描述成如下:

 酒 a = 剑南春

 酒 b = 五粮液

酒 c = 酒鬼酒

…

这里所表现的的就是多态。剑南春、五粮液、酒鬼酒都是酒的子类,我们只是通过酒这一个父类就能够引用不同的子类,这就是多态——我们只有在运行的时候才会知道引用变量所指向的具体实例对象。

诚然,要理解多态我们就必须要明白什么是“向上转型”。在继承中我们简单介绍了向上转型,这里就在啰嗦下:在上面的喝酒例子中,酒(Win)是父类,剑南春(JNC)、五粮液(WLY)、酒鬼酒(JGJ)是子类。

我们定义如下代码:

JNC a = new JNC();

对于这个代码我们非常容易理解无非就是实例化了一个剑南春的对象嘛!但是这样呢?

Wine a = new JNC();

在这里我们这样理解,这里定义了一个Wine 类型的a,它指向JNC对象实例。由于JNC是继承与Wine,所以JNC可以自动向上转型为Wine,所以a是可以指向JNC实例对象的。这样做存在一个非常大的好处,在继承中我们知道子类是父类的扩展,它可以提供比父类更加强大的功能,如果我们定义了一个指向子类的父类引用类型,那么它除了能够引用父类的共性外,还可以使用子类强大的功能。

但是向上转型存在一些缺憾,那就是它必定会导致一些方法和属性的丢失,而导致我们不能够获取它们。所以父类类型的引用可以调用父类中定义的所有属性和方法,对于只存在与子类中的方法和属性它就望尘莫及了。

看以下代码

public class Wine {
    public void fun1(){
        System.out.println("Wine 的Fun.....");
        fun2();
    }
    
    public void fun2(){
        System.out.println("Wine 的Fun2...");
    }
}

public class JNC extends Wine{
    /**
     * @desc 子类重载父类方法
     *        父类中不存在该方法,向上转型后,父类是不能引用该方法的
     * @param a
     * @return void
     */
    public void fun1(String a){
        System.out.println("JNC 的 Fun1...");
        fun2();
    }
    
    /**
     * 子类重写父类方法
     * 指向子类的父类引用调用fun2时,必定是调用该方法
     */
    public void fun2(){
        System.out.println("JNC 的Fun2...");
    }
}

public class Test {
    public static void main(String[] args) {
        Wine a = new JNC();
        a.fun1();
    }
}
/**
-------------------------------------------------
Output:
Wine 的Fun.....
JNC 的Fun2...
*/

从程序的运行结果中我们发现,a.fun1()首先是运行父类Wine中的fun1().然后再运行子类JNC中的fun2()

分析:在这个程序中子类JNC重载了父类Wine的方法fun1(),重写fun2(),而且重载后的fun1(String a)与 fun1()不是同一个方法,由于父类中没有该方法,向上转型后会丢失该方法,所以执行JNC的Wine类型引用是不能引用fun1(String a)方法。而子类JNC重写了fun2() ,那么指向JNC的Wine引用会调用JNC中fun2()方法。

所以对于多态我们可以总结如下:

指向子类的父类引用由于向上转型了,它只能访问父类中拥有的方法和属性,而对于子类中存在而父类中不存在的方法,该引用是不能使用的,尽管是重载该方法。若子类重写了父类中的某些方法,在调用该些方法的时候,必定是使用子类中定义的这些方法(动态连接、动态调用)。

对于面向对象,多态分为编译时多态和运行时多态.

  • 其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。

  • 而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。

打铁要趁热,再来看一个多态的经典例子

摘自:http://blog.csdn.net/thinkGhoster/archive/2008/04/19/2307001.aspx。

public class A {
    public String show(D obj) {
        return ("A and D");
    }

    public String show(A obj) {
        return ("A and A");
    } 

}

public class B extends A{
    public String show(B obj){
        return ("B and B");
    }
    
    public String show(A obj){
        return ("B and A");
    } 
}

public class C extends B{

}

public class D extends B{

}

public class Test {
    public static void main(String[] args) {
        A a1 = new A();
        A a2 = new B();
        B b = new B();
        C c = new C();
        D d = new D();
        
        System.out.println("1--" + a1.show(b));
        System.out.println("2--" + a1.show(c));
        System.out.println("3--" + a1.show(d));
        System.out.println("4--" + a2.show(b));
        System.out.println("5--" + a2.show(c));
        System.out.println("6--" + a2.show(d));
        System.out.println("7--" + b.show(b));
        System.out.println("8--" + b.show(c));
        System.out.println("9--" + b.show(d));      
    }
}

运行结果

--A and A
--A and A
--A and D
--B and A
--B and A
--A and D
--B and B
--B and B
--A and D
在这里看结果1、2、3还好理解,从4开始就开始糊涂了,对于4来说为什么输出不是“B and B”呢?

首先我们先看一句话:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这句话对多态进行了一个概括。其实在继承链中对象方法的调用存在一个优先级:this.show(O)super.show(O)this.show((super)O)super.show((super)O)

分析

从上面的程序中我们可以看出A、B、C、D存在如下关系。

首先我们分析5: a2.show(c),a2是A类型的引用变量,所以this就代表了A,a2.show(c),它在A类中找发现没有找到,于是到A的超类中找(super),由于A没有超类(Object除外),所以跳到第三级,也就是this.show((super)O),C的超类有B、A,所以(super)O为B、A,this同样是A,这里在A中找到了show(A obj),同时由于a2是B类的一个引用且B类重写了show(A obj),因此最终会调用子类B类的show(A obj)方法,结果也就是B and A

按照同样的方法我也可以确认其他的答案。

方法已经找到了但是我们这里还是存在一点疑问,我们还是来看这句话:当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法。这我们用一个例子来说明这句话所代表的含义:a2.show(b);

这里a2是引用变量,为A类型,它引用的是B对象,因此按照上面那句话的意思是说有B来决定调用谁的方法,所以a2.show(b)应该要调用B中的show(B obj),产生的结果应该是“B and B”,但是为什么会与前面的运行结果产生差异呢?这里我们忽略了后面那句话“但是这儿被调用的方法必须是在超类中定义过的”,那么show(B obj)在A类中存在吗?根本就不存在!所以这句话在这里不适用?那么难道是这句话错误了?非也!其实这句话还隐含这这句话:它仍然要按照继承链中调用方法的优先级来确认。所以它才会在A类中找到show(A obj),同时由于B重写了该方法所以才会调用B类中的方法,否则就会调用A类中的方法。

** 所以多态机制遵循的原则概括为:**

当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,但是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法,但是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)super.show(O)this.show((super)O)super.show((super)O)

对象与内存分析

new 关键字表示创建一个对象
new 关键字表示实例化对象
new 关键字表示申请内存空间
注意:如果使用一个没有申请内存空间的对象,会报空指针异常:java.lang.NullPointerException

(1)new关键字:表示向内存申请空间,也表示实例化一个对象,创建一个对象。

(2)一个对象在内存中的大小,由该对象的所有属性所占的内存大小的总和。引用类型变量在32位系统上占. 4个 字节,在64位 系统上占8个字节。加上而外的对象隐性数据所占的大小。

(3)相同的类 型才可以赋值

(4)不同的引用,指向同一个对象,任何一个引用改变对象的值,其它引用都会反映出来。

(5)编程时要注意的问题,在确定不使用对象时,要尽早释放对象:引用=null

(6)当一个堆中的对象没有被任何引用变量所指向时,该对象会被JVM 的 GC 程序认为是垃圾对象,从而被回收

5、this关键字

  • this关键字指向的是当前对象的引用
  • 调用类中的属性:this.属性名称,指的是访问类中的成员变量,用来区分成员变量和局部变量(重名问题)
  • 调用类中的方法:this.方法名称,用来访问本类的成员方法
  • 调用类构造方法:this();访问本类的构造方法,()中可以有参数的 如果有参数 就是调用指定的有参构造
    注意:
    1.this() 不能使用在普通方法中,只能写在构造方法中
    2.必须是构造方法中的第一条语句

6、static关键字

static关键字的作用:方便在没有创建对象的情况下来进行调用(方法/变量)。

  • a、使用static关键字修饰一个属性:声明为static的变量实质上就是全局变量
  • b、使用static关键字修饰一个方法:在一个类中定义一个方法为static,那就是说,无需本类的对象即可调用此方法(类调用)
  • c、使用static关键字修饰一个类(内部类):

**声明为static的方法有以下几条限制: **

  • 它们仅能调用其他的static 方法,反过来是可以的。
  • 它们只能访问static数据。
  • 它们不能以任何方式引用thissuper
  • 不允许用来修饰局部变量

可以参考:https://www.cnblogs.com/dolphin0520/p/3799052.html

7、super关键字

可以理解为对父类的引用,使用super来调用父类的属性,方法,和构造方法

super可以完成以下的操作:

a、使用super调用父类中的属性,可以从父类实例处获得信息。
b、使用super调用父类中的方法,可以委托父类对象帮助完成某件事情。
c、使用super调用父类中的构造方法(super(实参)形式),必须在子类构造方法的第一条语句,调用父类中相应的构造方法,若不显示的写出来,默认调用父类的无参构造方法,比如:super();

8、final关键字

final关键字完成以下的操作:

a、使用final关键字声明一个常量
final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的
b、使用final关键字声明一个方法
该方法为最终方法,且只能被子类继承,但是不能被子类重写。
c、使用final关键字声明一个类
该类就转变为最终类,没有子类的类,fianl修饰的类无法被继承。
d、在方法参数中使用final,在该方法内部不能修改参数的值(在内部类中详解)

当final变量是基本数据类型以及String类型时,如果在编译期间能知道它的确切值,则编译器会把它当做编译期常量使用,不过要注意,只有在编译期间能确切知道final变量值的情况下,编译器才会进行这样的优化
引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。

9、值传递与引用传递?

首先,注意:在java中只有按值传递,并没有所谓的按引用传递
java数据类型可以分为两大类:

  • 基本类型(primitive types)
  • 引用类型(reference types)

基本数据类型的按值传递

public class Swap {
public static void main(String[] args) {
  int x = 10;
  int y = 20;
  swap(x, y);
  System.out.println("x(2) = " + x);
  System.out.println("y(2) = " + y);
}
public static void swap(int x, int y) {
  int temp = x;
  x = y;
  y = temp;
  System.out.println("x(1) = " + x);
  System.out.println("y(1) = " + y);
}
}
/*输出
x(1) = 20
y(1) = 10
x(2) = 10
y(2) = 20
*/

main函数调用swap函数来交换 x,y的值,然而调用函数之后发现main中x,y的值并未交换。包括在Java api中找不到一个可以交换两个变量的方法。这与Java语言的特性有关。通过一个图就可以知道上面程序的运行结果了。

main函数中的x,y和swap函数中的x,y分别存放在不同的区域,在main中调用swap函数的时候,会将main中的x,y的值赋给swap中的x,y。当swap函数中对x,y交换时只是对swap帧中的x,y做交换,并不会改变main中的x,y。所以当函数返回时main中的x,y并不会改变


引用数据类型的按值传递

引用数据数据类型分为三种:

  • ①接口
  • ②类
  • ③数组
public static void main(String[] args) { 
  int []a={10,20};
  System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=10,a[1]=20; 
  swap(a, 0, 1);
  System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=20,a[1]=10; 
}
public static void swap(int []a,int i,int j){
  nt temp=a[i];
  a[i]=a[j];
  a[j]=temp;
  System.out.println("a[0] :"a[0]+"a[1] : "+a[1]);//a[0]=20,a[1]=10;
}
//输出
/*a[0]=10 a[1]=20
a[0]=20 a[1]=10
a[0]=20 a[1]=10 
*/

运行程序后发现,swap函数对a[0] ,a[1]的操作竟然影响到了main函数中的a[0] ,a[1]的值,真是不可思议。为什么会产生如此之结果。原来引用类型的按值传递,传递的是对象的地址

由图可以看出在swap中仅仅是得到了数组的地址,并没有对数组的元素进行复制,在swap中对数组的操作是直接对main函数中数组的操作,因此swap函数返回后main函数中的a[0] ,a[1]的值发生交换

引用https://blog.csdn.net/u013309870/article/details/75499175

10、对象的一对一关系

java中对象的对应关系有很多种,比如单向一对一,双向一对一,一对多,多对一,多对多等,其实现原理相同
其实可以理解为类的组合问题,把对象当做另一个的属性来操作,使得其产生对应关系(很多设计模式就是通过类的组合实现的)

分别是Hero、Weapon、One2One(包含主函数)
class Hero {
  private String name; //属性私有化 ,防止外面直接赋值和调用
  private int age; 
  private Weapon weapon; //声明一个武器类的对象 (私有化)
  //此处省略get,set方法和构造器
}

class Weapon {
  private String name;
  private int age; 
  private Hero hero; //声明一个对象
  //此处省略get,set方法和构造器
}

public class One2One {
  public static void main(String[] args) {
    Hero hero = new Hero("刘备",35); //实例化一个对象,并且赋值
    Weapon weapon = new Weapon("双股剑",3);
    //建立关系 ,(一句代码搞定)
    hero.setWeapon(weapon); //把武器类对象设置成英雄类中的一个属性
    weapon.setHero(hero);
    //输出结果,用英雄类查找其武器
    System.out.println("我的名字叫"+hero.getName()+",我现在"+hero.getAge()+"岁了,我用的武器是"+w.getName()+",等级为"+w.getAge()+"级。");
    //我的名字叫刘备,我现在35岁了,我用的武器是双股剑,等级为3级。

    hero.setAge(350); //修改其属性
    weapon.setName("生锈的双股剑");
    weapon.setAge(2);

    //Hero h = weapon.getHero();
    //用武器查找其主人
    System.out.println("我是"+weapon.getName()+",我的等级已经降落为"+weapon.getAge()+"级了,我的主人是"+weapon.getHero().getName()+",现在已      经"+weapon.getHero().getAge()+"岁了");
    //我是生锈的双股剑,我的等级已经降落为2级了,我的主人是刘备,现在已经350岁了

}

11、接口

接口的定义格式:

interface 接口名称{
  全局常量 ;
  抽象方法 ;
}

示列:

interface IEat{
  //public abstract void eat();
  void eat();  //默认为public abstract void eat();
  //public static final int NUM = 10;
  int NUM = 10; }

interface ISleep extends IEat{
void sleep();
}

public interface IUser {
  static void say() {
  System.out.println("say_" + IUser.class);
 }

  default void eat() {
  System.out.println("eat_" + IUser.class);
 }
}

接口的使用规则:

(1)定义一个接口,使用interface关键字
(2)在一个接口中,只能定义常量、抽象方法,JDK1.8后可以定义默认的实现方法
(3)接口可以继承多个接口:extends xxx,xxx
(4)一个具体类实现接口使用implements关键字
(5)一个类可以实现多个接口
(6)抽象类实现接口可以不实现接口的方法
(7)在接口中定义的方法没有声明 访问修饰符,默认为public
(8)接口不能有构造方法
(9)接口不能被实例化

java8新增

(1)增加了default方法和static方法,这两种方法完全可以有方法体
(2)default方法属于实例,static方法属于类(接口)
(3)接口中的静态方法不会被继承,接口中的静态变量会被继承

12、instanceof关键字

instanceof 是用于检查对象是否为指定的类型,通常在把父类引用强制转换为子类引用时要使用,以避免发生类型转换异常(ClassCastException)。

语法格式如下:

对象 instanceof 类型--返回boolean类型值

示例:

if(homeChicken instanceof Chicken){
  //...
}

该语句一般用于判断一个对象是否为某个类的实例,是返回true,否返回false

父类的设计法则
通过instanceof关键字,我们可以很方便的检查对象的类型,但如果一个父类的子类过多,这样的判断还是显得很繁琐,那么如何去设计一个父类呢?

a、父类通常情况下都设计为抽象类或接口,其中优先考虑接口,如接口不能满足才考虑抽象类。

b、一个具体的类尽可能不去继承另一个具体类,这样的好处是无需检查对象是否为父类的对象。

13、内部类

内部类就是在一个类的内部定义的类。

  • 成员内部类:内部类对象依赖外部类对象而存在,即在创建一个普通内部类对象时首先需要创建其外部类对象
    内部类对象可以访问外部类对象中所有访问权限的字段,同时,外部类对象也可以通过内部类的对象引用来访问内部类中定义的所有访问权限的字段

成员内部类格式如下:

class Outer {
  class Inner{}
}

编译上述代码会产生两个文件:

Outer.classOuter$Inner.class

在外部创建内部类对象

内部类除了可以在外部类中产生实例化对象,也可以在外部类的外部来实例化。
那么,根据内部类生成的*.class文件:Outer.𝐼𝑛𝑛𝑒𝑟.𝑐𝑙𝑎𝑠𝑠Inner.class“ 符号在程序运行时将替换成"."
所以内部类的访问:通过“外部类.内部类”的形式表示。

Outer out = new Outer() ;// 产生外部类实例
Outer.Inner in = null; // 声明内部类对象
in = out.new Inner() ; // 实例化内部类对象

局部内部类

内部类可以作为一个类的成员外,还可以把类放在方法内定义(不常用,匿名内部类可以显示局部内部类的功能)。
在局部内部类里面可以访问外部类对象的所有访问权限的字段,而外部类却不能访问局部内部类中定义的字段

注意:

a、局部内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。
b、局部内部类对象不能使用该内部类所在方法的非final局部变量。

class Outer {
  public void doSomething(){
  class Inner{
    public void seeOuter(){}
  }
  }
}

静态内部类

在一个类内部定义一个静态内部类:

静态的含义是该内部类可以像其他静态成员一样,没有外部类对象时,也能够访问它。静态嵌套类仅能访问外部类的静态成员和方法。
静态内部类中也无法访问外部类的非静态成员

class Outer{
  static class Inner{}
}
class Test {
  public static void main(String[] args){
  Outer.Inner n = new Outer.Inner();
}
}

匿名内部类

匿名内部类就是没有名字的内部类。

匿名内部类的三种情况

(1)继承式的匿名内部类
(2)接口式的匿名内部类
(3)参数式的匿名内部类

在使用匿名内部类时,要记住以下几个原则:

(1)不能有构造方法,只能有一个实例。
(2)不能定义任何静态成员、静态方法。
(3)不能是public,protected,private,static。
(4)一定是在new的后面,用其隐含实现一个接口或继承一个类。
(5)匿名内部类为局部的,所以局部内部类的所有限制都对其生效


局部内部类访问局部变量必须用final修饰,为什么?

  • 当调用这个方法时,局部变量如果没有用final修饰,他的生命周期和方法的生命周期是一样的,当方法被调用时会入栈,
    方法结束后即弹栈,这个局部变量也会消失,那么如果局部内部类对象还没有马上消失想用这个局部变量,显然已无法使用了,
    如果用final修饰会在类加载的时候进入常量池,即使方法弹栈,常量池的常量还在,也就可以继续使用了。

注意:在jdk1.8中取消了在局部内部类中使用的变量必须显示的使用final修饰,编译器默认会为这个变量加上final

内部类的作用

  • 每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
    如果没有内部类提供的可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决。从这个角度看,
    内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了“多重继承”。
posted @ 2021-09-13 00:20  石佳炜  阅读(150)  评论(0编辑  收藏  举报