Java基础-温故知新

54 阅读9分钟

1 对象与引用

1.对象创建

  • 堆内存 (heap):保存每一个对象的属性内容,堆内存需要用关键字 new 才可以开辟,如果一个对象没有对应的堆内存指向,将无法使用;

  • 栈内存(stack):保存的是一块堆内存的地址数值,可以把它想象成一个int 型变量(每一个int 型变量只能存放一个数值),所以每一块栈内存只能够保留一块堆内存地址。

  • 堆内存:保存对象的真正数据,都是每一个对象的属性内容;

  • 栈内存:保存的是一块堆内存的空间地址,为了方便理解,可以简单地将栈内存中保存的数据理解为对象的名称(Book book), 就假设保存的是“book”对象名称。

2.一块堆内存空间(保存对象的属性信息)可以同时被多个栈内存共同指向,则每一个栈内存都可以修改同一块堆内存空间的属性值

3.在引用数据类型关系时,一块没有任何栈内存指向的堆内存空间将成为垃圾,所有的垃圾会不定期地被垃圾收集器 (Garbage Collector)回收,回收后会被释放掉其所占用的空间。

    public static void main(String args[]){
        Book bookA = new Book();    //声明并实例化第一个对象
        Book bookB = new Book();    //声明并实例化第二个对象
        bookA.title ="Java开发";     //设置第一个对象的属性内容
        bookA.price =89.8;
        bookB.title ="JSP开发";     //设置第二个对象的属性内容
        bookB.price =69.8;
        bookB = bookA;                //引用传递
        bookB.price =100.1;         //利用第二个对象设置属性内容
        bookA.getInfo();            //调用类中的方法输出信息
     }

2 封装

封装的目的是,避免其他类的方法直接对对象的属性进行赋值操作,而是通过对象中特定规则下的赋值方法去给属性赋值。任何对象的属性都应设为private,就是为了避免其他类直接赋值,比如直接把价格设置为负数,这是错误的;反而通过在setter方法中设置限制条件,可以避免这种情况。

3 匿名对象

直接通过new Class()创建的对象,由于没有栈内存,所以只能使用一次,之后会被GC回收。

一般用于参数传递、链式调用、对象初始化等。

4 多态

多态是指允许程序中出现重名现象。

  • 方法重载:在一个类中,允许多个方法使用同一个名字,但方法的参数不同,完成的功能也不同;
  • 对象多态:子类对象可以与父类对象进行相互转换,而且根据其使用的子类不同完成的功能也不同

5 String类设计模式——享元设计模式

    在JVM的内存结构中,存在一个称为"字符串常量池"的对象池。当使用直接赋值的方式定义一个String对象时,如果该字符串在常量池中不存在,则会将这个字符串对象添加到常量池中保存。如果后续有其他 String对象也采用了相同的直接赋值,并且具有相同的内容,就会直接引用已经存在于常量池中的对象,而不是再次创建新的对象。

    如果要明确地调用 String 类中的构造方法进行String 类对象的实例化操作,那么一定要使用关键字 new, 而每当使用关键字 new 就表示要开辟新的堆内存空间。解决办法是使用String类的intern()方法将以构造方法方式创建的对象手动加入到"字符串常量池"中。

    综上,对于String类的两种对象实例化方式的区别说明如下:

  • 直接赋值(String str = "字符串"; ): 只会开辟一块堆内存空间,并且会自动保存在对象池中以供下次重复使用;
  • 构造方法(String str = new String("字符串")): 会开辟两块堆内存空间,其中有一块空间将成为垃圾,并且不会自动入池,但可以使用intern()方法手工入池。

6 继承的限制

    限制一:Java 不允许多继承,但是允许多层继承。

    限制二: 子类在继承父类时,严格来讲会继承父类中的全部操作,但是对于所有的私有操作属于隐式继承,而所有的非私有操作属于显式继承。

    父类的private属性需要通过getter/setter方法操作。

    限制三: 在子类对象构造前一定会默认调用父类的构造(默认使用无参构造),以保证父类的对象先实例化,子类对象后实例化。

    子类会默认调用父类的无参构造函数。如果父类没有无参构造函数,子类会无法正常实例化。

7 static与final

static全局共享,只占一份内存,可通过类名直接访问;

final类不可继承,final方法不可重写或覆盖,final变量不可修改。

8 抽象类

抽象类就是指在普通类的结构里面增加抽象方法的组成部分,抽象方法指的是没有方法体的方法,同时抽象方法还必须使用 abstract 关键字进行定义。拥有抽象方法的类一定属于抽象类,抽象类要使用 abstract 声明。

所有的普通方法上面都会有一个"{...}",来表示方法体,有方法体的方法一定可以被对象直接调用。抽象类中的抽象方法没有方法体,声明时不需要加“{}”,但是必须有 abstract 声明,否则在编译时将出现语法错误。

(1)抽象类里面由于会存在一些属性,那么在抽象类中一定会存在构造方法,目的是为属性初始化,并且子类对象实例化时依然满足先执行父类构造再调用子类构造的情况 (2)抽象类不能使用 final定义,因为抽象类必须有子类,而 final定义的类不能有子类; (3)抽象类中可以没有任何抽象方法,但是只要是抽象类,就不能直接使用关键字new 实例化对象。

(4)抽象类中依然可以定义内部的抽象类,而实现的子类也可以根据需要选择是否定义内部类来继承抽象内部类。

(5)外部抽象类不允许使用 static 声明,而内部的抽象类允许使用 static 声明,使用 static 声明的内部抽象类就相当于是一个外部抽象类,继承的时候使用“外部类.内部类”的形式表示类名称。

(6)在抽象类中,如果定义了static 属性或方法时,就可以在没有对象的时候直接调用。

9 浅拷贝与深拷贝

浅拷贝只是复制了指针或引用,通常是通过使用赋值操作符或者拷贝构造函数来实现的;

深拷贝则是复制了整个对象,使用 ObjectOutputStream 将原始对象写入一个 ByteArrayOutputStream 中,然后使用 ByteArrayInputStream 和 ObjectInputStream 读取这个流并创建一个新的对象。

10 Thread、Runnable、Callable

1.java.lang.Thread 是一个负责线程操作的类,任何类只需要继承 Thread 类就可以成为一个线程的主类。线程启动的 主方法需要覆写 Thread 类中的 run()方法实现,但如果直接调用 run()方法,并不能启动多线程,多线程启动的唯一方法就是 Thread 类中的 start()方法。

        之所以要通过start()方法而不是直接通过run()方法,是因为start() 方法中调用了一个 start0()方法,该方法可以根据不同的操作系统进行资源的分配。

        但Thread只能单继承,通过实现Runnable接口可以实现多继承。

2.使用Runnable 接口并且正常覆写了 run()方法,如果启动多线程, 一定需要通过 Thread 类中的 start() 方法才可以完成。

        Thread 类中提供的一个有参构造方法:

public Thread(Runnable target);
//    范例 5: 利用Thread 类启动多线程
public class TestDemo {
    public static void main(String[] args){
        MyThread mt1 = new MyThread("线程A");    // 实例化多线程类对象
        MyThread mt2 = new MyThread("线程B"); 
        MyThread mt3 = new MyThread("线程C"); 
        new Thread(mt1).start();        //利用Thread 启动多线程
        new Thread(mt2).start();
        new Thread(mt3).start();
    }
}

通过new Thread(mt3).start();方式可以启动多线程。

3.Thread 类是 Runnable 接口的子类。使用 Runnable 接口可以更加方便地表示出数据共享的概念。 例如买票系统使用Runnable可以对全局变量实现共享,而Thread会每个线程各自创建一个全局变量。

4.Callable解决了Runnable的run()方法没有返回值的缺点。

        当多线程的主体类定义完成后,要利用 Thread 类启动多线程,但是在 Thread 类中并没有定义任何构造方法可以直接接收 Callable 接口对象实例,并且由于需要接收 call()方法返回值的问题,从 JDK1.5开始, Java 提供了一个 java.util.concurrent.FutureTask<V> 类。

        通过FutureTask 类继承结构可以发现它是 Runnable 接口的子类,并且 FutureTask 类可以接收 Callable 接口实例,这样依然可以利用Thread 类来实现多线程的启动,而如果要想接收返回结果,利用 Future 接口中的 get() 方法即可。

//    范例 10: 启动多线程
import java.util.concurrent.FutureTask;

public class TestDemo {
    public static void main(String[] args) throws Exception{
        MyThread mt1 = new MyThread();  // 实例化多线程对象
        MyThread mt2 = new MyThread(); // 实例化多线程对象
        FutureTask<String> task1 = new FutureTask<String>(mt1);
        FutureTask<String> task2 = new FutureTask<String>(mt2);
        // FutureTask是 Runnable接口子类,所以可以使用Thread类的构造来接收task对象
        new Thread(task1).start();              //启动第一个线程
        new Thread(task2).start();               //启动第二个线程
        //多线程执行完毕后可以取得内容,依靠FutureTask的父接口Future中的get()方法实现 
        System.out.println("A线程的返回结果:"+ task1.get());
        System.out.println("B线程的返回结果:"+ task2.get());
    }
}