Skip to content

Java入门 - 面向对象


用来描述一类事物的类叫做Javabean类. 在Javabean类当中是不写main方法的.

我们在完成一个Javabean类之后,我们还可以写一个测试类,用来测试刚才的Javabean.测试类中是有main方法的. 测试类可以写在同一个包下临时测试.

我们一个Java文件中是可以有多个class类的,但是只能有一个类是public修饰的,public修饰的类名需要成为代码文件名. 但是实际开发的时候,还是一个文件一个类.

像c++11一样,类的成员变量也是可以直接赋予初值的.但是一般没必要.(保持默认就好) ddb69b0afe812b175433e0cfe0857fc6_MD5

我们一般把成员变量放在类的开始.构造函数放在其后.


封装:

private:

  • 修饰成员(包括变量和函数): 只能在本类才能被访问. (需要与外界交互的的成员变量都可以设置public的get和set方法,这是经典的Java风格.)

就近原则:

fe2d6137e9876a1f24ef65c1931e70eb_MD5 会输出10. 这是因为局部变量更近. 如果我们想要获得成员变量呢?用this.age 用这个方式,可以set方法这样写:

public class Person{
    private String name;
    public void setName(String name)
    {
        this.name = name;
    }
}


构造方法/构造器:

6311a1aaa2b13b201a5e6bbf8946c8b5_MD5

我们在创建对象的时候: Phone ph = new Phone("iphone","16pro",10000);

如果我们没有写任何构造方法,那么会生成一个空的(空参构造),和c++一样. 我们不能手动调用构造方法.

  • 一般来说,不论是否使用,我们都会把无参构造和带全部参数的构造都写.这是标准Javabean的规则.

生成标准Javabean的方式: 在我们写完了类的成员变量之后,可以使用alt + insert 来进行一键生成.当然直接右键找到就行. ptg插件也可以做到,而且更快(安装之后,右键 找到生成javabean)


内存结构: java8以前: 运行一个类的时候,对应的类的class文件就会被加载到方法区当中. fb6029d91a1add34da6e8e9a3115649a_MD5 注意,这里会打印出s1的地址.(就是对象的堆空间的地址)

一个对象对应的堆内存当中,会有一块空间存储成员方法的地址.不像c++,这个函数指针每个对象都有一份,而不是共享的.

如果出现赋值呢?

Student s2 = s1;
那么s2会指向s1对应的空间,也就是两个变量会指向两个空间.

如果接下来我们把s1 = null呢?(s2没变) 接下来访问s1.name 会抛出空指针异常.

基本数据类型存储在栈上,值就是变量存储的值. 引用数据类型的值是指针,指向堆空间.


f3e34eff501f8b9536d80a65dfde0e49_MD5


Static 静态变量

public static String staticInstance;//这就是一个静态变量

和C++一样,也支持类名调用,不过是用.调用的 如 ClassName.staticInstance

静态变量放在静态区(JDK8以前在方法区,以后在堆空间).静态区在类加载的时候就加载进来的.这时候往往没有任何对象. 什么时候加载类?第一次使用这个类时(准确来说应该是那一行代码执行之前)就会加载.


Static 静态方法

多用在测试类和工具类当中.

也同样可以使用类名和对象调用.

静态方法当中没有this,因此不能访问成员变量

静态方法在方法区.(其实成员方法也是在方法区).


工具类:帮助我们做事情,但是不描述任何事物的类. 类名常常带有Util,如ArrUtil.Util表示工具. 构造方法常常私有,因为其不描述任何事物,不应该实例化对象. 如: private ArrUtil(){}; 方法应该全部定义成静态方法.


其实定义成员函数的时候,可以手动把第一个位置的this指针参数手动写出来. 但是,这样写没有任何作用.在调用成员方法的时候,和不写时的调用方式是一样的,都不用手动传. 也不推荐写.


main方法的String[] 参数准确来说,是去掉了./a.exe 之后的命令行参数(注:当然,java生成的也不是exe)


继承:

public class Student extends Person
{
...
}

每一个类都直接或者间接的继承于Object类. 也就是说,只写一个public class Student,没有指明父类的时候,虚拟机就会自动给我们添加一个父类Object.


父类当中的private 变量/方法 子类都无法访问. 但是注意:不管什么修饰的变量,子类都能继承下来,但是只是有的无法访问罢了. (ps:想访问怎么办?父类的get/set往往是public的,调用父类的get/set就行了) private成员方法则是 根本没有继承下来.

总结: Open: iamwsll11744385708368492.png image.png (这个图不准确,请见下.) 其实这和c++一模一样的


成员变量:内存结构是和c++类似. 子类对象在堆空间的部分,既包含了父类继承下来的成员,也包含自己独有的成员.

成员方法:这和c++不一样 从顶级父类开始,会有一张虚方法表.虚方法表里包含非private 非static 非final 的成员方法.这些方法都是虚方法.

继承时,会把虚方法表交给子类.子类在父类虚方法表的基础上,添加自己的虚方法. (这就是之前说"private成员方法则是 根本没有继承下来"的原因). Object有5个虚方法.

这和c++有所区别.c++只有virtual修饰的才是虚方法,并且和修饰符无关.

因此,构造方法和成员变量都是和c++类似,成员方法不是.


b站链接

大约在视频39min处,讲了如何使用java自带的内存分析工具(jps,jhsdb(HotSpot Debugger)). Open: iamwsll11744387059280441.png image.png 一个对象当中,第一行:对象头,包含了一些对象信息,如哈希值. 第二行(要从右往左看,因为右边是低位,左边是高位)前4字节记录的是当前对象的类型. 后面的111 222 333 都是成员变量.

tip:第一行是低位,第二行是高位.

内存分析工具非常强大,类长什么样 各种信息几乎都能看到.注意:内存分析工具只能在程序运行时查看.

ps:以下程序:

Student s = new Student();
ClassLayout layout = ClassLayout.parseInstance(s);//第三方库
System.out.println(layout.toPrintable());
会把上面的东西打印出来: Open: iamwsll11744446459158766.png image.png 关注一下20 4这一行:java虚拟机要求每个对象都是8字节的整数倍,否则会自动补全.因此这4个字节就是补齐用的.


成员变量如果父类和子类同名,则使用就近原则,使用子类. 调用父类则使用 super.name 如果父类的父类还有name,应该怎么调用?没办法调用.

如果只有父类有个成员叫name,那么使用name/this.name/super.name都是可以的


成员方法呢?也是逐渐向父类找.另外,也可以通过super直接调用父类的方法. 另外,如果子类和父类有同样的方法签名,那么构成重写. 重写需要在子类方法上面标记@override,这样会使得虚拟机帮我们检查是否构成重写.

重写的时候,会修改虚方法表中的方法.(这便是重写的原理) 只有被添加到虚方法表中的方法才能重写 - 重写时,子类访问权限必须大于等于父类(private<protect<public) 也就是说不能父类方法public,子类方法private - 重写时,返回值类型子类要小于父类.(也就是c++当中的,可以做到子类返回一个子类,父类返回一个父类) 因此建议:子类和父类重写的方法要保持一致.


  • 子类的所有构造方法 默认会先访问父类的无参构造,再执行自己. 这个规则的本质:子类构造方法方法体的第一行语句默认是super();不写也存在.
  • 如果想调用父类的构造方法,可以在子类的构造方法开头(必须在第一行)手动调用super(...);

其实也可以通过this调用自己的构造函数: Open: iamwsll11744446708042184.png image.png 这里的this调用的时候,也必须要求在第一行.


多态:也和c++一模一样

Person one = new Student(); Open: iamwsll11744453884014598.png image.png 注: 上面如果定义的时候是Dog a = new Dog(); 那么a.name肯定是Dog的name.

如果想要调用子类特有的方式呢? Animal a= new Dog(); Dog b = a; b.DogFuc(); 即可.这样就是把父类强转成子类.

注意,如果瞎转,那么会导致ClassCastException异常.怎么安全地转化呢?

Open: iamwsll11744454630068651.png image.png instanceof关键字:左边是变量,右边是类名.如果变量属于这个类型,那么就返回true;

这样写有一点麻烦,Java14时: Open: iamwsll11744454721484144.png image.png


加载字节码文件的时候,先加载父类,再加载子类.


包: 每一个class文件开头的: Open: iamwsll11744455145930463.png image.png 代表的是当前的类属于这个包. 如果我们使用一个类,需要把包前缀全写上.如com.itheima.domain.Student. 这种写法叫做全类名,也叫全限定名.

全类名写的太过麻烦.我们可以在java文件的开头写上 import com.itheima.domain.Student;//这个就叫做导包. 这样java文件类只要提到Student就知道是com.itheima.domain.Student中的Student了.

Open: iamwsll11744455355437933.png image.png 在iDEA当中,项目结构-模块-import module,选择对应包的iml即可

我们甚至可以通过调用 对象名.main()的方式来调用其他类的main方法.(注意传参,传个null就行)


final:修饰不同的类型: Open: iamwsll11744455582697306.png image.png

写法:public final void func(){} final class Base{ } final int a = 10;(能不能先声明再赋值?不行)

Object 的getClass()就是final的,签名是这样:Open: iamwsll11744455682150677.png image.png native表示这个函数是从c/c++中写的(这也是为什么它没有方法体的原因)

Open: iamwsll11744455870313931.png image.png

Open: iamwsll11744455883059841.png image.png


Open: iamwsll11744456918153446.png image.png 默认的方式就是只能在同一个包里可见.(一个家庭中可见) protected:其他包里的子类也可见(一个家庭中可见,外面的儿子也可见)

实际上开发的时候只需要private 和 public


代码块: Open: iamwsll11744457387521290.png image.png Open: iamwsll11744457488654631.png image.png 构造代码块会在构造函数之前执行. 这样可以抽取构造函数公共的代码. 现在几乎不用了.因为不够灵活. Open: iamwsll11744457650715734.png image.png 这个倒是挺重要的. 注意,static代码块当中只能调用静态方法 静态成员.


抽象类

抽象方法:在父类当中,如果故意不写方法体,并用abstract修饰. Open: iamwsll11744458134136359.png image.png 子类必须重写否则会报错. 拥有抽象方法的类就叫做抽象类. 抽象类必须写成public abstract class ClassName{ } 抽象类不能实例化. 抽象类也要有构造方法(全参,无参),用来给子类调用. 抽象类几乎就是一个正常的类,可以有自己的成员变量.

另外,抽象类也可以不含抽象方法.

抽象类的子类要么重写抽象类所有的抽象方法,要么是抽象类.

tip:抽象类的作用是什么? 抽象方法定义了子类方法的格式.这样,多人开发的时候,不同的人写不同的子类,格式都是一样的. 另外,还有不让别人创造对应的实例的作用.


接口interface

Open: iamwsll1174445902609169.png image.png 这样确实好. 话说回来,c++有多继承,替代了这个interface.

接口的作用是:"这个类"拥有这个"行为": Open: iamwsll11744459190902815.png image.png 因此,抽象类和interface是两种完全不一样的概念.

接口在地位上和类是很像的.也就是说,项目层级结构上,可以把接口看成一个类.


implements: Open: iamwsll11744459267110809.png image.png

这样可以一键实现: Open: iamwsll1174445945968184.png image.png


接口的细节: Open: iamwsll11744459662500603.png image.png

这样写就行: Open: iamwsll11744459721138799.png image.png

如果要实现多个接口,但是接口之间的方法有重名,怎么办? 只需要重写一次.也就是重写一个方法就行.

接口可以继承,也可以多继承.继承一律用extends Open: iamwsll11744460074209606.png image.png 实现接口时,需要把接口的父接口全部实现. Open: iamwsll11744461507793564.png image.png


JDK8 :接口中可以定义有方法体的方法. 分为两种:默认方法,静态方法.

默认方法 - 为什么这么设计?原先的设计中,如果我们增加接口中的方法,就会导致所有的子类都会报错,也就是不兼容,这是非常不好的.于是,默认方法就出现了.默认方法是为了解决接口升级的问题.

  • 默认方法格式:public default void fun(){...};

  • 默认方法不强制重写.但是如果被重写,重写的时候需要去掉default关键字.

  • public修饰符是默认的,可以省略.但是default不能省略.
    • 如果实现了多个接口,但是多个接口当中存在相同名字的默认方法,子类就必须对该方法进行重写.

静态方法 Open: iamwsll1174446087255310.png image.png - 静态方法不能重写.(不能用override修饰).为什么?因为静态方法不能添加到虚方法表里. - 但是能定义一个同名的方法.(只是刚好有个重名的方法).由于调用静态方法只能通过接口名,因此不会用冲突.

JDk9:可以定义私有方法. 这个倒是很好理解,因为需要抽取公共代码,并且不需要外界看到. 两种方式: private void fun(){...};//在默认方法中可以调用 private static void fun(){...};//在静态方法当中可以调用


内部类

A类的内部定义B类,B类就是内部类. Open: iamwsll11744462611210399.png image.png 关于右下角:其实意思就是外部类访问内部类,就需要这样:Open: iamwsll1174446276558928.png image.png

在我们查看源码的时候,就可以看到内部类: Open: iamwsll11744462837320734.png image.png 如这些蓝色的c开头的就是ArrayLists的内部类. 比如迭代器就是ArrayLists的内部类.


内部类的分类

成员内部类(不常用) 如之前的适配器. Open: iamwsll11744463663545918.png image.png Open: iamwsll11744463748601796.png image.png 注意,用static修饰的就是静态内部类了. Open: iamwsll11744463787839688.png image.png 如 Outer o = new Outer(); Outer.Inner oi = o.new Inner();

当然,可以直接成:Outer.Inner oi = new Outer().new Inner(); 我们可以看出,创建外部类对象时,并不会创建内部类对象. 内部类对象的创建,必须由某个具体的外部类对象来new才行.

Open: iamwsll1174446406133060.png image.png 这样写会报错吗?会,因为inner是private的,因此外部并不知道Outer.Inner是什么. 解决方法: 写成 Object inner 0=o.getInstance();

如果我们这样写: Open: iamwsll11744464171523277.png image.png 则会打印出地址: Open: iamwsll11744464187556226.png image.png 可见内部类和外部类是用$ 隔开的. 事实上,Outer和Inner类是两个不同的class文件.Inner类的字节码文件叫Outer&Inner.java

我们一般采用让内部类private,用get方法对外提供.

内部类获取变量: 获取外部类变量直接获取就行. 如果出现重名,顺序也是"就近原则",局部变量>自己的成员变量>外部类成员变量. 如果重名,但是还想调用外部类的变量,那么需要通过Outer.this.name来调用

Open: iamwsll11744464775449842.png image.png 由此可见: 外部类对象并不包含内部类对象.内部类对象实际上是个独立的内存空间,只不过内部类对象会有一个Outer.this指针指向自己的外部类对象.

静态内部类(不常用)是成员内部类的特殊情况. 静态内部类只能访问外部类中的静态方法和静态变量. Open: iamwsll11744471741823258.png image.png Open: iamwsll11744472016605382.png image.png 事实上,static内部类是一个完全独立的类,和普通的类没有任何区别,表现上没有什么差异性.只不过,访问这个类的东西都要加一个Outer前缀罢了. 成员内部类会有一个Outer.this指向自己的外部类对象的地址.静态内部类则没有这个指针 至于只能访问外部类中的静态方法和静态变量 那就也不奇怪了,毕竟内部类和外部类没有关系.

局部内部类(不常用) Open: iamwsll11744472654225787.png image.png 由于地位上就是局部变量,因此public/private这样不能修饰局部变量的就不能修饰局部内部类;而可以修饰局部变量的如final就可以 Open: iamwsll11744472833450531.png image.png

匿名内部类(常用) Open: iamwsll11744472956451501.png image.png Open: iamwsll11744473005135985.png image.png 注意new语句的结束有个分号. Open: iamwsll11744473042127207.png image.png 这两者实际上是等价的.为什么格式是这样的? Open: iamwsll11744473207548454.png image.png

匿名内部类实际上会生成一个Test\(1.class的文件来描述这个类(Test.java是创建这个匿名内部类对象所在的文件名) 如果Test.java中创建了另外一个匿名内部类,就会生成Test\)2.class Open: iamwsll11744473576585392.png image.png

如何使用? 用法一:(最常用:当一个方法需要传递一个接口类对象,但是我们只需要传递一次,犯不着单独定义一个类) Open: iamwsll11744473680667971.png image.png 用法2: Open: iamwsll11744473792532163.png image.png 用法3: Open: iamwsll11744473825437651.png image.png 刚才我们都是把匿名内部类对象写在局部变量的位置(这样就成了局部内部类). 其实也可以写成其他类的成员变量位置(这就成了成员内部类)