面向对象编程
1 方法
所谓的方法就是类中声明的函数,所以声明方式完全一样,但是必须通过使用对象进行调用,属于类的一部分。类的方法
def main(args: Array[String]): Unit = {
val t1 = new Test()
val t2 = new Test()
println(t1 == t2)
}
class Test{
}
两者内存地址不同
重写equals判定规则,但是重写equals需要满足条件:类型相同
def main(args: Array[String]): Unit = {
val t1 = new Test()
val t2 = new Test()
println(t1 == t2)
}
class Test{
val id = 1
override def equals(obj: Any): Boolean ={
//判断类型是否相同
if(obj.isInstanceOf[Test]){
//将other转换成Test类型
var other = obj.asInstanceOf[Test]
this.id == other.id
}else{
false
}
}
一些常用方法,不用预先定义即可使用
java.lang这个包中的类在java中使用不需要显示的导入,scala中也存在同样的操作
- java.lang
- scala
- Predef
println("123")
Predef.println("123")
以下代码类似于java中的Test.class,获取到这个对象
val value : Class[Test] = classOf[Test]
方法的重载
两个方法的名字相同时,看名称和参数列表(个数,顺序,类型),如果对应的类型没有查找到,会找对应的通用类型(父类)
public class TestOverload {
public static void main(String[] args) {
AAA aaa = new BBB();
test(aaa); //aaa
}
public static void test(AAA aaa){
System.out.println("aaa");
}
public static void test(BBB bbb){
System.out.println("bbb");
}
}
class AAA{
}
class BBB extends AAA{
}
方法的重写–动态绑定机制
子类重写父类的方法,可能是因为父类的方法不完整,也可能是父类的程序逻辑不满足子类的要求
也就是如何区分父类子类当中相同的方法,需要采用动态绑定机制:在调用对象的成员方法过程中,将方法和对象的实际内存进行绑定,然后调用
public class TestOverride {
public static void main(String[] args) {
A1 a1 = new A1();
System.out.println(a1.sum()); //20
B1 b1 = new B1();
System.out.println(b1.sum()); //40
A1 a2 = new B1();
System.out.println(a2.sum()); //40
}
}
class A1{
public int i = 10;
public int sum(){
return i + 10;
}
}
class B1 extends A1{
public int i = 20;
public int sum(){
return i + 20;
}
}
现在注释掉B1中的sum()方法,a3的实际内存是B1,在B1中找不到sum方法,所以去找父类,父类中的属性只能在父类中声明,动态绑定机制对属性无效,在哪里声明,在哪里使用,所以结果为20,而不是30
A1 a3 = new B1();
System.out.println(a3.sum()); //20
在两类中添加getI方法
class A1{
public int i = 10;
public int sum(){
return getI() + 10;
}
public int getI(){
return i;
}
}
class B1 extends A1{
public int i = 20;
// public int sum(){
// return i + 20;
// }
public int getI(){
return i;
}
}
只要调用成员方法,不论直接调用还是间接调用,就会遵循动态绑定机制,去找实际内存地址
A1 a3 = new B1();
System.out.println(a3.sum()); //30
静态方法,动态绑定机制不会生效,不会找实际内存地址,是什么类型就去找对应类的静态方法
2 对象
java中构造对象的方法
- 反射
- new
- 反序列化(将文件中的数据放到内存当中)
- clone(java不能操作硬件,所以不能直接复制内存,所以委托操作系统执行复制功能,由native本地化的程序来完成复制功能)
所以,kafka高吞吐消息系统实现原理之一的零拷贝设计思路来源于clone:直接由操作系统进行交互,不用传给java application,java application直接去cache执行输入输出
3 构造方法
java中的构造方法
- 提供无参,公共的构造方法
- 构造方法可以重载
- 构造方法可以互相调用
- 如果父类的构造方法有参,构建子类对象之前,先把父类的构造方法显示调用一下(super)
- 构造方法的名称应该和类型一致,所以子类无法构建父类的构造方法
scala中的方法
-
提供无参,公共的构造方法
-
构造方法的名称和类名不一致
scala是一个完全面向对象的语言,又是一个完全面向函数的语言。所以类也是一个函数,声明一个类就等同于声明一个函数
那么就可以在类名的后面加上一个小括号,表示构造参数列表,花括号内的内容又叫做构造方法体,还叫做类的主体内容
所以,类的初始化内容如下,类花括号内的内容在创建对象时都会运行
def main(args: Array[String]): Unit = {
val user = new User()
}
class User(){
val name:String = "zhangsan"
def test(): Unit ={
}
println("XXX")
}
如果提供了类的构造方法,那么jvm不会再给类提供无参的构造方法
def main(args: Array[String]): Unit = {
val user = new User("zhangsan")
}
class User(name:String){
println("name:" + name)
}
如果希望有两个构造方法,一个有参数,一个无参数,scala无法做到
之所以在类名后面提供构造方法,通过添加一个小括号来实现,主要目的是为了类的初始化
scala将构造方法分为两大类:主构造方法和辅助构造方法,用于完成类的初始化操作的构造方法称为主构造方法,其他的构造方法称之为辅助构造方法,辅助构造方法的名字是this关键字,其他和普通方法一样
辅助构造方法
辅助构造方法的作用是在类初始化完成后,做一些辅助功能,所以辅助方法在执行之前首先调用主构造方法完成类的初始化
def main(args: Array[String]): Unit = {
val user = new User("zhangsan")
}
class User(name:String){
println("我是主构造方法,我先来")
def this(){
this("zhangsan")
println("初始化完成了,到我上场了...")
}
只会执行主构造方法,因为辅助构造方法无参,初始化时有参数,调用主构造方法
def main(args: Array[String]): Unit = {
val user = new User()
}
class User(name:String){
println("我是主构造方法,我先来")
def this(){
this("zhangsan")
println("初始化完成了,到我上场了...")
}
}
全部执行
辅助构造方法的重载
辅助构造方法可以重载,并且可以互相调用,但是调用的辅助构造方法需要提前声明
def main(args: Array[String]): Unit = {
val user = new User(1)
}
class User(name:String){
println("我是主构造方法,我先来")
def this(){
this("zhangsan")
println("初始化完成了,到我上场了...")
}
def this(age:Int){
this()
println("我也是辅助构造方法")
}
}
构造方法传参的目的:将外部的参数传递给内部使用
/**
* class User{
* private String name;
* public User(String name){
* this.name = name;
* }
* }
*/
scala中的实现:需要在构造方法前使用var或val声明
def main(args: Array[String]): Unit = {
var user = new User("zhangsan")
user.name = "lisi"
}
class User(var name : String){
}
4 继承
子类想初始化,必须先等父类初始化完成
def main(args: Array[String]): Unit = {
val user = new User("zhangsan")
}
class Person(){
println("Person")
}
class User(var name : String) extends Person {
println("User")
}
如果父类不提供无参构造方法,子类通过在父类后面添加小括号去显示调用父类的构造方法
def main(args: Array[String]): Unit = {
val user = new User("zhangsan")
}
class Person(p:String){
println("Person")
}
class User(var name : String) extends Person("zhangsan") {
println("User")
}
注意以下代码的执行顺序Person–>Person1–>User–>User1
def main(args: Array[String]): Unit = {
val user = new User()
}
class Person(p:String){
println("Person")
def this(){
this("zhangsan")
println("Person1")
}
}
class User(var name : String) extends Person() {
println("User")
def this(){
this("zhangsan")
println("User1")
}
}
5 单例
在单例中存在构造方法私有化,在参数列表前添加private关键字实现
在java中想要在上述环境下创建对象,需要提供一个公共的静态的返回本类型的方法来得到这个单例的对象
scala中没有静态语法,但是可以直接使用java中的静态操作,这是因为scala采用了特殊的处理方式来代替静态语法:object
object关键字可以用于创建对象,对象的名字就是声明的名字
def main(args: Array[String]): Unit = {
val person = Person.getInstance()
println(person)
}
//伴生类
class Person private(){
}
//伴生对象
object Person{
def getInstance():Person = {
new Person()
}
}
为什么可以在一个类中访问另一个类的私有构造方法,是因为object修饰的类会被编译成两个class文件Person.class和Person$.class
使用object声明的类和对象存在关系,这个对象等同于伴随着这个类创建时所产生的,所以将这个对象称之为伴生对象,这个类称之为伴生类,伴生对象是一个对象,可以访问伴生类中的所有东西,包括私有的
伴生对象是scala模拟静态语法产生的,一般写代码时,将静态语法操作的代码写在伴生对象中,将成员方法或属性写在伴生类中
class ScalaUser{
var name:String = _
}
object ScalaUser{
var email:String = _
}
前者只能new对象,通过对象调用方法(伴生类)
后者直接可以使用类名访问(伴生对象)
scala中伴生对象就是单例的
伴生对象只需要声明,不用创建,不需要构造参数列表
class Test private(){
}
object Test{
}
单例模式存在一个问题:创建的对象不会被回收,需要显示的回收(设置为null)
如果伴生对象中构建对象的方法名称为apply,编译器可以自动识别,所以方法名可以省略
def main(args: Array[String]): Unit = {
val test = Test.apply() //二者相同
val test1 = Test()
}
class Test private(){
}
object Test{
def apply(): Test = {
new Test()
}
}
以下三种调用方式不同
val test1 = new Test() //调用类的构造方法
val test2 = Test() //调用的是伴生对象的apply方法
val test3 = Test //伴生对象本体