Java细节回顾
数据转换
1:不能对布尔值进行转换
2:不能把对象类型转换为不相干的类型
3:在把高容量转换到低容量的时候,强制转换
4:转换的时候可能存在内存溢出,或者精度问题
5:数字溢出时,可以如下处理
int money = 100_0000_0000 int years = 20 long total = money * years //会溢出 long total2 = money * ((long)years) //需要先把一个数转为long
反编译 java---class(字节码文件)----通过IDEA查看反编译文件
1:点击上方菜单栏File ->Project Structure ->Project settings ->Project -> Project compiler output:
2:到以上目录中,拷贝对应文件,如本次run的文件为Main.java,则拷贝Main.class到Main.java平齐目录中
3:双击点击Main.class,此时就能看到对应的反编译代码了。
多态
1:类型转换异常:ClassCastException
2:存在条件:继承关系,方法需要重写,父类引用指向子类! 如 Person s1 = new Student();
3:static方法、final常量、private方法不可进行重写
4:如图中s2想要调用eat的话,需要先强转为Student类
5:如Student student = new Student(); Person person = student; 对应的person可能会丢失student自己的本来一些方法
6:对于方法的调用,编译看左边,运行看右边;对于变量的调用,编译看左边,运行看左边
抽象类
1:抽象类不能创建对象,只能靠子类去实现
2:抽象类中可以写普通方法
3:抽象类中,不一定包含抽象方法,但有抽象方法的类必定是抽象类
4:抽象类作为类一定有而且是必须有构造器,是供子类创建对象时,初始化父类成员使用的
5:抽象类的核心意义就是为了被子类继承,否则抽象毫无意义;抽象类是模板思想,部分实现,部分抽象
接口
1:当一个类实现多个接口时,多个接口中存在同名默认方法时,实现类必须重写这个方法
2:方法都是public abstract类型;定义的值都是public static final类型
3:接口的变量名称建议全部大写,多个单词用"_"连接
4:一个类实现多个接口,必须要重写所有接口的全部抽象方法,否则这个类要定义成抽象类
5:接口与接口是多继承关系,一个接口可以继承多个接口
6:如果实现了多个接口,多个接口中存在同名静态方法并不会冲突,原因是只能通过各自接口名访问静态方法
7:当一个类既继承父类,又实现若干个接口时,无论父类成员方法与接口中默认方法是否重名,子类都将执行父类成员方法
8:接口是更彻底的抽象,它没有构造器,不能创建对象
异常处理
异常体系架构
RuntimeException(运行时异常)
ArrayIndexOutOfBoundsException(数组下标异常)
NullPointerException(空指针异常)
ArithmeticException(算术异常)
MissingResourceException(丢失资源)
ClassNotFoundException(找不到类)等异常,这些异常是不检查异常,程序中可以选择捕获处理,也可以不处理
Error和Exception区别:Error通常是灾难性的致命的错误,是程序无法控制和处理的,当出现这些异常时,Java虚拟机(JVM)一般会选择终止线程;Exception通常情况下是可以被程序处理的,并且在程序中应该尽可能的去处理这些异常。
自定义实现异常
继承Exception/RuntimeException,重写构造器
在出现异常的地方用throw new 自定义对象抛出
异常处理方式
理论上最好方案:底层出现的异常抛出给最外层调用者集中捕获处理(程序在出现异常后不会立即死亡)
资源
资源都是实现了Closeable接口的,都自带close()关闭方法,可以放在异常处理的finally中进行回收
实际应用中的经验总结
处理运行时异常时,采用逻辑合理规避同时辅助try-catch处理,如下图
在多重catch块后面,可以加一个catch(Exception)来处理可能会被遗漏的异常
对于不确定的代码,也可以加上try-catch,处理潜在的异常
尽量去处理异常,切忌知识简单的printStackTrace()去打印输出
尽量添加finally语句去释放占用的资源
构造器
子类的全部构造器默认一定会调用父类的无参构造器,除非构造器第一行调用super(xxx)
枚举——用于做信息标志和信息分类
enum 枚举名称{ 实例1名称,实例2名称 }
1:枚举类是final修饰的,不能被继承
2:枚举类默认继承了枚举类型: java.lang.Enum
3:枚举类的第一行都时用常量存储的,默认存储了枚举对象
4:枚举类相当于是多例设计模式
5:枚举类的构造器默认是私有的
final关键字
1:final修饰类,类不能被继承
2:final修饰方法,方法不能被重写
3:final修饰变量,变量有且仅能被赋值一次
包
命名规范一般是公司域名的倒写+技术名称,如http://www.itheima.com => com.itheima.技术名称
相同包下的类可以直接访问;不同包下的类必须导包才可使用
权限修饰符——访问权限
private | 缺省 | protected | public | |
本类中 | √ | √ | √ | √ |
本包下其它类 | × | √ | √ | √ |
其它包下的类 | × | × | × | √ |
其它包下的子类 | × | × | √ | √ |
Object类的toString()方法
默认返回当前对象在堆内存中的地址信息,存在的意义就是被子类重写,以便能返回对象的数据内容
Object类的equals方法
默认是比较两个对象的地址是否相同,用”==“也可以。equals存在意义是被子类重写,以便程序员可以自己来定制比较规则,如下
Student zs1 = new Student("张森", 21, '男'); Student zs2 = new Student("张森", 21, '男'); System.out.println(zs1.equals(zs2)); public boolean equals(Object obj) { if (obj instanceof Student) { Student zs2 = (Student) obj; if (this.name.equals(zs2.name) && (this.age == zs2.age && this.sex == zs2.sex) { return true; } }else { return false; } }
时间毫秒值
可用于作时间的计算,例如代码的执行性能分析
long startTime = new Date().getTime(); for (int i = 0; i < 100000; i++) { System.out.println("ddd"); } long endTime = new Date().getTime(); System.out.println((endTime - startTime) / 1000.0 + "s");
SimpleDateFormat简单日期格式化类
可用于把此刻日期对象格式改为我们喜欢的对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss "); String rs = sdf.format(d); System.out.println(rs);
BigDicimal类
用于解决浮点运算导致数据失真的问题
double a = 0.1; double b = 0.2; double c = a + b; BigDecimal a1 = BigDecimal.valueOf(a); BigDecimal b1 = BigDecimal.valueOf(b); BigDecimal c1 = a1.add(b1); // 分别有subtract(减法)、multiply(乘法)、divide(除法)等函数 double rs = c1.doubleValue(); // 将BigDecimal转化为double用于实际使用 System.out.println(rs);
浮点型大小比较
Double.compare(double a, double b);
包装类
自动装箱:可把基本类型值赋给包装类;自动拆箱:可把包装类值赋给基本数据类型
基本数据类型 包装类(引用数据类型)
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean
功能如下:
1:可以把基本数据类型转为字符串类型值
-- 调用toString()
-- 调用Integer.toString(基本数据类型值)
-- 直接把基本数据类型+空字符串
2:把字符串类型数值转换成对应基本数据类型值
-- xxx.valueOf("字符串类型值"),如Integer.valueOf(“111")
泛型
可以在编译阶段约束只能操作某种数据类型
//变量 ArrayList<String> lists = new ArrayList<>(); //类,泛型变量定义时建议使用E, T, K, V MyArrayList<String> lists = new MyArrayList<>(); lists.add("test"); list.remove("test"); System.out.println(list); class MyArrayList<E>{ private ArrayList lists = new ArrayList(); public void add(E e){ list.add(e); } public void remove(E e){ list.remove(e); } @Override public String toString() { return lists.toString(); } } //泛型方法 修饰符 <泛型变量> 返回值类型 方法名称(形参列表){ } //泛型接口 修饰符 interface 接口名称<泛型变量>{ }
泛型没有继承关系,通配符?用在使用泛型的时候代表一切类型
public class Test { public static void main(String[] args) { ArrayList<BMW> bmws = new ArrayList<>(); bmws.add(new BMW()); run(bmws); // 会报错,因为泛型没有继承 run1(bmws); run2(bmws); ArrayList<Dog> dogs = new ArrayList<>(); dogs.add(new Dog()); run(dogs); //会报错 run1(dogs); run2(dogs); // 会报错,因为泛型没有继承 } public static void run(ArrayList<Car> cars) { } public static void run1(ArrayList<?> cars) { } public static void run2(ArrayList<? extends Car> cars) { } } class Car{ } class BMW extends Car{ } class BENZ extends Car{ } class Dog{ }
File类
文件路径分隔符
1. /
2. \\
3. File.separator
File类的API
public String getAbsolutePath():返回文件的绝对路径字符串
public String getPath():获取创建文件对象的时候用的路径
public String getName():返回由此File表示的文件或目录的名称
public long length():返回由此File表示的文件的长度
public boolean exists():判断此File是否存在
public boolean isDirectory():判断此File是否为目录
public boolean isFile():判断此File是否为文件
public boolean createNewFile():当且仅当具有该名称的文件不存在时,创建一个新的空文件(几乎不用,因为以后文件都是自动创建的)
public boolean delete():删除由此File表示的文件或目录(只能删除空目录)
public boolean mkdir():创建File表示的目录(只能创建一级目录)
public boolean mkdirs():可以创建多级目录(建议使用)
public String[] list():获取当前目录下所有“一级文件名称”到一个字符串数组中并返回
public File[] listFiles():获取当前目录下所有的“一级文件对象”到一个文件对象数组中去返回(重点常用)
InetAddress类
UDP通信
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class UDPServerDemo { public static void main(String[] args) throws Exception { // 1.创建一个接收客户端的数据包对象(集装箱) /** * new DatagramPacket(byte[], int length) * 参数一:接收数据的数组 * 参数二:接收数据的数组的长度 */ byte[] buffer = new byte[1024 * 64]; DatagramPacket packet = new DatagramPacket(buffer, buffer.length); // 2.创建一个接收端的码头对象 DatagramSocket socket = new DatagramSocket(6666); // 3.开始接收 socket.receive(packet); // 4.从集装箱中获取本次读取的数据量 int len = packet.getLength(); // 5.输出数据 String rs = new String(buffer, 0, len); System.out.println(rs); // 6.服务端还可以获取发来信息的客户端的IP和端口 String ip = packet.getAddress().getHostAddress(); int port = packet.getPort(); System.out.println(ip + ":" + port); } } public class UDPClientDemo { public static void main(String[] args) throws IOException { // 1.创建一个集装箱对象,用于封装需要发送的数据包 /** * new DatagramPacker(byte[], buf, int length, InetAddress address, int port) * 参数一:封装数据的字节数组 * 发送数据的长度 * 服务端的IP地址 * 服务端程序的端口号码 */ byte[] buffer = "今晚约吗?".getBytes(); DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getLocalHost(), 6666); // 2.创建一个码头对象 DatagramSocket socket = new DatagramSocket(); // 3.开始发送数据 socket.send(packet); socket.close(); } }
TCP通信
客户端发送一行数据,服务端接收一行数据
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public static void main(String[] args) throws Exception{ System.out.println("服务端启动"); // 1.注册端口 ServerSocket ss = new ServerSocket(8888); // 2.开始等待接收客户端的Socket管道连接 Socket socket = ss.accept(); // 3.从Socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 4.把字节输入流转换成字符输入流 Reader isr = new InputStreamReader(is); // 5.把字符输入流包装成缓冲字符输入流 BufferedReader br = new BufferedReader(isr); // 6.按照行读取消息,对方怎么发,server端就该怎么接 String msg; if ((msg = br.readLine()) != null) { System.out.println("收到:" + msg); } } public static void main(String[] args) throws IOException { // 1.客户端要请求于服务端的socket管道连接 Socket socket = new Socket("127.0.0.1", 8888); // 2.从socket通信管道中获得一个字节输出流 OutputStream os = socket.getOutputStream(); // 3.把低级的字节输出流包装成高级的打印流 PrintStream ps = new PrintStream(os); // 4.开始发消息出去 ps.println("我是客户端,喜欢你很久了"); ps.flush(); System.out.println("客户端发送完毕"); }
服务端引入多线程,实现一个服务端可以同时接收多个客户端的消息
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class ServerDemo2 { public static void main(String[] args) throws Exception{ System.out.println("服务端启动"); // 1.注册端口 ServerSocket ss = new ServerSocket(8888); // 2.定义一个循环不断的接收客户端的连接请求 while(true) { // 3.开始等待接收客户端的Socket管道连接 Socket socket = ss.accept(); // 4. 每接收到一个客户端必须为这个客户端管道分配一个独立的线程来处理与之通信 new ServerReaderThread(socket).start(); } } } class ServerReaderThread extends Thread{ private Socket socket; public ServerReaderThread(Socket socket) { this.socket = socket; } @Override public void run() { try { // 5.从Socket通信管道中得到一个字节输入流 InputStream is = socket.getInputStream(); // 6.把字节输入流转换成字符输入流 Reader isr = new InputStreamReader(is); // 7.把字符输入流包装成缓冲字符输入流 BufferedReader br = new BufferedReader(isr); // 8.按照行读取消息,对方怎么发,server端就该怎么接 String msg; while ((msg = br.readLine()) != null) { System.out.println(socket.getRemoteSocketAddress() + "说:" + msg); } } catch (Exception e) { System.out.println(socket.getRemoteSocketAddress() + "有人下线了"); } } } public class ClientDemo { public static void main(String[] args) throws IOException { // 1.客户端要请求于服务端的socket管道连接 Socket socket = new Socket("127.0.0.1", 8888); // 2.从socket通信管道中获得一个字节输出流 OutputStream os = socket.getOutputStream(); // 3.把低级的字节输出流包装成高级的打印流 PrintStream ps = new PrintStream(os); // 4.开始发消息出去 while(true) { Scanner sc = new Scanner(System.in); System.out.println("请说:"); ps.println(sc.nextLine()); ps.flush(); } } }
基本通信模式