不要焦虑也不要放弃,每天进步一点点
本文面向的是对 java 只想做一个基本了解的同学,比如暂时不搞开发的脚本小子,如果需要深入学习,不建议观看,看完本文可以掌握 java hello world 编写和各语言通用的流程控制函数和忽略不计的面向对象知识
java是一种面向对象的编程语言,有很多特定的工具类/函数,方便不同情景的快速编程,使用的开发工具是 IntelliJ IDEA
参考视频教程:狂神的java基础
新建 HelloWorld.java 文件(环境安装略) 本文使用 SDK 版本为1.8
public class HelloWorld {
public static void main(String[] args){
System.out.print("hello world!");
}
}
① java 是严格大小写的语言,Test 和 test 是两个东西
② 类名要与文件名一致
java 是编译型语言,首先需将 HelloWorld.java 文件编译成 HelloWorld.class 文件再执行
PS C:\java_lession> javac .\HelloWorld.java
PS C:\java_lession> java HelloWorld
hello world!
class 文件是字节码文件,介于 java 类文件和机器码之间的一种文件,然后由 JVM 来解释执行
java 是强类型语言,需要先定义数据类型,然后定义名称,再赋值,像php、python等不用预先定义,即可使用
//定义变量为String字符串类型,变量名为test,值为 this is a test
String test = "this is a test";
变量命名时只能以字母、$ 美元符、或 _ 下划线开头
整数
byte:占 1个字节 范围:-128-127
short:占 2个字节 范围:-32768-32767
int:占 4个字节 范围:-2147483648-2147483647
long:占 8个字节 范围:-9223372036854775808-9223372036854775807
//long num = 10L; long类型要在数字后面加个"L"来标识
浮点类型(小数)
float:占 4个字节 //float num = 1.1F; 需加"F"来标识
double:占 8个字节
//double精度高,有效数字16位,float精度7位
最好避免使用浮点数进行比较,如果超出范围,在定义时没有报错,但是存储时数据已经有损失
字符类型
char:占 2个字节 //只能定义一个字符,且必须用单引号包裹
char a = '网';
char a = 'A';
char 类型能表示的数据十分有限,而字符串我们是通过String类来定义,是类而不是数据类型
在定义时,一旦类型确定,会在内存开辟相应大小的空间来存放数据,尽量选择合适的类型来节省空间
boolean:true or false 只占一位
除了以上8种基本数据类型,其它的叫做引用数据类型
位(bit):存储数据的最小单位,0 或 1 占一位,简写为 b
字节(Byte):8 位为一个字节,可以表示0-255,在少数系统中不是 8 位
因为我们交流不能使用 01010 字符串,所以要使用编码,来使 01 字符串和字母或汉字对应,而能表示人类语言的一个最小单位,也就是数据处理的基本单位,或许可以称为字节
比如 ascii 编码,收录了 128 个字符,常见字母和常见符号,用一个字节即可表示所有
java 中不同进制数据表示方法
int i1 = 10; // 10进制
int i2 = 0b10; // 2进制
int i3 = 010; // 8进制
int i4 = 0x10; // 16进制
// sout 回车 输出语句
System.out.println();
// psvm 主函数
public static void main(String[] args) {}
和之前学习的写法有所不同
char a = '中';
System.out.println((int)a);
>>>> 20013
System.out.println((int)'s');
>>>> 115
unicode 编码,又称万国码,将世界上所有的文字用 2 个字节统一进行编码,而常用的 utf8 编码是 unicode 的一种实现方式(unicode在扩充表意文字时发现不够用了),可根据不同编码通过变长(1~4字节)来表示,所以可以理解为什么汉字可以转换成数字
可以尝试打印一下看看汉字的编码范围,在 java 中是使用 UTF-16 的编码方式
System.out.println((char)13312);
ascii:65--a
unicode:61--a
utf-16:97--a
而普通类型转换可以不写,就是类似小范围转大范围,整数转小数这类的,略
顾名思义,就是数据与内存大小不匹配,装不下了
// 定义两个变量
int money = 10_0000_0000;
int years = 20;
// 变量相乘 一个乘之前做了类型转换 一个没有
long total = money*years;
long total2 = money*(long)years;
// 打印变量
System.out.println(total);
System.out.println(total2);
money*years 的值已经超过了 int 所能表示的最大值,就会溢出,结果如下
>>>> -1474836480
>>>> 20000000000
类是变量(属性)和函数(方法)的集合,用 class 关键字标识
类有什么用? 为什么要把一堆数据函数绑定到一起? 这是为了实现封装,类是对现实生活中一类具有共同特征的事物的抽象,比如我要实现一个生成订单的功能,要扣减库存、扣减促销资源,更新用户账户等操作,我把这一系列操作抽象成一个订单类,需要的变量函数我都放到里面,这样就比较方便使用与理解
我们需要知道,类是一种数据类型,不是数据,不在内存中存储,所以我们需要实例化一个类,叫做对象,才能对它进行操作
public class a{}
包括类变量、实例变量、局部变量
public class Variable {
static int allClicks = 0; // 类变量 static关键字
String str = "hello world!"; // 实例变量 不需要初始化即可使用 有默认值
public void method(){
int i = 0; // 局部变量 必须声明和初始化
}
}
局部变量就是只在最近的一个花括号内才能使用的变量
使用 final 关键字,常量的名称一般用全大写的,比较容易区分
final double PI = 3.14;
类似 final、static…等非数据类型的叫修饰符,不区分先后顺序,随意
static final double PI = 3.14;
final static double PI = 3.14;
public static final double PI = 3.14;
推荐养成的习惯,便于阅读交流
所有变量名、方法名、类名:见名知意
类成员变量:首字母小写,后面单词首字母大写
局部变量:首字母小写,后面单词首字母大写
常量:全部字母大写,可加下划线
类名:首字母大写,后面单词首字母大写 Man GoodMan
方法名:首字母小写,后面单词首字母大写 run() runRun()
java 支持如下运算符
算数运算符: + - * / %(取模) ++ --
赋值运算符: =
关系运算符: > < >= <= == != instanceof
位运算符: & | ^ ~ >> << >>>
条件运算符: ?:
扩展赋值运算符: += -= *= /=
因为任何计算都要转换为 01 序列进行运算,位运算在某些情况下可以提高计算机的运算效率
如计算 2 的 n 次幂
2^3 = 2<<3;
它的意思是将二进制数向左移 3 位,如何理解,拿十进制数看一下,0013,左移一位(空位补0),0130,就是扩大了十倍,左移两位,1300,就是扩大了一百倍,同理,二进制数左移一位,就是扩大两倍,左移两位,就是四倍,10 位就是 2^10,1024 倍…类似的,可以增加幂运算的计算效率
int a = 3;
int b = a++;
System.out.println(a);
System.out.println(b);
>>> 4
>>> 3
要给程序一个入口 main 函数,一个项目一个 main 函数就可以
int a = 3;
int b = ++a;
System.out.println(a);
System.out.println(b);
>>> 4
>>> 4
区别就是如果自增运算符在后,是先赋值,a才会加一,在前面则是先加一再赋值
在进行与(&&)运算的时候,必须两个都为真结果才为真,那么只要第一个条件为假,就不必判断后面的条件了,这就是所谓的短路运算
int c = 4;
boolean d = (c<4)&&(c++)<5;
System.out.println(c);
System.out.println(d);
>>> 4
>>> false
在判断完 c<4 为假,就进行一些操作将其简化为假,并不会判断后面的语句然后执行 c++,所以 c 的值仍然为 4
int c = 4;
if ((c < 4)) {
c++;
}
boolean d = false;
可以看到只单独判断了 c 是否小于4
x ? y:z
如果 x 语句结果为真,则结果为 y,为假则结果为 z
int c = 4;
int result = c>5 ? c++ : c-- ;
System.out.println(c);
>>> 3
语句有一个返回结果,需要用变量接收
为了更好的组织类,java 提供了包机制,用于区别类名的命名空间,类似于文件夹的功能
package pkg1[.pkg2[.pkg3]...];
一般利用公司域名倒过来作为包名
导入包使用 import 语句
import package1[.package2...].(classname|*)
// 创建一个扫描器对象,用于接收键盘数据
Scanner scanner = new Scanner(System.in);
System.out.println("使用next方式接收:");
// 判断用户有没有输入字符串
if(scanner.hasNext()){
String str = scanner.next();
System.out.println("输出的内容为:"+ str);
}
// IO流的类用完要随手关 养成节约资源的好习惯
scanner.close();
使用next方式接收:
hello world
输出的内容为:hello
使用 nextline 方法
String str = scanner.nextLine();
使用next方式接收:
hello world
输出的内容为:hello world
next 方法以空格作为结束符,nextline 以回车作为结束符
还可以简单判断数据的类型,例如
Scanner scanner = new Scanner(System.in);
System.out.println("请输入整数:");
if(scanner.hasNextInt()){
int i = scanner.nextInt();
System.out.println("整数数据:"+i);
}else {
System.out.println("输入的不是整数数据!");
}
scanner.close();
if(布尔表达式/默认为true){
// 如果布尔表达式为 true 将执行的语句
}
if(布尔表达式/默认为true){
// 如果布尔表达式为 true 将执行的语句
}else{
// 如果表达式为false
}
if(条件1){
...
}else if(条件2){
...
}else{
// 都不满足时执行
}
switch(expression){
case value1:
// 语句
break;//可选
case value2:
// 语句
break;
case value3:
// 语句
break;//可选
default: //可选
// 语句
}
break 语句是跳出当前语句,即{},结束 switch 语句,如果没有的话,在满足value2时,执行完对应的语句还会继续向下执行,不进行值的判断,直到遇到 break 语句或全部执行完才结束
当满足条件时,执行语句
int i = 0;
while(i<100){
i++;
}
一定会执行一次,然后判断是否满足条件,满足则继续执行
int i = 0;
do {
i++;
}while (i<100);
9.for 快速打印 i=0;i<9,i++ 的 for 循环
for (int i = 1; i <= 9; i++) {
for (int j = 1; j <= i; j++) {
int s = i*j;
System.out.print(i+"*"+j+"="+s+"\t");
}
System.out.print("\n");
}
增强 for 循环,可以作为遍历数组集合的快捷方式
int[] numbers = {10,20,30,40,50};
for(int x:numbers){
System.out.println(x);
}
break 中止循环,不执行剩余语句
continue 中止本次循环,直接进行下一次循环
// 打印三角形 一共打印 I 行
final int I = 25;
// 打印第 k 行
for (int k=0;k<I;k++){
// 打印第 j 列
for (int j = 1; j <=(2*I-1); j++) {
// 当满足条件时 打印 * 否则打印空格
if((I-k)<=j&&j<=(I+k)){
System.out.print("*");
}else {
System.out.print(" ");
}
}
System.out.println("");
}
因为所有输出结果都是一行一行打印的,所以我们只需要思考每一行中什么时候应该打印星号,什么时候打印空格
方法就是类函数,最常见的就是程序入口的 main 方法,下面是一个计算两数之和的简单方法
// public、static是修饰符 int是返回值类型,没有则是void addNum是函数名
public static int addNum(int a,int b){
return a+b;
}
// 调用
int c = addNum(3,4);
某方法在读取到 return 语句时,该方法就会结束然后返回值
方法调用就是通过对象名.方法名
在某个类中定义了多个方法名相同的方法,但是他们的参数数量不同或类型不同或顺序不同,就认定为不同方法,这就叫方法重载
参数顺序不同
public static int add(int a,float b){}
public static int add(float a,int b){}
形参类型不同
public static int add(int a,int b){}
public static int add(float a,int b){}
参数个数不同
public static int add(int a,int b){}
public static int add(int a,int b,int c){}
因为 java 函数要求形参和实参的数量和类型都要匹配,但是有些时候需要传入的参数不确定,比如使用一些命令行工具可传入多个参数,对于这种不完全匹配的情况可以使用可变参数
public static void test(double... numbers){}
重复调用自身知道达到某一条件退出
public static long f(long n){
if(n==1){
return 1;
}else {
return n*f(n-1);
}
System.out.println(f(25));
求阶乘的函数,返回f(n)=n*f(n-1),直到n=1停止,不推荐使用递归调用,因为调用程序需要不断压栈,可能导致程序溢出
定义:相同类型数据的有序集合,类型必须相同,每个数据称作数组元素,可以通过下标访问
dataType[] a1; // dataType[] 代表数组数据的类型 int、String...
dataType a2[]; // 在变量名后加中括号 非常不推荐
int[] nums = new int[10]; //初始化数组大小 动态初始化&默认初始化
int[] a = {1,2,3,4,5}; //静态初始化
需要定义数组的初始大小
nums[0] = 1; // 赋值 有默认值
nums[5] = 100;
System.out.println(nums[5]); //通过下标取值 大于范围会越界
简单来说就是数组的元素还是数组
int[][] array = {{1,2,7},{2,3,5},{3,4,6},{4,5,9}}
array[0]-->{1,2}
array[0][1]-->2
array[2][2]-->6
int[][][] array= {{{1,2}, {1,2}, {5,6}},{{1,2},{5,6},{5,6}},{{1,2},{5,6},{5,6}}};
数组有个常用的 Arrays 类,ctrl+左键即可点击看源码,左侧结构/structure可看到相关方法,方便看源码,很多常用的工具类都是某些开发者写的一些函数,自己也可以开发
这是用来压缩数据的一种方法,无论是传输还是存储,数据的压缩都是非常重要的
主要适用于有用数据比较少,大部分是一些无意义数据的情况,举例就是棋盘对局
然后第一行记录矩阵共有几行几列,几个数据,接下来就记录哪一行哪一列,数据的具体值是多少
这里只是初步了解,不做过多讲解
所谓面向对象,就是一种功能分类,首先考虑是宏观层面的,再考虑具体代码实现
本质是:以类的方式组织代码,以对象的形式封装数据
test 软件包下新建一个 Demo05 的 java 类
package test;
public class Demo05 {
public static String test(){
return "测试";
}
}
test 软件包下新建一个 Demo01 的 java 类,调用 Demo05 的 test 方法
package test;
public class Demo01 {
public static void main(String[] args) {
System.out.println(Demo05.test());
}
}
因为是静态函数,在 static main 方法中可以直接以类名.方法名调用,如果是非静态的,需要实例化,而类型就是类名
Demo05 a = new Demo05();
System.out.println(a.test());
静态方法在类存在的时候就存在了,而非静态的方法在实例化之后才存在
在使用 new 实例化对象时会调用的方法
public Test(){}
函数名和类名一样,没有返回值,作用就是用来初始化定义一些值
还可以多个构造方法,比如一个有参数,一个没有参数,类似之前说的方法重载,一旦定义有参数的构造方法,就必须定义没有参数的构造方法
public Test(){}
public Test(String name){}
java 中对象时保存在堆中的,数组对象也是在堆中,堆栈是两个比较常用的概念,我们目前只需要知道是辅助程序运行的,用于存放数据,标识运行状态的东西
举个例子,一个 main 函数,实例化一个 pet 类并调用赋值
堆栈中大致过程示意图(不完全)
程序无非就是操作一些数据,进行逻辑运算的,在堆中有专门的方法区,用来存放类相关数据,堆中还会存放实例化的对象什么的,栈一般存放方法(执行到哪个方法)和变量引用,然后到堆中去寻找具体的数据和方法,入栈出栈在之前都有接触过
减少暴露在外的数据,我们在之前的一篇 php 反序列化习题的文章接触过就是私有属性
private name = "xiaoming";
无法通过类名.属性名进行赋值调用,但是可以通过类内的 get/set 方法来操作,保证操作在可控范围内,通过 get/set 加属性名进行定义
继承是指类之间的一种关系,父类子类,子类继承父类,相当于对父类进行扩展
比如有学生类/老师类两个子类,他们都继承自父类-人类,学生类老师类相对于人类变得更具体了,扩展了一些属性和方法,一般使用 extends 关键字
public class Student extends Person{}
java 只有单继承没有多继承,也就是只能有一个父类,子类会拥有父类除了私有的(private)其它全部的属性和方法
在子类构造方法调用父类的构造方法使用 super 关键字,当然,还是不能调用私有属性的
super.test();
super.name;
子类如果要重写继承父类的方法,不要加 static 关键字,静态方法在最开始加载,子类的非静态方法重写会覆盖父类的方法,很容易理解,不贴代码了
A extends B
A a = new A();
B b = new A();
A 继承 B 类,两种写法都是可以实例化 A 的,区别在于,父类引用指向子类对象(B b=new A)只能调用子类中重写的父类方法(没有属性的事),不能直接调用子类独有的方法,而子类引用指向子类对象,可以调用自己的所有方法,如果子类重写了某方法,则都会调用子类的方法
当然,也不是不可能,只要将对象进行类型转换就可以调用子类方法了,还是上面的例子,子类 A 中有一个 test 方法
A{
test(){}
}
调用,引用子类 A 的对象 a 可以直接调用子类方法,而引用父类 B 的对象 b 需要进行类型转换才能调用(之前讲过的强制类型转换,和它很像)
a.test();
((A)b).test();
多态的一个比较大的作用就是解耦,什么意思呢?比如我设计了一个父类 List,还有一些子类,某一天我的一个子类 ArrayList 用不到了,需要修改为 LinkedList,只要修改实例化的子类名称就可以了,比较容易维护
List list = new ArrayList();
List list = new LinkedList();
说抽象类呢,它只有方法的名字,没有方法的实现,使用 abstract 关键字定义
public abstract class Action{
public abstract void dosomething();
}
由继承它的子类进行方法的实现,除非子类还是一个抽象类
Java里面由于不允许多重继承,所以如果要实现多个类的功能,则可以通过实现多个接口来实现
接口由 interface 关键字定义,里面没有方法的实现,只需定义方法的名字,默认都是 public 的抽象类,需要在实现类里面写具体的代码
public interface UserService {
void add(String name);
void delete(String name);
}
实现类需要用 implements 关键字定义,Alt+Insert 快捷键插入实现方法
public class UserServiceImpl implements UserService{
@Override
public void add(String name) {
}
@Override
public void delete(String name) {
}
}
网上一堆眼花缭乱的什么接口和多继承有什么关系,没怎么看懂,倒是如果有十个接口的方法要实现,可以用所谓的继承在一个类里实现,既然最终都要在类里重写,不知道这一步有什么用,不过这种东西应该都是便于开发维护的东西,以后有了经验自然就知道了,没必要在这里硬抠
在类内定义类,如何调用?
package test;
public class Outer {
private int id = 100;
public void out(){
System.out.println("外");
}
public class Inner{
public void in(){
System.out.println("内");
}
}
}
实例化外部类,然后再实例化内部类(java中可以直接在类内实例化自己)
package test;
import test.Outer;
public class test {
public static void main(String[] args) {
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
}
}
还可以调用外部类的私有属性,和私有方法
private int id = 100;
private void test(){
system.out.println("私有方法");
}
// 内部类调用
public class Inner{
public void in(){
System.out.println(id);
test();
}
}
//实例化调用
inner.in();
我觉得这个做题可能会用到
程序经常会遇到各种各样的bug导致不能正常运行,这时就需要引入异常捕获机制,让程序更稳定的运行,即使有一些错误,也能照跑不误
举例:try 尝试执行代码块内的语句,如果catch语句捕捉到错误则执行catch代码块的语句,报错类型是根据语句决定的
int a = 2;
int b = 0;
try{
System.out.println(a/b);
}catch(Exception e){
System.out.println("xx");
}finally {
System.out.println("fin");
}
finally 是不管是否报错,最终都会执行的语句
throw、throws一般是在方法里使用的,这里就不说了
自带的异常类不能满足我们的需求的时候可以自己写,java的很多东西都是可以自定义的,实现自定义异常类需要继承 Exception 类或其子类,如果自定义运行时异常类需继承 RuntimeException 类或其子类,这里不做讲解
将源码拖到编译器中,Maven 会自动加载需要的依赖包,然后一般是修改数据库配置等,再一键编译运行,这和 php 区别是比较大的
直接将class文件拖到编译器,会直接显示反编译的文件
注意编码,可以看到 case 的值并不是明文,这是hashcode,通过它来指代
看完本套课程,印象最深的就是那句:程序,是对世界的建模!瞬间高大上起来了