Java入门 - 面向对象¶
用来描述一类事物的类叫做Javabean类. 在Javabean类当中是不写main方法的.
我们在完成一个Javabean类之后,我们还可以写一个测试类,用来测试刚才的Javabean.测试类中是有main方法的. 测试类可以写在同一个包下临时测试.
我们一个Java文件中是可以有多个class类的,但是只能有一个类是public修饰的,public修饰的类名需要成为代码文件名. 但是实际开发的时候,还是一个文件一个类.
像c++11一样,类的成员变量也是可以直接赋予初值的.但是一般没必要.(保持默认就好)

我们一般把成员变量放在类的开始.构造函数放在其后.
封装:
private:¶
- 修饰成员(包括变量和函数): 只能在本类才能被访问. (需要与外界交互的的成员变量都可以设置public的get和set方法,这是经典的Java风格.)
就近原则:
会输出10. 这是因为局部变量更近.
如果我们想要获得成员变量呢?用this.age
用这个方式,可以set方法这样写:
public class Person{
private String name;
public void setName(String name)
{
this.name = name;
}
}
构造方法/构造器:

我们在创建对象的时候: Phone ph = new Phone("iphone","16pro",10000);
如果我们没有写任何构造方法,那么会生成一个空的(空参构造),和c++一样. 我们不能手动调用构造方法.
- 一般来说,不论是否使用,我们都会把无参构造和带全部参数的构造都写.这是标准Javabean的规则.
生成标准Javabean的方式: 在我们写完了类的成员变量之后,可以使用alt + insert 来进行一键生成.当然直接右键找到就行. ptg插件也可以做到,而且更快(安装之后,右键 找到生成javabean)
内存结构:
java8以前:
运行一个类的时候,对应的类的class文件就会被加载到方法区当中.
注意,这里会打印出s1的地址.(就是对象的堆空间的地址)
一个对象对应的堆内存当中,会有一块空间存储成员方法的地址.不像c++,这个函数指针每个对象都有一份,而不是共享的.
如果出现赋值呢?
Student s2 = s1;
如果接下来我们把s1 = null呢?(s2没变) 接下来访问s1.name 会抛出空指针异常.
基本数据类型存储在栈上,值就是变量存储的值. 引用数据类型的值是指针,指向堆空间.

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
(这个图不准确,请见下.)
其实这和c++一模一样的
成员变量:内存结构是和c++类似. 子类对象在堆空间的部分,既包含了父类继承下来的成员,也包含自己独有的成员.
成员方法:这和c++不一样 从顶级父类开始,会有一张虚方法表.虚方法表里包含非private 非static 非final 的成员方法.这些方法都是虚方法.
继承时,会把虚方法表交给子类.子类在父类虚方法表的基础上,添加自己的虚方法. (这就是之前说"private成员方法则是 根本没有继承下来"的原因). Object有5个虚方法.
这和c++有所区别.c++只有virtual修饰的才是虚方法,并且和修饰符无关.
因此,构造方法和成员变量都是和c++类似,成员方法不是.
大约在视频39min处,讲了如何使用java自带的内存分析工具(jps,jhsdb(HotSpot Debugger)).
Open: iamwsll11744387059280441.png
一个对象当中,第一行:对象头,包含了一些对象信息,如哈希值.
第二行(要从右往左看,因为右边是低位,左边是高位)前4字节记录的是当前对象的类型.
后面的111 222 333 都是成员变量.
tip:第一行是低位,第二行是高位.
内存分析工具非常强大,类长什么样 各种信息几乎都能看到.注意:内存分析工具只能在程序运行时查看.
ps:以下程序:
Student s = new Student();
ClassLayout layout = ClassLayout.parseInstance(s);//第三方库
System.out.println(layout.toPrintable());
关注一下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
这里的this调用的时候,也必须要求在第一行.
多态:也和c++一模一样
Person one = new Student();
Open: iamwsll11744453884014598.png
注:
上面如果定义的时候是Dog a = new Dog();
那么a.name肯定是Dog的name.
如果想要调用子类特有的方式呢? Animal a= new Dog(); Dog b = a; b.DogFuc(); 即可.这样就是把父类强转成子类.
注意,如果瞎转,那么会导致ClassCastException异常.怎么安全地转化呢?
Open: iamwsll11744454630068651.png
instanceof关键字:左边是变量,右边是类名.如果变量属于这个类型,那么就返回true;
这样写有一点麻烦,Java14时:
Open: iamwsll11744454721484144.png

加载字节码文件的时候,先加载父类,再加载子类.
包:
每一个class文件开头的:
Open: iamwsll11744455145930463.png
代表的是当前的类属于这个包.
如果我们使用一个类,需要把包前缀全写上.如com.itheima.domain.Student.
这种写法叫做全类名,也叫全限定名.
全类名写的太过麻烦.我们可以在java文件的开头写上 import com.itheima.domain.Student;//这个就叫做导包. 这样java文件类只要提到Student就知道是com.itheima.domain.Student中的Student了.
Open: iamwsll11744455355437933.png
在iDEA当中,项目结构-模块-import module,选择对应包的iml即可
我们甚至可以通过调用 对象名.main()的方式来调用其他类的main方法.(注意传参,传个null就行)
final:修饰不同的类型:
Open: iamwsll11744455582697306.png

写法:public final void func(){} final class Base{ } final int a = 10;(能不能先声明再赋值?不行)
Object 的getClass()就是final的,签名是这样:Open: iamwsll11744455682150677.png
native表示这个函数是从c/c++中写的(这也是为什么它没有方法体的原因)
Open: iamwsll11744455870313931.png

Open: iamwsll11744455883059841.png

Open: iamwsll11744456918153446.png
默认的方式就是只能在同一个包里可见.(一个家庭中可见)
protected:其他包里的子类也可见(一个家庭中可见,外面的儿子也可见)
实际上开发的时候只需要private 和 public
代码块:
Open: iamwsll11744457387521290.png
Open: iamwsll11744457488654631.png
构造代码块会在构造函数之前执行.
这样可以抽取构造函数公共的代码.
现在几乎不用了.因为不够灵活.
Open: iamwsll11744457650715734.png
这个倒是挺重要的.
注意,static代码块当中只能调用静态方法 静态成员.
抽象类
抽象方法:在父类当中,如果故意不写方法体,并用abstract修饰.
Open: iamwsll11744458134136359.png
子类必须重写否则会报错.
拥有抽象方法的类就叫做抽象类.
抽象类必须写成public abstract class ClassName{
}
抽象类不能实例化.
抽象类也要有构造方法(全参,无参),用来给子类调用.
抽象类几乎就是一个正常的类,可以有自己的成员变量.
另外,抽象类也可以不含抽象方法.
抽象类的子类要么重写抽象类所有的抽象方法,要么是抽象类.
tip:抽象类的作用是什么? 抽象方法定义了子类方法的格式.这样,多人开发的时候,不同的人写不同的子类,格式都是一样的. 另外,还有不让别人创造对应的实例的作用.
接口interface
Open: iamwsll1174445902609169.png
这样确实好.
话说回来,c++有多继承,替代了这个interface.
接口的作用是:"这个类"拥有这个"行为":
Open: iamwsll11744459190902815.png
因此,抽象类和interface是两种完全不一样的概念.
接口在地位上和类是很像的.也就是说,项目层级结构上,可以把接口看成一个类.
implements:
Open: iamwsll11744459267110809.png

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

接口的细节:
Open: iamwsll11744459662500603.png

这样写就行:
Open: iamwsll11744459721138799.png

如果要实现多个接口,但是接口之间的方法有重名,怎么办? 只需要重写一次.也就是重写一个方法就行.
接口可以继承,也可以多继承.继承一律用extends
Open: iamwsll11744460074209606.png
实现接口时,需要把接口的父接口全部实现.
Open: iamwsll11744461507793564.png

JDK8 :接口中可以定义有方法体的方法. 分为两种:默认方法,静态方法.
默认方法
- 为什么这么设计?原先的设计中,如果我们增加接口中的方法,就会导致所有的子类都会报错,也就是不兼容,这是非常不好的.于是,默认方法就出现了.默认方法是为了解决接口升级的问题.
-
默认方法格式:public default void fun(){...};
-
默认方法不强制重写.但是如果被重写,重写的时候需要去掉default关键字.
- public修饰符是默认的,可以省略.但是default不能省略.
-
- 如果实现了多个接口,但是多个接口当中存在相同名字的默认方法,子类就必须对该方法进行重写.
静态方法
Open: iamwsll1174446087255310.png
- 静态方法不能重写.(不能用override修饰).为什么?因为静态方法不能添加到虚方法表里.
- 但是能定义一个同名的方法.(只是刚好有个重名的方法).由于调用静态方法只能通过接口名,因此不会用冲突.
JDk9:可以定义私有方法.
这个倒是很好理解,因为需要抽取公共代码,并且不需要外界看到.
两种方式:
private void fun(){...};//在默认方法中可以调用
private static void fun(){...};//在静态方法当中可以调用
内部类
A类的内部定义B类,B类就是内部类.
Open: iamwsll11744462611210399.png
关于右下角:其实意思就是外部类访问内部类,就需要这样:Open: iamwsll1174446276558928.png

在我们查看源码的时候,就可以看到内部类:
Open: iamwsll11744462837320734.png
如这些蓝色的c开头的就是ArrayLists的内部类.
比如迭代器就是ArrayLists的内部类.
内部类的分类
成员内部类(不常用)
如之前的适配器.
Open: iamwsll11744463663545918.png
Open: iamwsll11744463748601796.png
注意,用static修饰的就是静态内部类了.
Open: iamwsll11744463787839688.png
如
Outer o = new Outer();
Outer.Inner oi = o.new Inner();
当然,可以直接成:Outer.Inner oi = new Outer().new Inner(); 我们可以看出,创建外部类对象时,并不会创建内部类对象. 内部类对象的创建,必须由某个具体的外部类对象来new才行.
Open: iamwsll1174446406133060.png
这样写会报错吗?会,因为inner是private的,因此外部并不知道Outer.Inner是什么.
解决方法: 写成 Object inner 0=o.getInstance();
如果我们这样写:
Open: iamwsll11744464171523277.png
则会打印出地址:
Open: iamwsll11744464187556226.png
可见内部类和外部类是用$ 隔开的.
事实上,Outer和Inner类是两个不同的class文件.Inner类的字节码文件叫Outer&Inner.java
我们一般采用让内部类private,用get方法对外提供.
内部类获取变量: 获取外部类变量直接获取就行. 如果出现重名,顺序也是"就近原则",局部变量>自己的成员变量>外部类成员变量. 如果重名,但是还想调用外部类的变量,那么需要通过Outer.this.name来调用
Open: iamwsll11744464775449842.png
由此可见:
外部类对象并不包含内部类对象.内部类对象实际上是个独立的内存空间,只不过内部类对象会有一个Outer.this指针指向自己的外部类对象.
静态内部类(不常用)是成员内部类的特殊情况.
静态内部类只能访问外部类中的静态方法和静态变量.
Open: iamwsll11744471741823258.png
Open: iamwsll11744472016605382.png
事实上,static内部类是一个完全独立的类,和普通的类没有任何区别,表现上没有什么差异性.只不过,访问这个类的东西都要加一个Outer前缀罢了.
成员内部类会有一个Outer.this指向自己的外部类对象的地址.静态内部类则没有这个指针
至于只能访问外部类中的静态方法和静态变量 那就也不奇怪了,毕竟内部类和外部类没有关系.
局部内部类(不常用)
Open: iamwsll11744472654225787.png
由于地位上就是局部变量,因此public/private这样不能修饰局部变量的就不能修饰局部内部类;而可以修饰局部变量的如final就可以
Open: iamwsll11744472833450531.png

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

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

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