Java|Java SE

环境

  1. 安装JDK(Java Development Kit),Java 语言的软件开发工具包
  2. 配置电脑的PATH环境变量
  3. 安装IDEA
基础语法
  1. 注释:单行使用//单行注释,多行使用/*多行注释*/
  2. 关键字:全部小写,代码编辑器有颜色标记
  3. 常量
  • 字符串常量 "Hello World"
  • 整数常量 12
  • 小数常量 12.23
  • 字符常量 a
  • 布尔常量 true false
  • 空常量 null
  1. 变量:内存中的一小块区域,在执行过程中,其值在一定的范围内改变
  • 变量要限定数据类型
  • 需要一个变量名
  • 初始化值,未赋予初始值的变量不能直接使用
  • 变量只在它的作用域内有效(块级作用域)
  1. 数据类型:Java是强类型语言,针对每一种数据都有明确的数据类型
  • 基本数据类型
    a. 整数:byte(1字节),short(2字节),int(4字节),long(8字节)
    b. 浮点数(float(4字节),double(8字节))
    c. 字符(char(2字节)
    d. 布尔(boolean(1字节)
  • 整数默认是int型,浮点数默认是double类型
  • 定义浮点类型变量时,在变量后面加上类型:12.31F 10000000000L
  1. 类型转换
  • 隐式转换:参与运算时转为存储空间更大的类型,防止损失精度
  • 强制类型转换:目标类型 变量名 = (目标类型)(数据)
  1. 标识符:给变量、包、类、方法取的名称
  • 组成:数字、大小写字母、下划线、$
  • 注意:不能以数字开头、不能是关键字
  • 命名规则:见名知意
    a. 包(文件夹,对类进行管理):全部小写,多级包用.隔开
    b. 类:大驼峰命名(HelloWorld)
    c. 方法和变量:小驼峰(helloWorld)
  1. 运算符
  • 算数运算:+, -, *, /, %, ++, --
    a. 整数相除得到整数,要想得小数,必须有浮点数参与运算
    b. 字符参与加法运算,使用的是字符的ASCII
    c. 字符串做加法运算,其实是在做字符串拼接
  • 赋值运算符:=, +=, -=, %=
    a. 扩展的赋值运算符隐含了强制类型转换
  • 关系运算符:==, !=, >, >=, <, <=
  • 逻辑运算符:&(与),|(或),!(非),^(异或),&&(双与),||(双或)
    a. &&在左边表达式为false时,右边不执行,而&则会去执行右边,|||也是同样的区别
  • 三元运算符:关系表达式?表达式1:表达式2
  1. 获取键盘输入
  • 使用JDK 的Scanner
package com.syntax; // 导包 import java.util.Scanner; public class ScannerDemo { public static void main(String[] args) { // 创建键盘输入对象 Scanner sc = new Scanner(System.in); // 接收数据 System.out.println("请输入一个数据:"); int i = sc.nextInt(); System.out.println("你输入了:" + i); } }

  1. 流程控制
  • 顺序结构:从上往下执行
  • 选择结构(if语句,switch语句)
  • 循环结构语句(for语句,while语句,do...while语句)
    a.for语句中初始化值只在循环体里面有效,而while语句的初始化值在循环体外,所以在外面也可以访问初始化值
    b. do...while会先执行一次循环体,再做条件判断
    c. break,结束整个循环
    d. continue,结束本次循环,继续下一次循环
  1. Random,用于产生随机数
package com.syntax; import java.util.Random; public class RandomDemo { public static void main(String[] args) { Random random = new Random(); int number = random.nextInt(10); // [0,10) System.out.print(number); } }

  1. 数组:存储多个同一数据类型元素的容器,可以存储基本数据类型或者引用数据类型,长度定义好了则无法改变。
  • 声明方式
    a. int[] arr; :定义一个数组,名为arr(推荐使用)
    b. int arr[]; :定义arr变量,是一个数组
  • 数组初始化
    a. 动态初始化,只给出长度,系统决定值 int[] arr = new int[10];
    b. 静态初始化,只给出值,系统决定长度 int[] arr = new int[]{1,2,3}int[] arr = {1,2,3}
  • 二维数组 :元素为一维数组的数组
    a. 声明:int[][] arr; int arr[][]; int[] arr[]; ,推荐使用第一种
    b. 初始化:动态:int[][] arr = new int[2][3],静态:int[][] arr = {{1,2},{3,4}}
    c. 遍历二维数组
package com.syntax; public class ArrayDemo { public static void main(String[] args) { int[][] arr = {{1,2,3},{4,5,6},{7,8,9}}; for (int x = 0; x < arr.length; x++) { for (int y = 0; y < arr[x].length; y++) { System.out.println(arr[x][y]); } } } }

  • 对象数组,用来存储对象的数组,长度固定
    Student[] stus = new Student[3];
  1. 方法:完成特定功能的代码块
  • 格式
权限修饰符 返回值类型 方法名 (参数类型 参数名1,参数类型 参数名2){ 方法体 return 返回值 } 权限修饰符:public default(不加修饰符,当前包下可用) private(只能在当前类访问) protected(子类对象) 返回值类型: 限定返回值的类型,如果没有返回值为void 参数类型:限定实参的数据类型 参数名:形参名称

  • 写方法的方式
    a. 明确返回值类型
    b. 参数列表
// 定义一个求和函数 package com.syntax; public class MethodDemo { public static void main(String[] args) { System.out.println(sum(1,2)); } public static int sum(int a, int b) { int c = a + b; return c; } }

  • 方法的重载
    a. 在同一个类中,方法名相同
    b. 参数不同,可以是参数个数不同,或者是参数对应的数据类型不同
    c. 和返回值无关
  • 方法的实参和形参
    a. 形参是定义函数时给的参数名称,实参是函数调用时传入的参数值
    b. 对于基本数据类型,形参改变并不会改变实参(函数在调用时,实参会赋值给形参),而引用数据类型则会使得它们都发生改变,因为它们是指向同一块内存区域(堆)
  1. 代码块 用 {}括起来的代码
  • 局部代码块:写在方法中,用来控制变量的作用域
  • 构造代码块:直接写在类中,用来提取无参构造函数和有参构造函数共有的代码块,会在对象实例化时调用,相当于写在了构造函数中(执行会先于构造函数代码)
  • 静态代码块,也是直接写在类中,随着类的加载而加载,只执行一次,用来做类的一些初始化工作
  1. 修饰符
  • public(类,成员变量,成员方法,构造方法):公用
  • default(类,成员变量,成员方法,构造方法):当前包下可用
  • protected(成员变量,成员方法,构造方法):子类可用
  • private(成员变量,成员方法,构造方法):当前类可用(用来修饰构造方法表示该类不能被实例化)
  • abstract(类,成员方法):定义抽象类和抽象方法
  • static(成员变量,成员方法):可通过类名调用
  • final (类,成员变量,成员方法):不可继承,不可改变(重写)
  • 常见规则
    a. 类:开发时一个java文件中一般只写一个类。如果要写多个类,类名与文件名相同时必须用public修饰,其他类不能用 public修饰
    b. 成员变量:开发时都使用private修饰,然后加上对应的get set方法
    c. 成员方法:用public修饰
    d. 构造方法:用public修饰,如果不想让它创建对象,就用private
面向对象
  1. 类与对象
  • 类:成员变量,成员方法
    a. 成员变量:写在类里面,不需要给定初始值
    b. 成员方法:无 static 关键字,通过对象调用
    c. 静态方法,有static关键字,可以使用类名直接调用,例如Math
    d. private关键字,修饰符,用来修饰成员变量或者成员方法,加该关键字表示只能在本类中访问,不能通过实例对象访问。加该关键字的成员变量可以为它添加对应的set、get方法来赋值,而不是直接访问赋值
    e. static关键字,用于修饰静态成员变量和静态成员方法,使之被所有对象共享。可以使用类名直接调用,在内存中和类一起加载在方法区中,(先于对象),所以静态方法只能调用静态方法和静态成员变量;非静态方法中可以调用非静态、静态成员变量和方法
    f. final关键字,可以修饰 类(不能被继承)、成员方法(不能被子类重写)、成员变量(必须初始化,可以显式初始化或者构造初始化,不可以修改,也就是常量)
    g. toString方法,存在于Object中,返回getClass().getName() + '@' + Integer.toHexString(hashCode())即类名加地址的十六进制值。当我们试图去打印一个对象时,会默认调用这个方法。所以我们一般会去重写这个方法用来测试。
  • 对象:属性,行为
    a.对象中的属性必须对应类中的成员变量,没有再类中定义的属性就无法使用
  • 构造方法:给对象数据进行初始化
    a. 方法名和类名相同
    b. 没有返回值和返回值类型,连 void都不能写 如
    c. 通过new关键字调用,创建对象时调用,如果我们没有编写构造方法,系统则会自动提供一个无参构造方法
    d. 构造方法也可以重载
public Student(name,age) { // 构造方法 this.name = name; this.age = age; }

  1. 封装
  • 概述:把成员变量隐藏在对象内部,外界无法操作和修改
  • 原则:把不需要对外提供的内容隐藏起来,提供公共方法来对其访问
  1. 继承 extends
  • 多个类有共有的成员变量和成员方法,我们将它抽取到同一个父类类中,然后其他类去继承这个父类,达到复用的效果
  • Java中只支持单一继承,但支持多层继承
  • 子类只能继承父类的非私有成员
  • super表示父类的引用
  • 方法的重写,在继承中,子类的方法和父类完全一样,子类重写了父类的方法,重写后想要调用父类的方法可以使用super关键字去调用
  • 构造方法的执行顺序,在子类初始化实例对象时,会执行子类的构造方法,子类的构造方法如果在第一行没有调用父类的构造方法(super())或自身的其他构造方法(this()),系统会默认调用父类的无参构造方法,所以在子类实例化时一定会调用一次父类的构造方法,其目的是初始化父类的成员变量供子类使用
  • 缺点:增强了类与类之间的耦合性
  1. 抽象 abstract,用于修饰方法和类
  • 抽象类:abstract class ClassName{}
  • 抽象方法:不同类的方法是相似的,但是内容又不是完全一样,所以我们只抽取它的声明,没有具体的方法体,这种方法叫做抽象方法,只能存在于抽象类中public abstract void method();
  • 非抽象子类如果继承的父类是抽象类,一定要重写父类的抽象方法
  • 抽象类不能创建对象,但是有构造方法,用来初始化成员变量
  1. 接口:处理单一继承的局限性
  • 接口中只能写抽象方法,而且也不能实例化 定义接口:interface 接口名{}
  • 接口和类的关系是实现,可以多实现(一个类实现多个接口), class 类名 implement 接口名{},类中必须实现接口的所有抽象方法
  • 接口中的方法默认且只能使用public&abstract修饰符,建议默认写上修饰符
  • 默认使用public static final来修饰成员变量(也就是常量)
  • 接口和接口之间是继承关系,而且是多继承
  • 优点:一对多实现,打破继承的局限性;对外提供规则的方法和变量;降低耦合
  1. 多态:允许将子类类型的指针赋值给父类类型的指针
  • 前提:继承关系,方法重写,父类引用指向子类对象(Father f = new Son();
  • 成员特点
    a. 成员变量:编译时看左边,运行时也是看左边
    b. 成员方法:编译时看左边,运行时看右边(动态绑定,看具体的数据类型)
    c. 静态方法:编译时看左边,运行时看左边。因为使用变量去调用静态方法,相当于用变量的类名去调用。
    d. 缺点:无法直接访问子类特有成员,
    e. 优点:提高可维护性,提高可扩展性
  1. this关键字:代表所在类的对象引用,super:代码父类的引用
    a. 静态方法随着类加载而加载(优先于对象),所以静态方法中没有this
    b. 通过this()调用子类的构造函数,super()调用父类的构造函数
  2. 字符串对象
  • 存储
    a. 字符串对象存储在堆中,字符串内容存储在方法区的常量池中(方便字符串的重复使用)
  • 创建
    a. 直接赋值 String str = "hello",直接指向方法区的常量池中字符串内容
    b. 构造函数 String str = new String("hello") 先在堆中创建一个字符串对象,这个对象存储着方法区的常量池中字符串内容的地址
    c. 传入一个字符数组String str = new String(char[], start, end)从字符数组截取一段包装成字符串对象
    d. StringBuilder 可变的字符串序列,解决字符串拼接的内存浪费问题(String是不可变的,拼接是产生一个新的字符串)
    e. StringBuilder转成String使用toString()StringStringBuilder,使用构造方法 StringBuilder sb = new StringBuilder(str)
  1. 集合类,长度可变
  • 使用
    a. 使用前需要导包
    b. ArrayList array = new ArrayList(); E表示泛型(任意引用数据类型,表示你当前集合要存储的引用数据类型)
  • 操作
    a. 增加:add()
    b. 获取 :get(int Index)
    c. 删除:remove(Object obj) 返回Booleanremove(int Index) 返回obj
    d. 修改:set(int Index, Object obj)返回被修改的内容
    e. 长度:size()
  • package关键字生命,在代码第一行,用来分类管理java文件
  • 有多层结构(一个包下有另一个包),不同包下的文件名可以重复
  • 相同包下的类可以直接访问;不同包下的类访问时要加上包名,或者使用关键字import将类导入
  1. 内部类:嵌套在其他类内部的类
  • 分类:成员内部类(类中),局部内部类(方法中),匿名内部类(实现接口或继承父类并立即创建一个对象,可以用来作为参数传递)
  • 内部类在编译时也会有单独的.class文件,但是前面会冠以外部类的类名和$符号。内部类是外部类的成员,所以内部类可以访问外部类的成员变量
  1. 包装类:封装了基本数据类型的类
  • byte(Byte) short(Short) int(Integer) long(Long) char(Character) float(Float) double(Double) boolean(Boolean)
  • Integer 可以用来转换intString类型的变量
    a. Integer i = new Integer("10"); 传入整数或者字符串,会被转换成整数
    b. String => int 使用 int intValue()static int parseInt(String s)
    c. int => String 使用 String toString() 或 直接加空字符串
    d. 自动装箱、拆箱 Integer i = 10; Integer i2 = i + 1;
IO流
  1. 概述
  • 处理设备之间的数据传输,如在文件中读取数据,或者将数据存储到文件中
  • 可以进行文件复制,文件上传,文件下载
  1. 分类
  • 字符输出流 FileWriter 写数据,字节输出流OutputStream
// 1. 创建输出流对象 FileWriter fw = new FileWriter("filePath", boolean append); // 2. 写入内容 fw.write("hello"); // 3. 刷新文件 fw.flush(); // 4. 释放资源 fw.close(); // 内部实现:创建一个文件,创建输出流对象,将该对象指向该文件

  • 字符输入流 FileReader 读数据,字节输入流InputStream
// 1. 创建输入流对象 FileReader fr = new FileReader("filePath"); // 2. 读数据 read()一次读一个字符,返回该字符的ASCII码 使用循环来读出文件所有内容 int ch; while((ch = fr.read()) != -1) { System.out.print((char)ch); } // 或者用一次读一个字符数组的方式读取, len 表示读出了几个字符(没读到则为-1) char[] chs = new char[1024]; int len; while((len = fr.read(chs)) != -1) { System.out.print(new String(chs, 0, len)); } // 3. 释放资源 fr.close();

  • 字节流和字符流:字节流(一次读、写一个字节,适用于任何文件、比如图片、视频等文件的读写),字符流(一次读、写一个字符,适用于文本文件读写)
  • 对象操作流:使用对象输出流输出对象时,必须用对象输入流读取对象
    a. 对象输出流 ObjectOutputStream
    b. 对象输入流 ObjectInputStream
public class ObjectOutputStreamDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { // 对象输出流对象 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Src")); Student s1 = new Student("zs", 18); Student s2 = new Student("ls", 19); Student s3 = new Student("ww", 20); oos.writeObject(s1); oos.writeObject(s2); oos.writeObject(s3); // 对象输入流 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Src")); // 读取对象try { while (true) { Object obj = ois.readObject(); System.out.println(obj); } }catch(EOFException e) { System.out.println("读完了"); }ois.close(); oos.close(); } }

  1. 复制文件:也就是先读文件,再写文件
  2. 字符缓冲流
  • BufferedReaderBufferedWriter创建对象时传入一个FileReaderFileWriter,其他使用方式同IO流,缓冲流更为高效
  • 特殊功能
    a. bw.newLine(); 写一个换行,会根据系统决定是什么换行符
    b. br.readLine(); 读一行数据,不包括换行符,返回一个字符串数据
  1. File类: 文件和目录路径名的抽象表现形式,File 类的实例是不可变的
  • 构造方法
File(File parent, String child) File(String pathname) File(String parent, String child)

  • 功能
    a. 创建功能 boolean createNewFile()
    b. 删除功能 boolean delete()
    c. 获取功能 File getAbsolutePath()等等
    d. 判断功能 boolean exists()
// 获取所有java文件 public static void getJavaFileName (File file) { File[] files = file.listFiles(); // 获取所有的文件和目录的 File 对象数组 for (File f : files) { // 遍历得到每一个File对象 if (f.isFile()) { if (f.getName().endsWith(".java")) { System.out.println(f); } } } }

集合体系结构
  • 最顶层是Collection
  • 集合的遍历,以ArrayList为例
// 1. 创建集合对象 ArrayList c = new ArrayList(); c.add("hello"); c.add("world"); // 遍历方式1 Object toArray() Object[] objs = c.toArray(); for (int i = 0; i < objs.length; i++) { String s = objs[i]; } // 遍历方式2 Iterator 注意:迭代器是集合的一个副本,在操作过程中如果集合改变,则会抛出异常 Iterator it = c.iterator(); while(it.hasNext()) { String s = it.next(); } // 遍历方式2 增强for 在底层是和迭代器一样的,所以在使用时也不能去修改集合 for (String s : c) { Systen.out.println(s); }

  • 常用集合类:
    1. ArrayList 底层结构是数组,查询快,增删慢
    2. LinkList 底层是链表,查询慢,增删快
  • Set集合:无序(存储和读取的顺序可能不同,无索引),无重复
  • Map双列集合 将键一对一(键唯一)映射到值的对象,一般使用
    hashMap
// map 的遍历 Map map = new HashMap<>(); map.put("23号", "James"); map.put("24号", "Kobe"); map.put("35号", "Durant"); // 方式1:获取所有的键 再获取值 Set keys = map.keySet(); : // 遍历键 获取值 for (String key : keys) { System.out.println(key + ":" + map.get(key)); }// 方式二,通过内部类 Entry 获取每对键和值 Set entries = map.entrySet(); for (Map.Entry entry : entries) { String key = entry.getKey(); String value = https://www.it610.com/article/entry.getValue(); System.out.println(key +":" + value); }

数据结构
  1. 数组
  • 查找方便,直接使用数组索引,但是数组长度和存储值的类型是不可变的,所以增删操作复杂
  1. 链表:存储当前地址、当前数据和下一个数据的地址
  • 查询慢,需要顺着链查找,但是增删快
  1. 栈和队列
  • 栈:先进后出
  • 队列:先进先出
异常
  1. 异常:在代码编译或者运行时出现的错误,异常包括错误的类型、原因和位置
  2. 体系结构:Throwable是所以错误和异常的超类
Throwable(最顶层) Error(不能处理的严重问题) Exception(可以处理的问题)

  1. 异常的处理
  • JVM:会把异常输出在控制台上,并终止程序的执行
  • 捕获处理 try...catch
try { // 有可能出错的代码 } catch (Exception e) { // 代码出错时执行,处理异常 } finally { // 一定会执行,用于释放资源、处理垃圾的收尾工作 }

  • 抛出异常: 当我们无法处理异常时(比如编译时异常),可以抛出异常,谁调用方法谁处理这个抛出的异常,使用关键字 throws
  1. 异常的分类
  • 运行时期异常:RuntimeException的子类,在编译时期可以不处理
  • 编译时期异常:Exception的子类,在编译时期处理
  1. 自定义异常
public class ExceptionDemo { public static void main(String[] args) { checkScore(200); }public static void checkScore(int score) { if (score < 0 || score > 100) { throw new ScoreException("成绩错误"); } System.out.println("正确的成绩"); } }class ScoreException extends RuntimeException { public ScoreException() { }public ScoreException(String message) { super(message); } }

多线程
  1. 概念
  • 进程:一个应用程序在内存中的执行区域
  • 线程: 进程中的执行控制单元,一个进程可以由一个线程(单线程:安全性高,效率低),或多个线程(多线程:效率高,存在安全问题)组成
  1. Thread
  • 使用
    a. 定义一个类继承Thread类并重写 run() 方法,在里面写这个写线程要做的事,然后创建线程对象再调用start()方法启动线程
    b. 定义一个类实现 Runable接口,实现run()方法,创建线程时传递一个该类的对象并启动线程
    c. 匿名内部类实现
public class ThreadDemo { public static void main(String[] args) { // 使用匿名内部类 创建线程1 Thread t1 = new Thread() { public void run() { for (int i = 0; i < 10; i++) { System.out.println(this.getName() + "使用匿名内部类实现多线程"); } } }; Thread t2 = new Thread() { public void run() { for (int i = 0; i < 10; i++) { System.out.println(this.getName() + "使用匿名内部类实现多线程"); } } }; t1.setName("Thread1"); t2.setName("Thread2"); t1.start(); t2.start(); } }

  1. 并发问题
    a. synchronized:同步(锁),可以修饰代码块和方法,被修饰的代码块和方法一旦被某个线程访问,则会被锁住,其他线程无法访问
// 效率低 synchronized(锁对象) { // 锁对象需要被所有的线程共享 // 线程代码 }// 同步方法 public synchronized void method() { // 锁对象是this,静态方法锁对象是当前类的字节码对象 }

  1. 线程的生命周期
    a. 新建 继承Thread或实现Runable接口
    b. 就绪:已经具备了执行能力,但是没有执行权利
    c. 阻塞、等待(wait() notify()),注意:阻塞状态需要回到就绪状态
    d. 运行
    e. 死亡
网络编程
  1. Socket
  • 网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket
  • 网络通信其实就是Socket之间的通信,数据在两个Socket间通过IO传输
  1. 使用UDP协议收发数据
  • 发送
public class UDPSend { public static void main(String[] args) throws IOException { // 1. 创建Socket对象 DatagramSocket ds = new DatagramSocket(); // 2. 创建数据并打包 DatagramPacket(数据包类,需要数据byte[],目标IP和端口号) String s = "hello world"; byte[] bys = s.getBytes(); // 数据 int length = bys.length; // 要发送数据长度 InetAddress address = InetAddress.getByName("DESKTOP-7MTQ9R3"); // 目标设备ip System.out.println(address); int port = 9999; // 目标设备端口 DatagramPacket dp = new DatagramPacket(bys, length, address, port); // 数据报包 // 3. 发送数据 ds.send(dp); // 4. 释放资源 ds.close(); } }

  • 接收数据
public class UDPReceive { public static void main(String[] args) throws IOException { // 1. 创建接收端Socket对象 DatagramSocket ds = new DatagramSocket(9999); // 2. 接收数据 byte[] bys = new byte[1024]; DatagramPacket dp = new DatagramPacket(bys, bys.length); // 接收数据包的容器对象 System.out.println(1); ds.receive(dp); // 阻塞 等待数据 System.out.println(1); // 3. 解析数据 InetAddress address = dp.getAddress(); byte[] data = https://www.it610.com/article/dp.getData(); // 获取接收到的数据 int length = dp.getLength(); // 获取接收到的数据长度 // 4. 输出数据 System.out.println("sender=>" + address.getHostAddress()); System.out.println(new String(data, 0, length)); // 5. 释放资源 ds.close(); } }

  1. 使用TCP协议收发数据
  • 发送
// 使用TCP协议发送数据(客户端) public class TCPSend { public static void main(String[] args) throws IOException { // 1. 创建发送端(客户端)Socket对象(创建连接) Socket s = new Socket(InetAddress.getByName("DESKTOP-7MTQ9R3"), 6666); // 2. 获取输出流对象 OutputStream os = s.getOutputStream(); // 3. 发送数据 String str = "hello world"; os.write(str.getBytes()); // 4. 释放资源 os.close(); } }

  • 接收
public class TCPReceive { public static void main(String[] args) throws IOException { // 1. 创建接收端(服务端)Socket对象 ServerSocket ss = new ServerSocket(6666); // 2. 监听接收数据 阻塞 返回一个Socket Socket s = ss.accept(); // 3. 获取输入流对象 InputStream is = s.getInputStream(); // 4. 获取数据 InetAddress address = s.getInetAddress(); byte[] bys = new byte[1024]; int len; // 存储读到的数据个数 len = is.read(bys); // 5. 输出数据 System.out.println("sender=>" + address); System.out.println(new String(bys, 0, len)); // 6. 释放资源 is.close(); } }

扩展
  1. 【Java|Java SE】Java中的内存分配

    Java|Java SE
    文章图片
    Java中的内存分配
  2. 成员变量和局部变量区别
  • 在类中位置不同,成员变量在类中方法外;局部变量在方法中或者方法声明上(形式参数)
  • 在内存中位置不同,成员变量在对象中,也就是堆内存中;局部变量在栈内存中
  • 生命周期不同,成员变量随着对象存在;局部变量随着方法的调用存在
  • 初始化,成员变量无需初始化化,有默认值;局部变量要使用必须初始化

    推荐阅读