Java面向考点复习

Java面向考点复习

1.Action event 事件监听方法(鼠标等事件)

在这里插入图片描述

  • Event(事件):用户对组件的一次操作称为一个事件,以类的形式出现。例如,键盘操作对应的事件类是 KeyEvent。
  • Event Source(事件源):事件发生的场所,通常就是各个组件,例如按钮 Button。
  • Event Handler(事件处理者):接收事件对象并对其进行处理的对象事件处理器,通常就是某个Java类中负责处理事件的成员方法。

授权模型(Delegation Model): 事件源可以把在其自身上所有可能发生的事件分别授权给不同的事件处理者来处理。

事件处理者也称为监听器,监听器时刻监听事件源上所有发生的事件类型,一旦该事件类型与自己所负责处理的事件类型一致,就马上进行处理。授权模型把事件的处理委托给外部的处理实体进行处理,实现了将事件源和监听器分开的机制。


动作事件监听器

  • 事件名称:ActionEvent。
  • 事件监听接口: ActionListener。
  • 事件相关方法:addActionListener() 添加监听,removeActionListener() 删除监听。
  • 涉及事件源:JButton、JList、JTextField 等。

自定义事件监听器必须继承 ActionListener 类,并重写父类的 actionPerformed() 方法。在 actionPerformed() 方法内编写按钮被单击后执行的功能。

以下示例代码展示了内部类和匿名内部类实现监听的方式,除此之外还可以直接在主类实现ActionListener接口以及外部类实现接口在主类中创建对象共4种方式实现监听

package Review;

import java.awt.BorderLayout;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;

public class ActionListenerDemo extends JFrame
{
    JLabel label;
    JButton button1;
    int clicks = 0;

    public ActionListenerDemo()
    {
        setTitle("动作事件监听器示例");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setBounds(100,100,400,200);
        JPanel contentPane=new JPanel();
        contentPane.setBorder(new EmptyBorder(5,5,5,5));
        contentPane.setLayout(new BorderLayout(0,0));
        setContentPane(contentPane);
        label=new JLabel(" ");
        label.setFont(new Font("楷体",Font.BOLD,16));    //修改字体样式
        contentPane.add(label, BorderLayout.SOUTH);
        button1=new JButton("我是普通按钮");    //创建JButton对象
        button1.setFont(new Font("黑体",Font.BOLD,16));    //修改字体样式
//        button1.addActionListener(new ActionListener()
//        {
//            public void actionPerformed(ActionEvent e)
//            {
//                label.setText("按钮被单击了 "+(clicks++)+" 次");
//            }
//        });
        button1.addActionListener(new button1ActionListener());
        contentPane.add(button1);
    }
    
    //处理按钮单击事件的匿名内部类
    class button1ActionListener implements ActionListener
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {
            label.setText("按钮被单击了 "+(clicks++)+" 次");
        }
    }
    public static void main(String[] args)
    {
        ActionListenerDemo frame=new ActionListenerDemo();
        frame.setVisible(true);
    }
}

焦点事件监听器

如将光标离开文本框时弹出对话框,或者将焦点返回给文本框等。

  • 事件名称:FocusEvent。
  • 事件监听接口: FocusListener。
  • 事件相关方法:addFocusListener() 添加监听,removeFocusListener() 删除监听。
  • 涉及事件源:Component 以及派生类。

FocusEvent 接口定义了两个方法,分别为 focusGained() 方法和 focusLost() 方法,其中 focusGained() 方法是在组件获得焦点时执行,focusLost() 方法是在组件失去焦点时执行。

核心代码:

		txtfield1=new JTextField();		
		txtfield1.addFocusListener(new FocusListener()
        {
            @Override
            public void focusGained(FocusEvent arg0)
            {
                // 获取焦点时执行此方法
                label.setText("文本框获得焦点,正在输入内容");
            }
            @Override
            public void focusLost(FocusEvent arg0)
            {
                // 失去焦点时执行此方法
                label.setText("文本框失去焦点,内容输入完成");
            }
        });
		contentPane.add(txtfield1);

考试涉及内容:

(1)监听键盘事件

通过addKeyListener,添加KeyAdapter对象(如果添加KeyListener对象就需要重写keyTypedkeyPressedkeyReleased三个方法),重写keyPressed即可

package Review;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class KeyBoardListener
{
    public static void main(String[] args)
    {
        Frame f = new Frame();
        f.setBounds(200, 200, 200, 200);
        f.addKeyListener(new KeyListener(){

            @Override
            public void keyTyped(KeyEvent e) {
                System.out.println("键盘输入");
            }

            @Override
            public void keyPressed(KeyEvent e) {
                System.out.println("键盘敲击");
                int code = e.getKeyCode();
                System.out.println(code);
            }

            @Override
            public void keyReleased(KeyEvent e) {
                System.out.println("键盘释放");
            }
        });
        f.setVisible(true);
    }
}
(2)监听鼠标事件
package Review;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

public class MouseListenerDEMO
{
    public static void main(String[] args)
    {
        Frame f = new Frame();
        f.setBounds(200, 200, 200, 200);
        f.addMouseListener(new MouseListener() {
            @Override
            public void mouseClicked(MouseEvent e) {
                System.out.println("鼠标点击");
            }

            @Override
            public void mousePressed(MouseEvent e) {
                System.out.println("鼠标按下");
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                System.out.println("鼠标抬起");
            }

            @Override
            public void mouseEntered(MouseEvent e) {
                System.out.println("鼠标进入");
            }

            @Override
            public void mouseExited(MouseEvent e) {
                System.out.println("鼠标离开");
            }
        });
        f.setVisible(true);
    }
}

2.char类型存储需要字节数

基础知识:Unicode 相当于一张表,建立了字符与编号之间的联系,而UTF-XX是这个编号的存储方式

Unicode 为世界上所有字符都分配了一个唯一的数字编号,这个编号范围从 0x000000 到 0x10FFFF (十六进制),有 110 多万,每个字符都有一个唯一的 Unicode 编号。

Unicode字符集规定的标准编码方案是UCS-2(UTF-16),用两个字节表示一个Unicode字符

① 对于编号在 U+0000 到 U+FFFF 的字符(常用字符集),直接用两个字节表示。
② 编号在 U+10000 到 U+10FFFF 之间的字符,需要用四个字节表示。

Java为应对这种情况,考虑到向前兼容的要求,Java用两个char来表示那些需要4字节的字符。所以,java中的char是占用两个字节,只不过有些字符需要两个char来表示。


3.字符串常量池

每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分肯定常量池中一定不存在两个相同的字符串。

String a="AA";
String b="AA";
String c=new String("AA");

a、b和字面上的AA都是指向JVM字符串常量池中的"AA"对象,他们指向同一个对象。

new关键字一定会产生一个对象AA,同时这个对象是存储在堆中。所以上面这一句应该产生了两个对象:保存在方法区中字符串常量池的AA和保存堆中AA。但是在Java中根本就不存在两个完全一模一样的字符串对象。故堆中的AA应该是引用字符串常量池中AA。所以c、堆AA、池AA的关系应该是:c—>堆AA—>池AA。

虽然a、b、c、c是不同的引用,但是从String的内部结构我们是可以理解上面的。String c = new String(“AA”);虽然c的内容是创建在堆中,但是他的内部value还是指向JVM常量池的AA的value,它构造AA时所用的参数依然是AA字符串常量。所以a==b是true,因为内存地址是一样的,a==c是false,因为c的内存地址是在堆中new的是新的地址

img
对于字符串而言,==比较的是存储地址,显然相同地址的字符串内容相同,但相同内容字符串可以在多个地址。+substring等方式产生的字符串并不共享。例如"Hello".substring(0,3) == "Hel"就有可能为false


4.向上转型&动态链接

从一个大范围类到小范围类是可以强转的,如:

Son son = new Son();
Father father = (Father)son;

反过来

Father father = new Father();
Son son = (Son)father;

就不行

向上转型:

因为子类是对父类的一个改进和扩充,所以一般子类在功能上较父类更强大,属性较父类更独特, 定义一个父类类型的引用指向一个子类的对象既可以使用子类强大的功能,又可以抽取父类的共性。 所以,父类类型的引用可以调用父类中定义的所有属性和方法,而对于子类中定义而父类中没有的方法,父类引用是无法调用的;

动态链接:

当父类中的一个方法只有在父类中定义而在子类中没有重写的情况下,才可以被父类类型的引用调用; 对于父类中定义的方法,如果子类中重写了该方法,那么父类类型的引用将会调用子类中的这个方法。


5.异常种类 异常处理

在这里插入图片描述

(1)throw

在指定方法中抛出指定的异常,必须写在方法内部

throw new xxxException("异常产生的原因");

对方法传递过来的参数必须先进行合法性校验,比如参数是数组却传过来了null就要抛出空指针异常(NullPointerException)

注:NullPointerException是一个运行期异常,不需要自己处理,交给JVM处理即可,但对于编译异常就必须通过throws或是用try…catch处理

Objects类自带一个检测是否为空指针的方法requireNonNull,可以直接使用


(2)throws

在方法声明时使用

修饰符 返回值类型 方法名(参数列表) throws AAAException,BBBException…
{
    throw new AAAException("产生原因");
    throw new BBBException("产生原因");
}

1.throws后需要声明方法内抛出的全部异常对象(有子父类关系则只用声明父类)

2.调用了抛出异常的方法就必须通过throws交给调用者->JVM去处理,或是用try…catch自己处理(即throws等需要在方法声明处与调用处声明两次)


(3)try…catch

可执行后续代码

try{
    可能产生异常的代码
}
catch(异常类名 变量名){
    对异常对象的处理(一般会选择记录到日志)
}
...
//可以有多个catch,但子类异常变量必须在前面

执行catch后会继续执行后续代码(但是try中产生异常后的代码不会执行),而throws交给JVM会进行中断处理

(4)Throwable中的三个异常处理方法

1.getMessage() 返回Throwable的简短描述
2.toString() 返回Throwable的详细消息字符串
3.printStackTrace() 打印异常对象

(5)finally

放在catch代码块后面,不管是否有异常都会执行
finally一般用于资源释放/回收,注意finally中的return一定会执行,为了避免最好不要在finally中返回

(6)子父类异常

父类方法声明抛出的异常子类在重写时只能抛出相同异常其子类异常不抛出异常
父类方法不抛出异常子类重写时只能捕获不能抛出(即只能try…catch)


6.静态工厂模式

img

public class ProductFactory {
  public static IProduct product=null;
  public static IProduct getProduct(String productType) throws InstantiationException, IllegalAccessException, ClassNotFoundException{
      
//   if(productType==null||productType.trim().equals("")){//默认创建ProductA
//     product=new ProductA();
//   }else if(productType.trim().equals("ProductA")){
//     product=new ProductA();
//   }else if(productType.trim().equals("ProductB")){
//     product=new ProductB();
//   }
//静态工厂一般使用类的反射来构建对象,像上面的构建也可以。
      
    if(productType.trim().equals("ProductA")){
      product=(IProduct)Class.forName("org.test.design.sf.ProductA").newInstance();
    }else if(productType.trim().equals("ProductB")){
      product=(IProduct)Class.forName("org.test.design.sf.ProductB").newInstance();
    }
    return product;
  }
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
    IProduct product_a=ProductFactory.getProduct("ProductA");
    product_a.work();
    IProduct product_b=ProductFactory.getProduct("ProductB");
    product_b.work();
}

7.值传递&引用传递

首先,Java中所谓的引用传递本质仍然是值传递。

先来看定义:

  • 值传递:将实际参数复制一份传递到函数中,函数对参数的修改不会影响实际参数
  • 引用传递:将实际参数的地址传递到函数中,函数对参数的所有修改直接体现于实际参数

在java的值传递中,是将实际参数引用的地址复制一份给形参。 换句话说,形参是一个新的引用,指向实际参数所在内存空间。举个最简单的例子,对于传统意义上的引用传递而言,在函数内使用new会使原引用指向另一片内存空间。而在java中,会复制(或者说临时创建)一个新的引用指过去,那么new的时候自然就是这个引用指向new的空间,而实际参数的引用不变,对new出来的对象的修改不作用到实际参数上。

在这里插入图片描述

Java中的传递,是值传递,而这个值,对于基本数据类型是值的复制,对引用数据类型实际上是对象的引用地址的复制。值得注意的是,在大多数语言中引用传递都是通过这样值传递的方式进行的,C++中直接传引用反而较为少见


易错示例:

public class Example{ 
	String str=new String("good"); 
	char[]ch={'a','b','c'};
    
    public void change(String str,char ch[])
    { 
		str="test ok"; ch[0]='g';
	} 
    
public static void main(String args[]){ 
	Example ex=new Example(); 
	ex.change(ex.str,ex.ch); 
	System.out.print(ex.str+" and "); 
	System.out.print(ex.ch); 
} 
} 

结果为good and gbc,注意String是不可变的(见3.字符串常量池


8.常用类

Math(主要看返回值和参数数量)

public static double sin (double radians)

public static double toRadians (double degree)

public static double toDegrees (double radians)

public static double asin (double a)

public static double exp ( double x )

public static double log ( double x )

public static double log10 (double x )

public static double pow ( double a, double b )

public static double sqrt ( double x )

public static double ceil ( double x ) 【向上取整】

public static double floor ( double x ) 【向下取整】

public static double rint ( double x ) 【取最接近的整数,如果有两个同样接近的整数(.5),就两个中随机取一个 】

public static int round ( float x ) Return (int)Math.floor (x + 0.5 )

public static long round ( double x ) Return (long)Math.floor ( x + 0.5)

abs 返回一个数(int、 long、 float、 double)的绝对值

public static double random()


Date

1、public Date()——分配 Date 对象并初始化此对象,以表示分配它的时间(精确到毫秒)。

2、public Date(long date)——根据给定的毫秒值创建日期对象。

3、public long getTime()——日期转毫秒值

通过getTime方法可以将一个日期类型转换为long类型的毫秒值

4、public void setTime(long time)——毫秒值转日期

当然也可以通过构造函数public Date(long date)将毫秒值转为日期类型。

通常我们会比较2个日期的大小,Date类提供以下方法用来比较

1、public boolean before(Date when)——测试此日期是否在指定日期之前,当且仅当此Date对象表示的瞬间比when表示的瞬间早,才返回true;否则返回false。

2、public boolean after(Date when)——测试此日期是否在指定日期之后,当且仅当此Date对象表示的瞬间比when表示的瞬间晚,才返回true;否则返回false。

3、public int compareTo(Date anotherDate)——比较两个日期的顺序。

如果参数Date等于此Date,则返回值0;如果此Date在Date参数之前,则返回小于0的值;如果此Date在Date参数之后,则返回大于0的值。


9.instanceof关键字

instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例,用法为:

boolean result = obj instanceof Class

其中 obj 为一个对象,Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。如果 obj 为 null,那么将返回 false。


10.非访问修饰符

static 修饰符

静态变量:

static 关键字用来声明独立于对象的静态变量,无论一个类实例化多少对象,它的静态变量只有一份拷贝。 静态变量也被称为类变量。局部变量不能被声明为 static 变量。

静态方法:

static 关键字用来声明独立于对象的静态方法。静态方法从参数列表得到数据,然后计算这些数据。静态方法不能访问非静态的数据和方法,因为这两项都依赖于具体的实例,而静态方法在对象实例化之前就已经被JVM装载,而类中的实例变量和实例对象必须在对象开辟堆内存之后才能使用。

优点:

1.属于类级别的,所有不需要创建对象就可以直接使用;

2.全局唯一,内存中唯一,静态变量可以唯一标识某些状态;

3.初始化在类加载时候,常驻在内存中,调用快捷方便。


final 修饰符

final 变量:

final 表示"最后的、最终的"含义,变量一旦赋值后,不能被重新赋值。被 final 修饰的实例变量必须显式指定初始值。

final 修饰符通常和 static 修饰符一起使用来创建类常量。

final 方法:

父类中的 final 方法可以被子类继承,但是不能被子类重写。

声明 final 方法的主要目的是防止该方法的内容被修改。


abstract 修饰符

抽象类:

抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。

一个类不能同时被 abstract 和 final 修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。

抽象类可以包含抽象方法和非抽象方法。

抽象方法

抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。

抽象方法不能被声明成 final 和 static。

任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。

如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。


synchronized 修饰符

synchronized 关键字声明的方法同一时间只能被一个线程访问。


transient 修饰符

序列化的对象包含被 transient 修饰的实例变量时,java 虚拟机跳过该特定的变量。

该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。


volatile 修饰符

volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。


11.多线程实现方式(主要两种)

(1)创建Thread类的子类

1.创建Thread类的子类
2.重写run方法,设置线程任务
3.创建当前类对象
4.调用父类的start方法,开启新线程,执行run方法

先执行优先级高的线程,若优先级相同则随机选择一个执行

(2)使用Runnable接口实现类

1.创建Runnable接口实现类
2.重写run方法,设置线程任务
3.创建当前类对象
4.创建Thread类对象,参数为该接口实现类对象,调用start方法

(3)区别

1.实现Runnable接口可以再继承其它类或实现其它接口,避免了局限性

2.实现Runnable接口把设置线程任务和开启新线程进行分离(解耦)


plus:实现Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 一、创建执行线程的方式三:实现Callable接口。相较于实现Runnable接口的方式,方法可以有返回值,并且可以抛出异常
 * 二、执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果
 */
public class TestCallable {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();

        // 1.执行Callable方式,需要FutureTask实现类的支持,用于接收运算结果
        FutureTask<Integer> result = new FutureTask<>(td);
        new Thread(result).start();

        // 2.接收线程运算后的结果
        Integer sum;
        try {
            //等所有线程执行完,获取值,因此FutureTask可用于闭锁
            sum = result.get();
            System.out.println("-----------------------------");
            System.out.println(sum);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

class ThreadDemo implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100000; i++) {
            System.out.println(i);
            sum += i;
        }
        return sum;
    }
}

12.装饰模式写出IO流

Java的java.io包中囊括了整个流的家族,输出流和输入流的谱系如下所示:
在这里插入图片描述

在这里插入图片描述

以InputStream为例,它是一个抽象类:

public abstract class InputStream implements Closeable {
    ...
    ...
}

并定义有抽象方法

public abstract int read() throws IOException;

该抽象方法由具体的子类去实现,通过InputStream的族谱图可以看到,直接继承了InputStream,并且提供某一特定功能的子类有:

  • ByteArrayInputStream
  • FileInputStream
  • ObjectInputStream
  • PipedInputStream
  • SequenceInputStream
  • StringBufferInputStream

这些子类都具有特定的功能,比如说,FileInputStream代表一个文件输入流并提供读取文件内容的功能,ObjectInputStream提供了对象反序列化的功能。

InputStream这个抽象类有一个子类与上述其它子类非常不同,这个子类就是FilterInputStream,可参见上图中的InputStream族谱图。

翻开FilterInputStream的代码,我们可以看到,它内部又维护了一个InputStream的成员对象,并且它的所有方法,都是调用这个成员对象的同名方法。
换句话说,FilterInputStream它什么事都不做。就是把调用委托给内部的InputStream成员对象。

FilterInputStream的又有其子类,分别是:

  • BufferedInputStream
  • DataInputStream
  • LineNumberInputStream
  • PushbackInputStream

以BufferedInputStream为例,这个类提供了提前读取数据的功能,也就是缓冲的功能。可以看看它的read方法:

    public synchronized int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return getBufIfOpen()[pos++] & 0xff;
    }

可以看到,当pos>=count时,意即需要提前缓冲一些数据的时候到了,那么就会调用fill()将缓冲区加满,以便后续读取。

BufferedInputStream就是一个装饰者,它能为一个原本没有缓冲功能的InputStream添加上缓冲的功能。

比如我们常用的FileInputStream,它并没有缓冲功能,我们每次调用read,都会向操作系统发起调用索要数据。假如我们通过BufferedInputStream来装饰它,那么每次调用read,会预先向操作系统多拿一些数据,这样就不知不觉中提高了程序的性能。如以下代码所示:

BufferedInputStream bis = new BufferedInputStream(new FileInputStream(new File("/home/user/abc.txt")));

同理,对于其它的FilterInputStream的子类,其作用也是一样的,那就是装饰一个InputStream,为它添加它原本不具有的功能。OutputStream以及家属对于装饰器模式的体现,也以此类推。


13.Layout 界面编程

BorderLayout

BorderLayout(边框布局管理器)是 Window、JFrame 和 JDialog 的默认布局管理器。边框布局管理器将窗口分为 5 个区域:North、South、East、West 和 Center。其中,North 表示北,将占据面板的上方;Soufe 表示南,将占据面板的下方;East表示东,将占据面板的右侧;West 表示西,将占据面板的左侧;中间区域 Center 是在东、南、西、北都填满后剩下的区域

在这里插入图片描述

边框布局管理器并不要求所有区域都必须有组件,如果四周的区域(North、South、East 和 West 区域)没有组件,则由 Center 区域去补充。如果单个区域中添加的不只一个组件,那么后来添加的组件将覆盖原来的组件,所以,区域中只显示最后添加的一个组件。

BorderLayout 布局管理器的构造方法如下所示。

  • BorderLayout():创建一个 Border 布局,组件之间没有间隙。
  • BorderLayout(int hgap,int vgap):创建一个 Border 布局,其中 hgap 表示组件之间的横向间隔;vgap 表示组件之间的纵向间隔,单位是像素。
package Review;

import javax.swing.JButton;
import javax.swing.JFrame;
import java.awt.*;
public class BorderLayoutDemo
{
    public static void main(String[] agrs)
    {
        JFrame frame=new JFrame("Java第三个GUI程序");    //创建Frame窗口
        frame.setSize(400,200);
        frame.setLayout(new BorderLayout());    //为Frame窗口设置布局为BorderLayout
        JButton button1=new JButton ("上");
        JButton button2=new JButton("左");
        JButton button3=new JButton("中");
        JButton button4=new JButton("右");
        JButton button5=new JButton("下");
        frame.add(button1,BorderLayout.NORTH);
        frame.add(button2,BorderLayout.WEST);
        frame.add(button3,BorderLayout.CENTER);
        frame.add(button4,BorderLayout.EAST);
        frame.add(button5,BorderLayout.SOUTH);
        frame.setBounds(300,200,600,300);
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

GridLayout

GridLayout(网格布局管理器)为组件的放置位置提供了更大的灵活性。它将区域分割成行数(rows)和列数(columns)的网格状布局,组件按照由左至右、由上而下的次序排列填充到各个单元格中。

GridLayout 的构造方法如下。

  • GridLayout(int rows,int cols):创建一个指定行(rows)和列(cols)的网格布局。布局中所有组件的大小一样,组件之间没有间隔。
  • GridLayout(int rows,int cols,int hgap,int vgap):创建一个指定行(rows)和列(cols)的网格布局,并且可以指定组件之间横向(hgap)和纵向(vgap)的间隔,单位是像素。

GridLayout 布局管理器总是忽略组件的最佳大小,而是根据提供的行和列进行平分。该布局管理的所有单元格的宽度和高度都是一样的。

package Review;

import javax.swing.*;
import java.awt.*;

public class Main {

    public static void main(String[] args) {
        JFrame jf = new JFrame("测试窗口");
        jf.setSize(200, 250);
        jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        jf.setLocationRelativeTo(null);

        // 创建 3 行 3 列 的网格布局
        GridLayout layout = new GridLayout(3, 3);

        // 设置 水平 和 竖直 间隙
         layout.setHgap(10);
         layout.setVgap(10);

        JPanel panel = new JPanel(layout);

        JButton btn01 = new JButton("按钮01");
        JButton btn02 = new JButton("按钮02");
        JButton btn03 = new JButton("按钮03");
        JButton btn04 = new JButton("按钮04");
        JButton btn05 = new JButton("按钮05");
        JButton btn06 = new JButton("按钮06");
        JButton btn07 = new JButton("按钮07");
        JButton btn08 = new JButton("按钮08");

        panel.add(btn01);
        panel.add(btn02);
        panel.add(btn03);
        panel.add(btn04);
        panel.add(btn05);
        panel.add(btn06);
        panel.add(btn07);
        panel.add(btn08);

        jf.setContentPane(panel);
        jf.setVisible(true);
    }
}

14.泛型

<泛型类型名>

1.泛型类

在类名后加,方法的返回值也为E
创建对象时要制定泛型类型,即与集合的创建方式相同

2.泛型方法

泛型类型名加在方法修饰符与返回类型之间

3.泛型接口

与泛型类相似,接口的实现类要制定泛型类型(比如implements interface)
如果是泛型类实现泛型接口就可以不用指定

4.泛型通配符

? 代表任意数据类型
适用于作为参数传递时,接收一个任意通配泛型形参做迭代,获取的对象只能说Object类型

5.泛型限定

1)泛型上限限定

? extends E
使用的泛型只能是E及其子类
比如E是Number,使用的就可以是Integer

2)泛型下限限定

? super E
使用的泛型只能是E及其父类

package day2;

import java.util.*;

public class day2 {

public void setName(E name)
{
    this.name = name;
}

public E getName()
{
    return this.name;
}

public static <M> void printVar(M m)
{
    System.out.println(m);
}

//泛型通配符
public static void printArray(ArrayList<?> arr)
{
    Iterator<?> it = arr.iterator();
    while(it.hasNext())
    {
        Object o = it.next();
        System.out.println(o);
    }
}

private E name;

public static void main(String args[]) {
    //迭代器
    Collection<String> coll = new ArrayList<>();
    coll.add("AAA");
    coll.add("BBB");
    coll.add("CCC");
    coll.add("XXX");

    Iterator<String> it = coll.iterator();
    while(it.hasNext())
    {
    	System.out.println(it.next());
	}
    for (Iterator<String> it2 = coll.iterator(); it2.hasNext(); ) 
    {
    	System.out.println(it2.next());
    }
    //泛型类
    day2<String> obj1 = new day2<String>();
    obj1.setName("AAA");
    System.out.println(obj1.name);

    day2<Integer> obj2 = new day2<Integer>();
    obj2.setName(111);
    System.out.println(obj2.name);

    day2<Character> obj3 = new day2<Character>();
    obj3.setName('X');
    System.out.println(obj3.name);

    //泛型方法
    day2.printVar("静态泛型方法");

    //泛型接口
    MyInterfaceImpl1 impl1 = new MyInterfaceImpl1();
    impl1.mth("泛型类实现泛型接口");

    new MyInterface<String>() {
        @Override
        public void mth(String s) {
            System.out.println(s);
        }
    }.mth("匿名对象实现泛型接口");
    System.out.println();

    //泛型通配符方法
    day2.printArray((ArrayList)coll);
}
}
package day2;
//泛型接口
public interface MyInterface<E> {
    public abstract void mth(E e);
}
package day2;
//泛型接口实现类
public class MyInterfaceImpl1<E> implements MyInterface<E>{
    @Override
    public void mth(E e)
    {
        System.out.println(e);
    }
}

15.多态

1、向上转型实现多态

public class TestMainClass {
	public static void main(String[] args) {
		A[] oArray = new B[6];
		int sum1=0, sum2=0;
		for (int i=0; i<oArray.length; i++) {
			oArray[i] = new B(i);
			sum1 += oArray[i]._value;
			sum2 += ((B)oArray[i])._value;
		}
		System.out.println("Sum1 = " + sum1);
		System.out.println("Sum2 = " + sum2);
	}
}
class A {
	public int _value = 1;
	public A() {
		_value = 2;
	}
}
class B extends A {
	public int _value = 3;
	public B(int value) {
		_value = value;
	}
}

Sum1 = 15 Sum1 = 12

Sum2 = 18 Sum2 = 15

易错点

对于new B(0);这个对象的value是通过B的有参构造器赋值,故value为0

但当将其存入A类引用的数组时,由于父类引用指向子类对象只能访问父类数据成员、子类重写方法与父类其它方法(下面会解释为什么),所以oArray[i]访问的永远都是2 (因为B类构造方法调用前会默认调用父类缺省构造方法)。或许可以理解为oArray[]中放的都是A类引用指向B类对象(getClass获得的一定是B),因此oArray[i] = new B(i);这句话干的事情是:第i个A类引用现在指向这个新的B,那么显然当我们试图使用oArray[i]去访问这个对象时只能访问A的数据成员和B中的重写方法及A的其它方法,而刚才的赋值是隐式调用父类构造方法给value赋了2,放到子类对象的super空间的value域(见下图);子类构造方法赋了B类对象的value属性,放在子类内容的value域。

在这里插入图片描述

而对于sum2 += ((B)oArray[i])._value;亲测这个强转并不会调用构造方法,那么它干的事情就是把oArray[i]从一个装A类引用的数组转成一个装B类引用的数组,指向的还是B类对象,那么此时对象的数据成员就可以访问了(值在第一步已经赋进去了,只是A类引用访问不到而已),所以是0加到5。

关于为什么父类引用访问不到子类数据成员,我的理解是多态中数据成员和方法的绑定方式是不同的,对于方法而言

在这里插入图片描述

如果创建的是一个B类对象,那么里面就会有父类的同名方法,通过虚拟方法表在被访问时映射过来。但是对于数据成员而言,其实使用的是静态绑定 例如Father father = new Son(); 在编译期间 father就是个Father对象,系统不知道实际上它是一个 Son对象,这得在运行期间由JVM判断(父类的静态方法和数据成员遵循相同的规则),因此自然是编译期它是什么类型就访问什么类型的数据成员。


2、接口回调实现多态

接口回调是指:可以把使用某一接口的类创建的对象的引用赋给该接口声明的接口变量,那么该接口变量就可以调用被类实现的接口的方法。实际上,当接口变量调用被类实现的接口中的方法时,就是通知相应的对象调用接口的方法,这一过程称为对象功能的接口回调。

interface People{
   void peopleList();
}

class Student implements People{
   public void peopleList(){
    System.out.println("I’m a student.")}
}

class Teacher implements People{
  public void peopleList(){
    System.out.println("I’m a teacher.");
}
}

public class Example{
  public static void main(String args[]){
    People a;            
	a=new Student();     
	a.peopleList();
	a=new Teacher();     
	a.peopleList();   
}
}

接口回调父类方法更加的简洁,是抽象方法不需要有方法体;

都是编译时显示父类方法行为特征,运行时显示子类行为特征。

(Thinking in Java)

使用接口的核心原因:为了能够向上转型为多个基类型。即利用接口的多实现,可向上转型为多个接口基类型。

从实现了某接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的。

这两个概念是从两个方面来解释一个行为。接口回调的概念,强调使用接口来实现回调对象方法使用权的功能。而向上转型则牵涉到多态和运行期绑定的范畴。


16.接口和抽象类

  1. 接口不能用于实例化对象。

    接口中所有的方法必须是抽象方法。

    接口不能包含成员变量,除了 static 和 final 变量。

    接口方法默认为public abstract

  2. 抽象类和接口的区别
    抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
    抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
    接口中不能含有静态代码块以及静态方法,而抽象类是可以有静态代码块和静态方法。
    一个类只能继承一个抽象类,而一个类却可以实现多个接口。

  3. 成员变量:
    抽象类可有成员变量,也可以有常量
    接口只能有常量(因为他无法给成员赋值,只能一开始就初始化)
    成员方法:
    抽象类可有抽象方法,也可有非抽象方法
    接口只能有抽象方法,而且方法有默认修饰符public abstract


17.重载的顺序

public class Test{
    
  public static void say(Object arg){
     System.out.println("object");
  }

  public static void say(int arg){
     System.out.println("int");
  }

  public static void say(char arg){
     System.out.println("char");
  }

  public static void say(long arg){
     System.out.println("long");
  }

  public static void say(char... arg){
     System.out.println("char...");
  }

  public static void say(Character arg){
     System.out.println("Character");
  }
    
  public static void say(Serializable arg){
     System.out.println("Serializable");
  }

  public static void main(String[] args){
   say('a');
  }
}

输出结果为char

这也很好解释,优先肯定匹配类型相同的

如果我们注释掉say(char arg)

输出结果为int

这是因为此时发生了一次类型转换,'a’除了可以代表一个字符串,还可以代表数字97,因此参数类型为int的重载也是合适的,

继续注释say(int arg)

输出结果为long

这个时候发生了两次自动类型转换,'a’转型为整数97之后进一步转型为97L,匹配的参数类型为long的重载,

代码中没有写float,double的重载,实际上自动类型转换还能继续发生多次,按照char->int->long->float->double的顺序进行匹配,但不会匹配到byte和short类型的重载,因为char转byte和short的转型是不安全的。

我们继续注释say(long arg)

输出结果为Character

这次发生了一次自动装箱

继续注释掉say(Character arg)

输出结果为Seriablizable

这是因为java.long.Seriablizable是java.long.Character类实现的接口,当自动装箱之后发现找不到装箱类,但是找到装箱类实现的接口类型,所以紧接着又发生了一次自动转型。

继续注释say(Seriablizable arg)

结果就变成了Object

这是因为char装箱成Character之后转型父型了,因为Object是所有类的父类,

继续注释say(Object arg)

结果为Char...

可见数组参数重载的优先级最低。


18.网络编程

服务端实现:

public class ServerTCP {
    public static void main(String[] args) throws IOException {
        System.out.println("服务端启动 , 等待连接 .... ");
        ServerSocket ss = new ServerSocket(6666);
        Socket server = ss.accept();
        InputStream is = server.getInputStream();
        byte[] b = new byte[1024];
        int len = is.read(b);
        String msg = new String(b, 0, len);
        System.out.println(msg);
        
      	OutputStream out = server.getOutputStream();
      	out.write("我很好,谢谢你".getBytes());
      	out.close();
        is.close();
        server.close();
    }
}

客户端实现:

public class ClientTCP {
	public static void main(String[] args) throws Exception {
		System.out.println("客户端 发送数据");
		Socket client = new Socket("localhost", 6666);
		OutputStream os = client.getOutputStream();
		os.write("你好么? tcp ,我来了".getBytes());
        
      	InputStream in = client.getInputStream();
      	byte[] b = new byte[100];
      	int len = in.read(b);
      	System.out.println(new String(b, 0, len));
      	in.close();
		os.close();
		client.close();
	}
}

19.IO流输入输出抽象类 文件输入输出

一、字节流

输入流是将资源数据读入到缓冲Buffer中,输出流是将缓冲Buffer中的数据按照指定格式写出到一个指定的位置(从内存写到硬盘)

java程序–>JVM–>OS–>OS的写数据方法–>写入

输入流输出流
字节流InputStreamOutputStream
字符流ReaderWriter
1.OutputStream
  • public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
  • public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
  • public void write(byte[] b):将 b.length字节从指定的字节数组写入此输出流。
  • public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
  • public abstract void write(int b) :将指定的字节输出流。
FileOutputStream
FileOutputStream(String name);
FileOutputStream(File file);
构造方法作用:
  1. 创建一个FileOutputStream对象

  2. 根据参数(文件/文件路径)创建一个空文件

  3. 让FileOutputStream对象指向该文件

使用:
  1. 创建一个FileOutputStream对象,指定目的地

  2. 调用write,将数据写入文件

//将指定的字节输出流
public abstract void write(int b); 
  1. 释放资源

例:write(97);首先会转为二进制1100001,存到硬盘中

任何文本编辑器(如记事本)打开文件时都会查询编码表,将字节转换为字符,即参数写入97会得到a而非97

如果使用其它两个write一次性写入多个字节,那么:

  1. 如果第一个字节是正的0-127,会查询ASCII码表
  2. 如果第一个字节是负数,会将一二两个字节合并去查GBK(系统默认码表)
数据追加续写
  • public FileOutputStream(File file, boolean append): 创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name, boolean append): 创建文件输出流以指定的名称写入文件。

这两个构造方法,参数中都需要传入一个boolean类型的值,true 表示追加数据,false 表示清空原有数据。这样创建的输出流对象,就可以指定是否追加续写

	public static void main(String[] args) throws IOException 
    {
        FileOutputStream fos = new FileOutputStream("D:/test/fos.txt",true);
        byte[] b = "abcde".getBytes();
        fos.write(b,2,2);
        fos.write(b);
        fos.close();
	}

2.InputStream

当完成流的操作时,必须调用close方法释放系统资源

  • public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
  • public abstract int read(): 从输入流读取数据的下一个字节。
  • public int read(byte[] b): 从输入流中读取一些字节数,并将它们存储到字节数组b中 。
FileInputStream
  • FileInputStream(File file): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
  • FileInputStream(String name): 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
  • read(),每次可以读取一个字节的数据,提升为int类型,读取到文件末尾,返回-1
		FileInputStream fis = new FileInputStream("read.txt");
        int b ;
        while ((b = fis.read())!=-1) {
            System.out.println((char)b);
        }
        fis.close();
  • read(byte[] b),每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回-1
public class FISRead {
    public static void main(String[] args) throws IOException{
       	FileInputStream fis = new FileInputStream("read.txt"); //文件中为abcde
        int len ;
        byte[] b = new byte[2];
        while (( len= fis.read(b))!=-1) {
            //len 每次读取的有效字节个数
            System.out.println(new String(b,0,len));
        }
        fis.close();
    }
}

注意一定要用System.out.println(new String(b,0,len));因为每次不一定都能读取到两个字节,如果只读了一个那就会有一个没有刷新,用len获取有效字节数

3.输入输出搭配
public class Copy {
public static void main(String[] args) throws IOException {
    FileInputStream fis = new FileInputStream("D:\\test.jpg");
    FileOutputStream fos = new FileOutputStream("test_copy.jpg");

    byte[] b = new byte[1024];
    int len;

    while ((len = fis.read(b))!=-1) 
    {
        fos.write(b, 0 , len);
    }

    fos.close();
    fis.close();
}
}

二、字符流

1.Reader
  • public void close() :关闭此流并释放与此流相关联的任何系统资源。
  • public int read(): 从输入流读取一个字符。
  • public int read(char[] cbuf): 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
FileReader
  • FileReader(File file): 创建一个新的 FileReader ,给定要读取的File对象。
  • FileReader(String fileName): 创建一个新的 FileReader ,给定要读取的文件的名称。

与字节流基本相同,类比即可

2.Writer
  • void write(int c) 写入单个字符。
  • void write(char[] cbuf)写入字符数组。
  • abstract void write(char[] cbuf, int off, int len)写入字符数组的某一部分,off数组的开始索引,len写的字符个数。
  • void write(String str)写入字符串。
  • void write(String str, int off, int len) 写入字符串的某一部分,off字符串的开始索引,len写的字符个数。
  • void flush()刷新该流的缓冲。
  • void close() 关闭此流,但要先刷新它。
FileWriter
  • FileWriter(File file): 创建一个新的 FileWriter,给定要读取的File对象。
  • FileWriter(String fileName): 创建一个新的 FileWriter,给定要读取的文件的名称。

关闭资源时,与FileOutputStream不同,如果不关闭,数据只是保存到缓冲区,并未保存到文件。

因为内置缓冲区的原因,如果不关闭输出流,无法写出字符到文件中。但是关闭的流对象,是无法继续写出数据的。如果我们既想写出数据,又想继续使用流,就需要flush 方法了。

  • flush :刷新缓冲区,流对象可以继续使用。
  • close:先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。
public class FWWrite {
    public static void main(String[] args) throws IOException {
        // 使用文件名称创建流对象
        FileWriter fw = new FileWriter("fw.txt");
        // 写出数据,通过flush
        fw.write('刷'); // 写出第1个字符
        fw.flush();
        fw.write('新'); // 继续写出第2个字符,写出成功
        fw.flush();

        fw.write('关'); // 写出第1个字符
        fw.close();
        fw.write('闭'); // 继续写出第2个字符,【报错】java.io.IOException: Stream closed
        fw.close();
    }
}

三、字节流和字符流的区别

  • 字节流操作的基本单元为字节;字符流操作的基本单元为Unicode码元。
  • 字节流默认不使用缓冲区;字符流使用缓冲区。
  • 字节流通常用于处理二进制数据,实际上它可以处理任意类型的数据,但它不支持直接写入或读取Unicode码元;字符流通常处理文本数据,它支持写入及读取Unicode码元。

字节流直接操作文件本身,不关也会写进去

字符流操作时使用内存了缓冲区,在close()或flush()时会将缓冲区中内容输出到文件


20.多线程&等待唤醒机制

在这里插入图片描述

1.多线程

yield( ) :线程让步,当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行。在大多数情况下,yield()将导致线程从运行状态转到可运行状态。

sleep() 是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态)。

wait() 是Object类的方法,调用对象的wait()方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的notify()方法(或notifyAll()方法)时才能唤醒等待池中的线程进入等待锁定池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态

plus:wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

suspend() 方法的作用是将一个线程挂起(暂停),不会释放锁。

resume() 方法的作用则是将一个挂起的线程重新开始并继续向下运行。

使用 suspend 和 resume 方法容易出现因为线程的暂停而导致数据不同步的问题,现已被弃用。

Thread.join()方法用于把指定的线程加入到当前线程中,把当前线程的CPU执行时间让给另一个线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。


2.等待唤醒机制

wait:线程不再活动,不再参与调度,进入 wait pool中,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。等着别的线程通知(notify) 在这个对象上等待的线程从wait set 中释放出来,重新进入到调度队列(ready queue)中

notify:则选取所通知对象的 wait set 中的一个线程释放。

notifyAll:则释放所通知对象的 wait set 上的全部线程。

哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以它需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。

如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态。否则,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态


1.wait方法与notify方法必须要由同一个锁对象调用。因为对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。
2.wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
3.wait方法与notify方法必须要在同步代码块或者是同步函数中使用。必须要通过锁对象调用这2个方法。

设计思路上就是创建一个类,该类对象为锁对象,故这个类应该要有某个标志状态的属性。两个(或多个)生产者、消费者类均使用该类对象作为锁,在状态为某一特值时等待,在被唤醒后(就是wait后面)写将要执行的语句,执行完后notify让另一个线程进行类似流程操作即可。


21.数据共享机制

多线程共享数据

1.如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,卖票系统就可以这么做。

2.如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,例如银行存取款


有三种方法来解决此类问题:

1.将共享数据封装成另外一个对象,然后将这个对象逐一传递给各个Runnable对象,每个线程对共享数据的操作方法也分配到那个对象身上完成,这样容易实现针对数据进行各个操作的互斥和通信

public class doThreadShareData {
    //java.util.concurrent
    public static void main(String[] args) {
        ShareData shareData =new ShareData();
        new Thread(new Runnable() {
            @Override
            public void run() {
                shareData.deccreament();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                shareData.increment();
            }
        }).start();
    }
 
}
class ShareData {
    private int j = 0;
    public synchronized void increment(){
        j++;
    }
    public synchronized void deccreament(){
        j--;
    }
}

2.将Runnable对象作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个Runnable对象调用外部类的这些方法。


3.使用Map

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class ThreadScopeShareData {
public static Map<Object, Integer> map = new HashMap<Object, Integer>();
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName() + " set data:" + data);
                    map.put(Thread.currentThread(), data);
                    //将值按照Thread去设值,取的时候也按Thread去取,以保证数据的共享,但又保证了对象的独立.
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }

    static class A {
        public int get() {
            data = map.get(Thread.currentThread());
            System.out.println("a from thread:" + Thread.currentThread().getName() + " is " + data);
            return data;
        }
    }

    static class B {
        public int get() {
            int data = map.get(Thread.currentThread());
            System.out.println("b from thread:" + Thread.currentThread().getName() + " is " + data);
            return data;
        }
    }
}

22.Collection接口各种实现类的常用方法 Collections工具类

Collection

Collection<String> coll = new ArrayList<>();
//多态性
一、List(有序;允许重复;有索引值-可用for遍历)
  1. Vector集合
  2. ArrayList集合(底层为数组,查询快,增删慢)
  3. LinkedList集合(底层为链表,查询慢,增删快)
二、Set(无序;不允许重复;无索引值)
  1. TreeSet集合(底层为二叉树,用于排序)
  2. HashSet集合(底层为哈希表+红黑树,存取无序)
    1)LinkedHashSet集合(底层为哈希表+链表,存取有序)
三、所有单列集合有共性的方法
  1. add,remove,clear,isEmpty
  2. contains判断集合中是否包含某个元素,返回布尔值
  3. toArray集合转数组

List

  1. 有序,存取顺序一致
  2. 有索引值
  3. 允许元素重复

含索引值的方法

  1. add(int index, E element) 将元素添加至索引值对应位置,后面的元素后移
  2. get(int index) 返回相应位置元素
  3. remove(int index) 移除指定位置元素并返回该元素
  4. set(int index, E element) 用元素替换相应位置元素,返回被替换的元素
一、ArrayList

底层为数组,查询快,增删慢

1.长度可变化
2.泛型,集合中全部元素为同一类型,泛型只能是引用类型(例如类的对象),不能是基本类型(可以是String,不能说int这些,因为集合中存储的是地址值,而基本数据类型没有地址值)。要装基本类型就需要使用对应的包装类,除了int对应Integer,char对应Character,其它都是基本类型的首字母大写即可

ArrayList<String> list = new ArrayList<>();

3.直接打印得到的是内容(无内容打印中括号)而不是地址值(其它非基本类型直接打印一般为地址值,ArrayList重写了toString方法)
4.添加内容使用add,注意添加内容类型必须与声明的泛型相同,返回值为添加成功与否的布尔值
5.读取/删除使用get和remove方法,参数为索引值
6.size方法获取集合长度,为包含的元素个数
7.集合作为方法的参数或返回值都是以地址值传送

二、LinkedList

底层为双向链表,增删快,查找慢
有多个对首尾元素进行操作的方法

1.addFirst,addLast方法将元素插入首/尾
2.push方法将元素推入堆栈(等效于addFirst)
3.removeFirst,removeLast方法将首/尾元素移除并返回
4.pop方法从堆栈中弹出元素(等效于removeFirst)
5.getFirst/getLast获取首尾元素

三、Vector

与ArrayList类似,较少使用


Set

  1. 不允许重复
  2. 无索引值,不能使用for循环遍历
一、HashSet

哈希值为十进制逻辑地址(==比较的是物理地址),通过hashCode方法可获得,String类重写了hashCode方法

add方法会调用hashCode方法,如果出现相同哈希值会调用equals方法,相同则不再存储
底层为哈希表(因此存储的元素必须重写hashCode和equals方法)

哈希表=数组+链表(同组元素过多时转为红黑树),数组存储各元素哈希值,哈希值相同的元素在一个分组下以链表形式存储,多于8个时转为红黑树存储,查询速度快

存取顺序不一定一致
遍历可以使用迭代器或for each循环


二、LinkedHashSet

将HashSet的链表改为双向链表,是有序的HashSet,仍然不允许重复


Collections集合方法

均为静态方法,直接用Collections类名调用

一、添加多个元素到指定集合中
Collections.addAll(集合名,元素1、元素2...);
二、打乱集合
Collections.shuffle(集合名);
三、按默认规则排序(升序/自然顺序)
Collections.sort(集合名);

被排序的集合元素类型必须重写了Comparable接口下的compareTo方法

方法返回当前数据成员-参数对应数据成员即为升序排序

package day3;

public class Person implements Comparable<Person> {
    private String name;
    private int age;

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

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Person p) {
        return this.getAge() - p.getAge();
    }
}
四、按传入的比较器排序

在主类中重写Comparator(在util包中)下的compare方法(对两个参数对象进行比较,不涉及当前对象)

		Collections.sort(list3, new Comparator<Person>() 
		{
            @Override
            public int compare(Person p1, Person p2) 
            {
                return p2.getAge() - p1.getAge();
            }
    	});

23.Map

在这里插入图片描述

1.双列集合,为键值对形式,类似于python的字典
2.key不允许重复,value可重复

一、HashMap

1.实现了Map接口
2.底层为哈希表,查询速度快
3.存取无序

1)LinkedHashMap

1.继承了HashMap
2.底层为哈希表+链表
3.存取有序

2)存储自定义类型元素

由于key不重复,故自定义类必须重写hashCode和equals方法


二、HashTable

1.底层为哈希表,单线程(HashMap为多线程),速度慢
2.key和value均不能为null

三、常用方法

1.put(key,value) 将键值对插入集合,若key重复则返回被替代的value,否则返回null
2.remove(key) 删除指定键值对,若key存在返回对应的value,不存在则返回null
3.get(key) 获得键对应的值,key不存在返回null
4.containsKey(key) 判断是否包含指定键,返回布尔值

四、遍历Map

1.keySet() 将集合中全部键取出存储到一个Set集合中,再遍历Set集合通过get(key)方法遍历value

2.Entry接口
在Map创建时会自动创建Entry对象记录键和值,一个键值对对应一个Entry对象
(1) entrySet() 将Map内全部Entry对象取出放入Set
(2) getKey() 获取key
(3) getValue() 获取value


24.线程安全

1.线程安全问题产生情况

若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

	private int ticket = 100;

    @Override
    public void run() {

        while (true)
        {
            if(ticket > 0)
            {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "张票");
                ticket--;
            }
        }

    }
		Runnable run = new RunnableImpl();

        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        Thread thread3 = new Thread(run);
        thread1.start();
        thread2.start();
        thread3.start();

三个线程抢夺CPU执行权,在sleep处失去执行权,这时判断条件(ticket数量)会因为其它线程执行而改变,判断条件在不一定成立的情况下向后执行,出现线程安全问题

解决思路:在一个线程涉及到访问共享数据时,不管是否失去执行权其它线程必须等待


2.线程同步

解决访问共享资源时产生的线程安全问题

(1)同步代码块

synchronized同步使相应区块资源实行互斥访问

synchronized(锁对象)
{
	//需要同步操作的代码
}

锁对象可以是任意对象,只需保证每个线程均使用同一对象即可(也就是放在方法外面声明)

锁对象只允许一个线程在同步代码块中执行

		while (true)
        {
            synchronized (obj)
            {
                if(ticket > 0)
                {
                    try {
                        Thread.sleep(10);
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "张票");
                    ticket--;
                }
            }
        }

原理:执行到synchronized代码块时会检查是否有锁对象,如果有则获取并执行,没有就阻塞,执行完归还(这就是为什么锁对象只能有一个)


(2)同步方法
public synchronized void method(){}

把访问共享数据的代码放进去即可

	@Override
    public void run() {

        while (true)
        {
            payTicket();
        }

    }

    public synchronized void payTicket()
    {
        if(ticket > 0)
        {
            try {
                Thread.sleep(10);
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "张票");
            ticket--;
        }
    }

原理是一样的,只不过锁对象换成了this(线程实现类对象)

注意如果同步方法是静态的,那么使用的锁就必须是所在类的字节码文件对象,也就是 类名.class,解释来说,静态是和类一起加载的,所以在静态加载的时候是不可能有object的对象,所以锁就必须用在静态加在之前的一个对象。


(3)静态同步方法

将方法和相关变量声明为静态也可实现同步,此时锁对象是实现类的class文件对象(如RunnableImpl.class)


(4)Lock锁
//获取锁
void lock();
//释放锁
void unlock();

创建一个ReentrantLock(Lock接口实现类)对象,在可能产生安全问题的代码前后分别调用lock和unlock

	ReentrantLock lock = new ReentrantLock();

	@Override
    public void run() {

        while (true)
        {
            lock.lock();
            if(ticket > 0)
            {
                try {
                    Thread.sleep(10);
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "张票");
                ticket--;
            }
            lock.unlock();
        }
    }

考虑到总是要释放锁的,为了提高效率,更好的写法是:

		while (true)
        {
            lock.lock();
            if(ticket > 0)
            {
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "-->第" + ticket + "张票");
                    ticket--;
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
                finally 
                {
                    lock.unlock();
                }
            }
        }

25.一些小细节

25.1 实现Runnable创建多线程的方式

应该是:

 		Runnable run = new RunnableImpl();
        Thread thread1 = new Thread(run);
        Thread thread2 = new Thread(run);
        thread1.start();
        thread2.start();

而不是:

	Thread thread1 = new Thread(new RunnableImpl());
    Thread thread2 = new Thread(new RunnableImpl());
    thread1.start();
    thread2.start();

25.2 静态方法访问的变量也必须是静态的

25.3 UTF-8中一个中文是三字节,GBK中一个中文是两字节

25.4 (输出流)Windows系统里,每行结尾是 回车+换行 ,即\r\n

25.5 流操作完毕后,必须释放系统资源,调用close方法

25.6 流的关闭原则:先开后关,后开先关

25.7 write(97)写的是a(ASCII),想写a要用write('a')

25.8 测试类一定不要忘了main函数

25.9 如果==作用于基本数据类型的变量则直接比较其存储的"值"是否相等;如果作用于引用类型的变量(String),则比较的是所指向的对象的地址(即是否指向同一个对象)。如果没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址

25.10 父类的构造方法是不会被子类继承的,但是子类的构造方法中会有一个隐式的super()来调用父类中的无参数构造方法,显式调用时同理

25.11 continue:不再执行循环体中continue语句之后的代码,直接进行下一次循环

25.12 catch (Exception e)能抓到所有异常,后面的catch不会再执行,事实上后面再抓子类异常是错误,IDEA根本就不通过(Error:(23, 9) java: 已捕获到异常错误java.lang.ArithmeticException),但考试时或许可以忽略这一点

25.13 注意分辨println和print

25.14 追加写入通过构造器第二个参数实现

OutputStream output = new FileOutputStream(orgFile,true);

25.15 线程锁要声明为静态,一个对象一个锁和没有是一样的,当然也可以直接把空字符串作为对象锁

25.16 文件操作基本上所有代码都是写在main方法里的,不要忘了写,更不要忘了抛异常

25.17 序列化应用于IO流读取对象时,只需要对象类实现标记接口Serializable即可使用序列化输入输出流存取(输出对应序列化,输入对应反序列化)。静态优先于非静态加载到内存,因此static修饰的成员不能被序列化(序列化的都是对象),被transient(瞬态关键字)修饰的成员也不能被序列化(作用主要就是为了防止成员被序列化)

25.18 注意,如果实现多个接口中有重名方法且它们仅仅是返回类型不同(其他情况下这两个方法被识别为不同,可以分别重写),那么就不能同时实现两个接口方法(返回类型相同显然实现前面那个就行,类只需要一个这样的方法),可以通过两个内部类分别实现来解决。继承也是同理,子类如果有一个和父类只有返回类型不同的方法就会报错

25.19 父类引用指向子类对象,对于方法重写,编译看父类,运行看子类

25.20 has a是关联关系,关联分双向关联和单向关联,双向关联是A,B类分别持有对方的引用(或是对方的属性),单向关联是一方持另一方的引用

25.21 构造方法中只能使用this(参数列表)调用构造方法,不能直接使用类名调用其他构造。这是因为构造方法非静态,必须要用对象去调用(this就是当前对象)

25.22 float f = 1.1;是错的,应该是float f = 1.1f;同样int i = 4L也是错的,long不能隐式转换成int

25.23 注意i–和--i的区别,下面循环体的语句会执行一次,–i则不会执行

		int i = 5;
        while (i-- > 4)
        {
            System.out.println("aaa");
        }

同理return a++会返回a之后再把a自增

25.24 final修饰类对象参数其属性可修改,只是限制类的派生而已,但如果是基本数据类型则不可修改

void add(final A a)
{
    a._value++;
}

25.25 静态方法不能用this调用,直接方法名即可调用

25.26 子类重写父类方法,访问权限不能降低(protected可以重写为public)

25.27 静态代码块(或自由代码块)可以有多个,相互之间是平级的,按照顺序执行

25.28 >>为符号扩展,>>>则是将高位补0,<<同理且不存在<<<

25.29 基础数据类型的强制转换遵循小到大为隐式转换,大到小需要显式指定

在这里插入图片描述

25.30 在一个类里面有main函数,那么main函数可以直接访问这个类的private成员。因为main函数也是类函数(只是他是静态的),可以访问私有成员

25.31 Java与C++不同,不支持方法中的参数带默认值,但是可以使用函数重载来实现该功能


  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值