java代码审计-基础语法篇
文章目录前言1. 基础语法2. 构造函数前言java是一个解释型的语言,可跨平台执行。一个 Java 程序可以认为是一系列对象的集合,而这些对象通过调用彼此的方法来协同工作,每个java文件都是一个类,同一文件夹下可以互相调用其他文件下的类,不同文件夹下可以通过import命令进行导入。java中没有析构函数,finalize是用来回收内存的。1. 基础语法大小写敏感:Java 是大小写敏感的。类
文章目录
前言
java是一个解释型的语言,可跨平台执行。一个 Java 程序可以认为是一系列对象的集合
,而这些对象通过调用彼此的方法来协同工作,每个java文件都是一个类
,同一文件夹下可以互相调用其他文件下的类,不同文件夹下可以通过import命令进行导入。
java中没有析构函数,finalize是用来回收内存的。
java中的包其实就是文件夹,在同一个包中可以理解成同一文件夹下。
1. 基础逻辑
大小写敏感:Java 是大小写敏感的。
类名:对于所有的类来说,类名的首字母应该大写。如果类名由若干单词组成,那么每个单词的首字母应该大写。
方法名:所有的方法名都应该以小写字母开头。如果方法名含有若干单词,则后面的每个单词首字母大写。
源文件名:源文件名必须和类名相同。当保存文件的时候,你应该使用类名作为文件名保存(切记 Java 是大小写敏感的),文件名的后缀为 .java。(如果文件名和类名不相同则会导致编译错误)。
主方法入口:所有的 Java 程序由 public static void main(String[] args) 方法开始执行。
2. 构造函数
一个类可以有多个构造函数,根据对象的创建情况可以触发不同的构造函数,如下代码所示,我们如果在创建对象的时候传入了参数,则会触发第二个构造函数,如果不传入参数则会触发第一个构造函数:
class test{
String name = "cody";
public test(){
System.out.println("Other class named test");
}
}
public class demo1 {
public demo1()
{
System.out.println("The first Construct def.");
}
public demo1(String a)
{
System.out.println("The second Construct def,string is "+a);
}
public static void main(String[] args) {
demo1 test = new demo1("SFL");
demo1 test2 = new demo1();
test te = new test();
System.out.println(te.name);
}
}
运行结果如下:
子类是不继承父类的构造函数,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
子类代码Demo.java:
public class Demo extends Test{
public Demo(int a){
super(a); //必须有参数,父类的有参数构造函数才会执行,super相当于给父类的构造函数传参数。
System.out.println("子类 有参数 构造函数启动");
}
public Demo(){
super(); //无论这行有没有,父类的无参数构造函数都会执行。
System.out.println("子类 无参数 构造函数启动");
}
public static void main(String[] args) {
Demo a = new Demo();
a.pri();
}
}
父类代码Test.java:
public class Test {
private int var;
public Test(){
System.out.println("父类 无参数 构造函数启动");
}
public Test(int a){
this.var = a;
System.out.println("父类构造函数启动");
}
public void pri(){
System.out.println("父类中var参数的值为:"+this.var);
}
}
运行结果如下:
综上,父类的构造函数先于子类的执行。
3. 类与对象
假设有一个类名为test,创建对象abe:
test abe = new test();
default类不能其他类访问,只能被当前类或同一个包中的其他类访问,而default类不用标注default
访问修饰符,且一个java文件中只能有一个public类,而default类不限制数量。
如下图,test为私有类,demo1为公有类:
IDEA中一个java class内文件下有好几个java class,如下图:
就代表了这个Demo1.java的运行函数所在的java class为Demo1,还有一个类名为Test。
4. 变量与常量
变量声明:
char a = 'a';
final char b = 'b';
上面两个的区别是,a是个变量,值可以其他代码进行更改。b在char之前加了final是个常量
,是无法被更改的。
5. 变量类型
- 类变量,当类变量被改变时,所有对象的类变量也会被改变。
- 实例变量,当对象被创建当时候实例变量也就被确定了。假设一个类创建了两个对象,第一个对象内实例变量被改变后,第二个对象内当实例变量不会被改变。
- 局部变量,局部变量在函数中,只能被函数所访问。
class Demo {
static String a = "静态变量";
public String b = "实例变量";
public void ch(String c){
a = c;
}
public String pri(){
return a;
}
}
public class Demo1 {
public static void main(String[] args) {
Demo t2 = new Demo();
t2.b = "qwer";
System.out.println("将第一个实例的实例变量更改为qwer");
System.out.println("第 1 个对象的实例变量:"+t2.b);
Demo t3 = new Demo();
System.out.println("第 2 个对象的实例变量:"+t3.b);
t2.ch("asdf");
System.out.println("将第一个对象的类变量更改为asdf");
System.out.println("第一个对象的类变量:"+t2.pri());
System.out.println("第一个对象的类变量:"+t3.pri());
}
}
运行结果如下:
6. 访问控制修饰符
-
default (即默认,什么也不写): 同一个包/文件夹下可调用,同一个java文件中可调用,
可用来修饰:类、变量、方法、接口
。 -
private : 只有当前java文件可以访问,
仅不能修饰类。
-
public : 对所有类可见,
可用来修饰:类、变量、方法、接口
。 -
protected : 当子类与父类在同一个包中的时候,父类的实例或者说对象,可以访问父类的protected类型的数据或者函数。如果父类和子类不在一个包中的时候,在子类中创建父类的实例后,这个父类的实例不能访问父类的protected类型的数据或者函数。
protected不能修饰接口。
关于protected修饰符的测试:
Test类在animals包中,里面有protected类型的数据,代码如下:
Demo跟Test都在animails包中,PrimitiveTypeTest跟Test不在同一个包中。我们会用Demo与PrimitiveTypeTest分别继承Test类,然后测试是否能访问pro这个被protected类型的数据,文件结构如下:
当父类与子类不在同一包,也就是PrimitiveTypeTest继承Test的时候。我们可以通过创建子类的实例间接访问父类的数据。
接着我们new一个父类的实例,发现不同通过这实例访问父类的pro数据,程序报错:
下图中是Demo继承Test类,这两个文件在同一个包中,这时候可以直接创建一个父类的实例,通过这个实例可以访问父类被protected的数据:
7.访问控制与继承
-
父类中声明为 public 的
方法
在子类中也必须为 public。 -
父类中声明为 protected 的
方法
在子类中要么声明为 protected,要么声明为 public,不能声明为 private。 -
父类中声明为 private 的
方法
,不能够被继承。
8. 非访问修饰符
-
static 修饰符,用来修饰类方法和类变量,不过
静态方法中不能调用类的非静态变量。
而且static类型的变量或者方法可以直接用类名进行调用,不需要创建实例。 -
final 修饰符,用来修饰类、方法和变量,final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
-
abstract 可以创建一个方法,这个方法可以不用写细节只需要一个名字,然后再创建实例的时候实现这个方法的具体细节。如下图创建了一个abstract类型的方法名为funcc,创建完方法后,
必须将这个方法所在的类的类型改为abstract。
- synchronized用于修饰方法在同一时间内只能被一个线程所访问。
- transient用于序列化的时候,序列化对象包含被transient修饰的实例变量的时候,jvm不会序列化这个实例变量,会跳过它去序列化别的变量。
- volatile修饰符修饰的变量在每次被线程访问的时候都会强制从内存中读重新读取该成员的值,而且当其发生变化的时候,会强制线程将变化的值写回内存,这样使得在任何时刻,所有的线程看到的被标记volatile修饰符的值都一样。
9.继承/重写/重载
1. 继承
super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类,不过不能在main函数中使用
。
this关键字:指向自己的引用,不过不能在main函数中使用
。
public class Demo extends Test{
int aaa = 123;
public void t2(){
this.aaa+=1111;
}
public void te(){
System.out.println(this.aaa);
}
public static void main(String[] args) {
Demo t = new Demo();
Demo t2 = new Demo();
t.t2();
t.te();
t2.te();
}
}
运行结果:
继承的时候,会继承父类的构造函数。默认父类的无参数构造函数会执行,有参数构造函数需要在子类的构造函数中用super函数进行传参,构造函数不能被重写。
2. 重写
重写就是继承过后重写继承过来的函数,但无法重写构造函数
,且参数列表必须跟原函数相同,举例如下:
Test.java
public class Test {
private int var = 2;
public void ec(){
System.out.println("Test类的函数ec执行成功");
}
public void pri(){
System.out.println("父类中var参数的值为:"+this.var);
}
}
Demo.java中继承了Test类后重写了pri函数:
Demo.java
public class Demo extends Test{
public void pri(){
System.out.println("重写");
}
public static void main(String[] args) {
Demo a = new Demo();
a.pri();
a.ec();
}
}
3. 重载
重载就是在一个类中,可以创建很多个同名的函数,这些函数的接收参数必须不同,修饰符可以相同,举例如下:
public class Test {
private int var;
public void def1(){
System.out.println("第一个def1函数");
}
public void def1(int a){
this.var = a;
System.out.println("第二个def1函数,且传入参数为:"+a);
}
public void pri(){
System.out.println("父类中var参数的值为:"+this.var);
}
}
Demo去继承它并调用,代码如下:
public class Demo extends Test{
public static void main(String[] args) {
Demo a = new Demo();
a.def1();
a.def1(123);
}
}
运行结果如下:
10.多态
多态的三个条件:
- 继承
- 重写
- 父类类型的子类对象,
Parent p = new child();
形成的效果是:访问此对象的某个函数的时候,先在父类中查找看是否有此函数名,如果有则去子类调用这个函数,如果没有则直接报错。这样子就可以实现父类部分函数的重写也就是多态,多态对象,只能调用父类中有的方法,不能调用子类中有但父类没有的方法。
举例如下:
Test.java
public class Test {
private int var = 2;
public void ec(){
System.out.println("Test类的函数ec执行成功");
}
public void pri(){
System.out.println("父类中var参数的值为:"+this.var);
}
}
Demo.java
class a extends Test{
public void pri(){
System.out.println("a类中重写pri函数");
}
public void pri2(){
System.out.println("a类中的pri2函数,但父类没有这个函数");
}
}
class b extends Test{
public void pri(){
System.out.println("b类中重写pri函数");
}
public void pri2(){
System.out.println("b类中的pri2函数,但父类没有这个函数");
}
}
public class Demo extends Test{
public static void main(String[] args) {
Test de1 = new a();
Test de2 = new b();
a de3 = new a(); //创建一个a类型的a的实例,命名为de3
de1.pri();
de2.pri();
de1.ec();
de2.ec();
de1.pri2(); //报错,将这行注释掉后运行成功,因为de1实例中没有pri2这个函数。
de3.pri2(); //成功
}
}
运行结果如下:
11. 接口
一个类通过继承接口进而继承接口中写的方法。当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。举例如下:
接口文件Animal.java:
interface Animal {
public void eat();
public void travel();
}
实现接口的Mammal.java:
public class MammalInt implements Animal{
public void eat(){
System.out.println("Mammal eats");
}
public void travel(){
System.out.println("Mammal travels");
}
public int noOfLegs(){
return 0;
}
public static void main(String args[]){
MammalInt m = new MammalInt();
m.eat();
m.travel();
}
}
运行结果如下:
如果少实现接口中的一个函数travel(),将其注释掉:
则报错如下:
12. 循环
1.for增强循环
主要用来遍历数据。
public class Test {
public static void main(String args[]){
int [] numbers = {10, 20, 30, 40, 50};
for(int x : numbers ){
System.out.print( x );
System.out.print(",");
}
System.out.print("\n");
String [] names ={"James", "Larry", "Tom", "Lacy"};
for( String name : names ) {
System.out.print( name );
System.out.print(",");
}
}
}
2.关键字
continue:执行到continue到时候跳转到下一个循环。
break:执行到break直接跳出循环。
13. 异常处理
1. 捕获所有异常
// 文件名 : ExcepTest.java
import java.io.*;
public class ExcepTest{
public static void main(String args[]){
try{
int a[] = new int[2];
System.out.println("Access element three :" + a[3]);
}catch(Exception e){
System.out.println("Exception thrown :" + e);
}
}
}
2. 自定义异常函数
throws与throw的区别
throws用在方法的后面,用来声明这个方法可能会触发什么异常。
throw用在方法中,用来抛出异常。
参考代码:
//创建自定义异常函数,逻辑如下
class arbitrary extends Exception
{
private String str;
public arbitrary(String s) {
this.str = s;
}
public String get(){
return this.str;
}
}
public class Test {
private int var=1;
private int var2 = 2;
//定义了def1函数可能会抛出arbitrary异常
public void def1() throws arbitrary{
System.out.println("第一个def1函数");
//如果var=var2则抛出arbitrary异常
if (var!=var2)
{
//相当于创建了一个arbitrary类,传入参数。
throw new arbitrary("自定义异常");
}
}
public static void main(String[] args) throws Exception {
try{
Test de1 = new Test();
de1.def1();
}
//如果出现异常则尝试捕获arbitrary异常并实例华且命名为e
catch (arbitrary e){
//打印实例e的get函数的返回值
System.out.println(e.get());
//打印堆栈最终信息
e.printStackTrace();
}
}
}
14. 程序执行过程中接收用户输入
import java.util.Scanner;
public class ExcepTest {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
// 从键盘接收数据
// next方式接收字符串
System.out.println("next方式接收:");
// 判断是否还有输入
if (scan.hasNext()) {
String str1 = scan.nextLine();
System.out.println("输入的数据为:" + str1);
}
scan.close();
}
}
15. 文件操作
1. 读文件
import java.io.*;
import java.util.Scanner;
public class file_op {
//按照字符去读文件内容
void File_reader(String path) throws IOException {
FileReader file = new FileReader(path);
int ch = 0;
System.out.println("使用FileReader类构造对象来读文件:");
while((ch = file.read())!=-1 )
{
System.out.print((char)ch+"<-->"+ch+"\n");
}
file.close();
}
//按字符读取
void Buffer_Reader(String path) throws IOException
{
// BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(path)));
BufferedReader br = new BufferedReader(new FileReader(path));
String data = null;
System.out.println("使用BufferedReader、FileInputeStream、InputStreamReader或者BufferedReader配合FileReader构造对象来读文件:");
while((data = br.readLine())!=null)
{
System.out.println(data);
}
}
//按字节读取
void F_InputStream(String path) throws IOException
{
FileInputStream fis = new FileInputStream(path);
Scanner sc = new Scanner(fis,"UTF-8");
System.out.println("使用FileInputStream与Scanner配合来读文件:");
while (sc.hasNextLine())
{
System.out.println(sc.nextLine());
}
}
public static void main(String[] args) throws IOException {
String path = "/Users/shukasakai/Desktop/1.txt";
file_op fi = new file_op();
fi.File_reader(path);
fi.Buffer_Reader(path);
fi.F_InputStream(path);
}
}
2. 写文件
读取1.txt文件的内容并写入2.txt:
import java.io.*;
public class Out {
void FOut(String path) throws IOException
{
FileOutputStream file = new FileOutputStream(path);
FileInputStream ff = new FileInputStream("/Users/shukasakai/Desktop/1.txt");
while(ff.available()>0)
{
file.write(ff.read());
}
}
public static void main(String[] args) throws IOException {
String path = "/Users/shukasakai/Desktop/2.txt";
Out test = new Out();
test.FOut(path);
}
}
3. 总结
1. FileOutputStream/FileInputStream
FileInputStream类是从文件中读取数据,然后通过read函数输出,输出值是int类型的数据,这个数据是字符的unicode编码的10进制显示
,例如t的unicode编码\u0074,0x74转换为10进制为116,那么t对应的int值就为116。read函数每次只能读一个字节,下次执行就会读取下一个字节的数据了。
如果文件中只有一个t,那么使用FileInputStream的read函数读出的数据就是116,同理,使用FileOutputStream的write(116),也可以将t写入到指定的文件中,且一次只能写入一个字节。测试代码如下所示:
//从1.txt中读取数据并写十进制为112的字符也就是字符p到2.txt中
public class Out {
void FOut(String path) throws IOException
{
FileOutputStream file = new FileOutputStream(path);
FileInputStream ff = new FileInputStream("/Users/shukasakai/Desktop/1.txt");
while(ff.available()>0)
{
System.out.println(ff.read());
file.write(112);
}
}
public static void main(String[] args) throws IOException {
String path = "/Users/shukasakai/Desktop/2.txt";
Out test = new Out();
test.FOut(path);
}
}
执行结果如下,可看到读取的数据,10是换行符:
最终有12个p在里面,证明我们写了12次,进而得知1.txt中有12个字节的数据。
总的来说,FileOutputStream/FileInputStream是对字节进行操作的,无法字节读取字符或者写入字符到文件中,必须通过字节流这个媒介。工作模式大概如下:
读文件:将文件中的字符转化成字节后一个字节一个字节的读取,并转化成unicode的10进制格式。
写文件:将任意进制的数据转化成unicode编码,然后转化成字符写入到文件中。
2. FileReader/FileWrite
import java.io.*;
public class Read {
void File_reader(String path) throws IOException {
FileReader file = new FileReader(path);
FileWriter file33 = new FileWriter("/Users/shukasakai/Desktop/3.txt");
int ch = 0;
System.out.println("使用FileReader类构造对象来读文件:");
while((ch = file.read())!=-1 )
{
System.out.print((char)ch+"<-->"+ch+"\n");
file33.write(116);
file33.flush();
}
file33.close();
file.close();
}
public static void main(String[] args) throws IOException {
String path = "/Users/shukasakai/Desktop/1.txt";
Read test = new Read();
test.File_reader(path);
}
}
同样的读取1.txt内容,每读到一个字符就向3.txt写一个字符t(t的10进制为116),我们发现最终写了10个字符T,因为1.txt中有一个汉子,一个汉子是3个字节。除了一个汉子外还有9个字符,分别是6个字母和3个换行符。因此一共有10个字符。
FileWrite还可以直接写字符。FileReader也是字节对字符进行操作的,不过读出来的数据还是会显示成unicode的10进制编码格式。
基本上所有的对于字节操作的类都有对应的对于字符操作的类,主要区别为:
- 对于字节进行操作的,可以不用在乎编码,一次只能操作一个字节,相关函数为InputStream/OutputStream的子类。
- 对于字符进行操作,一次操作一个字符,可能同时有多个字节例如操作的字符是一个汉子。操作字符的时候要在意编码。例如Writer/Reader的子类。
16. 序列化
我们先看看基本的序列化与反序列化:
序列化代码:
import java.io.*;
class Employee implements java.io.Serializable{
private static String a = "静态变量";
public String b = "实例变量";
public void pri(){
System.out.println("变量b第值为:"+this.b);
}
}
public class Demo1 {
public static void main(String[] args) {
String path = "/Users/shukasakai/Desktop/employee.ser1";
Employee t2 = new Employee();
try
{
FileOutputStream fileOut =
new FileOutputStream(path);
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(t2);
out.close();
fileOut.close();
System.out.printf("Serialized data is saved in :"+path);
}catch(IOException i)
{
i.printStackTrace();
}
}
}
反序列化代码:
import java.io.*;
public class Unser {
public static void main(String [] args)
{
Employee t2 = new Employee();
try
{
FileInputStream fileIn = new FileInputStream("/Users/shukasakai/Desktop/employee.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
t2 = (Employee) in.readObject();
in.close();
fileIn.close();
}catch(IOException i)
{
i.printStackTrace();
return;
}catch(ClassNotFoundException c)
{
System.out.println("Employee class not found");
c.printStackTrace();
return;
}
System.out.println("Deserialized Employee...");
System.out.println("对象的实例变量:"+t2.b);
t2.pri();
}
}
序列化一般是将一个实例或者说对象序列化成一个字节流文件,默认后缀为.ser。
反序列化是将这个文件中的实例恢复出来,可以理解成执行了创建实例的操作。
反序列化一个ser文件,这个ser文件中的实例的结构必须跟当前文件能访问到的某一个类的结构一样才行。比如将下面的类先实例化,然后将实例序列化成一个123.ser文件:
这时候我们创建第二个文件,来反序列化出123.ser文件中的实例,并调用它的两个函数,第二个文件中有一个类如下,跟我们序列化的对象区别是参数a,b的值不同,函数输出语句中有部分字符不一样,但是函数名与各种参数的类型都跟实例完全匹配:
此时我们发现输出如下:
我们调用反序列化后对象的函数发现,实例的函数变成了当前类中的函数
,但是a,b变量的值还是实例的值
并不是第二个文件中的值。
我们发现,序列化能改变传入类的参数的值,但是不能改变类中的函数。于是我们有可能利用恶意变量结合反序列化来实现恶意炒作。
ac ed 00 05
为java序列化数据的象征。
综上我们可以创造一个含有恶意参数的恶意实例,序列化后传输给执行反序列化的文件让其对它进行解析,然后有可能造成反序列化漏洞。
反序列化的格式要求:
- 被序列化的对象中的数据格式要跟反序列化中的某一个类的数据格式完全相同,函数数量跟函数名还有函数返回值格式也要完全相同,但是函数内容不需要完全相同,甚至被序列化的对象中函数都可以没有函数内容只需要函数名即可,具体的函数内容以反序列化文件中的对应类为准。
- 如果反序列化文件中的对应类有参数有
final/private
关键字类似于public final String a = "asd";
,即使序列化的时候传入参数为public final String a = "asdfasdfasdf";
也不会改变最终a的值,也就是说最终a的值还是asd,一切以反序列化的类中final/private关键字的变量的值为准,但是public/protected关键字的值可以因为序列化的实例的值的不同而改变。
其他
-
@override是为代码,表示重写它之后的函数,如果父类中没有这个函数则会报错。
-
Scanner类接收的数据是inputstream,还有一个编码方式。
更多推荐
所有评论(0)