Java 初级
数据类型转换
练习:
short a = 1;
short b = 2;
那么 a+b 是什么类型?
变量的命名规则:
变量命名只能使用字母 数字 $ _
变量第一个字符 只能使用 字母 $ _
变量第一个字符 不能使用数字变量不能使用关键字
注:_ 是下划线,不是-减号或者—— 破折号
作用域
练习:属性的作用域在方法中,参数的作用域也在方法中,如果属性和参数命名相同了的话? 那么到底取哪个值?答案
public class HelloWorld {
int i = 1; //属性名是i
public void method1(int i){ //参数也是i
System.out.println(i);
}
public static void main(String[] args) {
new HelloWorld().method1(5);
//结果打印出来是 1还是5?
}
}
输出应该是
5
在堆中的确是将
1
赋值给i;
但是在栈中又
new
了一个新值,且其地址指向堆中的i使得i的值又变回
5
final
final修饰的类不能被继承
final定义的方法不能被重写
final定义的常量不能被重写赋值
自增 自减操作符
public class HelloWorld {
public static void main(String[] args) {
int i = 5;
System.out.println(i++); //输出5
System.out.println(i); //输出6
int j = 5;
System.out.println(++j); //输出6
System.out.println(j); //输出6
}
}
int i = 1;
int j = ++i + i++ + ++i + ++i + i++;
System.out.println(j); // j=2+2+4+5+5=18
public class HelloWorld {
public static void main(String[] args) {
//长路与 无论第一个表达式的值是true或者false,第二个的值,都会被运算
int i = 2;
System.out.println( i== 1 & i++ ==2 ); //无论如何i++都会被执行,所以i的值变成了3
System.out.println(i);
//短路与 只要第一个表达式的值是false的,第二个表达式的值,就不需要进行运算了
int j = 2;
System.out.println( j== 1 && j++ ==2 ); //因为j==1返回false,所以右边的j++就没有执行了,所以j的值,还是2
System.out.println(j);
}
}
switch可以使用byte,short,int,char,String,enum
注: 每个表达式结束,都应该有一个break;
注: String在Java1.7之前是不支持的, Java从1.7开始支持switch用String的,编译后是把 String转化为hash值,其实还是整数
注: enum是枚举类型,在枚举章节有详细讲解
冒泡排序:
public void BubbleSort(int[] arr){
for(int i=0;i<arr.length-1;i++){
for(int j=0;j<arr.length-1-i;j++){
if(arr[j]>arr[j+1]){ //相邻两个元素作比较,如果前面元素大于后面,进行交换
int temp = arr[j+1];
arr[j+1] = arr[j];
arr[j] = temp;
}
}
}
}
// 优化
public class BubbleSort implements IArraySort {
@Override
public int[] sort(int[] sourceArray) throws Exception {
// 对 arr 进行拷贝,不改变参数内容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
for (int i = 1; i < arr.length; i++) {
// 设定一个标记,若为true,则表示此次循环没有进行交换,也就是待排序列已经有序,排序已经完成。
boolean flag = true;
for (int j = 0; j < arr.length - i; j++) {
if (arr[j] > arr[j + 1]) {
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
flag = false;
}
}
if (flag) {
break;
}
}
return arr;
}
}
//增强型for循环遍历
for (int each : values) {
System.out.println(each);
}
引用和指向
new Hero();
代表创建了一个Hero对象
但是也仅仅是创建了一个对象,没有办法访问它
为了访问这个对象,会使用引用来代表这个对象Hero h = new Hero();
h这个变量是Hero类型,又叫做引用
=的意思指的h这个引用代表右侧创建的对象
“代表” 在面向对象里,又叫做“指向”
继承的特性
子类拥有父类非 private 的属性、方法。
子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
子类可以用自己的方式实现父类的方法。
Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)
super 与 this 关键字
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
class Animal {
void eat() {
System.out.println("animal : eat");
}
}
class Dog extends Animal {
void eat() {
System.out.println("dog : eat");
}
void eatTest() {
this.eat(); // this 调用自己的方法
super.eat(); // super 调用父类方法
}
}
public class Test {
public static void main(String[] args) {
Animal a = new Animal();
a.eat();
Dog d = new Dog();
d.eatTest();
}
}
animal : eat
dog : eat
animal : eat
重写(Override)与重载(Overload)
重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变
方法的重写规则
参数列表与被重写方法的参数列表必须完全相同。
返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
父类的成员方法只能被它的子类重写。
声明为 final 的方法不能被重写。
声明为 static 的方法不能被重写,但是能够被再次声明。
子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
构造方法不能被重写。
如果不能继承一个类,则不能重写该类的方法。
当需要在子类中调用父类的被重写方法时,要使用 super 关键字。
重载(Overload)
重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。最常用的地方就是构造器的重载。
重载规则:
- 被重载的方法必须改变参数列表(参数个数或类型不一样);
- 被重载的方法可以改变返回类型;
- 被重载的方法可以改变访问修饰符;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载。
- 无法以返回值类型作为重载函数的区分标准。
public class Overloading {
public int test(){
System.out.println("test1");
return 1;
}
public void test(int a){
System.out.println("test2");
}
//以下两个参数类型顺序不同
public String test(int a,String s){
System.out.println("test3");
return "returntest3";
}
public String test(String s,int a){
System.out.println("test4");
return "returntest4";
}
public static void main(String[] args){
Overloading o = new Overloading();
System.out.println(o.test());
o.test(1);
System.out.println(o.test(1,"test3"));
System.out.println(o.test("test4",1));
}
}
总结
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
- (1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
- (2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
- (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
成员变量有四种修饰符
private 私有的
package/friendly/default 不写
protected 受保护的
public 公共的
对象属性的初始化有三种方式:
1. 声明该属性的时候初始化
2. 构造方法中初始化
3. 初始化块
单例模式:
单例模式又叫做 Singleton模式,指的是一个类,在一个JVM里,只有一个实例存在。
package charactor;
public class Singleton{
//私有化构造方法使得该类无法在外部通过new 进行实例化
private Singleton(){
}
//准备一个类属性,指向一个实例化对象。 因为是类属性,所以只有一个
private static Singleton instance = new GiantDragon();
//public static 方法,提供给调用者获取12行定义的对象
public static Singleton getInstance(){
return instance;
}
}
package charactor;
public class Singleton{
//私有化构造方法使得该类无法在外部通过new 进行实例化
private Singleton(){
}
//准备一个类属性,用于指向一个实例化对象,但是暂时指向null
private static Singleton instance;
//public static 方法,返回实例对象
public static Singleton getInstance(){
//第一次访问的时候,发现instance没有指向任何对象,这时实例化一个对象
if(null==instance){
instance = new Singleton();
}
//返回 instance指向的对象
return instance;
}
}
饿汉式是立即加载的方式,无论是否会用到这个对象,都会加载。
如果在构造方法里写了性能消耗较大,占时较久的代码,比如建立与数据库的连接,那么就会在启动的时候感觉稍微有些卡顿。懒汉式,是延迟加载的方式,只有使用的时候才会加载。
看业务需求,如果业务上允许有比较充分的启动和初始化时间,就使用饿汉式,否则就使用懒汉式
什么是单例模式?
回答的时候,要答到三元素
1. 构造方法私有化
2. 静态属性指向实例
3. public static的 getInstance方法,返回第二步的静态属性
枚举类成员
枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。
枚举既可以包含具体方法,也可以包含抽象方法。 如果枚举类具有抽象方法,则枚举类的每个实例都必须实现它。
enum Color
{
RED, GREEN, BLUE;
// 构造函数
private Color()
{
System.out.println("Constructor called for : " + this.toString());
}
public void colorInfo()
{
System.out.println("Universal Color");
}
}
public class Test
{
// 输出
public static void main(String[] args)
{
Color c1 = Color.RED;
System.out.println(c1);
c1.colorInfo();
}
}
接口
接口与类的区别:
- 接口不能用于实例化对象。
- 接口没有构造方法。
- 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
- 接口不能包含成员变量,除了 static 和 final 变量。
- 接口不是被类继承了,而是要被类实现。
- 接口支持多继承。
接口特性
- 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。
- 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
- 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
抽象类和接口的区别
- 1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
- 2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
- 3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
- 4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
/* 文件名 : NameOfInterface.java */
import java.lang.*;
//引入包
public interface NameOfInterface
{
//任何类型 final, static 字段
//抽象方法
}
接口有以下特性:
- 接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
- 接口中每一个方法也是隐式抽象的,声明时同样不需要abstract关键字。
- 接口中的方法都是公有的。
多态
类的多态
父类引用指向子类对象
要实现类的多态,需要如下条件
1. 父类(接口)引用指向子类对象
2. 调用的方法有重写
操作符的多态
同一个操作符在不同情境下,具备不同的作用
- 如果+号两侧都是整型,那么+代表 数字相加
- 如果+号两侧,任意一个是字符串,那么+代表字符串连接
多态存在的三个必要条件
- 继承
- 重写
- 父类引用指向子类对象:Parent p = new Child();
当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
public class Test {
public static void main(String[] args) {
show(new Cat()); // 以 Cat 对象调用 show 方法
show(new Dog()); // 以 Dog 对象调用 show 方法
Animal a = new Cat(); // 向上转型
a.eat(); // 调用的是 Cat 的 eat
Cat c = (Cat)a; // 向下转型
c.work(); // 调用的是 Cat 的 work
}
public static void show(Animal a) {
a.eat();
// 类型判断
if (a instanceof Cat) { // 猫做的事情
Cat c = (Cat)a;
c.work();
} else if (a instanceof Dog) { // 狗做的事情
Dog c = (Dog)a;
c.work();
}
}
}
abstract class Animal {
abstract void eat();
}
class Cat extends Animal {
public void eat() {
System.out.println("吃鱼");
}
public void work() {
System.out.println("抓老鼠");
}
}
class Dog extends Animal {
public void eat() {
System.out.println("吃骨头");
}
public void work() {
System.out.println("看家");
}
}
Java 中级
1、异常处理
2、I\O 文件流
什么是流(Stream),流就是一系列的数据
当不同的介质之间有数据交互的时候,JAVA就使用流来实现。
数据源可以是文件,还可以是数据库,网络甚至是其他的程序
比如读取文件的数据到程序中,站在程序的角度来看,就叫做输入流
输入流: InputStream 从硬盘 --》 内存 把数据从硬盘的文件,读取到JVM(内存)。
输出流:OutputStream 从内存 --》硬盘
package com.frank.filetest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
/**
* @Author: 张超杰
* @Date: 2022/4/14
* @Description: 使用绝对路径或者相对路径创建File对象
*/
public class TestFile {
public static void main(String[] args) {
try {
File file = new File("D:/TestFile.txt");
// 通过这个输入流,就可以把数据从硬盘,读取到Java的虚拟机中来,也就是读取到内存中
FileInputStream fileInputStream = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
File file1 = new File("D:/testFile.txt");
// 输出流
FileOutputStream fos = new FileOutputStream(file1);
} catch (FileNotFoundException e){
e.printStackTrace();
}
}
}
- InputStream字节输入流
- OutputStream字节输出流
用于以字节的形式读取和写入数据
package com.frank.filetest;
import java.io.*;
/**
* @Author: 张超杰
* @Date: 2022/4/14
* @Description: 使用绝对路径或者相对路径创建File对象
*/
public class TestFile {
public static void main(String[] args) {
try {
File file = new File("D:/TestFile.txt");
// 通过这个输入流,就可以把数据从硬盘,读取到Java的虚拟机中来,也就是读取到内存中
FileInputStream fis = new FileInputStream(file);
//创建字节数组,其长度就是文件的长度
byte[] all = new byte[(int) file.length()];
fis.read(all);
for (byte b:all){
System.out.println(b);
}
//每次使用完流,都应该进行关闭
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
// 准备文件testFile1.txt其中的内容是空的
File file1 = new File("D:/testFile1.txt");
// 准备长度是2的字节数组,用88,89初始化,其对应的字符分别是X,Y
byte data[] = {88,89};
// 创建基于文件的输出流
FileOutputStream fos = new FileOutputStream(file1);
// 把数据写入到输出流
fos.write(data);
// 关闭输出流
fos.close();
} catch (IOException e){
e.printStackTrace();
}
}
}
注: 如果文件d:/testFile1.txt不存在,写出操作会自动创建该文件。
但是如果是文件 d:/xyz/testFile1.txt,而目录xyz又不存在,会抛出异常
package com.frank.filetest;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
/**
* @Author: 张超杰
* @Date: 2022/4/14
* @Description:
* 找到一个大于100k的文件,按照100k为单位,拆分成多个子文件,并且以编号作为文件名结束。
* 比如文件 eclipse.exe,大小是309k。
* 拆分之后,成为
* eclipse.exe-0
* eclipse.exe-1
* eclipse.exe-2
* eclipse.exe-3
*/
public class SplitFile {
public static void main(String[] args) {
int eachSize = 100 * 1024; // 100K
File srcFile = new File("d:/redis-benchmark.exe");
splitFile(srcFile,eachSize);
}
/**
* 拆分的思路,先把源文件的所有内容读取到内存中,然后从内存中挨个分到子文件里
* @param srcFile 要拆分的源文件
* @param eachSize 按照这个大小,拆分
*/
private static void splitFile(File srcFile, int eachSize){
if(0 == srcFile.length())
throw new RuntimeException("文件长度为0,不可拆分");
byte[] fileContent = new byte[(int) srcFile.length()];
// 先把文件读取到数组中
try {
FileInputStream fis = new FileInputStream(srcFile);
fis.read(fileContent);
fis.close();
}catch (IOException e){
e.printStackTrace();
}
// 计算需要被划分成多少份子文件
int fileNumber;
// 文件是否能被整除得到的子文件个数是不一样的
// (假设文件长度是25,每份的大小是5,那么就应该是5个)
// (假设文件长度是26,每份的大小是5,那么就应该是6个)
if(0 == fileContent.length % eachSize){
fileNumber = (int) (fileContent.length / eachSize);
}else {
fileNumber = (int) (fileContent.length / eachSize) + 1;
}
for (int i = 0; i < fileNumber; i++){
String eachFileName = srcFile.getName()+"-"+i;
File eachFile = new File(srcFile.getParent(),eachFileName);
byte[] eachContent;
// 从源文件的内容里,复制部分数据到子文件
// 除开最后一个文件,其他文件大小都是100k
// 最后一个文件的大小是剩余的
if(i != fileNumber - 1){ // 不是最后一个
eachContent = Arrays.copyOfRange(fileContent,eachSize * i,eachSize * (i + 1));
}else{ // 最后一个
eachContent = Arrays.copyOfRange(fileContent,eachSize * i,fileContent.length);
}
try {
FileOutputStream fos = new FileOutputStream(eachFile);
fos.write(eachContent);
fos.close();
System.out.printf("输出的子文件%s,其大小是 %d字节",eachFile.getAbsoluteFile(),eachFile.length());
}catch (IOException e){
e.printStackTrace();
}
}
}
}
所有的流,无论是输入流还是输出流,使用完毕之后,都应该关闭。 如果不关闭,会产生对资源占用的浪费。 当量比较大的时候,会影响到业务的正常开展。
缓存流
以介质是硬盘为例,字节流和字符流的弊端:
在每一次读写的时候,都会访问硬盘。 如果读写的频率比较高的时候,其性能表现不佳。
为了解决以上弊端,采用缓存流。
缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。
就好比吃饭,不用缓存就是每吃一口都到锅里去铲。用缓存就是先把饭盛到碗里,碗里的吃完了,再到锅里去铲
缓存流在写入数据的时候,会先把数据写入到缓存区,直到缓存区达到一定的量,才把这些数据,一起写入到硬盘中去。按照这种操作模式,就不会像字节流,字符流那样每写一个字节都访问硬盘,从而减少了IO操作
BufferedReader 缓存字符输入流可以一次读取一行数据
PrintWriter 缓存字符输出流, 可以一次写出一行数据
有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush
数据流
DataInputStream 数据输入流
DataOutputStream 数据输出流
package stream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class TestStream {
public static void main(String[] args) {
write();
read();
}
private static void read() {
File f =new File("d:/lol.txt");
try (
FileInputStream fis = new FileInputStream(f);
DataInputStream dis =new DataInputStream(fis);
){
boolean b= dis.readBoolean();
int i = dis.readInt();
String str = dis.readUTF();
System.out.println("读取到布尔值:"+b);
System.out.println("读取到整数:"+i);
System.out.println("读取到字符串:"+str);
} catch (IOException e) {
e.printStackTrace();
}
}
private static void write() {
File f =new File("d:/lol.txt");
try (
FileOutputStream fos = new FileOutputStream(f);
DataOutputStream dos =new DataOutputStream(fos);
){
dos.writeBoolean(true);
dos.writeInt(300);
dos.writeUTF("123 this is gareen");
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out 是常用的在控制台输出数据的
System.in 可以从控制台输入数据
package stream;
import java.util.Scanner;
public class TestStream {
public static void main(String[] args) {
Scanner s = new Scanner(System.in);
while(true){
String line = s.nextLine();
System.out.println(line);
}
}
}